Skip to content

nodes: add MEP movement controls and DWV parity#438

Merged
Aymericr merged 39 commits into
pascalorg:mainfrom
sudhir9297:fix/wed-jun-17
Jun 23, 2026
Merged

nodes: add MEP movement controls and DWV parity#438
Aymericr merged 39 commits into
pascalorg:mainfrom
sudhir9297:fix/wed-jun-17

Conversation

@sudhir9297

@sudhir9297 sudhir9297 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a broad MEP interaction pass for ducts, DWV pipe, linesets, liquid lines, and roof accessories, with the branch now merged forward from current main. The PR introduces run-aligned cube/arrow handles, vertical and run-translation auto-routing, connected-joint endpoint movement, DWV pipe/fitting parity with duct movement controls, continuous drawing for DWV/refrigerant lines, shared endpoint controls for linesets/liquid lines, and delete repair for auto-inserted DWV bends so pipe lengths restore when the bend is removed.

How to test

  1. Run bun run check and confirm Biome exits successfully. Current main emits existing material-work warnings/infos, but the command exits cleanly.
  2. Run bun run build and confirm the monorepo build completes.
  3. Run bun run check-types and confirm all workspace typechecks complete.
  4. Run bun test packages/nodes/src/shared/pipe-vertical-offset.test.ts packages/nodes/src/shared/pipe-run-translation-offset.test.ts packages/nodes/src/shared/vertical-offset.test.ts packages/nodes/src/shared/run-translation-offset.test.ts packages/nodes/src/pipe-fitting/parametrics.test.ts and confirm the focused MEP routing suite passes.
  5. In the editor, draw ducts and DWV pipes, select segment/fitting corner cubes, and drag the arrows up/down/across runs. Confirm connected geometry updates live without rotating already-drawn connected runs.
  6. Create L/U-style duct and DWV routes, move one side to a different height, then move the middle/top run through aligned and non-aligned levels. Confirm aligned sides collapse to simpler fittings while non-aligned sides keep their offsets and adjust connector lengths.
  7. Draw linesets and liquid lines continuously, then select endpoint cubes and drag endpoint arrows. Confirm endpoint movement and connected-follow behavior match the duct/pipe editing model.
  8. Delete an auto-inserted DWV bend from an L-shape and confirm the shortened pipe ends extend back to the original corner.

Screenshots / screen recording

Screen recording not attached yet — this is a visual/interactive editing change and should get a quick reviewer clip before merge.

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

Note

High Risk
Large changes to port connectivity propagation and delete/reconcile paths affect core scene editing for all duct/pipe joints; mistakes could corrupt paths or undo stacks across connected MEP graphs.

Overview
MEP connectivity and fittingsport-connectivity now walks the full joint graph (not one hop): fittings translate rigidly, runs stretch along-axis and translate perpendicular without skew, with new tests. Duct fitting moves follow connected runs (Alt detaches); parametrics add onDelete elbow repair, auto-offset tag handling, rect defaults, 0° coupling angles, body paint/slots, and inspector W/H swap. Schema adds optional slots on duct nodes; pipe fittings/traps get similar angle/default tweaks.

Editor handles and UX — Registry adds LatchHandle, faceNormal, and latchGroup for collapsed arrow clusters; dormers get front/back depth handles, window latch cube, and placement guides on roof. Arrow handles gain a thinner chevron profile; contextual help surfaces MEP handle hints. Build tab adds Add Trap (pipe-trap); cursor sphere can show the dot at the tip for ceiling-height placement.

Viewer and previewTranslucent wall mode cycles through toolbars, command palette, and preview overlay; preview adds first-person walkthrough (controls + pointer lock). getCeilingAt / getCeilingHeightAt support ceiling-aware placement. Roof accessory move/place tools publish shared roof-surface placement guides (chimney move gets cancel/reparent fixes).

DeletesdeleteNodes invokes per-kind parametrics.onDelete before removal so neighbours restore consistently in one undo step.

Reviewed by Cursor Bugbot for commit a20298f. Bugbot is set up for automated code reviews on this repo. Configure here.

