@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
package/dist/store/use-scene.js
CHANGED
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { temporal } from 'zundo';
|
|
3
3
|
import { create } from 'zustand';
|
|
4
|
+
import { parseMaterialRef, toSceneMaterialRef } from '../material-library';
|
|
5
|
+
import { getNodePluginId, isNodeKindEnabled, nodeRegistry } from '../registry/registry';
|
|
4
6
|
import { BuildingNode } from '../schema';
|
|
5
7
|
import { generateCollectionId } from '../schema/collections';
|
|
8
|
+
import { DoorNode as DoorNodeSchema } from '../schema/nodes/door';
|
|
9
|
+
import { ElevatorNode as ElevatorNodeSchema } from '../schema/nodes/elevator';
|
|
6
10
|
import { LevelNode } from '../schema/nodes/level';
|
|
11
|
+
import { getPitchFromActiveRoofHeight, } from '../schema/nodes/roof-segment';
|
|
12
|
+
import { segmentPointToRoofWallFace } from '../schema/nodes/roof-segment-walls';
|
|
13
|
+
import { ShelfNode as ShelfNodeSchema } from '../schema/nodes/shelf';
|
|
7
14
|
import { SiteNode } from '../schema/nodes/site';
|
|
8
|
-
import { StairNode as StairNodeSchema } from '../schema/nodes/stair';
|
|
15
|
+
import { getEffectiveStairSurfaceMaterial, StairNode as StairNodeSchema, } from '../schema/nodes/stair';
|
|
9
16
|
import { StairSegmentNode as StairSegmentNodeSchema } from '../schema/nodes/stair-segment';
|
|
17
|
+
import { getEffectiveWallSurfaceMaterial } from '../schema/nodes/wall';
|
|
18
|
+
import { WindowNode as WindowNodeSchema } from '../schema/nodes/window';
|
|
19
|
+
import { generateSceneMaterialId, } from '../schema/scene-material';
|
|
20
|
+
import { healSceneNodes } from '../utils/heal-scene-graph';
|
|
10
21
|
import * as nodeActions from './actions/node-actions';
|
|
11
22
|
import { resetSceneHistoryPauseDepth } from './history-control';
|
|
12
23
|
function getFiniteNumber(value, fallback) {
|
|
13
24
|
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
14
25
|
}
|
|
26
|
+
function getFiniteNumberInRange(value, fallback, min, max) {
|
|
27
|
+
const finite = getFiniteNumber(value, fallback);
|
|
28
|
+
return Math.min(Math.max(finite, min), max);
|
|
29
|
+
}
|
|
15
30
|
function getBoolean(value, fallback) {
|
|
16
31
|
return typeof value === 'boolean' ? value : fallback;
|
|
17
32
|
}
|
|
@@ -19,7 +34,7 @@ function getEnumValue(value, allowed, fallback) {
|
|
|
19
34
|
return typeof value === 'string' && allowed.includes(value) ? value : fallback;
|
|
20
35
|
}
|
|
21
36
|
function getNullableString(value) {
|
|
22
|
-
return typeof value === 'string' ? value : null;
|
|
37
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
23
38
|
}
|
|
24
39
|
function getStringArray(value) {
|
|
25
40
|
return Array.isArray(value)
|
|
@@ -81,40 +96,227 @@ function normalizeStairSegmentNode(node) {
|
|
|
81
96
|
const parsed = StairSegmentNodeSchema.safeParse(sanitized);
|
|
82
97
|
return parsed.success ? parsed.data : null;
|
|
83
98
|
}
|
|
84
|
-
function
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
function normalizeDoorNode(node) {
|
|
100
|
+
const parsed = DoorNodeSchema.safeParse(node);
|
|
101
|
+
return parsed.success ? { ...node, ...parsed.data } : null;
|
|
102
|
+
}
|
|
103
|
+
// Windows saved before a schema field existed (e.g. `columnRatios`/`rowRatios`/
|
|
104
|
+
// `frameThickness`) load without it; the mesh builder then reads undefined and
|
|
105
|
+
// throws every frame. Zod-parse on load so schema defaults land, like doors.
|
|
106
|
+
function normalizeWindowNode(node) {
|
|
107
|
+
const parsed = WindowNodeSchema.safeParse(node);
|
|
108
|
+
return parsed.success ? { ...node, ...parsed.data } : null;
|
|
109
|
+
}
|
|
110
|
+
function normalizeShelfNode(node) {
|
|
111
|
+
const sanitized = {
|
|
112
|
+
...node,
|
|
113
|
+
children: getStringArray(node.children),
|
|
114
|
+
position: getVector3(node.position, [0, 0, 0]),
|
|
115
|
+
rotation: getVector3(node.rotation, [0, 0, 0]),
|
|
116
|
+
width: getFiniteNumberInRange(node.width, 1.2, 0.3, 3.0),
|
|
117
|
+
depth: getFiniteNumberInRange(node.depth, 0.3, 0.1, 1.0),
|
|
118
|
+
thickness: getFiniteNumberInRange(node.thickness, 0.04, 0.01, 0.1),
|
|
119
|
+
height: getFiniteNumberInRange(node.height, 0.9, 0.05, 2.5),
|
|
120
|
+
rows: Math.round(getFiniteNumberInRange(node.rows, 1, 1, 8)),
|
|
121
|
+
columns: Math.round(getFiniteNumberInRange(node.columns, 1, 1, 6)),
|
|
122
|
+
style: getEnumValue(node.style, ['wall-shelf', 'bookshelf', 'open-rack', 'cubby'], 'wall-shelf'),
|
|
123
|
+
withBack: getBoolean(node.withBack, false),
|
|
124
|
+
withSides: getBoolean(node.withSides, true),
|
|
125
|
+
withBottom: getBoolean(node.withBottom, false),
|
|
126
|
+
bracketStyle: getEnumValue(node.bracketStyle, ['minimal', 'industrial', 'hidden'], 'minimal'),
|
|
90
127
|
};
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
128
|
+
const parsed = ShelfNodeSchema.safeParse(sanitized);
|
|
129
|
+
return parsed.success ? parsed.data : null;
|
|
130
|
+
}
|
|
131
|
+
function normalizeElevatorNode(node) {
|
|
132
|
+
const sanitized = {
|
|
133
|
+
...node,
|
|
134
|
+
position: getVector3(node.position, [0, 0, 0]),
|
|
135
|
+
rotation: getFiniteNumber(node.rotation, 0),
|
|
136
|
+
width: getFiniteNumber(node.width, 1.84),
|
|
137
|
+
depth: getFiniteNumber(node.depth, 1.84),
|
|
138
|
+
shaftWidth: node.shaftWidth === undefined ? undefined : getFiniteNumber(node.shaftWidth, 1.84),
|
|
139
|
+
shaftDepth: node.shaftDepth === undefined ? undefined : getFiniteNumber(node.shaftDepth, 1.84),
|
|
140
|
+
shaftWallThickness: getFiniteNumber(node.shaftWallThickness, 0.09),
|
|
141
|
+
cabHeight: getFiniteNumber(node.cabHeight, 2.35),
|
|
142
|
+
doorWidth: getFiniteNumber(node.doorWidth, 0.95),
|
|
143
|
+
doorHeight: getFiniteNumber(node.doorHeight, 2.1),
|
|
144
|
+
fromLevelId: getNullableString(node.fromLevelId),
|
|
145
|
+
toLevelId: getNullableString(node.toLevelId),
|
|
146
|
+
servedLevelIds: node.servedLevelIds === undefined ? undefined : getStringArray(node.servedLevelIds),
|
|
147
|
+
disabledLevelIds: getStringArray(node.disabledLevelIds),
|
|
148
|
+
serviceOnlyLevelIds: getStringArray(node.serviceOnlyLevelIds),
|
|
149
|
+
defaultLevelId: getNullableString(node.defaultLevelId),
|
|
150
|
+
speed: getFiniteNumber(node.speed, 2.2),
|
|
151
|
+
doorDurationMs: getFiniteNumber(node.doorDurationMs, 900),
|
|
152
|
+
dwellMs: getFiniteNumber(node.dwellMs, 1400),
|
|
153
|
+
};
|
|
154
|
+
const parsed = ElevatorNodeSchema.safeParse(sanitized);
|
|
155
|
+
return parsed.success ? parsed.data : null;
|
|
156
|
+
}
|
|
157
|
+
function findBuildingIdForLevel(levelId, nodes) {
|
|
158
|
+
const level = nodes[levelId];
|
|
159
|
+
const directBuildingId = typeof level?.parentId === 'string' ? level.parentId : null;
|
|
160
|
+
if (directBuildingId && nodes[directBuildingId]?.type === 'building') {
|
|
161
|
+
return directBuildingId;
|
|
162
|
+
}
|
|
163
|
+
for (const [candidateId, candidate] of Object.entries(nodes)) {
|
|
164
|
+
if (candidate?.type !== 'building')
|
|
165
|
+
continue;
|
|
166
|
+
if (getStringArray(candidate.children).includes(levelId)) {
|
|
167
|
+
return candidateId;
|
|
94
168
|
}
|
|
95
|
-
return {
|
|
96
|
-
...node,
|
|
97
|
-
interiorMaterial: legacyFinish.material,
|
|
98
|
-
interiorMaterialPreset: legacyFinish.materialPreset,
|
|
99
|
-
exteriorMaterial: legacyFinish.material,
|
|
100
|
-
exteriorMaterialPreset: legacyFinish.materialPreset,
|
|
101
|
-
};
|
|
102
169
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
function migrateElevatorParent(id, node, nodes) {
|
|
173
|
+
const parentId = typeof node.parentId === 'string' ? node.parentId : null;
|
|
174
|
+
if (!parentId)
|
|
175
|
+
return node;
|
|
176
|
+
const parent = parentId ? nodes[parentId] : null;
|
|
177
|
+
if (parent?.type !== 'level')
|
|
178
|
+
return node;
|
|
179
|
+
const buildingId = findBuildingIdForLevel(parentId, nodes);
|
|
180
|
+
if (!buildingId)
|
|
181
|
+
return node;
|
|
182
|
+
const building = buildingId ? nodes[buildingId] : null;
|
|
183
|
+
if (building?.type !== 'building')
|
|
184
|
+
return node;
|
|
185
|
+
nodes[parentId] = {
|
|
186
|
+
...parent,
|
|
187
|
+
children: getStringArray(parent.children).filter((childId) => childId !== id),
|
|
188
|
+
};
|
|
189
|
+
const buildingChildren = getStringArray(building.children);
|
|
190
|
+
nodes[buildingId] = {
|
|
191
|
+
...building,
|
|
192
|
+
children: buildingChildren.includes(id) ? buildingChildren : [...buildingChildren, id],
|
|
193
|
+
};
|
|
194
|
+
return {
|
|
195
|
+
...node,
|
|
196
|
+
parentId: buildingId,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
// Reuse an already-minted scene material for an identical inline legacy
|
|
200
|
+
// material so a whole building painted one custom colour collapses to one
|
|
201
|
+
// shared datablock (mirrors `commitSlotPaint`'s dedupe-on-match).
|
|
202
|
+
function findMintedSceneMaterialRef(material, mintedMaterials) {
|
|
203
|
+
const target = JSON.stringify(material);
|
|
204
|
+
for (const sceneMaterial of Object.values(mintedMaterials)) {
|
|
205
|
+
if (JSON.stringify(sceneMaterial.material) === target) {
|
|
206
|
+
return toSceneMaterialRef(sceneMaterial.id);
|
|
207
|
+
}
|
|
109
208
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
// Turn a legacy surface spec (`{ material, materialPreset }`) into a
|
|
212
|
+
// `MaterialRef`: a preset that's already a `library:`/`scene:` ref is used
|
|
213
|
+
// as-is; an inline material mints (or reuses) a scene material. Returns
|
|
214
|
+
// undefined when the spec carries no material. Shared by every legacy→slots
|
|
215
|
+
// migration below.
|
|
216
|
+
function legacySpecToMaterialRef(spec, mintedMaterials) {
|
|
217
|
+
if (typeof spec.materialPreset === 'string' && parseMaterialRef(spec.materialPreset)) {
|
|
218
|
+
return spec.materialPreset;
|
|
219
|
+
}
|
|
220
|
+
if (spec.material !== undefined) {
|
|
221
|
+
const existing = findMintedSceneMaterialRef(spec.material, mintedMaterials);
|
|
222
|
+
if (existing)
|
|
223
|
+
return existing;
|
|
224
|
+
const id = generateSceneMaterialId();
|
|
225
|
+
mintedMaterials[id] = {
|
|
226
|
+
id,
|
|
227
|
+
name: `Material ${Object.keys(mintedMaterials).length + 1}`,
|
|
228
|
+
material: spec.material,
|
|
115
229
|
};
|
|
230
|
+
return toSceneMaterialRef(id);
|
|
231
|
+
}
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
// Move the retired inline `material*` / `interiorMaterial*` / `exteriorMaterial*`
|
|
235
|
+
// fields onto the unified `node.slots` model (interior / exterior → a
|
|
236
|
+
// `library:`/`scene:` ref), minting scene materials for inline customs into
|
|
237
|
+
// `mintedMaterials` (merged into the scene material map by the caller). Already
|
|
238
|
+
// slot-modelled walls and walls with no legacy material are left untouched.
|
|
239
|
+
function migrateWallSurfaceMaterials(node, mintedMaterials) {
|
|
240
|
+
if (node.slots && (node.slots.interior !== undefined || node.slots.exterior !== undefined)) {
|
|
241
|
+
return node;
|
|
242
|
+
}
|
|
243
|
+
const slots = { ...(node.slots ?? {}) };
|
|
244
|
+
for (const side of ['interior', 'exterior']) {
|
|
245
|
+
const spec = getEffectiveWallSurfaceMaterial(node, side);
|
|
246
|
+
const ref = legacySpecToMaterialRef(spec, mintedMaterials);
|
|
247
|
+
if (ref)
|
|
248
|
+
slots[side] = ref;
|
|
249
|
+
}
|
|
250
|
+
if (Object.keys(slots).length === 0) {
|
|
251
|
+
return node;
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
...node,
|
|
255
|
+
slots,
|
|
256
|
+
material: undefined,
|
|
257
|
+
materialPreset: undefined,
|
|
258
|
+
interiorMaterial: undefined,
|
|
259
|
+
interiorMaterialPreset: undefined,
|
|
260
|
+
exteriorMaterial: undefined,
|
|
261
|
+
exteriorMaterialPreset: undefined,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
// Move a kind's single legacy `material` / `materialPreset` onto its declared
|
|
265
|
+
// slots. A pre-slot-model node painted one material rendered that material on
|
|
266
|
+
// every part (each slot resolves `node.slots[slot]` → legacy → default), so the
|
|
267
|
+
// migration writes the same ref to every slot id the kind can expose — unused
|
|
268
|
+
// conditional slots are harmless. Already slot-modelled or unpainted nodes are
|
|
269
|
+
// left untouched. Mirrors `migrateWallSurfaceMaterials` for single-surface and
|
|
270
|
+
// whole-object kinds (slab, ceiling, fence, column, shelf).
|
|
271
|
+
function migrateSingleMaterialSlots(node, slotIds, mintedMaterials) {
|
|
272
|
+
if (node.slots && Object.keys(node.slots).length > 0) {
|
|
273
|
+
return node;
|
|
274
|
+
}
|
|
275
|
+
const ref = legacySpecToMaterialRef({ material: node.material, materialPreset: node.materialPreset }, mintedMaterials);
|
|
276
|
+
if (!ref) {
|
|
277
|
+
return node;
|
|
278
|
+
}
|
|
279
|
+
const slots = {};
|
|
280
|
+
for (const slotId of slotIds)
|
|
281
|
+
slots[slotId] = ref;
|
|
282
|
+
return { ...node, slots, material: undefined, materialPreset: undefined };
|
|
283
|
+
}
|
|
284
|
+
// Stair carries per-role legacy fields (`treadMaterial*` / `sideMaterial*` /
|
|
285
|
+
// `railingMaterial*`) plus a catch-all. Map each to its slot via the same
|
|
286
|
+
// fallback chain the renderer uses (`getEffectiveStairSurfaceMaterial`):
|
|
287
|
+
// tread→treads, side→body, railing→railing. Runs after
|
|
288
|
+
// `migrateStairSurfaceMaterials` has normalised the legacy fields.
|
|
289
|
+
function migrateStairSurfaceSlots(node, mintedMaterials) {
|
|
290
|
+
if (node.slots && Object.keys(node.slots).length > 0) {
|
|
291
|
+
return node;
|
|
292
|
+
}
|
|
293
|
+
const roleToSlot = [
|
|
294
|
+
['tread', 'treads'],
|
|
295
|
+
['side', 'body'],
|
|
296
|
+
['railing', 'railing'],
|
|
297
|
+
];
|
|
298
|
+
const slots = {};
|
|
299
|
+
for (const [role, slotId] of roleToSlot) {
|
|
300
|
+
const spec = getEffectiveStairSurfaceMaterial(node, role);
|
|
301
|
+
const ref = legacySpecToMaterialRef(spec, mintedMaterials);
|
|
302
|
+
if (ref)
|
|
303
|
+
slots[slotId] = ref;
|
|
304
|
+
}
|
|
305
|
+
if (Object.keys(slots).length === 0) {
|
|
306
|
+
return node;
|
|
116
307
|
}
|
|
117
|
-
return
|
|
308
|
+
return {
|
|
309
|
+
...node,
|
|
310
|
+
slots,
|
|
311
|
+
material: undefined,
|
|
312
|
+
materialPreset: undefined,
|
|
313
|
+
treadMaterial: undefined,
|
|
314
|
+
treadMaterialPreset: undefined,
|
|
315
|
+
sideMaterial: undefined,
|
|
316
|
+
sideMaterialPreset: undefined,
|
|
317
|
+
railingMaterial: undefined,
|
|
318
|
+
railingMaterialPreset: undefined,
|
|
319
|
+
};
|
|
118
320
|
}
|
|
119
321
|
function migrateStairSurfaceMaterials(node) {
|
|
120
322
|
const hasRailing = node.railingMaterial !== undefined || typeof node.railingMaterialPreset === 'string';
|
|
@@ -233,7 +435,20 @@ function migrateRoofSurfaceMaterials(node) {
|
|
|
233
435
|
return next;
|
|
234
436
|
}
|
|
235
437
|
function migrateNodes(nodes) {
|
|
236
|
-
|
|
438
|
+
// Repair pre-existing corruption (null children, zero-length walls) before
|
|
439
|
+
// any per-type migration runs, so already-saved scenes load cleanly.
|
|
440
|
+
const { nodes: healed } = healSceneNodes(nodes);
|
|
441
|
+
const patchedNodes = { ...healed };
|
|
442
|
+
// Scene materials minted while moving legacy wall fields onto `node.slots`;
|
|
443
|
+
// merged into the scene material map by the caller (`setScene`).
|
|
444
|
+
const mintedMaterials = {};
|
|
445
|
+
// Pass 1: all node types except elevator.
|
|
446
|
+
// Elevator migration (migrateElevatorParent) mutates level.children to remove
|
|
447
|
+
// the elevator ID. If the elevator is processed before its parent level in
|
|
448
|
+
// Object.entries order, the level migration in this same pass would then see
|
|
449
|
+
// a children array that still contains the elevator ID and filter it out as
|
|
450
|
+
// "missing" — corrupting the level. Running elevators in a second pass after
|
|
451
|
+
// all levels are stable avoids the race entirely.
|
|
237
452
|
for (const [id, node] of Object.entries(patchedNodes)) {
|
|
238
453
|
// 1. Item scale migration
|
|
239
454
|
if (node.type === 'item' && !('scale' in node)) {
|
|
@@ -244,6 +459,9 @@ function migrateNodes(nodes) {
|
|
|
244
459
|
const oldRoof = node;
|
|
245
460
|
const suffix = id.includes('_') ? id.split('_')[1] : Math.random().toString(36).slice(2);
|
|
246
461
|
const segmentId = `rseg_${suffix}`;
|
|
462
|
+
const segWidth = oldRoof.length ?? 8;
|
|
463
|
+
const segDepth = (oldRoof.leftWidth ?? 2.2) + (oldRoof.rightWidth ?? 2.2);
|
|
464
|
+
const legacyRoofHeight = oldRoof.height ?? 2.5;
|
|
247
465
|
const segment = {
|
|
248
466
|
object: 'node',
|
|
249
467
|
id: segmentId,
|
|
@@ -254,10 +472,18 @@ function migrateNodes(nodes) {
|
|
|
254
472
|
position: [0, 0, 0],
|
|
255
473
|
rotation: 0,
|
|
256
474
|
roofType: 'gable',
|
|
257
|
-
width:
|
|
258
|
-
depth:
|
|
259
|
-
|
|
260
|
-
|
|
475
|
+
width: segWidth,
|
|
476
|
+
depth: segDepth,
|
|
477
|
+
// Schema default (0.5), NOT 0: a zero-height wall builds a flat,
|
|
478
|
+
// degenerate CSG brush → "Coplanar clip not handled" + NaN geometry, so
|
|
479
|
+
// the migrated legacy roof never renders. New roofs use 0.5 too.
|
|
480
|
+
wallHeight: 0.5,
|
|
481
|
+
pitch: getPitchFromActiveRoofHeight({
|
|
482
|
+
roofType: 'gable',
|
|
483
|
+
width: segWidth,
|
|
484
|
+
depth: segDepth,
|
|
485
|
+
roofHeight: legacyRoofHeight,
|
|
486
|
+
}),
|
|
261
487
|
wallThickness: 0.1,
|
|
262
488
|
deckThickness: 0.1,
|
|
263
489
|
overhang: 0.3,
|
|
@@ -269,11 +495,47 @@ function migrateNodes(nodes) {
|
|
|
269
495
|
children: [segmentId],
|
|
270
496
|
};
|
|
271
497
|
}
|
|
498
|
+
// 2b. roof-segment: guarantee a valid positive pitch (degrees).
|
|
499
|
+
// Saved scenes wrote `roofHeight` in metres; the schema now stores `pitch`
|
|
500
|
+
// in degrees. Convert the legacy field when present, and — crucially — fall
|
|
501
|
+
// back to the schema default for any segment that carries no usable pitch or
|
|
502
|
+
// roofHeight (older/partial saves). Without this, the slope-frame guard
|
|
503
|
+
// resolves a missing pitch to a FLAT frame, so the roof renders as a slab.
|
|
504
|
+
// The migration result is cast, not zod-parsed, so the schema default never
|
|
505
|
+
// applies on its own — this branch is the only place it lands.
|
|
506
|
+
if (node.type === 'roof-segment') {
|
|
507
|
+
const currentPitch = node.pitch;
|
|
508
|
+
const hasValidPitch = typeof currentPitch === 'number' && currentPitch > 0;
|
|
509
|
+
if (!hasValidPitch) {
|
|
510
|
+
const { roofHeight, ...rest } = node;
|
|
511
|
+
const width = typeof node.width === 'number' ? node.width : 8;
|
|
512
|
+
const depth = typeof node.depth === 'number' ? node.depth : 6;
|
|
513
|
+
const roofType = (typeof node.roofType === 'string' ? node.roofType : 'gable');
|
|
514
|
+
const derived = typeof roofHeight === 'number' && roofHeight > 0
|
|
515
|
+
? getPitchFromActiveRoofHeight({ roofType, width, depth, roofHeight })
|
|
516
|
+
: 0;
|
|
517
|
+
// 40° matches the RoofSegmentNode schema default.
|
|
518
|
+
patchedNodes[id] = { ...rest, pitch: derived > 0 ? derived : 40 };
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (node.type === 'door') {
|
|
522
|
+
const normalized = normalizeDoorNode(node);
|
|
523
|
+
if (normalized) {
|
|
524
|
+
patchedNodes[id] = normalized;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (node.type === 'window') {
|
|
528
|
+
const normalized = normalizeWindowNode(node);
|
|
529
|
+
if (normalized) {
|
|
530
|
+
patchedNodes[id] = normalized;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
272
533
|
if (node.type === 'stair') {
|
|
273
534
|
const normalized = normalizeStairNode(migrateStairSurfaceMaterials(node));
|
|
274
535
|
if (normalized) {
|
|
275
536
|
patchedNodes[id] = normalized;
|
|
276
537
|
}
|
|
538
|
+
patchedNodes[id] = migrateStairSurfaceSlots(patchedNodes[id], mintedMaterials);
|
|
277
539
|
}
|
|
278
540
|
if (node.type === 'stair-segment') {
|
|
279
541
|
const normalized = normalizeStairSegmentNode(node);
|
|
@@ -282,13 +544,172 @@ function migrateNodes(nodes) {
|
|
|
282
544
|
}
|
|
283
545
|
}
|
|
284
546
|
if (node.type === 'wall') {
|
|
285
|
-
patchedNodes[id] = migrateWallSurfaceMaterials(patchedNodes[id]);
|
|
547
|
+
patchedNodes[id] = migrateWallSurfaceMaterials(patchedNodes[id], mintedMaterials);
|
|
548
|
+
}
|
|
549
|
+
// Cabinet v2→v3: node-level `doorStyle` was dead (geometry reads only the
|
|
550
|
+
// per-compartment `doorType`) and was removed from both cabinet schemas;
|
|
551
|
+
// `handlePosition: 'edge'` behaved identically to 'auto' and was dropped
|
|
552
|
+
// from the enum; the compartment stack became a discriminated union that
|
|
553
|
+
// rejects a `cooktopLayout` mismatched to its gas/induction type (the old
|
|
554
|
+
// loose schema ignored it).
|
|
555
|
+
if (node.type === 'cabinet' || node.type === 'cabinet-module') {
|
|
556
|
+
const { doorStyle: _doorStyle, ...rest } = node;
|
|
557
|
+
const next = rest;
|
|
558
|
+
if (next.handlePosition === 'edge')
|
|
559
|
+
next.handlePosition = 'auto';
|
|
560
|
+
if (Array.isArray(next.stack)) {
|
|
561
|
+
next.stack = next.stack.map((compartment) => {
|
|
562
|
+
if (!compartment || typeof compartment !== 'object')
|
|
563
|
+
return compartment;
|
|
564
|
+
const layout = compartment.cooktopLayout;
|
|
565
|
+
if (typeof layout !== 'string')
|
|
566
|
+
return compartment;
|
|
567
|
+
if (compartment.type === 'cooktop-gas' && !layout.startsWith('gas-')) {
|
|
568
|
+
return { ...compartment, cooktopLayout: 'gas-4burner' };
|
|
569
|
+
}
|
|
570
|
+
if (compartment.type === 'cooktop-induction' && !layout.startsWith('induction-')) {
|
|
571
|
+
return { ...compartment, cooktopLayout: 'induction-4zone' };
|
|
572
|
+
}
|
|
573
|
+
return compartment;
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
patchedNodes[id] = next;
|
|
577
|
+
}
|
|
578
|
+
if (node.type === 'slab' || node.type === 'ceiling') {
|
|
579
|
+
patchedNodes[id] = migrateSingleMaterialSlots(patchedNodes[id], ['surface'], mintedMaterials);
|
|
580
|
+
}
|
|
581
|
+
if (node.type === 'fence') {
|
|
582
|
+
patchedNodes[id] = migrateSingleMaterialSlots(patchedNodes[id], ['posts', 'infill', 'base', 'rail'], mintedMaterials);
|
|
583
|
+
}
|
|
584
|
+
if (node.type === 'column') {
|
|
585
|
+
patchedNodes[id] = migrateSingleMaterialSlots(patchedNodes[id], ['shaft', 'base', 'capital', 'frame'], mintedMaterials);
|
|
586
|
+
}
|
|
587
|
+
if (node.type === 'shelf') {
|
|
588
|
+
const normalized = normalizeShelfNode(node);
|
|
589
|
+
if (normalized) {
|
|
590
|
+
patchedNodes[id] = normalized;
|
|
591
|
+
}
|
|
592
|
+
patchedNodes[id] = migrateSingleMaterialSlots(patchedNodes[id], ['shelves', 'frame', 'back'], mintedMaterials);
|
|
593
|
+
}
|
|
594
|
+
// Roof-segment hosting was added in this migration cycle (the same
|
|
595
|
+
// pattern as shelf above). Older segments saved before the schema
|
|
596
|
+
// gained `children` need the field initialised so
|
|
597
|
+
// `createNode(chimney, segmentId)` finds an array to append to —
|
|
598
|
+
// without this every "Add Element" click on the roof panel results
|
|
599
|
+
// in an orphaned accessory (parented in scene state but never
|
|
600
|
+
// appended to `seg.children`, so the renderer's recursive
|
|
601
|
+
// `<NodeRenderer>` mount never sees it).
|
|
602
|
+
if (node.type === 'roof-segment' && !Array.isArray(node.children)) {
|
|
603
|
+
patchedNodes[id] = { ...node, children: [] };
|
|
604
|
+
}
|
|
605
|
+
// Roof-hosted wall children (door / window / item) originally stored
|
|
606
|
+
// SEGMENT-LOCAL positions with the face yaw in rotation[1]; the
|
|
607
|
+
// format moved to explicit `roofFace` + FACE-LOCAL coords so the
|
|
608
|
+
// renderer's face frame can track segment edits live. Convert in
|
|
609
|
+
// place: face from the old cardinal yaw, u/v from the outer-plane
|
|
610
|
+
// projection, z re-based from the outer plane to the wall mid-plane.
|
|
611
|
+
if ((node.type === 'door' || node.type === 'window' || node.type === 'item') &&
|
|
612
|
+
typeof node.roofSegmentId === 'string' &&
|
|
613
|
+
node.roofFace === undefined) {
|
|
614
|
+
const current = patchedNodes[id];
|
|
615
|
+
const segment = patchedNodes[current.roofSegmentId];
|
|
616
|
+
if (segment?.type === 'roof-segment') {
|
|
617
|
+
const tau = Math.PI * 2;
|
|
618
|
+
const yaw = (((current.rotation?.[1] ?? 0) % tau) + tau) % tau;
|
|
619
|
+
const eps = 1e-3;
|
|
620
|
+
const face = yaw < eps || tau - yaw < eps
|
|
621
|
+
? 'front'
|
|
622
|
+
: Math.abs(yaw - Math.PI) < eps
|
|
623
|
+
? 'back'
|
|
624
|
+
: Math.abs(yaw - Math.PI / 2) < eps
|
|
625
|
+
? 'right'
|
|
626
|
+
: Math.abs(yaw - (3 * Math.PI) / 2) < eps
|
|
627
|
+
? 'left'
|
|
628
|
+
: null;
|
|
629
|
+
if (face) {
|
|
630
|
+
const { u, v, dist } = segmentPointToRoofWallFace(segment, face, current.position);
|
|
631
|
+
patchedNodes[id] = {
|
|
632
|
+
...current,
|
|
633
|
+
roofFace: face,
|
|
634
|
+
position: [u, v, dist + (segment.wallThickness ?? 0.1) / 2],
|
|
635
|
+
rotation: [0, 0, 0],
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
}
|
|
286
639
|
}
|
|
287
640
|
if (node.type === 'roof') {
|
|
288
641
|
patchedNodes[id] = migrateRoofSurfaceMaterials(patchedNodes[id]);
|
|
289
642
|
}
|
|
643
|
+
// Legacy: site.children used to hold nested BuildingNode / ItemNode
|
|
644
|
+
// objects (see the SiteNode schema before the children-as-ids fix).
|
|
645
|
+
// Flatten any leftover nested children into ids, and absorb the
|
|
646
|
+
// embedded nodes into the flat map so the rest of the loader can
|
|
647
|
+
// treat the site like every other parent.
|
|
648
|
+
if (node.type === 'site' && Array.isArray(node.children)) {
|
|
649
|
+
let needsFlatten = false;
|
|
650
|
+
const flattened = [];
|
|
651
|
+
for (const child of node.children) {
|
|
652
|
+
if (typeof child === 'string') {
|
|
653
|
+
flattened.push(child);
|
|
654
|
+
}
|
|
655
|
+
else if (child && typeof child === 'object' && typeof child.id === 'string') {
|
|
656
|
+
needsFlatten = true;
|
|
657
|
+
flattened.push(child.id);
|
|
658
|
+
if (!patchedNodes[child.id]) {
|
|
659
|
+
patchedNodes[child.id] = { ...child, parentId: id };
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (needsFlatten) {
|
|
664
|
+
patchedNodes[id] = { ...node, children: flattened };
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
// Level children normalization.
|
|
668
|
+
// Pre-0.9.1 JSONs may carry child IDs that no longer exist in the node
|
|
669
|
+
// map (e.g. elevator IDs that lived under a level before the elevator
|
|
670
|
+
// parent migration moved them up to building). If those dangling IDs are
|
|
671
|
+
// left in place, collectReachableNodeIds marks the level as having
|
|
672
|
+
// reachable children that don't exist, which corrupts the scene graph
|
|
673
|
+
// traversal and leaves the LevelNode in a broken state — making floors
|
|
674
|
+
// impossible to drag or delete after import.
|
|
675
|
+
// We intentionally do NOT filter by type prefix here; being permissive
|
|
676
|
+
// about which types are allowed as children prevents data loss when new
|
|
677
|
+
// child types are added to the schema in the future.
|
|
678
|
+
if (node.type === 'level') {
|
|
679
|
+
const rawChildren = getStringArray(node.children);
|
|
680
|
+
const validChildren = rawChildren.filter((childId) => {
|
|
681
|
+
const exists = Boolean(patchedNodes[childId]);
|
|
682
|
+
if (!exists) {
|
|
683
|
+
console.warn('[migrateNodes] level', id, 'references missing child', childId, '— dropping');
|
|
684
|
+
}
|
|
685
|
+
return exists;
|
|
686
|
+
});
|
|
687
|
+
const levelNumber = getFiniteNumber(node.level, 0);
|
|
688
|
+
patchedNodes[id] = {
|
|
689
|
+
...node,
|
|
690
|
+
level: levelNumber,
|
|
691
|
+
children: validChildren,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
// Pass 2: elevator migration.
|
|
696
|
+
// migrateElevatorParent mutates the parent level's children array (removes
|
|
697
|
+
// the elevator ID from it). Running this after Pass 1 guarantees that the
|
|
698
|
+
// level normalization above has already seen a clean children list — if we
|
|
699
|
+
// ran elevator migration inside Pass 1, the order of Object.entries
|
|
700
|
+
// iteration would be non-deterministic: processing an elevator before its
|
|
701
|
+
// parent level would mutate the level's children mid-iteration, potentially
|
|
702
|
+
// causing the level branch above to see a stale node reference.
|
|
703
|
+
for (const [id, node] of Object.entries(patchedNodes)) {
|
|
704
|
+
if (node.type !== 'elevator')
|
|
705
|
+
continue;
|
|
706
|
+
const parentMigrated = migrateElevatorParent(id, node, patchedNodes);
|
|
707
|
+
const normalized = normalizeElevatorNode(parentMigrated);
|
|
708
|
+
if (normalized) {
|
|
709
|
+
patchedNodes[id] = normalized;
|
|
710
|
+
}
|
|
290
711
|
}
|
|
291
|
-
return patchedNodes;
|
|
712
|
+
return { nodes: patchedNodes, mintedMaterials };
|
|
292
713
|
}
|
|
293
714
|
function getNodeChildIds(node) {
|
|
294
715
|
if (!('children' in node && Array.isArray(node.children))) {
|
|
@@ -347,6 +768,9 @@ const useScene = create()(temporal((set, get) => ({
|
|
|
347
768
|
dirtyNodes: new Set(),
|
|
348
769
|
// 4. Collections
|
|
349
770
|
collections: {},
|
|
771
|
+
materials: {},
|
|
772
|
+
installedPlugins: [],
|
|
773
|
+
hasExplicitPluginInstallState: false,
|
|
350
774
|
// 5. Read-only lock
|
|
351
775
|
readOnly: false,
|
|
352
776
|
setReadOnly: (readOnly) => set({ readOnly }),
|
|
@@ -356,15 +780,25 @@ const useScene = create()(temporal((set, get) => ({
|
|
|
356
780
|
rootNodeIds: [],
|
|
357
781
|
dirtyNodes: new Set(),
|
|
358
782
|
collections: {},
|
|
783
|
+
materials: {},
|
|
784
|
+
installedPlugins: [],
|
|
785
|
+
hasExplicitPluginInstallState: false,
|
|
359
786
|
});
|
|
360
787
|
},
|
|
361
788
|
clearScene: () => {
|
|
789
|
+
const installedPlugins = get().installedPlugins;
|
|
790
|
+
const hasExplicitPluginInstallState = get().hasExplicitPluginInstallState;
|
|
362
791
|
get().unloadScene();
|
|
363
792
|
get().loadScene(); // Default scene
|
|
793
|
+
set({ installedPlugins, hasExplicitPluginInstallState });
|
|
364
794
|
},
|
|
365
|
-
setScene: (nodes, rootNodeIds) => {
|
|
795
|
+
setScene: (nodes, rootNodeIds, extra) => {
|
|
366
796
|
// Apply backward compatibility migrations
|
|
367
|
-
const patchedNodes = migrateNodes(nodes);
|
|
797
|
+
const { nodes: patchedNodes, mintedMaterials } = migrateNodes(nodes);
|
|
798
|
+
// Scene materials minted by the wall legacy→slots migration join the
|
|
799
|
+
// loaded palette (existing refs win on id collision — there are none,
|
|
800
|
+
// ids are freshly generated).
|
|
801
|
+
const materials = { ...mintedMaterials, ...(extra?.materials ?? {}) };
|
|
368
802
|
// Remove orphans: nodes whose parentId points to a non-existent node
|
|
369
803
|
const cleanedNodes = { ...patchedNodes };
|
|
370
804
|
for (const node of Object.values(cleanedNodes)) {
|
|
@@ -373,12 +807,6 @@ const useScene = create()(temporal((set, get) => ({
|
|
|
373
807
|
delete cleanedNodes[node.id];
|
|
374
808
|
}
|
|
375
809
|
}
|
|
376
|
-
set({
|
|
377
|
-
nodes: cleanedNodes,
|
|
378
|
-
rootNodeIds,
|
|
379
|
-
dirtyNodes: new Set(),
|
|
380
|
-
collections: {},
|
|
381
|
-
});
|
|
382
810
|
const normalizedRootNodeIds = normalizeRootNodeIds(cleanedNodes, rootNodeIds);
|
|
383
811
|
const reachableNodeIds = collectReachableNodeIds(cleanedNodes, normalizedRootNodeIds);
|
|
384
812
|
if (normalizedRootNodeIds.length > 0) {
|
|
@@ -389,17 +817,47 @@ const useScene = create()(temporal((set, get) => ({
|
|
|
389
817
|
delete cleanedNodes[node.id];
|
|
390
818
|
}
|
|
391
819
|
}
|
|
820
|
+
// Single tracked `set`: with zundo, every tracked write pushes the
|
|
821
|
+
// pre-write state onto `pastStates`. Writing the scene in two steps
|
|
822
|
+
// (as this used to) exposed a half-normalized intermediate state —
|
|
823
|
+
// and the pre-load (possibly empty) state — as undo targets.
|
|
392
824
|
set({
|
|
393
825
|
nodes: cleanedNodes,
|
|
394
826
|
rootNodeIds: normalizedRootNodeIds,
|
|
395
827
|
dirtyNodes: new Set(),
|
|
396
|
-
collections: {},
|
|
828
|
+
collections: extra?.collections ?? {},
|
|
829
|
+
materials,
|
|
830
|
+
installedPlugins: Array.from(new Set(extra?.installedPlugins ?? [])),
|
|
831
|
+
hasExplicitPluginInstallState: extra?.hasExplicitPluginInstallState ?? false,
|
|
397
832
|
});
|
|
398
833
|
// Mark all nodes as dirty to trigger re-validation
|
|
399
834
|
Object.values(cleanedNodes).forEach((node) => {
|
|
400
835
|
get().markDirty(node.id);
|
|
401
836
|
});
|
|
402
837
|
},
|
|
838
|
+
setInstalledPlugins: (pluginIds, options) => {
|
|
839
|
+
if (get().readOnly)
|
|
840
|
+
return;
|
|
841
|
+
const nextInstalledPlugins = Array.from(new Set(pluginIds));
|
|
842
|
+
const previousInstalledPlugins = get().installedPlugins;
|
|
843
|
+
const dirtyNodes = new Set(get().dirtyNodes);
|
|
844
|
+
for (const node of Object.values(get().nodes)) {
|
|
845
|
+
if (!getNodePluginId(node.type))
|
|
846
|
+
continue;
|
|
847
|
+
if (!isNodeKindEnabled(node.type, nextInstalledPlugins)) {
|
|
848
|
+
dirtyNodes.delete(node.id);
|
|
849
|
+
}
|
|
850
|
+
else if (!isNodeKindEnabled(node.type, previousInstalledPlugins)) {
|
|
851
|
+
if (nodeRegistry.get(node.type)?.dirtyTracking !== false)
|
|
852
|
+
dirtyNodes.add(node.id);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
set({
|
|
856
|
+
installedPlugins: nextInstalledPlugins,
|
|
857
|
+
hasExplicitPluginInstallState: options?.explicit ?? get().hasExplicitPluginInstallState,
|
|
858
|
+
dirtyNodes,
|
|
859
|
+
});
|
|
860
|
+
},
|
|
403
861
|
loadScene: () => {
|
|
404
862
|
if (get().rootNodeIds.length > 0) {
|
|
405
863
|
// Assign all nodes as dirty to force re-validation
|
|
@@ -417,7 +875,7 @@ const useScene = create()(temporal((set, get) => ({
|
|
|
417
875
|
children: [level0.id],
|
|
418
876
|
});
|
|
419
877
|
const site = SiteNode.parse({
|
|
420
|
-
children: [building],
|
|
878
|
+
children: [building.id],
|
|
421
879
|
});
|
|
422
880
|
// Define all nodes flat
|
|
423
881
|
const nodes = {
|
|
@@ -430,6 +888,11 @@ const useScene = create()(temporal((set, get) => ({
|
|
|
430
888
|
set({ nodes, rootNodeIds });
|
|
431
889
|
},
|
|
432
890
|
markDirty: (id) => {
|
|
891
|
+
const node = get().nodes[id];
|
|
892
|
+
if (node && !isNodeKindEnabled(node.type, get().installedPlugins))
|
|
893
|
+
return;
|
|
894
|
+
if (node && nodeRegistry.get(node.type)?.dirtyTracking === false)
|
|
895
|
+
return;
|
|
433
896
|
get().dirtyNodes.add(id);
|
|
434
897
|
},
|
|
435
898
|
clearDirty: (id) => {
|
|
@@ -437,6 +900,7 @@ const useScene = create()(temporal((set, get) => ({
|
|
|
437
900
|
},
|
|
438
901
|
createNodes: (ops) => nodeActions.createNodesAction(set, get, ops),
|
|
439
902
|
createNode: (node, parentId) => nodeActions.createNodesAction(set, get, [{ node, parentId }]),
|
|
903
|
+
applyNodeChanges: (changes) => nodeActions.applyNodeChangesAction(set, get, changes),
|
|
440
904
|
updateNodes: (updates) => nodeActions.updateNodesAction(set, get, updates),
|
|
441
905
|
updateNode: (id, data) => nodeActions.updateNodesAction(set, get, [{ id, data }]),
|
|
442
906
|
// --- DELETE ---
|
|
@@ -540,10 +1004,37 @@ const useScene = create()(temporal((set, get) => ({
|
|
|
540
1004
|
return { collections: nextCollections, nodes: nextNodes };
|
|
541
1005
|
});
|
|
542
1006
|
},
|
|
1007
|
+
// --- SCENE MATERIALS ---
|
|
1008
|
+
addSceneMaterial: (material) => {
|
|
1009
|
+
if (get().readOnly)
|
|
1010
|
+
return;
|
|
1011
|
+
set((state) => ({
|
|
1012
|
+
materials: { ...state.materials, [material.id]: material },
|
|
1013
|
+
}));
|
|
1014
|
+
},
|
|
1015
|
+
updateSceneMaterial: (id, data) => {
|
|
1016
|
+
if (get().readOnly)
|
|
1017
|
+
return;
|
|
1018
|
+
set((state) => {
|
|
1019
|
+
const material = state.materials[id];
|
|
1020
|
+
if (!material)
|
|
1021
|
+
return state;
|
|
1022
|
+
return { materials: { ...state.materials, [id]: { ...material, ...data } } };
|
|
1023
|
+
});
|
|
1024
|
+
},
|
|
1025
|
+
removeSceneMaterial: (id) => {
|
|
1026
|
+
if (get().readOnly)
|
|
1027
|
+
return;
|
|
1028
|
+
set((state) => {
|
|
1029
|
+
const materials = { ...state.materials };
|
|
1030
|
+
delete materials[id];
|
|
1031
|
+
return { materials };
|
|
1032
|
+
});
|
|
1033
|
+
},
|
|
543
1034
|
}), {
|
|
544
1035
|
partialize: (state) => {
|
|
545
|
-
const { nodes, rootNodeIds, collections } = state;
|
|
546
|
-
return { nodes, rootNodeIds, collections };
|
|
1036
|
+
const { nodes, rootNodeIds, collections, materials, installedPlugins } = state;
|
|
1037
|
+
return { nodes, rootNodeIds, collections, materials, installedPlugins };
|
|
547
1038
|
},
|
|
548
1039
|
limit: 50, // Limit to last 50 actions
|
|
549
1040
|
}));
|
|
@@ -554,6 +1045,12 @@ let prevFutureLength = 0;
|
|
|
554
1045
|
let prevNodesSnapshot = null;
|
|
555
1046
|
export function clearSceneHistory() {
|
|
556
1047
|
resetSceneHistoryPauseDepth();
|
|
1048
|
+
// Resetting the pause-depth counter without resuming would strand the
|
|
1049
|
+
// temporal store in `isTracking: false` if a pause window was active when
|
|
1050
|
+
// the scene was (re)loaded — every edit after the load would then be
|
|
1051
|
+
// invisible to undo. Resume unconditionally so the cleared history starts
|
|
1052
|
+
// tracking from the loaded baseline.
|
|
1053
|
+
useScene.temporal.getState().resume();
|
|
557
1054
|
useScene.temporal.getState().clear();
|
|
558
1055
|
prevPastLength = 0;
|
|
559
1056
|
prevFutureLength = 0;
|