hcad-0.1: HCad/Part.hs
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RebindableSyntax #-}
module HCad.Part where
import Algebra.Linear
import Algebra.Classes
import Algebra.Category
import Prelude hiding (Num(..),(/),divMod,div,recip,fromRational, (.), mod, id)
import Data.Foldable
import GHC.TypeLits
import Data.List (intercalate)
import Data.Kind (Type)
import Data.Type.Equality
import Unsafe.Coerce
import Data.Char (toLower)
import qualified Data.Set as Set
data SCAD = SCAD {scadPrim :: String
,scadArgs :: [(String,String)]
,scadBody :: [SCAD]}
newtype Sq4 a = Sq4 (SqMat V4' a)
instance Functor Sq4 where
fmap f (Sq4 m) = Sq4 (f >$< m)
instance Foldable Sq4 where
foldMap f (Sq4 (Mat v)) = foldMap (foldMap f) v
data Op = Union | Intersection | Hull deriving Show
data DSC vec a where
Polygon :: Int -> [V2 a] -> DSC V2' a
Polyhedron :: Int -> [V3 a] -> [[Int]] -> DSC V3' a
Prim :: SCAD -> DSC vec a
Color :: Double -> V3 s -> DSC vec s -> DSC vec s
NOp :: Op -> [DSC vec a] -> DSC vec a
Difference :: DSC vec a -> DSC vec a -> DSC vec a
MultMat :: Sq4 a -> DSC vec a -> DSC vec a
LExtrude :: a -> a -> a -> DSC V2' a -> DSC V3' a
RExtrude :: Maybe Int -> a -> DSC V2' a -> DSC V3' a
Mirror :: Euclid v a -> DSC v a -> DSC v a
deriving instance Foldable vec => Foldable (DSC vec)
type V4' = VNext V3'
difference' :: DSC vec a -> DSC vec a -> DSC vec a
difference' (Difference a b) c = Difference a (unions' [b,c])
difference' x y = Difference x y
pattern Uni :: forall vec a. [DSC vec a] -> DSC vec a
pattern Uni xs = NOp Union xs
unions' :: [DSC vec a] -> DSC vec a
unions' xs = Uni (unions'' xs)
unions'' :: [DSC vec a] -> [DSC vec a]
unions'' [] = []
unions'' (Uni xs:ys) = unions'' (xs++ys)
unions'' (x:xs) = x:unions'' xs
-- | add one dimension to the argument (the extra dimension is "diagonal")
addOneMat :: (Ring s, Applicative v, Applicative v) => Mat s v v -> Mat s (VNext v) (VNext v)
addOneMat (Mat vs) = Mat (VNext (VNext <$> vs <*> pure zero) (VNext (pure zero) one))
homMat :: ScadV v => Applicative v => Functor v => Ring s => SqMat v s -> SqMat V4' s
homMat = addOneMat . conv3dMat
-- | translation as a matrix transforming homogeneous vectors
translateToMat :: (Traversable v, Ring s, Applicative v) => Euclid v s -> SqMat (VNext v) s
translateToMat (Euclid v) = Mat (VNext (VNext <$> i <*> v) (VNext (pure zero) one))
where Mat i = identity
instance ScadV V2' where
conv3dVec (Euclid v) = Euclid (VNext v zero)
conv3dMat = addOneMat
instance ScadV V3' where
conv3dVec = id
conv3dMat = id
class (Traversable v, Applicative v) => ScadV v where
conv3dVec :: Additive a => Euclid v a -> Euclid V3' a
conv3dMat :: Ring a => SqMat v a -> SqMat V3' a
translate' :: ScadV vec => Traversable vec => Ring a => Applicative vec => Euclid vec a -> DSC vec a -> DSC vec a
translate' v = multmat'' (translateToMat $ conv3dVec v)
multmat' :: ScadV vec => Ring a => Traversable vec => Applicative vec => SqMat vec a -> DSC vec a -> DSC vec a
multmat' = multmat'' . homMat
multmat'' :: Ring a => Traversable vec => Applicative vec => SqMat V4' a -> DSC vec a -> DSC vec a
multmat'' v (Color a c t) = Color a c (multmat'' v t)
multmat'' v (NOp op ts) = NOp op (multmat'' v <$> ts)
multmat'' v (Difference t u) = Difference (multmat'' v t) (multmat'' v u)
multmat'' v (MultMat (Sq4 v') t) = MultMat (Sq4 (v . v')) t
multmat'' v t = MultMat (Sq4 v) t
convexity :: DSC vec a -> Int
convexity = \case
(Difference x y) -> convexity x + convexity y
(MultMat _ r) -> convexity r
(LExtrude _ _ _ r) -> convexity r
(Polygon convex _) -> convex
(Polyhedron convex _ _) -> convex
(Prim _) -> 2
(Color _ _ r) -> convexity r
(NOp Hull _) -> 2
(NOp Intersection rs) -> maximum (map convexity rs)
(NOp _ rs) -> sum (map convexity rs)
Mirror _ r -> convexity r
RExtrude {} -> 10
toSCAD :: Foldable vec => Functor vec => Floating a => Field a => Show a => DSC vec a -> SCAD
toSCAD = \case
Mirror normal r -> SCAD "mirror" [("v",renderVec normal)] [toSCAD r]
RExtrude fn angle partCode ->
SCAD "rotate_extrude" ([("angle",showAngle angle)] ++ [("$fn",show x) | Just x <- [fn]]) [toSCAD partCode]
(LExtrude height scaleFactor twist partCode) ->
(SCAD "linear_extrude"
[("height",show height)
,("center","true")
,("convexity",show (convexity partCode))
,("scale",show scaleFactor)
,("twist",showAngle twist)] [toSCAD partCode])
MultMat (Sq4 m) r -> SCAD "multmatrix" [("m",m')] [toSCAD r]
where m' = showL (toList (showL . toList . (fmap show) <$> fromMat m))
Polygon _ points -> SCAD "polygon" [("points",showL (map renderVec points))] []
Polyhedron _ points faces -> SCAD "polyhedron" [("points",showL (map renderVec points))
,("faces",showL $ map (showL . map show) $ faces)] []
Prim p -> p
NOp op rs -> SCAD (map toLower $ show op) [] (map toSCAD rs)
Difference r1 r2 -> SCAD "difference" [] [toSCAD r1, toSCAD r2]
Color a c r -> SCAD "color" [("c",renderVec c),("alpha",show a)] [toSCAD r]
data Part xs vec a
= Part {partVertices :: NamedVec xs (Euclid vec a) -- TODO: use Loc here
,partBases :: NamedVec xs (SqMat vec a)
,partCode :: DSC vec a }
type Part3 xs a = Part xs V3' a
type Part2 xs a = Part xs V2' a
type family (++) (a::[k]) (b::[k]) where
'[] ++ a = a
(x ': xs) ++ ys = x ': (xs ++ ys)
unitR :: xs :~: (xs ++ '[])
unitR = unsafeCoerce Refl
(#>) :: a :~: b -> (a ~ b => k) -> k
Refl #> k = k
infixr 0 #>
infixr ++*
(++*) :: NamedVec xs v -> NamedVec ys v -> NamedVec (xs ++ ys) v
Nil ++* ys = ys
(x :* xs) ++* ys = x :* xs ++* ys
type FieldName = [Symbol]
data NamedVec (fields::[FieldName]) vec where
Nil :: NamedVec '[] vec
(:*) :: vec -> NamedVec xs vec -> NamedVec (x ': xs) vec
infixr :*
class KnownLen xs where
repet :: a -> NamedVec xs a
appl :: NamedVec xs (a -> b) -> NamedVec xs a -> NamedVec xs b
instance KnownLen '[] where
repet _ = Nil
appl _ _ = Nil
instance KnownLen xs => KnownLen (x ': xs) where
repet x = x :* repet x
(f :* fs) `appl` (a :* as) = f a :* (fs `appl` as)
instance KnownLen xs => Applicative (NamedVec xs) where
pure = repet
(<*>) = appl
-- instance (Additive vec, KnownLen xs) => Additive (NamedVec xs vec) where
-- zero = repet zero
-- v1 + v2 = (+) <$> v1 <*> v2
-- instance (AbelianAdditive vec, KnownLen xs) => AbelianAdditive (NamedVec xs vec)
-- instance Module s vec => Module s (NamedVec xs vec)
deriving instance (Functor (NamedVec faces))
deriving instance (Foldable (NamedVec faces))
deriving instance (Traversable (NamedVec faces))
class (∈) (x :: FieldName) (xs :: [FieldName]) where
getField :: NamedVec xs a -> a
instance {-# OVERLAPPING #-} x ∈ (x ': xs) where
getField (x :* _) = x
instance {-# OVERLAPPING #-} x ∈ xs => x ∈ (y ': xs) where
getField (_y :* xs) = getField @x xs
class (⊆) (xs :: [FieldName]) (ys :: [FieldName]) where
filterVec :: NamedVec ys a -> NamedVec xs a
instance {-# OVERLAPPING #-} xs ⊆ ys => xs ⊆ (x ': ys) where
filterVec (_ :* xs) = filterVec xs
instance {-# OVERLAPPING #-} xs ⊆ ys => (x ': xs) ⊆ (x ': ys) where
filterVec (x :* xs) = x :* filterVec xs
instance {-# OVERLAPPING #-} '[] ⊆ '[] where
filterVec Nil = Nil
getBase :: forall x xs v a. x ∈ xs => Part xs v a -> SqMat v a
getBase = getField @x . partBases
getVertex :: forall x xs v a. x ∈ xs => Part xs v a -> Euclid v a
getVertex = getField @x . partVertices
getLoc :: forall x xs v a. x ∈ xs => RelLoc xs v a
getLoc p = Loc (getVertex @x p) (getBase @x p)
class KnownD v where
is3d :: Bool
-------------------------------------------
-- Primitive ops
type family SimpleFields x where
SimpleFields '[] = '[]
SimpleFields ( x ': xs) = '[x] ': SimpleFields xs
type family MapCons x xs where
MapCons _ '[] = '[]
MapCons x ( y ': ys) = ( (x ': y) ': MapCons x ys )
nameVec :: forall x xs vec. NamedVec xs vec -> NamedVec (MapCons x xs) vec
nameVec Nil = Nil
nameVec (a :* as) = (a :* nameVec @x as)
name :: forall x xs vec a. Part xs vec a -> Part (MapCons x xs) vec a
name (Part {..}) = Part{partVertices = nameVec @x partVertices
,partBases = nameVec @x partBases
,..}
weaken :: ys ⊆ xs => Part xs vec a -> Part ys vec a
weaken (Part {..}) = Part{partVertices = filterVec partVertices
,partBases = filterVec partBases
,..}
forget :: Part xs vec a -> Part '[] vec a
forget Part{..} = Part {partBases=Nil,partVertices=Nil,..}
meshImport :: String -> Part3 '[] a
meshImport fname = Part {partBases=Nil
,partVertices=Nil
,partCode= Prim (SCAD "import" [("file",show fname)] [])}
color' :: (Show s) => Double -> V3 s -> Part xs vec s -> Part xs vec s
color' a c Part{..} = Part {partCode = Color a c partCode
,..}
color :: (Show s) => V3 s -> Part xs vec s -> Part xs vec s
color = color' 1
cube :: Show a => Floating a => Field a
=> Part '[ '["bottom"], '["top"], '["right"], '["back"],
'["left"], '["front"], '["northEast"], '["northWest"],
'["southWest"], '["southEast"]] V3' a
cube = extrude one square
sphere :: Part3 '[] a
sphere = Part {partVertices = Nil, partBases = Nil
,partCode = Prim (SCAD "sphere" [("r","0.5")] [])}
square :: forall a. Module a a => Floating a => Show a => Field a
=> Part2 (SimpleFields '[East, North, West, South, "northEast", "northWest", "southWest", "southEast"]) a
square = Part {partVertices = matVecMul <$> partBases <*> (V2 <$> scales <*> pure 0)
,partCode = Prim (SCAD "square" [("size","1"),("center","true")] [])
,..}
where partBases = rotation2d <$> angles
scales = 0.5 :* 0.5 :* 0.5 :* 0.5 :* sqrt 0.5 :* sqrt 0.5 :* sqrt 0.5 :* sqrt 0.5 :* Nil
angles = (pi *) <$> (0 :* 0.5 :* 1 :* 1.5 :* 0.25 :* 0.75 :* 1.25 :* 1.75 :* Nil)
rectangle :: (Field s, Show s, Module s s, Floating s) =>
Euclid V2' s
-> Part
'[ '["right"], '["back"], '["left"], '["front"], '["northEast"],
'["northWest"], '["southWest"], '["southEast"]]
V2'
s
rectangle sz = scale' sz square
circle :: Part2 '[] a
circle = Part {partVertices = Nil, partBases = Nil
,partCode = Prim (SCAD "circle" [("r","0.5")] [])}
polygon' :: Show a => Int -> [V2 a] -> Part2 '[] a
polygon' convex points
= Part {partVertices = Nil
,partBases = Nil
,partCode = Polygon convex points}
polygon :: Show a => [V2 a] -> Part2 '[] a
polygon = polygon' 2
tessalateFace :: [a] -> [[a]]
tessalateFace [x,y,z] = [[x,y,z]]
tessalateFace (a:b:c:vs) = [a,b,c]:tessalateFace (a:c:vs)
-- | List of faces. Points in a faces must be coplanar, and going
-- clockwise when looking from outside. Faces must form a closed polyhedron.
polyhedron :: Ord a => [[V3 a]] -> Part3 '[] a
polyhedron faces = Part {partVertices=Nil, partBases=Nil,partCode = Polyhedron 1 (toList vertices) faces'}
where vertices = Set.fromList (concat faces)
faces' = concatMap tessalateFace $ map (map (flip Set.findIndex vertices)) $ faces
extrude :: forall a xs. Field a => Floating a => Module a a => Show a
=> a -> Part2 xs a -> Part3 (SimpleFields '[Nadir,Zenith] ++ xs) a
extrude height p = extrudeEx height 1 0 p
extrudeEx :: forall a xs. Floating a => Field a => Module a a => Show a
=> a -> a -> a -> Part2 xs a -> Part3 (SimpleFields '[Nadir,Zenith] ++ xs) a
extrudeEx height scaleFactor twist Part{..}
= Part {partVertices = (flip matVecMul (V3 0 0 (0.5 * height)) <$> botTopBases) ++* (z0 <$> partVertices)
,partBases = botTopBases ++* (conv <$> partBases)
,partCode = LExtrude height scaleFactor twist partCode
}
where botTopBases = flip rotation3d (V3 1 0 0) <$> angles
angles = pi :* zero :* Nil
z0 (V2 x y) = (V3 x y zero)
zz0 (Mat2x2 a b c d) =
Mat3x3 a b 0
c d 0
0 0 1
zToX = Mat3x3 0 0 1
0 1 0
(-1) 0 0
conv m = zz0 m . zToX . transpose (zz0 m)
lathe :: (Show a, Field a, Floating a) => Part2 xs a -> Part3 '[] a
lathe = latheEx Nothing (2*pi)
latheEx :: (Show a, Division a, Floating a) => Maybe Int -> a -> Part2 xs a -> Part3 '[] a
latheEx fn angle Part{..} =
Part {partVertices = Nil,
partBases = Nil,
partCode = RExtrude fn angle partCode
}
flattenUnions :: [SCAD] -> [SCAD]
flattenUnions (SCAD "union" [] xs:ys) = xs ++ flattenUnions ys
flattenUnions (x:xs) = x:flattenUnions xs
flattenUnions [] = []
mkUnion :: [SCAD] -> SCAD
mkUnion xs = SCAD "union" [] (flattenUnions xs)
(/+) :: Part xs v a -> Part ys v a -> Part (xs ++ ys) v a
(/+) p1 p2 = Part {partVertices = partVertices p1 ++* partVertices p2
,partBases = partBases p1 ++* partBases p2
,partCode = unions' [partCode p1,partCode p2]}
union :: Part ys v a -> Part xs v a -> Part (xs ++ ys) v a
union = flip (/+)
unions :: [Part xs v a] -> Part '[] v a
unions ps = Part {partVertices = Nil
,partBases = Nil
,partCode = unions' (map partCode ps)}
intersection :: Part ys v a -> Part xs v a -> Part (xs ++ ys) v a
intersection p2 p1 = Part {partVertices = partVertices p1 ++* partVertices p2
,partBases = partBases p1 ++* partBases p2
,partCode = NOp Intersection [partCode p1,partCode p2]}
hull :: Part ys v a -> Part xs v a -> Part (xs ++ ys) v a
hull p2 p1 = Part {partVertices = partVertices p1 ++* partVertices p2
,partBases = partBases p1 ++* partBases p2
,partCode = NOp Hull [partCode p1,partCode p2]}
hulls :: [Part xs v a] -> Part '[] v a
hulls ps = Part {partVertices = Nil
,partBases = Nil
,partCode = NOp Hull (map partCode ps)}
(/-) :: Part xs v a -> Part ys v a -> Part (xs ++ ys) v a
(/-) p1 p2 = Part {partVertices = partVertices p1 ++* partVertices p2
,partBases = partBases p1 ++* partBases p2
,partCode = difference' (partCode p1) (partCode p2)}
difference :: Part ys v a -> Part xs v a -> Part (xs ++ ys) v a
difference = flip (/-)
translate :: forall (v :: Type -> Type) s xs. ScadV v => Ring s => Traversable v => Additive s => Applicative v => Foldable v => Show s => Euclid v s -> Part xs v s -> Part xs v s
translate v Part{..} = Part {partBases = partBases
,partVertices = (v +) <$> partVertices
,partCode = translate' v partCode
}
rotate :: ScadV v => Traversable v => Applicative v => Show s => Floating s => Division s => Module s s => Ring s => SqMat v s -> Part xs v s -> Part xs v s
rotate m Part{..} = Part {partVertices = matVecMul m <$> partVertices
,partBases = (m .) <$> partBases
,partCode = multmat' m partCode}
mirror :: forall a v xs. Applicative v => Field a => Ring a => Foldable v => Show a => Euclid v a -> Part xs v a -> Part xs v a
mirror normal Part{..}
= Part {partBases = mm <$> partBases
,partVertices = m <$> partVertices
,partCode = Mirror normal partCode}
where m :: Euclid v a -> Euclid v a
m x = x - (fromInteger 2 * d) *^ normal
where d = normal · x
m' :: v a -> v a
m' = fromEuclid . m . Euclid
mm :: SqMat v a -> SqMat v a
mm = Mat . fmap m' . fromMat
scale' :: ScadV v => (Field s,Show s) => Euclid v s -> Part xs v s -> Part xs v s
scale' v Part{..} = Part {partBases = partBases -- FIXME: shear the base!
,partVertices = (v ⊙) <$> partVertices
,partCode = multmat' (diagonal v) partCode }
scale :: (ScadV v, Field s, Show s) => s -> Part xs v s -> Part xs v s
scale s = scale' (pure s)
------------------------------------------------
-- Locations and relative locations
data Loc v a = Loc {locPoint :: Euclid v a, locBase :: SqMat v a}
locNormal :: Ring a => Loc V3' a -> Euclid V3' a
locNormal = flip matVecMul (V3 zero zero one) . locBase
-- | Origin point with normal pointing to 'Zenith'.
origin :: Ring a => Loc V3' a
origin = Loc {locPoint = zero, locBase = identity}
type RelLoc xs v a = Part xs v a -> Loc v a
-- | Put the focus point on the given point (not changing the focused
-- direction)
at :: Ring s => (ScadV v, Show s) => (RelLoc xs v s) -> (Part xs v s -> Part ys v s) -> (Part xs v s -> Part ys v s)
at relLoc f body = translating (locPoint (relLoc body)) f body
translating :: ScadV v => Ring s => Show s =>
Euclid v s
-> (Part xs1 v s -> Part xs2 v s)
-> Part xs1 v s
-> Part xs2 v s
translating delta f = translate delta . f . translate (negate delta)
-- -- | Put the focus point over or under the given point (so, leaving
-- -- z-coordinate unchanged)
-- atXY :: (Show s, Division s, Module s s) =>
-- (Part xs (V3 s) -> Loc (V3 s))
-- -> (Part xs (V3 s) -> Part ys (V3 s))
-- -> Part xs (V3 s)
-- -> Part ys (V3 s)
-- atXY f = at (projectOnPlane origin . f)
rotating :: ScadV v => (Show s, Floating s, Field s, Module s s) =>
SqMat v s
-> (Part xs1 v s -> Part xs2 v s)
-> Part xs1 v s
-> Part xs2 v s
rotating o f = rotate o . f . rotate (transpose o)
-- | Put the focus point on the given locus
on :: ScadV v => Division a => Module a a => Floating a => Field a => Show a
=> RelLoc xs v a -> (Part xs v a -> Part ys v a) -> (Part xs v a -> Part ys v a)
on relLoc f body = translating locPoint (rotating locBase f) body
where Loc{..} = relLoc body
-- | Center the given location
center :: ScadV v => Ring a => Show a => RelLoc xs v a -> Part xs v a -> Part xs v a
center getX p = translate (negate (locPoint (getX p))) p
-- | Shift and rotate part to the given location
withLoc :: Floating a => Show a => Field a => ScadV v => Loc v a -> Part xs v a -> Part xs v a
withLoc Loc{..} = translate locPoint . rotate locBase
------------------------------------------------
-- Non-primitive ops
rotate2d :: (Show s, Floating s, Field s) =>
s -> Part xs V2' s -> Part xs V2' s
rotate2d angle = rotate (rotation2d angle)
xAxis, yAxis, zAxis :: Ring a => V3 a
xAxis = V3 one zero zero
yAxis = V3 zero one zero
zAxis = V3 zero zero one
mirrored :: forall v a xs. Module a a => Field a => Applicative v => (Foldable v, Show a) => Euclid v a -> Part xs v a -> Part xs v a
mirrored axis part = unitR @xs #> union (forget $ mirror axis part) part
mirroring :: (Applicative v, Field a, Module a a, Foldable v, Show a) =>
Euclid v a -> (Part xs v a -> Part xs v a) -> Part xs v a -> Part xs v a
mirroring axis f = mirror axis . f . mirror axis . f
-- | Regular polygon contained a unit-diameter circle.
regularPolygon :: Field a => Module a a => Division a => Floating a => Show a => Int -> Part2 '[] a
regularPolygon order = scale 0.5 (polygon coords)
where coords=[V2 (cos th) (sin th)
| i <- [0..order-1],
let th = fromIntegral i*(2.0*pi/fromIntegral order) ];
-- | Regular polygon containing a unit-diameter circle.
regularPolygonO :: Field a => Module a a => Division a => Floating a => Show a => Int -> Part2 '[] a
regularPolygonO order = scale (1 / cos (pi / fromIntegral order)) $ regularPolygon order
epsilon :: Field a => a
epsilon = 0.001
rectangleWithChamferCorners :: Floating a => Show a => Field a => a -> Euclid V2' a -> Part ('[ '["right"], '["back"], '["left"], '["front"],
'["northEast"], '["northWest"], '["southWest"],
'["southEast"]]) V2' a
rectangleWithChamferCorners r sz@(V2 w h) = rect {partCode = code}
where rect = rectangle sz
code = partCode $
mirrored (V2 1 0) $
mirrored (V2 0 1) $
polygon [V2 (-epsilon) (-epsilon), V2 (-epsilon) (h/2), V2 (w/2-r) (h/2), V2 (w/2) (h/2-r), V2 (w/2) (-epsilon) ]
rectangleWithRoundedCorners :: Floating a => Show a => Field a => a -> Euclid V2' a -> Part ('[ '["right"], '["back"], '["left"], '["front"],
'["northEast"], '["northWest"], '["southWest"],
'["southEast"]]) V2' a
rectangleWithRoundedCorners r sz@(V2 w h) =
mirrored (V2 1 0) $
mirrored (V2 0 1) $
union (translate (V2 (w/2-r) (h/2-r)) $ scale (2*r) $ circle) $
rectangleWithChamferCorners r sz
-- | A circle with an angular top. The argument is the top angle; often pi/2 or pi/3
waterdrop :: Field a => (Division a, Group a, Floating a, Show a) => a -> Part2 '[] a
waterdrop alpha = union circle (scale 0.5 $ polygon [V2 c s, V2 0 (1/s), V2 (-c) s])
where s = sin alpha
c = cos alpha
-- | Create a mortise
push :: forall xs ys a. Floating a => Show a => Ring a => Field a => a -> Part2 ys a -> (Part3 xs a -> Part3 xs a)
push depth shape =
unitR @xs #> (difference $ forget $
translate (V3 zero zero (epsilon - 0.5 * depth)) (extrude (depth+2*epsilon) shape))
where epsilon :: a
epsilon = 0.05
-- | Create a tenon
pull :: forall xs ys a. Module a a => Floating a => Show a => Field a => a -> Part2 ys a -> (Part3 xs a -> Part3 xs a)
pull depth shape = unitR @xs #> union $ forget $ translate (V3 0 0 (0.5 * depth - epsilon)) (extrude depth shape)
where epsilon :: a
epsilon = 0.05
cone' :: (Floating a, Field a, Module a a, Show a) => a -> Part3 '[ '["bottom"], '["top"]] a
cone' angle = (extrudeEx c 0 0 circle)
where c = sin angle
counterSink :: forall xs a.
(Floating a, Show a, Module a a, Field a)
=> a -> a -> Part3 xs a -> Part3 xs a
counterSink angle diameter = unitR @xs #> difference (forget negative) where
negative = translate (V3 0 0 epsilon) $ center nadir $ rotate (rotation3d pi (V3 1 0 0)) (scale diameter $ cone' angle)
epsilon = 0.05
----------------------------------
-- Filling
linearRepeat' :: ScadV v => Ring s => Show s =>
Int -> [Euclid v s] -> Part xs v s -> Part '[] v s
linearRepeat' number intervals part =
unions [translate (mult (fromIntegral k) (intervals !! k) +
mult (fromIntegral j) (add intervals)) part
| i <- [negate number `div` 2..number `div` 2],
let (j,k) = i `divMod` length intervals
]
linearRepeat :: forall s v xs. ScadV v => Show s => Field s =>
Int -> Euclid v s -> Part xs v s -> Part '[] v s
linearRepeat number interval part =
unions [translate ((shift + mult (fromIntegral i) interval)) part | i <- [negate number `div` 2..number `div` 2]]
where shift = if number `mod` 2 == 1 then (fromRational 0.5::s) *^ interval else zero
linearFill :: (ScadV v, Show s, RealFrac s, Floating s, Field s, Ring s) =>
s -> Euclid v s -> Part xs v s -> Part '[] v s
linearFill len interval part = linearRepeat (floor (len / norm interval)) interval part
-- | Fill a rectangle in hexagonal pattern
hexagonFill :: Module Int s => RealFrac s => Floating s => Show s => Field s => Module s s
=> s -> s -> s
-> Part2 xs s
-> Part2 ('[ '["right"], '["back"], '["left"], '["front"],
'["northEast"], '["northWest"], '["southWest"],
'["southEast"]] :: [[Symbol]]) s
hexagonFill len width cell_size shape
= intersection (scale' (V2 len width) square) $
linearRepeat' no_of_rows (V2 tr_x <$> [negate tr_y, tr_y]) $
linearFill (width + cell_size) (V2 0 cell_size) $ -- width + cell_size: we need a bit larger area because of the tr_y offsets
shape
where no_of_rows = floor(1.2 * len / cell_size)
tr_x = sqrt(3)/2 * cell_size
tr_y = cell_size / 2
--------------------------------------
-- Locations
south :: '[South] ∈ xs => RelLoc xs v a; south = getLoc @'[South]
north :: '[North] ∈ xs => RelLoc xs v a; north = getLoc @'[North]
west :: '[West] ∈ xs => RelLoc xs v a; west = getLoc @'[West]
east :: '[East] ∈ xs => RelLoc xs v a; east = getLoc @'[East]
nadir :: '[Nadir] ∈ xs => RelLoc xs v a; nadir = getLoc @'[Nadir]
zenith :: '[Zenith] ∈ xs => RelLoc xs v a; zenith = getLoc @'[Zenith]
southEast :: '["southEast"] ∈ xs => RelLoc xs v a; southEast = getLoc @'["southEast"]
northEast :: '["northEast"] ∈ xs => RelLoc xs v a; northEast = getLoc @'["northEast"]
southWest :: '["southWest"] ∈ xs => RelLoc xs v a; southWest = getLoc @'["southWest"]
northWest :: '["northWest"] ∈ xs => RelLoc xs v a; northWest = getLoc @'["northWest"]
projectOnPlane :: (Module scalar scalar, Field scalar) =>
Loc V3' scalar -> Loc V3' scalar -> Loc V3' scalar
projectOnPlane plane@Loc {locPoint = planeOrigin}
Loc {..} = Loc {locPoint = position, locBase = locBase}
where θ = (planeOrigin - locPoint) · planeNormal
position = θ *^ planeNormal + locPoint
planeNormal = locNormal plane
-- equation : (position - planeOrigin) · planeNormal = 0
(|<-) :: (Module scalar scalar, Field scalar)
=> (t -> Loc V3' scalar) -> (t -> Loc V3' scalar) -> t -> Loc V3' scalar
(plane |<- pos) p = projectOnPlane (plane p) (pos p)
infixr |<-
projectOnLine :: (Module scalar scalar, Field scalar) =>
Loc V3' scalar -> Loc V3' scalar -> Loc V3' scalar
projectOnLine line@Loc {locPoint = lineOrigin}
Loc {..} = Loc {locPoint = position, locBase = locBase}
where cosθ = (locPoint - lineOrigin) · lineVec
position = lineOrigin + cosθ *^ lineVec
lineVec = locNormal line
(/<-) :: (Module scalar scalar, Field scalar) =>
(t -> Loc V3' scalar)
-> (t -> Loc V3' scalar) -> t -> Loc V3' scalar
(line /<- pos) p = projectOnLine (line p) (pos p)
projectOnPoint :: (Module scalar scalar, Field scalar) =>
Loc V3' scalar -> Loc V3' scalar -> Loc V3' scalar
projectOnPoint Loc {locPoint = lineOrigin}
Loc {..} = projectOnLine Loc {locBase=locBase, locPoint=lineOrigin} Loc {..}
(.<-) :: (Module scalar scalar, Field scalar) =>
(t -> Loc V3' scalar)
-> (t -> Loc V3' scalar) -> t -> Loc V3' scalar
(line .<- pos) p = projectOnPoint (line p) (pos p)
-- yxPoint :: V2 a -> V2 a -> V2 a
-- yxPoint (V2 _ y) (V2 x _) = V2 x y
-- yxLoc :: (t -> Loc V2' a) -> (t -> Loc V2' a) -> t -> Loc V2' a
-- yxLoc f g p = Loc (yxPoint (locPoint y) (locPoint x)) (yxPoint (locBase y) (locBase x))
-- where y = f p
-- x = g p
type East = "right"
type West = "left"
type North = "back"
type South = "front"
type Zenith = "top"
type Nadir = "bottom"
-------------------------------------
-- Rendering
renderVec :: (Show a, Foldable t) => t a -> String
renderVec v = showL (map show (toList v))
showL :: [String] -> String
showL v = "[" <> intercalate ", " v <> "]"
showAngle :: Show a => Field a => Floating a => a -> String
showAngle x = show (x * (180 / pi))