{-# 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))