Construct mesh export
SDF to triangle mesh export pipeline
Overview
The mesh export pipeline converts data-driven SDF scenes into triangle mesh files (STL, OBJ, PLY, VTK). The scene's field — evaluated through the libeval Native JIT — is the single source of truth: extraction, post-processing and the quality metrics all consume the same evaluator, so the exported mesh can be measured exactly against what was modelled.
Feature-preserving extraction and the post-process pipeline are specified in the construct-mesh-quality internal spec (content/doc/internal/specs/construct-mesh-quality/); this page is the reference summary.
Architecture
Scene snapshot (QVector<SDFNode>)
-> SdfSceneGraphBuilder -> eval::Graph
-> eval Backend::Native JIT (CPUFieldEvaluator; GPU batch path for chunks)
-> Φ(x) and ∇Φ(x) (IFieldEvaluator::evaluateWithGradient — analytic
where the op tree is capable, FD-tetrahedron floor
everywhere else)
-> extractor: Marching Cubes | Dual Contouring | adaptive octree DC
-> post-process chain (optional): weld → sdfProject → coplanarMerge → qemDecimate
-> mesh::Mesh<float> -> mesh::io::IORegistry -> STL/OBJ/PLY/VTK
Extractor family
Selected per export via ExportRequest.extractor (UI: the export dialog's Quality group):
| Extractor | Behaviour |
|---|---|
MarchingCubes |
Legacy baseline. Vertices constrained to grid edges — sharp features alias into a staircase at any finite resolution. Chunked + parallel. |
DualContouring |
Uniform-grid Ju-2002 DC: one vertex per sign-change cell, positioned by a robust QEF over the cell's Hermite (Φ, ∇Φ) samples; bisection-refined edge crossings; exact chunk-seam welds via a shared global grid frame. |
DualContouringOctree |
One global octree over the domain with a conservative manifold-preserving collapse gate: triangles concentrate where the surface has detail (a box collapses to its exact 12-triangle mesh) and the output is guaranteed closed ∧ manifold ∧ orientable on the whole test corpus. |
Post-process stages
Declared as an ExportRequest.postprocess string list (also the bench definition's postprocess array); executed at the single buildAndSaveMesh hook. All stages preserve the watertight-manifold invariant — mutating stages re-validate and restore their input on regression.
| Stage | Does |
|---|---|
weld[(tol)] |
Spatial-hash vertex weld of the extractor's triangle soup (auto-prepended when absent). |
sdfProject[(iters)] |
Newton-snaps vertices onto the exact zero level set x ← x − Φ·∇Φ/‖∇Φ‖² with tangential relaxation; crease vertices move only along their feature line, corners are pinned. |
coplanarMerge[(angleDeg)] |
Region-grows near-coplanar patches and retriangulates each from its boundary loop — flat caps become fans. |
qemDecimate[(ratio)] |
Feature-aware quadric decimation: crease constraint planes + corner pinning keep the sharp edges extraction captured; link-condition collapses only. |
Quality metrics (SDF ground truth)
construct::bench::computeMeshMetrics measures a mesh against the same field it was extracted from — no reference mesh:
- Surface error — |Φ| at vertices, edge midpoints and face centroids (mean/p95/max).
- Feature error — surface error restricted to the field-tagged high-curvature band (the cylinder-rim metric MC fails).
- Normal error — angle between face normals and ∇Φ.
- Topology — closed ∧ manifold ∧ orientable + boundary-edge count (
HalfedgeMeshpredicates); this triple is the export quality gate. - Triangle quality — min-angle statistics.
The MeshExportBench runner records these per scene × pipeline definition into a lab::TestDB results database; browse it with the Mesh Lab activity or the mesh.bench.* OPAL tasks.
Source files
construct/eval/CPUFieldEvaluator.* — libeval Native evaluator (+ analytic ∇Φ)
construct/eval/AnalyticGradient.* — §AR-2.2 capability-gated analytic gradients
construct/mesh/MarchingCubes.* — baseline extractor
construct/mesh/DualContouring.* — uniform-grid DC + QEF
construct/mesh/Octree.* — adaptive octree DC + manifold gate
construct/mesh/Qef.* — robust QEF solver
construct/export/ExportPipeline.* — async orchestrator + post-process hook
construct/export/PostProcess.* — §BM-2 spec-string parser/runner
construct/bench/MeshExportBench.* — corpus × definitions bench runner
construct/bench/MeshMetrics.* — SDF-ground-truth metrics
construct/bench/MeshBenchWorker.* — overnight batch worker, inputs and outputs by hash
construct/bench/MeshBenchJob.* — submits a bench set and writes its outputs back
construct/ui/MeshExportConfigDialog.* — export dialog (Quality group)
construct/ui/MeshLabActivity.* — results browser
libmesh/mesh/process/… — weld / SdfProjection / CoplanarMerge / QEM
Tests
testMeshExport,testConstructCli— pipeline end-to-end.testQef,testManifoldDC— DC extraction + the watertight-manifold corpus invariant.testSdfProjection,testFeatureQem— post-process stages + the post-decimate invariant.testMeshMetrics,testMeshExportBench— metrics and the per-cell gates.construct-bench mesh-full --scenes <dir> --definitions <dir>— the whole corpus x definitions matrix. It wastestMeshBenchFull, which asserted only that the bench ran and had to writeQTEST_FUNCTION_TIMEOUTinto its own environment to outlive QtTest's watchdog (no-environment-variables row T35).testMeshLab— batch worker → results DB → browser → OPAL tasks.