Own the whole export pipeline, colour and mesh repair included - #93
Merged
Conversation
Export was split between two implementations: this repo wrote STL/OBJ/OFF/
3MF as flat geometry, while BelfrySCAD's exporters.py held the actual
truth about colour and mesh repair in ~400 lines of Python the CLI never
saw. The two disagreed. `cube(100); cube(100, center=true);` exported here
as two overlapping objects of 24 triangles where real OpenSCAD writes one
of 36 -- top level is an implicit union -- and OBJ came out with no `o`
groups, no materials and no .mtl at all.
Ported from exporters.py, behaviour-for-behaviour:
* splitBodiesForExport: the implicit top-level union, cut into objects
that never share volume. One object per colour, later shape winning
any overlap (painter's order), then one per connected component.
* Per-triangle colour, which 3MF's model is built for -- its spec is
explicit that colour describes the surface, not the distribution of
material through the volume -- so a body whose surface came out of a
multi-colour CSG merge is written faithfully with no volume split.
* PLY, VRML97 and X3D 3.3 (Interchange profile) writers, plus ASCII STL
and OBJ with a companion .mtl.
* exportModel(): one entry point, format from the extension, returning
the warnings to surface rather than logging them. It also carries the
repair policy the GUI used to apply itself -- sliver stripping and the
mesh check on the merged mesh, per-body checks for the multi-object
formats -- and keeps open shells' triangles rather than dropping them.
The two subtle rules came across intact, both of which look like details
and are not: a per-triangle colour array indexes the triangle list it was
built against, so it is carried only when the result's triangles compare
equal rather than assuming which paths are no-ops; and the per-colour
claim skips the subtraction when bounding boxes cannot overlap, because
`A - disjoint B` returns A's volume but reorders its triangle list.
Python gets an opaque Geometry handle instead of another array round-trip.
bodyToDict flattens every Manifold into numpy for the renderer, which is
all the renderer needs, but export has to do real CSG -- rebuilding
Manifolds from those arrays costs ~146ms on a 224k-triangle model and
loses Manifold's provenance. Evaluator stashes the handle as `.geometry`
alongside csg_tree/profile_result, so evaluate()'s own 2-tuple result is
unchanged and callers that only render never see it.
Measured honestly: like-for-like on Dalek (224k triangles) this is 1045ms
against Python's 976ms, i.e. no faster today. The rebuild saving is real
but swamped by the mesh checks and the split, which dominate and which
both sides hand to the same Manifold library. The handle is kept for the
architecture and the provenance, not for a speedup it does not deliver.
Output verified identical to the Python implementation across all six
formats on a two-tone-plus-transparent model -- triangle counts, object
counts, per-triangle colour indices, material counts -- and the union bug
now matches real OpenSCAD exactly (36 triangles, 1 object).
1481 tests pass, green across three consecutive -j8 runs after fixing a
test-isolation bug the new CLI format cases introduced: they wrote and
deleted a shared cube.scad, so under ctest -j they destroyed each other's
input.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Export was split between two implementations: this repo wrote STL/OBJ/OFF/3MF as flat geometry, while BelfrySCAD's
exporters.pyheld the actual truth about colour and mesh repair in ~400 lines of Python the CLI never saw. They disagreed:cube(100); cube(100,center=true);→ 3MFv/f, noo, nousemtl, no.mtlPorted from exporters.py, behaviour-for-behaviour
splitBodiesForExport— the implicit top-level union, cut into objects that never share volume: one per colour (later shape wins an overlap, painter's order), then one per connected component..mtl.exportModel()— one entry point, format from the extension, returning warnings rather than logging them, and carrying the repair policy the GUI used to apply itself (sliver stripping + mesh check on the merged mesh; per-body checks for multi-object formats). Open shells keep their triangles rather than being dropped.Two rules came across intact that look like details and are not:
A - disjoint Breturns A's volume but reorders its triangle list, silently destroying those colours.Python gets a handle, not another array round-trip
bodyToDictflattens every Manifold into numpy for the renderer, which is all the renderer needs — but export has to do real CSG.Evaluatorstashes an opaqueGeometryhandle as.geometry, alongsidecsg_tree/profile_result, soevaluate()'s own 2-tuple result is unchanged and render-only callers never see it.Measured honestly: this is not faster today. Like-for-like on Dalek (224k triangles) it is 1045 ms against Python's 976 ms. The ~146 ms rebuild saving is real in isolation but swamped by the mesh checks and the split, which dominate and which both sides hand to the same Manifold library. The handle is kept for the architecture and the provenance, not for a speedup it does not deliver.
Verification
-j8runs — after fixing a test-isolation bug my new CLI format cases introduced (they wrote and deleted a sharedcube.scad, so underctest -jthey destroyed each other's input).CLI now reaches every format:
-o out.{stl,obj,off,3mf,ply,wrl,x3d}, plus--ascii-stl.🤖 Generated with Claude Code