Giter Club home page Giter Club logo

opencascade-hs's People

Contributors

ad-si avatar joe-warren avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

aavogt

opencascade-hs's Issues

The Query Problem

Background

A Waterfall.Solid is a datatype that represents a CAD model of a 3D object.

Under the hood, a Waterfall.Solid is a newtype of the Acquire Monad, of a Pointer to an OpenCascade TopoDS.Shape, defined like so:

newtype Solid = Solid { runSolid :: Acquire (Ptr TopoDS.Shape.Shape) }

A Solid wraps an IO action which generates that shape using the underlying CAD library.

Despite the fact that Waterfall.Solid is implemented as a newtype of a Monad.
Most CAD operations are not monadic functions.
If you think about a program like the following, there would be no benefit to having to use Applicative syntax in the implementation of someComposite.

someCube :: Waterfall.Solid
someCube = Waterfall.centeredCube

someCylinder :: Waterfall.Solid
someCylinder = Waterfall.unitCylinder

someSphere :: Waterfall.Solid
someSphere = Waterfall.unitSphere

someComposite :: Waterfall.Solid
someComposite = (someCube `Waterfall.union` someCylinder) `Waterfall.difference` someSphere

I've got no desire to move away from this pattern of representing Solid as a wrapped Acqure (for example by switching to ForeignPtr).
This is not just because I like the use of Acquire to manage memory, but also because BRep CAD libraries are inherently somewhat error prone. OpenCascade operations will frequently raise exceptions, and modeling Solid with a monad under the hood lets me be precise about where these errors are thrown.


My Design Problem

My issue with the above design is that there are functions that I want to write that let you query properties of a Waterfall.Solid.

A representative example would be calculating the volume of an object. We might reasonably want a function like.

volume :: Solid -> Double

However, with the Acquire monad in the way, it's not possible to write this.

Examples of things we might want to calculate from a Waterfall.Solid include:

  • Volume (Solid -> Double)
  • Center of Mass (Solid -> V3 Double)
  • Moment of Inertia (Solid -> Double)
  • Axis Aligned Bounding Box (Solid -> (V3 Double, V3 Double))
  • Oriented Bounding Box (tbd)
  • Minimum Distance (Solid -> Solid -> Double)
  • Solids Overlap (Solid -> Solid -> Bool)
  • Solid Enclosed By (Solid -> Solid -> Bool)

Proposed solution

I want to expose a monad from Waterfall which would be implemented as a newtype of Acquire.

newtype WaterfallM a = { runWaterfallM :: Acquire a } deriving (Functor, Applicative, Monad) via Acquire 

This would let me write volume with the following type

volume :: Solid -> WaterfallM Double

I'd then provide a way to join an operation in WaterfallM into a Solid:

joinWaterfallM :: WaterfallM Solid -> Solid.

Because there are multiple types implemented in terms of Acquire, this would probably be implemented with a typeclass.

class WaterfallMJoinable a where
    joinWaterfallM :: WaterfallM a -> a

This would have instances for Solid, Path, Path2D and Shape

An example program written with this API might look like:

normalizeVolume :: Waterfall.Solid -> Waterfall.Solid
normalizeVolume s = joinWaterfallM $
    vol <- Waterfall.volume s
    return $ Waterfall.unitScale (vol ** (1/3)) s

Open Questions

  • Are there better alternatives to this design that I've overlooked?
    • This feels like a common problem, is there any prior art?
  • WaterfallM is a dire name, as is joinWaterfallM, what would be better?
    • I quite like Query for the monad, embedQuery for the function, and Embeddable for the typeclass? Is this anything?
  • I'm slightly wary that if I write this naively, I'll run the risk of having to compute some solids multiple times. Is there anything I can add to this design to mitigate that?
    • e.g in the normalizeVolume example, I wouldn't want to compute the solid once to calculate the volume, and then redo that work when computing the eventual result
  • Typically, this is the sort of thing that I'd just prototype, but I know if I implement this I'm going to wind out releasing it, and I wanted to write up the problem and try to get some feedback first. Should I just pull the trigger?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.