@pascal-app/core 0.8.0 → 0.9.2
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/dist/events/bus.d.ts +67 -13
- 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 +14 -23
- package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
- package/dist/hooks/scene-registry/scene-registry.js +59 -26
- package/dist/hooks/scene-registry/scene-registry.test.d.ts +2 -0
- package/dist/hooks/scene-registry/scene-registry.test.d.ts.map +1 -0
- package/dist/hooks/scene-registry/scene-registry.test.js +26 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +366 -0
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +79 -11
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-manager.js +508 -141
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.js +109 -18
- package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts +2 -0
- package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/wall-slab-overlap.test.js +363 -0
- package/dist/index.d.ts +36 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -6
- 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 +19 -0
- package/dist/lib/measurement-geometry.d.ts.map +1 -0
- package/dist/lib/measurement-geometry.js +232 -0
- package/dist/lib/measurement-geometry.test.d.ts +2 -0
- package/dist/lib/measurement-geometry.test.d.ts.map +1 -0
- package/dist/lib/measurement-geometry.test.js +102 -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/polygon-relations.d.ts +10 -0
- package/dist/lib/polygon-relations.d.ts.map +1 -0
- package/dist/lib/polygon-relations.js +73 -0
- 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 +525 -31
- package/dist/lib/slab-polygon.test.d.ts +2 -0
- package/dist/lib/slab-polygon.test.d.ts.map +1 -0
- package/dist/lib/slab-polygon.test.js +635 -0
- 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-pause.test.d.ts +2 -0
- package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
- package/dist/lib/space-detection-pause.test.js +42 -0
- package/dist/lib/space-detection.d.ts +54 -2
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +599 -124
- package/dist/lib/space-detection.test.d.ts +2 -0
- package/dist/lib/space-detection.test.d.ts.map +1 -0
- package/dist/lib/space-detection.test.js +376 -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 +183 -0
- package/dist/lib/zone-quantities.test.d.ts +2 -0
- package/dist/lib/zone-quantities.test.d.ts.map +1 -0
- package/dist/lib/zone-quantities.test.js +82 -0
- package/dist/material-library.d.ts +20 -1
- package/dist/material-library.d.ts.map +1 -1
- package/dist/material-library.js +3737 -200
- package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
- package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
- package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
- package/dist/registry/handles.d.ts +394 -0
- package/dist/registry/handles.d.ts.map +1 -0
- package/dist/registry/handles.js +24 -0
- package/dist/registry/index.d.ts +7 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +4 -0
- package/dist/registry/registry.d.ts +136 -0
- package/dist/registry/registry.d.ts.map +1 -0
- package/dist/registry/registry.js +282 -0
- package/dist/registry/registry.test.d.ts +2 -0
- package/dist/registry/registry.test.d.ts.map +1 -0
- package/dist/registry/registry.test.js +196 -0
- package/dist/registry/relations-resolver.d.ts +42 -0
- package/dist/registry/relations-resolver.d.ts.map +1 -0
- package/dist/registry/relations-resolver.js +77 -0
- package/dist/registry/relations-resolver.test.d.ts +2 -0
- package/dist/registry/relations-resolver.test.d.ts.map +1 -0
- package/dist/registry/relations-resolver.test.js +183 -0
- package/dist/registry/scene-api.d.ts +44 -0
- package/dist/registry/scene-api.d.ts.map +1 -0
- package/dist/registry/scene-api.js +111 -0
- package/dist/registry/scene-api.test.d.ts +2 -0
- package/dist/registry/scene-api.test.d.ts.map +1 -0
- package/dist/registry/scene-api.test.js +183 -0
- package/dist/registry/subtree.d.ts +67 -0
- package/dist/registry/subtree.d.ts.map +1 -0
- package/dist/registry/subtree.js +123 -0
- package/dist/registry/subtree.test.d.ts +2 -0
- package/dist/registry/subtree.test.d.ts.map +1 -0
- package/dist/registry/subtree.test.js +163 -0
- package/dist/registry/types.d.ts +2099 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +1 -0
- package/dist/schema/index.d.ts +35 -4
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +32 -4
- package/dist/schema/material.d.ts +13 -2
- package/dist/schema/material.d.ts.map +1 -1
- package/dist/schema/material.js +17 -5
- package/dist/schema/material.test.d.ts +2 -0
- package/dist/schema/material.test.d.ts.map +1 -0
- package/dist/schema/material.test.js +43 -0
- package/dist/schema/nodes/box-vent.d.ts +74 -0
- package/dist/schema/nodes/box-vent.d.ts.map +1 -0
- package/dist/schema/nodes/box-vent.js +63 -0
- package/dist/schema/nodes/building.d.ts +1 -1
- package/dist/schema/nodes/building.d.ts.map +1 -1
- package/dist/schema/nodes/building.js +3 -2
- package/dist/schema/nodes/cabinet.d.ts +534 -0
- package/dist/schema/nodes/cabinet.d.ts.map +1 -0
- package/dist/schema/nodes/cabinet.js +137 -0
- package/dist/schema/nodes/ceiling.d.ts +5 -2
- package/dist/schema/nodes/ceiling.d.ts.map +1 -1
- package/dist/schema/nodes/ceiling.js +5 -1
- package/dist/schema/nodes/chimney.d.ts +158 -0
- package/dist/schema/nodes/chimney.d.ts.map +1 -0
- package/dist/schema/nodes/chimney.js +67 -0
- package/dist/schema/nodes/column.d.ts +600 -7
- package/dist/schema/nodes/column.d.ts.map +1 -1
- package/dist/schema/nodes/column.js +589 -6
- package/dist/schema/nodes/cupola.d.ts +66 -0
- package/dist/schema/nodes/cupola.d.ts.map +1 -0
- package/dist/schema/nodes/cupola.js +28 -0
- package/dist/schema/nodes/door.d.ts +13 -5
- package/dist/schema/nodes/door.d.ts.map +1 -1
- package/dist/schema/nodes/door.js +12 -0
- package/dist/schema/nodes/dormer.d.ts +228 -0
- package/dist/schema/nodes/dormer.d.ts.map +1 -0
- package/dist/schema/nodes/dormer.js +119 -0
- package/dist/schema/nodes/downspout.d.ts +75 -0
- package/dist/schema/nodes/downspout.d.ts.map +1 -0
- package/dist/schema/nodes/downspout.js +63 -0
- 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 +57 -0
- package/dist/schema/nodes/duct-terminal.d.ts.map +1 -0
- package/dist/schema/nodes/duct-terminal.js +51 -0
- package/dist/schema/nodes/elevator.d.ts +106 -0
- package/dist/schema/nodes/elevator.d.ts.map +1 -0
- package/dist/schema/nodes/elevator.js +57 -0
- package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
- package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
- package/dist/schema/nodes/eyebrow-vent.js +40 -0
- package/dist/schema/nodes/fence.d.ts +19 -2
- package/dist/schema/nodes/fence.d.ts.map +1 -1
- package/dist/schema/nodes/fence.js +27 -2
- package/dist/schema/nodes/gutter.d.ts +84 -0
- package/dist/schema/nodes/gutter.d.ts.map +1 -0
- package/dist/schema/nodes/gutter.js +76 -0
- package/dist/schema/nodes/hvac-equipment.d.ts +63 -0
- package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -0
- package/dist/schema/nodes/hvac-equipment.js +55 -0
- package/dist/schema/nodes/item.d.ts +12 -0
- package/dist/schema/nodes/item.d.ts.map +1 -1
- package/dist/schema/nodes/item.js +20 -0
- package/dist/schema/nodes/level.d.ts +1 -1
- package/dist/schema/nodes/level.d.ts.map +1 -1
- package/dist/schema/nodes/level.js +4 -0
- 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/measurement.test.d.ts +2 -0
- package/dist/schema/nodes/measurement.test.d.ts.map +1 -0
- package/dist/schema/nodes/measurement.test.js +139 -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-defaults.test.d.ts +2 -0
- package/dist/schema/nodes/ridge-vent-defaults.test.d.ts.map +1 -0
- package/dist/schema/nodes/ridge-vent-defaults.test.js +205 -0
- package/dist/schema/nodes/ridge-vent.d.ts +82 -0
- package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
- package/dist/schema/nodes/ridge-vent.js +248 -0
- 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-shape.test.d.ts +2 -0
- package/dist/schema/nodes/roof-segment-shape.test.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-shape.test.js +175 -0
- package/dist/schema/nodes/roof-segment-surface.test.d.ts +2 -0
- package/dist/schema/nodes/roof-segment-surface.test.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-surface.test.js +48 -0
- package/dist/schema/nodes/roof-segment-trim.test.d.ts +2 -0
- package/dist/schema/nodes/roof-segment-trim.test.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-trim.test.js +107 -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-walls.test.d.ts +2 -0
- package/dist/schema/nodes/roof-segment-walls.test.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-walls.test.js +92 -0
- package/dist/schema/nodes/roof-segment.d.ts +273 -3
- package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment.js +539 -3
- package/dist/schema/nodes/roof.d.ts +8 -8
- package/dist/schema/nodes/roof.d.ts.map +1 -1
- package/dist/schema/nodes/roof.js +4 -16
- package/dist/schema/nodes/shelf.d.ts +106 -0
- package/dist/schema/nodes/shelf.d.ts.map +1 -0
- package/dist/schema/nodes/shelf.js +90 -0
- package/dist/schema/nodes/site.d.ts +1 -122
- package/dist/schema/nodes/site.d.ts.map +1 -1
- package/dist/schema/nodes/site.js +2 -6
- package/dist/schema/nodes/skylight.d.ts +279 -0
- package/dist/schema/nodes/skylight.d.ts.map +1 -0
- package/dist/schema/nodes/skylight.js +152 -0
- package/dist/schema/nodes/slab.d.ts +5 -2
- package/dist/schema/nodes/slab.d.ts.map +1 -1
- package/dist/schema/nodes/slab.js +5 -1
- package/dist/schema/nodes/solar-panel.d.ts +117 -0
- package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
- package/dist/schema/nodes/solar-panel.js +47 -0
- package/dist/schema/nodes/stair-segment.d.ts +4 -4
- package/dist/schema/nodes/stair.d.ts +13 -12
- package/dist/schema/nodes/stair.d.ts.map +1 -1
- package/dist/schema/nodes/stair.js +3 -0
- package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
- package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
- package/dist/schema/nodes/surface-hole-metadata.js +3 -1
- package/dist/schema/nodes/turbine-vent.d.ts +68 -0
- package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
- package/dist/schema/nodes/turbine-vent.js +41 -0
- package/dist/schema/nodes/wall.d.ts +245 -6
- package/dist/schema/nodes/wall.d.ts.map +1 -1
- package/dist/schema/nodes/wall.js +257 -0
- package/dist/schema/nodes/wall.test.d.ts +2 -0
- package/dist/schema/nodes/wall.test.d.ts.map +1 -0
- package/dist/schema/nodes/wall.test.js +208 -0
- package/dist/schema/nodes/window.d.ts +11 -3
- package/dist/schema/nodes/window.d.ts.map +1 -1
- package/dist/schema/nodes/window.js +12 -0
- package/dist/schema/nodes/zone.d.ts +2 -0
- package/dist/schema/nodes/zone.d.ts.map +1 -1
- package/dist/schema/nodes/zone.js +6 -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/types.d.ts +2850 -655
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +49 -0
- package/dist/services/alignment-anchors.d.ts +100 -0
- package/dist/services/alignment-anchors.d.ts.map +1 -0
- package/dist/services/alignment-anchors.js +312 -0
- package/dist/services/alignment-anchors.test.d.ts +2 -0
- package/dist/services/alignment-anchors.test.d.ts.map +1 -0
- package/dist/services/alignment-anchors.test.js +383 -0
- package/dist/services/alignment.d.ts +131 -0
- package/dist/services/alignment.d.ts.map +1 -0
- package/dist/services/alignment.js +171 -0
- package/dist/services/alignment.test.d.ts +2 -0
- package/dist/services/alignment.test.d.ts.map +1 -0
- package/dist/services/alignment.test.js +92 -0
- package/dist/services/drag-session.d.ts +47 -0
- package/dist/services/drag-session.d.ts.map +1 -0
- package/dist/services/drag-session.js +100 -0
- package/dist/services/drag-session.test.d.ts +2 -0
- package/dist/services/drag-session.test.d.ts.map +1 -0
- package/dist/services/drag-session.test.js +219 -0
- package/dist/services/hosting.d.ts +88 -0
- package/dist/services/hosting.d.ts.map +1 -0
- package/dist/services/hosting.js +131 -0
- package/dist/services/hosting.test.d.ts +2 -0
- package/dist/services/hosting.test.d.ts.map +1 -0
- package/dist/services/hosting.test.js +230 -0
- package/dist/services/index.d.ts +13 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +12 -0
- package/dist/services/level-height.d.ts +28 -0
- package/dist/services/level-height.d.ts.map +1 -0
- package/dist/services/level-height.js +62 -0
- package/dist/services/movement.d.ts +48 -0
- package/dist/services/movement.d.ts.map +1 -0
- package/dist/services/movement.js +65 -0
- package/dist/services/movement.test.d.ts +2 -0
- package/dist/services/movement.test.d.ts.map +1 -0
- package/dist/services/movement.test.js +133 -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/opening-guides.test.d.ts +2 -0
- package/dist/services/opening-guides.test.d.ts.map +1 -0
- package/dist/services/opening-guides.test.js +208 -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/port-connectivity.test.d.ts +2 -0
- package/dist/services/port-connectivity.test.d.ts.map +1 -0
- package/dist/services/port-connectivity.test.js +297 -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/riser-diagram.test.d.ts +2 -0
- package/dist/services/riser-diagram.test.d.ts.map +1 -0
- package/dist/services/riser-diagram.test.js +66 -0
- package/dist/services/single-undo-dance.test.d.ts +2 -0
- package/dist/services/single-undo-dance.test.d.ts.map +1 -0
- package/dist/services/single-undo-dance.test.js +210 -0
- package/dist/services/snap.d.ts +80 -0
- package/dist/services/snap.d.ts.map +1 -0
- package/dist/services/snap.js +140 -0
- package/dist/services/snap.test.d.ts +2 -0
- package/dist/services/snap.test.d.ts.map +1 -0
- package/dist/services/snap.test.js +137 -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/system-graph.test.d.ts +2 -0
- package/dist/services/system-graph.test.d.ts.map +1 -0
- package/dist/services/system-graph.test.js +138 -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/services/validate-dwv.test.d.ts +2 -0
- package/dist/services/validate-dwv.test.d.ts.map +1 -0
- package/dist/services/validate-dwv.test.js +109 -0
- package/dist/solar-panel-presets.d.ts +17 -0
- package/dist/solar-panel-presets.d.ts.map +1 -0
- package/dist/solar-panel-presets.js +43 -0
- package/dist/store/actions/node-actions.d.ts +14 -2
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +585 -15
- package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
- package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
- package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
- package/dist/store/actions/reparent.test.d.ts +2 -0
- package/dist/store/actions/reparent.test.d.ts.map +1 -0
- package/dist/store/actions/reparent.test.js +212 -0
- package/dist/store/actions/ridge-vent-update.test.d.ts +2 -0
- package/dist/store/actions/ridge-vent-update.test.d.ts.map +1 -0
- package/dist/store/actions/ridge-vent-update.test.js +264 -0
- package/dist/store/history-control.d.ts +11 -0
- package/dist/store/history-control.d.ts.map +1 -1
- package/dist/store/history-control.js +33 -0
- package/dist/store/use-interactive.d.ts +39 -0
- package/dist/store/use-interactive.d.ts.map +1 -1
- package/dist/store/use-interactive.js +75 -0
- package/dist/store/use-live-node-overrides.d.ts +22 -0
- package/dist/store/use-live-node-overrides.d.ts.map +1 -0
- package/dist/store/use-live-node-overrides.js +59 -0
- package/dist/store/use-scene-dirty-tracking.test.d.ts +2 -0
- package/dist/store/use-scene-dirty-tracking.test.d.ts.map +1 -0
- package/dist/store/use-scene-dirty-tracking.test.js +68 -0
- package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
- package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
- package/dist/store/use-scene-elevator-migration.test.js +109 -0
- package/dist/store/use-scene-measurement-roundtrip.test.d.ts +2 -0
- package/dist/store/use-scene-measurement-roundtrip.test.d.ts.map +1 -0
- package/dist/store/use-scene-measurement-roundtrip.test.js +97 -0
- package/dist/store/use-scene-plugins.test.d.ts +2 -0
- package/dist/store/use-scene-plugins.test.d.ts.map +1 -0
- package/dist/store/use-scene-plugins.test.js +51 -0
- package/dist/store/use-scene-wall-slot-migration.test.d.ts +2 -0
- package/dist/store/use-scene-wall-slot-migration.test.d.ts.map +1 -0
- package/dist/store/use-scene-wall-slot-migration.test.js +210 -0
- package/dist/store/use-scene-window-migration.test.d.ts +2 -0
- package/dist/store/use-scene-window-migration.test.d.ts.map +1 -0
- package/dist/store/use-scene-window-migration.test.js +85 -0
- package/dist/store/use-scene.d.ts +28 -2
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +546 -49
- package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
- package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-dispatch.js +57 -0
- package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
- package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-geometry.js +50 -0
- package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
- package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-opening-sync.js +218 -0
- package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
- package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
- package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
- package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-opening-system.js +48 -0
- package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
- package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-runtime-system.js +8 -0
- package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
- package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-runtime.js +225 -0
- package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
- package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-runtime.test.js +64 -0
- package/dist/systems/elevator/elevator-service.d.ts +19 -0
- package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-service.js +111 -0
- 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/fence/fence-spline.test.d.ts +2 -0
- package/dist/systems/fence/fence-spline.test.d.ts.map +1 -0
- package/dist/systems/fence/fence-spline.test.js +136 -0
- package/dist/systems/stair/stair-footprint.d.ts +44 -0
- package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
- package/dist/systems/stair/stair-footprint.js +178 -0
- package/dist/systems/stair/stair-opening-preview.d.ts +2447 -0
- package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-preview.js +93 -0
- package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
- package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-preview.test.js +80 -0
- package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-sync.js +155 -147
- package/dist/systems/stair/stair-opening-sync.test.js +424 -2
- package/dist/systems/stair/stair-opening-system.d.ts +2 -0
- package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-system.js +104 -0
- package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
- package/dist/systems/wall/wall-mitering.js +38 -2
- package/dist/systems/wall/wall-mitering.test.d.ts +2 -0
- package/dist/systems/wall/wall-mitering.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-mitering.test.js +63 -0
- package/dist/systems/wall/wall-move.d.ts +48 -0
- package/dist/systems/wall/wall-move.d.ts.map +1 -0
- package/dist/systems/wall/wall-move.js +193 -0
- package/dist/utils/clone-scene-graph.d.ts +7 -4
- package/dist/utils/clone-scene-graph.d.ts.map +1 -1
- package/dist/utils/clone-scene-graph.js +29 -7
- package/dist/utils/clone-scene-graph.test.d.ts +2 -0
- package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
- package/dist/utils/clone-scene-graph.test.js +62 -0
- 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 +90 -0
- package/dist/utils/heal-scene-graph.test.d.ts +2 -0
- package/dist/utils/heal-scene-graph.test.d.ts.map +1 -0
- package/dist/utils/heal-scene-graph.test.js +88 -0
- package/dist/validation/validate-build-json.d.ts +44 -0
- package/dist/validation/validate-build-json.d.ts.map +1 -0
- package/dist/validation/validate-build-json.js +256 -0
- package/package.json +11 -4
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { calculateLevelMiters, getWallMiterBoundaryPoints } from './wall-mitering';
|
|
3
|
+
function wall(id, start, end) {
|
|
4
|
+
return {
|
|
5
|
+
id,
|
|
6
|
+
type: 'wall',
|
|
7
|
+
object: 'node',
|
|
8
|
+
visible: true,
|
|
9
|
+
parentId: 'level_test',
|
|
10
|
+
children: [],
|
|
11
|
+
start,
|
|
12
|
+
end,
|
|
13
|
+
thickness: 0.1,
|
|
14
|
+
height: 2.5,
|
|
15
|
+
frontSide: 'interior',
|
|
16
|
+
backSide: 'exterior',
|
|
17
|
+
metadata: {},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function maxBoundaryCoord(walls) {
|
|
21
|
+
const miter = calculateLevelMiters(walls);
|
|
22
|
+
let max = 0;
|
|
23
|
+
for (const w of walls) {
|
|
24
|
+
const bp = getWallMiterBoundaryPoints(w, miter);
|
|
25
|
+
expect(bp).not.toBeNull();
|
|
26
|
+
if (!bp)
|
|
27
|
+
continue;
|
|
28
|
+
for (const p of [bp.startLeft, bp.startRight, bp.endLeft, bp.endRight]) {
|
|
29
|
+
expect(Number.isFinite(p.x)).toBe(true);
|
|
30
|
+
expect(Number.isFinite(p.y)).toBe(true);
|
|
31
|
+
max = Math.max(max, Math.abs(p.x), Math.abs(p.y));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return max;
|
|
35
|
+
}
|
|
36
|
+
describe('wall mitering miter limit', () => {
|
|
37
|
+
// Two 3 m walls sharing the origin, meeting at decreasing angles. Without a
|
|
38
|
+
// miter limit the joint point runs to infinity as the angle → 0 (∝ 1/sin θ),
|
|
39
|
+
// which is the "infinite wall" seen when a room-preset preview lands on top of
|
|
40
|
+
// an existing wall. The boundary must stay bounded near the wall length.
|
|
41
|
+
test.each([90, 30, 10, 5, 1, 0.1, 0.01])('stays bounded at a %s° junction', (deg) => {
|
|
42
|
+
const rad = (deg * Math.PI) / 180;
|
|
43
|
+
const walls = [
|
|
44
|
+
wall('A', [0, 0], [3, 0]),
|
|
45
|
+
wall('B', [0, 0], [3 * Math.cos(rad), 3 * Math.sin(rad)]),
|
|
46
|
+
];
|
|
47
|
+
// 3 m walls + a few cm of joint: anything past ~4 m is a runaway spike.
|
|
48
|
+
expect(maxBoundaryCoord(walls)).toBeLessThan(4);
|
|
49
|
+
});
|
|
50
|
+
test('still miters a normal 90° corner', () => {
|
|
51
|
+
const walls = [wall('A', [0, 0], [3, 0]), wall('B', [0, 0], [0, 3])];
|
|
52
|
+
const miter = calculateLevelMiters(walls);
|
|
53
|
+
const bpA = getWallMiterBoundaryPoints(walls[0], miter);
|
|
54
|
+
expect(bpA).not.toBeNull();
|
|
55
|
+
if (!bpA)
|
|
56
|
+
throw new Error('expected miter boundary points');
|
|
57
|
+
// The shared corner is pulled to the mitred intersection, offset from the
|
|
58
|
+
// raw butt position (halfThickness 0.05) by the diagonal of the joint.
|
|
59
|
+
const startSideX = Math.min(bpA.startLeft.x, bpA.startRight.x);
|
|
60
|
+
expect(startSideX).toBeLessThan(-0.001);
|
|
61
|
+
expect(startSideX).toBeGreaterThan(-0.5);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { WallNode } from '../../schema';
|
|
2
|
+
export type WallPlanPoint = [number, number];
|
|
3
|
+
export type WallMoveAxis = [number, number];
|
|
4
|
+
export type WallMoveEndpoint = 'start' | 'end';
|
|
5
|
+
export type WallMoveBridgePlan<TWall extends Pick<WallNode, 'id' | 'start' | 'end'>> = {
|
|
6
|
+
wall: TWall;
|
|
7
|
+
originalPoint: WallPlanPoint;
|
|
8
|
+
movedEndpoint: WallMoveEndpoint;
|
|
9
|
+
};
|
|
10
|
+
export type WallMoveLinkedWallTargetPlan<TWall extends Pick<WallNode, 'id' | 'start' | 'end'>> = {
|
|
11
|
+
wall: TWall;
|
|
12
|
+
originalPoint: WallPlanPoint;
|
|
13
|
+
targetPoint: WallPlanPoint;
|
|
14
|
+
};
|
|
15
|
+
export type WallMoveJunctionPlan<TWall extends Pick<WallNode, 'id' | 'start' | 'end'>> = {
|
|
16
|
+
linkedWallsToMove: TWall[];
|
|
17
|
+
linkedWallTargetPlans: Array<WallMoveLinkedWallTargetPlan<TWall>>;
|
|
18
|
+
bridgePlans: Array<WallMoveBridgePlan<TWall>>;
|
|
19
|
+
wallsToDelete: TWall[];
|
|
20
|
+
};
|
|
21
|
+
export declare function getPerpendicularWallMoveAxis(start: WallPlanPoint, end: WallPlanPoint): WallMoveAxis | null;
|
|
22
|
+
export declare function constrainWallMoveDeltaToAxis(deltaX: number, deltaZ: number, axis: WallMoveAxis | null): WallPlanPoint;
|
|
23
|
+
/**
|
|
24
|
+
* Apply a junction plan to a list of linked walls, producing the per-wall
|
|
25
|
+
* endpoint updates. Mirrors the 3D `MoveWallTool`'s drag-preview behavior
|
|
26
|
+
* so the 2D move handler can drive the same scene topology.
|
|
27
|
+
*
|
|
28
|
+
* `linkedWallTargetPlans` take precedence over `linkedWallsToMove` — when
|
|
29
|
+
* the planner emits a target plan for a same-direction-consumed wall the
|
|
30
|
+
* matchPoint/targetPoint pair encodes the pivot, not the original ↔ next
|
|
31
|
+
* endpoint mapping.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getLinkedWallUpdates<TWall extends Pick<WallNode, 'id' | 'start' | 'end'>>(linkedWalls: Array<{
|
|
34
|
+
wall: TWall;
|
|
35
|
+
matchPoint?: WallPlanPoint;
|
|
36
|
+
targetPoint?: WallPlanPoint;
|
|
37
|
+
}>, originalStart: WallPlanPoint, originalEnd: WallPlanPoint, nextStart: WallPlanPoint, nextEnd: WallPlanPoint): Array<{
|
|
38
|
+
id: TWall['id'];
|
|
39
|
+
start: WallPlanPoint;
|
|
40
|
+
end: WallPlanPoint;
|
|
41
|
+
}>;
|
|
42
|
+
export declare function getPlannedLinkedWallUpdates<TWall extends Pick<WallNode, 'id' | 'start' | 'end'>>(plan: WallMoveJunctionPlan<TWall>, originalStart: WallPlanPoint, originalEnd: WallPlanPoint, nextStart: WallPlanPoint, nextEnd: WallPlanPoint): Array<{
|
|
43
|
+
id: TWall['id'];
|
|
44
|
+
start: WallPlanPoint;
|
|
45
|
+
end: WallPlanPoint;
|
|
46
|
+
}>;
|
|
47
|
+
export declare function planWallMoveJunctions<TWall extends Pick<WallNode, 'id' | 'start' | 'end'>>(linkedWalls: TWall[], originalStart: WallPlanPoint, originalEnd: WallPlanPoint, nextStart: WallPlanPoint, nextEnd: WallPlanPoint): WallMoveJunctionPlan<TWall>;
|
|
48
|
+
//# sourceMappingURL=wall-move.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wall-move.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-move.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAI5C,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAE5C,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAC3C,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,KAAK,CAAA;AAE9C,MAAM,MAAM,kBAAkB,CAAC,KAAK,SAAS,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,OAAO,GAAG,KAAK,CAAC,IAAI;IACrF,IAAI,EAAE,KAAK,CAAA;IACX,aAAa,EAAE,aAAa,CAAA;IAC5B,aAAa,EAAE,gBAAgB,CAAA;CAChC,CAAA;AAED,MAAM,MAAM,4BAA4B,CAAC,KAAK,SAAS,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,OAAO,GAAG,KAAK,CAAC,IAAI;IAC/F,IAAI,EAAE,KAAK,CAAA;IACX,aAAa,EAAE,aAAa,CAAA;IAC5B,WAAW,EAAE,aAAa,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,oBAAoB,CAAC,KAAK,SAAS,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,OAAO,GAAG,KAAK,CAAC,IAAI;IACvF,iBAAiB,EAAE,KAAK,EAAE,CAAA;IAC1B,qBAAqB,EAAE,KAAK,CAAC,4BAA4B,CAAC,KAAK,CAAC,CAAC,CAAA;IACjE,WAAW,EAAE,KAAK,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAA;IAC7C,aAAa,EAAE,KAAK,EAAE,CAAA;CACvB,CAAA;AAED,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,aAAa,EACpB,GAAG,EAAE,aAAa,GACjB,YAAY,GAAG,IAAI,CAUrB;AAED,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,YAAY,GAAG,IAAI,GACxB,aAAa,CAMf;AAkDD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,SAAS,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,OAAO,GAAG,KAAK,CAAC,EACvF,WAAW,EAAE,KAAK,CAAC;IACjB,IAAI,EAAE,KAAK,CAAA;IACX,UAAU,CAAC,EAAE,aAAa,CAAA;IAC1B,WAAW,CAAC,EAAE,aAAa,CAAA;CAC5B,CAAC,EACF,aAAa,EAAE,aAAa,EAC5B,WAAW,EAAE,aAAa,EAC1B,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,aAAa,GACrB,KAAK,CAAC;IAAE,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAAC,KAAK,EAAE,aAAa,CAAC;IAAC,GAAG,EAAE,aAAa,CAAA;CAAE,CAAC,CA2BtE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,SAAS,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,OAAO,GAAG,KAAK,CAAC,EAC9F,IAAI,EAAE,oBAAoB,CAAC,KAAK,CAAC,EACjC,aAAa,EAAE,aAAa,EAC5B,WAAW,EAAE,aAAa,EAC1B,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,aAAa,GACrB,KAAK,CAAC;IAAE,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAAC,KAAK,EAAE,aAAa,CAAC;IAAC,GAAG,EAAE,aAAa,CAAA;CAAE,CAAC,CAyBtE;AAED,wBAAgB,qBAAqB,CAAC,KAAK,SAAS,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,OAAO,GAAG,KAAK,CAAC,EACxF,WAAW,EAAE,KAAK,EAAE,EACpB,aAAa,EAAE,aAAa,EAC5B,WAAW,EAAE,aAAa,EAC1B,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,aAAa,GACrB,oBAAoB,CAAC,KAAK,CAAC,CAmI7B"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
const AXIS_EPSILON = 1e-6;
|
|
2
|
+
export function getPerpendicularWallMoveAxis(start, end) {
|
|
3
|
+
const dx = end[0] - start[0];
|
|
4
|
+
const dz = end[1] - start[1];
|
|
5
|
+
const length = Math.hypot(dx, dz);
|
|
6
|
+
if (length < AXIS_EPSILON)
|
|
7
|
+
return null;
|
|
8
|
+
// Perpendicular (normal) direction for moving the wall "sideways".
|
|
9
|
+
// This matches the arrow handles shown in the editor.
|
|
10
|
+
return [-dz / length, dx / length];
|
|
11
|
+
}
|
|
12
|
+
export function constrainWallMoveDeltaToAxis(deltaX, deltaZ, axis) {
|
|
13
|
+
if (axis) {
|
|
14
|
+
const projected = deltaX * axis[0] + deltaZ * axis[1];
|
|
15
|
+
return [axis[0] * projected, axis[1] * projected];
|
|
16
|
+
}
|
|
17
|
+
return [deltaX, deltaZ];
|
|
18
|
+
}
|
|
19
|
+
function pointsEqual(a, b) {
|
|
20
|
+
return Math.abs(a[0] - b[0]) <= AXIS_EPSILON && Math.abs(a[1] - b[1]) <= AXIS_EPSILON;
|
|
21
|
+
}
|
|
22
|
+
function wallTouchesPoint(wall, point) {
|
|
23
|
+
return pointsEqual(wall.start, point) || pointsEqual(wall.end, point);
|
|
24
|
+
}
|
|
25
|
+
function otherWallEndpoint(wall, point) {
|
|
26
|
+
return pointsEqual(wall.start, point) ? wall.end : wall.start;
|
|
27
|
+
}
|
|
28
|
+
function wallLengthFromPoint(wall, point) {
|
|
29
|
+
const freeEndpoint = otherWallEndpoint(wall, point);
|
|
30
|
+
return Math.hypot(freeEndpoint[0] - point[0], freeEndpoint[1] - point[1]);
|
|
31
|
+
}
|
|
32
|
+
function getMoveWallRelation(wall, sharedPoint, nextPoint) {
|
|
33
|
+
const moveX = nextPoint[0] - sharedPoint[0];
|
|
34
|
+
const moveZ = nextPoint[1] - sharedPoint[1];
|
|
35
|
+
const moveLength = Math.hypot(moveX, moveZ);
|
|
36
|
+
if (moveLength < AXIS_EPSILON)
|
|
37
|
+
return 'stationary';
|
|
38
|
+
const freeEndpoint = otherWallEndpoint(wall, sharedPoint);
|
|
39
|
+
const wallX = freeEndpoint[0] - sharedPoint[0];
|
|
40
|
+
const wallZ = freeEndpoint[1] - sharedPoint[1];
|
|
41
|
+
const wallLength = Math.hypot(wallX, wallZ);
|
|
42
|
+
if (wallLength < AXIS_EPSILON)
|
|
43
|
+
return 'stationary';
|
|
44
|
+
const normalizedCross = Math.abs(moveX * wallZ - moveZ * wallX) / (moveLength * wallLength);
|
|
45
|
+
if (normalizedCross > 1e-4)
|
|
46
|
+
return 'off-axis';
|
|
47
|
+
const normalizedDot = (moveX * wallX + moveZ * wallZ) / (moveLength * wallLength);
|
|
48
|
+
return normalizedDot >= 0 ? 'same-direction' : 'opposite-direction';
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Apply a junction plan to a list of linked walls, producing the per-wall
|
|
52
|
+
* endpoint updates. Mirrors the 3D `MoveWallTool`'s drag-preview behavior
|
|
53
|
+
* so the 2D move handler can drive the same scene topology.
|
|
54
|
+
*
|
|
55
|
+
* `linkedWallTargetPlans` take precedence over `linkedWallsToMove` — when
|
|
56
|
+
* the planner emits a target plan for a same-direction-consumed wall the
|
|
57
|
+
* matchPoint/targetPoint pair encodes the pivot, not the original ↔ next
|
|
58
|
+
* endpoint mapping.
|
|
59
|
+
*/
|
|
60
|
+
export function getLinkedWallUpdates(linkedWalls, originalStart, originalEnd, nextStart, nextEnd) {
|
|
61
|
+
return linkedWalls.map(({ wall, matchPoint, targetPoint }) => {
|
|
62
|
+
if (matchPoint && targetPoint) {
|
|
63
|
+
return {
|
|
64
|
+
id: wall.id,
|
|
65
|
+
start: pointsEqual(wall.start, matchPoint) ? targetPoint : wall.start,
|
|
66
|
+
end: pointsEqual(wall.end, matchPoint) ? targetPoint : wall.end,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const targetStart = targetPoint ?? nextStart;
|
|
70
|
+
const targetEnd = targetPoint ?? nextEnd;
|
|
71
|
+
return {
|
|
72
|
+
id: wall.id,
|
|
73
|
+
start: pointsEqual(wall.start, originalStart)
|
|
74
|
+
? targetStart
|
|
75
|
+
: pointsEqual(wall.start, originalEnd)
|
|
76
|
+
? targetEnd
|
|
77
|
+
: wall.start,
|
|
78
|
+
end: pointsEqual(wall.end, originalStart)
|
|
79
|
+
? targetStart
|
|
80
|
+
: pointsEqual(wall.end, originalEnd)
|
|
81
|
+
? targetEnd
|
|
82
|
+
: wall.end,
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
export function getPlannedLinkedWallUpdates(plan, originalStart, originalEnd, nextStart, nextEnd) {
|
|
87
|
+
const movePlans = new Map();
|
|
88
|
+
for (const wall of plan.linkedWallsToMove) {
|
|
89
|
+
movePlans.set(wall.id, { wall });
|
|
90
|
+
}
|
|
91
|
+
for (const targetPlan of plan.linkedWallTargetPlans) {
|
|
92
|
+
movePlans.set(targetPlan.wall.id, {
|
|
93
|
+
wall: targetPlan.wall,
|
|
94
|
+
matchPoint: targetPlan.originalPoint,
|
|
95
|
+
targetPoint: targetPlan.targetPoint,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return getLinkedWallUpdates(Array.from(movePlans.values()), originalStart, originalEnd, nextStart, nextEnd);
|
|
99
|
+
}
|
|
100
|
+
export function planWallMoveJunctions(linkedWalls, originalStart, originalEnd, nextStart, nextEnd) {
|
|
101
|
+
const linkedWallsToMove = new Map();
|
|
102
|
+
const linkedWallTargetPlans = new Map();
|
|
103
|
+
const bridgePlans = new Map();
|
|
104
|
+
const wallsToDelete = new Map();
|
|
105
|
+
const addStandardEndpointPlan = (endpoint, point, nextPoint, relatedWalls, keySuffix = '', useTargetPlans = false) => {
|
|
106
|
+
const hasSideBranch = relatedWalls.some((entry) => entry.relation === 'off-axis');
|
|
107
|
+
const hasOppositeBridge = relatedWalls.some((entry) => entry.relation === 'opposite-direction' && hasSideBranch);
|
|
108
|
+
for (const { wall, relation } of relatedWalls) {
|
|
109
|
+
if (relation === 'stationary' ||
|
|
110
|
+
relation === 'same-direction' ||
|
|
111
|
+
(relation === 'opposite-direction' && !hasSideBranch)) {
|
|
112
|
+
if (useTargetPlans) {
|
|
113
|
+
linkedWallTargetPlans.set(wall.id, {
|
|
114
|
+
wall,
|
|
115
|
+
originalPoint: point,
|
|
116
|
+
targetPoint: nextPoint,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
linkedWallsToMove.set(wall.id, wall);
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (relation === 'off-axis' && hasOppositeBridge) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
bridgePlans.set(`${wall.id}:${endpoint}${keySuffix}`, {
|
|
128
|
+
wall,
|
|
129
|
+
originalPoint: point,
|
|
130
|
+
movedEndpoint: endpoint,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const addEndpointPlan = (endpoint, point, nextPoint) => {
|
|
135
|
+
const moveLength = Math.hypot(nextPoint[0] - point[0], nextPoint[1] - point[1]);
|
|
136
|
+
const linkedAtEndpoint = linkedWalls
|
|
137
|
+
.filter((wall) => wallTouchesPoint(wall, point))
|
|
138
|
+
.map((wall) => ({
|
|
139
|
+
wall,
|
|
140
|
+
relation: getMoveWallRelation(wall, point, nextPoint),
|
|
141
|
+
}));
|
|
142
|
+
const consumedSameDirectionWall = linkedAtEndpoint
|
|
143
|
+
.filter((entry) => entry.relation === 'same-direction')
|
|
144
|
+
.map((entry) => ({
|
|
145
|
+
...entry,
|
|
146
|
+
distance: wallLengthFromPoint(entry.wall, point),
|
|
147
|
+
}))
|
|
148
|
+
.filter((entry) => moveLength + AXIS_EPSILON >= entry.distance)
|
|
149
|
+
.sort((a, b) => a.distance - b.distance)[0];
|
|
150
|
+
if (consumedSameDirectionWall) {
|
|
151
|
+
const pivotPoint = [
|
|
152
|
+
...otherWallEndpoint(consumedSameDirectionWall.wall, point),
|
|
153
|
+
];
|
|
154
|
+
const bridgeSource = linkedAtEndpoint.find((entry) => entry.relation === 'opposite-direction');
|
|
155
|
+
wallsToDelete.set(consumedSameDirectionWall.wall.id, consumedSameDirectionWall.wall);
|
|
156
|
+
linkedWallTargetPlans.set(consumedSameDirectionWall.wall.id, {
|
|
157
|
+
wall: consumedSameDirectionWall.wall,
|
|
158
|
+
originalPoint: point,
|
|
159
|
+
targetPoint: pivotPoint,
|
|
160
|
+
});
|
|
161
|
+
if (bridgeSource) {
|
|
162
|
+
linkedWallTargetPlans.set(bridgeSource.wall.id, {
|
|
163
|
+
wall: bridgeSource.wall,
|
|
164
|
+
originalPoint: point,
|
|
165
|
+
targetPoint: pivotPoint,
|
|
166
|
+
});
|
|
167
|
+
bridgePlans.set(`${bridgeSource.wall.id}:${endpoint}:through`, {
|
|
168
|
+
wall: bridgeSource.wall,
|
|
169
|
+
originalPoint: pivotPoint,
|
|
170
|
+
movedEndpoint: endpoint,
|
|
171
|
+
});
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const linkedAtPivot = linkedWalls
|
|
175
|
+
.filter((wall) => wall.id !== consumedSameDirectionWall.wall.id && wallTouchesPoint(wall, pivotPoint))
|
|
176
|
+
.map((wall) => ({
|
|
177
|
+
wall,
|
|
178
|
+
relation: getMoveWallRelation(wall, pivotPoint, nextPoint),
|
|
179
|
+
}));
|
|
180
|
+
addStandardEndpointPlan(endpoint, pivotPoint, nextPoint, linkedAtPivot, ':through-pivot', true);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
addStandardEndpointPlan(endpoint, point, nextPoint, linkedAtEndpoint);
|
|
184
|
+
};
|
|
185
|
+
addEndpointPlan('start', originalStart, nextStart);
|
|
186
|
+
addEndpointPlan('end', originalEnd, nextEnd);
|
|
187
|
+
return {
|
|
188
|
+
linkedWallsToMove: Array.from(linkedWallsToMove.values()),
|
|
189
|
+
linkedWallTargetPlans: Array.from(linkedWallTargetPlans.values()),
|
|
190
|
+
bridgePlans: Array.from(bridgePlans.values()),
|
|
191
|
+
wallsToDelete: Array.from(wallsToDelete.values()),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -4,6 +4,7 @@ export type SceneGraph = {
|
|
|
4
4
|
nodes: Record<AnyNodeId, AnyNode>;
|
|
5
5
|
rootNodeIds: AnyNodeId[];
|
|
6
6
|
collections?: Record<CollectionId, Collection>;
|
|
7
|
+
installedPlugins?: string[];
|
|
7
8
|
};
|
|
8
9
|
/**
|
|
9
10
|
* Deep clones a scene graph with all node IDs regenerated while preserving
|
|
@@ -33,10 +34,12 @@ export declare function cloneLevelSubtree(nodes: Record<AnyNodeId, AnyNode>, lev
|
|
|
33
34
|
newLevelId: AnyNodeId;
|
|
34
35
|
idMap: Map<string, string>;
|
|
35
36
|
};
|
|
37
|
+
export type ForkSceneGraphOptions = {
|
|
38
|
+
preserveScans?: boolean;
|
|
39
|
+
};
|
|
36
40
|
/**
|
|
37
|
-
* Forks a scene graph for use as a new project: clones with new IDs and
|
|
38
|
-
* scan and guide nodes
|
|
39
|
-
* imagery that shouldn't carry over to a forked project.
|
|
41
|
+
* Forks a scene graph for use as a new project: clones with new IDs and, by
|
|
42
|
+
* default, strips scan and guide nodes since they contain user-uploaded imagery.
|
|
40
43
|
*/
|
|
41
|
-
export declare function forkSceneGraph(sceneGraph: SceneGraph): SceneGraph;
|
|
44
|
+
export declare function forkSceneGraph(sceneGraph: SceneGraph, options?: ForkSceneGraphOptions): SceneGraph;
|
|
42
45
|
//# sourceMappingURL=clone-scene-graph.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clone-scene-graph.d.ts","sourceRoot":"","sources":["../../src/utils/clone-scene-graph.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"clone-scene-graph.d.ts","sourceRoot":"","sources":["../../src/utils/clone-scene-graph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAEnD,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAErE,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;IACjC,WAAW,EAAE,SAAS,EAAE,CAAA;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,CAAA;IAC9C,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC5B,CAAA;AAUD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,CAmHlE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EACjC,OAAO,EAAE,SAAS,GACjB;IAAE,WAAW,EAAE,OAAO,EAAE,CAAC;IAAC,UAAU,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAwF/E;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB,CAAA;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE,qBAA0B,GAClC,UAAU,CAqEZ"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { remapMeasurementReferences } from '../lib/measurement-geometry';
|
|
1
2
|
import { generateId } from '../schema/base';
|
|
2
3
|
/**
|
|
3
4
|
* Extracts the type prefix from a node ID (e.g., "wall_abc123" -> "wall")
|
|
@@ -16,7 +17,7 @@ function extractIdPrefix(id) {
|
|
|
16
17
|
* - Multi-scene in-memory scenarios
|
|
17
18
|
*/
|
|
18
19
|
export function cloneSceneGraph(sceneGraph) {
|
|
19
|
-
const { nodes, rootNodeIds, collections } = sceneGraph;
|
|
20
|
+
const { nodes, rootNodeIds, collections, installedPlugins } = sceneGraph;
|
|
20
21
|
// Build ID mapping: old ID -> new ID
|
|
21
22
|
const idMap = new Map();
|
|
22
23
|
// Pass 1: Generate new IDs for all nodes
|
|
@@ -57,6 +58,14 @@ export function cloneSceneGraph(sceneGraph) {
|
|
|
57
58
|
;
|
|
58
59
|
clonedNode.wallId = idMap.get(clonedNode.wallId);
|
|
59
60
|
}
|
|
61
|
+
// Remap roofSegmentId (doors/windows/items hosted on roof wall faces)
|
|
62
|
+
if ('roofSegmentId' in clonedNode && typeof clonedNode.roofSegmentId === 'string') {
|
|
63
|
+
;
|
|
64
|
+
clonedNode.roofSegmentId = idMap.get(clonedNode.roofSegmentId);
|
|
65
|
+
}
|
|
66
|
+
if (clonedNode.type === 'measurement') {
|
|
67
|
+
clonedNode.measurement = remapMeasurementReferences(clonedNode.measurement, idMap);
|
|
68
|
+
}
|
|
60
69
|
clonedNodes[newId] = clonedNode;
|
|
61
70
|
}
|
|
62
71
|
// Remap root node IDs
|
|
@@ -102,6 +111,7 @@ export function cloneSceneGraph(sceneGraph) {
|
|
|
102
111
|
nodes: clonedNodes,
|
|
103
112
|
rootNodeIds: clonedRootNodeIds,
|
|
104
113
|
...(clonedCollections && { collections: clonedCollections }),
|
|
114
|
+
...(installedPlugins && { installedPlugins: [...installedPlugins] }),
|
|
105
115
|
};
|
|
106
116
|
}
|
|
107
117
|
/**
|
|
@@ -119,7 +129,7 @@ export function cloneSceneGraph(sceneGraph) {
|
|
|
119
129
|
*/
|
|
120
130
|
export function cloneLevelSubtree(nodes, levelId) {
|
|
121
131
|
const levelNode = nodes[levelId];
|
|
122
|
-
if (
|
|
132
|
+
if (levelNode?.type !== 'level') {
|
|
123
133
|
throw new Error(`Node "${levelId}" is not a level`);
|
|
124
134
|
}
|
|
125
135
|
// Recursively collect the level node + all descendants via children arrays
|
|
@@ -185,17 +195,28 @@ export function cloneLevelSubtree(nodes, levelId) {
|
|
|
185
195
|
;
|
|
186
196
|
cloned.wallId = idMap.get(cloned.wallId) ?? cloned.wallId;
|
|
187
197
|
}
|
|
198
|
+
// Remap roofSegmentId (doors/windows/items hosted on roof wall faces)
|
|
199
|
+
if ('roofSegmentId' in cloned && typeof cloned.roofSegmentId === 'string') {
|
|
200
|
+
;
|
|
201
|
+
cloned.roofSegmentId =
|
|
202
|
+
idMap.get(cloned.roofSegmentId) ?? cloned.roofSegmentId;
|
|
203
|
+
}
|
|
204
|
+
if (cloned.type === 'measurement') {
|
|
205
|
+
cloned.measurement = remapMeasurementReferences(cloned.measurement, idMap);
|
|
206
|
+
}
|
|
188
207
|
clonedNodes.push(cloned);
|
|
189
208
|
}
|
|
190
209
|
return { clonedNodes, newLevelId, idMap };
|
|
191
210
|
}
|
|
192
211
|
/**
|
|
193
|
-
* Forks a scene graph for use as a new project: clones with new IDs and
|
|
194
|
-
* scan and guide nodes
|
|
195
|
-
* imagery that shouldn't carry over to a forked project.
|
|
212
|
+
* Forks a scene graph for use as a new project: clones with new IDs and, by
|
|
213
|
+
* default, strips scan and guide nodes since they contain user-uploaded imagery.
|
|
196
214
|
*/
|
|
197
|
-
export function forkSceneGraph(sceneGraph) {
|
|
198
|
-
|
|
215
|
+
export function forkSceneGraph(sceneGraph, options = {}) {
|
|
216
|
+
if (options.preserveScans) {
|
|
217
|
+
return cloneSceneGraph(sceneGraph);
|
|
218
|
+
}
|
|
219
|
+
const { nodes, rootNodeIds, collections, installedPlugins } = sceneGraph;
|
|
199
220
|
// First, identify scan and guide node IDs to exclude (user-uploaded imagery)
|
|
200
221
|
const excludedNodeIds = new Set();
|
|
201
222
|
for (const [nodeId, node] of Object.entries(nodes)) {
|
|
@@ -247,5 +268,6 @@ export function forkSceneGraph(sceneGraph) {
|
|
|
247
268
|
nodes: filteredNodes,
|
|
248
269
|
rootNodeIds: filteredRootNodeIds,
|
|
249
270
|
...(filteredCollections && { collections: filteredCollections }),
|
|
271
|
+
...(installedPlugins && { installedPlugins }),
|
|
250
272
|
});
|
|
251
273
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clone-scene-graph.test.d.ts","sourceRoot":"","sources":["../../src/utils/clone-scene-graph.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { forkSceneGraph } from './clone-scene-graph';
|
|
3
|
+
function makeNode(id, type, extra = {}) {
|
|
4
|
+
return {
|
|
5
|
+
object: 'node',
|
|
6
|
+
id,
|
|
7
|
+
type,
|
|
8
|
+
parentId: null,
|
|
9
|
+
visible: true,
|
|
10
|
+
metadata: {},
|
|
11
|
+
...extra,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function makeSceneGraph() {
|
|
15
|
+
const site = makeNode('site_1', 'site', { children: ['level_1'] });
|
|
16
|
+
const level = makeNode('level_1', 'level', {
|
|
17
|
+
parentId: 'site_1',
|
|
18
|
+
children: ['wall_1', 'scan_1', 'guide_1'],
|
|
19
|
+
});
|
|
20
|
+
const wall = makeNode('wall_1', 'wall', { parentId: 'level_1' });
|
|
21
|
+
const scan = makeNode('scan_1', 'scan', { parentId: 'level_1', url: 'scan.glb' });
|
|
22
|
+
const guide = makeNode('guide_1', 'guide', { parentId: 'level_1', url: 'guide.png' });
|
|
23
|
+
return {
|
|
24
|
+
nodes: {
|
|
25
|
+
['site_1']: site,
|
|
26
|
+
['level_1']: level,
|
|
27
|
+
['wall_1']: wall,
|
|
28
|
+
['scan_1']: scan,
|
|
29
|
+
['guide_1']: guide,
|
|
30
|
+
},
|
|
31
|
+
rootNodeIds: ['site_1'],
|
|
32
|
+
collections: {
|
|
33
|
+
['collection_1']: {
|
|
34
|
+
id: 'collection_1',
|
|
35
|
+
name: 'References',
|
|
36
|
+
nodeIds: ['scan_1', 'guide_1'],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
installedPlugins: ['pascal:trees'],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
describe('forkSceneGraph', () => {
|
|
43
|
+
test('strips scan and guide nodes by default', () => {
|
|
44
|
+
const forked = forkSceneGraph(makeSceneGraph());
|
|
45
|
+
const nodes = Object.values(forked.nodes);
|
|
46
|
+
expect(nodes.some((node) => node.type === 'scan')).toBe(false);
|
|
47
|
+
expect(nodes.some((node) => node.type === 'guide')).toBe(false);
|
|
48
|
+
expect(nodes.some((node) => node.type === 'wall')).toBe(true);
|
|
49
|
+
expect(forked.collections).toEqual({});
|
|
50
|
+
expect(forked.installedPlugins).toEqual(['pascal:trees']);
|
|
51
|
+
});
|
|
52
|
+
test('preserves scan and guide nodes when requested', () => {
|
|
53
|
+
const forked = forkSceneGraph(makeSceneGraph(), { preserveScans: true });
|
|
54
|
+
const nodes = Object.values(forked.nodes);
|
|
55
|
+
expect(nodes.some((node) => node.type === 'scan')).toBe(true);
|
|
56
|
+
expect(nodes.some((node) => node.type === 'guide')).toBe(true);
|
|
57
|
+
expect(nodes.map((node) => node.id)).not.toContain('scan_1');
|
|
58
|
+
expect(nodes.map((node) => node.id)).not.toContain('guide_1');
|
|
59
|
+
expect(Object.values(forked.collections ?? {}).flatMap((collection) => collection.nodeIds)).toHaveLength(2);
|
|
60
|
+
expect(forked.installedPlugins).toEqual(['pascal:trees']);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface HealSceneResult {
|
|
2
|
+
nodes: Record<string, unknown>;
|
|
3
|
+
/** Ids of zero-length walls that were dropped. */
|
|
4
|
+
droppedWallIds: string[];
|
|
5
|
+
/** Count of non-string (e.g. null) entries removed from `children` arrays. */
|
|
6
|
+
strippedChildRefs: number;
|
|
7
|
+
/**
|
|
8
|
+
* Count of child references removed because the child's `parentId` points at
|
|
9
|
+
* a different node (stale reparent leftovers), plus same-array duplicates.
|
|
10
|
+
*/
|
|
11
|
+
strippedStaleChildRefs: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Returns a healed copy of a `nodes` map. Pure — does not mutate `input`.
|
|
15
|
+
* Nodes that need no repair are passed through by reference.
|
|
16
|
+
*/
|
|
17
|
+
export declare function healSceneNodes(input: Record<string, unknown>): HealSceneResult;
|
|
18
|
+
//# sourceMappingURL=heal-scene-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heal-scene-graph.d.ts","sourceRoot":"","sources":["../../src/utils/heal-scene-graph.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,kDAAkD;IAClD,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,8EAA8E;IAC9E,iBAAiB,EAAE,MAAM,CAAA;IACzB;;;OAGG;IACH,sBAAsB,EAAE,MAAM,CAAA;CAC/B;AAgBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,CA0D9E"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Repairs scene-graph corruption that pre-dates the source fixes, so existing
|
|
2
|
+
// saved scenes still load. Known kinds of damage:
|
|
3
|
+
//
|
|
4
|
+
// 1. A `children` array containing a non-string entry. The capture wall-merge
|
|
5
|
+
// re-attached a wall-hosted item without minting an id, so `undefined` was
|
|
6
|
+
// pushed into the wall's children — which serializes to `[null]`. The wall
|
|
7
|
+
// schema rejects `null` children, so the whole scene fails to load.
|
|
8
|
+
// 2. A zero-length wall (start === end). It renders nothing, but lingers as a
|
|
9
|
+
// junk node and is a foot-gun for snapping/mitering.
|
|
10
|
+
// 3. A child referenced by a parent it no longer belongs to: the child's
|
|
11
|
+
// `parentId` points at node B while node A's `children` still lists it
|
|
12
|
+
// (stale leftover from a reparent that didn't clean the old parent). The
|
|
13
|
+
// duplicate reference renders the child twice (duplicate React keys in the
|
|
14
|
+
// 2D plan, doubled hosted geometry in 3D). Same-array duplicates are
|
|
15
|
+
// collapsed too.
|
|
16
|
+
//
|
|
17
|
+
// All are also prevented at the source now; this is the load-time safety net
|
|
18
|
+
// for already-saved scenes.
|
|
19
|
+
const ZERO_LENGTH_EPS = 1e-6;
|
|
20
|
+
function isWallLike(node) {
|
|
21
|
+
if (!node || typeof node !== 'object')
|
|
22
|
+
return false;
|
|
23
|
+
const n = node;
|
|
24
|
+
return (n.type === 'wall' &&
|
|
25
|
+
Array.isArray(n.start) &&
|
|
26
|
+
Array.isArray(n.end) &&
|
|
27
|
+
typeof n.start[0] === 'number' &&
|
|
28
|
+
typeof n.start[1] === 'number' &&
|
|
29
|
+
typeof n.end[0] === 'number' &&
|
|
30
|
+
typeof n.end[1] === 'number');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns a healed copy of a `nodes` map. Pure — does not mutate `input`.
|
|
34
|
+
* Nodes that need no repair are passed through by reference.
|
|
35
|
+
*/
|
|
36
|
+
export function healSceneNodes(input) {
|
|
37
|
+
const droppedWallIds = [];
|
|
38
|
+
// Pass 1: drop childless zero-length walls. (Only childless ones — a wall
|
|
39
|
+
// carrying a door/window must keep its hosts, degenerate or not.)
|
|
40
|
+
const kept = {};
|
|
41
|
+
for (const [id, node] of Object.entries(input)) {
|
|
42
|
+
if (isWallLike(node)) {
|
|
43
|
+
const children = node.children;
|
|
44
|
+
const childless = !Array.isArray(children) || children.length === 0;
|
|
45
|
+
const dx = node.end[0] - node.start[0];
|
|
46
|
+
const dz = node.end[1] - node.start[1];
|
|
47
|
+
if (childless && Math.hypot(dx, dz) <= ZERO_LENGTH_EPS) {
|
|
48
|
+
droppedWallIds.push(id);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
kept[id] = node;
|
|
53
|
+
}
|
|
54
|
+
const dropped = new Set(droppedWallIds);
|
|
55
|
+
let strippedChildRefs = 0;
|
|
56
|
+
let strippedStaleChildRefs = 0;
|
|
57
|
+
// Pass 2: clean `children` arrays — drop non-string entries (the `[null]`
|
|
58
|
+
// bug), references to walls we just removed, same-array duplicates, and
|
|
59
|
+
// stale references whose child's `parentId` names a different parent.
|
|
60
|
+
const nodes = {};
|
|
61
|
+
for (const [id, node] of Object.entries(kept)) {
|
|
62
|
+
const children = node?.children;
|
|
63
|
+
if (Array.isArray(children)) {
|
|
64
|
+
const seen = new Set();
|
|
65
|
+
const cleaned = children.filter((c) => {
|
|
66
|
+
if (typeof c !== 'string' || dropped.has(c)) {
|
|
67
|
+
strippedChildRefs++;
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (seen.has(c)) {
|
|
71
|
+
strippedStaleChildRefs++;
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
seen.add(c);
|
|
75
|
+
const child = kept[c];
|
|
76
|
+
if (child && typeof child.parentId === 'string' && child.parentId !== id) {
|
|
77
|
+
strippedStaleChildRefs++;
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
});
|
|
82
|
+
if (cleaned.length !== children.length) {
|
|
83
|
+
nodes[id] = { ...node, children: cleaned };
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
nodes[id] = node;
|
|
88
|
+
}
|
|
89
|
+
return { nodes, droppedWallIds, strippedChildRefs, strippedStaleChildRefs };
|
|
90
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heal-scene-graph.test.d.ts","sourceRoot":"","sources":["../../src/utils/heal-scene-graph.test.ts"],"names":[],"mappings":""}
|