fix: address review findings from PR #402 MEP systems

- 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
This commit is contained in:
Open Pascal
2026-06-16 19:30:51 +00:00
parent 5551500d98
commit 54a24e4c5c
12 changed files with 220 additions and 31 deletions
@@ -447,7 +447,12 @@ export function MoveRegistryNodeTool({ node }: { node: AnyNode }) {
// duct run end. Takes precedence over grid / alignment snap; Alt
// bypasses. Only kinds that opted in via `movable.portSnap`.
if (!bypass && portSnapConfig) {
const mated = resolvePortSnap(node, [x, z], portSnapConfig)
// Build the preview node at the ORIGINAL position but with the LIVE
// rotation so `def.ports` reflects any mid-drag R/T rotation. Without
// this the snap solver mates the pre-rotation collar and commit then
// writes the rotated node offset from the port it visually snapped to.
const snapNode = buildPreviewNode(originalPosition, rotationRef.current)
const mated = resolvePortSnap(snapNode, [x, z], portSnapConfig)
if (mated) {
x = mated[0]
z = mated[1]
@@ -30,6 +30,7 @@ export const tools: ToolConfig[] = [
{ id: 'duct-terminal', iconSrc: '/icons/registers.png', label: 'Register' },
{ id: 'hvac-equipment', iconSrc: '/icons/HVAC.png', label: 'HVAC Unit' },
{ id: 'pipe-segment', iconSrc: '/icons/dwv-pipes.png', label: 'DWV Pipe' },
{ id: 'pipe-trap', iconSrc: '/icons/dwv-pipes.png', label: 'Trap' },
{ id: 'pipe-fitting', iconSrc: '/icons/duct-fitting.png', label: 'Pipe Fitting' },
{ id: 'lineset', iconSrc: '/icons/lineset.png', label: 'Lineset' },
{ id: 'liquid-line', iconSrc: '/icons/lineset.png', label: 'Liquid Line' },
+1
View File
@@ -114,6 +114,7 @@ export type StructureTool =
| 'liquid-line'
| 'pipe-segment'
| 'pipe-fitting'
| 'pipe-trap'
// Furnish mode tools (items and decoration)
export type FurnishTool = 'item'