Part connection ontology + deep solid merge for module-instantiated parts#99
Part connection ontology + deep solid merge for module-instantiated parts#99krazyjakee wants to merge 3 commits into
Conversation
Design note and seed part library for a blocky, grid-based part system whose parts are mogen modules with typed oriented connectors. The connection vocabulary is grounded in the real LDCad shadow-library snapping metas (SNAP_CYL/CLP/FGR/SPH/GEN); studs/pins/axles collapse into one parametric cylinder, mapping onto Connector's pos+quat+tag+radius. - docs/part-connection-ontology.md: tag scheme (kind:profile:gender[:key]), mate predicate, Lego 5:2 grid, two-layer semantic/geometry split, and the verified solid-merge behaviour + module-nesting gap. - examples/parts/lego_bricks.mog: brick_1x1 / plate_1x1 / tile_1x1 / brick_2x1 with connectors at real Lego measurements; compiles and builds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`solid { … }` previously only merged direct leaf-mesh children, so parts
authored as modules (always group-wrapped by `use`) never merged — a build
made of `use`d parts kept every node separate.
Add a deep merge to `merge_solid_groups`: when a solid's entire subtree is
mergeable leaves (no skins/colliders/slots/protected nodes/non-manifold
meshes/cast_shadow=false opt-outs), collapse ALL same-material *descendant*
leaves into one CSG-unioned mesh per material, baking each leaf's transform
relative to the solid and flattening the intermediate groups. Any keeper in
the subtree disqualifies the solid and falls back to the existing shallow
direct-child merge. Watertight via the same try_union_many/clean_csg_output
path as in-DSL CSG.
examples/parts/lego_bricks.mog now exports 8 nodes / 3 meshes (was 30 / 5).
Adds two tests (nested-group flatten+merge; keeper triggers safe fallback)
and updates docs/part-connection-ontology.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
collect_deep_leaves() only recorded mergeable leaves and recursed into non-mergeable nodes with children. A non-mergeable, childless node (e.g. a named anchor/locator with no mesh) fell through both branches and was silently discarded, even though solid_qualifies_deep() lets such nodes qualify a solid for the deep merge (they're not a keeper mesh, skin, collider, or slot). Track these as "keepers" and re-emit each one baked into solid-space before the merged material groups, alongside regression tests for the fix and for grouping deep-merged leaves by material.
Review findingsReviewed 1. Correctness (High) — deep merge silently dropped meshless marker nodes — fixed
Confirmed with a reproduction: a Fix: 2. Test coverage (Medium) — no test for multi-material deep merge — fixedAll existing deep-merge tests used a single material. Added 3. Visual review (informational, out of scope for this PR)Built One separate observation: the demo renders as a tiny dot in the 800×800 thumbnail, whereas a meter-scale example ( Other checks
Pushed to Generated by Claude Code |
What & why
Groundwork for a blocky, grid-based part system where parts are authored as mogen
modules with typed, orientedconnectors so an algorithm can snap them into models. The connection vocabulary is mined from Lego (specifically the LDCad shadow-library snapping metas) — not its geometry, so there's no copyright entanglement — and the parts use real Lego measurements.Along the way this fixes a concrete blocker: the export merge couldn't collapse parts built from modules, which defeats the whole "minimum geometry" premise.
Changes
1. Ontology + seed parts (
36da4fa)docs/part-connection-ontology.md— the design: connection taxonomy grounded in the real LDCad metas (SNAP_CYL/CLP/FGR/SPH/GEN; studs/pins/axles collapse into one parametric cylinder, mapping ontoConnector'spos+quat+tag+radius), the tag schemekind:profile:gender[:key], the mate predicate, the Lego 5:2 grid, and the two-layer semantic/geometry split.examples/parts/lego_bricks.mog—brick_1x1/plate_1x1/tile_1x1/brick_2x1with connectors at real Lego dimensions (stud pitch 8mm, plate 3.2mm, brick 9.6mm), plus a demo build. Validates clean, builds to GLB.2. Deep solid merge (
dd8fd69)solid { … }previously only merged direct leaf-mesh children. Ausealways wraps its geometry in a group (module body +use's implicit transform group), so module-instantiated parts were never direct children and never merged.merge_solid_groupsnow does a deep merge: when a solid's entire subtree is mergeable leaves, it collapses all same-material descendant leaves into one CSG-unioned mesh per material, baking each leaf's solid-relative transform and flattening the intermediate groups. Any keeper (skin / collider / slot / non-manifold mesh /cast_shadow=falseopt-out / skin-or-clip-referenced node) disqualifies the solid, which falls back to the existing shallow direct-child merge.try_union_many→clean_csg_outputpath as in-DSL CSG.Verification
solidcourse collapses to onemerged_<material>leaf; the standalone tile, not in asolid, stays separate as intended).mogen-exporttests pass, including two new deep-merge tests (nested-group flatten+merge; keeper triggers safe fallback). Clippy clean on the new code.Reviewer notes
mogen buildsummary line reports pre-merge scene counts —inspectthe GLB to see the merged result. (This tripped me up mid-development; worth knowing.)solid-tagged subtrees; the globalmerge_sibling_meshespass and the shallow path are untouched.🤖 Generated with Claude Code