feat: HVAC ductwork + DWV plumbing systems#402
Merged
Conversation
Items (e.g. solar panels) can now be placed on sloped roof surfaces. The placement system computes euler rotation from the roof surface normal so items sit flush on the slope instead of going inside. - Add roofStrategy to placement-strategies with enter/move/click/leave - Wire roof:enter/move/click/leave events in the placement coordinator - Add calculateRoofRotation in placement-math using surface normals - Support full 3D cursor rotation for sloped surfaces - Items on roofs are parented to the level with world-space rotation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Typed connection points (level-local position, outward direction, diameter, supply/return tag) that kinds expose via def.ports. Placement tools snap to them; a future system graph walks them for connectivity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four new kinds wired into AnyNode and the event bus: - duct-segment: round duct run as a 3D polyline (diameter, material, insulation R, supply/return) - duct-fitting: elbow / tee / reducer with position + euler rotation - duct-terminal: supply register / diffuser / return grille with floor / ceiling / wall mount - hvac-equipment: furnace / air handler / condenser cabinet Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reusable prefix/value pill (with optional signed deltas and an emphasised primary part) so node tools can show the same themed readout the wall H/L/T pill uses. MeasurementPill now delegates to it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- StructureTool gains duct-segment / duct-fitting / duct-terminal / hvac-equipment so the Build tab can arm the registry tools. - useEditor.rotationAxis + cycleRotationAxis(): the world axis R/T rotates fully-3D kinds (duct fittings) around. Lives on the editor store so both the nodes package and the floating action menu can share it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When a duct fitting is selected, show the active R/T rotation axis in a DimensionPill-styled chip stacked directly above the move / duplicate / delete menu — same slot the wall height pill uses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Round duct runs as a registry-driven kind: - geometry: capped cylinder sections + sphere joints + translucent insulation shell; shared buildSection/createDuctMaterial helpers - def.ports: run start/end exposed as typed ports (outward tangents) - shared/ports.ts: scene-wide port query + XZ nearest-port snap used by every HVAC tool - tool: one-segment-per-two-clicks placement with 45° XZ angle lock (Shift = free), Alt-drag vertical risers, port snapping, and a DimensionPill delta readout - system: selection-time path-point drag handles portaled into the duct's scene group — axis-constrained by default, Alt = free plane, Shift = no grid snap, endpoint port-snap, single-undo commits - floorplan: real-width line + system-tinted dashed centerline; risers render as circles Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First kind to expose def.ports. Click-place tool snaps the ghost onto any scene port (position AND orientation, pivoting on the inlet collar); R/T rotates ±45° around the shared useEditor.rotationAxis, Alt cycles the axis (also for a selected fitting via def.keyboardActions + a listener-only def.system). Floorplan renders the projected port-stub symbol. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mount-aware (floor / ceiling / wall) face orientation with a single collar port so duct runs end onto a terminal. Frame + louver geometry, yaw click-place tool with R/T rotation, system-tinted floorplan symbol. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Floor-placed cabinet with supply/return collar ports (condenser has none), giving duct runs a real origin. Cabinet + collar geometry, condenser fan detail, yaw click-place tool with R/T rotation, floorplan footprint with equipment diagonal and collar dots. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
duct-segment, duct-fitting, duct-terminal, hvac-equipment join the registry and are re-exported from the package root. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Duct, Duct Fitting, Register, and HVAC Unit tiles (placeholder icons borrowed from existing assets) arming the registry placement tools. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rebuild the hvac-equipment geometry so each unit reads as real gear: - Furnace: hollow sheet-metal cabinet built from butt-jointed plates (no more z-fighting), open front cut exposing a blue squirrel-cage blower and orange burner manifold/gas valve, plus a front gas line with drip leg. Supply (top) and return (side) walls now carry real circular holes with open-ended collars so ducts pass through. - Air handler: tall white cabinet with two stacked guarded axial fans and finned coil bands down the sides. - Condenser: white coil cabinet with vertical louvered fins on all four faces, dark base/top frame and corner posts, and a top fan under a radial wire guard over a recessed throat. - Shared white cabinet color across all three units. - Default supply/return collar diameter dropped to 8" so duct holes match typical runs. Also: duct-terminal gains floor/ceiling/wall mounting (M to cycle) and the duct-segment snap indicator is smaller.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reads the mated-joint relationship back out of coinciding ports so an edit to one node carries its neighbours along: a moved fitting stretches the duct endpoints touching its collars and rigidly drags fittings mated collar-to-collar. Propagation is deliberately one hop — no runaway network rearrangement. Pure logic (def.ports + arithmetic), consumed by the editor move tool and the duct-segment handle system. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lity - cursorAttached: pin the dragged node to the cursor instead of the offset-preserving drag — small connector-like kinds (duct fittings) read as lagging behind the mouse otherwise. - portSnap: magnetically shift the dragged node so its closest own port mates onto a nearby scene port (optionally filtered by distribution system), e.g. a register collar onto a duct run end. Alt bypasses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
plumbing-fixture kind: toilet / lavatory / kitchen-sink / tub / washer, each a recognizable simple silhouette with one WASTE port at its floor rough-in — so drain runs are drawn FROM a fixture, the way DWV systems actually start. FIXTURE_SPECS centralizes per-type footprint, drain rough-in position, drain size, and IPC Table 709.1 DFU values. - Click-place tool: Q cycles the fixture type (ghost rebuilds live), R/T rotate, pill shows the type + its DFU. - System graph sums drainage fixture units per component (summary.fixtureUnits) and the action-menu pill shows "N DFU" — the load number the upcoming pipe-sizing validators read. - Floorplan: footprint rect + drain dot (toilets get the conventional bowl ellipse); Build tab gains a Fixture tile (lucide:bath). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Swaps the fixture node kind (toilets/sinks/tubs) for a pipe-trap node across the schema union, event bus, node registry, and editor menus. The DFU load accounting that depended on fixtures is removed from the system graph; trap-based DWV modeling supersedes the fixture approach. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Extends ParametricDescriptor with a patch-aware `derive(next, patch)` and a cross-node `reconcile(prev, next)` companion. The inspector folds the derive result into the same update and applies reconcile's other-node patches in one gesture, so editing one field can keep dependent fields and neighbouring nodes consistent (e.g. duct runs re-trimmed onto a resized fitting's collars). Direct store/MCP writes bypass it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds flat-oval cross-sections to duct segments and fittings (elbows sweep a stadium ring through the mitered solid; tees carry oval run/branch profiles), with roll continuity so a riser's profile stays continuous through a fitting. Tees gain a `branchAngle` (45–135°): 90° square tap, <90° a lateral leaning downstream toward flow, >90° leaning upstream. Auto-fitting sizes oval joints by area-equivalent diameter, and the Build tab arms the fitting tool from a Duct sub-panel (also drops the removed fixture tile). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A freely placed drain start is now raised so the 1:48 fall lands on the grid plane (nothing clips below), while a port/body-snapped start keeps its fixed height and the end drops instead. Adds a selection-time system module exposing path-point handles to edit a committed run. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds two core services: `buildRiserDiagram` projects a plumbing network to an isometric riser diagram (lines/markers), and `validateDwv` reports DWV code findings by severity. Surfaces them in the editor via a toggleable riser-diagram overlay panel and a view-toggle button. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The automatic ¼"/ft drain fall made freshly drawn waste runs read as bent/crooked. Runs now draw level; S toggles slope mode while the tool is armed. Angle-locked ends also snap run LENGTH along the ray instead of per-axis, so off-grid starts (port/body snaps) stay on the 45° ray. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Click-place tool for DWV fittings mirroring the duct-fitting pattern: ghost preview, DWV port mating, R/T rotation with Alt axis cycling, selection-time axis pill. Armed from an Add Fitting button under the DWV Pipe tile, same as the Duct panel. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Resolve the blockers, suggestions, and nits from the architecture review: - Mount fitting selection affordances via def.affordanceTools.selection instead of def.system; rename the per-kind system.tsx files to selection.tsx and add a SelectionAffordanceManager in the editor. - Add a distributionRole field to NodeDefinition (run/fitting/terminal/ equipment) and key system-graph summarization off it instead of branching on node.type. - Lift getLevelHeight/DEFAULT_LEVEL_HEIGHT into core's level-height service so viewer and nodes share one implementation. - Split riser-diagram and floating-action-menu panels so the full-node subscription lives in a child mounted only when needed. - Bundle inspector reconcile writes into a single updateNodes call. - Move shared fitting-rotation helpers to nodes/src/shared. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# Conflicts: # apps/editor/components/build-tab.tsx # packages/editor/src/components/tools/registry/move-registry-node-tool.tsx
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apply safe biome fixes (formatting, import sort, unused-import removal) to the branch's own duct/lineset/pipe-trap files so `bun run check` passes. Unsafe useExhaustiveDependencies hints left untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…al alignment Duct, duct-fitting, pipe, and lineset now duplicate and move with a translucent ghost that rides the cursor and only lands on the commit click — nothing is inserted into the scene before that. Each kind ships its own ghost+box mover (affordanceTools.move) and routes through a pure-draft branch in the 3D floating action menu; the MoveTool dispatcher prefers affordanceTools.move over capabilities.movable so duct-fitting uses its ghost. The 2D floorplan overlay drives the same drag for any path kind generically. Dragged runs/fittings now show a footprint bounding box (DragBoundingBox in 3D, an SVG rect in 2D) with Figma-style alignment guides drawn relative to the box. Every kind now contributes alignment anchors: nodeAlignmentAnchors emits path vertices, typed-port positions, and the position centre, so dragging or placing any item snaps to ducts, fittings, pipes, and linesets across all collectAlignmentAnchors consumers (3D mover, ghost movers, fresh placement, surface snap). The 2D overlay no longer skips thin run lines as candidates. Move tools hide the real 3D mesh imperatively (not the store `visible` flag) so a node never vanishes from the 2D floorplan during a drag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Icons - Add raster icons (HVAC, duct, duct-fitting, registers, dwv-pipes, lineset) and wire them through each node's presentation.icon as `kind: 'url'`, the Build tab, and the action-menu structure tools. Lineset drops the `lucide:cable` placeholder for its own PNG. 3D draw cursor - Register, lineset, DWV pipe, and the duct/pipe fittings now render the shared CursorSphere (ground ring + vertical line + tool-icon badge) in the 3D view, matching the duct tool and the 2D floorplan overlay. The icon resolves from the active structure-tools entry. Previously only the 2D floorplan drew this, so the indicator was absent in 3D. Alignment & path editing - Add shared draw-alignment helper: Figma-style guides layered onto the HVAC/DWV draw cursors so runs line up with other nodes while being drawn (published to both the 2D plan and the 3D view). - Add shared path-point-affordance: 2D floor-plan drag handles for polyline path vertices (duct/pipe/lineset), the plan counterpart of their 3D selection handles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a `liquid-line` node — the thin bare-copper rail split out of the lineset — as its own drawable MEP run, plus a Follow mode that traces it alongside an existing lineset. - New node under packages/nodes/src/liquid-line: schema, single-centerline geometry, floorplan, parametrics, endpoint-fold connect, selection + ghost move/duplicate affordances, and a draw tool (same model as the lineset tool: 45° lock, Shift free, Alt vertical, refrigerant-port snap). - Follow mode: arm "Follow lineset" (Build → MEP panel toggle or `F`), then click a lineset to lay a liquid line beside it, tracing its whole path at a small clear-air gap on the cursor's side. Backed by a shared useLiquidLineToolOptions store so panel and tool stay in sync. - Shared path-offset helper (parallel miter offset) drives the trace. - Lineset geometry now draws a single centerline pipe (suction + optional jacket), dropping the parallel liquid rail it used to render. - Register the kind across core schema/events, the nodes plugin, the editor StructureTool union + lookup table, the floating-action-menu path-kind branch, and the Build tab's MEP group. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # packages/core/src/services/index.ts
Auto-insert a fitting when a drawn run taps or crosses an existing run: ducts mint a tee (end/side tap) or 4-way cross (pass-through); DWV pipes mint a square sanitary tee (was a 45° wye) or a new cross fitting. Body taps now step along the run with grid snap (Shift frees to smooth). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Floor-mounted registers ignored slab thickness and sat at y=0, sinking into the slab. Declare the `floorPlaced` capability so the generic FloorElevationSystem lifts the committed mesh onto the slab surface (footprint-driven, multi-slab aware), and lift the placement ghost via getFloorStackPreviewPosition so the preview matches. Ceiling-mounted registers snapped to a global "tallest ceiling, else wall, else 2.5m" plane. Resolve the actual ceiling the cursor ray hits (point-in-polygon, holes excluded) and take that surface's own height; refuse placement when no real ceiling is under the cursor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Wrap loadExampleFile/handleFile/loadAndConvert in useCallback and reorder before their useEffect in IfcConverter to fix noInvalidUseBeforeDeclaration - Remove unused biome-ignore comments for noConsole (rule is off) in dormer/csg-geometry.ts and noArrayIndexKey (rule is off) in dormer/window-assembly.tsx - Remove misplaced useExhaustiveDependencies suppression in dormer/panel-position-section.tsx (rule not firing on that hook) - Add correct suppression for resolvedRadii spread-dep pattern in dormer/window-assembly.tsx - Biome auto-fixed excess/missing hook deps across 30+ node files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The biome --unsafe autofix stripped [shading, textures, colorPreset, sceneTheme] from the mount effect's dep array, treating them as unused. They are deliberate re-run TRIGGERS: the effect re-marks every def.geometry node dirty whenever an appearance value changes, so the builders re-run and pick up the new shading / textures / color preset / scene theme. Without them, switching render mode, toggling textures, or changing the preset no longer rebuilds existing registry-driven geometry. All four are primitives (stable by value), so listing them is safe; added a scoped biome-ignore so the trigger-only deps survive future autofixes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The earlier `biome check --write --unsafe` pass (commit a3b5e4d) did far more than satisfy the failing check. `useExhaustiveDependencies` is info-level and never failed `bun run check` — the only error-level failure was a formatter diff. But --unsafe rewrote ~25 hook dependency arrays, and several of those rewrites are regressions: - dormer/panel-position-section: replaced the stable `roofChildrenKey` signature with raw `roof.children` / `roof` captures, which both broke memoization AND introduced a TS error (`'roof' is possibly 'undefined'`) that failed `tsc --build` for @pascal-app/nodes. - gutter/downspout renderers: replaced deliberate `JSON.stringify` value-comparison deps (there to avoid expensive CSG rebuilds) with broad object-identity deps → CSG re-runs on every node mutation. - turbine-vent: added `node` to specific-property lists, defeating the narrow geometry-rebuild triggers. - floorplan-panel / liquid-line: dropped real deps (`levelId`, `follow`) → stale-closure risk. Reverts every such file to its pre-lint state (74b7351), restoring the authors' intentional dependency arrays. The only genuinely-required fix from that pass is kept: the duct-terminal import formatting (the actual error-level failure) and the GeometrySystem appearance-dep restoration (committed separately in c1bfb88). Verified: `bun run check` exits 0, all 9 packages typecheck, and the test suite is unchanged (1228 pass / 20 pre-existing env failures). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file is Next.js typegen output ("should not be edited") and was
inadvertently swept into the previous commit by `git add -A` after a
local `next typegen` run rewrote its route-types import path. Restore
the branch's committed version so the lint cleanup doesn't carry an
unrelated, environment-specific change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ain) bun run check already passed (0 errors); these were warn-level: - Remove 5 dead biome-ignore comments — noConsole (csg-geometry, 3×) and noArrayIndexKey (window-assembly, 2×) are both disabled in biome.jsonc, so the suppressions had no effect. The console calls / index keys are unchanged (the rules are off); only the dead comments are gone. - Relocate the useExhaustiveDependencies suppression in panel-position-section from inside the useMemo body (where it covered nothing) to directly above the useMemo call, so it actually suppresses the diagnostic. Preserves the intentional `roofChildrenKey` stable-signature dep array — no behavior change. - panel.tsx: `!(node && node.roofSegmentId)` → `!node?.roofSegmentId` (useOptionalChain), equivalence-preserving. bun run check now reports 0 errors and 0 warnings. The remaining 92 are info-level useExhaustiveDependencies hints (intentional parametric narrow-rebuild deps); left untouched — broadening them reintroduces the CSG-rebuild churn reverted earlier. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An exhaustive audit of the useExhaustiveDependencies info-level hints (28 files) confirmed two were genuine, user-visible stale-closure bugs (the other ~90 are the intentional narrow-rebuild pattern, left as-is). 1. chimney/renderer.tsx — `segmentBrushes` memoised on 9 hand-listed segment fields, omitting `gambrelLowerWidthRatio` / `mansardSteepWidthRatio` / `dutchHipWidthRatio`, which `getRoofSegmentBrushes` reads to shape the trim brushes. Editing one of those ratios re-identifies `segment` (so `geo`/`trimmedBody` re-CSG) but NOT the brushes — the chimney then trims against the old roof outline. Depend on `[segment]` directly, matching the sibling `geo` memo whose comment already argues whole-object deps avoid exactly this forgotten-field class of bug. `segment` is a useScene selector ref, stable except when the segment's own data changes, so a chimney slider drag still hits the cache. 2. floorplan-panel.tsx — `handlePointerMove` calls `showOpeningGhost` (whose door-swing-arc vs window-panes glyph is bound to `isDoorBuildActive`) but omitted it from deps. A door→window tool switch changes none of the handler's other listed deps, so it kept the stale closure and floated a door symbol while the window tool was armed. Add `showOpeningGhost` to the dep array. Verified: bun run check exits 0 (0 errors, 0 warnings; 88 intentional infos remain), all 9 packages typecheck, tests unchanged (1228 pass). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Most are the intentional tight-dep pattern in packages/nodes (geometry/ material memos capture the whole node but list only shape fields, so they don't rebuild on name/visible flips) — suppressed with a reason rather than widening the deps, plus a few intentional re-run triggers (follow, levelId, mount-only effects). Two were real and fixed: - floorplan-panel handlePointerMove: depended on isOpeningPlacementActive (= isOpeningBuildActive || isOpeningMoveActive) while the body branches on the operands individually, so a build->move transition kept a stale closure. Now lists both individual flags. - floorplan-panel showOpeningGhost: dropped three stable module-level schema imports from the dep array. Also converts one string concat to a template literal (useTemplate). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pull Bot
pushed a commit
to qili724427533-cmyk/editor
that referenced
this pull request
Jun 16, 2026
- core: fix layer violation in level-height.ts — extract sceneRegistry
import, replace with optional WallBaseYResolver callback so core stays
pure (no Three.js mesh state); viewer callers pass resolver, headless
callers (MCP/tests) get deterministic node-data-only result
- core: generalise port-connectivity service from duct-only to all
distribution families — match partners by distributionRole ('run' →
endpoint stretch, 'fitting' → rigid follow) instead of hard-coded
duct-segment/duct-fitting type names; add system-compat guard so
cross-system ports (e.g. supply duct vs waste pipe) don't fuse
- editor: fix port-snap rotation bug in move tool — pass preview node
at live rotation into resolvePortSnap so own-port positions reflect
any mid-drag R/T rotation before computing the snap delta
- editor: wire pipe-trap into UI — add to StructureTool union,
MepToolKind, MEP_ITEMS Build-tab tile, and structure-tools action menu
- test: add port-connectivity-pipe.test.ts — 2 tests covering
pipe-fitting → pipe-segment endpoint drag and cross-system isolation
- test: fix stale pipe-auto-fitting.test.ts wye expectation — author
deliberately chose square sanitary-tee for DWV side-taps (documented
in PR description and PipeFittingNode schema); update the one test
that still expected wye to match the implemented behaviour
- nit: fix optional-chain biome warning in validate-dwv.ts
pull Bot
pushed a commit
to qili724427533-cmyk/editor
that referenced
this pull request
Jun 16, 2026
- core: decouple drag-follow from distributionRole — add portConnectivityFollow flag to NodeDefinition; pipe-trap opts out (portConnectivityFollow: false) so dragging a connected pipe endpoint stretches the trap arm instead of yanking the anchored trap fixture - core: remove the module-level getLevelHeight cache entirely — it was keyed only by nodes-object identity, which could return stale heights for in-place mutations by pure/headless callers. The function is now fully pure and deterministic; viewer hot path recomputes per frame as before (the cache only ever skipped the resolver-free branch) - test: harden port-connectivity-pipe.test.ts — real DuctSegmentNode cross-family isolation case (was waste-vs-vent), new pipe-trap anchor case (run drag doesn't move trap; trap drag still stretches run), and beforeEach/afterEach registry reset instead of leaky beforeAll - nit: biome format/import-order on all touched files
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.
What does this PR do?
Adds two new MEP node families to the editor — HVAC ductwork and DWV (drain-waste-vent) plumbing — built on a shared port-connectivity model.
NodePort/def.portson the node registry, a port-connectivity service, system-scoped port queries,cursorAttached+portSnapoptions on themovablecapability, and parametric derive/reconcile inspector hooks.duct-segment(draw tool, drag handles, floorplan, ceiling mode, diameter stepping, rectangular trunk cross-sections),duct-fitting(elbow / tee / cross / reducer with typed ports, auto-elbow insertion at corners, tee taps, profile inheritance, mitered rect elbows),duct-terminal(registers / diffusers / return grilles that mount and snap to duct ports),hvac-equipment(furnace / air-handler / condenser with detailed models + refrigerant service ports), andlineset(refrigerant suction + liquid pair).pipe-segment(drains level by default, ¼"/ft slope is an S-key opt-in), auto-minted DWV fittings (bends / sanitary tees / crosses), plumbing fixtures (toilets / sinks / tubs with drain ports),pipe-trap, a riser diagram + validation services, and apipe-fittingplacement tool.origin/main(roof wall openings, direct-manipulation, snap work) into the branch — two conflicts inbuild-tab.tsxandmove-registry-node-tool.tsxwere resolved to keep both sides' behavior.How to test
bun devand open the editor atlocalhost:3002.bun check-types— all packages should pass.Screenshots / screen recording
N/A in this description — recording strongly encouraged given the interactive 3D tooling; reviewer should exercise the Build-tab HVAC/DWV tools per the steps above.
Checklist
bun devbun checkto verify)mainbranch