Assembly workbench
A cad-assembly sheet places references: part documents, STEP imports, inline pipe runs and terrain. It authors no geometry of its own; it composes geometry that already exists.
Layout
| Region | Contents |
|---|---|
| Viewport | The composed scene. Click an instance to select it; move/rotate gizmos write source. |
| Floating controls (top right) | Move · Rotate · Undo · Redo · Fit · Walk · Pipe · Terrain. |
| Parts list (left) | Placed instances with moved/missing badges and an eye toggle, a Terrain section, and the BOM roll-up. |
| Placement panel | Typed absolute placement (tx/ty/tz + Euler XYZ) written as an instanceTransform statement. |
| Material panel | Per-instance surface colour + metallic/roughness, written as an instanceMaterial block. |
| Terrain dialog | Map picker / editor for inline DEM terrain (see below). |
| Source panel | The assembly program itself — placement is code. |
Placing things
A reference is a value pinned by content hash, never a path or "latest". The source names a reference; the page payload owns it.
instance(context, id + "t103a", {
"part" : "Aerobic tanktainer — T-103",
"transform" : transform(vector(0, 0, 0) * millimeter, identityMatrix(3))
});Place appends a marked // cad-tool:import-instance block for STEP assets, so an import composes through exactly the same rigid-placement path as a part document. A missing reference is kept as a missing instance plus an error — never silently dropped. Moved instances are overridden with an instanceTransform host op; reset restores the program placement.
The assembly BOM rolls up counts by part name + hash. Instances can be coloured individually with instanceMaterial (color, metallic, roughness), which is how process banks are colour-coded in the Hikurangi train.
Mates are deliberately not in the program. Placement is rigid translation + rotation about X/Y/Z. A constraint solve is not implemented.
STEP imports
A cad-part or cad-assembly page stores a pinned imports list; the bytes live in the app-server's project-scoped content-addressed store. In an assembly an import is referenceable like a part document: Place appends an instance(..., { "part": "<file name>" }) block. Reads are cached per worker by name + hash, so editing the assembly does not re-read a large STEP each time.
Limits: 96 MB upload cap; a 45 MB vendor assembly (13 solids, 546k triangles) takes ~17 s to read plus ~22 s to tessellate in the Node host. The import/read progress is shown in the Imports pane.
Pipes
The Pipe control turns the viewport into two-click picking: click the start point, click the end point, and the run is written as a marked // cad-tool:pipe block:
pipe(context, id + "pipe1", {
"start" : vector(-7230, -600, 1100) * millimeter,
"end" : vector(-2230, -600, 1100) * millimeter,
"outerRadius" : 133 * millimeter,
"innerRadius" : 125 * millimeter,
"name" : "F1 T-103 outlet → T-104 inlet"
});A pipe is a straight, watertight tube mesh built by the kernel with the same world-millimetre convention as everything else. It renders in the composed scene, is picked in the Parts list, and its radii, name and endpoints are edited in the pipe panel (writing the block in place). Pipes are not part references: they are not in the BOM, and flanges/elbows/valves are not yet generated geometry — model those as part documents or import them.
Terrain
An assembly can carry one or more inline terrains picked from a map: Terrain opens a slippy map (keyless Esri imagery + Terrarium elevation), Add terrain fetches the DEM tiles covering the footprint, resamples a 17 × 17 grid and writes a terrain(...) block with the height grid and its WGS-84 site footprint.
- The mesh is a closed, watertight heightfield (top + base slab + skirts) built without OCCT, so it cannot fail for tolerance reasons.
- The grid's highest point is seated on the assembly floor (
originZmm= −highest sample), so placed equipment sits on top of the terrain. - Imagery is derived view data: the editor stitches a satellite drape at view time and never writes imagery bytes to source.
- The same dialog edits an existing site in place (moving a site is one undoable source edit), and a terrain is selected in the viewport to move with the gizmo.
Terrain limits: the mesh spans the picked footprint only (no multi-tile paged DEMs), imagery is not cached to disk, and an offline session shows the untextured solid. Selecting a terrain offers Seat under floor and Delete terrain actions.
Composition and performance
The worker evaluates every referenced part, composes the rigid placements and pipe/terrain meshes, and returns one combined scene plus per-instance records and the BOM. Imported and part geometry are cached by name + hash across edits. A large import blocks its worker (the pane shows reading…), so assemblies are designed for plant-scale layout rather than browsing very heavy vendor geometry.
Worked example: the Hikurangi train
The largest assembly in the repo is the 1,000 m³/day MBR train (data/hikurangi-mbr/assemblies/full-process-train.fs): 13 ISO tanktainers in four process banks plus 8 equipment skids, placed over a picked DEM site. Its program shows every assembly feature together — instances, per-bank instanceMaterial colouring, an inline terrain(...) block, and the inter-bank forward-flow pipe(...) bridges generated by tools/hikurangi-mbr/piping.mjs:
yarn hikurangi:build-stages # regenerate the stage + train documents (incl. pipes)
yarn hikurangi:verify # kernel checks: geometry, placement, terrain, pipe schedule
yarn hikurangi:render-stages # renders the train with the pipes drawnSee docs/HIKURANGI-MBR-PIPING-PLAN.md for the connection standard and the remaining connection work (per-tank nozzle migration, return loops, valves).