Product composition engine (typed ports, couplings, conduits)
Generated from
pattern-library/registry.json— do not edit by hand; change the registry and runbun run patterns:build.
Category: 3D · Tier: deep · Maturity: proven (verified 2026-10-20 @ 42d5b7c-dirty) · Risk: low — frontend only; plausibility checks, not engineering certification
Reusable 3D products with typed connection ports join into an editable assembly: a framework-free core keeps definitions, occurrences, ports, couplings, conduit routes and port rules apart, validates and edits them deterministically, and a thin Threlte layer renders and edits the result.
When to use: Use when users assemble products that connect through defined points — pipes and fittings, cables and devices, modular furniture — and the assembly must be saved, validated against port rules and edited by pointer and keyboard.
Avoid when
- One product on its own, viewed or customised — the model viewer and customizer patterns are enough
- The result must be engineering- or electrically-validated (sizing, pressure, load, code compliance) — this engine checks plausibility of declared data, never physics
- Parts must satisfy geometric constraints beyond face-to-face mates (distances, angles, parametric CAD) — there is no constraint solver
- A BIM model is the source of truth — edit it in a BIM toolkit and treat this as a viewer at most
Alternatives
- An IFC/BIM toolkit (IfcOpenShell, web-ifc) working on the IFC model directly — prefer when the authoritative data is an IFC model that other trades also edit
- CAD routing (SolidWorks Routing, Onshape mate connectors) — prefer when routes need fittings catalogues, bend radii, sizing or certified drawings
Docs
- docs/blueprint/3d/product-composition.md — The full pattern: layer split, frames + IFC mapping, invariants, operations, diagnostics, scope and emulation steps (GitHub · GitLab)
Code
src/lib/product-composition/— Framework-free core: frames, standards, serialisation, resolution, networks, validation, operations, conduits (GitHub · GitLab)src/lib/product-composition/README.md— The core's boundaries and one responsibility per file (GitHub · GitLab)src/lib/schemas/product-composition.ts— The persisted document (Valibot), with the port-frame convention (GitHub · GitLab)src/lib/components/3d/composition/— Threlte rendering and the CompositionEditor (undo, gestures, import/export) (GitHub · GitLab)src/lib/showcases/3d/compose/— Heating and shelving fixtures on the same engine (GitHub · GitLab)
Tests
src/lib/product-composition/frame.test.ts— Canonical frames: quantise then fix the quaternion sign, so a half turn serialises one way; mates and the free-clocking snap (GitHub · GitLab)src/lib/product-composition/standards.test.ts(GitHub · GitLab)src/lib/product-composition/serialize.test.ts(GitHub · GitLab)src/lib/product-composition/resolve.test.ts(GitHub · GitLab)src/lib/product-composition/network.test.ts(GitHub · GitLab)src/lib/product-composition/validate.test.ts(GitHub · GitLab)src/lib/product-composition/operations.test.ts— Connect/refuse rules, a mated snap moving the whole cluster, drift-free moves, a consistent ring of mates, the remove cascade (GitHub · GitLab)src/lib/product-composition/conduit.test.ts(GitHub · GitLab)src/lib/showcases/3d/compose/compose.test.ts— Both fixtures valid on the unchanged core; the domain-neutrality and boundary gates; the six-step scenario on the core alone (GitHub · GitLab)src/lib/components/3d/composition/composition-editor.svelte.test.ts— Editor state: apply/refuse, undo, one undo step per drag gesture, import (GitHub · GitLab)src/lib/components/3d/composition/conduit-tube.test.ts— Filleted tube path through the conduit polyline (GitHub · GitLab)tests/browser/compose.spec.ts— Playwright: the scenario by keyboard plus a gizmo drag, asserted through Export JSON — bun run test, outside validate (GitHub · GitLab)
Proof
Invariants
- The core imports no framework, DOM or renderer — only its schema, Valibot and Three.js math — so any caller validates a document exactly as the editor does.
- Compatibility comes from PortStandard data the caller supplies; the core names no domain (a gate fails on heating or shelving words in its code).
- Operations are pure and deterministic: they return { composition, diagnostics }, never throw, refuse by returning the input unchanged, and the same edits give byte-identical documents (numbers quantised, quaternion sign fixed after quantising).
- A coupling says which ports are joined; a conduit says where the pipe or cable runs. Conduit ends always derive from the live port frames.
- Mates hold: a mated connect snaps the moving side's whole cluster, a move re-derives the cluster along a spanning tree with no drift, and a ring is accepted only if its mates already hold.
- Removing a product removes its couplings and their conduits; no coupling joins one product to itself or the same pair twice; a mate uses its port exclusively.
- Findings mean plausibility of declared data only — never certification — and the UI says so on screen.
Emulation notes
- Fix the port frame convention first (+Z out, +Y clocking, mate = half turn about +Y) and write the IFC mapping down before any geometry.
- Express every compatibility rule as PortStandard data; keep a grep gate for your own domain's words over the core.
- Write the scenario test on the pure core before any UI; the browser spec then replays the same steps.
- Route conduits by hand with waypoints; add auto-routing (bend-penalised A* on a voxel grid) only for obstacles, many conduits or fitting catalogues.
- Give every canvas action a keyboard path (product list, port list, partner picker, nudge buttons) and pause camera controls during gizmo drags yourself if your Threlte version does not.
Depends on
Machine-readable record: product-connection-engine in pattern-library/registry.json.