RasmahRasmah

Converting Representations: What Survives and What Does Not

Overview

Rasmah keeps one parametric model and derives every other representation from it. This tutorial is a map of those representations, the file formats that serialize each one, and — most importantly — which conversions are lossless and which lose information and are controlled by a resolution parameter.

The representations, from most to least “exact”:

Representation Type What it stores Lossless?
Parametric model Feature the feature recipe (CSG operations over values) — (source of truth)
BRep BRep exact topology + analytic surfaces exact geometry
SDF ImplicitGeometry signed-distance scalar field analytic (per feature)
Surface mesh TriangleMesh triangle soup / shared-vertex mesh approximates a surface
Volume mesh TetMesh tetrahedral mesh approximates a solid
Scalar volume DicomVolume a regular 3D grid of values sampled field

File formats ↔︎ representations

Format Representation it stores Nature
.rasmah parametric model lossless (JSON)
STEP BRep lossless within the analytic subset
STL / OBJ / PLY / .vtp / MSH (tris) surface mesh lossless for the mesh
.vtu / MSH (tets) / INP volume mesh lossless for the mesh
DICOM / NIfTI scalar volume lossless for the grid (pixel depth)

The conversion graph

using Rasmah

model = difference(box(4.0, 2.0, 0.5), cylinder(0.5, 0.5))
Difference{Box{Float64, Float64, Float64}, Cylinder{Float64, Float64}}(Box{Float64, Float64, Float64}(4.0, 2.0, 0.5), Cylinder{Float64, Float64}(0.5, 0.5))
                    Feature  ──brep()──►  BRep  ──write_step──►  .step
                       │                        │
                  field(x)                 surface_mesh(b) (tessellate)
                       │                        │
                       ▼                        ▼
                      SDF  ◄──── to_sdf(b) ──  surface mesh ──► STL/OBJ/PLY/VTK
                       │                        ▲
             surface_mesh / tetrahedralize      │ to_sdf(mesh)
                       │                        │
                       ├──────────────►  volume mesh  ──► VTU/MSH/INP
                       │
                   sample_grid
                       │
                       ▼
                  scalar volume  ──►  DICOM / NIfTI
                       ▲
                       │  segment(vol, threshold)
                      SDF

Feature → SDF and Feature → BRep

Both are lossless: the signed-distance field is evaluated analytically from the same features, and the BRep is the exact topological model. No resolution parameter exists because no sampling happens:

b = brep(model)                        # exact BRep

# numeric volumes via the divergence theorem (analytic `volume` is primitive-only)
println("SDF volume:  ", round(volume(surface_mesh(model; n=64)); digits=3))
println("BRep volume: ", round(volume(surface_mesh(b; n=48)); digits=3))
SDF volume:  3.607 m^3
BRep volume: 3.608 m^3

SDF / BRep → mesh (lossy, resolution-controlled)

Meshing is where information is lost: a smooth surface becomes flat triangles, and the error is set by a resolution parameter. Every mesher takes one:

# implicit → surface (n = grid resolution along each axis)
m1 = surface_mesh(model; n=32)
m2 = surface_mesh(model; n=96)
println("n=32: ", size(m1.faces, 2), " tris, area ", round(surface_area(m1); digits=4))
println("n=96: ", size(m2.faces, 2), " tris, area ", round(surface_area(m2); digits=4))

# implicit → volume (n, and optionally conform for a boundary-fitted mesh)
vol = tetrahedralize(model, 16)
volc = tetrahedralize(model, 16; conform=true)
println("marching tets: ", size(vol.elements, 2), "   conforming: ", size(volc.elements, 2))

# exact BRep → surface (n = tessellation points per curve/loop)
mb = surface_mesh(b; n=24)
println("tessellated BRep: ", size(mb.faces, 2), " tris")

# exact BRep → volume (`volume_mesh` is the named alias of `tetrahedralize(b, n)`)
vb = volume_mesh(b, 12)
println("BRep volume mesh: ", size(vb.elements, 2), " tets")
n=32: 12304 tris, area 21.1712 m^2
n=96: 110144 tris, area 21.7226 m^2
marching tets: 34936   conforming: 34936
tessellated BRep: 33706 tris
BRep volume mesh: 15892 tets

Options worth knowing:

  • surface_mesh(; adaptive=true, max_depth=6) — adaptive octree instead of a uniform grid (finer near the surface only).
  • tetrahedralize(; conform=true) — snaps boundary vertices onto the surface and relaxes the interior (see Meshing Algorithms).

Mesh → SDF (lossy, but converges)

A mesh can be turned back into a signed-distance field via a bounding-volume hierarchy + winding-number sign. It is approximate but the underlying surface is recovered to the mesh’s own resolution:

sdf2 = to_sdf(m2)                      # surface mesh → SDF
println("recovered distance at (2.5,0,0): ", sdf2([2.5, 0.0, 0.0]))
println("analytic distance:             ", field(model, [2.5, 0.0, 0.0]))
recovered distance at (2.5,0,0): 0.5
analytic distance:             0.5

SDF ↔︎ scalar volume (lossy, grid-limited)

sample_grid evaluates an SDF on a regular grid (the DICOM/NIfTI export path), and segment thresholds a volume back into a level set (the import path). Both are limited by the grid spacing:

grid = sample_grid(x -> field(model, x), [-2.5, -1.5, -0.75], [2.5, 1.5, 0.75], (50, 30, 15))
println("sampled grid: ", size(grid.values), " (spacing ", round.(grid.spacing; digits=3), ")")

seg = segment(grid, 0.0)               # volume → level set
println("inside the solid (1.5,0,0): ", seg([1.5, 0.0, 0.0]) > 0)
println("in the hole (0,0,0):        ", seg([0.0, 0.0, 0.0]) < 0)
println("outside the box (2.4,0,0):  ", seg([2.4, 0.0, 0.0]) < 0)
sampled grid: (50, 30, 15) (spacing [0.102, 0.103, 0.107])
inside the solid (1.5,0,0): true
in the hole (0,0,0):        true
outside the box (2.4,0,0):  true

What cannot be converted

  • Mesh → BRep (reverse engineering a surface mesh into exact topology/NURBS) is out of scope — a triangle mesh has lost the analytic surface.
  • SDF → BRep in general: the implicit field has no topology to recover.
  • BRep → SDF exists but is lossy (to_sdf(b) tessellates first).

Summary

lossless:  Feature ⇄ SDF ⇄ Feature,  Feature → BRep,  BRep → STEP,
           mesh → STL/OBJ/PLY/VTK/MSH/INP (for the mesh itself),
           grid → DICOM/NIfTI (to pixel depth)

lossy (resolution n):  anything → mesh,  mesh → SDF,  SDF ⇄ grid,
                       BRep → SDF

impossible/out of scope:  mesh → BRep,  SDF → BRep

The parametric model is the only thing worth keeping around: it regenerates every other representation, exactly, at any resolution, and differentiably.

Next steps