forked from Haskell-Things/ImplicitCAD
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Primitives.hs
392 lines (310 loc) · 12.9 KB
/
Primitives.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
-- 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.
-- This file provides primitive objects for the openscad parser.
-- The code is fairly straightforward; an explanation of how
-- the first one works is provided.
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances, ScopedTypeVariables #-}
module Graphics.Implicit.ExtOpenScad.Primitives (primitives) where
import Graphics.Implicit.Definitions
import Graphics.Implicit.ExtOpenScad.Definitions
import Graphics.Implicit.ExtOpenScad.Util
import Graphics.Implicit.ExtOpenScad.Util.ArgParser
import Graphics.Implicit.ExtOpenScad.Util.Computation
import qualified Graphics.Implicit.Primitives as Prim
import Data.Maybe (fromMaybe, isNothing)
import qualified Data.Either as Either
import qualified Graphics.Implicit.SaneOperators as S
primitives :: [(String, [ComputationStateModifier] -> ArgParser ComputationStateModifier)]
primitives = [ sphere, cube, square, cylinder, circle, polygon, union, difference, intersect, translate, scale, rotate, extrude, pack, shell, rotateExtrude ]
moduleWithSuite name modArgMapper = (name, modArgMapper)
moduleWithoutSuite name modArgMapper = (name, \suite -> modArgMapper)
-- **Exmaple of implementing a module**
-- sphere is a module without a suite named sphere,
-- this means that the parser will look for this like
-- sphere(args...);
sphere = moduleWithoutSuite "sphere" $ do
example "sphere(3);"
example "sphere(r=5);"
-- What are the arguments?
-- The radius, r, which is a (real) number.
-- Because we don't provide a default, this ends right
-- here if it doesn't get a suitable argument!
r :: ℝ <- argument "r"
`doc` "radius of the sphere"
-- So what does this module do?
-- It adds a 3D object, a sphere of radius r,
-- using the sphere implementation in Prim
-- (Graphics.Implicit.Primitives)
addObj3 $ Prim.sphere r
cube = moduleWithoutSuite "cube" $ do
-- examples
example "cube(size = [2,3,4], center = true, r = 0.5);"
example "cube(4);"
-- arguments
size :: Either ℝ ℝ3 <- argument "size"
`doc` "cube size"
center :: Bool <- argument "center"
`doc` "should center?"
`defaultTo` False
r :: ℝ <- argument "r"
`doc` "radius of rounding"
`defaultTo` 0
-- Tests
test "cube(4);"
`eulerCharacteristic` 2
test "cube(size=[2,3,4]);"
`eulerCharacteristic` 2
-- A helper function for making rect3's accounting for centerdness
let rect3 x y z =
if center
then Prim.rect3R r (-x/2, -y/2, -z/2) (x/2, y/2, z/2)
else Prim.rect3R r (0, 0, 0) (x, y, z)
case size of
Right (x,y,z) -> addObj3 $ rect3 x y z
Left w -> addObj3 $ rect3 w w w
square = moduleWithoutSuite "square" $ do
-- examples
example "square(size = [3,4], center = true, r = 0.5);"
example "square(4);"
-- arguments
size :: Either ℝ ℝ2 <- argument "size"
`doc` "square size"
center :: Bool <- argument "center"
`doc` "should center?"
`defaultTo` False
r :: ℝ <- argument "r"
`doc` "radius of rounding"
`defaultTo` 0
-- Tests
test "square(2);"
`eulerCharacteristic` 0
test "square(size=[2,3]);"
`eulerCharacteristic` 0
-- A helper function for making rect2's accounting for centerdness
let rect x y =
if center
then Prim.rectR r (-x/2, -y/2) (x/2, y/2)
else Prim.rectR r ( 0, 0 ) ( x, y )
-- caseOType matches depending on whether size can be coerced into
-- the right object. See Graphics.Implicit.ExtOpenScad.Util
case size of
Left w -> addObj2 $ rect w w
Right (x,y) -> addObj2 $ rect x y
cylinder = moduleWithoutSuite "cylinder" $ do
example "cylinder(r=10, h=30, center=true);"
example "cylinder(r1=4, r2=6, h=10);"
example "cylinder(r=5, h=10, $fn = 6);"
-- arguments
r :: ℝ <- argument "r"
`defaultTo` 1
`doc` "radius of cylinder"
h :: ℝ <- argument "h"
`defaultTo` 1
`doc` "height of cylinder"
r1 :: ℝ <- argument "r1"
`defaultTo` 1
`doc` "bottom radius; overrides r"
r2 :: ℝ <- argument "r2"
`defaultTo` 1
`doc` "top radius; overrides r"
fn :: ℕ <- argument "$fn"
`defaultTo` (-1)
`doc` "number of sides, for making prisms"
center :: Bool <- argument "center"
`defaultTo` False
`doc` "center cylinder with respect to z?"
-- Tests
test "cylinder(r=10, h=30, center=true);"
`eulerCharacteristic` 0
test "cylinder(r=5, h=10, $fn = 6);"
`eulerCharacteristic` 0
-- The result is a computation state modifier that adds a 3D object,
-- based on the args.
addObj3 $ if r1 == 1 && r2 == 1
then let
obj2 = if fn < 0 then Prim.circle r else Prim.polygonR 0 $
let sides = fromIntegral fn
in [(r*cos θ, r*sin θ )| θ <- [2*pi*n/sides | n <- [0.0 .. sides - 1.0]]]
obj3 = Prim.extrudeR 0 obj2 h
in if center
then Prim.translate (0,0,-h/2) obj3
else obj3
else if center
then Prim.translate (0,0,-h/2) $ Prim.cylinder2 r1 r2 h
else Prim.cylinder2 r1 r2 h
circle = moduleWithoutSuite "circle" $ do
example "circle(r=10); // circle"
example "circle(r=5, $fn=6); //hexagon"
-- Arguments
r :: ℝ <- argument "r"
`doc` "radius of the circle"
fn :: ℕ <- argument "$fn"
`doc` "if defined, makes a regular polygon with n sides instead of a circle"
`defaultTo` (-1)
test "circle(r=10);"
`eulerCharacteristic` 0
if fn < 3
then addObj2 $ Prim.circle r
else addObj2 $ Prim.polygonR 0 $
let sides = fromIntegral fn
in [(r*cos θ, r*sin θ )| θ <- [2*pi*n/sides | n <- [0.0 .. sides - 1.0]]]
polygon = moduleWithoutSuite "polygon" $ do
example "polygon ([(0,0), (0,10), (10,0)]);"
points :: [ℝ2] <- argument "points"
`doc` "vertices of the polygon"
paths :: [ℕ ] <- argument "paths"
`doc` "order to go through vertices; ignored for now"
`defaultTo` []
r :: ℝ <- argument "r"
`doc` "rounding of the polygon corners; ignored for now"
`defaultTo` 0
case paths of
[] -> addObj2 $ Prim.polygonR 0 points
_ -> noChange;
union = moduleWithSuite "union" $ \suite -> do
r :: ℝ <- argument "r"
`defaultTo` 0.0
`doc` "Radius of rounding for the union interface"
if r > 0
then getAndCompressSuiteObjs suite (Prim.unionR r) (Prim.unionR r)
else getAndCompressSuiteObjs suite Prim.union Prim.union
intersect = moduleWithSuite "intersection" $ \suite -> do
r :: ℝ <- argument "r"
`defaultTo` 0.0
`doc` "Radius of rounding for the intersection interface"
if r > 0
then getAndCompressSuiteObjs suite (Prim.intersectR r) (Prim.intersectR r)
else getAndCompressSuiteObjs suite Prim.intersect Prim.intersect
difference = moduleWithSuite "difference" $ \suite -> do
r :: ℝ <- argument "r"
`defaultTo` 0.0
`doc` "Radius of rounding for the difference interface"
if r > 0
then getAndCompressSuiteObjs suite (Prim.differenceR r) (Prim.differenceR r)
else getAndCompressSuiteObjs suite Prim.difference Prim.difference
translate = moduleWithSuite "translate" $ \suite -> do
example "translate ([2,3]) circle (4);"
example "translate ([5,6,7]) sphere(5);"
v :: Either ℝ (Either ℝ2 ℝ3) <- argument "v"
`doc` "vector to translate by"
let
translateObjs shift2 shift3 =
getAndTransformSuiteObjs suite (Prim.translate shift2) (Prim.translate shift3)
case v of
Left x -> translateObjs (x,0) (x,0,0)
Right (Left (x,y)) -> translateObjs (x,y) (x,y,0.0)
Right (Right (x,y,z)) -> translateObjs (x,y) (x,y,z)
deg2rad x = x / 180.0 * pi
-- This is mostly insane
rotate = moduleWithSuite "rotate" $ \suite -> do
a <- argument "a"
`doc` "value to rotate by; angle or list of angles"
-- caseOType matches depending on whether size can be coerced into
-- the right object. See Graphics.Implicit.ExtOpenScad.Util
-- Entries must be joined with the operator <||>
-- Final entry must be fall through.
caseOType a $
( \xy ->
getAndTransformSuiteObjs suite (Prim.rotate $ deg2rad xy ) (Prim.rotate3 (deg2rad xy, 0, 0) )
) <||> ( \(yz,xy,xz) ->
getAndTransformSuiteObjs suite (Prim.rotate $ deg2rad xy ) (Prim.rotate3 (deg2rad yz, deg2rad xz, deg2rad xy) )
) <||> ( \(yz,xz) ->
getAndTransformSuiteObjs suite (id ) (Prim.rotate3 (deg2rad yz, deg2rad xz, 0))
) <||> ( \_ -> noChange )
scale = moduleWithSuite "scale" $ \suite -> do
example "scale(2) square(5);"
example "scale([2,3]) square(5);"
example "scale([2,3,4]) cube(5);"
v :: Either ℝ (Either ℝ2 ℝ3) <- argument "v"
`doc` "vector or scalar to scale by"
let
scaleObjs strech2 strech3 =
getAndTransformSuiteObjs suite (Prim.scale strech2) (Prim.scale strech3)
case v of
Left x -> scaleObjs (x,0) (x,0,0)
Right (Left (x,y)) -> scaleObjs (x,y) (x,y,0.0)
Right (Right (x,y,z)) -> scaleObjs (x,y) (x,y,z)
extrude = moduleWithSuite "linear_extrude" $ \suite -> do
example "linear_extrude(10) square(5);"
height :: Either ℝ (ℝ -> ℝ -> ℝ) <- argument "height" `defaultTo` (Left 1)
`doc` "height to extrude to..."
center :: Bool <- argument "center" `defaultTo` False
`doc` "center? (the z component)"
twist :: Maybe (Either ℝ (ℝ -> ℝ)) <- argument "twist" `defaultTo` Nothing
`doc` "twist as we extrude, either a total amount to twist or a function..."
scale :: Maybe (Either ℝ (ℝ -> ℝ)) <- argument "scale" `defaultTo` Nothing
`doc` "scale according to this funciton as we extrud..."
translate :: Maybe (Either ℝ2 (ℝ -> ℝ2)) <- argument "translate" `defaultTo` Nothing
`doc` "translate according to this funciton as we extrude..."
r :: ℝ <- argument "r" `defaultTo` 0
`doc` "round the top?"
let
degRotate = (\θ (x,y) -> (x*cos(θ)+y*sin(θ), y*cos(θ)-x*sin(θ))) . (*(2*pi/360))
heightn = case height of
Left h -> h
Right f -> f 0 0
height' = case height of
Right f -> Right $ uncurry f
Left a -> Left a
shiftAsNeeded =
if center
then Prim.translate (0,0,-heightn/2.0)
else id
funcify :: S.Multiplicative ℝ a a => Either a (ℝ -> a) -> ℝ -> a
funcify (Left val) h = (h/heightn) S.* val
funcify (Right f ) h = f h
twist' = fmap funcify twist
scale' = fmap funcify scale
translate' = fmap funcify translate
getAndModUpObj2s suite $ \obj -> case height of
Left constHeight | isNothing twist && isNothing scale && isNothing translate ->
shiftAsNeeded $ Prim.extrudeR r obj constHeight
_ ->
shiftAsNeeded $ Prim.extrudeRM r twist' scale' translate' obj height'
rotateExtrude = moduleWithSuite "rotate_extrude" $ \suite -> do
example "rotate_extrude() translate(20) circle(10);"
totalRot :: ℝ <- argument "a" `defaultTo` 360
`doc` "angle to sweep"
r :: ℝ <- argument "r" `defaultTo` 0
translate :: Either ℝ2 (ℝ -> ℝ2) <- argument "translate" `defaultTo` Left (0,0)
let
n = fromIntegral $ round $ totalRot / 360
cap = (360*n /= totalRot)
|| (Either.either ( /= (0,0)) (\f -> f 0 /= f totalRot) ) translate
capM = if cap then Just r else Nothing
getAndModUpObj2s suite $ \obj -> Prim.rotateExtrude totalRot capM translate obj
{-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 -> Prim.extrudeRMod r (\θ (x,y) -> (x*cos(θ)+y*sin(θ), y*cos(θ)-x*sin(θ)) ) obj h)
-}
shell = moduleWithSuite "shell" $ \suite -> do
w :: ℝ <- argument "w"
`doc` "width of the shell..."
getAndTransformSuiteObjs suite (Prim.shell w) (Prim.shell w)
-- Not a perenant solution! Breaks if can't pack.
pack = moduleWithSuite "pack" $ \suite -> do
example "pack ([45,45], sep=2) { circle(10); circle(10); circle(10); circle(10); }"
-- arguments
size :: ℝ2 <- argument "size"
`doc` "size of 2D box to pack objects within"
sep :: ℝ <- argument "sep"
`doc` "mandetory space between objects"
-- The actual work...
return $ \ ioWrappedState -> do
(varlookup, obj2s, obj3s) <- ioWrappedState
(varlookup2, obj2s2, obj3s2) <- runComputations (return (varlookup, [], [])) suite
if not $ null obj3s2
then case Prim.pack3 size sep obj3s2 of
Just solution -> return (varlookup2, obj2s, obj3s ++ [solution] )
Nothing -> do
putStrLn "Can't pack given objects in given box with present algorithm"
return (varlookup2, obj2s, obj3s)
else case Prim.pack2 size sep obj2s2 of
Just solution -> return (varlookup2, obj2s ++ [solution], obj3s)
Nothing -> do
putStrLn "Can't pack given objects in given box with present algorithm"
return (varlookup2, obj2s, obj3s)