Support negative space in SymbolicObjs
I'd like to make a SymbolicObj3 which consists of a part, plus mounting holes for it. I can either model the part, and compose it into a bigger design via union, or model the mounting holes and compose them in via difference. But keeping this as two separate parts is inconvenient; it means the two need to be correctly positioned relative to one another in the calling code, rather than in the defining code. Not a good way to prevent user errors!
AFAIK, there is no blending operation that can union the part and simultaneously carve out the mounting holes. This is extremely desirable to me!
I think I've worked out how to do this. The trick is just to give a second interpretation of the symbolic object's free structure.
merge :: SymbolicObj3 -> SymbolicObj3 -> SymbolicObj3
merge obj (Union3 r u) = unionR r $ obj : u
merge obj (Difference3 r (d1:ds)) = differenceR r $ union obj d1 : ds
-- etc
This seems to work!
merge :: SymbolicObj3 -> SymbolicObj3 -> SymbolicObj3
merge x (UnionR3 d l_s) = unionR d $ x : l_s
merge x (DifferenceR3 d (l : l_s)) = differenceR d $ union [x, l] : l_s
merge x (IntersectR3 d (l : l_s)) = intersectR d $ union [x, l] : l_s
merge x (Translate3 pddd s) = translate pddd $ merge (translate (negate pddd) x) s
merge x (Scale3 pddd s) = scale pddd $ merge (scale (allthree (1 /) pddd) x) s
merge x (Rotate3 pddd s) = rotate3 pddd $ merge (rotate3 (negate pddd) x) s
merge x y = union [x, y]
main :: IO ()
main = writeSTL 1 "/tmp/merge.stl" $
merge (center3 $ box 12 12 5) $
center3 $ difference [ cylinder 5 8 , cylinder 3 8 ]
which results in:

Notice how the differing cylinder cuts through the box.
@julialongtin are you interested in a PR?
I guess this thing still needs to be recursive in order to compose well. It can't, for instance, union together several pieces which contain holes.