@pascal-app/core 0.9.1 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -10
- package/dist/events/bus.d.ts +49 -2
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/hooks/scene-registry/item-clip-registry.d.ts +18 -0
- package/dist/hooks/scene-registry/item-clip-registry.d.ts.map +1 -0
- package/dist/hooks/scene-registry/item-clip-registry.js +10 -0
- package/dist/hooks/scene-registry/scene-registry.d.ts +8 -1
- package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
- package/dist/hooks/scene-registry/scene-registry.js +27 -1
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +17 -1
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/floor-placed-elevation.js +61 -8
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +135 -28
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-manager.js +427 -271
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +46 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.js +208 -9
- package/dist/hooks/spatial-grid/support-host-id.d.ts +3 -0
- package/dist/hooks/spatial-grid/support-host-id.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/support-host-id.js +2 -0
- package/dist/hooks/spatial-grid/support-host-patch.d.ts +51 -0
- package/dist/hooks/spatial-grid/support-host-patch.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/support-host-patch.js +164 -0
- package/dist/index.d.ts +33 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -10
- package/dist/lib/level-name.d.ts +4 -0
- package/dist/lib/level-name.d.ts.map +1 -0
- package/dist/lib/level-name.js +10 -0
- package/dist/lib/measurement-geometry.d.ts +23 -0
- package/dist/lib/measurement-geometry.d.ts.map +1 -0
- package/dist/lib/measurement-geometry.js +249 -0
- package/dist/lib/polygon-geometry.d.ts +0 -1
- package/dist/lib/polygon-geometry.d.ts.map +1 -1
- package/dist/lib/polygon-geometry.js +0 -18
- package/dist/lib/selection-proxy.d.ts +4 -0
- package/dist/lib/selection-proxy.d.ts.map +1 -0
- package/dist/lib/selection-proxy.js +12 -0
- package/dist/lib/slab-polygon.d.ts +31 -2
- package/dist/lib/slab-polygon.d.ts.map +1 -1
- package/dist/lib/slab-polygon.js +557 -31
- package/dist/lib/slots.d.ts +14 -0
- package/dist/lib/slots.d.ts.map +1 -0
- package/dist/lib/slots.js +26 -0
- package/dist/lib/space-detection.d.ts +40 -6
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +574 -192
- package/dist/lib/terrain-brush.d.ts +163 -0
- package/dist/lib/terrain-brush.d.ts.map +1 -0
- package/dist/lib/terrain-brush.js +359 -0
- package/dist/lib/terrain-codec.d.ts +51 -0
- package/dist/lib/terrain-codec.d.ts.map +1 -0
- package/dist/lib/terrain-codec.js +167 -0
- package/dist/lib/terrain-field.d.ts +150 -0
- package/dist/lib/terrain-field.d.ts.map +1 -0
- package/dist/lib/terrain-field.js +248 -0
- package/dist/lib/terrain-raycast.d.ts +44 -0
- package/dist/lib/terrain-raycast.d.ts.map +1 -0
- package/dist/lib/terrain-raycast.js +188 -0
- package/dist/lib/terrain-source.d.ts +60 -0
- package/dist/lib/terrain-source.d.ts.map +1 -0
- package/dist/lib/terrain-source.js +90 -0
- package/dist/lib/terrain-support.d.ts +60 -0
- package/dist/lib/terrain-support.d.ts.map +1 -0
- package/dist/lib/terrain-support.js +150 -0
- package/dist/lib/wall-distance.d.ts +60 -0
- package/dist/lib/wall-distance.d.ts.map +1 -0
- package/dist/lib/wall-distance.js +85 -0
- package/dist/lib/zone-quantities.d.ts +21 -0
- package/dist/lib/zone-quantities.d.ts.map +1 -0
- package/dist/lib/zone-quantities.js +428 -0
- package/dist/material-library.d.ts +28 -1
- package/dist/material-library.d.ts.map +1 -1
- package/dist/material-library.js +2233 -441
- package/dist/registry/handles.d.ts +91 -3
- package/dist/registry/handles.d.ts.map +1 -1
- package/dist/registry/index.d.ts +3 -3
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +1 -1
- package/dist/registry/registry.d.ts +46 -3
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +82 -1
- package/dist/registry/subtree.d.ts.map +1 -1
- package/dist/registry/subtree.js +8 -1
- package/dist/registry/types.d.ts +781 -39
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/schema/index.d.ts +27 -8
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +23 -6
- package/dist/schema/material.d.ts +3 -2
- package/dist/schema/material.d.ts.map +1 -1
- package/dist/schema/material.js +7 -5
- package/dist/schema/nodes/box-vent.d.ts +2 -2
- package/dist/schema/nodes/cabinet.d.ts +536 -0
- package/dist/schema/nodes/cabinet.d.ts.map +1 -0
- package/dist/schema/nodes/cabinet.js +139 -0
- package/dist/schema/nodes/ceiling.d.ts +4 -3
- package/dist/schema/nodes/ceiling.d.ts.map +1 -1
- package/dist/schema/nodes/ceiling.js +11 -1
- package/dist/schema/nodes/chimney.d.ts +4 -4
- package/dist/schema/nodes/column.d.ts +4 -2
- package/dist/schema/nodes/column.d.ts.map +1 -1
- package/dist/schema/nodes/column.js +5 -0
- package/dist/schema/nodes/construction-dimension.d.ts +195 -0
- package/dist/schema/nodes/construction-dimension.d.ts.map +1 -0
- package/dist/schema/nodes/construction-dimension.js +151 -0
- package/dist/schema/nodes/cupola.d.ts +2 -2
- package/dist/schema/nodes/door.d.ts +42 -5
- package/dist/schema/nodes/door.d.ts.map +1 -1
- package/dist/schema/nodes/door.js +32 -0
- package/dist/schema/nodes/dormer.d.ts +8 -8
- package/dist/schema/nodes/dormer.js +1 -1
- package/dist/schema/nodes/downspout.d.ts +2 -2
- package/dist/schema/nodes/duct-fitting.d.ts +91 -0
- package/dist/schema/nodes/duct-fitting.d.ts.map +1 -0
- package/dist/schema/nodes/duct-fitting.js +92 -0
- package/dist/schema/nodes/duct-segment.d.ts +61 -0
- package/dist/schema/nodes/duct-segment.d.ts.map +1 -0
- package/dist/schema/nodes/duct-segment.js +73 -0
- package/dist/schema/nodes/duct-terminal.d.ts +58 -0
- package/dist/schema/nodes/duct-terminal.d.ts.map +1 -0
- package/dist/schema/nodes/duct-terminal.js +53 -0
- package/dist/schema/nodes/elevator.d.ts +3 -2
- package/dist/schema/nodes/elevator.d.ts.map +1 -1
- package/dist/schema/nodes/elevator.js +3 -0
- package/dist/schema/nodes/eyebrow-vent.d.ts +2 -2
- package/dist/schema/nodes/fence.d.ts +20 -2
- package/dist/schema/nodes/fence.d.ts.map +1 -1
- package/dist/schema/nodes/fence.js +33 -2
- package/dist/schema/nodes/gutter.d.ts +2 -2
- package/dist/schema/nodes/hvac-equipment.d.ts +64 -0
- package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -0
- package/dist/schema/nodes/hvac-equipment.js +57 -0
- package/dist/schema/nodes/item.d.ts +9 -0
- package/dist/schema/nodes/item.d.ts.map +1 -1
- package/dist/schema/nodes/item.js +24 -0
- package/dist/schema/nodes/level.d.ts +2 -1
- package/dist/schema/nodes/level.d.ts.map +1 -1
- package/dist/schema/nodes/level.js +12 -31
- package/dist/schema/nodes/lineset.d.ts +42 -0
- package/dist/schema/nodes/lineset.d.ts.map +1 -0
- package/dist/schema/nodes/lineset.js +38 -0
- package/dist/schema/nodes/liquid-line.d.ts +34 -0
- package/dist/schema/nodes/liquid-line.d.ts.map +1 -0
- package/dist/schema/nodes/liquid-line.js +24 -0
- package/dist/schema/nodes/measurement.d.ts +306 -0
- package/dist/schema/nodes/measurement.d.ts.map +1 -0
- package/dist/schema/nodes/measurement.js +87 -0
- package/dist/schema/nodes/pipe-fitting.d.ts +56 -0
- package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -0
- package/dist/schema/nodes/pipe-fitting.js +45 -0
- package/dist/schema/nodes/pipe-segment.d.ts +48 -0
- package/dist/schema/nodes/pipe-segment.d.ts.map +1 -0
- package/dist/schema/nodes/pipe-segment.js +36 -0
- package/dist/schema/nodes/pipe-trap.d.ts +43 -0
- package/dist/schema/nodes/pipe-trap.d.ts.map +1 -0
- package/dist/schema/nodes/pipe-trap.js +36 -0
- package/dist/schema/nodes/ridge-vent.d.ts +18 -3
- package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
- package/dist/schema/nodes/ridge-vent.js +221 -3
- package/dist/schema/nodes/roof-segment-shape.d.ts +90 -0
- package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-shape.js +318 -0
- package/dist/schema/nodes/roof-segment-walls.d.ts +101 -0
- package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-walls.js +330 -0
- package/dist/schema/nodes/roof-segment.d.ts +84 -8
- package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment.js +290 -5
- package/dist/schema/nodes/roof.d.ts +8 -8
- package/dist/schema/nodes/shelf.d.ts +4 -2
- package/dist/schema/nodes/shelf.d.ts.map +1 -1
- package/dist/schema/nodes/shelf.js +8 -0
- package/dist/schema/nodes/site.d.ts +9 -0
- package/dist/schema/nodes/site.d.ts.map +1 -1
- package/dist/schema/nodes/site.js +8 -6
- package/dist/schema/nodes/skylight.d.ts +10 -10
- package/dist/schema/nodes/slab.d.ts +8 -2
- package/dist/schema/nodes/slab.d.ts.map +1 -1
- package/dist/schema/nodes/slab.js +18 -2
- package/dist/schema/nodes/solar-panel.d.ts +4 -4
- package/dist/schema/nodes/spawn.d.ts +1 -0
- package/dist/schema/nodes/spawn.d.ts.map +1 -1
- package/dist/schema/nodes/spawn.js +2 -0
- package/dist/schema/nodes/stair-segment.d.ts +4 -4
- package/dist/schema/nodes/stair.d.ts +16 -13
- package/dist/schema/nodes/stair.d.ts.map +1 -1
- package/dist/schema/nodes/stair.js +11 -1
- package/dist/schema/nodes/structural-grid.d.ts +27 -0
- package/dist/schema/nodes/structural-grid.d.ts.map +1 -0
- package/dist/schema/nodes/structural-grid.js +17 -0
- package/dist/schema/nodes/turbine-vent.d.ts +2 -2
- package/dist/schema/nodes/wall.d.ts +248 -6
- package/dist/schema/nodes/wall.d.ts.map +1 -1
- package/dist/schema/nodes/wall.js +266 -0
- package/dist/schema/nodes/window.d.ts +40 -3
- package/dist/schema/nodes/window.d.ts.map +1 -1
- package/dist/schema/nodes/window.js +30 -0
- package/dist/schema/nodes/zone.d.ts +22 -0
- package/dist/schema/nodes/zone.d.ts.map +1 -1
- package/dist/schema/nodes/zone.js +21 -0
- package/dist/schema/scene-material.d.ts +41 -0
- package/dist/schema/scene-material.d.ts.map +1 -0
- package/dist/schema/scene-material.js +9 -0
- package/dist/schema/terrain.d.ts +28 -0
- package/dist/schema/terrain.d.ts.map +1 -0
- package/dist/schema/terrain.js +30 -0
- package/dist/schema/types.d.ts +1300 -97
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +27 -0
- package/dist/services/__fixtures__/wall-plane-top-boundary-repro.d.ts +80 -0
- package/dist/services/__fixtures__/wall-plane-top-boundary-repro.d.ts.map +1 -0
- package/dist/services/__fixtures__/wall-plane-top-boundary-repro.js +89 -0
- package/dist/services/alignment-anchors.d.ts.map +1 -1
- package/dist/services/alignment-anchors.js +30 -1
- package/dist/services/alignment.d.ts +5 -0
- package/dist/services/alignment.d.ts.map +1 -1
- package/dist/services/alignment.js +2 -0
- package/dist/services/hosting.d.ts +8 -0
- package/dist/services/hosting.d.ts.map +1 -1
- package/dist/services/hosting.js +13 -4
- package/dist/services/index.d.ts +9 -2
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +9 -2
- package/dist/services/level-height.d.ts +27 -0
- package/dist/services/level-height.d.ts.map +1 -0
- package/dist/services/level-height.js +94 -0
- package/dist/services/opening-guides.d.ts +141 -0
- package/dist/services/opening-guides.d.ts.map +1 -0
- package/dist/services/opening-guides.js +267 -0
- package/dist/services/port-connectivity.d.ts +115 -0
- package/dist/services/port-connectivity.d.ts.map +1 -0
- package/dist/services/port-connectivity.js +300 -0
- package/dist/services/riser-diagram.d.ts +40 -0
- package/dist/services/riser-diagram.d.ts.map +1 -0
- package/dist/services/riser-diagram.js +110 -0
- package/dist/services/snap.d.ts +9 -1
- package/dist/services/snap.d.ts.map +1 -1
- package/dist/services/snap.js +21 -1
- package/dist/services/storey.d.ts +103 -0
- package/dist/services/storey.d.ts.map +1 -0
- package/dist/services/storey.js +296 -0
- package/dist/services/system-graph.d.ts +30 -0
- package/dist/services/system-graph.d.ts.map +1 -0
- package/dist/services/system-graph.js +164 -0
- package/dist/services/validate-dwv.d.ts +32 -0
- package/dist/services/validate-dwv.d.ts.map +1 -0
- package/dist/services/validate-dwv.js +118 -0
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +141 -4
- package/dist/store/history-control.d.ts +31 -0
- package/dist/store/history-control.d.ts.map +1 -1
- package/dist/store/history-control.js +130 -0
- package/dist/store/use-live-terrain.d.ts +65 -0
- package/dist/store/use-live-terrain.d.ts.map +1 -0
- package/dist/store/use-live-terrain.js +31 -0
- package/dist/store/use-live-transforms.d.ts +8 -0
- package/dist/store/use-live-transforms.d.ts.map +1 -1
- package/dist/store/use-scene.d.ts +47 -3
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +992 -55
- package/dist/systems/elevator/elevator-opening-system.d.ts +1 -0
- package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-opening-system.js +27 -21
- package/dist/systems/elevator/elevator-service.d.ts +0 -2
- package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-service.js +2 -24
- package/dist/systems/fence/fence-centerline.d.ts +12 -0
- package/dist/systems/fence/fence-centerline.d.ts.map +1 -0
- package/dist/systems/fence/fence-centerline.js +37 -0
- package/dist/systems/fence/fence-spline.d.ts +39 -0
- package/dist/systems/fence/fence-spline.d.ts.map +1 -0
- package/dist/systems/fence/fence-spline.js +196 -0
- package/dist/systems/slab/slab-placement.d.ts +11 -0
- package/dist/systems/slab/slab-placement.d.ts.map +1 -0
- package/dist/systems/slab/slab-placement.js +14 -0
- package/dist/systems/slab/slab-support.d.ts +137 -0
- package/dist/systems/slab/slab-support.d.ts.map +1 -0
- package/dist/systems/slab/slab-support.js +557 -0
- package/dist/systems/stair/stair-footprint.d.ts.map +1 -1
- package/dist/systems/stair/stair-footprint.js +21 -11
- package/dist/systems/stair/stair-opening-preview.d.ts +1300 -505
- package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-sync.js +19 -33
- package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-system.js +32 -4
- package/dist/systems/stair/stair-rise.d.ts +22 -0
- package/dist/systems/stair/stair-rise.d.ts.map +1 -0
- package/dist/systems/stair/stair-rise.js +79 -0
- package/dist/systems/wall/wall-curve.d.ts +10 -0
- package/dist/systems/wall/wall-curve.d.ts.map +1 -1
- package/dist/systems/wall/wall-curve.js +1 -1
- package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
- package/dist/systems/wall/wall-mitering.js +44 -8
- package/dist/systems/wall/wall-top.d.ts +40 -0
- package/dist/systems/wall/wall-top.d.ts.map +1 -0
- package/dist/systems/wall/wall-top.js +46 -0
- package/dist/utils/clone-scene-graph.d.ts +1 -0
- package/dist/utils/clone-scene-graph.d.ts.map +1 -1
- package/dist/utils/clone-scene-graph.js +56 -5
- package/dist/utils/heal-scene-graph.d.ts +18 -0
- package/dist/utils/heal-scene-graph.d.ts.map +1 -0
- package/dist/utils/heal-scene-graph.js +106 -0
- package/dist/validation/validate-build-json.d.ts +3 -0
- package/dist/validation/validate-build-json.d.ts.map +1 -1
- package/dist/validation/validate-build-json.js +90 -5
- package/package.json +4 -4
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +0 -2
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +0 -1
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +0 -273
- package/dist/lib/space-detection-pause.test.d.ts +0 -2
- package/dist/lib/space-detection-pause.test.d.ts.map +0 -1
- package/dist/lib/space-detection-pause.test.js +0 -42
- package/dist/lib/space-detection.test.d.ts +0 -2
- package/dist/lib/space-detection.test.d.ts.map +0 -1
- package/dist/lib/space-detection.test.js +0 -77
- package/dist/registry/registry.test.d.ts +0 -2
- package/dist/registry/registry.test.d.ts.map +0 -1
- package/dist/registry/registry.test.js +0 -183
- package/dist/registry/relations-resolver.test.d.ts +0 -2
- package/dist/registry/relations-resolver.test.d.ts.map +0 -1
- package/dist/registry/relations-resolver.test.js +0 -183
- package/dist/registry/scene-api.test.d.ts +0 -2
- package/dist/registry/scene-api.test.d.ts.map +0 -1
- package/dist/registry/scene-api.test.js +0 -183
- package/dist/registry/subtree.test.d.ts +0 -2
- package/dist/registry/subtree.test.d.ts.map +0 -1
- package/dist/registry/subtree.test.js +0 -133
- package/dist/schema/asset-url.test.d.ts +0 -2
- package/dist/schema/asset-url.test.d.ts.map +0 -1
- package/dist/schema/asset-url.test.js +0 -134
- package/dist/services/alignment-anchors.test.d.ts +0 -2
- package/dist/services/alignment-anchors.test.d.ts.map +0 -1
- package/dist/services/alignment-anchors.test.js +0 -383
- package/dist/services/alignment.test.d.ts +0 -2
- package/dist/services/alignment.test.d.ts.map +0 -1
- package/dist/services/alignment.test.js +0 -92
- package/dist/services/drag-session.test.d.ts +0 -2
- package/dist/services/drag-session.test.d.ts.map +0 -1
- package/dist/services/drag-session.test.js +0 -219
- package/dist/services/hosting.test.d.ts +0 -2
- package/dist/services/hosting.test.d.ts.map +0 -1
- package/dist/services/hosting.test.js +0 -199
- package/dist/services/movement.test.d.ts +0 -2
- package/dist/services/movement.test.d.ts.map +0 -1
- package/dist/services/movement.test.js +0 -133
- package/dist/services/single-undo-dance.test.d.ts +0 -2
- package/dist/services/single-undo-dance.test.d.ts.map +0 -1
- package/dist/services/single-undo-dance.test.js +0 -210
- package/dist/services/snap.test.d.ts +0 -2
- package/dist/services/snap.test.d.ts.map +0 -1
- package/dist/services/snap.test.js +0 -95
- package/dist/store/actions/node-mutation-sanitize.test.d.ts +0 -2
- package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +0 -1
- package/dist/store/actions/node-mutation-sanitize.test.js +0 -147
- package/dist/store/actions/reparent.test.d.ts +0 -2
- package/dist/store/actions/reparent.test.d.ts.map +0 -1
- package/dist/store/actions/reparent.test.js +0 -212
- package/dist/store/use-scene-elevator-migration.test.d.ts +0 -2
- package/dist/store/use-scene-elevator-migration.test.d.ts.map +0 -1
- package/dist/store/use-scene-elevator-migration.test.js +0 -109
- package/dist/systems/elevator/elevator-opening-sync.test.d.ts +0 -2
- package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +0 -1
- package/dist/systems/elevator/elevator-opening-sync.test.js +0 -146
- package/dist/systems/elevator/elevator-runtime.test.d.ts +0 -2
- package/dist/systems/elevator/elevator-runtime.test.d.ts.map +0 -1
- package/dist/systems/elevator/elevator-runtime.test.js +0 -64
- package/dist/systems/stair/stair-opening-preview.test.d.ts +0 -2
- package/dist/systems/stair/stair-opening-preview.test.d.ts.map +0 -1
- package/dist/systems/stair/stair-opening-preview.test.js +0 -80
- package/dist/systems/stair/stair-opening-sync.test.d.ts +0 -2
- package/dist/systems/stair/stair-opening-sync.test.d.ts.map +0 -1
- package/dist/systems/stair/stair-opening-sync.test.js +0 -446
- package/dist/utils/clone-scene-graph.test.d.ts +0 -2
- package/dist/utils/clone-scene-graph.test.d.ts.map +0 -1
- package/dist/utils/clone-scene-graph.test.js +0 -59
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { pointInPolygon, pointOnPolygonBoundary, wallOverlapsSlabFootprint, } from '../systems/slab/slab-support';
|
|
2
|
+
import { DEFAULT_LEVEL_HEIGHT } from './level-height';
|
|
3
|
+
/**
|
|
4
|
+
* Gap kept between a ceiling's stored height and its clamp bound (storey
|
|
5
|
+
* plane or covering-slab underside), so the ceiling surface never
|
|
6
|
+
* coincides with the solid above it.
|
|
7
|
+
*/
|
|
8
|
+
export const CEILING_CLAMP_MARGIN = 0.01;
|
|
9
|
+
/**
|
|
10
|
+
* Stored storey height in meters (floor-to-floor). Falls back to
|
|
11
|
+
* {@link DEFAULT_LEVEL_HEIGHT} for unmigrated legacy levels whose `height`
|
|
12
|
+
* field is absent.
|
|
13
|
+
*/
|
|
14
|
+
export function getStoredLevelHeight(level) {
|
|
15
|
+
return level.height ?? DEFAULT_LEVEL_HEIGHT;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolves the owning building: explicit `parentId` pointing at a building
|
|
19
|
+
* wins; legacy levels that only appear in a building's `children` array
|
|
20
|
+
* resolve through that membership.
|
|
21
|
+
*/
|
|
22
|
+
function resolveLevelBuildingId(levelId, parentId, buildings) {
|
|
23
|
+
const directParent = parentId ? buildings.find((building) => building.id === parentId) : undefined;
|
|
24
|
+
if (directParent)
|
|
25
|
+
return directParent.id;
|
|
26
|
+
return buildings.find((building) => building.children.includes(levelId))?.id ?? null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Per-building stacked elevations from stored storey heights: levels are
|
|
30
|
+
* sorted by ordinal ascending within each building, the lowest level's floor
|
|
31
|
+
* sits at 0, and each next floor sits on top of the previous storey height.
|
|
32
|
+
* Levels with no resolvable building share one legacy stack from 0.
|
|
33
|
+
*
|
|
34
|
+
* Pure — operates on the serialized nodes record only.
|
|
35
|
+
*/
|
|
36
|
+
export function getLevelElevations(nodes) {
|
|
37
|
+
const buildings = Object.values(nodes).filter((node) => node?.type === 'building');
|
|
38
|
+
const entries = [];
|
|
39
|
+
for (const node of Object.values(nodes)) {
|
|
40
|
+
if (node?.type !== 'level')
|
|
41
|
+
continue;
|
|
42
|
+
const level = node;
|
|
43
|
+
entries.push({
|
|
44
|
+
levelId: level.id,
|
|
45
|
+
baseY: 0,
|
|
46
|
+
height: getStoredLevelHeight(level),
|
|
47
|
+
buildingId: resolveLevelBuildingId(level.id, level.parentId, buildings),
|
|
48
|
+
ordinal: level.level,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const elevations = new Map();
|
|
52
|
+
const cumulativeYByBuilding = new Map();
|
|
53
|
+
for (const entry of entries.sort((a, b) => a.ordinal - b.ordinal)) {
|
|
54
|
+
const baseY = cumulativeYByBuilding.get(entry.buildingId) ?? 0;
|
|
55
|
+
elevations.set(entry.levelId, {
|
|
56
|
+
baseY,
|
|
57
|
+
height: entry.height,
|
|
58
|
+
buildingId: entry.buildingId,
|
|
59
|
+
ordinal: entry.ordinal,
|
|
60
|
+
});
|
|
61
|
+
cumulativeYByBuilding.set(entry.buildingId, baseY + entry.height);
|
|
62
|
+
}
|
|
63
|
+
return elevations;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The id of the level directly above `levelId` in its own stack (same
|
|
67
|
+
* resolved building, or the shared legacy stack for building-less levels):
|
|
68
|
+
* the level with the lowest ordinal strictly greater than the queried
|
|
69
|
+
* level's. `null` when the level is topmost or unresolvable.
|
|
70
|
+
*/
|
|
71
|
+
export function findLevelAboveId(levelId, elevations) {
|
|
72
|
+
const entry = elevations.get(levelId);
|
|
73
|
+
if (!entry)
|
|
74
|
+
return null;
|
|
75
|
+
let aboveId = null;
|
|
76
|
+
let aboveOrdinal = Number.POSITIVE_INFINITY;
|
|
77
|
+
for (const [candidateId, candidate] of elevations) {
|
|
78
|
+
if (candidateId === levelId)
|
|
79
|
+
continue;
|
|
80
|
+
if (candidate.buildingId !== entry.buildingId)
|
|
81
|
+
continue;
|
|
82
|
+
if (candidate.ordinal > entry.ordinal && candidate.ordinal < aboveOrdinal) {
|
|
83
|
+
aboveOrdinal = candidate.ordinal;
|
|
84
|
+
aboveId = candidateId;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return aboveId;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The level directly above `levelId` — see {@link findLevelAboveId}.
|
|
91
|
+
* `null` when topmost or unresolvable. Pure.
|
|
92
|
+
*/
|
|
93
|
+
export function getLevelAbove(levelId, nodes) {
|
|
94
|
+
const aboveId = findLevelAboveId(levelId, getLevelElevations(nodes));
|
|
95
|
+
if (!aboveId)
|
|
96
|
+
return null;
|
|
97
|
+
const above = nodes[aboveId];
|
|
98
|
+
return above?.type === 'level' ? above : null;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The id of the level directly below `levelId` in its own stack — mirror of
|
|
102
|
+
* {@link findLevelAboveId}: the level with the highest ordinal strictly less
|
|
103
|
+
* than the queried level's. `null` when the level is lowest or unresolvable.
|
|
104
|
+
*/
|
|
105
|
+
export function findLevelBelowId(levelId, elevations) {
|
|
106
|
+
const entry = elevations.get(levelId);
|
|
107
|
+
if (!entry)
|
|
108
|
+
return null;
|
|
109
|
+
let belowId = null;
|
|
110
|
+
let belowOrdinal = Number.NEGATIVE_INFINITY;
|
|
111
|
+
for (const [candidateId, candidate] of elevations) {
|
|
112
|
+
if (candidateId === levelId)
|
|
113
|
+
continue;
|
|
114
|
+
if (candidate.buildingId !== entry.buildingId)
|
|
115
|
+
continue;
|
|
116
|
+
if (candidate.ordinal < entry.ordinal && candidate.ordinal > belowOrdinal) {
|
|
117
|
+
belowOrdinal = candidate.ordinal;
|
|
118
|
+
belowId = candidateId;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return belowId;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The level directly below `levelId` — see {@link findLevelBelowId}.
|
|
125
|
+
* `null` when lowest or unresolvable. Pure.
|
|
126
|
+
*/
|
|
127
|
+
export function getLevelBelow(levelId, nodes) {
|
|
128
|
+
const belowId = findLevelBelowId(levelId, getLevelElevations(nodes));
|
|
129
|
+
if (!belowId)
|
|
130
|
+
return null;
|
|
131
|
+
const below = nodes[belowId];
|
|
132
|
+
return below?.type === 'level' ? below : null;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Storey height of the queried level plus the level-above's covering
|
|
136
|
+
* (non-recessed) slabs. `null` when `levelId` doesn't resolve to a level.
|
|
137
|
+
* A missing level above yields an empty slab list, not `null` — the
|
|
138
|
+
* storey height is still meaningful for the clamp bound.
|
|
139
|
+
*/
|
|
140
|
+
function resolveCoveringSlabContext(levelId, nodes) {
|
|
141
|
+
const level = nodes[levelId];
|
|
142
|
+
if (level?.type !== 'level')
|
|
143
|
+
return null;
|
|
144
|
+
const above = getLevelAbove(levelId, nodes);
|
|
145
|
+
const slabs = [];
|
|
146
|
+
for (const childId of above?.children ?? []) {
|
|
147
|
+
const child = nodes[childId];
|
|
148
|
+
if (child?.type !== 'slab')
|
|
149
|
+
continue;
|
|
150
|
+
const slab = child;
|
|
151
|
+
// Recessed slabs (pools) are open shells, not covering solids.
|
|
152
|
+
if (slab.recessed === true)
|
|
153
|
+
continue;
|
|
154
|
+
if (slab.polygon.length < 3)
|
|
155
|
+
continue;
|
|
156
|
+
slabs.push(slab);
|
|
157
|
+
}
|
|
158
|
+
return { storeyHeight: getStoredLevelHeight(level), slabs };
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Underside of `slab`'s solid in the QUERIED level's local Y. The solid
|
|
162
|
+
* occupies `[elevation - thickness, elevation]` in ITS level's local Y,
|
|
163
|
+
* which sits `storeyHeight` above the queried level's floor.
|
|
164
|
+
*/
|
|
165
|
+
function coveringUndersideY(storeyHeight, slab) {
|
|
166
|
+
return storeyHeight + ((slab.elevation ?? 0.05) - (slab.thickness ?? 0.05));
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Whether `slab`'s stored footprint (polygon minus holes) covers `[x, z]`.
|
|
170
|
+
* Ray-cast pointInPolygon flips arbitrarily for points exactly ON the
|
|
171
|
+
* boundary (min-side edges read inside, max-side edges outside), so
|
|
172
|
+
* boundary contact counts as covered explicitly — the same convention as
|
|
173
|
+
* the slab-support interval classification. A point on a hole's rim keeps
|
|
174
|
+
* coverage (mirrors the support election's hole handling).
|
|
175
|
+
*
|
|
176
|
+
* Raw stored polygon + holes on purpose (mirrors getCeilingAt): the
|
|
177
|
+
* clamp bound doesn't need the rendered footprint's junction trims,
|
|
178
|
+
* and staying off the render path keeps this query cheap and pure.
|
|
179
|
+
*/
|
|
180
|
+
function slabCoversPoint(slab, x, z) {
|
|
181
|
+
if (!pointInPolygon(x, z, slab.polygon) && !pointOnPolygonBoundary(x, z, slab.polygon)) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
for (const hole of slab.holes ?? []) {
|
|
185
|
+
if (hole.length < 3)
|
|
186
|
+
continue;
|
|
187
|
+
if (pointInPolygon(x, z, hole) && !pointOnPolygonBoundary(x, z, hole))
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Lowest underside among `slabs` covering `[x, z]`, in the queried
|
|
194
|
+
* level's local Y, or `null` when none covers the point.
|
|
195
|
+
*/
|
|
196
|
+
function lowestCoveringUndersideAt(context, x, z) {
|
|
197
|
+
let lowest = null;
|
|
198
|
+
for (const slab of context.slabs) {
|
|
199
|
+
if (!slabCoversPoint(slab, x, z))
|
|
200
|
+
continue;
|
|
201
|
+
const underside = coveringUndersideY(context.storeyHeight, slab);
|
|
202
|
+
if (lowest === null || underside < lowest)
|
|
203
|
+
lowest = underside;
|
|
204
|
+
}
|
|
205
|
+
return lowest;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Underside of the LOWEST slab from the level above that covers
|
|
209
|
+
* level-local point `[x, z]`, expressed in the queried level's local Y:
|
|
210
|
+
* `storeyHeight + (slab.elevation - slab.thickness)`. `recessed` slabs
|
|
211
|
+
* (pools) never cover. `null` when no covering slab (or no level above).
|
|
212
|
+
*
|
|
213
|
+
* Coordinate spaces: levels stack in Y only (`LevelNode` carries no XZ
|
|
214
|
+
* transform and the viewer's LevelSystem writes only `position.y`), so a
|
|
215
|
+
* level-local `[x, z]` is valid in every level of the stack unchanged.
|
|
216
|
+
*/
|
|
217
|
+
export function getCoveringSlabUndersideAt(levelId, nodes, x, z) {
|
|
218
|
+
const context = resolveCoveringSlabContext(levelId, nodes);
|
|
219
|
+
if (!context)
|
|
220
|
+
return null;
|
|
221
|
+
return lowestCoveringUndersideAt(context, x, z);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Top plane for a plane-bound wall on `levelId`, in level-local Y:
|
|
225
|
+
* `min(stored storey height, lowest covering-slab underside over the wall's
|
|
226
|
+
* span)` — a thick or flush slab on the level above SHORTENS the walls below
|
|
227
|
+
* instead of colliding with them (Revit-style automatic attach).
|
|
228
|
+
*
|
|
229
|
+
* Coverage: the wall's thickness band (centerline + face lines, arc-aware)
|
|
230
|
+
* is clipped against each covering slab's stored polygon minus holes via
|
|
231
|
+
* {@link wallOverlapsSlabFootprint} — the same overlap machinery as the
|
|
232
|
+
* support election. Point sampling is deliberately avoided: auto-slab
|
|
233
|
+
* polygons derive from wall CENTERLINES, so perimeter walls sit exactly ON
|
|
234
|
+
* the polygon boundary, where ray-cast point-in-polygon flips with the
|
|
235
|
+
* edge's orientation (one wall clamped, its neighbor didn't). Boundary
|
|
236
|
+
* contact counts as covered on every side of the slab.
|
|
237
|
+
*
|
|
238
|
+
* This is THE plane for a plane-bound wall (`height` absent). Explicit-height
|
|
239
|
+
* walls ignore the value (`resolveWallTop` returns their stored height), so
|
|
240
|
+
* passing it wherever a raw storey height feeds `resolveWallTop` /
|
|
241
|
+
* `resolveWallEffectiveHeight` is always safe. Falls back to
|
|
242
|
+
* {@link DEFAULT_LEVEL_HEIGHT} when `levelId` doesn't resolve to a level.
|
|
243
|
+
*/
|
|
244
|
+
export function getWallPlaneTop(wall, levelId, nodes) {
|
|
245
|
+
const context = resolveCoveringSlabContext(levelId, nodes);
|
|
246
|
+
if (!context)
|
|
247
|
+
return DEFAULT_LEVEL_HEIGHT;
|
|
248
|
+
let plane = context.storeyHeight;
|
|
249
|
+
for (const slab of context.slabs) {
|
|
250
|
+
const underside = coveringUndersideY(context.storeyHeight, slab);
|
|
251
|
+
if (underside >= plane)
|
|
252
|
+
continue;
|
|
253
|
+
if (!wallOverlapsSlabFootprint(wall, slab.polygon, slab.holes))
|
|
254
|
+
continue;
|
|
255
|
+
plane = underside;
|
|
256
|
+
}
|
|
257
|
+
return plane;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Upper bound for a ceiling's stored height over `polygon` on `levelId`:
|
|
261
|
+
* `min(storey plane, lowest covering-slab underside) - CEILING_CLAMP_MARGIN`.
|
|
262
|
+
* The covering underside is sampled at every polygon vertex plus the
|
|
263
|
+
* centroid — cheap, and a slab overlapping a convex-ish ceiling almost
|
|
264
|
+
* always covers one of those points; exact polygon-vs-polygon overlap is
|
|
265
|
+
* not worth its cost for a clamp bound. Ceiling outlines share footprint
|
|
266
|
+
* edges with the slabs above them the same way walls do, so vertices
|
|
267
|
+
* sitting exactly on a slab's boundary count as covered on every side
|
|
268
|
+
* (see `slabCoversPoint`) instead of flipping with the edge orientation.
|
|
269
|
+
*
|
|
270
|
+
* Returns `Infinity` when `levelId` doesn't resolve, so callers clamp
|
|
271
|
+
* against nothing rather than a garbage plane.
|
|
272
|
+
*/
|
|
273
|
+
export function getCeilingClampBound(levelId, nodes, polygon) {
|
|
274
|
+
const context = resolveCoveringSlabContext(levelId, nodes);
|
|
275
|
+
if (!context)
|
|
276
|
+
return Number.POSITIVE_INFINITY;
|
|
277
|
+
let bound = context.storeyHeight;
|
|
278
|
+
if (polygon.length > 0) {
|
|
279
|
+
let cx = 0;
|
|
280
|
+
let cz = 0;
|
|
281
|
+
for (const [x, z] of polygon) {
|
|
282
|
+
cx += x;
|
|
283
|
+
cz += z;
|
|
284
|
+
}
|
|
285
|
+
const samples = [
|
|
286
|
+
...polygon,
|
|
287
|
+
[cx / polygon.length, cz / polygon.length],
|
|
288
|
+
];
|
|
289
|
+
for (const [x, z] of samples) {
|
|
290
|
+
const underside = lowestCoveringUndersideAt(context, x, z);
|
|
291
|
+
if (underside !== null && underside < bound)
|
|
292
|
+
bound = underside;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return bound - CEILING_CLAMP_MARGIN;
|
|
296
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AnyNode, AnyNodeId } from '../schema';
|
|
2
|
+
export type SystemSummary = {
|
|
3
|
+
/** Every node in this connected component. */
|
|
4
|
+
nodeIds: AnyNodeId[];
|
|
5
|
+
/** Distribution loops present, e.g. ['supply'], ['supply','return']. */
|
|
6
|
+
systems: string[];
|
|
7
|
+
/** Duct / lineset run statistics. */
|
|
8
|
+
runCount: number;
|
|
9
|
+
runLengthM: number;
|
|
10
|
+
fittingCount: number;
|
|
11
|
+
terminalCount: number;
|
|
12
|
+
equipmentCount: number;
|
|
13
|
+
/** False = orphaned subtree: air goes nowhere (no furnace / air
|
|
14
|
+
* handler / condenser anywhere in the component). */
|
|
15
|
+
connectedToEquipment: boolean;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Group every port-bearing node into connected components via coinciding
|
|
19
|
+
* ports. Nodes with ports but no joints form singleton components; nodes
|
|
20
|
+
* without `def.ports` don't participate at all.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildPortComponents(nodes: Readonly<Record<AnyNodeId, AnyNode>>): AnyNodeId[][];
|
|
23
|
+
/**
|
|
24
|
+
* Summary of the system the given node belongs to, or null when the node
|
|
25
|
+
* has no ports (not a distribution kind). A node with ports but no
|
|
26
|
+
* joints yet still gets a (singleton) summary — `connectedToEquipment:
|
|
27
|
+
* false` is the interesting signal there.
|
|
28
|
+
*/
|
|
29
|
+
export declare function summarizeSystemFor(nodeId: AnyNodeId, nodes: Readonly<Record<AnyNodeId, AnyNode>>): SystemSummary | null;
|
|
30
|
+
//# sourceMappingURL=system-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-graph.d.ts","sourceRoot":"","sources":["../../src/services/system-graph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAoBnD,MAAM,MAAM,aAAa,GAAG;IAC1B,8CAA8C;IAC9C,OAAO,EAAE,SAAS,EAAE,CAAA;IACpB,wEAAwE;IACxE,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,CAAA;IACtB;0DACsD;IACtD,oBAAoB,EAAE,OAAO,CAAA;CAC9B,CAAA;AA2DD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,EAAE,EAAE,CA4B9F;AAkDD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAC1C,aAAa,GAAG,IAAI,CAStB"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { nodeRegistry } from '../registry';
|
|
2
|
+
/**
|
|
3
|
+
* The "System" primitive: connected components over the port graph.
|
|
4
|
+
*
|
|
5
|
+
* Two nodes are joined when a port of one coincides in space with a port
|
|
6
|
+
* of the other — the same mated-joint relationship `port-connectivity`
|
|
7
|
+
* uses for drag propagation, read here at whole-scene scope. A component
|
|
8
|
+
* is one distribution system: a furnace, its trunk, the tees, branches,
|
|
9
|
+
* and registers hanging off it.
|
|
10
|
+
*
|
|
11
|
+
* Pure logic (def.ports + arithmetic), no rendering — lives in core so
|
|
12
|
+
* the editor (badges, schedules) and analyses (sizing, code checks) can
|
|
13
|
+
* share it.
|
|
14
|
+
*/
|
|
15
|
+
/** Distance (meters) under which two ports count as the same joint —
|
|
16
|
+
* matches port-connectivity's tolerance for hand-placed joints. */
|
|
17
|
+
const COINCIDENT_EPS_M = 0.05;
|
|
18
|
+
function collectPorts(nodes) {
|
|
19
|
+
const result = [];
|
|
20
|
+
for (const node of Object.values(nodes)) {
|
|
21
|
+
if (!node)
|
|
22
|
+
continue;
|
|
23
|
+
const ports = nodeRegistry.get(node.type)?.ports?.(node);
|
|
24
|
+
if (!ports)
|
|
25
|
+
continue;
|
|
26
|
+
for (const port of ports) {
|
|
27
|
+
result.push({
|
|
28
|
+
nodeId: node.id,
|
|
29
|
+
x: port.position[0],
|
|
30
|
+
y: port.position[1],
|
|
31
|
+
z: port.position[2],
|
|
32
|
+
system: port.system,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
/** Union-find over node ids. */
|
|
39
|
+
class Components {
|
|
40
|
+
parent = new Map();
|
|
41
|
+
find(id) {
|
|
42
|
+
let root = this.parent.get(id) ?? id;
|
|
43
|
+
if (root !== id) {
|
|
44
|
+
root = this.find(root);
|
|
45
|
+
this.parent.set(id, root);
|
|
46
|
+
}
|
|
47
|
+
return root;
|
|
48
|
+
}
|
|
49
|
+
union(a, b) {
|
|
50
|
+
const ra = this.find(a);
|
|
51
|
+
const rb = this.find(b);
|
|
52
|
+
if (ra !== rb)
|
|
53
|
+
this.parent.set(rb, ra);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function pathLength(path) {
|
|
57
|
+
let total = 0;
|
|
58
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
59
|
+
const a = path[i];
|
|
60
|
+
const b = path[i + 1];
|
|
61
|
+
total += Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
|
|
62
|
+
}
|
|
63
|
+
return total;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Group every port-bearing node into connected components via coinciding
|
|
67
|
+
* ports. Nodes with ports but no joints form singleton components; nodes
|
|
68
|
+
* without `def.ports` don't participate at all.
|
|
69
|
+
*/
|
|
70
|
+
export function buildPortComponents(nodes) {
|
|
71
|
+
const ports = collectPorts(nodes);
|
|
72
|
+
const components = new Components();
|
|
73
|
+
const epsSq = COINCIDENT_EPS_M * COINCIDENT_EPS_M;
|
|
74
|
+
for (let i = 0; i < ports.length; i++) {
|
|
75
|
+
const a = ports[i];
|
|
76
|
+
for (let j = i + 1; j < ports.length; j++) {
|
|
77
|
+
const b = ports[j];
|
|
78
|
+
if (a.nodeId === b.nodeId)
|
|
79
|
+
continue;
|
|
80
|
+
const dx = a.x - b.x;
|
|
81
|
+
const dy = a.y - b.y;
|
|
82
|
+
const dz = a.z - b.z;
|
|
83
|
+
if (dx * dx + dy * dy + dz * dz <= epsSq)
|
|
84
|
+
components.union(a.nodeId, b.nodeId);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const grouped = new Map();
|
|
88
|
+
const seen = new Set();
|
|
89
|
+
for (const port of ports) {
|
|
90
|
+
if (seen.has(port.nodeId))
|
|
91
|
+
continue;
|
|
92
|
+
seen.add(port.nodeId);
|
|
93
|
+
const root = components.find(port.nodeId);
|
|
94
|
+
const group = grouped.get(root);
|
|
95
|
+
if (group)
|
|
96
|
+
group.push(port.nodeId);
|
|
97
|
+
else
|
|
98
|
+
grouped.set(root, [port.nodeId]);
|
|
99
|
+
}
|
|
100
|
+
return [...grouped.values()];
|
|
101
|
+
}
|
|
102
|
+
function summarize(nodeIds, nodes) {
|
|
103
|
+
const systems = new Set();
|
|
104
|
+
let runCount = 0;
|
|
105
|
+
let runLengthM = 0;
|
|
106
|
+
let fittingCount = 0;
|
|
107
|
+
let terminalCount = 0;
|
|
108
|
+
let equipmentCount = 0;
|
|
109
|
+
for (const id of nodeIds) {
|
|
110
|
+
const node = nodes[id];
|
|
111
|
+
if (!node)
|
|
112
|
+
continue;
|
|
113
|
+
const role = nodeRegistry.get(node.type)?.distributionRole;
|
|
114
|
+
const fields = node;
|
|
115
|
+
if (role === 'run') {
|
|
116
|
+
runCount += 1;
|
|
117
|
+
if (fields.path)
|
|
118
|
+
runLengthM += pathLength(fields.path);
|
|
119
|
+
// Linesets carry refrigerant; duct / pipe runs name their own loop.
|
|
120
|
+
systems.add(fields.system ?? 'refrigerant');
|
|
121
|
+
}
|
|
122
|
+
else if (role === 'fitting') {
|
|
123
|
+
fittingCount += 1;
|
|
124
|
+
if (fields.system)
|
|
125
|
+
systems.add(fields.system);
|
|
126
|
+
}
|
|
127
|
+
else if (role === 'terminal') {
|
|
128
|
+
terminalCount += 1;
|
|
129
|
+
systems.add(fields.terminalType === 'return-grille' ? 'return' : 'supply');
|
|
130
|
+
}
|
|
131
|
+
else if (role === 'equipment') {
|
|
132
|
+
equipmentCount += 1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
nodeIds,
|
|
137
|
+
systems: [...systems].sort(),
|
|
138
|
+
runCount,
|
|
139
|
+
runLengthM,
|
|
140
|
+
fittingCount,
|
|
141
|
+
terminalCount,
|
|
142
|
+
equipmentCount,
|
|
143
|
+
connectedToEquipment: equipmentCount > 0,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Summary of the system the given node belongs to, or null when the node
|
|
148
|
+
* has no ports (not a distribution kind). A node with ports but no
|
|
149
|
+
* joints yet still gets a (singleton) summary — `connectedToEquipment:
|
|
150
|
+
* false` is the interesting signal there.
|
|
151
|
+
*/
|
|
152
|
+
export function summarizeSystemFor(nodeId, nodes) {
|
|
153
|
+
const node = nodes[nodeId];
|
|
154
|
+
if (!node)
|
|
155
|
+
return null;
|
|
156
|
+
const ports = nodeRegistry.get(node.type)?.ports?.(node);
|
|
157
|
+
if (!ports || ports.length === 0)
|
|
158
|
+
return null;
|
|
159
|
+
for (const component of buildPortComponents(nodes)) {
|
|
160
|
+
if (component.includes(nodeId))
|
|
161
|
+
return summarize(component, nodes);
|
|
162
|
+
}
|
|
163
|
+
return summarize([nodeId], nodes);
|
|
164
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AnyNode, AnyNodeId } from '../schema';
|
|
2
|
+
/**
|
|
3
|
+
* IPC validators for the DWV (drain-waste-vent) system — the "CodeRule"
|
|
4
|
+
* primitive from the domain brief. The slope, minimum-size, and
|
|
5
|
+
* trap-arm rules are all geometric and read straight off the node
|
|
6
|
+
* fields, so they live here in core (pure logic) where the editor can
|
|
7
|
+
* surface them and analyses can reuse them.
|
|
8
|
+
*
|
|
9
|
+
* Scope is residential IPC, simplified:
|
|
10
|
+
* - 704.1 drainage slope by pipe size.
|
|
11
|
+
* - 909 trap-arm maximum developed length by trap size.
|
|
12
|
+
*
|
|
13
|
+
* These are intentionally conservative approximations, not a certified
|
|
14
|
+
* plan-check — enough to flag the mistakes a drawing tool invites.
|
|
15
|
+
*/
|
|
16
|
+
/** Drainage findings, worst-first per consumer's sort. */
|
|
17
|
+
export type DwvSeverity = 'error' | 'warning';
|
|
18
|
+
export type DwvFinding = {
|
|
19
|
+
severity: DwvSeverity;
|
|
20
|
+
/** Stable rule id, e.g. 'slope-too-flat'. */
|
|
21
|
+
code: string;
|
|
22
|
+
/** Human-readable, already-formatted message. */
|
|
23
|
+
message: string;
|
|
24
|
+
/** Nodes the finding implicates (usually one). */
|
|
25
|
+
nodeIds: AnyNodeId[];
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Run every DWV rule over the scene and return the findings. Empty
|
|
29
|
+
* array = nothing to flag. Pure: no scene/store access, no rendering.
|
|
30
|
+
*/
|
|
31
|
+
export declare function validateDwv(nodes: Readonly<Record<AnyNodeId, AnyNode>>): DwvFinding[];
|
|
32
|
+
//# sourceMappingURL=validate-dwv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-dwv.d.ts","sourceRoot":"","sources":["../../src/services/validate-dwv.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAGnD;;;;;;;;;;;;;GAaG;AAEH,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,SAAS,CAAA;AAE7C,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,WAAW,CAAA;IACrB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAA;IACZ,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,kDAAkD;IAClD,OAAO,EAAE,SAAS,EAAE,CAAA;CACrB,CAAA;AAuDD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAAG,UAAU,EAAE,CAwErF"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { buildPortComponents } from './system-graph';
|
|
2
|
+
/** IPC 704.1 minimum drainage slope (rise/run, dimensionless) by
|
|
3
|
+
* nominal pipe size: ¼"/ft (1:48) under 3", ⅛"/ft (1:96) for 3–6",
|
|
4
|
+
* 1/16"/ft (1:192) at 8"+. */
|
|
5
|
+
function minSlopeFor(diameterIn) {
|
|
6
|
+
if (diameterIn < 3)
|
|
7
|
+
return 1 / 48;
|
|
8
|
+
if (diameterIn < 8)
|
|
9
|
+
return 1 / 96;
|
|
10
|
+
return 1 / 192;
|
|
11
|
+
}
|
|
12
|
+
/** IPC Table 909.1 maximum trap-arm developed length (meters) by trap
|
|
13
|
+
* size: 30" @ 1¼", 42" @ 1½", 60" @ 2", 72" @ 3", 120" @ 4". */
|
|
14
|
+
const TRAP_ARM_MAX_M = [
|
|
15
|
+
[1.25, 30 * 0.0254],
|
|
16
|
+
[1.5, 42 * 0.0254],
|
|
17
|
+
[2, 60 * 0.0254],
|
|
18
|
+
[3, 72 * 0.0254],
|
|
19
|
+
[4, 120 * 0.0254],
|
|
20
|
+
];
|
|
21
|
+
function trapArmMaxFor(diameterIn) {
|
|
22
|
+
let max = Infinity;
|
|
23
|
+
for (const [size, lengthM] of TRAP_ARM_MAX_M) {
|
|
24
|
+
if (diameterIn <= size)
|
|
25
|
+
return lengthM;
|
|
26
|
+
max = lengthM;
|
|
27
|
+
}
|
|
28
|
+
return max;
|
|
29
|
+
}
|
|
30
|
+
/** Slopes shallower than this fraction of the minimum are flagged
|
|
31
|
+
* "too flat" — a small tolerance keeps round-off off the list. */
|
|
32
|
+
const SLOPE_TOLERANCE = 0.9;
|
|
33
|
+
/** Horizontal legs shorter than this (meters) are treated as vertical
|
|
34
|
+
* stacks and skipped from the slope check. */
|
|
35
|
+
const VERTICAL_LEG_EPS_M = 0.02;
|
|
36
|
+
function legSlope(a, b) {
|
|
37
|
+
const horizontalM = Math.hypot(b[0] - a[0], b[2] - a[2]);
|
|
38
|
+
if (horizontalM < VERTICAL_LEG_EPS_M)
|
|
39
|
+
return { horizontalM, slope: Infinity };
|
|
40
|
+
return { horizontalM, slope: Math.abs(a[1] - b[1]) / horizontalM };
|
|
41
|
+
}
|
|
42
|
+
function inchLabel(value) {
|
|
43
|
+
return `${value}"`;
|
|
44
|
+
}
|
|
45
|
+
/** Per-foot slope as a readable fraction, e.g. 0.0208 → '¼"/ft'. */
|
|
46
|
+
function slopePerFootLabel(slope) {
|
|
47
|
+
const inchesPerFoot = slope * 12;
|
|
48
|
+
return `${inchesPerFoot.toFixed(2)}"/ft`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Run every DWV rule over the scene and return the findings. Empty
|
|
52
|
+
* array = nothing to flag. Pure: no scene/store access, no rendering.
|
|
53
|
+
*/
|
|
54
|
+
export function validateDwv(nodes) {
|
|
55
|
+
const findings = [];
|
|
56
|
+
// ── Per-segment slope (waste only) ──────────────────────────────
|
|
57
|
+
for (const node of Object.values(nodes)) {
|
|
58
|
+
if (node?.type !== 'pipe-segment' || node.system !== 'waste')
|
|
59
|
+
continue;
|
|
60
|
+
const path = node.path;
|
|
61
|
+
const minSlope = minSlopeFor(node.diameter);
|
|
62
|
+
const maxSlope = node.diameter / 12; // 1 pipe-diameter per foot → siphoning
|
|
63
|
+
let flaggedFlat = false;
|
|
64
|
+
let flaggedSteep = false;
|
|
65
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
66
|
+
const { slope } = legSlope(path[i], path[i + 1]);
|
|
67
|
+
if (slope === Infinity)
|
|
68
|
+
continue; // vertical stack leg
|
|
69
|
+
if (!flaggedFlat && slope < minSlope * SLOPE_TOLERANCE) {
|
|
70
|
+
findings.push({
|
|
71
|
+
severity: 'error',
|
|
72
|
+
code: 'slope-too-flat',
|
|
73
|
+
message: `${inchLabel(node.diameter)} drain slopes ${slopePerFootLabel(slope)} — IPC 704.1 requires at least ${slopePerFootLabel(minSlope)}.`,
|
|
74
|
+
nodeIds: [node.id],
|
|
75
|
+
});
|
|
76
|
+
flaggedFlat = true;
|
|
77
|
+
}
|
|
78
|
+
if (!flaggedSteep && slope > maxSlope) {
|
|
79
|
+
findings.push({
|
|
80
|
+
severity: 'warning',
|
|
81
|
+
code: 'slope-too-steep',
|
|
82
|
+
message: `${inchLabel(node.diameter)} drain slopes ${slopePerFootLabel(slope)} — over one pipe-diameter per foot risks siphoning the traps.`,
|
|
83
|
+
nodeIds: [node.id],
|
|
84
|
+
});
|
|
85
|
+
flaggedSteep = true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// ── Component-scoped trap rules ──────────────────────────────────
|
|
90
|
+
for (const component of buildPortComponents(nodes)) {
|
|
91
|
+
const traps = [];
|
|
92
|
+
for (const id of component) {
|
|
93
|
+
const node = nodes[id];
|
|
94
|
+
if (!node)
|
|
95
|
+
continue;
|
|
96
|
+
if (node.type === 'pipe-trap') {
|
|
97
|
+
traps.push(node);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Trap-arm developed length: trap outlet → its vent, capped by size.
|
|
101
|
+
// Independent of waste segments — a trap on its own can already be
|
|
102
|
+
// over-armed.
|
|
103
|
+
for (const trap of traps) {
|
|
104
|
+
const t = trap;
|
|
105
|
+
const armLengthM = t.armLengthM ?? 0;
|
|
106
|
+
const maxArm = trapArmMaxFor(t.diameter);
|
|
107
|
+
if (armLengthM > maxArm + 1e-6) {
|
|
108
|
+
findings.push({
|
|
109
|
+
severity: 'error',
|
|
110
|
+
code: 'trap-arm-too-long',
|
|
111
|
+
message: `${inchLabel(t.diameter)} trap arm runs ${(armLengthM / 0.0254).toFixed(0)}" to its vent — IPC 909.1 caps it at ${(maxArm / 0.0254).toFixed(0)}".`,
|
|
112
|
+
nodeIds: [t.id],
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return findings;
|
|
118
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-actions.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-actions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"node-actions.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-actions.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EASf,MAAM,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAG9C,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,CAAA;AAC3D,KAAK,YAAY,GAAG;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAA;AAC7D,KAAK,YAAY,GAAG,SAAS,CAAA;AA0vB7B,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,YAAY,EAAE,SAkDpB,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAA;CAAE,SAyIvF,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAAE,SAoFrD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,SAAS,EAAE,SA8JjB,CAAA"}
|