sudhir9297 and others added 30 commits May 19, 2026 02:59
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>
Duct draw tool's ceiling mode now hangs each path point just below the
ceiling actually covering it (per-room heights tracked), with a
translucent surface highlight and a plumb line to the floor so the
in-flight point reads clearly from any angle.

Dragging a duct corner that sits on a fitting now carries the fitting's
other ducts along (port-connectivity second hop), so the joint moves
together instead of tearing apart.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Alt detaches a dragged duct/pipe endpoint or fitting from its connected
joint (no elbow re-aim, no connectivity follow); Ctrl/Cmd drives vertical
riser movement on the fitting move. Behavioral parity across 2D and 3D.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bring pipe-segment endpoint drags and pipe-fitting moves to parity with
duct: free-drag endpoints, Alt-detach, Ctrl/Cmd-vertical riser, elbow
re-aim, and connectivity follow. Generalizes the shared elbow-reaim and
auto-fitting helpers to dispatch by run kind so 2D and 3D share one path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add violet directional arrow affordances to duct-fitting selection (height,
move cross, rotate arc) mirroring the duct-segment rig: portaled into the
parent frame to stay out of the selection outline, rendered via the shared
HandleArrow, and carrying mated-run connectivity through the single-undo
dance. The move cross engages press-drag-release (placementDragMode) the same
way the floating drag does, so the markup hit-areas go inert and the fitting
move tool commits on pointer-up.

Also re-export the HandleArrow primitives from @pascal-app/editor and extend
the duct-segment side-move/floorplan affordances.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the hover-reveal / multi-handle selection rigs with a single
click-to-latch cube that opens a directional cluster, shared between
duct segments and fittings via a new selection-handles module
(HandleCube / MoveChevron / RotateArc, all sized to the roof pitch cube).

- Duct segment: per-vertex + run-center cubes reveal axis-locked move
  chevrons (down arrow always shown), plus a roll arc at the run center.
- Duct fitting: center cube reveals six ±XYZ move arrows and three
  per-axis rotation arcs (oriented in place), replacing the old
  height/move/rotate trio with axis-cycling.
- Rotation (fitting arcs + duct roll) snaps to 45° steps; Shift = smooth.
- thin chevron profile + press-drag-release commit retained.
Build a fully-determined basis for the duct roll gizmo so the curved
arrow always seats at the top-outer 45° corner regardless of run
direction, instead of an arbitrary apex from a single setFromUnitVectors.
The selection-rig ±Y arrows now own vertical movement, so the redundant
Ctrl-modifier riser drag is removed from the fitting move tool.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Align the duct run-center cube + horizontal arrows to the run axis
(matching the per-vertex handles) while keeping whole-run translate.
Endpoint side / up-down swing arrows now follow grid snap points and
port-snap onto nearby collars (Shift sweeps smoothly). Relax elbow
realign + fitting schemas to flatten to a straight 0° coupling. Surface
HVAC-specific hints in the select-mode helper panel for duct / fitting.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Linesets and liquid lines now commit one independent two-point node per
drawn segment instead of folding into a single mitered polyline, so each
line selects and deletes on its own. Endpoint caps fill shared-coordinate
joints so connected segments still read as continuous pipe.

