feat: HVAC ductwork + DWV plumbing systems#402
Open
sudhir9297 wants to merge 66 commits into
Open
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>
Every equipment type now exposes one refrigerant port on its +X service-valve face (condenser/air-handler near the bottom third, furnace up at the cased A-coil), advertised at the nominal 7/8" suction OD so a lineset run mates cleanly. Geometry grows the matching service-valve detail (copper stubs + valve bodies), and the duct collar defaults drop to 8" round. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The refrigerant-side sibling of duct-segment: a copper suction + liquid line pair as a polyline, joining a split system's outdoor condenser to the indoor coil. Same two-click draw model and selection-time path handles, but it snaps onto refrigerant service ports instead of duct collars. Schema + event wiring in core, geometry / floorplan / tool / handle system in nodes, StructureTool id and a Build tab tile in the editor app. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drawing a new run off another run's open end at an angle now mints a correctly-oriented elbow fitting at the joint instead of leaving a butt joint. planElbowAtPort (shared/auto-fitting.ts) plans the elbow purely from the joint port and the leaving direction: angle = the actual turn (15-90°, vertical turns included via a full 3D basis transfer), inlet collar exactly on the run's port, and the new duct pulled back to the outlet collar so duct meets metal. The draw tool tracks which port each segment end snapped to and commits duct + fittings as one createNodes batch. Joints onto fittings / equipment / terminals stay direct connections — only run-to-run corners get a fitting. Straight continuations and back-turns past 90° are left as plain joints. Tests round-trip every plan through the fitting kind's own port math to prove both collars mate (position + direction) in horizontal, 45°, and riser cases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The elbow's junction was placed one leg beyond the existing run's end, bulging the corner outward past where the user clicked. Now the junction centers exactly on the drawn corner: the plan returns a trimmedPortPoint one leg back along the existing run, the draw tool shortens that run to it (skipping the fitting when the trim would consume the run), and the trim + elbow + new duct commit through applyNodeChanges as a single undo step. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Starting a duct on another run's BODY (centerline snap, new findNearestRunBodyXZ) now splits the trunk and mints a tee at the tap point: the original node keeps the upstream half trimmed one run-leg short, a new duct-segment carries the downstream half starting one leg after, and the branch leaves square from the tee's collar (the drawn direction is projected perpendicular to the trunk axis — vertical drops included). Split + tee + branch commit through applyNodeChanges as a single undo step. Taps with no room for the run legs (too near a segment end) or drawn parallel to the trunk fall back to plain placement; run end ports keep priority over body hits so elbow joints still win near an open end. planTeeAtRunBody round-trips through the fitting kind's own port math in tests: all three collars mate, split halves end exactly on the run collars, polyline trunks split inside the hit segment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ending (or starting) a duct on an existing elbow's open collar left the elbow at its old orientation — a sloped run arriving at a riser elbow butt-joined at the wrong angle. planElbowRealign now patches the elbow in place: junction and the mated collar stay exactly where they were (the other run stays connected), the free collar swings to face the drawn direction, and the elbow's angle adjusts to the turn that requires. The drawn duct starts/ends at the swung collar. Realigns outside the elbow's 15-90° range, non-elbow fittings, and unknown ports are left as plain joints. Patch ships in the same applyNodeChanges as the new duct — one undo step. Tests verify the mated collar is bit-identical before/after and the free collar lands on the returned point facing the run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The doc's "System" primitive: buildPortComponents groups every
port-bearing node into connected components via coinciding ports
(union-find, same tolerance as port-connectivity), and
summarizeSystemFor reports a component's distribution loops, run
count + total length, fitting / terminal / equipment counts, and the
key health signal — connectedToEquipment.
The floating action menu surfaces it: selecting any HVAC node shows a
pill above move/duplicate/delete with the system ("Supply · 7.2m ·
3 runs · 2 registers") and an amber "⚠ no equipment" warning when the
tree doesn't reach a furnace / air handler — orphaned subtrees are
visible at a glance. The fitting's rotation-axis pill stacks beneath
it in the same slot.
Core tests cover component grouping (tolerance, port-less bystanders)
and summaries (full tree stats, orphan detection) via stub registry
definitions, keeping core free of nodes-package imports.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Duct segments gain shape: 'round' | 'rect' with width/height in inches (default 14x8 trunk) — real US systems are a rect trunk with round branches. Q toggles the shape in the draw tool (ghost preview becomes a box, pill reads W/H instead of diameter, run commits named "Trunk"); the inspector switches between diameter and width/height fields. Geometry builds box sections with a stable basis (width stays horizontal — the minimal-rotation cylinder quaternion would roll the profile on axis-aligned runs), cube joints at interior points, and a matching insulation shell. The floor plan draws rect runs at their actual width. Joints stay round: rect ports advertise the area-equivalent diameter (2*sqrt(wh/pi), clamped to the fitting ceiling), so tee taps on a rect trunk mint a sensibly-sized round tee and split halves inherit the rect profile. Old scenes parse unchanged via schema defaults. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drawing off an existing node now CONTINUES its cross-section: snapping the segment start onto a port inherits the owner's profile — a rect trunk end keeps drawing rect at its exact W×H, a round run / fitting collar keeps its diameter, equipment / terminal collars start round at the port's size. The ghost preview, pill readout (W·H vs Ø), ceiling offset, and committed node all follow. Body taps (tee branches) keep the tool's own profile, since branches off trunks are round. duct-fitting gains shape/width/height: rect elbows and tee run-legs build as boxes at the trunk's profile with a cube junction (tee branch collar stays round), inspector swaps diameter for W/H. The planners carry profiles through — planElbowAtPort takes a DuctProfile (rect elbows mint at the trunk's W×H with the area-equivalent diameter driving leg lengths), planTeeAtRunBody stamps the trunk's shape onto the tee. Old scenes parse unchanged via schema defaults. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rect elbow was two box stubs pushed into an axis-aligned cube, so the legs interpenetrated and the corner read as colliding boxes at any angle off 90°. It's now ONE closed swept solid: the rect profile runs from the inlet face through a miter ring on the corner's bisector plane (2D miter-join offset, exact for the elbow's 15-90° range) to the outlet face — a crisp seam like a folded sheet-metal square elbow. Non-indexed triangles give flat face normals for the metal look. The rect tee also drops its junction cube: one straight rect box inlet→outlet with the round branch stub tapping its side. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The five HVAC tiles were borrowing unrelated PNGs (wall, column, window, elevator). BuildType gains an `iconify` alternative to `iconSrc`, rendered via @iconify/react: wind (duct), git-branch (fitting), air-vent (register), heater (HVAC unit), cable (lineset). The duct-terminal registry presentation icon aligns to air-vent too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First kind of the research doc's Phase 2 (DWV plumbing): pipe-segment, the plumbing sibling of duct-segment on the same polyline + typed-port machinery, with SLOPE as the new ingredient. - Schema: path / diameter (1¼–8" nominal) / pvc-abs-cast-iron / system waste|vent. Slope lives in the path Y coordinates; Y may go below the floor. - Draw tool: two-click segments where WASTE runs fall automatically at the IPC default ¼" per foot of horizontal run (the pill's Y delta shows the live drop); vents stay level. Q toggles waste/vent, [ / ] steps nominal sizes, Alt-drag draws vertical stacks, 45° lock with Shift free, port snap scoped to the new DWV port systems so drains never mate onto duct or refrigerant collars. - Geometry: capped PVC/ABS/cast-iron sections with coupling hubs; floor plan follows drafting convention (waste solid at pipe width, vents dashed, stacks as circles). - System graph counts pipe runs, the floating-menu pill covers them, Build tab gains a DWV Pipe tile (lucide:droplets). Deferred: DWV fittings (wye / sanitary tee), fixtures, traps, IPC validators, riser view. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pipe-fitting kind (elbow / wye / sanitary-tee) with typed ports and the plumbing-correct joint vocabulary, minted automatically by the pipe draw tool: - Corner joints: a run drawn off another run's open end at an angle mints a BEND — planCornerJoint extracts the duct elbow's junction / trim / collar math into a domain-agnostic planner shared by both. - Body taps: starting on a run's side splits it and mints the code-correct entry — a 45° WYE leaning downstream on horizontal drains, a square SANITARY TEE on vertical stacks (axis steeper than ~45° reads as a stack). - findNearestRunBodyXZ takes a kind filter so pipe taps target pipes and duct taps target ducts. Fittings carry pvc/abs/cast-iron material and waste/vent system from the run they join; no placement tool (a loose DWV fitting isn't a real workflow) but full inspector editing, floorplan symbol (45° branch at true plan angle), system-graph counting, and the action-menu pill. Joints round-trip through the fitting's own port math in tests. Note: src/shared/floorplan-cursor.test.ts fails under bun from a pre-existing three-bvh-csg import issue (ships in 8dc602c) — unrelated to this change. 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
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 / 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 / wyes / sanitary tees), 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 12 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