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
@@ -39,7 +39,9 @@ export const LevelSystem = () => {
obj.position.y = lerp(obj.position.y, targetY, delta * 12) // Smoothly animate to new Y position
obj.visible = levelMode !== 'solo' || level?.id === selectedLevel || !selectedLevel
cumulativeY += getLevelHeight(levelId, nodes)
cumulativeY += getLevelHeight(levelId, nodes, (wallId) =>
sceneRegistry.nodes.get(wallId)?.position.y,
)
}
}, 5) // Using a lower priority so it runs after transforms from other systems have settled
return null
@@ -41,7 +41,9 @@ export function snapLevelsToTruePositions(): () => void {
for (const { levelId, obj } of entries) {
obj.position.y = cumulativeY
obj.visible = true
cumulativeY += getLevelHeight(levelId, nodes)
cumulativeY += getLevelHeight(levelId, nodes, (wallId) =>
sceneRegistry.nodes.get(wallId)?.position.y,
)
}
return () => {