haskell-gimbal-keyring.hs
Keyring intended as a give-away at a conference
#!/usr/bin/env stack
{- stack script --resolver lts-23.15
--package linear
--package waterfall-cad
--extra-dep waterfall-cad-0.6.1.0
--extra-dep opencascade-hs-0.6.1.0
-}
-- short-description: Haskell Gimbal Toy
--
-- description: Keyring intended as a give-away at a conference
--
-- image: https://doscienceto.it/blog/photos/haskell-gimbal-01.jpg
-- image: https://doscienceto.it/blog/photos/haskell-gimbal-02.jpg
import qualified Waterfall
import Linear
import Control.Lens
haskellLogo =
let paths = [
Waterfall.pathFrom (V2 0 12)
[ Waterfall.lineTo (V2 4 6)
, Waterfall.lineTo (V2 0 0)
, Waterfall.lineTo (V2 3 0)
, Waterfall.lineTo (V2 7 6)
, Waterfall.lineTo (V2 3 12)
],
Waterfall.pathFrom (V2 4 0)
[ Waterfall.lineTo (V2 8 6)
, Waterfall.lineTo (V2 4 12)
, Waterfall.lineTo (V2 7 12)
, Waterfall.lineTo (V2 15 0)
, Waterfall.lineTo (V2 12 0)
, Waterfall.lineTo (V2 9.5 3.75)
, Waterfall.lineTo (V2 7 0)
],
Waterfall.pathFrom (V2 13.66 3.5)
[ Waterfall.lineTo (V2 12.333 5.5)
, Waterfall.lineTo (V2 17 5.5)
, Waterfall.lineTo (V2 17 3.5)
],
Waterfall.closeLoop $ Waterfall.pathFrom (V2 11.666 6.5)
[ Waterfall.lineTo (V2 10.333 8.5)
, Waterfall.lineTo (V2 17 8.5)
, Waterfall.lineTo (V2 17 6.5)
]
]
logo = mconcat . fmap (Waterfall.prism 3 . Waterfall.makeShape . Waterfall.closeLoop) $ paths
Just (lo, hi) = Waterfall.axisAlignedBoundingBox logo
w = (hi - lo) ^. _x
m = Waterfall.centerOfMass logo
in Waterfall.scale (V3 1 1 10)
. Waterfall.uScale ( 0.55* ((r-(segWidth +gapWidth))*2 ) / w )
. Waterfall.translate (negate m)
$ logo
h :: Double
h = 12
r :: Double
r = 20
segWidth :: Double
segWidth = 3
gapWidth :: Double
gapWidth = 0.5
insetWidth :: Double
insetWidth = 2.5
heightMask :: Waterfall.Solid
heightMask = Waterfall.scale (V3 (r*6) (r*6) h) Waterfall.centeredCube
coneMask :: Waterfall.Solid
coneMask = Waterfall.scale (V3 20 (r*6) h) Waterfall.centeredCube
cone :: Double -> Waterfall.Solid
cone r =
Waterfall.intersection coneMask
. Waterfall.uScale r
. Waterfall.rotate (unit _x) (pi/2)
. Waterfall.translate (negate $ unit _z)
$ Waterfall.unitCone
cones :: Double -> Waterfall.Solid
cones r =
mconcat $
[ Waterfall.rotate (unit _z) t $ cone r
| t <- [0, pi]
]
chamfer :: Waterfall.Solid -> Waterfall.Solid
chamfer = Waterfall.chamfer 1
ringHole :: Waterfall.Solid
ringHole =
let hole = Waterfall.translate (unit _y ^* (r - segWidth/2 - 0.25))
. Waterfall.scale (V3 1.5 0.75 100)
$ Waterfall.centeredCylinder
holeH = 5
divots = mconcat
[
Waterfall.translate (unit _y ^* (r - segWidth/2 - 0.25) + unit _z^* v)
. Waterfall.rotate (unit _x) (-pi/2)
. Waterfall.scale (V3 1.5 1.5 100)
$ Waterfall.unitCylinder
| v <- [holeH/2, -holeH/2]
]
divots' = mconcat
[
Waterfall.translate (unit _y ^* (r - segWidth/2 - 0.25) + unit _z^* v)
. Waterfall.scale (V3 3 3 (h-holeH))
. Waterfall.translate (unit _y ^* (0.5))
$ Waterfall.centeredCube
| v <- [h/2, -h/2]
]
in hole <> divots <> divots'
grip :: Waterfall.Solid
grip =
let n = 64
in mconcat
. take n
. iterate (Waterfall.rotate (unit _z) (pi * 2 / fromIntegral n))
. Waterfall.rotate (unit _y) (pi/6)
. Waterfall.translate (unit _y ^* r)
. Waterfall.scale (V3 0.5 0.5 100)
$ Waterfall.centeredCylinder
gimbalSegmentA :: Waterfall.Solid
gimbalSegmentA =
let outerSphere = Waterfall.uScale r $ Waterfall.unitSphere
innerSphere = Waterfall.uScale (r - segWidth) $ Waterfall.unitSphere
cutout = Waterfall.rotate (unit _z) (pi/2) $ cones (r + insetWidth - segWidth)
filleted = chamfer ((outerSphere `Waterfall.intersection` heightMask) `Waterfall.difference` innerSphere)
in filleted `Waterfall.difference` (cutout <> ringHole <> grip)
gimbalSegmentB :: Waterfall.Solid
gimbalSegmentB =
let outerSphere = Waterfall.uScale (r - segWidth - gapWidth) $ Waterfall.unitSphere
innerSphere = Waterfall.uScale (r - segWidth * 2 - gapWidth ) $ Waterfall.unitSphere
pin = (Waterfall.rotate (unit _z) (pi/2) $ cone (r + insetWidth - segWidth - gapWidth))
`Waterfall.difference` (Waterfall.uScale (r - segWidth * 1.55 - gapWidth ) $ Waterfall.unitSphere)
pin' = Waterfall.rotate (unit _z) (pi-0.001) pin
cutout = cones (r + insetWidth - segWidth *2 - gapWidth)
filleted = chamfer $ (outerSphere `Waterfall.intersection` heightMask) `Waterfall.difference` (innerSphere)
in (filleted `Waterfall.difference` cutout) <> (pin <> pin')
gimbalSegmentC :: Waterfall.Solid
gimbalSegmentC =
let outerSphere = Waterfall.uScale (r - segWidth * 2 - gapWidth * 2 ) $ Waterfall.unitSphere
pin = cones (r + insetWidth - segWidth *2 - gapWidth*2)
face = chamfer (outerSphere `Waterfall.intersection` heightMask)
in (face <> pin) `Waterfall.difference` haskellLogo
gimbalSegments :: Waterfall.Solid
gimbalSegments = gimbalSegmentA <> gimbalSegmentB <> gimbalSegmentC
main :: IO ()
main = Waterfall.writeSTL 0.1 "gimbal-keyring.stl" gimbalSegments