@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,141 @@
|
|
|
1
|
+
/** An opening's footprint in its host wall's local frame. */
|
|
2
|
+
export type OpeningSpan = {
|
|
3
|
+
id: string;
|
|
4
|
+
/** Centre along the wall, measured from `wall.start` (m). */
|
|
5
|
+
centerS: number;
|
|
6
|
+
/** Along-wall extent (m). */
|
|
7
|
+
width: number;
|
|
8
|
+
/** Vertical centre above the wall base (floor at y=0) (m). */
|
|
9
|
+
centerY: number;
|
|
10
|
+
/** Vertical extent (m). */
|
|
11
|
+
height: number;
|
|
12
|
+
};
|
|
13
|
+
export type WallExtent = {
|
|
14
|
+
/** Wall length (m). */
|
|
15
|
+
length: number;
|
|
16
|
+
/** Wall height (m). */
|
|
17
|
+
height: number;
|
|
18
|
+
};
|
|
19
|
+
export type OpeningGuideTolerances = {
|
|
20
|
+
/** Max distance for an edge/centre to count as aligned with a neighbour (m). */
|
|
21
|
+
align: number;
|
|
22
|
+
/** Max difference between two gaps for them to count as equal (m). */
|
|
23
|
+
equalSpacing: number;
|
|
24
|
+
/** Gaps below this are treated as touching/overlap noise and ignored (m). */
|
|
25
|
+
minGap: number;
|
|
26
|
+
};
|
|
27
|
+
export declare const DEFAULT_OPENING_GUIDE_TOLERANCES: OpeningGuideTolerances;
|
|
28
|
+
/** Which along-wall feature of an opening a guide references. */
|
|
29
|
+
export type AlongWallFeature = 'left' | 'center' | 'right';
|
|
30
|
+
/** Which vertical feature of an opening a guide references. */
|
|
31
|
+
export type VerticalFeature = 'sill' | 'center' | 'top';
|
|
32
|
+
export type SillHeadGuide = {
|
|
33
|
+
/** Floor (y=0) → the opening's bottom edge (m). */
|
|
34
|
+
sill: number;
|
|
35
|
+
/** Wall-local y of the bottom edge. */
|
|
36
|
+
bottomY: number;
|
|
37
|
+
/** The opening's top edge → the wall top (m). */
|
|
38
|
+
head: number;
|
|
39
|
+
/** Wall-local y of the top edge. */
|
|
40
|
+
topY: number;
|
|
41
|
+
};
|
|
42
|
+
export type EdgeGap = {
|
|
43
|
+
side: 'left' | 'right';
|
|
44
|
+
/** Clearance along the wall (m). */
|
|
45
|
+
distance: number;
|
|
46
|
+
/** Wall-local s of the moving opening's edge. */
|
|
47
|
+
fromS: number;
|
|
48
|
+
/** Wall-local s of the neighbour edge / wall end. */
|
|
49
|
+
toS: number;
|
|
50
|
+
target: 'opening' | 'wall-start' | 'wall-end';
|
|
51
|
+
/** Set when `target === 'opening'`. */
|
|
52
|
+
targetId?: string;
|
|
53
|
+
};
|
|
54
|
+
export type AlongWallAlignment = {
|
|
55
|
+
/** Wall-local s the two features share. */
|
|
56
|
+
s: number;
|
|
57
|
+
movingFeature: AlongWallFeature;
|
|
58
|
+
targetId: string;
|
|
59
|
+
targetFeature: AlongWallFeature;
|
|
60
|
+
/** Delta to add to the moving opening's `centerS` to make them coincide. */
|
|
61
|
+
snap: number;
|
|
62
|
+
};
|
|
63
|
+
export type VerticalAlignment = {
|
|
64
|
+
/** Wall-local y the two features share. */
|
|
65
|
+
y: number;
|
|
66
|
+
movingFeature: VerticalFeature;
|
|
67
|
+
targetId: string;
|
|
68
|
+
targetFeature: VerticalFeature;
|
|
69
|
+
/** Delta to add to the moving opening's `centerY` to make them coincide. */
|
|
70
|
+
snap: number;
|
|
71
|
+
};
|
|
72
|
+
export type EqualSpacingRun = {
|
|
73
|
+
/** The repeated gap value (average of the run's gaps) (m). */
|
|
74
|
+
gap: number;
|
|
75
|
+
/** The equal-gap segments along the wall, in order (left → right). */
|
|
76
|
+
segments: {
|
|
77
|
+
fromS: number;
|
|
78
|
+
toS: number;
|
|
79
|
+
}[];
|
|
80
|
+
/** Participating opening ids, ordered along the wall, including the moving one. */
|
|
81
|
+
openingIds: string[];
|
|
82
|
+
};
|
|
83
|
+
export type OpeningGuides = {
|
|
84
|
+
sillHead: SillHeadGuide | null;
|
|
85
|
+
gaps: EdgeGap[];
|
|
86
|
+
alongWall: AlongWallAlignment | null;
|
|
87
|
+
vertical: VerticalAlignment | null;
|
|
88
|
+
equalSpacing: EqualSpacingRun | null;
|
|
89
|
+
};
|
|
90
|
+
export type OpeningGuideInput = {
|
|
91
|
+
moving: OpeningSpan;
|
|
92
|
+
/** Other openings on the SAME wall (the moving opening excluded). */
|
|
93
|
+
siblings: readonly OpeningSpan[];
|
|
94
|
+
wall: WallExtent;
|
|
95
|
+
/**
|
|
96
|
+
* Whether to compute vertical (sill/head/vertical-alignment) guides. True for
|
|
97
|
+
* windows; false for doors, which sit on the floor so their sill is always 0.
|
|
98
|
+
*/
|
|
99
|
+
includeVertical: boolean;
|
|
100
|
+
tolerances?: Partial<OpeningGuideTolerances>;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Edge-to-edge clearance from the moving opening to the nearest neighbour on
|
|
104
|
+
* each side, falling back to the wall ends when there is no neighbour — the
|
|
105
|
+
* "how much wall is left here" reading. Returns 0–2 gaps (one per side); a side
|
|
106
|
+
* is omitted when its clearance is below `minGap` (the opening is flush against
|
|
107
|
+
* or overlapping that neighbour).
|
|
108
|
+
*/
|
|
109
|
+
export declare function computeEdgeGaps(moving: OpeningSpan, siblings: readonly OpeningSpan[], wall: WallExtent, minGap: number): EdgeGap[];
|
|
110
|
+
/**
|
|
111
|
+
* The closest coincidence between any of the moving opening's edges/centre and
|
|
112
|
+
* any sibling's edges/centre along the wall, within `tolerance`. Edge-to-edge
|
|
113
|
+
* and centre-to-centre are weighed equally; the single closest pair wins
|
|
114
|
+
* (matching the one-guide-per-axis behaviour of the floor-plane resolver).
|
|
115
|
+
*/
|
|
116
|
+
export declare function detectAlongWallAlignment(moving: OpeningSpan, siblings: readonly OpeningSpan[], tolerance: number): AlongWallAlignment | null;
|
|
117
|
+
/**
|
|
118
|
+
* The closest coincidence between the moving opening's sill/centre/top and any
|
|
119
|
+
* sibling's sill/centre/top, within `tolerance` — the "these two windows share
|
|
120
|
+
* a sill height" detector. Same single-best-match policy as the along-wall
|
|
121
|
+
* variant.
|
|
122
|
+
*/
|
|
123
|
+
export declare function detectVerticalAlignment(moving: OpeningSpan, siblings: readonly OpeningSpan[], tolerance: number): VerticalAlignment | null;
|
|
124
|
+
/**
|
|
125
|
+
* Figma-style equal-spacing detection: order all openings along the wall, look
|
|
126
|
+
* at the clearances BETWEEN consecutive openings, and return the longest run of
|
|
127
|
+
* ≥2 consecutive gaps that are equal within `tolerance` and that the moving
|
|
128
|
+
* opening participates in (so the badges only appear while the drag is actually
|
|
129
|
+
* forming or extending a series). Returns null when no such run exists.
|
|
130
|
+
*
|
|
131
|
+
* Gaps below `minGap` (touching/overlapping openings) break a run — a row of
|
|
132
|
+
* flush openings is not "equally spaced".
|
|
133
|
+
*/
|
|
134
|
+
export declare function detectEqualSpacing(allOpenings: readonly OpeningSpan[], movingId: string, tolerance: number, minGap: number): EqualSpacingRun | null;
|
|
135
|
+
/**
|
|
136
|
+
* Compute every proximity/alignment guide for the moving opening in one pass.
|
|
137
|
+
* Pure: feed it the moving opening's wall-local span, its same-wall siblings,
|
|
138
|
+
* and the wall extent; render the result in whichever view.
|
|
139
|
+
*/
|
|
140
|
+
export declare function computeOpeningGuides(input: OpeningGuideInput): OpeningGuides;
|
|
141
|
+
//# sourceMappingURL=opening-guides.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opening-guides.d.ts","sourceRoot":"","sources":["../../src/services/opening-guides.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// Proximity / alignment guides for wall-hosted openings (doors, windows).
|
|
2
|
+
//
|
|
3
|
+
// Pure geometry over a single host wall's LOCAL frame — no Three.js, no scene
|
|
4
|
+
// store, no React — so it runs identically for the 3D viewport and the 2D
|
|
5
|
+
// floor plan and is unit-testable in isolation. Callers extract the spans from
|
|
6
|
+
// the scene graph (an opening's `position[0]` is its along-wall centre, its
|
|
7
|
+
// `position[1]` its vertical centre with the wall base at y=0) and feed them in;
|
|
8
|
+
// the renderers transform the returned wall-local coordinates back to world
|
|
9
|
+
// (3D) or plan (2D).
|
|
10
|
+
//
|
|
11
|
+
// What it produces, mirroring the affordances architects expect (and Figma's
|
|
12
|
+
// smart guides):
|
|
13
|
+
// - sill/head : a window's bottom edge → floor and top edge → wall top.
|
|
14
|
+
// - edge gaps : along-wall clearance to the nearest neighbour opening (or
|
|
15
|
+
// the wall end) on each side.
|
|
16
|
+
// - alongWall : the moving opening's edge/centre lining up with a
|
|
17
|
+
// neighbour's edge/centre along the wall.
|
|
18
|
+
// - vertical : two openings sharing a sill / head / vertical centre.
|
|
19
|
+
// - equalSpacing : a run of 3+ openings with (near-)equal gaps between them.
|
|
20
|
+
//
|
|
21
|
+
// Detection is passive — it reports what currently coincides within tolerance
|
|
22
|
+
// and the snap delta that would make it exact, leaving the snap decision to the
|
|
23
|
+
// caller's manipulation policy (grid vs. alignment vs. Shift bypass).
|
|
24
|
+
export const DEFAULT_OPENING_GUIDE_TOLERANCES = {
|
|
25
|
+
// Parity with the along-wall snap threshold (`ALONG_WALL_ALIGN_THRESHOLD_M`).
|
|
26
|
+
align: 0.08,
|
|
27
|
+
equalSpacing: 0.03,
|
|
28
|
+
minGap: 0.02,
|
|
29
|
+
};
|
|
30
|
+
const leftEdge = (s) => s.centerS - s.width / 2;
|
|
31
|
+
const rightEdge = (s) => s.centerS + s.width / 2;
|
|
32
|
+
const bottomEdge = (s) => s.centerY - s.height / 2;
|
|
33
|
+
const topEdge = (s) => s.centerY + s.height / 2;
|
|
34
|
+
function alongWallFeatureCoord(s, feature) {
|
|
35
|
+
if (feature === 'left')
|
|
36
|
+
return leftEdge(s);
|
|
37
|
+
if (feature === 'right')
|
|
38
|
+
return rightEdge(s);
|
|
39
|
+
return s.centerS;
|
|
40
|
+
}
|
|
41
|
+
function verticalFeatureCoord(s, feature) {
|
|
42
|
+
if (feature === 'sill')
|
|
43
|
+
return bottomEdge(s);
|
|
44
|
+
if (feature === 'top')
|
|
45
|
+
return topEdge(s);
|
|
46
|
+
return s.centerY;
|
|
47
|
+
}
|
|
48
|
+
const ALONG_WALL_FEATURES = ['left', 'center', 'right'];
|
|
49
|
+
const VERTICAL_FEATURES = ['sill', 'center', 'top'];
|
|
50
|
+
/**
|
|
51
|
+
* Edge-to-edge clearance from the moving opening to the nearest neighbour on
|
|
52
|
+
* each side, falling back to the wall ends when there is no neighbour — the
|
|
53
|
+
* "how much wall is left here" reading. Returns 0–2 gaps (one per side); a side
|
|
54
|
+
* is omitted when its clearance is below `minGap` (the opening is flush against
|
|
55
|
+
* or overlapping that neighbour).
|
|
56
|
+
*/
|
|
57
|
+
export function computeEdgeGaps(moving, siblings, wall, minGap) {
|
|
58
|
+
const movingLeft = leftEdge(moving);
|
|
59
|
+
const movingRight = rightEdge(moving);
|
|
60
|
+
// A sibling that straddles one of the moving opening's edges is an OVERLAP,
|
|
61
|
+
// not a neighbour: there is no clearance on that side, and we must not fall
|
|
62
|
+
// back to the wall end (which would report a misleading distance measured
|
|
63
|
+
// "through" the overlapping opening).
|
|
64
|
+
const leftCrossed = siblings.some((s) => leftEdge(s) < movingLeft && rightEdge(s) > movingLeft);
|
|
65
|
+
const rightCrossed = siblings.some((s) => leftEdge(s) < movingRight && rightEdge(s) > movingRight);
|
|
66
|
+
let leftNeighbour = null;
|
|
67
|
+
let rightNeighbour = null;
|
|
68
|
+
for (const sib of siblings) {
|
|
69
|
+
const sibRight = rightEdge(sib);
|
|
70
|
+
const sibLeft = leftEdge(sib);
|
|
71
|
+
// Entirely to the left of the moving opening → candidate left neighbour.
|
|
72
|
+
if (sibRight <= movingLeft && (leftNeighbour === null || sibRight > leftNeighbour.s)) {
|
|
73
|
+
leftNeighbour = { s: sibRight, id: sib.id };
|
|
74
|
+
}
|
|
75
|
+
// Entirely to the right → candidate right neighbour.
|
|
76
|
+
if (sibLeft >= movingRight && (rightNeighbour === null || sibLeft < rightNeighbour.s)) {
|
|
77
|
+
rightNeighbour = { s: sibLeft, id: sib.id };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const gaps = [];
|
|
81
|
+
if (!leftCrossed) {
|
|
82
|
+
const leftToS = leftNeighbour ? leftNeighbour.s : 0;
|
|
83
|
+
const leftDistance = movingLeft - leftToS;
|
|
84
|
+
if (leftDistance >= minGap) {
|
|
85
|
+
gaps.push({
|
|
86
|
+
side: 'left',
|
|
87
|
+
distance: leftDistance,
|
|
88
|
+
fromS: movingLeft,
|
|
89
|
+
toS: leftToS,
|
|
90
|
+
target: leftNeighbour ? 'opening' : 'wall-start',
|
|
91
|
+
targetId: leftNeighbour?.id,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (!rightCrossed) {
|
|
96
|
+
const rightToS = rightNeighbour ? rightNeighbour.s : wall.length;
|
|
97
|
+
const rightDistance = rightToS - movingRight;
|
|
98
|
+
if (rightDistance >= minGap) {
|
|
99
|
+
gaps.push({
|
|
100
|
+
side: 'right',
|
|
101
|
+
distance: rightDistance,
|
|
102
|
+
fromS: movingRight,
|
|
103
|
+
toS: rightToS,
|
|
104
|
+
target: rightNeighbour ? 'opening' : 'wall-end',
|
|
105
|
+
targetId: rightNeighbour?.id,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return gaps;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The closest coincidence between any of the moving opening's edges/centre and
|
|
113
|
+
* any sibling's edges/centre along the wall, within `tolerance`. Edge-to-edge
|
|
114
|
+
* and centre-to-centre are weighed equally; the single closest pair wins
|
|
115
|
+
* (matching the one-guide-per-axis behaviour of the floor-plane resolver).
|
|
116
|
+
*/
|
|
117
|
+
export function detectAlongWallAlignment(moving, siblings, tolerance) {
|
|
118
|
+
let best = null;
|
|
119
|
+
let bestAbs = tolerance;
|
|
120
|
+
for (const movingFeature of ALONG_WALL_FEATURES) {
|
|
121
|
+
const movingCoord = alongWallFeatureCoord(moving, movingFeature);
|
|
122
|
+
for (const sib of siblings) {
|
|
123
|
+
if (sib.id === moving.id)
|
|
124
|
+
continue;
|
|
125
|
+
for (const targetFeature of ALONG_WALL_FEATURES) {
|
|
126
|
+
const targetCoord = alongWallFeatureCoord(sib, targetFeature);
|
|
127
|
+
const diff = targetCoord - movingCoord;
|
|
128
|
+
const abs = Math.abs(diff);
|
|
129
|
+
if (abs <= bestAbs && (best === null || abs < bestAbs)) {
|
|
130
|
+
bestAbs = abs;
|
|
131
|
+
best = {
|
|
132
|
+
s: targetCoord,
|
|
133
|
+
movingFeature,
|
|
134
|
+
targetId: sib.id,
|
|
135
|
+
targetFeature,
|
|
136
|
+
snap: diff,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return best;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The closest coincidence between the moving opening's sill/centre/top and any
|
|
146
|
+
* sibling's sill/centre/top, within `tolerance` — the "these two windows share
|
|
147
|
+
* a sill height" detector. Same single-best-match policy as the along-wall
|
|
148
|
+
* variant.
|
|
149
|
+
*/
|
|
150
|
+
export function detectVerticalAlignment(moving, siblings, tolerance) {
|
|
151
|
+
let best = null;
|
|
152
|
+
let bestAbs = tolerance;
|
|
153
|
+
for (const movingFeature of VERTICAL_FEATURES) {
|
|
154
|
+
const movingCoord = verticalFeatureCoord(moving, movingFeature);
|
|
155
|
+
for (const sib of siblings) {
|
|
156
|
+
if (sib.id === moving.id)
|
|
157
|
+
continue;
|
|
158
|
+
for (const targetFeature of VERTICAL_FEATURES) {
|
|
159
|
+
const targetCoord = verticalFeatureCoord(sib, targetFeature);
|
|
160
|
+
const diff = targetCoord - movingCoord;
|
|
161
|
+
const abs = Math.abs(diff);
|
|
162
|
+
if (abs <= bestAbs && (best === null || abs < bestAbs)) {
|
|
163
|
+
bestAbs = abs;
|
|
164
|
+
best = {
|
|
165
|
+
y: targetCoord,
|
|
166
|
+
movingFeature,
|
|
167
|
+
targetId: sib.id,
|
|
168
|
+
targetFeature,
|
|
169
|
+
snap: diff,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return best;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Figma-style equal-spacing detection: order all openings along the wall, look
|
|
179
|
+
* at the clearances BETWEEN consecutive openings, and return the longest run of
|
|
180
|
+
* ≥2 consecutive gaps that are equal within `tolerance` and that the moving
|
|
181
|
+
* opening participates in (so the badges only appear while the drag is actually
|
|
182
|
+
* forming or extending a series). Returns null when no such run exists.
|
|
183
|
+
*
|
|
184
|
+
* Gaps below `minGap` (touching/overlapping openings) break a run — a row of
|
|
185
|
+
* flush openings is not "equally spaced".
|
|
186
|
+
*/
|
|
187
|
+
export function detectEqualSpacing(allOpenings, movingId, tolerance, minGap) {
|
|
188
|
+
if (allOpenings.length < 3)
|
|
189
|
+
return null;
|
|
190
|
+
const sorted = [...allOpenings].sort((a, b) => a.centerS - b.centerS);
|
|
191
|
+
const movingIndex = sorted.findIndex((s) => s.id === movingId);
|
|
192
|
+
if (movingIndex < 0)
|
|
193
|
+
return null;
|
|
194
|
+
// Clearance between opening i and i+1.
|
|
195
|
+
const gaps = [];
|
|
196
|
+
for (let i = 0; i < sorted.length - 1; i++) {
|
|
197
|
+
const a = sorted[i];
|
|
198
|
+
const b = sorted[i + 1];
|
|
199
|
+
if (!a || !b)
|
|
200
|
+
continue;
|
|
201
|
+
const fromS = rightEdge(a);
|
|
202
|
+
const toS = leftEdge(b);
|
|
203
|
+
gaps.push({ value: toS - fromS, fromS, toS });
|
|
204
|
+
}
|
|
205
|
+
// Longest contiguous window of gaps that are (a) each ≥ minGap and (b)
|
|
206
|
+
// mutually equal within tolerance (window max − min ≤ tolerance), spanning at
|
|
207
|
+
// least 2 gaps and including the moving opening. Brute force over windows
|
|
208
|
+
// (openings per wall are few). A first-gap-anchored greedy scan is NOT
|
|
209
|
+
// equivalent: it drops a valid run that begins partway through a drifting
|
|
210
|
+
// sequence — e.g. gaps 1.00, 1.02, 1.04 with the moving opening at the end,
|
|
211
|
+
// where [1.02, 1.04] is a real run. On a length tie the leftmost window wins,
|
|
212
|
+
// for determinism.
|
|
213
|
+
let best = null;
|
|
214
|
+
for (let lo = 0; lo < gaps.length; lo++) {
|
|
215
|
+
let min = Number.POSITIVE_INFINITY;
|
|
216
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
217
|
+
for (let hi = lo; hi < gaps.length; hi++) {
|
|
218
|
+
const gap = gaps[hi];
|
|
219
|
+
if (!gap || gap.value < minGap)
|
|
220
|
+
break; // a sub-minGap gap can't join a run
|
|
221
|
+
min = Math.min(min, gap.value);
|
|
222
|
+
max = Math.max(max, gap.value);
|
|
223
|
+
if (max - min > tolerance)
|
|
224
|
+
break; // extending only widens the spread
|
|
225
|
+
const gapCount = hi - lo + 1;
|
|
226
|
+
if (gapCount < 2)
|
|
227
|
+
continue;
|
|
228
|
+
const firstOpening = lo; // gap i sits between openings i and i+1
|
|
229
|
+
const lastOpening = hi + 1;
|
|
230
|
+
if (movingIndex < firstOpening || movingIndex > lastOpening)
|
|
231
|
+
continue;
|
|
232
|
+
if (best !== null && gapCount <= best.segments.length)
|
|
233
|
+
continue;
|
|
234
|
+
const windowGaps = gaps.slice(lo, hi + 1);
|
|
235
|
+
best = {
|
|
236
|
+
gap: windowGaps.reduce((sum, g) => sum + g.value, 0) / windowGaps.length,
|
|
237
|
+
segments: windowGaps.map((g) => ({ fromS: g.fromS, toS: g.toS })),
|
|
238
|
+
openingIds: sorted.slice(firstOpening, lastOpening + 1).map((s) => s.id),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return best;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Compute every proximity/alignment guide for the moving opening in one pass.
|
|
246
|
+
* Pure: feed it the moving opening's wall-local span, its same-wall siblings,
|
|
247
|
+
* and the wall extent; render the result in whichever view.
|
|
248
|
+
*/
|
|
249
|
+
export function computeOpeningGuides(input) {
|
|
250
|
+
const tol = { ...DEFAULT_OPENING_GUIDE_TOLERANCES, ...input.tolerances };
|
|
251
|
+
const { moving, siblings, wall, includeVertical } = input;
|
|
252
|
+
const sillHead = includeVertical
|
|
253
|
+
? {
|
|
254
|
+
sill: bottomEdge(moving),
|
|
255
|
+
bottomY: bottomEdge(moving),
|
|
256
|
+
head: wall.height - topEdge(moving),
|
|
257
|
+
topY: topEdge(moving),
|
|
258
|
+
}
|
|
259
|
+
: null;
|
|
260
|
+
return {
|
|
261
|
+
sillHead,
|
|
262
|
+
gaps: computeEdgeGaps(moving, siblings, wall, tol.minGap),
|
|
263
|
+
alongWall: detectAlongWallAlignment(moving, siblings, tol.align),
|
|
264
|
+
vertical: includeVertical ? detectVerticalAlignment(moving, siblings, tol.align) : null,
|
|
265
|
+
equalSpacing: detectEqualSpacing([moving, ...siblings], moving.id, tol.equalSpacing, tol.minGap),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { AnyNode, AnyNodeId } from '../schema';
|
|
2
|
+
/**
|
|
3
|
+
* Connectivity-aware editing for port-bearing distribution kinds
|
|
4
|
+
* (HVAC ductwork AND DWV plumbing).
|
|
5
|
+
*
|
|
6
|
+
* Two nodes are "connected" when a port of one coincides in space with a
|
|
7
|
+
* port of the other — exactly how the placement tools mate a fitting onto
|
|
8
|
+
* a duct end (they snap the fitting's collar onto the run's open port).
|
|
9
|
+
* This service reads that relationship back out so an edit to one node can
|
|
10
|
+
* carry its neighbours along.
|
|
11
|
+
*
|
|
12
|
+
* Pure logic: it asks each node for its ports via `def.ports` (level-local
|
|
13
|
+
* meters) and does arithmetic. No Three.js, no rendering — it lives in
|
|
14
|
+
* core and is consumed by the editor's move tool and the duct/pipe
|
|
15
|
+
* selection affordances alike.
|
|
16
|
+
*
|
|
17
|
+
* ## Propagation model
|
|
18
|
+
*
|
|
19
|
+
* The joint graph is snapshotted once at drag start (`analyzePortConnectivity`)
|
|
20
|
+
* and walked every frame (`resolveConnectivityUpdates`) given the moved node's
|
|
21
|
+
* live transform. Deltas flow outward from the moved node through coincident
|
|
22
|
+
* ports:
|
|
23
|
+
*
|
|
24
|
+
* - **Fitting** (rigid): a collar pushed by delta `d` translates the whole
|
|
25
|
+
* fitting by `d`; every other collar carries that same `d` onward.
|
|
26
|
+
* - **Run** (stretch + slide, never skew): an endpoint pushed by delta `d` is
|
|
27
|
+
* split against the run's own axis. The *parallel* part slides only that
|
|
28
|
+
* endpoint (the run lengthens / shortens); the *perpendicular* part
|
|
29
|
+
* translates the entire run (so its direction is preserved). The far
|
|
30
|
+
* endpoint therefore moves by just the perpendicular part, and that part
|
|
31
|
+
* propagates onward to whatever is mated to the far endpoint.
|
|
32
|
+
*
|
|
33
|
+
* Propagation walks the whole connected component so a joint stays welded all
|
|
34
|
+
* the way down the chain, with a visited guard so cycles (looped runs) and
|
|
35
|
+
* shared joints terminate. First-reached (shortest path) wins on a node
|
|
36
|
+
* reachable two ways.
|
|
37
|
+
*/
|
|
38
|
+
type Point = readonly [number, number, number];
|
|
39
|
+
/** A node carried by the edit, plus the snapshot needed to revert it. Kept
|
|
40
|
+
* deliberately small: the move tools read only `kind` + `nodeId` and the
|
|
41
|
+
* matching start snapshot to revert before the single tracked commit. */
|
|
42
|
+
export type PortConnection = {
|
|
43
|
+
/** A fitting mated collar-to-collar: it translates rigidly. */
|
|
44
|
+
kind: 'rigid-node';
|
|
45
|
+
nodeId: AnyNodeId;
|
|
46
|
+
/** Node's `position` at edit-start. */
|
|
47
|
+
startPosition: Point;
|
|
48
|
+
} | {
|
|
49
|
+
/** A run whose endpoint(s) ride the edit: it stretches and/or
|
|
50
|
+
* translates, never skews. */
|
|
51
|
+
kind: 'run';
|
|
52
|
+
nodeId: AnyNodeId;
|
|
53
|
+
/** The run's full `path` at edit-start. */
|
|
54
|
+
startPath: Point[];
|
|
55
|
+
};
|
|
56
|
+
/** One node in the snapshotted joint graph (everything reachable from the
|
|
57
|
+
* moved node, excluding the moved node itself). */
|
|
58
|
+
type GraphNode = {
|
|
59
|
+
id: AnyNodeId;
|
|
60
|
+
role: 'run' | 'fitting';
|
|
61
|
+
ports: ReadonlyArray<{
|
|
62
|
+
id: string;
|
|
63
|
+
position: Point;
|
|
64
|
+
system?: string;
|
|
65
|
+
}>;
|
|
66
|
+
startPath?: Point[];
|
|
67
|
+
startPosition?: Point;
|
|
68
|
+
};
|
|
69
|
+
/** Who else sits on a given node's port, keyed `nodeId` → `portId` → mates. */
|
|
70
|
+
type Adjacency = Record<string, Record<string, Array<{
|
|
71
|
+
nodeId: AnyNodeId;
|
|
72
|
+
portId: string;
|
|
73
|
+
}>>>;
|
|
74
|
+
export type PortConnectivity = {
|
|
75
|
+
movedNodeId: AnyNodeId;
|
|
76
|
+
/** The moved node's port world positions at edit-start, keyed by port id —
|
|
77
|
+
* the reference each frame's delta is measured from. */
|
|
78
|
+
startMovedPorts: Record<string, Point>;
|
|
79
|
+
/** Reachable run/fitting nodes (excludes the moved node), keyed by id. */
|
|
80
|
+
graph: Record<string, GraphNode>;
|
|
81
|
+
/** Port coincidence edges across the moved node + every graph node. */
|
|
82
|
+
adjacency: Adjacency;
|
|
83
|
+
/** Flat list of carried nodes for the move tools' revert + "anything to
|
|
84
|
+
* follow?" check. Derived from `graph`. */
|
|
85
|
+
connections: PortConnection[];
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Snapshot the joint graph reachable from `movedNode`'s ports, taken at the
|
|
89
|
+
* start of a move/resize. Call once before the drag; feed the result to
|
|
90
|
+
* `resolveConnectivityUpdates` on every frame.
|
|
91
|
+
*
|
|
92
|
+
* Only `run`-role partners (segments) and `fitting`-role partners are walked —
|
|
93
|
+
* terminals and equipment usually mount to a surface and shouldn't be yanked
|
|
94
|
+
* off it when an adjacent fitting nudges. Fittings that declare
|
|
95
|
+
* `portConnectivityFollow: false` are anchored fixtures (e.g. pipe-trap) and
|
|
96
|
+
* are skipped, so a connected run stretches against them instead.
|
|
97
|
+
*/
|
|
98
|
+
export declare function analyzePortConnectivity(movedNode: AnyNode, nodes: Record<string, AnyNode>): PortConnectivity;
|
|
99
|
+
/**
|
|
100
|
+
* Given the moved node in its live (in-drag) transform, produce the patches
|
|
101
|
+
* that keep every connected node attached. `previewNode` is the moved node
|
|
102
|
+
* with its current drag position/rotation applied so its ports recompute.
|
|
103
|
+
*
|
|
104
|
+
* Walks the snapshotted graph, propagating each port delta outward: fittings
|
|
105
|
+
* translate rigidly, runs stretch along their axis and translate across it
|
|
106
|
+
* (never skew when driven from one end), and effective port movement carries on
|
|
107
|
+
* to neighbouring joints. Port-level output guards bound cycles while still
|
|
108
|
+
* allowing a looped/shared run to accept constraints at both endpoints.
|
|
109
|
+
*/
|
|
110
|
+
export declare function resolveConnectivityUpdates(connectivity: PortConnectivity, previewNode: AnyNode): {
|
|
111
|
+
id: AnyNodeId;
|
|
112
|
+
data: Partial<AnyNode>;
|
|
113
|
+
}[];
|
|
114
|
+
export {};
|
|
115
|
+
//# sourceMappingURL=port-connectivity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port-connectivity.d.ts","sourceRoot":"","sources":["../../src/services/port-connectivity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,KAAK,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAY9C;;0EAE0E;AAC1E,MAAM,MAAM,cAAc,GACtB;IACE,+DAA+D;IAC/D,IAAI,EAAE,YAAY,CAAA;IAClB,MAAM,EAAE,SAAS,CAAA;IACjB,uCAAuC;IACvC,aAAa,EAAE,KAAK,CAAA;CACrB,GACD;IACE;mCAC+B;IAC/B,IAAI,EAAE,KAAK,CAAA;IACX,MAAM,EAAE,SAAS,CAAA;IACjB,2CAA2C;IAC3C,SAAS,EAAE,KAAK,EAAE,CAAA;CACnB,CAAA;AAEL;oDACoD;AACpD,KAAK,SAAS,GAAG;IACf,EAAE,EAAE,SAAS,CAAA;IACb,IAAI,EAAE,KAAK,GAAG,SAAS,CAAA;IACvB,KAAK,EAAE,aAAa,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,KAAK,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtE,SAAS,CAAC,EAAE,KAAK,EAAE,CAAA;IACnB,aAAa,CAAC,EAAE,KAAK,CAAA;CACtB,CAAA;AAED,+EAA+E;AAC/E,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAAC,CAAA;AAE7F,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,SAAS,CAAA;IACtB;6DACyD;IACzD,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IACtC,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAChC,uEAAuE;IACvE,SAAS,EAAE,SAAS,CAAA;IACpB;gDAC4C;IAC5C,WAAW,EAAE,cAAc,EAAE,CAAA;CAC9B,CAAA;AAkCD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,OAAO,EAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,gBAAgB,CAuFlB;AA4GD;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,gBAAgB,EAC9B,WAAW,EAAE,OAAO,GACnB;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAAE,CA0F7C"}
|