packages feed

waterfall-cad-0.6.0.0: src/Waterfall/TwoD/Internal/Shape.hs

{-# OPTIONS_HADDOCK not-home #-}
module Waterfall.TwoD.Internal.Shape
( Shape (..)
, acquireShape
, shapeFromAcquire
, union2D
, difference2D
, intersection2D
, unions2D
, intersections2D
, emptyShape
) where

import qualified OpenCascade.TopoDS as TopoDS
import Foreign.Ptr
import Data.Acquire (Acquire)
import Algebra.Lattice
import Waterfall.Internal.Finalizers (toAcquire, unsafeFromAcquire)
import qualified OpenCascade.BRepAlgoAPI.Fuse as Fuse
import qualified OpenCascade.BRepAlgoAPI.Cut as Cut
import qualified OpenCascade.BRepAlgoAPI.Common as Common
import qualified OpenCascade.TopoDS.Shape as TopoDS.Shape
import qualified OpenCascade.BRepBuilderAPI.MakeFace as MakeFace
import qualified OpenCascade.BOPAlgo.Operation as BOPAlgo.Operation
import qualified OpenCascade.BOPAlgo.BOP as BOPAlgo.BOP
import qualified OpenCascade.BOPAlgo.Builder as BOPAlgo.Builder
import OpenCascade.Inheritance (upcast)
import Control.Monad.IO.Class (liftIO)
import Data.Foldable (traverse_)

-- | A Region in 2D Space 
-- 
-- In general, this is used as a face, and extruded along some sort of path
--
-- Under the hood, this is represented by an OpenCascade `TopoDS.Shape`
-- 
-- This should be of type `TopoDS.Face`, constrained to the plane \( z=0 \).
--
-- Please feel free to report a bug if you're able to construct a `Shape`
-- which does not lie on this plane (without using Internal functions).
-- Or which is not either a `TopoDS.Face`, or a composite of faces.
newtype Shape = Shape { rawShape :: Ptr TopoDS.Shape }

acquireShape :: Shape -> Acquire (Ptr TopoDS.Shape)
acquireShape (Shape ptr) = toAcquire ptr

shapeFromAcquire :: Acquire (Ptr TopoDS.Shape) -> Shape
shapeFromAcquire = Shape . unsafeFromAcquire

toBoolean2D :: (Ptr TopoDS.Shape.Shape -> Ptr TopoDS.Shape.Shape -> Acquire (Ptr TopoDS.Shape.Shape)) -> Shape -> Shape -> Shape
toBoolean2D f (Shape ptrA) (Shape ptrB) = Shape . unsafeFromAcquire $ do
    a <- toAcquire ptrA
    b <- toAcquire ptrB
    f a b

-- | Take the union of two 2D shapes.
-- The region occupied by either one of them
union2D :: Shape -> Shape -> Shape
union2D = toBoolean2D Fuse.fuse

-- | Take the difference of two 2D shapes.
-- The region occupied by the first, but not the second
difference2D :: Shape -> Shape -> Shape
difference2D = toBoolean2D Cut.cut

-- | Take the intersection of two 2D shapes.
-- The region occupied by both of them
intersection2D :: Shape -> Shape -> Shape
intersection2D = toBoolean2D Common.common

toBooleans2D :: BOPAlgo.Operation.Operation -> [Shape] -> Shape
toBooleans2D _ [] = emptyShape
toBooleans2D _ [x] = x
toBooleans2D op (h:shapes) = Shape . unsafeFromAcquire $ do
    firstPtr <- toAcquire . rawShape $ h
    ptrs <- traverse (toAcquire . rawShape) shapes
    bop <- BOPAlgo.BOP.new
    let builder = upcast bop
    liftIO $ do
        BOPAlgo.BOP.setOperation bop op
        BOPAlgo.Builder.addArgument builder firstPtr
        traverse_ (BOPAlgo.BOP.addTool bop) ptrs
        BOPAlgo.Builder.setRunParallel builder True
        BOPAlgo.Builder.perform builder
    BOPAlgo.Builder.shape builder

-- | Take the union of a list of 2D shapes
-- May be more performant than chaining multiple applications of `union2D`
unions2D :: [Shape] -> Shape
unions2D = toBooleans2D BOPAlgo.Operation.Fuse

-- | Take the intersection of a list of 2D shapes
-- May be more performant than chaining multiple applications of `intersection2D`
intersections2D :: [Shape] -> Shape
intersections2D = toBooleans2D BOPAlgo.Operation.Common

-- | An empty 2D shape
emptyShape :: Shape
emptyShape = Shape . unsafeFromAcquire $ 
    upcast <$> (MakeFace.face =<< MakeFace.new)

-- defining these boolean operators here, rather than in Waterfall.TwoD.Booleans 
-- means that we can use them in typeclass instances without resorting to orphans

instance Semigroup Shape where
    (<>) = union2D

instance Monoid Shape where
    mempty = emptyShape
    mconcat = unions2D

instance Lattice Shape where 
    (/\) = intersection2D
    (\/) = union2D

instance BoundedJoinSemiLattice Shape where
    bottom = emptyShape