implicit-0.0.1: Graphics/Implicit/ExtOpenScad/Statements.hs
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Released under the GNU GPL, see LICENSE
-- We'd like to parse openscad code, with some improvements, for backwards compatability.
-- Implement statements for things other than primitive objects!
module Graphics.Implicit.ExtOpenScad.Statements where
import Prelude hiding (lookup)
import Graphics.Implicit.Definitions
import Graphics.Implicit.ExtOpenScad.Definitions
import Graphics.Implicit.ExtOpenScad.Expressions
import Graphics.Implicit.ExtOpenScad.Util
import Graphics.Implicit.ExtOpenScad.Primitives
import qualified Graphics.Implicit.Operations as Op
import Data.Map (Map, lookup, insert)
import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Expr
import Control.Monad (liftM)
tryMany = (foldl1 (<|>)) . (map try)
-- | A statement in our programming openscad-like programming language.
computationStatement :: GenParser Char st ComputationStateModifier
computationStatement =
(try $ do -- suite statemetns: no semicolon...
many space
s <- tryMany [
ifStatement,
forStatement,
unionStatement,
intersectStatement,
differenceStatement,
translateStatement,
rotateStatement,
scaleStatement,
extrudeStatement,
shellStatement
-- rotateExtrudeStatement
]
many space
return s
) <|> (try $ do -- Non suite statements. Semicolon needed...
many space
s <- tryMany [
echoStatement,
assigmentStatement,
includeStatement,
useStatement,
sphere,
cube,
square,
cylinder,
circle,
polygon
]
many space
char ';'
many space
return s
)<|> (many space >> comment)
-- | A suite of statements!
-- What's a suite? Consider:
--
-- union() {
-- sphere(3);
-- }
--
-- The suite was in the braces ({}). Similarily, the
-- following has the same suite:
--
-- union() sphere(3);
--
-- We consider it to be a list of statements which
-- are in tern ComputationStateModifier s.
-- So this parses them.
suite :: GenParser Char st [ComputationStateModifier]
suite = (liftM return computationStatement <|> do
char '{'
many space
stmts <- many (try computationStatement)
many space
char '}'
return stmts
) <?> "statement suite"
-- | Run a list of computations!
-- We start with a state and run it through a bunch of ComputationStateModifier s.
runComputations :: ComputationState -> [ComputationStateModifier] -> ComputationState
runComputations = foldl (\a b -> b $ a)
-- | We think of comments as statements that do nothing. It's just convenient.
comment =
(((try $ do
string "//"
many ( noneOf "\n")
string "\n"
) <|> (do
string "/*"
manyTill anyChar (try $ string "*/")
)) >> return id) <?> "comment"
-- An included statement! Basically, inject another openscad file here...
includeStatement :: GenParser Char st ComputationStateModifier
includeStatement = (do
string "include"
many space
string "<"
filename <- many (noneOf "<>")
string ">"
return $ \ ioWrappedState -> do
state@(varlookup,obj2s,obj3s) <- ioWrappedState;
content <- readFile filename
case parse (many1 computationStatement) "" content of
Left err -> do
putStrLn $ "Error parsing included file " ++ filename
putStrLn $ show err
putStrLn $ "Ignoring included file " ++ filename ++ "..."
return state
Right result -> runComputations (return state) result
) <?> "include statement"
-- In a use statement, variables are imported but we drop any existing 2D/3D objects.
useStatement :: GenParser Char st ComputationStateModifier
useStatement = (do
string "use"
many space
string "<"
filename <- many (noneOf "<>")
string ">"
return $ \ ioWrappedState -> do
state@(varlookup, _, _) <- ioWrappedState;
content <- readFile filename
case parse (many1 computationStatement) "" content of
Left err -> do
putStrLn $ "Error parsing used file " ++ filename
putStrLn $ show err
putStrLn $ "Ignoring used file " ++ filename ++ "..."
return state
Right result -> runComputations (return (varlookup,[],[])) result
) <?> "use statement"
-- | An assignment statement (parser)
assigmentStatement :: GenParser Char st ComputationStateModifier
assigmentStatement =
(try $ do
varSymb <- variableSymb
many space
char '='
many space
valExpr <- expression 0
return $ \ ioWrappedState -> do
(varlookup, obj2s, obj3s) <- ioWrappedState
let
val = valExpr varlookup
return (insert varSymb val varlookup, obj2s, obj3s)
) <|> (try $ do
varSymb <- variableSymb
many space
char '('
many space
argVars <- sepBy variableSymb (many space >> char ',' >> many space)
many space
char ')'
many space
char '='
many space
valExpr <- expression 0
return $ \ ioWrappedState -> do
(varlookup, obj2s, obj3s) <- ioWrappedState
let
makeFunc baseExpr (argVar:xs) varlookup' = OFunc $
\argObj -> makeFunc baseExpr xs (insert argVar argObj varlookup')
makeFunc baseExpr [] varlookup' = baseExpr varlookup'
val = makeFunc valExpr argVars varlookup
return (insert varSymb val varlookup, obj2s, obj3s)
)<?> "assignment statement"
-- | An echo statement (parser)
echoStatement :: GenParser Char st ComputationStateModifier
echoStatement = do
string "echo"
many space
char '('
many space
val <- expression 0
many space
char ')'
return $ \ ioWrappedState -> do
state@(varlookup, _, _) <- ioWrappedState
putStrLn $ show $ val varlookup
return state
ifStatement = (do
string "if"
many space
char '('
bexpr <- expression 0
char ')'
many space
statementsTrueCase <- suite
many space
statementsFalseCase <- try (string "else" >> many space >> suite ) <|> (return [])
return $ \ ioWrappedState -> do
state@(varlookup, _, _) <- ioWrappedState
if case bexpr varlookup of
OBool b -> b
_ -> False
then runComputations (return state) statementsTrueCase
else runComputations (return state) statementsFalseCase
) <?> "if statement"
forStatement = (do
-- a for loop is of the form:
-- for ( vsymb = vexpr ) loopStatements
-- eg. for ( a = [1,2,3] ) {echo(a); echo "lol";}
string "for"
many space
char '('
many space
vsymb <- variableSymb
many space
char '='
vexpr <- expression 0
char ')'
many space
loopStatements <- suite
return $ \ ioWrappedState -> do
-- a for loop unpackages the state from an io monad
state@(varlookup,_,_) <- ioWrappedState;
let
-- each iteration of the loop consists of unpacking the state
loopOnce ::
ComputationState -- ^ The state at this point in the loop
-> OpenscadObj -- ^ The value of vsymb for this iteration
-> ComputationState -- ^ The resulting state
loopOnce ioWrappedState val = do
(varlookup, a, b) <- ioWrappedState;
let
vsymbSetState = return (insert vsymb val varlookup, a, b)
runComputations vsymbSetState loopStatements
-- Then loops once for every entry in vexpr
foldl (loopOnce) (return state) $ case vexpr varlookup of
OList l -> l;
_ -> [];
) <?> "for statement"
moduleWithSuite ::
String -> ([ComputationStateModifier] -> ArgParser ComputationStateModifier)
-> GenParser Char st ComputationStateModifier
moduleWithSuite name argHandeler = (do
string name;
many space;
(unnamed, named) <- moduleArgsUnit
many space;
statements <- suite
return $ \ ioWrappedState -> do
state@(varlookup, obj2s, obj3s) <- ioWrappedState
case argMap
(map ($varlookup) unnamed)
(map (\(a,b) -> (a, b varlookup)) named) (argHandeler statements)
of
Just computationModifier -> computationModifier (return state)
Nothing -> (return state);
) <?> (name ++ " statement")
getAndModUpObj2s :: (Monad m) => [ComputationStateModifier]
-> (Obj2Type -> Obj3Type)
-> m ComputationStateModifier
getAndModUpObj2s suite obj2mod =
return $ \ ioWrappedState -> do
(varlookup, obj2s, obj3s) <- ioWrappedState
(varlookup2, obj2s2, obj3s2) <- runComputations (return (varlookup, [], [])) suite
return
(varlookup2,
obj2s,
obj3s ++ (case obj2s2 of [] -> []; x:xs -> [obj2mod x]) )
getAndCompressSuiteObjs :: (Monad m) => [ComputationStateModifier]
-> ([Obj2Type] -> Obj2Type)
-> ([Obj3Type] -> Obj3Type)
-> m ComputationStateModifier
getAndCompressSuiteObjs suite obj2modifier obj3modifier =
return $ \ ioWrappedState -> do
(varlookup, obj2s, obj3s) <- ioWrappedState
(varlookup2, obj2s2, obj3s2) <- runComputations (return (varlookup, [], [])) suite
return
(varlookup2,
obj2s ++ (case obj2s2 of [] -> []; _ -> [obj2modifier obj2s2]),
obj3s ++ (case obj3s2 of [] -> []; _ -> [obj3modifier obj3s2]) )
getAndTransformSuiteObjs :: (Monad m) => [ComputationStateModifier]
-> (Obj2Type -> Obj2Type)
-> (Obj3Type -> Obj3Type)
-> m ComputationStateModifier
getAndTransformSuiteObjs suite obj2modifier obj3modifier =
return $ \ ioWrappedState -> do
(varlookup, obj2s, obj3s) <- ioWrappedState
(varlookup2, obj2s2, obj3s2) <- runComputations (return (varlookup, [], [])) suite
return
(varlookup2,
obj2s ++ (map obj2modifier obj2s2),
obj3s ++ (map obj3modifier obj3s2) )
unionStatement = moduleWithSuite "union" $ \suite -> do
r <- realArgumentWithDefault "r" 0.0
if r > 0
then getAndCompressSuiteObjs suite (Op.unionR r) (Op.unionR r)
else getAndCompressSuiteObjs suite Op.union Op.union
intersectStatement = moduleWithSuite "intersection" $ \suite -> do
r <- realArgumentWithDefault "r" 0.0
if r > 0
then getAndCompressSuiteObjs suite (Op.intersectR r) (Op.intersectR r)
else getAndCompressSuiteObjs suite Op.intersect Op.intersect
differenceStatement = moduleWithSuite "difference" $ \suite -> do
r <- realArgumentWithDefault "r" 0.0
if r > 0
then getAndCompressSuiteObjs suite (Op.differenceR r) (Op.differenceR r)
else getAndCompressSuiteObjs suite Op.difference Op.difference
translateStatement = moduleWithSuite "translate" $ \suite -> do
v <- argument "v"
case v of
OList ((ONum x):(ONum y):(ONum z):[]) ->
getAndTransformSuiteObjs suite (Op.translate (x,y) ) (Op.translate (x,y,z))
OList ((ONum x):(ONum y):[]) ->
getAndTransformSuiteObjs suite (Op.translate (x,y) ) (Op.translate (x,y,0.0))
OList ((ONum x):[]) ->
getAndTransformSuiteObjs suite (Op.translate (x,0.0) ) (Op.translate (x,0.0,0.0))
ONum x ->
getAndTransformSuiteObjs suite (Op.translate (x,0.0) ) (Op.translate (x,0.0,0.0))
_ -> noChange
-- This is mostly insane
rotateStatement = moduleWithSuite "rotate" $ \suite -> do
a <- argument "a"
case a of
ONum xy -> getAndTransformSuiteObjs suite (Op.rotateXY xy ) (Op.rotate3 (xy, 0, 0) )
OList ((ONum yz):(ONum xz):(ONum xy):[]) ->
getAndTransformSuiteObjs suite (Op.rotateXY xy ) (Op.rotate3 (yz, xz, xy) )
OList ((ONum yz):(ONum xz):[]) ->
getAndTransformSuiteObjs suite (id ) (Op.rotate3 (yz, xz, 0))
OList ((ONum yz):[]) ->
getAndTransformSuiteObjs suite (id) (Op.rotate3 (yz, 0, 0))
_ -> noChange
scaleStatement = moduleWithSuite "scale" $ \suite -> do
v <- argument "v"
case v of
{-OList ((ONum x):(ONum y):(ONum z):[]) ->
getAndTransformSuiteObjs suite (Op.translate (x,y) ) (Op.translate (x,y,z))
OList ((ONum x):(ONum y):[]) ->
getAndTransformSuiteObjs suite (Op.translate (x,y) ) (Op.translate (x,y,0.0))
OList ((ONum x):[]) ->
getAndTransformSuiteObjs suite (Op.translate (x,0.0) ) (Op.translate (x,0.0,0.0)-}
ONum s ->
getAndTransformSuiteObjs suite (Op.scale s) (Op.scale s)
extrudeStatement = moduleWithSuite "linear_extrude" $ \suite -> do
height <- realArgument "height"
center <- boolArgumentWithDefault "center" False
twist <- argumentWithDefault "twist" (ONum 0)
r <- realArgumentWithDefault "r" 0
let
degRotate = (\θ (x,y) -> (x*cos(θ)+y*sin(θ), y*cos(θ)-x*sin(θ))) . (*(2*pi/360))
shiftAsNeeded =
if center
then Op.translate (0,0,-height/2.0)
else id
case twist of
ONum 0 -> getAndModUpObj2s suite (\obj -> shiftAsNeeded $ Op.extrudeR r obj height)
ONum rot ->
getAndModUpObj2s suite (\obj ->
shiftAsNeeded $ Op.extrudeRMod r
(degRotate . (*(rot/height)))
obj height
)
OFunc rotf ->
getAndModUpObj2s suite (\obj ->
shiftAsNeeded $ Op.extrudeRMod r
(\h -> degRotate $ case rotf (ONum h) of
ONum n -> n
_ -> 0
) obj height
)
{-rotateExtrudeStatement = moduleWithSuite "rotate_extrude" $ \suite -> do
h <- realArgument "h"
center <- boolArgumentWithDefault "center" False
twist <- realArgumentWithDefault 0.0
r <- realArgumentWithDefault "r" 0.0
getAndModUpObj2s suite (\obj -> Op.extrudeRMod r (\θ (x,y) -> (x*cos(θ)+y*sin(θ), y*cos(θ)-x*sin(θ)) ) obj h)
-}
shellStatement = moduleWithSuite "shell" $ \suite -> do
w <- realArgumentWithDefault "w" 0.0
getAndTransformSuiteObjs suite (Op.shell w) (Op.shell w)