summaryrefslogtreecommitdiff
path: root/openscad/Graphics/OpenSCAD.hs
diff options
context:
space:
mode:
Diffstat (limited to 'openscad/Graphics/OpenSCAD.hs')
-rw-r--r--openscad/Graphics/OpenSCAD.hs638
1 files changed, 638 insertions, 0 deletions
diff --git a/openscad/Graphics/OpenSCAD.hs b/openscad/Graphics/OpenSCAD.hs
new file mode 100644
index 0000000..e4efe7b
--- /dev/null
+++ b/openscad/Graphics/OpenSCAD.hs
@@ -0,0 +1,638 @@
1{-# LANGUAGE FlexibleContexts #-}
2{-# LANGUAGE FlexibleInstances #-}
3
4{- |
5Module : Graphics.OpenSCAD
6Description : Type-checked wrappers for the OpenSCAD primitives.
7Copyright : © Mike Meyer, 2014
8License : BSD4
9Maintainer : mwm@mired.org
10Stability : experimental
11
12= Overview
13
14The Graphics.OpenSCAD module provides abstract data types for creating
15OpenSCAD model definitions calls, along with a function to render it
16as a string, and some utilities. The primary goal is that the output
17should always be valid OpenSCAD. If you manage to generate OpenSCAD
18source that causes OpenSCAD to complain, please open an issue.
19
20The primary effect of this is that Graphics.OpenSCAD distinguishes
21between 2d and 3d 'Model's. If you want to mix them, you must
22explicitly convert between them. While two-dimensional model creation
23could be polymorphic functions that create either, so that such models
24could be treated as either 2d or 3d, you'd still have to explicitly
25convert models whose type was fixed as 2d by a transformation, and
26'render' wouldn't work if the type was still ambiguous, ala @render $
27square 2@.
28
29= Usage
30
31Standard usage is to have a @main@ function that looks like:
32
33@
34main = draw $ /Solid/
35@
36or
37@
38main = drawL $ [/Solid/]
39@
40
41and then set your IDE's compile command to use @runhaskell@ or
42equivalent to run your code and send the output to a .scad file. Open
43that file in OpenSCAD, and set it to automatically reload if the file
44changes. Recompiling your program will cause the model to be loaded
45and displayed by OpenSCAD.
46
47The type constructors are not exported, with functions being exported
48in their stead. This allows extra checking to be done on those that
49need it. It also provides consistency, as otherwise you'd have to
50remember whether 'box' is a constructor or a convenience function,
51etc.
52
53Because of this, the constructors are not documented, the exported
54functions are. The documentation is generally just the corresponding
55OpenSCAD function name, along with the names of the arguments from the
56OpenSCAD documentation. If no OpenSCAD function name is given, then
57it's the same as the 'Graphics.OpenSCAD' function. You should check
58the OpenSCAD documentation for usage information.
59
60= Oddities
61
62'importFile' has been left polymorphic. I couldn't find a sane way to
63check that you're importing the right file type, so detecting such
64errors - including importing a 3d file and trying to extrude it - have
65to be left up to OpenSCAD in any case. So for now, there's just
66'importFile'. This does create the oddity that if you import a file
67and try and render it without doing something to indicate how many
68dimensions it has (one of the transformations, an extrusion or
69projection, or 'solid') you'll get a compile error because the type is
70ambiguous. Later, this may turn into @import2d@ and @import3d@.
71
72The interfaces for 'polygon's and 'polyhedron's is seriously different
73from the OpenSCAD interface. Rather than expecting you to enter a list
74of points and then references to them, you just enter the points
75directly. If you really want to do it the OpenSCAD way, you can do
76something like:
77
78@
79draw $ polyhedron [[(p 0, p 1, p 2), (p 0, p 2, p 3), ... ]]
80where points = [.....]
81 p i = points !! i
82@
83
84Also, the OpenSCAD polyedron code recently changed. The old version
85requires that the faces all be triangles, the new version allows for
86them to be arbitrary polygons. 'Graphics.OpenSCAD' supports both: if
87all your faces are triangles, it will use the old version. If some
88have more points, the new version will be used. If any have fewer than
89three points you get an error. At this time, no tests are done on the
90faces. That will probably change in the future.
91
92Finally, polygon and polyhedron can generate errors on input that
93seems to generate proper solids. If you turn on 'View->Thrown
94Together', you'll see it highlighting errors in the object.
95
96Offset is missing even though it's documented, as it isn't supported
97by a released version of OpenSCAD, so presumably subject to change. It
98is implemented, but untested as yet. You can add it to the module's
99export lists if you want to play with it.
100
101-}
102
103module Graphics.OpenSCAD (
104 -- * Types
105 -- ** A 'Model' to be rendered, and a 'Vector' that fixes the
106 -- number of dimensions it has.
107 Model, Vector,
108 -- ** Types aliases with fixed dimensions
109 Model2d, Model3d, Vector2d, Vector3d,
110 -- ** Other type aliases
111 Facet, TransMatrix,
112 -- ** Type for 'unsafePolyhedron' 'Sides' argument
113 Sides(..),
114 -- * Primitive creation
115 -- ** 'Model2d's
116 rectangle, square, circle, polygon, unsafePolygon, projection, importFile,
117 -- ** 'Model3d's
118 sphere, box, cube, cylinder, obCylinder, polyhedron, unsafePolyhedron,
119 multMatrix, linearExtrude, rotateExtrude, surface, solid,
120 -- * Functions
121 -- ** Combinations
122 union, intersection, difference, minkowski, hull,
123 -- ** Transformations
124 scale, resize, rotate, translate, mirror, color, transparent, up,
125 -- ** Rendering
126 render, renderL,
127 -- ** 'Facet's.
128 var, fn, fs, fa, def,
129 -- ** General convenience functions
130 diam, draw, drawL, (#),
131 module Colours)
132
133where
134
135import Data.Colour (Colour, AlphaColour, alphaChannel, darken, over, black)
136import Data.Colour.Names as Colours
137import Data.Colour.SRGB (channelRed, channelBlue, channelGreen, toSRGB)
138import Data.List (elemIndices, nub, intercalate)
139import qualified Data.List.NonEmpty as NE
140import Data.Semigroup (Semigroup((<>), sconcat))
141import qualified Data.Set as Set
142import System.FilePath (FilePath)
143
144-- A vector in 2 or 3-space. They are used in transformations of
145-- 'Model's of their type.
146class Eq a => Vector a where
147 rVector :: a -> String
148 toList :: a -> [Double]
149 (#*) :: a -> a -> a -- cross product
150 (#-) :: a -> a -> a -- difference between two vectors
151
152 (#.) :: a -> a -> Double -- dot product
153 v1 #. v2 = sum $ zipWith (*) (toList v1) (toList v2)
154
155 isZero :: a -> Bool -- is a zero vector. Arguably should use eps.
156 isZero = all (== 0) . toList
157
158 collinear :: [a] -> Bool -- are all points collinear?
159 collinear [] = False
160 collinear [_] = False
161 collinear [v1, v2] = v1 /= v2
162 collinear (v1:v2:vs)
163 | v1 /= v2 = all (\v -> isZero $ (v2 #- v1) #* (v1 #- v)) vs
164 | otherwise = collinear (v2:vs)
165
166-- | 'Vector2d' is used where 'Graphics.OpenSCAD' expects an OpenSCAD
167-- @vector@ of length 2.
168type Vector2d = (Double, Double)
169instance Vector Vector2d where
170 rVector (x, y) = "[" ++ show x ++ "," ++ show y ++ "]"
171 toList (x, y) = [x, y]
172 (x1, y1) #- (x2, y2) = (x1 - x2, y1 - y2)
173 (x1, y1) #* (x2, y2) = (0, x1 * y2 - y1 * x2) -- for purposes of collinear
174
175-- | 'Vector3d' is used where 'Graphics.OpenSCAD' expects an OpenSCAD
176-- @vector@ of length 3.
177type Vector3d = (Double, Double, Double)
178instance Vector Vector3d where
179 rVector (x, y, z) = "[" ++ show x ++ "," ++ show y ++ "," ++ show z ++ "]"
180 toList (x, y, z) = [x, y, z]
181 (x1, y1, z1) #- (x2, y2, z2) = (x1 - x2, y1 - y2, z1 - z2)
182 (x1, y1, z1) #* (x2, y2, z2) = (y1 * z2 - z1 * y2,
183 z1 * x2 - x1 * z2,
184 x1 * y2 - y1 * x2)
185
186-- Coplanar only makes sense for R3, so it's not part of the Vector class
187coplanar :: [Vector3d] -> Bool
188coplanar vs | length vs <= 3 = True -- by definition
189 | collinear $ take 3 vs = coplanar $ tail vs
190 | otherwise =
191 all (\v -> (v3 #- v1) #. ((v2 #- v1) #* (v #- v3)) == 0) vs'
192 where (v1:v2:v3:vs') = vs
193
194
195-- | A 4x4 transformation matrix specifying a complete 3-space
196-- transform of a 'Model3d'.
197type TransMatrix =
198 ((Double, Double, Double, Double), (Double, Double, Double, Double),
199 (Double, Double, Double, Double), (Double, Double, Double, Double))
200
201
202-- While it's tempting to add more options to Solid, Shape or Model,
203-- don't do it. Instead, add functions that add that functionality,
204-- by building the appropriate structure, like cube vs. box.
205
206-- | A 'Facet' is used to set one of the special variables that
207-- control the mesh used during generation of circular objects. They
208-- appear as arguments to various constructors, as well as in the
209-- 'var' function to set them for the argument objects.
210data Facet = Fa Double | Fs Double | Fn Int | Def deriving Show
211
212-- | A 'Join' controls how edges in a 'polygon' are joined by the
213-- 'offset' operation.
214data Join = Bevel | Round | Miter Double deriving Show
215
216-- A 'Shape' is a 2-dimensional primitive to be used in a 'Model2d'.
217data Shape = Rectangle Double Double
218 | Circle Double Facet
219 | Polygon Int [Vector2d] [[Int]]
220 | Projection Bool Model3d
221 | Offset Double Join Shape
222 deriving Show
223
224-- | The third argument to unsafePolyhedron is a 'Sides'.
225data Sides = Faces [[Int]] | Triangles [[Int]] deriving Show
226
227-- A 'Solid' is a 3-dimensional primitive to be used in a 'Model3d'.
228data Solid = Sphere Double Facet
229 | Box Double Double Double
230 | Cylinder Double Double Facet
231 | ObCylinder Double Double Double Facet
232 | Polyhedron Int [Vector3d] Sides
233 | MultMatrix TransMatrix Model3d
234 | LinearExtrude Double Double Vector2d Int Int Facet Model2d
235 | RotateExtrude Int Facet Model2d
236 | Surface FilePath Bool Int
237 | ToSolid Model2d
238 deriving Show
239
240-- | A 'Model' is either a 'Model2d', a 'Model3d', a transformation of
241-- a 'Model', a combination of 'Model's, or a 'Model' with it's
242-- rendering tweaked by a 'Facet'. 'Model's can be rendered.
243data Model v = Shape Shape
244 | Solid Solid
245 | Scale v (Model v)
246 | Resize v (Model v)
247 | Rotate v (Model v)
248 | Translate v (Model v)
249 | Mirror v (Model v)
250 | Color (Colour Double) (Model v)
251 | Transparent (AlphaColour Double) (Model v)
252 -- and combinations
253 | Union [Model v]
254 | Intersection [Model v]
255 | Minkowski [Model v]
256 | Hull [Model v]
257 | Difference (Model v) (Model v)
258 -- And oddball stuff control
259 | Import FilePath
260 | Var Facet [Model v]
261 deriving Show
262
263-- | A two-dimensional model. Note that the types do not mix
264-- implicitly. You must turn a 'Model2d' into a 'Model3d' using one of
265-- 'linearExtrude', 'rotateExtrude', or 'solid'.
266type Model2d = Model Vector2d
267
268-- | A three-dimensional model. You can create a 'Model2d' from a
269-- 'Model3d' using 'projection'.
270type Model3d = Model Vector3d
271
272-- Tools for creating 'Model2d's.
273-- | Create a rectangular 'Model2d' with @rectangle /x-size y-size/@.
274rectangle :: Double -> Double -> Model2d
275rectangle w h = Shape $ Rectangle w h
276
277-- | 'square' is a 'rectangle' with both sides the same size.
278square :: Double -> Model2d
279square s = rectangle s s
280
281-- | Create a circular 'Model' with @circle /radius/ 'Facet'@.
282circle :: Double -> Facet -> Model2d
283circle r f = Shape $ Circle r f
284
285-- | Project a 'Model3d' into a 'Model' with @projection /cut 'Model3d'/@.
286projection :: Bool -> Model3d -> Model2d
287projection c s = Shape $ Projection c s
288
289-- | Turn a list of lists of 'Vector2d's and an Int into @polygon
290-- /convexity points path/@. The argument to polygon is the list of
291-- paths that is the second argument to the OpenSCAD polygon function,
292-- except the points are 'Vector2d's, not references to 'Vector2d's in
293-- that functions points argument. If you were just going to pass in
294-- the points, it now needs to be in an extra level of 'List'.
295polygon :: Int -> [[Vector2d]] -> Model2d
296polygon convexity paths
297 | any ((< 3) . length) paths = error "Polygon has fewer than 3 points."
298 | any collinear paths = error "Points in polygon are collinear."
299 | otherwise = let points = nub $ concat paths
300 in Shape . Polygon convexity points
301 $ map (concatMap (`elemIndices` points)) paths
302
303-- | This provides direct access to the OpenScad @polygon@ command for
304-- performance reasons. This version uses the OpenSCAD arguments:
305-- @polygon /convexity points path/@ to allow client code to save
306-- space. However, it bypasses all the checks done by
307-- 'polygon', which need the other representation.
308unsafePolygon :: Int -> [Vector2d] -> [[Int]] -> Model2d
309unsafePolygon convexity points paths = Shape $ Polygon convexity points paths
310
311-- | 'offset' a 'Model2d's edges by @offset /delta join/@.
312offset :: Double -> Join -> Model2d -> Model2d
313offset d j (Shape s) = Shape $ Offset d j s
314
315-- Tools for creating Model3ds
316-- | Create a sphere with @sphere /radius 'Facet'/@.
317sphere :: Double -> Facet -> Model3d
318sphere r f = Solid $ Sphere r f
319
320-- | Create a box with @cube /x-size y-size z-size/@
321box :: Double -> Double -> Double -> Model3d
322box x y z = Solid $ Box x y z
323
324-- | A convenience function for creating a cube as a 'box' with all
325-- sides the same length.
326cube :: Double -> Model3d
327cube x = box x x x
328
329-- | Create a cylinder with @cylinder /radius height 'Facet'/@.
330cylinder :: Double -> Double -> Facet -> Model3d
331cylinder h r f = Solid $ Cylinder h r f
332
333-- | Create an oblique cylinder with @cylinder /radius1 height radius2 'Facet'/@.
334obCylinder :: Double -> Double -> Double -> Facet -> Model Vector3d
335obCylinder r1 h r2 f= Solid $ ObCylinder r1 h r2 f
336
337-- | Turn a list of list of 'Vector3d's and an int into @polyhedron
338-- /convexity points 'Sides'/@. The argument to polyhedron is the list
339-- of paths that is the second argument to the OpenSCAD polyhedron
340-- function, except the points are 'Vector3d's, not the references to
341-- 'Vector3d's used in that functions @points@ argument. The function
342-- will build the appropriate function call, using @faces@ if you pass
343-- in a side that uses more than 3 points, or @triangles@ if not. Note
344-- that @faces@ doesn't work in older versions of OpenSCAD, and
345-- @triangles@ is depreciated. Until a mechanism to set the version of
346-- OpenSCAD is provided, generating the @faces@ version will cause an
347-- error.
348--
349-- Passing in 'Sides' that have fewer than three points, have
350-- collinear points or have points that aren't in the same plane is an
351-- error that is caught by the library.
352polyhedron :: Int -> [[Vector3d]] -> Model3d
353polyhedron convexity paths
354 | any ((< 3) . length) paths = error "Some face has fewer than 3 points."
355 | any collinear paths = error "Some face has collinear points."
356 | any (not . coplanar) paths = error "Some face isn't coplanar."
357 | length vectors /= length (nub vectors) =
358 error "Some faces have different orientation."
359 | 2 * length edges /= length vectors = error "Some edges are not in two faces."
360 | xCross headMax xMax tailMax > 0 =
361 error "Face orientations are counterclockwise."
362 | otherwise = Solid . Polyhedron convexity points $ sides sidesIn
363 where vectors = concatMap (\p -> zip p (tail p ++ [head p])) paths
364 edges = nub $ map (Set.fromList . \(a, b) -> [a, b]) vectors
365 points = nub $ concat paths
366 xMax = maximum points
367 faceMax = head $ filter (elem xMax) paths
368 (maxFirst, maxLast) = break (== xMax) faceMax
369 (headMax, tailMax) = (if null maxFirst
370 then last maxLast
371 else last maxFirst,
372 if null (tail maxLast)
373 then head maxFirst
374 else head (tail maxLast))
375 xCross a b c = (\(a, b, c) -> a) $ (a #- b) #* (b #- c)
376 sidesIn = map (concatMap (`elemIndices` points)) paths
377 sides ss | any ((> 3) . length) ss = Faces ss
378 | all ((== 3) . length) ss = Triangles ss
379 | otherwise = error "Some faces have fewer than 3 points."
380
381-- | This provides direct access to the OpenSCAD @polyhedron@ command
382-- for performance reasons. This version uses the OpenSCAD arguments:
383-- @polyhedron /convexity points 'Sides'/@ to allow client code to
384-- save space. However, it bypasses all the checks done by
385-- 'polyhedron', which needs the other representation.
386unsafePolyhedron :: Int -> [Vector3d] -> Sides -> Model3d
387unsafePolyhedron convexity points sides = Solid $ Polyhedron convexity points sides
388
389
390-- | Transform a 'Model3d' with a 'TransMatrix'
391multMatrix :: TransMatrix -> Model3d -> Model3d
392multMatrix t m = Solid $ MultMatrix t m
393
394-- | Turn a 'Model2d' into a 'Model3d' exactly as is.
395solid :: Model2d -> Model3d
396solid = Solid . ToSolid
397
398-- | Extrude a 'Model2d' along a line with @linear_extrude@.
399linearExtrude :: Double -- ^ height
400 -> Double -- ^ twist
401 -> Vector2d -- ^ scale
402 -> Int -- ^ slices
403 -> Int -- ^ convexity
404 -> Facet
405 -> Model2d -- ^ to extrude
406 -> Model3d
407linearExtrude h t sc sl c f m = Solid $ LinearExtrude h t sc sl c f m
408
409-- | Rotate a 'Model2d' around the origin with @rotate_extrude
410-- /convexity 'Facet' 'Model'/@
411rotateExtrude :: Int -> Facet -> Model2d -> Model3d
412rotateExtrude c f m = Solid $ RotateExtrude c f m
413
414-- | Load a height map from a file with @surface /FilePath Invert Convexity/@.
415surface :: FilePath -> Bool -> Int -> Model3d
416surface f i c = Solid $ Surface f i c
417
418-- And the one polymorphic function we have.
419-- | 'importFile' is @import /filename/@.
420importFile :: Vector v => FilePath -> Model v
421importFile = Import
422
423
424-- Transformations
425-- | Scale a 'Model', the vector specifying the scale factor for each axis.
426scale :: Vector v => v -> Model v -> Model v
427scale = Scale
428
429-- | Resize a 'Model' to occupy the dimensions given by the vector. Note that
430-- this does nothing prior to the 2014 versions of OpenSCAD.
431resize :: Vector v => v -> Model v -> Model v
432resize = Resize
433
434-- | Rotate a 'Model' by different amounts around each of the three axis.
435rotate :: Vector v => v -> Model v -> Model v
436rotate = Rotate
437
438-- | Translate a 'Model' along a 'Vector'.
439translate :: Vector v => v -> Model v -> Model v
440translate = Translate
441
442-- | Mirror a 'Model' across a plane intersecting the origin.
443mirror :: Vector v => v -> Model v -> Model v
444mirror = Mirror
445
446-- | Render a 'Model' in a specific color. This doesn't use the
447-- OpenSCAD color model, but instead uses the 'Data.Colour' model. The
448-- 'Graphics.OpenSCAD' module rexports 'Data.Colour.Names' so you can
449-- conveniently say @'color' 'red' /'Model'/@.
450color :: Vector v => Colour Double -> Model v -> Model v
451color = Color
452
453-- | Render a 'Model' in a transparent color. This uses the
454-- 'Data.Colour.AlphaColour' color model.
455transparent :: Vector v => AlphaColour Double -> Model v -> Model v
456transparent = Transparent
457
458-- | A 'translate' that just goes up, since those seem to be common.
459up :: Double -> Model3d -> Model3d
460up f = translate (0, 0, f)
461
462
463-- Combinations
464-- | Create the union of a list of 'Model's.
465union :: Vector v => [Model v] -> Model v
466union = Union
467
468-- | Create the intersection of a list of 'Model's.
469intersection :: Vector v => [Model v] -> Model v
470intersection = Intersection
471
472-- | The difference between two 'Model's.
473difference :: Vector v => Model v -> Model v -> Model v
474difference = Difference
475
476-- | The Minkowski sum of a list of 'Model's.
477minkowski :: Vector v => [Model v] -> Model v
478minkowski = Minkowski
479
480-- | The convex hull of a list of 'Model's.
481hull :: Vector v => [Model v] -> Model v
482hull = Hull
483
484
485-- | 'render' does all the real work. It will walk the AST for a 'Model',
486-- returning an OpenSCAD program in a 'String'.
487render :: Vector v => Model v -> String
488render (Shape s) = rShape s
489render (Solid s) = rSolid s
490render (Union ss) = rList "union()" ss
491render (Intersection ss) = rList "intersection()" ss
492render (Difference s1 s2) = "difference(){" ++ render s1 ++ render s2 ++ "}\n"
493render (Minkowski ss) = rList "minkowski()" ss
494render (Hull ss) = rList "hull()" ss
495render (Scale v s) = rVecSolid "scale" v s
496render (Resize v s) = rVecSolid "resize" v s
497render (Translate v s) = rVecSolid "translate" v s
498render (Rotate v s) = "rotate(" ++ rVector v ++ ")" ++ render s
499render (Mirror v s) = rVecSolid "mirror" v s
500render (Import f) = "import(\"" ++ f ++ "\");\n"
501render (Color c s) = let r = toSRGB c in
502 "color(" ++ rVector (channelRed r, channelGreen r, channelBlue r) ++ ")\n"
503 ++ render s
504render (Transparent c s) =
505 "color(" ++ rQuad (channelRed r, channelGreen r, channelBlue r, a) ++ ")"
506 ++ render s
507 where r = toSRGB $ toPure c
508 a = alphaChannel c
509 toPure ac = if a > 0 then darken (recip a) (ac `over` black) else black
510render (Var (Fa f) ss) = rList ("assign($fa=" ++ show f ++ ")") ss
511render (Var (Fs f) ss) = rList ("assign($fs=" ++ show f ++ ")") ss
512render (Var (Fn n) ss) = rList ("assign($fn=" ++ show n ++ ")") ss
513
514-- utility for rendering Shapes.
515rShape :: Shape -> String
516rShape (Rectangle r f) = "square([" ++ show r ++ "," ++ show f ++ "]);\n"
517rShape (Circle r f) = "circle(" ++ show r ++ rFacet f ++ ");\n"
518rShape (Projection c s) =
519 "projection(cut=" ++ (if c then "true)" else "false)") ++ render s
520rShape (Polygon c points paths) = "polygon(points=" ++ rVectorL points ++
521 ",paths=" ++ show paths ++ ",convexity=" ++ show c ++ ");\n"
522rShape (Offset d j s) =
523 "offset(delta=" ++ show d ++ "," ++ rJoin j ++ ")" ++ rShape s
524
525-- utility for rendering Joins
526rJoin :: Join -> String
527rJoin Bevel = "join_type=bevel"
528rJoin Round = "join_type=round"
529rJoin (Miter l) = "miter_limit=" ++ show l
530
531-- utilities for rendering Solids.
532rSolid :: Solid -> String
533rSolid (Sphere x f) = "sphere(" ++ show x ++ rFacet f ++ ");\n"
534rSolid (Box x y z) =
535 "cube([" ++ show x ++ "," ++ show y ++ "," ++ show z ++ "]);\n"
536rSolid (Cylinder r h f) =
537 "cylinder(r=" ++ show r ++ ",h=" ++ show h ++ rFacet f ++ ");\n"
538rSolid (ObCylinder r1 h r2 f) =
539 "cylinder(r1=" ++ show r1 ++ ",h=" ++ show h ++ ",r2=" ++ show r2 ++ rFacet f
540 ++ ");\n"
541rSolid (Polyhedron c ps ss) = "polyhedron(points=" ++ rVectorL ps ++ rSides ss
542 ++ ",convexity=" ++ show c ++ ");\n"
543rSolid (MultMatrix (a, b, c, d) s) =
544 "multmatrix([" ++ rQuad a ++ "," ++ rQuad b ++ "," ++ rQuad c ++ ","
545 ++ rQuad d ++"])\n" ++ render s
546rSolid (LinearExtrude h t sc sl c f sh) =
547 "linear_extrude(height=" ++ show h ++ ",twist=" ++ show t ++ ",scale="
548 ++ rVector sc ++ ",slices=" ++ show sl ++ ",convexity=" ++ show c
549 ++ rFacet f ++ ")" ++ render sh
550rSolid (RotateExtrude c f sh) =
551 "rotate_extrude(convexity=" ++ show c ++ rFacet f ++ ")" ++ render sh
552rSolid (Surface f i c) =
553 "surface(file=\"" ++ f ++ "\"," ++ (if i then "invert=true," else "")
554 ++ "convexity=" ++ show c ++ ");\n"
555rSolid (ToSolid s) = render s
556
557-- render a list of vectors as an Openscad vector of vectors.
558rVectorL vs = "[" ++ intercalate "," (map rVector vs) ++ "]"
559
560-- render a Sides.
561rSides (Faces vs) = ",faces=" ++ rListL vs
562rSides (Triangles vs) = ",triangles=" ++ rListL vs
563rListL vs = "[" ++ intercalate "," (map show vs) ++ "]"
564
565-- | A convenience function to render a list of 'Model's by taking
566-- their union.
567renderL :: Vector v => [Model v] -> String
568renderL = render . union
569
570-- | A convenience function to write the rendered 'Model' to
571-- standard output.
572draw :: Vector v => Model v -> IO ()
573draw = putStrLn . render
574
575-- | A convenience function to write a 'union' of 'Model's to
576-- standard output.
577drawL :: Vector v => [Model v] -> IO ()
578drawL = draw . Union
579
580-- And some misc. rendering utilities.
581rList n ss = n ++ "{\n" ++ concatMap render ss ++ "}"
582rVecSolid n v s = n ++ "(" ++ rVector v ++ ")\n" ++ render s
583rQuad (w, x, y, z) =
584 "[" ++ show w ++ "," ++ show x ++ "," ++ show y ++ "," ++ show z ++ "]"
585rFacet Def = ""
586rFacet f = "," ++ showFacet f
587
588-- render a facet setting.
589showFacet :: Facet -> String
590showFacet (Fa f) = "$fa=" ++ show f
591showFacet (Fs f) = "$fs=" ++ show f
592showFacet (Fn n) = "$fn=" ++ show n
593showFacet Def = ""
594
595-- Convenience functions for Facets.
596-- | 'var' uses @assign@ to set a 'Facet' variable for it's 'Model's.
597var :: Facet -> [Model v] -> Model v
598var = Var
599
600-- | 'fa' is used to set the @$fa@ variable in a 'Facet' or 'var'.
601fa :: Double -> Facet
602fa = Fa
603
604-- | 'fs' is used to set the @$fs@ variable in a 'Facet' or 'var'.
605fs :: Double -> Facet
606fs = Fs
607
608-- | 'fn' is used to set the @$fn@ variable in a 'Facet' or 'var'.
609fn :: Int -> Facet
610fn = Fn
611
612-- | 'def' is used where a 'Facet' is needed but we don't want to change
613-- any of the values.
614def :: Facet
615def = Def
616
617-- And one last convenience function.
618-- | Use 'diam' to turn a diameter into a radius for circles, spheres, etc.
619diam :: Double -> Double
620diam = (/ 2)
621-- Now, let Haskell work it's magic
622instance Vector v => Semigroup (Model v) where
623 a <> b = union [a, b]
624 sconcat = union . NE.toList
625
626instance Vector v => Monoid (Model v) where
627 mempty = Solid $ Box 0 0 0
628 mappend (Solid (Box 0 0 0)) b = b
629 mappend a (Solid (Box 0 0 0)) = a
630 mappend a b = union [a, b]
631 mconcat [a] = a
632 mconcat as = union as
633
634
635-- | You can use '(#)' to write transformations in a more readable postfix form,
636-- cube 3 # color red # translate (-3, -3, -3)
637infixl 8 #
638(#) = flip ($)