From c221b741842d262434959f959d30c5b16f8b2741 Mon Sep 17 00:00:00 2001 From: Joe Warren Date: Tue, 23 Jul 2024 19:53:09 +0100 Subject: [PATCH 01/13] Add Lofts --- .../cpp/hs_BRepBuilderAPI_MakeVertex.cpp | 15 ++++++ .../cpp/hs_BRepBuilderAPI_MakeVertex.h | 20 ++++++++ .../cpp/hs_BRepOffsetAPI_ThruSections.cpp | 18 +++++++ .../cpp/hs_BRepOffsetAPI_ThruSections.h | 23 +++++++++ opencascade-hs/cpp/hs_types.h | 1 + opencascade-hs/opencascade-hs.cabal | 6 +++ .../BRepBuilderAPI/Internal/Destructors.hs | 4 +- .../OpenCascade/BRepBuilderAPI/MakeVertex.hs | 26 ++++++++++ .../src/OpenCascade/BRepBuilderAPI/Types.hs | 5 +- .../BRepOffsetAPI/Internal/Destructors.hs | 4 +- .../OpenCascade/BRepOffsetAPI/ThruSections.hs | 27 ++++++++++ .../src/OpenCascade/BRepOffsetAPI/Types.hs | 5 +- waterfall-cad-examples/app/Main.hs | 2 + waterfall-cad-examples/src/LoftExample.hs | 49 +++++++++++++++++++ .../waterfall-cad-examples.cabal | 1 + .../src/Waterfall/Internal/ToOpenCascade.hs | 18 +++++++ waterfall-cad/src/Waterfall/Loft.hs | 26 ++++++++++ waterfall-cad/waterfall-cad.cabal | 2 + 18 files changed, 247 insertions(+), 5 deletions(-) create mode 100644 opencascade-hs/cpp/hs_BRepBuilderAPI_MakeVertex.cpp create mode 100644 opencascade-hs/cpp/hs_BRepBuilderAPI_MakeVertex.h create mode 100644 opencascade-hs/cpp/hs_BRepOffsetAPI_ThruSections.cpp create mode 100644 opencascade-hs/cpp/hs_BRepOffsetAPI_ThruSections.h create mode 100644 opencascade-hs/src/OpenCascade/BRepBuilderAPI/MakeVertex.hs create mode 100644 opencascade-hs/src/OpenCascade/BRepOffsetAPI/ThruSections.hs create mode 100644 waterfall-cad-examples/src/LoftExample.hs create mode 100644 waterfall-cad/src/Waterfall/Internal/ToOpenCascade.hs create mode 100644 waterfall-cad/src/Waterfall/Loft.hs diff --git a/opencascade-hs/cpp/hs_BRepBuilderAPI_MakeVertex.cpp b/opencascade-hs/cpp/hs_BRepBuilderAPI_MakeVertex.cpp new file mode 100644 index 0000000..e86779f --- /dev/null +++ b/opencascade-hs/cpp/hs_BRepBuilderAPI_MakeVertex.cpp @@ -0,0 +1,15 @@ +#include +#include "hs_BRepBuilderAPI_MakeVertex.h" +#include + +BRepBuilderAPI_MakeVertex * hs_new_BRepBuilderAPI_MakeVertex_fromPnt(gp_Pnt* pnt){ + return new BRepBuilderAPI_MakeVertex(*pnt); +} + +void hs_delete_BRepBuilderAPI_MakeVertex(BRepBuilderAPI_MakeVertex* builder){ + delete builder; +} + +TopoDS_Vertex * hs_BRepBuilderAPI_MakeVertex_vertex(BRepBuilderAPI_MakeVertex * builder){ + return new TopoDS_Vertex(builder->Vertex()); +} \ No newline at end of file diff --git a/opencascade-hs/cpp/hs_BRepBuilderAPI_MakeVertex.h b/opencascade-hs/cpp/hs_BRepBuilderAPI_MakeVertex.h new file mode 100644 index 0000000..21210b2 --- /dev/null +++ b/opencascade-hs/cpp/hs_BRepBuilderAPI_MakeVertex.h @@ -0,0 +1,20 @@ +#ifndef HS_BREPBUILDERAPI_MAKEVERTEX_H +#define HS_BREPBUILDERAPI_MAKEVERTEX_H + +#include "hs_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +BRepBuilderAPI_MakeVertex * hs_new_BRepBuilderAPI_MakeVertex_fromPnt(gp_Pnt* pnt); + +void hs_delete_BRepBuilderAPI_MakeVertex(BRepBuilderAPI_MakeVertex* builder); + +TopoDS_Vertex * hs_BRepBuilderAPI_MakeVertex_vertex(BRepBuilderAPI_MakeVertex * builder); + +#ifdef __cplusplus +} +#endif + +#endif // HS_BREPBUILDERAPI_MAKEVERTEX_H diff --git a/opencascade-hs/cpp/hs_BRepOffsetAPI_ThruSections.cpp b/opencascade-hs/cpp/hs_BRepOffsetAPI_ThruSections.cpp new file mode 100644 index 0000000..26c6670 --- /dev/null +++ b/opencascade-hs/cpp/hs_BRepOffsetAPI_ThruSections.cpp @@ -0,0 +1,18 @@ +#include +#include "hs_BRepOffsetAPI_ThruSections.h" + +BRepOffsetAPI_ThruSections * hs_new_BRepOffsetAPI_ThruSections(bool isSolid, bool ruled, double pres3d){ + return new BRepOffsetAPI_ThruSections(isSolid, ruled, pres3d); +} + +void hs_delete_BRepOffsetAPI_ThruSections(BRepOffsetAPI_ThruSections* thruSections){ + delete thruSections; +} + +void hs_BRepOffsetAPI_ThruSections_addWire(BRepOffsetAPI_ThruSections* thruSections, TopoDS_Wire * wire){ + thruSections->AddWire(*wire); +} + +void hs_BRepOffsetAPI_ThruSections_addVertex(BRepOffsetAPI_ThruSections* thruSections, TopoDS_Vertex* vertex){ + thruSections->AddVertex(*vertex); +} \ No newline at end of file diff --git a/opencascade-hs/cpp/hs_BRepOffsetAPI_ThruSections.h b/opencascade-hs/cpp/hs_BRepOffsetAPI_ThruSections.h new file mode 100644 index 0000000..aef8bdf --- /dev/null +++ b/opencascade-hs/cpp/hs_BRepOffsetAPI_ThruSections.h @@ -0,0 +1,23 @@ + +#ifndef HS_BREPOFFSETAPI_THRUSECTIONS_H +#define HS_BREPOFFSETAPI_THRUSECTIONS_H + +#include "hs_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +BRepOffsetAPI_ThruSections * hs_new_BRepOffsetAPI_ThruSections(bool isSolid, bool ruled, double pres3d); + +void hs_delete_BRepOffsetAPI_ThruSections(BRepOffsetAPI_ThruSections* thruSections); + +void hs_BRepOffsetAPI_ThruSections_addWire(BRepOffsetAPI_ThruSections* thruSections, TopoDS_Wire * wire); + +void hs_BRepOffsetAPI_ThruSections_addVertex(BRepOffsetAPI_ThruSections* thruSections, TopoDS_Vertex* vertex); + +#ifdef __cplusplus +} +#endif + +#endif // HS_BREPOFFSETAPI_THRUSECTIONS_H diff --git a/opencascade-hs/cpp/hs_types.h b/opencascade-hs/cpp/hs_types.h index 92a8218..7afa683 100644 --- a/opencascade-hs/cpp/hs_types.h +++ b/opencascade-hs/cpp/hs_types.h @@ -32,6 +32,7 @@ typedef void gp_Trsf2d; typedef void gp_XYZ; typedef void BRep_Builder; typedef void BRepBuilderAPI_Transform; +typedef void BRepBuilderAPI_MakeVertex; typedef void BRepBuilderAPI_MakeWire; typedef void BRepBuilderAPI_MakeFace; typedef void BRepBuilderAPI_MakeSolid; diff --git a/opencascade-hs/opencascade-hs.cabal b/opencascade-hs/opencascade-hs.cabal index f40e394..6ddaa86 100644 --- a/opencascade-hs/opencascade-hs.cabal +++ b/opencascade-hs/opencascade-hs.cabal @@ -35,6 +35,7 @@ extra-source-files: cpp/hs_BRepBuilderAPI_MakePolygon.h cpp/hs_BRepBuilderAPI_MakeShape.h cpp/hs_BRepBuilderAPI_MakeSolid.h + cpp/hs_BRepBuilderAPI_MakeVertex.h cpp/hs_BRepBuilderAPI_MakeWire.h cpp/hs_BRepBuilderAPI_Sewing.h cpp/hs_BRepBuilderAPI_Transform.h @@ -44,6 +45,7 @@ extra-source-files: cpp/hs_BRepMesh_IncrementalMesh.h cpp/hs_BRepOffsetAPI_MakeOffsetShape.h cpp/hs_BRepOffsetAPI_MakePipe.h + cpp/hs_BRepOffsetAPI_ThruSections.h cpp/hs_BRepPrimAPI_MakeBox.h cpp/hs_BRepPrimAPI_MakeCone.h cpp/hs_BRepPrimAPI_MakeCylinder.h @@ -134,6 +136,7 @@ library OpenCascade.BRepBuilderAPI.MakePolygon OpenCascade.BRepBuilderAPI.MakeShape OpenCascade.BRepBuilderAPI.MakeSolid + OpenCascade.BRepBuilderAPI.MakeVertex OpenCascade.BRepBuilderAPI.MakeWire OpenCascade.BRepBuilderAPI.Sewing OpenCascade.BRepBuilderAPI.Transform @@ -154,6 +157,7 @@ library OpenCascade.BRepOffsetAPI.Internal.Destructors OpenCascade.BRepOffsetAPI.MakeOffsetShape OpenCascade.BRepOffsetAPI.MakePipe + OpenCascade.BRepOffsetAPI.ThruSections OpenCascade.BRepOffsetAPI.Types OpenCascade.BRepPrimAPI OpenCascade.BRepPrimAPI.Internal.Destructors @@ -310,6 +314,7 @@ library cpp/hs_BRepBuilderAPI_MakePolygon.cpp cpp/hs_BRepBuilderAPI_MakeShape.cpp cpp/hs_BRepBuilderAPI_MakeSolid.cpp + cpp/hs_BRepBuilderAPI_MakeVertex.cpp cpp/hs_BRepBuilderAPI_MakeWire.cpp cpp/hs_BRepBuilderAPI_Sewing.cpp cpp/hs_BRepBuilderAPI_Transform.cpp @@ -319,6 +324,7 @@ library cpp/hs_BRepMesh_IncrementalMesh.cpp cpp/hs_BRepOffsetAPI_MakeOffsetShape.cpp cpp/hs_BRepOffsetAPI_MakePipe.cpp + cpp/hs_BRepOffsetAPI_ThruSections.cpp cpp/hs_BRepPrimAPI_MakeBox.cpp cpp/hs_BRepPrimAPI_MakeCone.cpp cpp/hs_BRepPrimAPI_MakeCylinder.cpp diff --git a/opencascade-hs/src/OpenCascade/BRepBuilderAPI/Internal/Destructors.hs b/opencascade-hs/src/OpenCascade/BRepBuilderAPI/Internal/Destructors.hs index 5119edf..0d411c2 100644 --- a/opencascade-hs/src/OpenCascade/BRepBuilderAPI/Internal/Destructors.hs +++ b/opencascade-hs/src/OpenCascade/BRepBuilderAPI/Internal/Destructors.hs @@ -1,6 +1,7 @@ {-# LANGUAGE CApiFFI #-} module OpenCascade.BRepBuilderAPI.Internal.Destructors -( deleteMakeWire +( deleteMakeVertex +, deleteMakeWire , deleteMakeFace , deleteMakeSolid , deleteSewing @@ -10,6 +11,7 @@ import OpenCascade.BRepBuilderAPI.Types import Foreign.Ptr +foreign import capi unsafe "hs_BRepBuilderAPI_MakeVertex.h hs_delete_BRepBuilderAPI_MakeVertex" deleteMakeVertex :: Ptr MakeVertex -> IO () foreign import capi unsafe "hs_BRepBuilderAPI_MakeWire.h hs_delete_BRepBuilderAPI_MakeWire" deleteMakeWire :: Ptr MakeWire -> IO () foreign import capi unsafe "hs_BRepBuilderAPI_MakeFace.h hs_delete_BRepBuilderAPI_MakeFace" deleteMakeFace :: Ptr MakeFace -> IO () foreign import capi unsafe "hs_BRepBuilderAPI_MakeSolid.h hs_delete_BRepBuilderAPI_MakeSolid" deleteMakeSolid :: Ptr MakeSolid -> IO () diff --git a/opencascade-hs/src/OpenCascade/BRepBuilderAPI/MakeVertex.hs b/opencascade-hs/src/OpenCascade/BRepBuilderAPI/MakeVertex.hs new file mode 100644 index 0000000..f8e974c --- /dev/null +++ b/opencascade-hs/src/OpenCascade/BRepBuilderAPI/MakeVertex.hs @@ -0,0 +1,26 @@ +{-# LANGUAGE CApiFFI #-} +module OpenCascade.BRepBuilderAPI.MakeVertex +( MakeVertex +, fromPnt +, vertex +) where + +import OpenCascade.BRepBuilderAPI.Types (MakeVertex) +import OpenCascade.BRepBuilderAPI.Internal.Destructors (deleteMakeVertex) +import qualified OpenCascade.GP as GP +import qualified OpenCascade.TopoDS as TopoDS +import qualified OpenCascade.TopoDS.Internal.Destructors as TopoDS.Destructors +import Foreign.Ptr (Ptr) +import Data.Acquire (Acquire, mkAcquire) +import OpenCascade.Inheritance (upcast) + +foreign import capi unsafe "hs_BRepBuilderAPI_MakeVertex.h hs_new_BRepBuilderAPI_MakeVertex_fromPnt" rawFromPnt :: Ptr GP.Pnt -> IO (Ptr MakeVertex) + +fromPnt :: Ptr GP.Pnt -> Acquire (Ptr MakeVertex) +fromPnt pnt = mkAcquire (rawFromPnt pnt) (deleteMakeVertex) + + +foreign import capi unsafe "hs_BRepBuilderAPI_MakeVertex.h hs_BRepBuilderAPI_MakeVertex_vertex" rawVertex :: Ptr MakeVertex -> IO (Ptr TopoDS.Vertex) + +vertex :: Ptr MakeVertex -> Acquire (Ptr TopoDS.Vertex) +vertex builder = mkAcquire (rawVertex builder) (TopoDS.Destructors.deleteShape . upcast) diff --git a/opencascade-hs/src/OpenCascade/BRepBuilderAPI/Types.hs b/opencascade-hs/src/OpenCascade/BRepBuilderAPI/Types.hs index ac6524f..9a2095d 100644 --- a/opencascade-hs/src/OpenCascade/BRepBuilderAPI/Types.hs +++ b/opencascade-hs/src/OpenCascade/BRepBuilderAPI/Types.hs @@ -1,7 +1,8 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE EmptyDataDecls #-} module OpenCascade.BRepBuilderAPI.Types -( MakeWire +( MakeVertex +, MakeWire , MakeFace , MakeSolid , MakeShape @@ -10,6 +11,7 @@ module OpenCascade.BRepBuilderAPI.Types import qualified OpenCascade.Inheritance as Inheritance +data MakeVertex data MakeWire data MakeFace data MakeSolid @@ -18,6 +20,7 @@ data MakeShape data Sewing +instance Inheritance.SubTypeOf MakeShape MakeVertex instance Inheritance.SubTypeOf MakeShape MakeWire instance Inheritance.SubTypeOf MakeShape MakeSolid instance Inheritance.SubTypeOf MakeShape MakeFace diff --git a/opencascade-hs/src/OpenCascade/BRepOffsetAPI/Internal/Destructors.hs b/opencascade-hs/src/OpenCascade/BRepOffsetAPI/Internal/Destructors.hs index e1ca3ae..3f49bbb 100644 --- a/opencascade-hs/src/OpenCascade/BRepOffsetAPI/Internal/Destructors.hs +++ b/opencascade-hs/src/OpenCascade/BRepOffsetAPI/Internal/Destructors.hs @@ -2,6 +2,7 @@ module OpenCascade.BRepOffsetAPI.Internal.Destructors ( deleteMakePipe , deleteMakeOffsetShape +, deleteThruSections ) where import OpenCascade.BRepOffsetAPI.Types @@ -10,5 +11,4 @@ import Foreign.Ptr foreign import capi unsafe "hs_BRepOffsetAPI_MakePipe.h hs_delete_BRepOffsetAPI_MakePipe" deleteMakePipe :: Ptr MakePipe -> IO () foreign import capi unsafe "hs_BRepOffsetAPI_MakeOffsetShape.h hs_delete_BRepOffsetAPI_MakeOffsetShape" deleteMakeOffsetShape :: Ptr MakeOffsetShape -> IO () - - +foreign import capi unsafe "hs_BRepOffsetAPI_ThruSections.h hs_delete_BRepOffsetAPI_ThruSections" deleteThruSections :: Ptr ThruSections -> IO () diff --git a/opencascade-hs/src/OpenCascade/BRepOffsetAPI/ThruSections.hs b/opencascade-hs/src/OpenCascade/BRepOffsetAPI/ThruSections.hs new file mode 100644 index 0000000..9ba68f6 --- /dev/null +++ b/opencascade-hs/src/OpenCascade/BRepOffsetAPI/ThruSections.hs @@ -0,0 +1,27 @@ +{-# LANGUAGE CApiFFI #-} +module OpenCascade.BRepOffsetAPI.ThruSections +( ThruSections +, new +, addWire +, addVertex +) where + + +import OpenCascade.BRepOffsetAPI.Types (ThruSections) +import OpenCascade.BRepOffsetAPI.Internal.Destructors (deleteThruSections) +import qualified OpenCascade.TopoDS as TopoDS +import Foreign.Ptr +import Foreign.C (CBool (..), CDouble (..)) +import Data.Acquire +import OpenCascade.Internal.Bool (boolToCBool) +import Data.Coerce (coerce) + + +foreign import capi unsafe "hs_BRepOffsetAPI_ThruSections.h hs_new_BRepOffsetAPI_ThruSections" rawNew :: CBool -> CBool -> CDouble -> IO (Ptr ThruSections) + +new :: Bool -> Bool -> Double -> Acquire (Ptr ThruSections) +new makeSolid ruled precision = mkAcquire (rawNew (boolToCBool makeSolid) (boolToCBool ruled) (coerce precision)) deleteThruSections + +foreign import capi unsafe "hs_BRepOffsetAPI_ThruSections.h hs_BRepOffsetAPI_ThruSections_addWire" addWire :: Ptr ThruSections -> Ptr TopoDS.Wire -> IO () + +foreign import capi unsafe "hs_BRepOffsetAPI_ThruSections.h hs_BRepOffsetAPI_ThruSections_addVertex" addVertex :: Ptr ThruSections -> Ptr TopoDS.Vertex -> IO () \ No newline at end of file diff --git a/opencascade-hs/src/OpenCascade/BRepOffsetAPI/Types.hs b/opencascade-hs/src/OpenCascade/BRepOffsetAPI/Types.hs index d69b4a6..b5972f4 100644 --- a/opencascade-hs/src/OpenCascade/BRepOffsetAPI/Types.hs +++ b/opencascade-hs/src/OpenCascade/BRepOffsetAPI/Types.hs @@ -3,6 +3,7 @@ module OpenCascade.BRepOffsetAPI.Types ( MakePipe , MakeOffsetShape +, ThruSections ) where import qualified OpenCascade.Inheritance as Inheritance @@ -10,6 +11,8 @@ import OpenCascade.BRepBuilderAPI.MakeShape (MakeShape) data MakePipe data MakeOffsetShape +data ThruSections instance Inheritance.SubTypeOf MakeShape MakePipe -instance Inheritance.SubTypeOf MakeShape MakeOffsetShape \ No newline at end of file +instance Inheritance.SubTypeOf MakeShape MakeOffsetShape +instance Inheritance.SubTypeOf MakeShape ThruSections \ No newline at end of file diff --git a/waterfall-cad-examples/app/Main.hs b/waterfall-cad-examples/app/Main.hs index 1eeae31..b82ddf6 100644 --- a/waterfall-cad-examples/app/Main.hs +++ b/waterfall-cad-examples/app/Main.hs @@ -7,6 +7,7 @@ import FilletExample (filletExample) import RevolutionExample (revolutionExample) import SweepExample (sweepExample) import OffsetExample (offsetExample) +import LoftExample (loftExample) import TextExample (textExample) import BoundingBoxExample (boundingBoxExample) import ReadSolidExpressionExample (readSolidExpressionExample) @@ -37,6 +38,7 @@ exampleOption = OA.flag' revolutionExample (OA.long "revolution" <> OA.help "demonstrates revolving a path into a solid" ) <|> OA.flag' sweepExample (OA.long "sweep" <> OA.help "demonstrates sweeping a shape along a path" ) <|> OA.flag' offsetExample (OA.long "offset" <> OA.help "demonstrates offsetting the surface of a shape" ) <|> + OA.flag' loftExample (OA.long "loft" <> OA.help "demonstrates generating a loft from a series of paths" ) <|> OA.flag' boundingBoxExample (OA.long "bound" <> OA.help "demonstrates calculating the oriented bounding box, and axis aligned bounding box, of a shape" ) <|> (OA.flag' gearExample (OA.long "gear" <> OA.help "generate an involute gear") <*> (OA.option OA.auto (OA.long "thickness" <> OA.help "gear depth") <|> pure 1.0) <*> diff --git a/waterfall-cad-examples/src/LoftExample.hs b/waterfall-cad-examples/src/LoftExample.hs new file mode 100644 index 0000000..d640860 --- /dev/null +++ b/waterfall-cad-examples/src/LoftExample.hs @@ -0,0 +1,49 @@ +module LoftExample +( loftExample +) where + +import Linear (V3 (..)) +import qualified Waterfall.Transforms as Transforms +import qualified Waterfall.TwoD.Transforms as Transforms2D +import qualified Waterfall.Booleans as Booleans +import qualified Waterfall.Solids as Solids +import qualified Waterfall.Sweep as Sweep +import qualified Waterfall.TwoD.Shape as Shape +import qualified Waterfall.Loft as Loft +import qualified Waterfall.Path.Common as Path + +loftExample :: Solids.Solid +loftExample = + let precision = 1e-6 + paths = [ let p x z = V3 x 0 z + in Transforms.rotate (V3 1 0 0) 0.4 $ Path.pathFrom (p 0 0) + [ Path.lineTo (p 2 0) + , Path.bezierTo (p 4 0) (p 5 3) (p 5 4) + , Path.lineTo (p (-5) 4) + , Path.bezierTo (p (-5) 3) (p (-4) 0) (p (-2) 0) + , Path.lineTo (p 0 0) + ] + , let p x z = V3 x 1 z + in Path.pathFrom (p 0 0) + [ Path.lineTo (p 2 0) + , Path.bezierTo (p 4 0) (p 5 3) (p 5 4) + , Path.lineTo (p (-5) 4) + , Path.bezierTo (p (-5) 3) (p (-4) 0) (p (-2) 0) + , Path.lineTo (p 0 0) + ] + + , let p x z = V3 x 20 z + in Path.pathFrom (p 0 0) + [ Path.lineTo (p 5 0) + , Path.bezierTo (p 6 0) (p 6 3) (p 6 4) + , Path.lineTo (p (-6) 4) + , Path.bezierTo (p (-6) 3) (p (-6) 0) (p (-5) 0) + , Path.lineTo (p 0 0) + ] + ] + body = + Loft.pointedLoft + precision + Nothing + paths (Just (V3 0 30 6)) + in body `Booleans.difference` (Transforms.translate (V3 0 (0.025 * 30) 0.5) $ Transforms.uScale 0.95 body) \ No newline at end of file diff --git a/waterfall-cad-examples/waterfall-cad-examples.cabal b/waterfall-cad-examples/waterfall-cad-examples.cabal index 0250444..1dd27f4 100644 --- a/waterfall-cad-examples/waterfall-cad-examples.cabal +++ b/waterfall-cad-examples/waterfall-cad-examples.cabal @@ -32,6 +32,7 @@ library CsgExample FilletExample GearExample + LoftExample OffsetExample PrismExample ReadSolidExpressionExample diff --git a/waterfall-cad/src/Waterfall/Internal/ToOpenCascade.hs b/waterfall-cad/src/Waterfall/Internal/ToOpenCascade.hs new file mode 100644 index 0000000..7595021 --- /dev/null +++ b/waterfall-cad/src/Waterfall/Internal/ToOpenCascade.hs @@ -0,0 +1,18 @@ +module Waterfall.Internal.ToOpenCascade +( v3ToVertex +) where + +import Linear (V3 (..)) +import Data.Acquire (Acquire, mkAcquire) +import Foreign.Ptr (Ptr) +import qualified OpenCascade.TopoDS as TopoDS +import qualified OpenCascade.GP.Pnt as GP.Pnt +import qualified OpenCascade.BRepBuilderAPI.MakeVertex as MakeVertex + +v3ToVertex :: V3 Double -> Acquire (Ptr TopoDS.Vertex) +v3ToVertex (V3 x y z) = do + pnt <- GP.Pnt.new x y z + builder <- MakeVertex.fromPnt pnt + MakeVertex.vertex builder + + \ No newline at end of file diff --git a/waterfall-cad/src/Waterfall/Loft.hs b/waterfall-cad/src/Waterfall/Loft.hs new file mode 100644 index 0000000..a159225 --- /dev/null +++ b/waterfall-cad/src/Waterfall/Loft.hs @@ -0,0 +1,26 @@ +module Waterfall.Loft +( pointedLoft +, loft +) where + +import Linear (V3 (..)) +import Waterfall.Internal.Path (Path, rawPath) +import Waterfall.Internal.Solid (Solid (..), solidFromAcquire) +import Waterfall.Internal.ToOpenCascade (v3ToVertex) +import qualified OpenCascade.BRepOffsetAPI.ThruSections as ThruSections +import qualified OpenCascade.BRepBuilderAPI.MakeShape as MakeShape +import OpenCascade.Inheritance (upcast) +import Control.Monad.IO.Class (liftIO) +import Control.Monad (forM_, (<=<)) + +pointedLoft :: Double -> Maybe (V3 Double) -> [Path] -> Maybe (V3 Double) -> Solid +pointedLoft precision start paths end = + solidFromAcquire $ do + thruSections <- ThruSections.new True False precision + forM_ start ((liftIO . ThruSections.addVertex thruSections) <=< v3ToVertex) + forM_ paths (liftIO . ThruSections.addWire thruSections . rawPath) + forM_ end ((liftIO . ThruSections.addVertex thruSections) <=< v3ToVertex) + MakeShape.shape (upcast thruSections) + +loft :: Double -> [Path] -> Solid +loft precision paths = pointedLoft precision Nothing paths Nothing \ No newline at end of file diff --git a/waterfall-cad/waterfall-cad.cabal b/waterfall-cad/waterfall-cad.cabal index 0aa39dd..4a2c41d 100644 --- a/waterfall-cad/waterfall-cad.cabal +++ b/waterfall-cad/waterfall-cad.cabal @@ -40,7 +40,9 @@ library Waterfall.Internal.Path Waterfall.Internal.Remesh Waterfall.Internal.Solid + Waterfall.Internal.ToOpenCascade Waterfall.IO + Waterfall.Loft Waterfall.Offset Waterfall.Path Waterfall.Path.Common From 36d02decb7e91f44bf6e704872d6243f09c71aaa Mon Sep 17 00:00:00 2001 From: Joseph Warren Date: Thu, 25 Jul 2024 02:52:05 +0100 Subject: [PATCH 02/13] Add ability to reverse paths, simplify loft example code --- opencascade-hs/cpp/hs_Geom_Curve.cpp | 8 +++ opencascade-hs/cpp/hs_Geom_Curve.h | 5 ++ .../cpp/hs_ShapeExtend_WireData.cpp | 25 +++++++++ opencascade-hs/cpp/hs_ShapeExtend_WireData.h | 24 +++++++++ opencascade-hs/cpp/hs_types.h | 1 + opencascade-hs/opencascade-hs.cabal | 5 ++ opencascade-hs/src/OpenCascade/Geom/Curve.hs | 17 +++++- .../ShapeExtend/Internal/Destructors.hs | 10 ++++ .../src/OpenCascade/ShapeExtend/Types.hs | 6 +++ .../src/OpenCascade/ShapeExtend/WireData.hs | 39 ++++++++++++++ waterfall-cad-examples/app/Main.hs | 2 +- waterfall-cad-examples/src/LoftExample.hs | 54 +++++++++---------- waterfall-cad/src/Waterfall/Internal/Edges.hs | 49 +++++++++++++++-- waterfall-cad/src/Waterfall/Path.hs | 5 +- waterfall-cad/src/Waterfall/Path/Common.hs | 24 ++++++++- waterfall-cad/src/Waterfall/TwoD/Path2D.hs | 14 +++-- 16 files changed, 244 insertions(+), 44 deletions(-) create mode 100644 opencascade-hs/cpp/hs_ShapeExtend_WireData.cpp create mode 100644 opencascade-hs/cpp/hs_ShapeExtend_WireData.h create mode 100644 opencascade-hs/src/OpenCascade/ShapeExtend/Internal/Destructors.hs create mode 100644 opencascade-hs/src/OpenCascade/ShapeExtend/Types.hs create mode 100644 opencascade-hs/src/OpenCascade/ShapeExtend/WireData.hs diff --git a/opencascade-hs/cpp/hs_Geom_Curve.cpp b/opencascade-hs/cpp/hs_Geom_Curve.cpp index 9003730..f167baa 100644 --- a/opencascade-hs/cpp/hs_Geom_Curve.cpp +++ b/opencascade-hs/cpp/hs_Geom_Curve.cpp @@ -14,3 +14,11 @@ gp_Pnt * hs_Geom_Curve_value(Handle(Geom_Curve) * curve, double u){ gp_Vec * hs_Geom_Curve_dn(Handle (Geom_Curve) * curve, double u, int n){ return new gp_Vec((*curve)->DN(u, n)); } + +double hs_Geom_Curve_reversedParameter(Handle (Geom_Curve) * curve, double parameter){ + return (*curve)->ReversedParameter(parameter); +} + +Handle (Geom_Curve) * hs_Geom_Curve_reversed(Handle (Geom_Curve) * curve){ + return new opencascade::handle((*curve)->Reversed()); +} \ No newline at end of file diff --git a/opencascade-hs/cpp/hs_Geom_Curve.h b/opencascade-hs/cpp/hs_Geom_Curve.h index e311ed2..485a5e5 100644 --- a/opencascade-hs/cpp/hs_Geom_Curve.h +++ b/opencascade-hs/cpp/hs_Geom_Curve.h @@ -11,6 +11,11 @@ void hs_delete_Handle_Geom_Curve(Handle(Geom_Curve) * handle); gp_Pnt * hs_Geom_Curve_value(Handle(Geom_Curve) * curve, double u); gp_Vec * hs_Geom_Curve_dn(Handle (Geom_Curve) * curve, double u, int n); + +double hs_Geom_Curve_reversedParameter(Handle (Geom_Curve) * curve, double parameter); + +Handle (Geom_Curve) * hs_Geom_Curve_reversed(Handle (Geom_Curve) * curve); + #ifdef __cplusplus } #endif diff --git a/opencascade-hs/cpp/hs_ShapeExtend_WireData.cpp b/opencascade-hs/cpp/hs_ShapeExtend_WireData.cpp new file mode 100644 index 0000000..80de705 --- /dev/null +++ b/opencascade-hs/cpp/hs_ShapeExtend_WireData.cpp @@ -0,0 +1,25 @@ +#include +#include "hs_ShapeExtend_WireData.h" + +#include + +ShapeExtend_WireData * hs_new_ShapeExtend_WireData_fromWireChainedAndManifold(TopoDS_Wire* wire, bool chained, bool manifoldMode){ + return new ShapeExtend_WireData(*wire, chained, manifoldMode); +} + +void hs_delete_ShapeExtend_WireData(ShapeExtend_WireData * wireData){ + delete wireData; +} + +void hs_ShapeExtend_WireData_reverse(ShapeExtend_WireData * wireData){ + wireData->Reverse(); +} + +TopoDS_Wire * hs_ShapeExtend_WireData_wire(ShapeExtend_WireData * wireData){ + return new TopoDS_Wire(wireData->Wire()); +} + + +TopoDS_Wire * hs_ShapeExtend_WireData_wireAPIMake(ShapeExtend_WireData * wireData){ + return new TopoDS_Wire(wireData->WireAPIMake()); +} \ No newline at end of file diff --git a/opencascade-hs/cpp/hs_ShapeExtend_WireData.h b/opencascade-hs/cpp/hs_ShapeExtend_WireData.h new file mode 100644 index 0000000..77b1fee --- /dev/null +++ b/opencascade-hs/cpp/hs_ShapeExtend_WireData.h @@ -0,0 +1,24 @@ +#ifndef HS_SHAPEEXTEND_WIREDATA_H +#define HS_SHAPEEXTEND_WIREDATA_H + +#include "hs_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +ShapeExtend_WireData * hs_new_ShapeExtend_WireData_fromWireChainedAndManifold(TopoDS_Wire* wire, bool chained, bool manifoldMode); + +void hs_delete_ShapeExtend_WireData(ShapeExtend_WireData * wireData); + +void hs_ShapeExtend_WireData_reverse(ShapeExtend_WireData * wireData); + +TopoDS_Wire * hs_ShapeExtend_WireData_wire(ShapeExtend_WireData * wireData); + +TopoDS_Wire * hs_ShapeExtend_WireData_wireAPIMake(ShapeExtend_WireData * wireData); + +#ifdef __cplusplus +} +#endif + +#endif // HS_SHAPEEXTEND_WIREDATA_H \ No newline at end of file diff --git a/opencascade-hs/cpp/hs_types.h b/opencascade-hs/cpp/hs_types.h index 7afa683..fba166f 100644 --- a/opencascade-hs/cpp/hs_types.h +++ b/opencascade-hs/cpp/hs_types.h @@ -83,6 +83,7 @@ typedef void TDF_Label; typedef void TDocStd_Document; typedef void XCAFDoc_ShapeTool; typedef void ShapeFix_Solid; +typedef void ShapeExtend_WireData; typedef int ShapeExtend_Status; typedef void Poly_Triangulation; typedef void Poly_Triangle; diff --git a/opencascade-hs/opencascade-hs.cabal b/opencascade-hs/opencascade-hs.cabal index 6ddaa86..1e6cd19 100644 --- a/opencascade-hs/opencascade-hs.cabal +++ b/opencascade-hs/opencascade-hs.cabal @@ -85,6 +85,7 @@ extra-source-files: cpp/hs_RWMesh_CafReader.h cpp/hs_RWObj_CafReader.h cpp/hs_RWObj_CafWriter.h + cpp/hs_ShapeExtend_WireData.h cpp/hs_ShapeFix_Solid.h cpp/hs_STEPControl_Reader.h cpp/hs_STEPControl_Writer.h @@ -232,7 +233,10 @@ library OpenCascade.RWObj.CafWriter OpenCascade.RWObj.Internal.Destructors OpenCascade.RWObj.Types + OpenCascade.ShapeExtend.Internal.Destructors OpenCascade.ShapeExtend.Status + OpenCascade.ShapeExtend.Types + OpenCascade.ShapeExtend.WireData OpenCascade.ShapeFix.Internal.Destructors OpenCascade.ShapeFix.Solid OpenCascade.ShapeFix.Types @@ -364,6 +368,7 @@ library cpp/hs_RWMesh_CafReader.cpp cpp/hs_RWObj_CafReader.cpp cpp/hs_RWObj_CafWriter.cpp + cpp/hs_ShapeExtend_WireData.cpp cpp/hs_ShapeFix_Solid.cpp cpp/hs_STEPControl_Reader.cpp cpp/hs_STEPControl_Writer.cpp diff --git a/opencascade-hs/src/OpenCascade/Geom/Curve.hs b/opencascade-hs/src/OpenCascade/Geom/Curve.hs index 8f5bb48..257b4ff 100644 --- a/opencascade-hs/src/OpenCascade/Geom/Curve.hs +++ b/opencascade-hs/src/OpenCascade/Geom/Curve.hs @@ -2,6 +2,8 @@ module OpenCascade.Geom.Curve ( value , dn +, reversedParameter +, reversed ) where import Foreign.Ptr import Foreign.C @@ -10,7 +12,9 @@ import Data.Acquire import OpenCascade.Geom.Types (Curve) import OpenCascade.GP (Pnt, Vec) import OpenCascade.GP.Internal.Destructors (deletePnt, deleteVec) +import OpenCascade.Geom.Internal.Destructors (deleteHandleCurve) import OpenCascade.Handle (Handle) + foreign import capi unsafe "hs_Geom_Curve.h hs_Geom_Curve_value" rawValue :: Ptr (Handle Curve) -> CDouble -> IO(Ptr Pnt) value :: Ptr (Handle Curve) -> Double -> Acquire (Ptr Pnt) @@ -19,4 +23,15 @@ value curve u = mkAcquire (rawValue curve (coerce u)) deletePnt foreign import capi unsafe "hs_Geom_Curve.h hs_Geom_Curve_dn" rawDN :: Ptr (Handle Curve) -> CDouble -> CInt -> IO (Ptr Vec) dn :: Ptr (Handle Curve) -> Double -> Int -> Acquire (Ptr Vec) -dn curve u n = mkAcquire (rawDN curve (coerce u) (fromIntegral n)) deleteVec \ No newline at end of file +dn curve u n = mkAcquire (rawDN curve (coerce u) (fromIntegral n)) deleteVec + +foreign import capi unsafe "hs_Geom_Curve.h hs_Geom_Curve_reversedParameter" rawReversedParameter :: Ptr (Handle Curve) -> CDouble -> IO CDouble + +reversedParameter :: Ptr (Handle Curve) -> Double -> IO Double +reversedParameter = coerce rawReversedParameter + + +foreign import capi unsafe "hs_Geom_Curve.h hs_Geom_Curve_reversed" rawReversed :: Ptr (Handle Curve) -> IO (Ptr (Handle Curve)) + +reversed :: Ptr (Handle Curve) -> Acquire (Ptr (Handle Curve)) +reversed c = mkAcquire (rawReversed c) deleteHandleCurve diff --git a/opencascade-hs/src/OpenCascade/ShapeExtend/Internal/Destructors.hs b/opencascade-hs/src/OpenCascade/ShapeExtend/Internal/Destructors.hs new file mode 100644 index 0000000..8d837d2 --- /dev/null +++ b/opencascade-hs/src/OpenCascade/ShapeExtend/Internal/Destructors.hs @@ -0,0 +1,10 @@ +{-# LANGUAGE CApiFFI #-} +module OpenCascade.ShapeExtend.Internal.Destructors +( deleteWireData +) where + +import OpenCascade.ShapeExtend.Types (WireData) + +import Foreign.Ptr + +foreign import capi unsafe "hs_ShapeExtend_WireData.h hs_delete_ShapeExtend_WireData" deleteWireData :: Ptr WireData -> IO () \ No newline at end of file diff --git a/opencascade-hs/src/OpenCascade/ShapeExtend/Types.hs b/opencascade-hs/src/OpenCascade/ShapeExtend/Types.hs new file mode 100644 index 0000000..bc48b72 --- /dev/null +++ b/opencascade-hs/src/OpenCascade/ShapeExtend/Types.hs @@ -0,0 +1,6 @@ +{-# LANGUAGE EmptyDataDecls #-} +module OpenCascade.ShapeExtend.Types +( WireData +) where + +data WireData \ No newline at end of file diff --git a/opencascade-hs/src/OpenCascade/ShapeExtend/WireData.hs b/opencascade-hs/src/OpenCascade/ShapeExtend/WireData.hs new file mode 100644 index 0000000..15259a6 --- /dev/null +++ b/opencascade-hs/src/OpenCascade/ShapeExtend/WireData.hs @@ -0,0 +1,39 @@ +{-# LANGUAGE CApiFFI #-} +module OpenCascade.ShapeExtend.WireData +( WireData +, fromWireChainedAndManifold +, reverse +, wire +, wireAPIMake +) where + +import Prelude hiding (reverse) +import OpenCascade.ShapeExtend.Types (WireData) +import OpenCascade.ShapeExtend.Internal.Destructors (deleteWireData) +import qualified OpenCascade.TopoDS as TopoDS +import OpenCascade.TopoDS.Internal.Destructors (deleteShape) +import OpenCascade.Internal.Bool (boolToCBool) +import OpenCascade.Inheritance (upcast) +import Foreign.Ptr (Ptr) +import Foreign.C (CBool (..)) +import Data.Acquire (Acquire, mkAcquire) + +foreign import capi unsafe "hs_ShapeExtend_WireData.h hs_new_ShapeExtend_WireData_fromWireChainedAndManifold" rawFromWireChainedAndManifold :: Ptr TopoDS.Wire -> CBool -> CBool -> IO (Ptr WireData) + +fromWireChainedAndManifold :: Ptr TopoDS.Wire -> Bool -> Bool -> Acquire (Ptr WireData) +fromWireChainedAndManifold theWire chained manifold = + mkAcquire (rawFromWireChainedAndManifold theWire (boolToCBool chained) (boolToCBool manifold)) (deleteWireData) + +foreign import capi unsafe "hs_ShapeExtend_WireData.h hs_ShapeExtend_WireData_reverse" reverse :: Ptr WireData -> IO () + + +foreign import capi unsafe "hs_ShapeExtend_WireData.h hs_ShapeExtend_WireData_wire" rawWire :: Ptr WireData -> IO (Ptr TopoDS.Wire) + +wire :: Ptr WireData -> Acquire (Ptr TopoDS.Wire) +wire wireData = mkAcquire (rawWire wireData) (deleteShape . upcast) + + +foreign import capi unsafe "hs_ShapeExtend_WireData.h hs_ShapeExtend_WireData_wireAPIMake" rawWireAPIMake :: Ptr WireData -> IO (Ptr TopoDS.Wire) + +wireAPIMake :: Ptr WireData -> Acquire (Ptr TopoDS.Wire) +wireAPIMake wireData = mkAcquire (rawWireAPIMake wireData) (deleteShape . upcast) diff --git a/waterfall-cad-examples/app/Main.hs b/waterfall-cad-examples/app/Main.hs index b82ddf6..2858140 100644 --- a/waterfall-cad-examples/app/Main.hs +++ b/waterfall-cad-examples/app/Main.hs @@ -38,7 +38,7 @@ exampleOption = OA.flag' revolutionExample (OA.long "revolution" <> OA.help "demonstrates revolving a path into a solid" ) <|> OA.flag' sweepExample (OA.long "sweep" <> OA.help "demonstrates sweeping a shape along a path" ) <|> OA.flag' offsetExample (OA.long "offset" <> OA.help "demonstrates offsetting the surface of a shape" ) <|> - OA.flag' loftExample (OA.long "loft" <> OA.help "demonstrates generating a loft from a series of paths" ) <|> + OA.flag' loftExample (OA.long "loft" <> OA.help "generating a boat, defined as a loft of a series of paths" ) <|> OA.flag' boundingBoxExample (OA.long "bound" <> OA.help "demonstrates calculating the oriented bounding box, and axis aligned bounding box, of a shape" ) <|> (OA.flag' gearExample (OA.long "gear" <> OA.help "generate an involute gear") <*> (OA.option OA.auto (OA.long "thickness" <> OA.help "gear depth") <|> pure 1.0) <*> diff --git a/waterfall-cad-examples/src/LoftExample.hs b/waterfall-cad-examples/src/LoftExample.hs index d640860..eddc332 100644 --- a/waterfall-cad-examples/src/LoftExample.hs +++ b/waterfall-cad-examples/src/LoftExample.hs @@ -12,38 +12,36 @@ import qualified Waterfall.TwoD.Shape as Shape import qualified Waterfall.Loft as Loft import qualified Waterfall.Path.Common as Path +-- Build a boat, with the profile of the boat defined using a series of bezier curves loftExample :: Solids.Solid loftExample = let precision = 1e-6 - paths = [ let p x z = V3 x 0 z - in Transforms.rotate (V3 1 0 0) 0.4 $ Path.pathFrom (p 0 0) - [ Path.lineTo (p 2 0) - , Path.bezierTo (p 4 0) (p 5 3) (p 5 4) - , Path.lineTo (p (-5) 4) - , Path.bezierTo (p (-5) 3) (p (-4) 0) (p (-2) 0) - , Path.lineTo (p 0 0) - ] - , let p x z = V3 x 1 z - in Path.pathFrom (p 0 0) - [ Path.lineTo (p 2 0) - , Path.bezierTo (p 4 0) (p 5 3) (p 5 4) - , Path.lineTo (p (-5) 4) - , Path.bezierTo (p (-5) 3) (p (-4) 0) (p (-2) 0) - , Path.lineTo (p 0 0) - ] - - , let p x z = V3 x 20 z - in Path.pathFrom (p 0 0) - [ Path.lineTo (p 5 0) - , Path.bezierTo (p 6 0) (p 6 3) (p 6 4) - , Path.lineTo (p (-6) 4) - , Path.bezierTo (p (-6) 3) (p (-6) 0) (p (-5) 0) - , Path.lineTo (p 0 0) - ] - ] + paths = + [ let p x z = V3 x 0 z + -- the curve at the rear of the boat is tilted _slightly_ back + in Transforms.rotate (V3 1 0 0) 0.2 $ + Path.bezier (p 0 0) (p 4 0) (p 5 3) (p 5 4) + , let p x z = V3 x 2 z + in Path.bezier (p 0 0) (p 4 0) (p 5 3) (p 5 4) + , let p x z = V3 x 7.5 z + in Path.bezier (p 0 0) (p 4 0) (p 5 3) (p 5 4) + , let p x z = V3 x 20 z + in Path.bezier (p 0 0) (p 5 0) (p 5.5 3) (p 5.5 4.2) + ] + mirror = Path.reversePath . Transforms.mirror (V3 1 0 0 ) + makeSymetric p = mirror p <> p + symetricPaths = makeSymetric <$> paths body = Loft.pointedLoft precision Nothing - paths (Just (V3 0 30 6)) - in body `Booleans.difference` (Transforms.translate (V3 0 (0.025 * 30) 0.5) $ Transforms.uScale 0.95 body) \ No newline at end of file + ( Path.closeLoop <$> symetricPaths) + (Just (V3 0 30 5)) + -- shrink the boat shape slightly, and translate it + -- use this to hollow out the boat + cavity = Transforms.translate (V3 0 (0.025 * 30) 0.3) $ Transforms.uScale 0.95 body + -- sweep a circle along each of the paths, this makes them visible in the generated model + sweepWithCircle = (`Sweep.sweep` Transforms2D.uScale2D 0.2 Shape.unitCircle) + splines = mconcat $ sweepWithCircle <$> symetricPaths + in Transforms.uScale 0.1 $ + (body <> splines) `Booleans.difference` cavity \ No newline at end of file diff --git a/waterfall-cad/src/Waterfall/Internal/Edges.hs b/waterfall-cad/src/Waterfall/Internal/Edges.hs index ecaf843..f6152ce 100644 --- a/waterfall-cad/src/Waterfall/Internal/Edges.hs +++ b/waterfall-cad/src/Waterfall/Internal/Edges.hs @@ -2,18 +2,26 @@ module Waterfall.Internal.Edges ( edgeEndpoints , wireEndpoints , wireTangent +, reverseEdge +, reverseWire ) where import qualified OpenCascade.TopoDS as TopoDS +import qualified OpenCascade.TopoDS.Shape as TopoDS.Shape import qualified OpenCascade.BRep.Tool as BRep.Tool import qualified OpenCascade.Geom.Curve as Geom.Curve import qualified OpenCascade.BRepTools.WireExplorer as WireExplorer +import qualified OpenCascade.ShapeExtend.WireData as WireData +import qualified OpenCascade.BRepBuilderAPI.MakeEdge as MakeEdge import Waterfall.Internal.FromOpenCascade (gpPntToV3, gpVecToV3) import Data.Acquire import Control.Monad.IO.Class (liftIO) import Linear (V3 (..)) import Foreign.Ptr - +import OpenCascade.Inheritance (unsafeDowncast, upcast) +import qualified OpenCascade.BRepBuilderAPI.MakeEdge as MakeEdge +import qualified OpenCascade.BRepBuilderAPI.MakeWire as MakeWire +import Control.Monad (when) edgeEndpoints :: Ptr TopoDS.Edge -> IO (V3 Double, V3 Double) @@ -40,7 +48,6 @@ wireEndpoints wire = with (WireExplorer.fromWire wire) $ \explorer -> do e <- runToEnd return (s, e) - edgeTangent :: Ptr TopoDS.Edge -> IO (V3 Double) edgeTangent e = (`with` pure) $ do curve <- BRep.Tool.curve e @@ -50,4 +57,40 @@ edgeTangent e = (`with` pure) $ do wireTangent :: Ptr TopoDS.Wire -> IO (V3 Double) wireTangent wire = with (WireExplorer.fromWire wire) $ \explorer -> do v1 <- WireExplorer.current explorer - edgeTangent v1 \ No newline at end of file + edgeTangent v1 + +reverseEdge :: Ptr TopoDS.Edge -> Acquire (Ptr TopoDS.Edge) +reverseEdge e = do + curve <- BRep.Tool.curve e + firstP <- liftIO $ BRep.Tool.curveParamFirst e + lastP <- liftIO $ BRep.Tool.curveParamLast e + firstP' <- liftIO $ Geom.Curve.reversedParameter curve firstP + lastP' <- liftIO $ Geom.Curve.reversedParameter curve lastP + curve' <- Geom.Curve.reversed curve + MakeEdge.fromCurveAndParameters curve' lastP' firstP' + +reverseWire :: Ptr TopoDS.Wire -> Acquire (Ptr TopoDS.Wire) +reverseWire wire = do + explorer <- WireExplorer.fromWire wire + makeWire <- MakeWire.new + let runToEnd = do + edge <- liftIO $ WireExplorer.current explorer + edge' <- reverseEdge edge + liftIO $ WireExplorer.next explorer + more <- liftIO $ WireExplorer.more explorer + when more runToEnd + liftIO $ MakeWire.addEdge makeWire edge' + runToEnd + MakeWire.wire makeWire + +{-- +reverseWire :: Ptr TopoDS.Wire -> Acquire (Ptr TopoDS.Wire) +reverseWire wire = do + wire' <- liftIO . unsafeDowncast =<< TopoDS.Shape.copy (upcast wire) + wireData <- WireData.fromWireChainedAndManifold wire' False True + liftIO $ WireData.reverse wireData + wire'' <- WireData.wire wireData + --liftIO $ TopoDS.Shape.complement (upcast wire'') + --liftIO $ TopoDS.Shape.reverse (upcast wire'') + return wire'' +--} \ No newline at end of file diff --git a/waterfall-cad/src/Waterfall/Path.hs b/waterfall-cad/src/Waterfall/Path.hs index b0666db..1986cb6 100644 --- a/waterfall-cad/src/Waterfall/Path.hs +++ b/waterfall-cad/src/Waterfall/Path.hs @@ -18,6 +18,7 @@ module Waterfall.Path , pathFrom3D , pathFromTo3D , pathEndpoints3D +, closeLoop3D ) where import Waterfall.Internal.Path (Path(..)) @@ -83,4 +84,6 @@ pathFromTo3D = pathFromTo pathEndpoints3D :: Path -> (V3 Double, V3 Double) pathEndpoints3D = pathEndpoints - +-- | `closeLoop` with the type fixed to `Path` +closeLoop3D :: Path -> Path +closeLoop3D = closeLoop \ No newline at end of file diff --git a/waterfall-cad/src/Waterfall/Path/Common.hs b/waterfall-cad/src/Waterfall/Path/Common.hs index 859da0d..180ca4a 100644 --- a/waterfall-cad/src/Waterfall/Path/Common.hs +++ b/waterfall-cad/src/Waterfall/Path/Common.hs @@ -22,9 +22,12 @@ module Waterfall.Path.Common , pathFrom , pathFromTo , pathEndpoints +, closeLoop +, reversePath ) where import Data.Acquire import qualified OpenCascade.TopoDS as TopoDS +import qualified OpenCascade.TopoDS.Shape as TopoDS.Shape import qualified OpenCascade.GP as GP import Foreign.Ptr import Waterfall.Internal.Path (Path (..)) @@ -36,14 +39,15 @@ import qualified OpenCascade.BRepBuilderAPI.MakeWire as MakeWire import Control.Monad.IO.Class (liftIO) import qualified OpenCascade.BRepBuilderAPI.MakeEdge as MakeEdge import qualified OpenCascade.GC.MakeArcOfCircle as MakeArcOfCircle -import OpenCascade.Inheritance (upcast) +import OpenCascade.Inheritance (upcast, unsafeDowncast) import qualified OpenCascade.NCollection.Array1 as NCollection.Array1 import qualified OpenCascade.Geom.BezierCurve as BezierCurve import Data.Proxy (Proxy (..)) import Linear (V3 (..), V2 (..), _xy) import qualified OpenCascade.GP.Pnt as GP.Pnt import Control.Lens ((^.)) -import Waterfall.Internal.Edges (wireEndpoints) +import Waterfall.Internal.Edges (wireEndpoints, reverseWire) +import Control.Monad ((<=<)) -- | Class used to abstract over constructing `Path` and `Path2D` -- @@ -173,6 +177,22 @@ pathEndpoints path = unsafeFromAcquire $ do (s, e) <- liftIO $ wireEndpoints wire return (v3ToPoint (Proxy :: Proxy path) s, v3ToPoint (Proxy :: Proxy path) e) + +-- | Given a path, return a new path with the endpoints joined by a straight line. +closeLoop :: (AnyPath point path, Monoid path, Eq point) => path -> path +closeLoop p = + let (s, e) = pathEndpoints p + in if s == e + then p + else p <> line e s + +reversePath :: (AnyPath point path) => path -> path +reversePath = + fromWire . ( + reverseWire + <=< toWire + ) + instance AnyPath (V3 Double) Path where fromWire :: Acquire (Ptr TopoDS.Wire) -> Path fromWire = Path . unsafeFromAcquire diff --git a/waterfall-cad/src/Waterfall/TwoD/Path2D.hs b/waterfall-cad/src/Waterfall/TwoD/Path2D.hs index b9dd438..243413a 100644 --- a/waterfall-cad/src/Waterfall/TwoD/Path2D.hs +++ b/waterfall-cad/src/Waterfall/TwoD/Path2D.hs @@ -10,7 +10,6 @@ module Waterfall.TwoD.Path2D , arcTo , arcRelative , repeatLooping -, closeLoop -- $ reexports , line2D , lineTo2D @@ -24,6 +23,7 @@ module Waterfall.TwoD.Path2D , pathFrom2D , pathFromTo2D , pathEndpoints2D +, closeLoop2D ) where import Waterfall.TwoD.Internal.Path2D (Path2D(..)) @@ -87,12 +87,6 @@ repeatLooping p = Path2D . unsafeFromAcquire $ do let times :: Integer = abs . round $ pi * 2 / a toAcquire . rawPath . mconcat $ [rotate2D (negate (fromIntegral n) * a) p | n <- [0..times]] --- | Given a path, return a new path with the endpoints joined by a straight line. -closeLoop :: Path2D -> Path2D -closeLoop p = Path2D . unsafeFromAcquire $ do - path <- toAcquire . rawPath $ p - (s, e) <- liftIO . Internal.Edges.wireEndpoints $ path - toAcquire .rawPath . mconcat $ [p, line (e ^. _xy) (s ^. _xy)] -- $reexports -- @@ -144,4 +138,8 @@ pathFromTo2D = pathFromTo -- | `pathEndpoints`, with the type fixed to `Path2D` pathEndpoints2D :: Path2D -> (V2 Double, V2 Double) -pathEndpoints2D = pathEndpoints \ No newline at end of file +pathEndpoints2D = pathEndpoints + +-- | `closeLoop` with the type fixed to `Path2D` +closeLoop2D :: Path2D -> Path2D +closeLoop2D = closeLoop \ No newline at end of file From fe4b34139e8bb69f658ea316f63c77d2d18ed06f Mon Sep 17 00:00:00 2001 From: Joseph Warren Date: Thu, 25 Jul 2024 02:55:01 +0100 Subject: [PATCH 03/13] Add loft example to readme --- README.md | 2 ++ images/loft.png | Bin 0 -> 40053 bytes 2 files changed, 2 insertions(+) create mode 100644 images/loft.png diff --git a/README.md b/README.md index 416277c..fc99004 100644 --- a/README.md +++ b/README.md @@ -67,3 +67,5 @@ If you've found the project useful, or interesting, or if you've built anything [![](images/text.png)](waterfall-cad-examples/src/TextExample.hs) [![](images/bounding_boxes.png)](waterfall-cad-examples/src/BoundingBoxExample.hs) + +[![](images/loft.png)](waterfall-cad-examples/src/LoftExample.hs) diff --git a/images/loft.png b/images/loft.png new file mode 100644 index 0000000000000000000000000000000000000000..e43ce4bea7c80ef95cd6a761448c7bf6747adb32 GIT binary patch literal 40053 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 literal 0 HcmV?d00001 From 34aeaec6f7b94c0444a2603be72e2dec3c090399 Mon Sep 17 00:00:00 2001 From: Joe Warren Date: Fri, 26 Jul 2024 01:23:13 +0100 Subject: [PATCH 04/13] Add edges one at a time when joining paths It seems to be the case that when adding a wire to a MakeWire, rather than adding all the edges simultaneously as one contigious wire, instead the code walks through the wire, and adds the edge segments one at a time. This is bad, because the edges may be added in an order that makes the resulting path discontinuous, which blows up. Instead, walk through the edges using a WireExplorer, and add them in order. I still need to hammer out the edgecases, this commit is a WIP. I'm also currently not sure I love the monoid instance on Paths, because unconnectable paths can easilly introduce errors. I think my initial plan was to automatically join non coincident paths with a line segment, although even that is messy if the line segment accidentally closes the Path --- waterfall-cad/src/Waterfall/Internal/Edges.hs | 4 +++- waterfall-cad/src/Waterfall/Internal/Path.hs | 14 +++++++++++++- waterfall-cad/src/Waterfall/Path/Common.hs | 6 +----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/waterfall-cad/src/Waterfall/Internal/Edges.hs b/waterfall-cad/src/Waterfall/Internal/Edges.hs index f6152ce..31d7ae4 100644 --- a/waterfall-cad/src/Waterfall/Internal/Edges.hs +++ b/waterfall-cad/src/Waterfall/Internal/Edges.hs @@ -81,7 +81,9 @@ reverseWire wire = do when more runToEnd liftIO $ MakeWire.addEdge makeWire edge' runToEnd - MakeWire.wire makeWire + wire' <- MakeWire.wire makeWire + liftIO $ TopoDS.Shape.reverse (upcast wire') + return wire' {-- reverseWire :: Ptr TopoDS.Wire -> Acquire (Ptr TopoDS.Wire) diff --git a/waterfall-cad/src/Waterfall/Internal/Path.hs b/waterfall-cad/src/Waterfall/Internal/Path.hs index 3225033..8e9e173 100644 --- a/waterfall-cad/src/Waterfall/Internal/Path.hs +++ b/waterfall-cad/src/Waterfall/Internal/Path.hs @@ -12,6 +12,8 @@ import Control.Monad ((<=<)) import Control.Monad.IO.Class (liftIO) import qualified OpenCascade.TopoDS as TopoDS import qualified OpenCascade.BRepBuilderAPI.MakeWire as MakeWire +import qualified OpenCascade.BRepTools.WireExplorer as WireExplorer +import Control.Monad (when) import Foreign.Ptr import Data.Semigroup (sconcat) @@ -23,7 +25,17 @@ newtype Path = Path { rawPath :: Ptr TopoDS.Wire } joinPaths :: [Path] -> Path joinPaths paths = Path . unsafeFromAcquire $ do builder <- MakeWire.new - traverse_ (liftIO . MakeWire.addWire builder <=< toAcquire . rawPath) paths + let addPath p = do + wire <- toAcquire . rawPath $ p + explorer <- WireExplorer.fromWire wire + let runToEnd = do + edge <- liftIO $ WireExplorer.current explorer + liftIO $ MakeWire.addEdge builder edge + liftIO $ WireExplorer.next explorer + more <- liftIO $ WireExplorer.more explorer + when more runToEnd + runToEnd + traverse_ addPath paths MakeWire.wire builder -- | The Semigroup for `Path` attempts to join two paths that share a common endpoint. diff --git a/waterfall-cad/src/Waterfall/Path/Common.hs b/waterfall-cad/src/Waterfall/Path/Common.hs index 180ca4a..16a387b 100644 --- a/waterfall-cad/src/Waterfall/Path/Common.hs +++ b/waterfall-cad/src/Waterfall/Path/Common.hs @@ -187,11 +187,7 @@ closeLoop p = else p <> line e s reversePath :: (AnyPath point path) => path -> path -reversePath = - fromWire . ( - reverseWire - <=< toWire - ) +reversePath = fromWire . (reverseWire <=< toWire) instance AnyPath (V3 Double) Path where fromWire :: Acquire (Ptr TopoDS.Wire) -> Path From ebf56bae92a6de04cff8f98589d72eaf7b509427 Mon Sep 17 00:00:00 2001 From: Joe Warren Date: Sun, 4 Aug 2024 15:46:48 +0100 Subject: [PATCH 05/13] document the Loft functions --- waterfall-cad-examples/src/LoftExample.hs | 27 ++++++++++++++++------- waterfall-cad/src/Waterfall.hs | 3 +++ waterfall-cad/src/Waterfall/Loft.hs | 25 +++++++++++++++++++-- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/waterfall-cad-examples/src/LoftExample.hs b/waterfall-cad-examples/src/LoftExample.hs index eddc332..aa2fc60 100644 --- a/waterfall-cad-examples/src/LoftExample.hs +++ b/waterfall-cad-examples/src/LoftExample.hs @@ -1,3 +1,8 @@ +{-| +Module: Waterfall.Loft + +-} + module LoftExample ( loftExample ) where @@ -12,7 +17,13 @@ import qualified Waterfall.TwoD.Shape as Shape import qualified Waterfall.Loft as Loft import qualified Waterfall.Path.Common as Path --- Build a boat, with the profile of the boat defined using a series of bezier curves +-- | [Loft](https://en.wikipedia.org/wiki/Loft_\(3D\)) is a method to create smooth 3D shapes. +-- +-- Analagous to the [lofting](https://en.wikipedia.org/wiki/Lofting) process in boat building. +-- A loft is defined by planar cross-sections of the desired shape at chosen locations. +-- These cross-sections are then interpolated to form a smooth 3d shape. +-- +-- This example demonstrates the `Loft` module, by generating a boat, with the profile of the boat specified by a series of bezier curves. loftExample :: Solids.Solid loftExample = let precision = 1e-6 @@ -23,23 +34,23 @@ loftExample = Path.bezier (p 0 0) (p 4 0) (p 5 3) (p 5 4) , let p x z = V3 x 2 z in Path.bezier (p 0 0) (p 4 0) (p 5 3) (p 5 4) - , let p x z = V3 x 7.5 z + , let p x z = V3 x 5 z in Path.bezier (p 0 0) (p 4 0) (p 5 3) (p 5 4) - , let p x z = V3 x 20 z - in Path.bezier (p 0 0) (p 5 0) (p 5.5 3) (p 5.5 4.2) + , let p x z = V3 x 10 z + in Path.bezier (p 1.5 0) (p 4.5 0) (p 5.0 3) (p 5.0 4.2) ] - mirror = Path.reversePath . Transforms.mirror (V3 1 0 0 ) + mirror = Transforms.mirror (V3 1 0 0 ) . Path.reversePath makeSymetric p = mirror p <> p symetricPaths = makeSymetric <$> paths body = Loft.pointedLoft precision Nothing - ( Path.closeLoop <$> symetricPaths) - (Just (V3 0 30 5)) + (Path.closeLoop <$> symetricPaths) + (Just (V3 0 20 5)) -- shrink the boat shape slightly, and translate it -- use this to hollow out the boat - cavity = Transforms.translate (V3 0 (0.025 * 30) 0.3) $ Transforms.uScale 0.95 body + cavity = Transforms.translate (V3 0 (0.025 * 20) 0.3) $ Transforms.uScale 0.95 body -- sweep a circle along each of the paths, this makes them visible in the generated model sweepWithCircle = (`Sweep.sweep` Transforms2D.uScale2D 0.2 Shape.unitCircle) splines = mconcat $ sweepWithCircle <$> symetricPaths diff --git a/waterfall-cad/src/Waterfall.hs b/waterfall-cad/src/Waterfall.hs index f0cac56..bf4b25b 100644 --- a/waterfall-cad/src/Waterfall.hs +++ b/waterfall-cad/src/Waterfall.hs @@ -35,6 +35,8 @@ module Waterfall , module Waterfall.TwoD.Shape -- | Sweep a 2D `Shape` along a `Path`, constructing a `Solid`. , module Waterfall.Sweep +-- | Generate a [Loft](https://en.wikipedia.org/wiki/Loft_\(3D\)) between a sequence of `Path`s +, module Waterfall.Loft -- | Construct a `Solid` of revolution from a `Path2D`. , module Waterfall.Revolution -- | Transforms for data types that exist in Two Dimensional space, like `Shape` and `Path2D`. @@ -62,6 +64,7 @@ import Waterfall.Path import Waterfall.Revolution import Waterfall.Solids import Waterfall.Sweep +import Waterfall.Loft import Waterfall.Transforms import Waterfall.TwoD.Transforms import Waterfall.TwoD.Path2D diff --git a/waterfall-cad/src/Waterfall/Loft.hs b/waterfall-cad/src/Waterfall/Loft.hs index a159225..eb7b440 100644 --- a/waterfall-cad/src/Waterfall/Loft.hs +++ b/waterfall-cad/src/Waterfall/Loft.hs @@ -1,3 +1,13 @@ +{-| +Module: Waterfall.Loft + +[Loft](https://en.wikipedia.org/wiki/Loft_\(3D\)) is a method to create smooth 3D shapes. + +Analagous to the [lofting](https://en.wikipedia.org/wiki/Lofting) process in boat building. +A loft is defined by planar cross-sections of the desired shape at chosen locations. +These cross-sections are then interpolated to form a smooth 3d shape. + +-} module Waterfall.Loft ( pointedLoft , loft @@ -13,7 +23,15 @@ import OpenCascade.Inheritance (upcast) import Control.Monad.IO.Class (liftIO) import Control.Monad (forM_, (<=<)) -pointedLoft :: Double -> Maybe (V3 Double) -> [Path] -> Maybe (V3 Double) -> Solid +-- | Form a Loft which may terminate at defined points. +-- +-- If the start or end points are set to `Nothing` then one end of the loft will be the terminal cross section. +-- Otherwise, the loft will interpolate to that point. +pointedLoft :: Double -- ^ The loft precision, this should be a small value, e.g. @ 1e-6 @ + -> Maybe (V3 Double) -- ^ Optional start point for the loft + -> [Path] -- ^ Series of cross sections that the loft will pass through + -> Maybe (V3 Double) -- ^ Optional end point for the loft + -> Solid pointedLoft precision start paths end = solidFromAcquire $ do thruSections <- ThruSections.new True False precision @@ -22,5 +40,8 @@ pointedLoft precision start paths end = forM_ end ((liftIO . ThruSections.addVertex thruSections) <=< v3ToVertex) MakeShape.shape (upcast thruSections) -loft :: Double -> [Path] -> Solid +-- | Form a loft between a series of cross sections. +loft :: Double -- ^ The loft precision, this should be a small value, e.g @ 1e-6 @ + -> [Path] -- ^ Series of cross sections that the loft will pass through + -> Solid loft precision paths = pointedLoft precision Nothing paths Nothing \ No newline at end of file From 42882ac9a52242a85e2eeef3e4cd91f24441b451 Mon Sep 17 00:00:00 2001 From: Joe Warren Date: Sun, 4 Aug 2024 16:08:24 +0100 Subject: [PATCH 06/13] Fix (but only paritally), the monoid instances for Path and Path2D --- .../cpp/hs_BRepBuilderAPI_MakeWire.cpp | 4 + .../cpp/hs_BRepBuilderAPI_MakeWire.h | 2 + opencascade-hs/cpp/hs_BRep_Tool.cpp | 4 + opencascade-hs/cpp/hs_BRep_Tool.h | 2 + opencascade-hs/src/OpenCascade/BRep/Tool.hs | 8 ++ .../OpenCascade/BRepBuilderAPI/MakeWire.hs | 8 ++ .../OpenCascade/BRepBuilderAPI/WireError.hs | 2 +- waterfall-cad/src/Waterfall/Internal/Edges.hs | 74 ++++++++++++++----- waterfall-cad/src/Waterfall/Internal/Path.hs | 37 ++++------ .../src/Waterfall/Internal/ToOpenCascade.hs | 10 ++- waterfall-cad/src/Waterfall/Path/Common.hs | 5 +- .../src/Waterfall/TwoD/Internal/Path2D.hs | 12 +-- 12 files changed, 114 insertions(+), 54 deletions(-) diff --git a/opencascade-hs/cpp/hs_BRepBuilderAPI_MakeWire.cpp b/opencascade-hs/cpp/hs_BRepBuilderAPI_MakeWire.cpp index c270572..4ef1649 100644 --- a/opencascade-hs/cpp/hs_BRepBuilderAPI_MakeWire.cpp +++ b/opencascade-hs/cpp/hs_BRepBuilderAPI_MakeWire.cpp @@ -26,6 +26,10 @@ TopoDS_Wire * hs_BRepBuilderAPI_MakeWire_Wire(BRepBuilderAPI_MakeWire* builder){ return new TopoDS_Wire(builder->Wire()); } +TopoDS_Vertex * hs_BRepBuilderAPI_MakeWire_Vertex(BRepBuilderAPI_MakeWire* builder){ + return new TopoDS_Vertex(builder->Vertex()); +} + bool hs_BRepBuilderAPI_MakeWire_IsDone(BRepBuilderAPI_MakeWire* builder){ return builder->IsDone(); } diff --git a/opencascade-hs/cpp/hs_BRepBuilderAPI_MakeWire.h b/opencascade-hs/cpp/hs_BRepBuilderAPI_MakeWire.h index 30d895e..88cc0ea 100644 --- a/opencascade-hs/cpp/hs_BRepBuilderAPI_MakeWire.h +++ b/opencascade-hs/cpp/hs_BRepBuilderAPI_MakeWire.h @@ -19,6 +19,8 @@ void hs_BRepBuilderAPI_MakeWire_AddListOfShape(BRepBuilderAPI_MakeWire* builder, TopoDS_Wire * hs_BRepBuilderAPI_MakeWire_Wire(BRepBuilderAPI_MakeWire* builder); +TopoDS_Vertex * hs_BRepBuilderAPI_MakeWire_Vertex(BRepBuilderAPI_MakeWire* builder); + bool hs_BRepBuilderAPI_MakeWire_IsDone(BRepBuilderAPI_MakeWire* builder); BRepBuilderAPI_WireError hs_BRepBuilderAPI_MakeWire_Error(BRepBuilderAPI_MakeWire* builder); diff --git a/opencascade-hs/cpp/hs_BRep_Tool.cpp b/opencascade-hs/cpp/hs_BRep_Tool.cpp index 4348aa0..ab9e0dd 100644 --- a/opencascade-hs/cpp/hs_BRep_Tool.cpp +++ b/opencascade-hs/cpp/hs_BRep_Tool.cpp @@ -17,6 +17,10 @@ double hs_BRep_Tool_curveParamLast(TopoDS_Edge * edge){ double s, e; BRep_Tool::Curve(*edge, s, e); return e; +} + +gp_Pnt * hs_BRep_Tool_pnt(TopoDS_Vertex * vertex){ + return new gp_Pnt(BRep_Tool::Pnt(*vertex)); } Handle(Poly_Triangulation) * hs_BRep_Tool_triangulation(TopoDS_Face * face, TopLoc_Location * loc){ diff --git a/opencascade-hs/cpp/hs_BRep_Tool.h b/opencascade-hs/cpp/hs_BRep_Tool.h index d6787a7..c8ea80b 100644 --- a/opencascade-hs/cpp/hs_BRep_Tool.h +++ b/opencascade-hs/cpp/hs_BRep_Tool.h @@ -13,6 +13,8 @@ double hs_BRep_Tool_curveParamFirst(TopoDS_Edge * edge); double hs_BRep_Tool_curveParamLast(TopoDS_Edge * edge); +gp_Pnt * hs_BRep_Tool_pnt(TopoDS_Vertex * vertex); + Handle(Poly_Triangulation) * hs_BRep_Tool_triangulation(TopoDS_Face * face, TopLoc_Location * loc); #ifdef __cplusplus diff --git a/opencascade-hs/src/OpenCascade/BRep/Tool.hs b/opencascade-hs/src/OpenCascade/BRep/Tool.hs index b1522ba..c3cd8cb 100644 --- a/opencascade-hs/src/OpenCascade/BRep/Tool.hs +++ b/opencascade-hs/src/OpenCascade/BRep/Tool.hs @@ -3,17 +3,20 @@ module OpenCascade.BRep.Tool ( curve , curveParamFirst , curveParamLast +, pnt , triangulation ) where import qualified OpenCascade.Geom as Geom import qualified OpenCascade.TopoDS as TopoDS +import qualified OpenCascade.GP as GP import qualified OpenCascade.TopLoc.Types as TopLoc import qualified OpenCascade.Poly.Types as Poly import OpenCascade.Poly.Internal.Destructors (deleteHandleTriangulation) import OpenCascade.Handle (Handle) import OpenCascade.Geom.Internal.Destructors (deleteHandleCurve) +import OpenCascade.GP.Internal.Destructors (deletePnt) import Foreign.Ptr import Foreign.C import Data.Coerce @@ -34,6 +37,11 @@ foreign import capi unsafe "hs_BRep_Tool.h hs_BRep_Tool_curveParamLast" rawCurve curveParamLast :: Ptr TopoDS.Edge -> IO Double curveParamLast = coerce rawCurveParamLast +foreign import capi unsafe "hs_BRep_Tool.h hs_BRep_Tool_pnt" rawPnt :: Ptr (TopoDS.Vertex) -> IO (Ptr GP.Pnt) + +pnt :: Ptr TopoDS.Vertex -> Acquire (Ptr GP.Pnt) +pnt v = mkAcquire (rawPnt v) deletePnt + foreign import capi unsafe "hs_BRep_Tool.h hs_BRep_Tool_triangulation" rawTriangulation :: Ptr (TopoDS.Face) -> Ptr TopLoc.Location -> IO(Ptr (Handle Poly.Triangulation)) triangulation :: Ptr TopoDS.Face -> Ptr TopLoc.Location -> Acquire (Ptr (Handle Poly.Triangulation)) diff --git a/opencascade-hs/src/OpenCascade/BRepBuilderAPI/MakeWire.hs b/opencascade-hs/src/OpenCascade/BRepBuilderAPI/MakeWire.hs index b048ffa..1715663 100644 --- a/opencascade-hs/src/OpenCascade/BRepBuilderAPI/MakeWire.hs +++ b/opencascade-hs/src/OpenCascade/BRepBuilderAPI/MakeWire.hs @@ -6,6 +6,7 @@ module OpenCascade.BRepBuilderAPI.MakeWire , addWire , addListOfShape , wire +, vertex , isDone , error ) where @@ -51,6 +52,13 @@ foreign import capi unsafe "hs_BRepBuilderAPI_MakeWire.h hs_BRepBuilderAPI_MakeW wire :: Ptr MakeWire -> Acquire (Ptr TopoDS.Wire) wire builder = mkAcquire (rawWire builder) (TopoDS.Destructors.deleteShape . upcast) +-- vertex +-- +foreign import capi unsafe "hs_BRepBuilderAPI_MakeWire.h hs_BRepBuilderAPI_MakeWire_Vertex" rawVertex :: Ptr MakeWire -> IO (Ptr TopoDS.Vertex) + +vertex :: Ptr MakeWire -> Acquire (Ptr TopoDS.Vertex) +vertex builder = mkAcquire (rawVertex builder) (TopoDS.Destructors.deleteShape . upcast) + -- isDone -- foreign import capi unsafe "hs_BRepBuilderAPI_MakeWire.h hs_BRepBuilderAPI_MakeWire_IsDone" rawIsDone :: Ptr MakeWire -> IO (CBool) diff --git a/opencascade-hs/src/OpenCascade/BRepBuilderAPI/WireError.hs b/opencascade-hs/src/OpenCascade/BRepBuilderAPI/WireError.hs index ada1e66..460105b 100644 --- a/opencascade-hs/src/OpenCascade/BRepBuilderAPI/WireError.hs +++ b/opencascade-hs/src/OpenCascade/BRepBuilderAPI/WireError.hs @@ -3,4 +3,4 @@ module OpenCascade.BRepBuilderAPI.WireError ) where -- order must match the definition of BRepBuilderAPI_WireError -data WireError = WireDone | EmptyWire | DisconnectedWire | NonManifoldWire deriving (Eq, Enum) +data WireError = WireDone | EmptyWire | DisconnectedWire | NonManifoldWire deriving (Eq, Enum, Show) diff --git a/waterfall-cad/src/Waterfall/Internal/Edges.hs b/waterfall-cad/src/Waterfall/Internal/Edges.hs index 31d7ae4..58a7991 100644 --- a/waterfall-cad/src/Waterfall/Internal/Edges.hs +++ b/waterfall-cad/src/Waterfall/Internal/Edges.hs @@ -1,28 +1,28 @@ module Waterfall.Internal.Edges ( edgeEndpoints , wireEndpoints +, allWireEndpoints , wireTangent , reverseEdge , reverseWire +, intersperseLines +, joinWires ) where import qualified OpenCascade.TopoDS as TopoDS -import qualified OpenCascade.TopoDS.Shape as TopoDS.Shape import qualified OpenCascade.BRep.Tool as BRep.Tool import qualified OpenCascade.Geom.Curve as Geom.Curve import qualified OpenCascade.BRepTools.WireExplorer as WireExplorer -import qualified OpenCascade.ShapeExtend.WireData as WireData import qualified OpenCascade.BRepBuilderAPI.MakeEdge as MakeEdge import Waterfall.Internal.FromOpenCascade (gpPntToV3, gpVecToV3) import Data.Acquire import Control.Monad.IO.Class (liftIO) -import Linear (V3 (..)) +import Linear (V3 (..), distance) import Foreign.Ptr -import OpenCascade.Inheritance (unsafeDowncast, upcast) -import qualified OpenCascade.BRepBuilderAPI.MakeEdge as MakeEdge import qualified OpenCascade.BRepBuilderAPI.MakeWire as MakeWire import Control.Monad (when) - +import Waterfall.Internal.ToOpenCascade (v3ToPnt) +import Data.Foldable (traverse_) edgeEndpoints :: Ptr TopoDS.Edge -> IO (V3 Double, V3 Double) edgeEndpoints edge = (`with` pure) $ do @@ -33,6 +33,18 @@ edgeEndpoints edge = (`with` pure) $ do e <- (liftIO . gpPntToV3) =<< Geom.Curve.value curve p2 return (s, e) +allWireEndpoints :: Ptr TopoDS.Wire -> IO [(V3 Double, V3 Double)] +allWireEndpoints wire = with (WireExplorer.fromWire wire) $ \explorer -> do + let runToEnd = do + edge <- WireExplorer.current explorer + points <- edgeEndpoints edge + WireExplorer.next explorer + more <- WireExplorer.more explorer + if more + then (points:) <$> runToEnd + else pure [points] + runToEnd + wireEndpoints :: Ptr TopoDS.Wire -> IO (V3 Double, V3 Double) wireEndpoints wire = with (WireExplorer.fromWire wire) $ \explorer -> do v1 <- WireExplorer.current explorer @@ -81,18 +93,40 @@ reverseWire wire = do when more runToEnd liftIO $ MakeWire.addEdge makeWire edge' runToEnd - wire' <- MakeWire.wire makeWire - liftIO $ TopoDS.Shape.reverse (upcast wire') - return wire' + MakeWire.wire makeWire + +line' :: V3 Double -> V3 Double -> Acquire (Ptr TopoDS.Wire) +line' s e = do + builder <- MakeWire.new + pt1 <- v3ToPnt s + pt2 <- v3ToPnt e + edge <- MakeEdge.fromPnts pt1 pt2 + liftIO $ MakeWire.addEdge builder edge + MakeWire.wire builder + +intersperseLines :: [Ptr TopoDS.Wire] -> Acquire [Ptr TopoDS.Wire] +intersperseLines [] = pure [] +intersperseLines [x] = pure [x] +intersperseLines (a:b:xs) = do + (_, ea) <- liftIO $ wireEndpoints a + (sb, _) <- liftIO $ wireEndpoints b + if distance ea sb < 1e-6 + then (a :) <$> intersperseLines (b:xs) + else (a :) <$> ((:) <$> line' ea sb <*> intersperseLines (b:xs)) + +joinWires :: [Ptr TopoDS.Wire] -> Acquire (Ptr TopoDS.Wire) +joinWires wires = do + builder <- MakeWire.new + let addWire wire = do + explorer <- WireExplorer.fromWire wire + let runToEnd = do + edge <- liftIO $ WireExplorer.current explorer + liftIO $ MakeWire.addEdge builder edge + liftIO $ print =<< MakeWire.error builder + liftIO $ WireExplorer.next explorer + more <- liftIO $ WireExplorer.more explorer + when more runToEnd + runToEnd + traverse_ addWire $ wires + MakeWire.wire builder -{-- -reverseWire :: Ptr TopoDS.Wire -> Acquire (Ptr TopoDS.Wire) -reverseWire wire = do - wire' <- liftIO . unsafeDowncast =<< TopoDS.Shape.copy (upcast wire) - wireData <- WireData.fromWireChainedAndManifold wire' False True - liftIO $ WireData.reverse wireData - wire'' <- WireData.wire wireData - --liftIO $ TopoDS.Shape.complement (upcast wire'') - --liftIO $ TopoDS.Shape.reverse (upcast wire'') - return wire'' ---} \ No newline at end of file diff --git a/waterfall-cad/src/Waterfall/Internal/Path.hs b/waterfall-cad/src/Waterfall/Internal/Path.hs index 8e9e173..5387da7 100644 --- a/waterfall-cad/src/Waterfall/Internal/Path.hs +++ b/waterfall-cad/src/Waterfall/Internal/Path.hs @@ -3,44 +3,37 @@ module Waterfall.Internal.Path ( Path (..) , joinPaths +, allPathEndpoints ) where import Data.List.NonEmpty (NonEmpty ()) -import Data.Foldable (traverse_, toList) +import Data.Foldable (toList) import Waterfall.Internal.Finalizers (toAcquire, unsafeFromAcquire) -import Control.Monad ((<=<)) import Control.Monad.IO.Class (liftIO) import qualified OpenCascade.TopoDS as TopoDS -import qualified OpenCascade.BRepBuilderAPI.MakeWire as MakeWire -import qualified OpenCascade.BRepTools.WireExplorer as WireExplorer -import Control.Monad (when) import Foreign.Ptr +import Linear (V3 (..)) import Data.Semigroup (sconcat) - +import Waterfall.Internal.Edges (allWireEndpoints, intersperseLines, joinWires) -- | A Path in 3D Space -- -- Under the hood, this is represented by an OpenCascade `TopoDS.Wire`. newtype Path = Path { rawPath :: Ptr TopoDS.Wire } +-- | Exposing this because I found it useful for debugging +allPathEndpoints :: Path -> [(V3 Double, V3 Double)] +allPathEndpoints (Path raw) = unsafeFromAcquire $ do + wire <- toAcquire raw + liftIO $ allWireEndpoints wire + joinPaths :: [Path] -> Path joinPaths paths = Path . unsafeFromAcquire $ do - builder <- MakeWire.new - let addPath p = do - wire <- toAcquire . rawPath $ p - explorer <- WireExplorer.fromWire wire - let runToEnd = do - edge <- liftIO $ WireExplorer.current explorer - liftIO $ MakeWire.addEdge builder edge - liftIO $ WireExplorer.next explorer - more <- liftIO $ WireExplorer.more explorer - when more runToEnd - runToEnd - traverse_ addPath paths - MakeWire.wire builder + wires <- traverse (toAcquire . rawPath) paths + joinWires =<< intersperseLines wires --- | The Semigroup for `Path` attempts to join two paths that share a common endpoint. --- --- Attempts to combine paths that do not share a common endpoint currently in an error case that is not currently handled gracefully. +-- | Joins `Path`s, @ a <> b @ connects the end point of @ b @ to the start of @ b @, if these points are not coincident, a line is created between them. +-- +-- Attempts to combine paths in ways that generate a non manifold path will produce an error case that is not currently handled gracefully. instance Semigroup Path where sconcat :: NonEmpty Path -> Path sconcat = joinPaths . toList diff --git a/waterfall-cad/src/Waterfall/Internal/ToOpenCascade.hs b/waterfall-cad/src/Waterfall/Internal/ToOpenCascade.hs index 7595021..d9070d4 100644 --- a/waterfall-cad/src/Waterfall/Internal/ToOpenCascade.hs +++ b/waterfall-cad/src/Waterfall/Internal/ToOpenCascade.hs @@ -1,17 +1,23 @@ module Waterfall.Internal.ToOpenCascade ( v3ToVertex +, v3ToPnt ) where import Linear (V3 (..)) import Data.Acquire (Acquire, mkAcquire) import Foreign.Ptr (Ptr) import qualified OpenCascade.TopoDS as TopoDS +import qualified OpenCascade.GP as GP import qualified OpenCascade.GP.Pnt as GP.Pnt import qualified OpenCascade.BRepBuilderAPI.MakeVertex as MakeVertex + +v3ToPnt :: V3 Double -> Acquire (Ptr GP.Pnt) +v3ToPnt (V3 x y z) = GP.Pnt.new x y z + v3ToVertex :: V3 Double -> Acquire (Ptr TopoDS.Vertex) -v3ToVertex (V3 x y z) = do - pnt <- GP.Pnt.new x y z +v3ToVertex v = do + pnt <- v3ToPnt v builder <- MakeVertex.fromPnt pnt MakeVertex.vertex builder diff --git a/waterfall-cad/src/Waterfall/Path/Common.hs b/waterfall-cad/src/Waterfall/Path/Common.hs index 16a387b..b56cfdf 100644 --- a/waterfall-cad/src/Waterfall/Path/Common.hs +++ b/waterfall-cad/src/Waterfall/Path/Common.hs @@ -27,7 +27,6 @@ module Waterfall.Path.Common ) where import Data.Acquire import qualified OpenCascade.TopoDS as TopoDS -import qualified OpenCascade.TopoDS.Shape as TopoDS.Shape import qualified OpenCascade.GP as GP import Foreign.Ptr import Waterfall.Internal.Path (Path (..)) @@ -39,7 +38,7 @@ import qualified OpenCascade.BRepBuilderAPI.MakeWire as MakeWire import Control.Monad.IO.Class (liftIO) import qualified OpenCascade.BRepBuilderAPI.MakeEdge as MakeEdge import qualified OpenCascade.GC.MakeArcOfCircle as MakeArcOfCircle -import OpenCascade.Inheritance (upcast, unsafeDowncast) +import OpenCascade.Inheritance (upcast) import qualified OpenCascade.NCollection.Array1 as NCollection.Array1 import qualified OpenCascade.Geom.BezierCurve as BezierCurve import Data.Proxy (Proxy (..)) @@ -168,7 +167,7 @@ pathFromTo :: (Monoid path) => [point -> (point, path)] -> point -> (point, path pathFromTo commands start = let go (pos, paths) cmd = second (:paths) (cmd pos) (end, allPaths) = foldl' go (start, []) commands - in (end, mconcat allPaths) + in (end, mconcat . reverse $ allPaths) -- | Returns the start and end of a `Path` pathEndpoints :: forall point path. (AnyPath point path) => path -> (point, point) diff --git a/waterfall-cad/src/Waterfall/TwoD/Internal/Path2D.hs b/waterfall-cad/src/Waterfall/TwoD/Internal/Path2D.hs index 8c375f3..378c9f2 100644 --- a/waterfall-cad/src/Waterfall/TwoD/Internal/Path2D.hs +++ b/waterfall-cad/src/Waterfall/TwoD/Internal/Path2D.hs @@ -11,6 +11,7 @@ import qualified OpenCascade.TopoDS as TopoDS import qualified OpenCascade.BRepBuilderAPI.MakeWire as MakeWire import Foreign.Ptr import Data.Semigroup (sconcat) +import Waterfall.Internal.Edges (intersperseLines, joinWires) -- | A Path in 2D Space -- @@ -22,13 +23,12 @@ newtype Path2D = Path2D { rawPath :: Ptr TopoDS.Wire } joinPaths :: [Path2D] -> Path2D joinPaths paths = Path2D . unsafeFromAcquire $ do - builder <- MakeWire.new - traverse_ (liftIO . MakeWire.addWire builder <=< toAcquire . rawPath) paths - MakeWire.wire builder + wires <- traverse (toAcquire . rawPath) paths + joinWires =<< intersperseLines wires --- | The Semigroup for `Path2D` attempts to join two paths that share a common endpoint. --- --- Attempts to combine paths that do not share a common endpoint currently in an error case that is not currently handled gracefully. +-- | Joins `Path2D`s, @ a <> b @ connects the end point of @ b @ to the start of @ b @, if these points are not coincident, a line is created between them. +-- +-- Attempts to combine paths in ways that generate a non manifold path will produce an error case that is not currently handled gracefully. instance Semigroup Path2D where sconcat = joinPaths . toList a <> b = joinPaths [a, b] From 3fd11ca8eb441803b8b551888c8bede13793a324 Mon Sep 17 00:00:00 2001 From: Joe Warren Date: Sun, 4 Aug 2024 16:15:58 +0100 Subject: [PATCH 07/13] Add monomorphised versions of `reversePath` --- waterfall-cad/src/Waterfall/Path.hs | 7 ++++++- waterfall-cad/src/Waterfall/TwoD/Path2D.hs | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/waterfall-cad/src/Waterfall/Path.hs b/waterfall-cad/src/Waterfall/Path.hs index 1986cb6..fe9050b 100644 --- a/waterfall-cad/src/Waterfall/Path.hs +++ b/waterfall-cad/src/Waterfall/Path.hs @@ -19,6 +19,7 @@ module Waterfall.Path , pathFromTo3D , pathEndpoints3D , closeLoop3D +, reversePath3d ) where import Waterfall.Internal.Path (Path(..)) @@ -86,4 +87,8 @@ pathEndpoints3D = pathEndpoints -- | `closeLoop` with the type fixed to `Path` closeLoop3D :: Path -> Path -closeLoop3D = closeLoop \ No newline at end of file +closeLoop3D = closeLoop + +-- | `reversePath` with the type fixed to `Path` +reversePath3D :: Path -> Path +reversePath3D = reversePath \ No newline at end of file diff --git a/waterfall-cad/src/Waterfall/TwoD/Path2D.hs b/waterfall-cad/src/Waterfall/TwoD/Path2D.hs index 243413a..080b1e8 100644 --- a/waterfall-cad/src/Waterfall/TwoD/Path2D.hs +++ b/waterfall-cad/src/Waterfall/TwoD/Path2D.hs @@ -24,6 +24,7 @@ module Waterfall.TwoD.Path2D , pathFromTo2D , pathEndpoints2D , closeLoop2D +, reversePath2D ) where import Waterfall.TwoD.Internal.Path2D (Path2D(..)) @@ -142,4 +143,8 @@ pathEndpoints2D = pathEndpoints -- | `closeLoop` with the type fixed to `Path2D` closeLoop2D :: Path2D -> Path2D -closeLoop2D = closeLoop \ No newline at end of file +closeLoop2D = closeLoop + +-- | `reversePath` with the type fixed to `Path2D` +reversePath2D :: Path2D -> Path2D +reversePath2D = reversePath \ No newline at end of file From 1d5526a7748e975389932a4650c578c3fa5759de Mon Sep 17 00:00:00 2001 From: Joe Warren Date: Sun, 4 Aug 2024 16:23:32 +0100 Subject: [PATCH 08/13] update changelogs --- opencascade-hs/CHANGELOG.md | 8 ++++++++ waterfall-cad-examples/CHANGELOG.md | 2 ++ waterfall-cad/CHANGELOG.md | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/opencascade-hs/CHANGELOG.md b/opencascade-hs/CHANGELOG.md index cb52c81..23cd1e9 100644 --- a/opencascade-hs/CHANGELOG.md +++ b/opencascade-hs/CHANGELOG.md @@ -8,6 +8,14 @@ and this project adheres to the ## Unreleased +- Add `OpenCascade.BRepBuilderAPI.MakeVertex` +- Add `OpenCascade.BRepBuilderAPI.MakeWire.vertex` +- Add `OpenCascade.BRepOffsetAPI.ThruSections` +- Add `OpenCascade.BRep.Tool.pnt` +- Add `OpenCascade.Geom.Curve` methods `reversedParameter` and `reversed` +- Add `OpenCascade.ShapeExtend.WireData` +- Add `Show` instance to `OpenCascade.BRepBuilderAPI.WireError` + ## 0.3.0.1 ## 0.3.0.0 diff --git a/waterfall-cad-examples/CHANGELOG.md b/waterfall-cad-examples/CHANGELOG.md index 18f2c30..0d22748 100644 --- a/waterfall-cad-examples/CHANGELOG.md +++ b/waterfall-cad-examples/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to the ## Unreleased +- Add LoftExample + ## 0.3.0.1 ## 0.3.0.0 diff --git a/waterfall-cad/CHANGELOG.md b/waterfall-cad/CHANGELOG.md index 8f587ae..d814d9c 100644 --- a/waterfall-cad/CHANGELOG.md +++ b/waterfall-cad/CHANGELOG.md @@ -9,6 +9,11 @@ and this project adheres to the ## Unreleased +- Add `Waterfall.Loft` containing `loft` and `pointedLoft` +- Change the `Monoid` instance for `Path` and `Path2D`, so that in the expression `a <> b` a line is added between the end of `a` and the start of `b`, unless these points are coincident. +- Reverse the order in which Path.pathFrom adds path segments; required by the new Monoid behaviour. +- Add `Waterfall.Path.Common.reversePath`, reversing the direction of a `Path` or `Path2D`, along with monomorphised versions `reversePath3D` and `reversePath2D` + ## 0.3.0.1 ### Added From 63a4510d1a69a2bde3226e3aba5ee88986e41983 Mon Sep 17 00:00:00 2001 From: Joe Warren Date: Sun, 4 Aug 2024 16:26:44 +0100 Subject: [PATCH 09/13] fix function name in export --- waterfall-cad/src/Waterfall/Path.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/waterfall-cad/src/Waterfall/Path.hs b/waterfall-cad/src/Waterfall/Path.hs index fe9050b..d51d118 100644 --- a/waterfall-cad/src/Waterfall/Path.hs +++ b/waterfall-cad/src/Waterfall/Path.hs @@ -19,7 +19,7 @@ module Waterfall.Path , pathFromTo3D , pathEndpoints3D , closeLoop3D -, reversePath3d +, reversePath3D ) where import Waterfall.Internal.Path (Path(..)) From 1586a24215e5b0a116a1791cf45b10b06c88ade0 Mon Sep 17 00:00:00 2001 From: Joe Warren Date: Sun, 4 Aug 2024 16:31:27 +0100 Subject: [PATCH 10/13] remove debug print --- waterfall-cad/src/Waterfall/Internal/Edges.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/waterfall-cad/src/Waterfall/Internal/Edges.hs b/waterfall-cad/src/Waterfall/Internal/Edges.hs index 58a7991..b2502ef 100644 --- a/waterfall-cad/src/Waterfall/Internal/Edges.hs +++ b/waterfall-cad/src/Waterfall/Internal/Edges.hs @@ -122,7 +122,6 @@ joinWires wires = do let runToEnd = do edge <- liftIO $ WireExplorer.current explorer liftIO $ MakeWire.addEdge builder edge - liftIO $ print =<< MakeWire.error builder liftIO $ WireExplorer.next explorer more <- liftIO $ WireExplorer.more explorer when more runToEnd From 8a9a49921ffc7e36b3d39ec3479d04cf71573336 Mon Sep 17 00:00:00 2001 From: Joe Warren Date: Sun, 4 Aug 2024 16:32:06 +0100 Subject: [PATCH 11/13] Tweak loft Example parameters, update image --- images/loft.png | Bin 40053 -> 57829 bytes waterfall-cad-examples/src/LoftExample.hs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/images/loft.png b/images/loft.png index e43ce4bea7c80ef95cd6a761448c7bf6747adb32..3b686df5c3d5610b6d62e72f40815056d19aaecf 100644 GIT binary patch literal 57829 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�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 Cl%k~o literal 40053 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 diff --git a/waterfall-cad-examples/src/LoftExample.hs b/waterfall-cad-examples/src/LoftExample.hs index aa2fc60..522ed17 100644 --- a/waterfall-cad-examples/src/LoftExample.hs +++ b/waterfall-cad-examples/src/LoftExample.hs @@ -37,7 +37,7 @@ loftExample = , let p x z = V3 x 5 z in Path.bezier (p 0 0) (p 4 0) (p 5 3) (p 5 4) , let p x z = V3 x 10 z - in Path.bezier (p 1.5 0) (p 4.5 0) (p 5.0 3) (p 5.0 4.2) + in Path.bezier (p 1 0) (p 4.5 0) (p 5.25 3) (p 5.25 4.2) ] mirror = Transforms.mirror (V3 1 0 0 ) . Path.reversePath makeSymetric p = mirror p <> p From 886e69ddb0c2a2dbadb078a24e1aaf69c6c1b9c8 Mon Sep 17 00:00:00 2001 From: Joseph Warren Date: Sun, 4 Aug 2024 20:20:50 +0100 Subject: [PATCH 12/13] clean up whitespace --- waterfall-cad/src/Waterfall/Loft.hs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/waterfall-cad/src/Waterfall/Loft.hs b/waterfall-cad/src/Waterfall/Loft.hs index eb7b440..40b2fc3 100644 --- a/waterfall-cad/src/Waterfall/Loft.hs +++ b/waterfall-cad/src/Waterfall/Loft.hs @@ -6,7 +6,6 @@ Module: Waterfall.Loft Analagous to the [lofting](https://en.wikipedia.org/wiki/Lofting) process in boat building. A loft is defined by planar cross-sections of the desired shape at chosen locations. These cross-sections are then interpolated to form a smooth 3d shape. - -} module Waterfall.Loft ( pointedLoft @@ -25,11 +24,11 @@ import Control.Monad (forM_, (<=<)) -- | Form a Loft which may terminate at defined points. -- --- If the start or end points are set to `Nothing` then one end of the loft will be the terminal cross section. +-- If the start or end points are set to `Nothing` then one end of the loft will be the terminal cross-section. -- Otherwise, the loft will interpolate to that point. pointedLoft :: Double -- ^ The loft precision, this should be a small value, e.g. @ 1e-6 @ -> Maybe (V3 Double) -- ^ Optional start point for the loft - -> [Path] -- ^ Series of cross sections that the loft will pass through + -> [Path] -- ^ Series of cross-sections that the loft will pass through -> Maybe (V3 Double) -- ^ Optional end point for the loft -> Solid pointedLoft precision start paths end = @@ -40,8 +39,8 @@ pointedLoft precision start paths end = forM_ end ((liftIO . ThruSections.addVertex thruSections) <=< v3ToVertex) MakeShape.shape (upcast thruSections) --- | Form a loft between a series of cross sections. +-- | Form a loft between a series of cross-sections. loft :: Double -- ^ The loft precision, this should be a small value, e.g @ 1e-6 @ - -> [Path] -- ^ Series of cross sections that the loft will pass through + -> [Path] -- ^ Series of cross-sections that the loft will pass through -> Solid loft precision paths = pointedLoft precision Nothing paths Nothing \ No newline at end of file From 57ac0f807196b0ddae4ffc05711622baf1df9285 Mon Sep 17 00:00:00 2001 From: Joseph Warren Date: Sun, 4 Aug 2024 20:29:45 +0100 Subject: [PATCH 13/13] fix order of rotation in repeatLooping --- waterfall-cad/CHANGELOG.md | 1 + waterfall-cad/src/Waterfall/Internal/ToOpenCascade.hs | 2 +- waterfall-cad/src/Waterfall/TwoD/Internal/Path2D.hs | 5 +---- waterfall-cad/src/Waterfall/TwoD/Path2D.hs | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/waterfall-cad/CHANGELOG.md b/waterfall-cad/CHANGELOG.md index d814d9c..7e24c1b 100644 --- a/waterfall-cad/CHANGELOG.md +++ b/waterfall-cad/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to the - Change the `Monoid` instance for `Path` and `Path2D`, so that in the expression `a <> b` a line is added between the end of `a` and the start of `b`, unless these points are coincident. - Reverse the order in which Path.pathFrom adds path segments; required by the new Monoid behaviour. - Add `Waterfall.Path.Common.reversePath`, reversing the direction of a `Path` or `Path2D`, along with monomorphised versions `reversePath3D` and `reversePath2D` +- Fix order of rotation of `Waterfall.TwoD.Path2D.repeatLooping` ## 0.3.0.1 diff --git a/waterfall-cad/src/Waterfall/Internal/ToOpenCascade.hs b/waterfall-cad/src/Waterfall/Internal/ToOpenCascade.hs index d9070d4..c9063cb 100644 --- a/waterfall-cad/src/Waterfall/Internal/ToOpenCascade.hs +++ b/waterfall-cad/src/Waterfall/Internal/ToOpenCascade.hs @@ -4,7 +4,7 @@ module Waterfall.Internal.ToOpenCascade ) where import Linear (V3 (..)) -import Data.Acquire (Acquire, mkAcquire) +import Data.Acquire (Acquire) import Foreign.Ptr (Ptr) import qualified OpenCascade.TopoDS as TopoDS import qualified OpenCascade.GP as GP diff --git a/waterfall-cad/src/Waterfall/TwoD/Internal/Path2D.hs b/waterfall-cad/src/Waterfall/TwoD/Internal/Path2D.hs index 378c9f2..e20fc56 100644 --- a/waterfall-cad/src/Waterfall/TwoD/Internal/Path2D.hs +++ b/waterfall-cad/src/Waterfall/TwoD/Internal/Path2D.hs @@ -3,12 +3,9 @@ module Waterfall.TwoD.Internal.Path2D , joinPaths ) where -import Data.Foldable (traverse_, toList) +import Data.Foldable (toList) import Waterfall.Internal.Finalizers (toAcquire, unsafeFromAcquire) -import Control.Monad ((<=<)) -import Control.Monad.IO.Class (liftIO) import qualified OpenCascade.TopoDS as TopoDS -import qualified OpenCascade.BRepBuilderAPI.MakeWire as MakeWire import Foreign.Ptr import Data.Semigroup (sconcat) import Waterfall.Internal.Edges (intersperseLines, joinWires) diff --git a/waterfall-cad/src/Waterfall/TwoD/Path2D.hs b/waterfall-cad/src/Waterfall/TwoD/Path2D.hs index 080b1e8..72d2b2d 100644 --- a/waterfall-cad/src/Waterfall/TwoD/Path2D.hs +++ b/waterfall-cad/src/Waterfall/TwoD/Path2D.hs @@ -86,7 +86,7 @@ repeatLooping p = Path2D . unsafeFromAcquire $ do (s, e) <- liftIO . Internal.Edges.wireEndpoints $ path let a = unangle (e ^. _xy) - unangle (s ^. _xy) let times :: Integer = abs . round $ pi * 2 / a - toAcquire . rawPath . mconcat $ [rotate2D (negate (fromIntegral n) * a) p | n <- [0..times]] + toAcquire . rawPath . mconcat $ [rotate2D (fromIntegral n * a) p | n <- [0..times]] -- $reexports