Design Overview

Descartes current uses implicit modeling to create solid models. This is a brief overview of the system internals.

Implicit Geometry Pipeline

UX

The UX (API) is designed to be familiar to those who use OpenSCAD. Let's start with a simple example to show the similarities.

This can all be executed in the Julia REPL, so follow along:

using Descartes

c = Cuboid([5,5,5])

h = translate([2.5,2.5,0])Cylinder(1,5)

obj = diff(c,h)

m = HomogenousMesh(obj)

save("cube_with_hole.stl", m)

This first line:

using Descartes

This imports the Descartes library.

c = Cuboid([5,5,5])

Here we construct a cube of size 5x5x5.

h = translate([2.5,2.5,0])Cylinder(1,5)

This line shows the OpenSCADisms. This can be read right-to-left like a matrix operation. First we construct a cylinder of radius 1, and height 5. Next we translate the cylinder to the coordinates 2.5, 2.5, 0 to put it in the center of our cube.

obj = diff(c,h)

Here we difference the cylinder from the cube. The corresponding set operations are union and intersect.

m = HomogenousMesh(obj)

At this point we convert our object to a mesh. Prior to this point the model is simply a data structure. In the next section we will discuss the process of meshign more in-depth.

save("cube_with_hole.stl", m)

Finally we save the mesh as an STL. Other formats such as OBJ, PLY, and OFF are also supported.

Meshing

In the prior section we mentioned that the "model" we create is just a data structure until we mesh it. So let's explore what happens in the meshing process.

The code to convert our data structure to a mesh is actually fairly short:

Current version

function (::Type{MT})(primitives::AbstractPrimitive{3, T}...;
                                         samples=(128,128,128),
                                         algorithm=MarchingCubes()
                                         ) where {T, MT <: AbstractMesh}

    f(x) = FRep(primitives[1], x)
    mesh = MT(f, HyperRectangle(primitives[1]), samples, algorithm)

    for i = 2:length(primitives)
        b = HyperRectangle(primitives[i])
        lm = MT(x -> FRep(primitives[i], x), b, samples, algorithm)
        mesh = merge(mesh, lm)
    end

    return mesh
end

The first two lines here are where the meshing happens:

    f(x) = FRep(primitives[1], x)
    mesh = MT(f, HyperRectangle(primitives[1]), samples, algorithm)

Any primitve or operation must implement two core operations; FRep and HyperRectangle.

FRep is the functional representation (implicit representation) of the model. The first argument to FRep is always the primitive. The second is a generic AbstractVector. In julia we have type inference, so we do not need to annotate, so this will always be fast. FRep by convention must use 0 as the surface of the model, positive outside the model, and negative inside.

HyperRectangle should return the extents of the primitive. The underlying API will handle transformations and basic set operations for us.

In the next line we actually perform the meshing operation. We call the meshtype with the implicit function we created, f, in the bounds generated by HyperRectangle, uniformly sample the space by samples, and actually generate the triangular meshing using algorithm. In this case, the defaults are samples=(128,128,128) and algorithm=MarchingCubes().

In the loop for i = 2:length(primitives) ... end we handle additional primitives passed to our meshing function so you can create a single mesh output from several different objects. In order to maintain performance and resolution these are not unioned.