Dragging a shared endpoint carries mated segments along via port
connectivity (Alt detaches), so a run still edits as one welded piece.
Liquid-line follow mode traces the whole connected lineset run, laying a
per-segment parallel line down its full assembled length.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Lifting/lowering a connected run with the run-center cube now keeps each
connected end welded to its stationary partner instead of dragging the
whole network. Run-to-run ends get the classic S/Z offset (two elbows +
plumb riser, partner trimmed back one leg); elbow-connected ends form a
clean L — the existing elbow stays put and re-aims its collar vertical,
with one new top elbow + riser reconnecting to the lifted endpoint. The
offset is ghosted live and minted as a single undo step on release.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Measure roof accessory placement against the active roof face using visible surface bounds and preview geometry footprints. Add dormer-local guides and special linear handling for ridge vents and gutters.
# Conflicts:
#	apps/editor/components/build-tab.tsx
#	apps/editor/components/viewer-toolbar.tsx
#	packages/editor/src/index.tsx
#	packages/viewer/src/systems/wall/wall-materials.ts
Comment thread packages/nodes/src/duct-fitting/parametrics.ts
Comment thread packages/editor/src/components/ui/helpers/helper-manager.tsx
Comment thread packages/core/src/services/port-connectivity.ts Outdated
Comment thread packages/nodes/src/pipe-fitting/parametrics.ts
Comment thread packages/nodes/src/chimney/move-tool.tsx
Comment thread packages/nodes/src/chimney/move-tool.tsx
Comment thread packages/nodes/src/duct-fitting/parametrics.ts
Comment thread packages/nodes/src/box-vent/move-tool.tsx
Comment thread packages/nodes/src/chimney/move-tool.tsx
Comment thread packages/nodes/src/chimney/move-tool.tsx
Comment thread packages/core/src/services/port-connectivity.ts
Comment thread packages/core/src/services/port-connectivity.ts Outdated
- auto-fitting: tee branch now follows the drawn lateral angle; update the
  stale square-tee test + doc comment that contradicted the rewrite
- duct-segment: re-enable the vertical auto-offset rewind (the disabled stub
  left mintedIds empty, so re-dragging a tagged duct stranded old elbows/risers
  and stacked duplicates); remove the dead stub
- duct-segment: strip the stale auto-offset tag on manual corner/roll commits
  so the horizontal-move path no longer trusts an out-of-date base
- chimney: resume history before mutating segment children arrays so a
  cross-segment move reparents in one tracked transaction (undo stays consistent)
- dormer: align schema test with the new windowSill=false default
Comment thread packages/core/src/services/port-connectivity.ts
Comment thread packages/nodes/src/chimney/move-tool.tsx
Comment thread packages/nodes/src/chimney/move-tool.tsx
Comment thread packages/core/src/services/port-connectivity.ts
Comment thread packages/core/src/services/port-connectivity.ts
Comment thread packages/nodes/src/pipe-fitting/parametrics.ts Outdated
Comment thread packages/nodes/src/chimney/move-tool.tsx Outdated
Comment thread packages/nodes/src/duct-fitting/parametrics.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 4 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a20298f. Configure here.

className="size-4 object-contain"
height={16}
src="/icons/dwv-pipes.png"
width={16}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trap button broken icon path

Low Severity

The new Add Trap control loads /icons/dwv-pipes.png, but the app only ships dwv-pipes.webp (same path as the DWV pipe tile). The trap affordance can show a missing image while the rest of the pipe UI uses the webp asset.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a20298f. Configure here.

{ id: nodeId, data: { path: translationPlan.ductPath } as Partial<AnyNode> },
...translationPlan.updates,
],
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Translation move skips offset invalidation

Medium Severity

When a whole-run move commits via planRunTranslationOffsets and applyNodeChanges, the batch updates paths and mints fittings but never runs autoOffsetInvalidationUpdates. Ducts still carrying an auto-offset tag can keep stale minted/base metadata after auto-routed elbows are inserted, unlike the connectivity-only commit path used elsewhere in this PR.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a20298f. Configure here.

? 'run'
: single?.type === 'duct-fitting' || single?.type === 'pipe-fitting'
? 'fitting'
: null

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lineset help hints missing

Low Severity

The new MEP contextual-help path only treats duct-segment, pipe-segment, duct-fitting, and pipe-fitting as MEP selections. Selecting a lineset or liquid-line (which this PR adds endpoint handle editing for) still gets generic select hints instead of the run-style “click handle dot / Alt detach” guidance.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a20298f. Configure here.

if (metadata) data.metadata = metadata as DuctSegmentNode['metadata']
updates.push({ id: mate.duct.id, data })
}
return [...updates, ...invalidations]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate onDelete patches clobber

Medium Severity

When both endpoints of the same run mate one deleted elbow, onDelete emits two full path patches for the same node id. deleteNodesAction applies them in order with shallow merge, so the second patch replaces the first and only one endpoint is moved back to the junction.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a20298f. Configure here.

@Aymericr Aymericr merged commit a71de82 into pascalorg:main Jun 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants