waterfall-cad-0.2.2.0: src/Waterfall/Internal/Remesh.hs
{-|
Module: Waterfall.Internal.Remesh
This code exists because the opencascade GLTF loading code generates "weird" BReps
The FreeCAD sourcecode describes this as follows:
> The glTF reader creates a compound of faces that only contains the triangulation
> but not the underlying surfaces. This leads to faces without boundaries.
> The triangulation is used to create a valid shape.
The practical result of this, seems to be that directly using an `OpenCascade.TopoDS.Shape`
loaded using `OpenCascade.RWGltf.CafReader` in most operations will lead to segmentation faults.
However, we can safely access the Triangulation of the Shape, construct Polygons from this
and then use BReps derived from these Polygons.
In this way, the `remesh` function produces a new Boundary Represenation from the Mesh of an `OpenCascade.TopoDS.Shape`
-}
module Waterfall.Internal.Remesh
( remesh
) where
import qualified OpenCascade.GP.Pnt as GP.Pnt
import qualified OpenCascade.TopoDS as TopoDS
import qualified OpenCascade.TopoDS.Shape as TopoDS.Shape
import qualified OpenCascade.TopoDS.Compound as TopoDS.Compound
import qualified OpenCascade.TopExp.Explorer as TopExp.Explorer
import qualified OpenCascade.TopAbs.ShapeEnum as ShapeEnum
import qualified OpenCascade.TopAbs.Orientation as TopAbs.Orientation
import qualified OpenCascade.BRepBuilderAPI.Sewing as BRepBuilderAPI.Sewing
import qualified OpenCascade.BRepBuilderAPI.MakePolygon as BRepBuilderAPI.MakePolygon
import qualified OpenCascade.BRepBuilderAPI.MakeFace as BRepBuilderAPI.MakeFace
import qualified OpenCascade.BRepBuilderAPI.MakeSolid as BRepBuilderAPI.MakeSolid
import qualified OpenCascade.BRepBuilderAPI.MakeShape as BRepBuilderAPI.MakeShape
import qualified OpenCascade.BRep.Tool as BRep.Tool
import qualified OpenCascade.BRepLib as BRepLib
import qualified OpenCascade.TopoDS.Builder as TopoDS.Builder
import qualified OpenCascade.BRepMesh.IncrementalMesh as BRepMesh.IncrementalMesh
import qualified OpenCascade.Poly.Triangulation as Poly.Triangulation
import qualified OpenCascade.Poly.Triangle as Poly.Triangle
import qualified OpenCascade.TopLoc.Location as TopLoc.Location
import OpenCascade.Inheritance (upcast, unsafeDowncast)
import Foreign.Ptr (Ptr)
import Data.Acquire (Acquire)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad (when, unless, forM_, (<=<))
checkNonNull:: MonadIO m => Ptr TopoDS.Shape -> m (Maybe (Ptr TopoDS.Shape))
checkNonNull shape = do
isNull <- liftIO . TopoDS.Shape.isNull $ shape
return $ if isNull
then Nothing
else Just shape
remesh :: Ptr TopoDS.Shape -> Acquire (Maybe (Ptr TopoDS.Shape))
remesh s = do
let linDeflection = 0.01
mesh <- BRepMesh.IncrementalMesh.fromShapeAndLinDeflection s linDeflection
liftIO $ BRepMesh.IncrementalMesh.perform mesh
builder <- TopoDS.Builder.new
compound <- TopoDS.Compound.new
liftIO $ TopoDS.Builder.makeCompound builder compound
sewing <- BRepBuilderAPI.Sewing.new 1e-6 True True True False
explorer <- TopExp.Explorer.new s ShapeEnum.Face
let actionForEachFace :: Acquire ()
actionForEachFace = do
faceAsShape <- liftIO $ TopExp.Explorer.value explorer
faceAsFace <- liftIO . unsafeDowncast $ faceAsShape
loc <- TopLoc.Location.new
orientation <- liftIO $ TopoDS.Shape.orientation faceAsShape
trsf <- TopLoc.Location.toGPTrsf loc
triangulation <- BRep.Tool.triangulation faceAsFace loc
triCount <- liftIO $ Poly.Triangulation.nbTriangles triangulation
forM_ [1..triCount] $ \i -> do
triangle <- Poly.Triangulation.triangle triangulation i
let p = (`GP.Pnt.transformed` trsf) <=< Poly.Triangulation.node triangulation <=< liftIO . Poly.Triangle.value triangle
p1 <- p 1
p2 <- p 2
p3 <- p 3
let pointsEqual a b = liftIO $ GP.Pnt.isEqual a b 0
p12Coincident <- pointsEqual p1 p2
p13Coincident <- pointsEqual p1 p3
p23Coincident <- pointsEqual p2 p3
let anyPointsCoincident = p12Coincident || p13Coincident || p23Coincident
unless anyPointsCoincident $ do
let makePolygon p1' p2' p3' = BRepBuilderAPI.MakePolygon.from3Pnts p1' p2' p3' True
polygon <- if orientation == TopAbs.Orientation.Reversed
then makePolygon p1 p3 p2
else makePolygon p1 p2 p3
polygonIsNull <- liftIO $ TopoDS.Shape.isNull (upcast polygon)
unless polygonIsNull $ do
makeFace <- BRepBuilderAPI.MakeFace.fromWire polygon False
newFace <- BRepBuilderAPI.MakeShape.shape (upcast makeFace)
faceIsNull <- liftIO $ TopoDS.Shape.isNull newFace
unless faceIsNull $ liftIO $ TopoDS.Builder.add builder (upcast compound) newFace
let go = do
isMore <- liftIO $ TopExp.Explorer.more explorer
when isMore $ do
actionForEachFace
liftIO $ TopExp.Explorer.next explorer
go
go
liftIO $ BRepBuilderAPI.Sewing.load sewing (upcast compound)
liftIO . BRepBuilderAPI.Sewing.perform $ sewing
shape <- BRepBuilderAPI.Sewing.sewedShape sewing
makeSolid <- BRepBuilderAPI.MakeSolid.new
shapeAsShell <- liftIO $ unsafeDowncast shape
liftIO $ BRepBuilderAPI.MakeSolid.add makeSolid shapeAsShell
shapeAsSolid <- BRepBuilderAPI.MakeSolid.solid makeSolid
maybeNotNull <- checkNonNull (upcast shapeAsSolid)
case maybeNotNull of
Nothing -> return Nothing
Just _ -> do
orientable <- liftIO $ BRepLib.orientClosedSolid (shapeAsSolid)
if orientable
then return . Just . upcast $ shapeAsSolid
else return Nothing