@pascal-app/core 0.9.1 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -10
- package/dist/events/bus.d.ts +49 -2
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/hooks/scene-registry/item-clip-registry.d.ts +18 -0
- package/dist/hooks/scene-registry/item-clip-registry.d.ts.map +1 -0
- package/dist/hooks/scene-registry/item-clip-registry.js +10 -0
- package/dist/hooks/scene-registry/scene-registry.d.ts +8 -1
- package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
- package/dist/hooks/scene-registry/scene-registry.js +27 -1
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +17 -1
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/floor-placed-elevation.js +61 -8
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +135 -28
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-manager.js +427 -271
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +46 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.js +208 -9
- package/dist/hooks/spatial-grid/support-host-id.d.ts +3 -0
- package/dist/hooks/spatial-grid/support-host-id.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/support-host-id.js +2 -0
- package/dist/hooks/spatial-grid/support-host-patch.d.ts +51 -0
- package/dist/hooks/spatial-grid/support-host-patch.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/support-host-patch.js +164 -0
- package/dist/index.d.ts +33 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -10
- package/dist/lib/level-name.d.ts +4 -0
- package/dist/lib/level-name.d.ts.map +1 -0
- package/dist/lib/level-name.js +10 -0
- package/dist/lib/measurement-geometry.d.ts +23 -0
- package/dist/lib/measurement-geometry.d.ts.map +1 -0
- package/dist/lib/measurement-geometry.js +249 -0
- package/dist/lib/polygon-geometry.d.ts +0 -1
- package/dist/lib/polygon-geometry.d.ts.map +1 -1
- package/dist/lib/polygon-geometry.js +0 -18
- package/dist/lib/selection-proxy.d.ts +4 -0
- package/dist/lib/selection-proxy.d.ts.map +1 -0
- package/dist/lib/selection-proxy.js +12 -0
- package/dist/lib/slab-polygon.d.ts +31 -2
- package/dist/lib/slab-polygon.d.ts.map +1 -1
- package/dist/lib/slab-polygon.js +557 -31
- package/dist/lib/slots.d.ts +14 -0
- package/dist/lib/slots.d.ts.map +1 -0
- package/dist/lib/slots.js +26 -0
- package/dist/lib/space-detection.d.ts +40 -6
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +574 -192
- package/dist/lib/terrain-brush.d.ts +163 -0
- package/dist/lib/terrain-brush.d.ts.map +1 -0
- package/dist/lib/terrain-brush.js +359 -0
- package/dist/lib/terrain-codec.d.ts +51 -0
- package/dist/lib/terrain-codec.d.ts.map +1 -0
- package/dist/lib/terrain-codec.js +167 -0
- package/dist/lib/terrain-field.d.ts +150 -0
- package/dist/lib/terrain-field.d.ts.map +1 -0
- package/dist/lib/terrain-field.js +248 -0
- package/dist/lib/terrain-raycast.d.ts +44 -0
- package/dist/lib/terrain-raycast.d.ts.map +1 -0
- package/dist/lib/terrain-raycast.js +188 -0
- package/dist/lib/terrain-source.d.ts +60 -0
- package/dist/lib/terrain-source.d.ts.map +1 -0
- package/dist/lib/terrain-source.js +90 -0
- package/dist/lib/terrain-support.d.ts +60 -0
- package/dist/lib/terrain-support.d.ts.map +1 -0
- package/dist/lib/terrain-support.js +150 -0
- package/dist/lib/wall-distance.d.ts +60 -0
- package/dist/lib/wall-distance.d.ts.map +1 -0
- package/dist/lib/wall-distance.js +85 -0
- package/dist/lib/zone-quantities.d.ts +21 -0
- package/dist/lib/zone-quantities.d.ts.map +1 -0
- package/dist/lib/zone-quantities.js +428 -0
- package/dist/material-library.d.ts +28 -1
- package/dist/material-library.d.ts.map +1 -1
- package/dist/material-library.js +2233 -441
- package/dist/registry/handles.d.ts +91 -3
- package/dist/registry/handles.d.ts.map +1 -1
- package/dist/registry/index.d.ts +3 -3
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +1 -1
- package/dist/registry/registry.d.ts +46 -3
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +82 -1
- package/dist/registry/subtree.d.ts.map +1 -1
- package/dist/registry/subtree.js +8 -1
- package/dist/registry/types.d.ts +781 -39
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/schema/index.d.ts +27 -8
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +23 -6
- package/dist/schema/material.d.ts +3 -2
- package/dist/schema/material.d.ts.map +1 -1
- package/dist/schema/material.js +7 -5
- package/dist/schema/nodes/box-vent.d.ts +2 -2
- package/dist/schema/nodes/cabinet.d.ts +536 -0
- package/dist/schema/nodes/cabinet.d.ts.map +1 -0
- package/dist/schema/nodes/cabinet.js +139 -0
- package/dist/schema/nodes/ceiling.d.ts +4 -3
- package/dist/schema/nodes/ceiling.d.ts.map +1 -1
- package/dist/schema/nodes/ceiling.js +11 -1
- package/dist/schema/nodes/chimney.d.ts +4 -4
- package/dist/schema/nodes/column.d.ts +4 -2
- package/dist/schema/nodes/column.d.ts.map +1 -1
- package/dist/schema/nodes/column.js +5 -0
- package/dist/schema/nodes/construction-dimension.d.ts +195 -0
- package/dist/schema/nodes/construction-dimension.d.ts.map +1 -0
- package/dist/schema/nodes/construction-dimension.js +151 -0
- package/dist/schema/nodes/cupola.d.ts +2 -2
- package/dist/schema/nodes/door.d.ts +42 -5
- package/dist/schema/nodes/door.d.ts.map +1 -1
- package/dist/schema/nodes/door.js +32 -0
- package/dist/schema/nodes/dormer.d.ts +8 -8
- package/dist/schema/nodes/dormer.js +1 -1
- package/dist/schema/nodes/downspout.d.ts +2 -2
- package/dist/schema/nodes/duct-fitting.d.ts +91 -0
- package/dist/schema/nodes/duct-fitting.d.ts.map +1 -0
- package/dist/schema/nodes/duct-fitting.js +92 -0
- package/dist/schema/nodes/duct-segment.d.ts +61 -0
- package/dist/schema/nodes/duct-segment.d.ts.map +1 -0
- package/dist/schema/nodes/duct-segment.js +73 -0
- package/dist/schema/nodes/duct-terminal.d.ts +58 -0
- package/dist/schema/nodes/duct-terminal.d.ts.map +1 -0
- package/dist/schema/nodes/duct-terminal.js +53 -0
- package/dist/schema/nodes/elevator.d.ts +3 -2
- package/dist/schema/nodes/elevator.d.ts.map +1 -1
- package/dist/schema/nodes/elevator.js +3 -0
- package/dist/schema/nodes/eyebrow-vent.d.ts +2 -2
- package/dist/schema/nodes/fence.d.ts +20 -2
- package/dist/schema/nodes/fence.d.ts.map +1 -1
- package/dist/schema/nodes/fence.js +33 -2
- package/dist/schema/nodes/gutter.d.ts +2 -2
- package/dist/schema/nodes/hvac-equipment.d.ts +64 -0
- package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -0
- package/dist/schema/nodes/hvac-equipment.js +57 -0
- package/dist/schema/nodes/item.d.ts +9 -0
- package/dist/schema/nodes/item.d.ts.map +1 -1
- package/dist/schema/nodes/item.js +24 -0
- package/dist/schema/nodes/level.d.ts +2 -1
- package/dist/schema/nodes/level.d.ts.map +1 -1
- package/dist/schema/nodes/level.js +12 -31
- package/dist/schema/nodes/lineset.d.ts +42 -0
- package/dist/schema/nodes/lineset.d.ts.map +1 -0
- package/dist/schema/nodes/lineset.js +38 -0
- package/dist/schema/nodes/liquid-line.d.ts +34 -0
- package/dist/schema/nodes/liquid-line.d.ts.map +1 -0
- package/dist/schema/nodes/liquid-line.js +24 -0
- package/dist/schema/nodes/measurement.d.ts +306 -0
- package/dist/schema/nodes/measurement.d.ts.map +1 -0
- package/dist/schema/nodes/measurement.js +87 -0
- package/dist/schema/nodes/pipe-fitting.d.ts +56 -0
- package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -0
- package/dist/schema/nodes/pipe-fitting.js +45 -0
- package/dist/schema/nodes/pipe-segment.d.ts +48 -0
- package/dist/schema/nodes/pipe-segment.d.ts.map +1 -0
- package/dist/schema/nodes/pipe-segment.js +36 -0
- package/dist/schema/nodes/pipe-trap.d.ts +43 -0
- package/dist/schema/nodes/pipe-trap.d.ts.map +1 -0
- package/dist/schema/nodes/pipe-trap.js +36 -0
- package/dist/schema/nodes/ridge-vent.d.ts +18 -3
- package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
- package/dist/schema/nodes/ridge-vent.js +221 -3
- package/dist/schema/nodes/roof-segment-shape.d.ts +90 -0
- package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-shape.js +318 -0
- package/dist/schema/nodes/roof-segment-walls.d.ts +101 -0
- package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-walls.js +330 -0
- package/dist/schema/nodes/roof-segment.d.ts +84 -8
- package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment.js +290 -5
- package/dist/schema/nodes/roof.d.ts +8 -8
- package/dist/schema/nodes/shelf.d.ts +4 -2
- package/dist/schema/nodes/shelf.d.ts.map +1 -1
- package/dist/schema/nodes/shelf.js +8 -0
- package/dist/schema/nodes/site.d.ts +9 -0
- package/dist/schema/nodes/site.d.ts.map +1 -1
- package/dist/schema/nodes/site.js +8 -6
- package/dist/schema/nodes/skylight.d.ts +10 -10
- package/dist/schema/nodes/slab.d.ts +8 -2
- package/dist/schema/nodes/slab.d.ts.map +1 -1
- package/dist/schema/nodes/slab.js +18 -2
- package/dist/schema/nodes/solar-panel.d.ts +4 -4
- package/dist/schema/nodes/spawn.d.ts +1 -0
- package/dist/schema/nodes/spawn.d.ts.map +1 -1
- package/dist/schema/nodes/spawn.js +2 -0
- package/dist/schema/nodes/stair-segment.d.ts +4 -4
- package/dist/schema/nodes/stair.d.ts +16 -13
- package/dist/schema/nodes/stair.d.ts.map +1 -1
- package/dist/schema/nodes/stair.js +11 -1
- package/dist/schema/nodes/structural-grid.d.ts +27 -0
- package/dist/schema/nodes/structural-grid.d.ts.map +1 -0
- package/dist/schema/nodes/structural-grid.js +17 -0
- package/dist/schema/nodes/turbine-vent.d.ts +2 -2
- package/dist/schema/nodes/wall.d.ts +248 -6
- package/dist/schema/nodes/wall.d.ts.map +1 -1
- package/dist/schema/nodes/wall.js +266 -0
- package/dist/schema/nodes/window.d.ts +40 -3
- package/dist/schema/nodes/window.d.ts.map +1 -1
- package/dist/schema/nodes/window.js +30 -0
- package/dist/schema/nodes/zone.d.ts +22 -0
- package/dist/schema/nodes/zone.d.ts.map +1 -1
- package/dist/schema/nodes/zone.js +21 -0
- package/dist/schema/scene-material.d.ts +41 -0
- package/dist/schema/scene-material.d.ts.map +1 -0
- package/dist/schema/scene-material.js +9 -0
- package/dist/schema/terrain.d.ts +28 -0
- package/dist/schema/terrain.d.ts.map +1 -0
- package/dist/schema/terrain.js +30 -0
- package/dist/schema/types.d.ts +1300 -97
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +27 -0
- package/dist/services/__fixtures__/wall-plane-top-boundary-repro.d.ts +80 -0
- package/dist/services/__fixtures__/wall-plane-top-boundary-repro.d.ts.map +1 -0
- package/dist/services/__fixtures__/wall-plane-top-boundary-repro.js +89 -0
- package/dist/services/alignment-anchors.d.ts.map +1 -1
- package/dist/services/alignment-anchors.js +30 -1
- package/dist/services/alignment.d.ts +5 -0
- package/dist/services/alignment.d.ts.map +1 -1
- package/dist/services/alignment.js +2 -0
- package/dist/services/hosting.d.ts +8 -0
- package/dist/services/hosting.d.ts.map +1 -1
- package/dist/services/hosting.js +13 -4
- package/dist/services/index.d.ts +9 -2
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +9 -2
- package/dist/services/level-height.d.ts +27 -0
- package/dist/services/level-height.d.ts.map +1 -0
- package/dist/services/level-height.js +94 -0
- package/dist/services/opening-guides.d.ts +141 -0
- package/dist/services/opening-guides.d.ts.map +1 -0
- package/dist/services/opening-guides.js +267 -0
- package/dist/services/port-connectivity.d.ts +115 -0
- package/dist/services/port-connectivity.d.ts.map +1 -0
- package/dist/services/port-connectivity.js +300 -0
- package/dist/services/riser-diagram.d.ts +40 -0
- package/dist/services/riser-diagram.d.ts.map +1 -0
- package/dist/services/riser-diagram.js +110 -0
- package/dist/services/snap.d.ts +9 -1
- package/dist/services/snap.d.ts.map +1 -1
- package/dist/services/snap.js +21 -1
- package/dist/services/storey.d.ts +103 -0
- package/dist/services/storey.d.ts.map +1 -0
- package/dist/services/storey.js +296 -0
- package/dist/services/system-graph.d.ts +30 -0
- package/dist/services/system-graph.d.ts.map +1 -0
- package/dist/services/system-graph.js +164 -0
- package/dist/services/validate-dwv.d.ts +32 -0
- package/dist/services/validate-dwv.d.ts.map +1 -0
- package/dist/services/validate-dwv.js +118 -0
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +141 -4
- package/dist/store/history-control.d.ts +31 -0
- package/dist/store/history-control.d.ts.map +1 -1
- package/dist/store/history-control.js +130 -0
- package/dist/store/use-live-terrain.d.ts +65 -0
- package/dist/store/use-live-terrain.d.ts.map +1 -0
- package/dist/store/use-live-terrain.js +31 -0
- package/dist/store/use-live-transforms.d.ts +8 -0
- package/dist/store/use-live-transforms.d.ts.map +1 -1
- package/dist/store/use-scene.d.ts +47 -3
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +992 -55
- package/dist/systems/elevator/elevator-opening-system.d.ts +1 -0
- package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-opening-system.js +27 -21
- package/dist/systems/elevator/elevator-service.d.ts +0 -2
- package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-service.js +2 -24
- package/dist/systems/fence/fence-centerline.d.ts +12 -0
- package/dist/systems/fence/fence-centerline.d.ts.map +1 -0
- package/dist/systems/fence/fence-centerline.js +37 -0
- package/dist/systems/fence/fence-spline.d.ts +39 -0
- package/dist/systems/fence/fence-spline.d.ts.map +1 -0
- package/dist/systems/fence/fence-spline.js +196 -0
- package/dist/systems/slab/slab-placement.d.ts +11 -0
- package/dist/systems/slab/slab-placement.d.ts.map +1 -0
- package/dist/systems/slab/slab-placement.js +14 -0
- package/dist/systems/slab/slab-support.d.ts +137 -0
- package/dist/systems/slab/slab-support.d.ts.map +1 -0
- package/dist/systems/slab/slab-support.js +557 -0
- package/dist/systems/stair/stair-footprint.d.ts.map +1 -1
- package/dist/systems/stair/stair-footprint.js +21 -11
- package/dist/systems/stair/stair-opening-preview.d.ts +1300 -505
- package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-sync.js +19 -33
- package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-system.js +32 -4
- package/dist/systems/stair/stair-rise.d.ts +22 -0
- package/dist/systems/stair/stair-rise.d.ts.map +1 -0
- package/dist/systems/stair/stair-rise.js +79 -0
- package/dist/systems/wall/wall-curve.d.ts +10 -0
- package/dist/systems/wall/wall-curve.d.ts.map +1 -1
- package/dist/systems/wall/wall-curve.js +1 -1
- package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
- package/dist/systems/wall/wall-mitering.js +44 -8
- package/dist/systems/wall/wall-top.d.ts +40 -0
- package/dist/systems/wall/wall-top.d.ts.map +1 -0
- package/dist/systems/wall/wall-top.js +46 -0
- package/dist/utils/clone-scene-graph.d.ts +1 -0
- package/dist/utils/clone-scene-graph.d.ts.map +1 -1
- package/dist/utils/clone-scene-graph.js +56 -5
- package/dist/utils/heal-scene-graph.d.ts +18 -0
- package/dist/utils/heal-scene-graph.d.ts.map +1 -0
- package/dist/utils/heal-scene-graph.js +106 -0
- package/dist/validation/validate-build-json.d.ts +3 -0
- package/dist/validation/validate-build-json.d.ts.map +1 -1
- package/dist/validation/validate-build-json.js +90 -5
- package/package.json +4 -4
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +0 -2
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +0 -1
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +0 -273
- package/dist/lib/space-detection-pause.test.d.ts +0 -2
- package/dist/lib/space-detection-pause.test.d.ts.map +0 -1
- package/dist/lib/space-detection-pause.test.js +0 -42
- package/dist/lib/space-detection.test.d.ts +0 -2
- package/dist/lib/space-detection.test.d.ts.map +0 -1
- package/dist/lib/space-detection.test.js +0 -77
- package/dist/registry/registry.test.d.ts +0 -2
- package/dist/registry/registry.test.d.ts.map +0 -1
- package/dist/registry/registry.test.js +0 -183
- package/dist/registry/relations-resolver.test.d.ts +0 -2
- package/dist/registry/relations-resolver.test.d.ts.map +0 -1
- package/dist/registry/relations-resolver.test.js +0 -183
- package/dist/registry/scene-api.test.d.ts +0 -2
- package/dist/registry/scene-api.test.d.ts.map +0 -1
- package/dist/registry/scene-api.test.js +0 -183
- package/dist/registry/subtree.test.d.ts +0 -2
- package/dist/registry/subtree.test.d.ts.map +0 -1
- package/dist/registry/subtree.test.js +0 -133
- package/dist/schema/asset-url.test.d.ts +0 -2
- package/dist/schema/asset-url.test.d.ts.map +0 -1
- package/dist/schema/asset-url.test.js +0 -134
- package/dist/services/alignment-anchors.test.d.ts +0 -2
- package/dist/services/alignment-anchors.test.d.ts.map +0 -1
- package/dist/services/alignment-anchors.test.js +0 -383
- package/dist/services/alignment.test.d.ts +0 -2
- package/dist/services/alignment.test.d.ts.map +0 -1
- package/dist/services/alignment.test.js +0 -92
- package/dist/services/drag-session.test.d.ts +0 -2
- package/dist/services/drag-session.test.d.ts.map +0 -1
- package/dist/services/drag-session.test.js +0 -219
- package/dist/services/hosting.test.d.ts +0 -2
- package/dist/services/hosting.test.d.ts.map +0 -1
- package/dist/services/hosting.test.js +0 -199
- package/dist/services/movement.test.d.ts +0 -2
- package/dist/services/movement.test.d.ts.map +0 -1
- package/dist/services/movement.test.js +0 -133
- package/dist/services/single-undo-dance.test.d.ts +0 -2
- package/dist/services/single-undo-dance.test.d.ts.map +0 -1
- package/dist/services/single-undo-dance.test.js +0 -210
- package/dist/services/snap.test.d.ts +0 -2
- package/dist/services/snap.test.d.ts.map +0 -1
- package/dist/services/snap.test.js +0 -95
- package/dist/store/actions/node-mutation-sanitize.test.d.ts +0 -2
- package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +0 -1
- package/dist/store/actions/node-mutation-sanitize.test.js +0 -147
- package/dist/store/actions/reparent.test.d.ts +0 -2
- package/dist/store/actions/reparent.test.d.ts.map +0 -1
- package/dist/store/actions/reparent.test.js +0 -212
- package/dist/store/use-scene-elevator-migration.test.d.ts +0 -2
- package/dist/store/use-scene-elevator-migration.test.d.ts.map +0 -1
- package/dist/store/use-scene-elevator-migration.test.js +0 -109
- package/dist/systems/elevator/elevator-opening-sync.test.d.ts +0 -2
- package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +0 -1
- package/dist/systems/elevator/elevator-opening-sync.test.js +0 -146
- package/dist/systems/elevator/elevator-runtime.test.d.ts +0 -2
- package/dist/systems/elevator/elevator-runtime.test.d.ts.map +0 -1
- package/dist/systems/elevator/elevator-runtime.test.js +0 -64
- package/dist/systems/stair/stair-opening-preview.test.d.ts +0 -2
- package/dist/systems/stair/stair-opening-preview.test.d.ts.map +0 -1
- package/dist/systems/stair/stair-opening-preview.test.js +0 -80
- package/dist/systems/stair/stair-opening-sync.test.d.ts +0 -2
- package/dist/systems/stair/stair-opening-sync.test.d.ts.map +0 -1
- package/dist/systems/stair/stair-opening-sync.test.js +0 -446
- package/dist/utils/clone-scene-graph.test.d.ts +0 -2
- package/dist/utils/clone-scene-graph.test.d.ts.map +0 -1
- package/dist/utils/clone-scene-graph.test.js +0 -59
package/dist/registry/types.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ComponentType } from 'react';
|
|
2
|
-
import type { BufferGeometry, Object3D } from 'three';
|
|
2
|
+
import type { AnimationClip, BufferGeometry, Object3D, Ray } from 'three';
|
|
3
3
|
import type { ZodObject, z } from 'zod';
|
|
4
|
-
import type { MaterialSchema } from '../schema/material';
|
|
4
|
+
import type { MaterialSchema, MaterialTarget } from '../schema/material';
|
|
5
|
+
import type { MeasurementFeatureReference, MeasurementPoint } from '../schema/nodes/measurement';
|
|
6
|
+
import type { SceneMaterial, SceneMaterialId } from '../schema/scene-material';
|
|
5
7
|
import type { AnyNode, AnyNodeId } from '../schema/types';
|
|
6
8
|
import type { HandleList } from './handles';
|
|
7
9
|
import type { CloneNodesIntoOptions, Subtree } from './subtree';
|
|
@@ -16,20 +18,29 @@ export type GeometryContext = {
|
|
|
16
18
|
parent: AnyNode | null;
|
|
17
19
|
/**
|
|
18
20
|
* Pre-computed level-batch data, populated by the dispatcher when the
|
|
19
|
-
* kind declares `def.computeLevelData
|
|
20
|
-
* `def.
|
|
21
|
-
* single frame, so kinds
|
|
22
|
-
* data (wall mitering, gradient
|
|
23
|
-
* don't pay an O(N²) recomputation cost.
|
|
21
|
+
* kind declares `def.computeLevelData` (3D) or
|
|
22
|
+
* `def.computeFloorplanLevelData` (2D). Shared across every builder call
|
|
23
|
+
* in the same level batch within a single frame/render pass, so kinds
|
|
24
|
+
* whose geometry depends on cross-sibling data (wall mitering, gradient
|
|
25
|
+
* sky uniforms across a zone, etc.) don't pay an O(N²) recomputation cost.
|
|
24
26
|
*
|
|
25
27
|
* Typed as `unknown` at the framework boundary — kinds cast to their
|
|
26
|
-
* own `LevelData` shape inside `def.geometry` (the
|
|
27
|
-
* both the
|
|
28
|
-
* consumer, so the cast is internal).
|
|
29
|
-
* geometry` calls today; not used by `def.floorplan` (which already
|
|
30
|
-
* has cheap access to siblings through `ctx.siblings`).
|
|
28
|
+
* own `LevelData` shape inside `def.geometry` / `def.floorplan` (the
|
|
29
|
+
* same kind owns both the compute hook's return shape and the builder
|
|
30
|
+
* consumer, so the cast is internal).
|
|
31
31
|
*/
|
|
32
32
|
levelData?: unknown;
|
|
33
|
+
/**
|
|
34
|
+
* The scene's shared material library (`useScene.materials`), passed so a
|
|
35
|
+
* pure geometry builder can resolve `scene:<id>` slot refs without importing
|
|
36
|
+
* `useScene`. Populated by `<GeometrySystem>` for every `def.geometry` call;
|
|
37
|
+
* undefined for `def.floorplan`. `library:<id>` refs resolve against the
|
|
38
|
+
* static catalog and need no store, so builders only consult this for
|
|
39
|
+
* `scene:` refs.
|
|
40
|
+
*/
|
|
41
|
+
materials?: Record<SceneMaterialId, SceneMaterial>;
|
|
42
|
+
/** Opaque host/plugin context. Core never interprets extension values. */
|
|
43
|
+
extensions?: Readonly<Record<string, unknown>>;
|
|
33
44
|
/**
|
|
34
45
|
* Optional view state — only populated for `def.floorplan` builders. The
|
|
35
46
|
* 2D floor-plan layer surfaces selection / hover here so kinds can vary
|
|
@@ -40,6 +51,7 @@ export type GeometryContext = {
|
|
|
40
51
|
*/
|
|
41
52
|
viewState?: {
|
|
42
53
|
selected: boolean;
|
|
54
|
+
unit: 'metric' | 'imperial';
|
|
43
55
|
/** Marquee or programmatic highlight — shows selected chrome without keyboard focus. */
|
|
44
56
|
highlighted: boolean;
|
|
45
57
|
/** Pointer-hovered. */
|
|
@@ -60,6 +72,69 @@ export type GeometryContext = {
|
|
|
60
72
|
palette: FloorplanPalette;
|
|
61
73
|
};
|
|
62
74
|
};
|
|
75
|
+
export type MeasurementSnapKind = 'endpoint' | 'midpoint' | 'edge' | 'center' | 'face' | 'ridge' | 'height';
|
|
76
|
+
export type MeasurementFeatureGeometry = {
|
|
77
|
+
kind: 'point';
|
|
78
|
+
point: MeasurementPoint;
|
|
79
|
+
} | {
|
|
80
|
+
kind: 'segment';
|
|
81
|
+
start: MeasurementPoint;
|
|
82
|
+
end: MeasurementPoint;
|
|
83
|
+
} | {
|
|
84
|
+
kind: 'path';
|
|
85
|
+
points: MeasurementPoint[];
|
|
86
|
+
closed?: boolean;
|
|
87
|
+
} | {
|
|
88
|
+
kind: 'polygon';
|
|
89
|
+
points: MeasurementPoint[];
|
|
90
|
+
};
|
|
91
|
+
export type MeasurementFeature = {
|
|
92
|
+
/** Stable within the node kind; presentation labels must not be used as IDs. */
|
|
93
|
+
id: string;
|
|
94
|
+
label: string;
|
|
95
|
+
snapKind: MeasurementSnapKind;
|
|
96
|
+
geometry: MeasurementFeatureGeometry;
|
|
97
|
+
/**
|
|
98
|
+
* Level-local surface normal for contact markers. Continuous features may
|
|
99
|
+
* provide the normal from `resolve(...)` after applying reference parameters.
|
|
100
|
+
*/
|
|
101
|
+
normal?: MeasurementPoint;
|
|
102
|
+
/** Higher values win when multiple candidates occupy the same screen-space radius. */
|
|
103
|
+
priority?: number;
|
|
104
|
+
};
|
|
105
|
+
export type MeasurementFeatureBinding = {
|
|
106
|
+
featureId: string;
|
|
107
|
+
point: MeasurementPoint;
|
|
108
|
+
parameters?: Record<string, string | number | boolean>;
|
|
109
|
+
distance: number;
|
|
110
|
+
};
|
|
111
|
+
export type QuickMeasurementQuantity = 'length' | 'area' | 'volume';
|
|
112
|
+
export type QuickMeasurementMetric = {
|
|
113
|
+
key: string;
|
|
114
|
+
label: string;
|
|
115
|
+
abbreviation: string;
|
|
116
|
+
quantity: QuickMeasurementQuantity;
|
|
117
|
+
/** Canonical metres, square metres, or cubic metres according to `quantity`. */
|
|
118
|
+
value: number;
|
|
119
|
+
};
|
|
120
|
+
export type QuickMeasurementReport = {
|
|
121
|
+
title: string;
|
|
122
|
+
kindLabel: string;
|
|
123
|
+
/** Stable level-local label anchor chosen by the node kind. */
|
|
124
|
+
anchor: MeasurementPoint;
|
|
125
|
+
metrics: QuickMeasurementMetric[];
|
|
126
|
+
note?: string;
|
|
127
|
+
};
|
|
128
|
+
export type MeasurementContribution<N = AnyNode> = {
|
|
129
|
+
/** Enumerates semantic candidates for hover, quick measure, and snapping. */
|
|
130
|
+
features: (node: N, ctx: GeometryContext) => MeasurementFeature[];
|
|
131
|
+
/** Resolve IDs that cannot be fully enumerated by `features`. */
|
|
132
|
+
resolve?: (node: N, ctx: GeometryContext, reference: MeasurementFeatureReference) => MeasurementFeature | null;
|
|
133
|
+
/** Kind-aware nearest semantic binding for a level-local surface hit. */
|
|
134
|
+
match?: (node: N, ctx: GeometryContext, point: MeasurementPoint, maxDistance: number) => MeasurementFeatureBinding | null;
|
|
135
|
+
/** Live, non-persistent quantities shown by the smart measurement tool. */
|
|
136
|
+
quickMeasure?: (node: N, ctx: GeometryContext) => QuickMeasurementReport | null;
|
|
137
|
+
};
|
|
63
138
|
export type FloorplanPalette = {
|
|
64
139
|
selectedStroke: string;
|
|
65
140
|
selectedFill: string;
|
|
@@ -92,12 +167,16 @@ export type FloorplanPalette = {
|
|
|
92
167
|
measurementLabelText: string;
|
|
93
168
|
};
|
|
94
169
|
export type FloorplanPoint = readonly [x: number, y: number];
|
|
170
|
+
export type DimensionTerminator = 'architectural-tick' | 'filled-arrow' | 'open-arrow' | 'dot';
|
|
171
|
+
export type DimensionTextPosition = 'above' | 'centered';
|
|
95
172
|
export type FloorplanStyle = {
|
|
96
173
|
stroke?: string;
|
|
97
174
|
fill?: string;
|
|
98
175
|
strokeWidth?: number;
|
|
99
176
|
strokeDasharray?: string;
|
|
100
177
|
opacity?: number;
|
|
178
|
+
/** Opaque renderer/plugin metadata. Core never interprets these values. */
|
|
179
|
+
metadata?: Readonly<Record<string, unknown>>;
|
|
101
180
|
/**
|
|
102
181
|
* When `'non-scaling-stroke'`, the SVG renderer interprets `strokeWidth`
|
|
103
182
|
* as a constant screen-pixel width regardless of viewport zoom. Maps
|
|
@@ -133,11 +212,62 @@ export type FloorplanStyle = {
|
|
|
133
212
|
*/
|
|
134
213
|
cursor?: string;
|
|
135
214
|
};
|
|
215
|
+
export type NodePort = {
|
|
216
|
+
/** Stable identifier within the node, e.g. 'start', 'end', 'branch'. */
|
|
217
|
+
id: string;
|
|
218
|
+
/** Level-local meters. */
|
|
219
|
+
position: readonly [number, number, number];
|
|
220
|
+
/** Unit vector pointing OUT of the port (away from the node body). */
|
|
221
|
+
direction: readonly [number, number, number];
|
|
222
|
+
/** Nominal connection diameter in inches. For a rect / oval port this is
|
|
223
|
+
* the area-equivalent round size, so a round run still mates sensibly. */
|
|
224
|
+
diameter: number;
|
|
225
|
+
/** Which distribution loop the port belongs to, e.g. 'supply' | 'return'. */
|
|
226
|
+
system?: string;
|
|
227
|
+
/** Cross-section of the connection. Omitted = round at `diameter`. A duct
|
|
228
|
+
* run joining a rect / oval port adopts this shape and rolls its
|
|
229
|
+
* cross-section to line up with the collar. */
|
|
230
|
+
shape?: 'round' | 'rect' | 'oval';
|
|
231
|
+
/** Rect / oval cross-section in inches: width is the collar's horizontal
|
|
232
|
+
* face at roll 0, height the vertical one. */
|
|
233
|
+
width?: number;
|
|
234
|
+
height?: number;
|
|
235
|
+
};
|
|
136
236
|
export type ToolHint = {
|
|
137
237
|
/** Key combo or input label, e.g. 'Left click', 'Shift', 'Esc'. */
|
|
138
238
|
key: string;
|
|
139
239
|
/** Description of what the input does. Sentence case. */
|
|
140
240
|
label: string;
|
|
241
|
+
/**
|
|
242
|
+
* Only show this hint once the in-progress draft has at least this many
|
|
243
|
+
* vertices (reads `useEditor.draftVertexCount`). Lets a polygon tool's
|
|
244
|
+
* "Finish" hint appear only when finishing is actually possible (≥ 3 points),
|
|
245
|
+
* so the HUD reflects reality. Omit for always-shown hints.
|
|
246
|
+
*/
|
|
247
|
+
minDraftVertices?: number;
|
|
248
|
+
/**
|
|
249
|
+
* Render this hint as a live mode chip — like the snapping / continuation
|
|
250
|
+
* chips — instead of a static key row: the HUD shows the current value's
|
|
251
|
+
* label and clicking the row (or pressing `key`) cycles it. The kind owns
|
|
252
|
+
* the state (typically its own ephemeral store); `label` above becomes the
|
|
253
|
+
* fallback when the current value has no entry in `chip.labels`.
|
|
254
|
+
*/
|
|
255
|
+
chip?: ToolHintChip;
|
|
256
|
+
};
|
|
257
|
+
export type ToolHintChip = {
|
|
258
|
+
/** Subscribe to live value changes (Zustand-store-like); returns unsubscribe. */
|
|
259
|
+
subscribe: (onChange: () => void) => () => void;
|
|
260
|
+
/** Current value token, resolved through `labels` / `icons` for display. */
|
|
261
|
+
value: () => string;
|
|
262
|
+
/** Advance to the next value — the chip's click action. The keyboard path is
|
|
263
|
+
* the tool's own handler for the hint's `key`; both must hit the same store. */
|
|
264
|
+
cycle: () => void;
|
|
265
|
+
/** value → chip row label, e.g. `{ cabinet: 'Type: Cabinet', island: 'Type: Island' }`. */
|
|
266
|
+
labels: Record<string, string>;
|
|
267
|
+
/** value → iconify icon name. */
|
|
268
|
+
icons?: Record<string, string>;
|
|
269
|
+
/** Hover tooltip, e.g. 'Placement type — click or press I to cycle'. */
|
|
270
|
+
tooltip?: string;
|
|
141
271
|
};
|
|
142
272
|
export type FloorplanGeometry = ({
|
|
143
273
|
kind: 'path';
|
|
@@ -209,6 +339,8 @@ export type FloorplanGeometry = ({
|
|
|
209
339
|
* of the floor-plan's scene rotation (default 90°).
|
|
210
340
|
*/
|
|
211
341
|
upright?: boolean;
|
|
342
|
+
/** Opaque renderer/plugin metadata. Core never interprets these values. */
|
|
343
|
+
metadata?: Readonly<Record<string, unknown>>;
|
|
212
344
|
}
|
|
213
345
|
/**
|
|
214
346
|
* Bitmap overlay — captured top-down asset thumbnail, AI-generated
|
|
@@ -239,6 +371,8 @@ export type FloorplanGeometry = ({
|
|
|
239
371
|
translate?: FloorplanPoint;
|
|
240
372
|
rotate?: number;
|
|
241
373
|
};
|
|
374
|
+
/** Opaque renderer/plugin metadata. Core never interprets these values. */
|
|
375
|
+
metadata?: Readonly<Record<string, unknown>>;
|
|
242
376
|
}
|
|
243
377
|
/**
|
|
244
378
|
* Hatched fill overlay — same polygon shape as the kind's main fill but
|
|
@@ -396,7 +530,8 @@ export type FloorplanGeometry = ({
|
|
|
396
530
|
}
|
|
397
531
|
/**
|
|
398
532
|
* Centered length / distance label. Renders as a small rounded
|
|
399
|
-
* background plate
|
|
533
|
+
* background plate by default, or as outlined text when `appearance`
|
|
534
|
+
* is `'outlined'`, oriented along `angle` (radians). The
|
|
400
535
|
* 2D layer flips the label upright when it would otherwise be upside
|
|
401
536
|
* down. Use this for simple "what length am I?" badges (fence, item
|
|
402
537
|
* width, draft preview).
|
|
@@ -408,6 +543,26 @@ export type FloorplanGeometry = ({
|
|
|
408
543
|
text: string;
|
|
409
544
|
/** Rotation in radians. The renderer auto-flips to keep text upright. */
|
|
410
545
|
angle: number;
|
|
546
|
+
/** Keep the plate horizontal on screen instead of following a segment. */
|
|
547
|
+
screenUpright?: boolean;
|
|
548
|
+
/** Perpendicular screen-pixel offset from the anchor segment. */
|
|
549
|
+
offsetPx?: number;
|
|
550
|
+
/** Match map-style labels without changing the default editing badge. */
|
|
551
|
+
appearance?: 'plate' | 'outlined';
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Equal-spacing badge — a small accent pill marking one gap in a run of
|
|
555
|
+
* (near-)equally-spaced openings (the 2D counterpart of Figma's "=" distance
|
|
556
|
+
* chips). Emitted once per equal gap so the repeated value reads as a rhythm.
|
|
557
|
+
* `text` is the shared gap distance; `angle` orients the pill along the wall
|
|
558
|
+
* (the renderer auto-flips it upright).
|
|
559
|
+
*/
|
|
560
|
+
| {
|
|
561
|
+
kind: 'equal-spacing-badge';
|
|
562
|
+
point: FloorplanPoint;
|
|
563
|
+
text: string;
|
|
564
|
+
/** Rotation in radians. */
|
|
565
|
+
angle: number;
|
|
411
566
|
}
|
|
412
567
|
/**
|
|
413
568
|
* Architect's dimension overlay — extension lines from the edge
|
|
@@ -426,15 +581,62 @@ export type FloorplanGeometry = ({
|
|
|
426
581
|
kind: 'dimension';
|
|
427
582
|
start: FloorplanPoint;
|
|
428
583
|
end: FloorplanPoint;
|
|
584
|
+
/**
|
|
585
|
+
* Optional explicit dimension-line endpoints. Use these when the
|
|
586
|
+
* measured origins sit at different depths, such as stepped facades or
|
|
587
|
+
* an exterior column row. Extension lines still originate at
|
|
588
|
+
* `start`/`end`, while the measurement is drawn between these aligned
|
|
589
|
+
* baseline points.
|
|
590
|
+
*/
|
|
591
|
+
dimensionStart?: FloorplanPoint;
|
|
592
|
+
dimensionEnd?: FloorplanPoint;
|
|
429
593
|
/** Outward-pointing unit normal — the dimension line offsets along this. */
|
|
430
594
|
offsetNormal: FloorplanPoint;
|
|
431
595
|
/** Distance (plan units) from the edge to the dimension line. */
|
|
432
596
|
offsetDistance: number;
|
|
433
597
|
/** How far past the offset point the extension line continues. */
|
|
434
598
|
extensionOvershoot: number;
|
|
599
|
+
/** Optional gap before each extension line starts. Defaults to the project/document profile. */
|
|
600
|
+
extensionStartGap?: number;
|
|
601
|
+
/** Dimension-line terminator. Defaults to an architectural tick. */
|
|
602
|
+
terminator?: DimensionTerminator;
|
|
603
|
+
/** Dimension text position relative to the baseline. Defaults above the line. */
|
|
604
|
+
textPosition?: DimensionTextPosition;
|
|
435
605
|
text: string;
|
|
436
606
|
/** Optional override for the line/text colour. Defaults to the palette accent. */
|
|
437
607
|
stroke?: string;
|
|
608
|
+
} | {
|
|
609
|
+
kind: 'dimension-string';
|
|
610
|
+
segments: readonly {
|
|
611
|
+
start: FloorplanPoint;
|
|
612
|
+
end: FloorplanPoint;
|
|
613
|
+
/**
|
|
614
|
+
* Optional explicit dimension-line endpoints. Use these when the
|
|
615
|
+
* measured origins sit at different depths, such as stepped facades or
|
|
616
|
+
* an exterior column row. Extension lines still originate at
|
|
617
|
+
* `start`/`end`, while the measurement is drawn between these aligned
|
|
618
|
+
* baseline points.
|
|
619
|
+
*/
|
|
620
|
+
dimensionStart?: FloorplanPoint;
|
|
621
|
+
dimensionEnd?: FloorplanPoint;
|
|
622
|
+
text: string;
|
|
623
|
+
}[];
|
|
624
|
+
/** Outward-pointing unit normal shared by every segment in the string. */
|
|
625
|
+
offsetNormal: FloorplanPoint;
|
|
626
|
+
/** Distance (plan units) from each measured origin to its dimension line. */
|
|
627
|
+
offsetDistance: number;
|
|
628
|
+
/** How far past each offset point the extension line continues. */
|
|
629
|
+
extensionOvershoot: number;
|
|
630
|
+
/** Optional gap before each extension line starts. Defaults to the project/document profile. */
|
|
631
|
+
extensionStartGap?: number;
|
|
632
|
+
/** Dimension-line terminator shared by every segment. Defaults to an architectural tick. */
|
|
633
|
+
terminator?: DimensionTerminator;
|
|
634
|
+
/** Dimension text position shared by every segment. Defaults above the line. */
|
|
635
|
+
textPosition?: DimensionTextPosition;
|
|
636
|
+
/** Optional override for the line/text colour. Defaults to the palette accent. */
|
|
637
|
+
stroke?: string;
|
|
638
|
+
/** Opaque renderer/plugin metadata. Core never interprets these values. */
|
|
639
|
+
metadata?: Readonly<Record<string, unknown>>;
|
|
438
640
|
};
|
|
439
641
|
export type FloorplanAffordancePoint = readonly [x: number, y: number];
|
|
440
642
|
export type FloorplanAffordanceModifiers = {
|
|
@@ -447,17 +649,11 @@ export type FloorplanAffordanceSession = {
|
|
|
447
649
|
/** Node IDs the drag may mutate. Used by the dispatcher for the snapshot. */
|
|
448
650
|
affectedIds: AnyNodeId[];
|
|
449
651
|
/**
|
|
450
|
-
* Run a single drag tick.
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
* affected fields.
|
|
456
|
-
* - **Live-override preview**: implementation publishes per-frame
|
|
457
|
-
* overrides to `useLiveNodeOverrides` (or another preview store);
|
|
458
|
-
* `useScene` stays untouched during the drag. The session must
|
|
459
|
-
* also expose `commit()` below, since there's no scene diff for
|
|
460
|
-
* the dispatcher to write back.
|
|
652
|
+
* Run a single drag tick. New implementations publish per-frame overrides to
|
|
653
|
+
* `useLiveNodeOverrides` / `useLiveTransforms` (or another preview store);
|
|
654
|
+
* `useScene` stays untouched during the drag. Legacy sessions that still
|
|
655
|
+
* write preview state into `useScene` are supported only by the dispatcher's
|
|
656
|
+
* snapshot-diff compatibility path.
|
|
461
657
|
*
|
|
462
658
|
* Snap logic, linked-node cascade, and angle locking live here.
|
|
463
659
|
*/
|
|
@@ -472,13 +668,12 @@ export type FloorplanAffordanceSession = {
|
|
|
472
668
|
*/
|
|
473
669
|
canCommit(): boolean;
|
|
474
670
|
/**
|
|
475
|
-
* Optional atomic
|
|
476
|
-
* `FloorplanMoveTargetSession`.
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
* live overrides it published in `apply()`.
|
|
671
|
+
* Optional atomic commit hook — mirror of the same field on
|
|
672
|
+
* `FloorplanMoveTargetSession`. New live-preview sessions should provide
|
|
673
|
+
* this so the dispatcher can revert to the pre-drag baseline, resume
|
|
674
|
+
* history, then call `commit()`. The session owns the full final write
|
|
675
|
+
* (typically `applyNodeChanges` or `updateNodes`) plus clearing any live
|
|
676
|
+
* overrides it published in `apply()`.
|
|
482
677
|
*/
|
|
483
678
|
commit?(): void;
|
|
484
679
|
};
|
|
@@ -499,8 +694,10 @@ export type FloorplanMoveTargetSession = {
|
|
|
499
694
|
/** Node IDs the move may mutate. Used by the dispatcher for snapshot capture. */
|
|
500
695
|
affectedIds: AnyNodeId[];
|
|
501
696
|
/**
|
|
502
|
-
* Single move-preview tick. Implementations
|
|
503
|
-
*
|
|
697
|
+
* Single move-preview tick. Implementations publish per-frame overrides to
|
|
698
|
+
* `useLiveNodeOverrides` / `useLiveTransforms`; the scene store stays
|
|
699
|
+
* untouched during the drag. Provide `commit()` below so the final scene
|
|
700
|
+
* write happens once at the end.
|
|
504
701
|
*/
|
|
505
702
|
apply(args: {
|
|
506
703
|
planPoint: FloorplanAffordancePoint;
|
|
@@ -529,6 +726,14 @@ export type FloorplanMoveTargetSession = {
|
|
|
529
726
|
* returns.
|
|
530
727
|
*/
|
|
531
728
|
commit?(): void;
|
|
729
|
+
/**
|
|
730
|
+
* Optional R-key flip toggle. Kinds with a directional facing
|
|
731
|
+
* (door / window: front ↔ back) implement this so the overlay can flip
|
|
732
|
+
* the orientation mid-placement before commit. Toggling just records the
|
|
733
|
+
* intent; the visible change lands when the overlay re-runs `apply()` with
|
|
734
|
+
* the last pointer position. Kinds with no facing leave it unset.
|
|
735
|
+
*/
|
|
736
|
+
flipSide?(): void;
|
|
532
737
|
};
|
|
533
738
|
export type FloorplanMoveTarget<N> = (args: {
|
|
534
739
|
node: N;
|
|
@@ -541,17 +746,92 @@ export type Plugin = {
|
|
|
541
746
|
};
|
|
542
747
|
export type AnyNodeDefinition = NodeDefinition<ZodObject<any>>;
|
|
543
748
|
export type SurfaceRole = 'wall' | 'floor' | 'ceiling' | 'roof' | 'joinery' | 'glazing' | 'furnishing';
|
|
749
|
+
/** Role a kind plays in a duct / pipe / lineset distribution system. */
|
|
750
|
+
export type DistributionRole = 'run' | 'fitting' | 'terminal' | 'equipment';
|
|
751
|
+
/**
|
|
752
|
+
* A kind's snapping profile (see `NodeDefinition.snapProfile`).
|
|
753
|
+
* - `'item'` free object (furniture/fixtures): lines-default, no grid lattice, no angle.
|
|
754
|
+
* - `'structural'` walls / fences / slabs / ceilings / roofs / zones: grid-default, and an
|
|
755
|
+
* angle lock while *setting direction* (drafting a run/polygon, dragging an endpoint or a
|
|
756
|
+
* polygon vertex). A plain translate or a curve of a structural node has no angle.
|
|
757
|
+
*/
|
|
758
|
+
export type SnapProfile = 'item' | 'structural';
|
|
759
|
+
/**
|
|
760
|
+
* How a kind is treated by the GLB bake and the baked `/viewer`. See
|
|
761
|
+
* plans/editor-plugin-trees-example.md → Part D.
|
|
762
|
+
* - `'static'` (default) — baked as geometry; the viewer shows the baked mesh.
|
|
763
|
+
* - `'strip'` — excluded from the bake; the viewer rebuilds it live from
|
|
764
|
+
* `scene_graph` via the registry renderer (heavy reference assets: scans, guides).
|
|
765
|
+
* - `'replace'` — baked as *static* geometry (a plain glTF viewer still shows it),
|
|
766
|
+
* but our viewer removes the baked meshes for this kind and re-renders the node
|
|
767
|
+
* live from `scene_graph` — for dynamic content whose runtime look differs from a
|
|
768
|
+
* frozen snapshot (shader wind, interactivity), rendered through its own path.
|
|
769
|
+
*/
|
|
770
|
+
export type BakePolicy = 'static' | 'strip' | 'replace';
|
|
771
|
+
export type ExportAnimationContext<N = AnyNode> = {
|
|
772
|
+
node: N;
|
|
773
|
+
object: Object3D;
|
|
774
|
+
};
|
|
544
775
|
export type NodeDefinition<S extends ZodObject<any>> = {
|
|
545
776
|
kind: string;
|
|
546
777
|
schemaVersion: number;
|
|
547
778
|
schema: S;
|
|
548
779
|
category: NodeCategory;
|
|
780
|
+
/** Opaque host/plugin contributions. Core stores but never interprets them. */
|
|
781
|
+
extensions?: Readonly<Record<string, unknown>>;
|
|
549
782
|
surfaceRole?: SurfaceRole;
|
|
783
|
+
/**
|
|
784
|
+
* Show a floor direction-triangle while placing/moving — the kind has a
|
|
785
|
+
* meaningful front. `true` points along the node's local +Z (forward).
|
|
786
|
+
* `{ reversed: true }` points along local -Z, for kinds whose front is the
|
|
787
|
+
* -Z side (a stair faces *out* of its run: you approach from the low end,
|
|
788
|
+
* which sits on the -Z side of the footprint).
|
|
789
|
+
*/
|
|
790
|
+
facingIndicator?: boolean | {
|
|
791
|
+
reversed?: boolean;
|
|
792
|
+
};
|
|
793
|
+
/**
|
|
794
|
+
* Role this kind plays in a distribution system (HVAC duct / DWV pipe /
|
|
795
|
+
* refrigerant lineset). Lets the system-graph summary classify a
|
|
796
|
+
* component without branching on `node.type`:
|
|
797
|
+
* - `'run'` — a duct / pipe / lineset segment (carries `path`).
|
|
798
|
+
* - `'fitting'` — an inline fitting (elbow / tee / reducer / trap).
|
|
799
|
+
* - `'terminal'` — a grille / register / diffuser endpoint.
|
|
800
|
+
* - `'equipment'` — a furnace / air handler / condenser source.
|
|
801
|
+
* Kinds outside any distribution system leave this unset.
|
|
802
|
+
*/
|
|
803
|
+
distributionRole?: DistributionRole;
|
|
804
|
+
/**
|
|
805
|
+
* When `distributionRole` is `'fitting'`, controls whether this fitting
|
|
806
|
+
* is dragged as a rigid follower when a connected run endpoint moves.
|
|
807
|
+
*
|
|
808
|
+
* - `true` (default for `distributionRole === 'fitting'`): the fitting
|
|
809
|
+
* translates rigidly so its mated collar stays on the moved port — the
|
|
810
|
+
* right behaviour for in-line fittings (elbows, tees, wyes, crosses).
|
|
811
|
+
* - `false`: the fitting is anchored in space; moving a connected run
|
|
812
|
+
* endpoint stretches the run arm, not the fitting. Use this for
|
|
813
|
+
* fixed-position fixtures like `pipe-trap`.
|
|
814
|
+
*
|
|
815
|
+
* Has no effect when `distributionRole` is not `'fitting'`.
|
|
816
|
+
*/
|
|
817
|
+
portConnectivityFollow?: boolean;
|
|
550
818
|
defaults: () => Omit<z.infer<S>, 'id' | 'type'>;
|
|
551
|
-
migrate?: Record<number, (old: unknown) => unknown>;
|
|
552
819
|
capabilities: Capabilities;
|
|
553
820
|
relations?: Relations;
|
|
554
821
|
parametrics?: ParametricDescriptor<z.infer<S>>;
|
|
822
|
+
/**
|
|
823
|
+
* Whether scene mutations add this kind to `dirtyNodes` (the per-frame
|
|
824
|
+
* rebuild queue). Default true. Set `false` for structural/organizational
|
|
825
|
+
* kinds (site, building, level, zone, guide) that no dirty consumer ever
|
|
826
|
+
* rebuilds — no `def.geometry`, no legacy viewer system, no
|
|
827
|
+
* `capabilities.floorPlaced`. Their marks are never cleared, so they
|
|
828
|
+
* accumulate for the whole session, defeat every consumer's empty-set
|
|
829
|
+
* early exit each frame, and pollute the perf overlay's DIRTY readout.
|
|
830
|
+
* If a kind later gains a dirty consumer, delete the flag.
|
|
831
|
+
*/
|
|
832
|
+
dirtyTracking?: boolean;
|
|
833
|
+
/** GLB bake treatment for this kind (default `'static'`). See {@link BakePolicy}. */
|
|
834
|
+
bake?: BakePolicy;
|
|
555
835
|
/**
|
|
556
836
|
* Renderer for this kind. Optional under the three-checkbox composition
|
|
557
837
|
* model (see `wiki/architecture/node-definitions.md`): when omitted, the
|
|
@@ -564,6 +844,17 @@ export type NodeDefinition<S extends ZodObject<any>> = {
|
|
|
564
844
|
* already null-guard on `def.renderer` so omitting it is safe.
|
|
565
845
|
*/
|
|
566
846
|
renderer?: RendererSource<z.infer<S>>;
|
|
847
|
+
/**
|
|
848
|
+
* Collective renderer the baked `/viewer` uses to re-render this kind live when
|
|
849
|
+
* `bake === 'replace'`. It receives every node of this kind under one baked
|
|
850
|
+
* level and is portaled into that level's `Object3D`, so an instanced kind can
|
|
851
|
+
* draw them as instanced meshes in level-local space (riding level stacking for
|
|
852
|
+
* free) instead of the frozen baked meshes (which the viewer hides). Needed when
|
|
853
|
+
* the normal per-node `renderer` can't stand alone in a baked scene (e.g. an
|
|
854
|
+
* instanced kind whose `renderer` is an invisible selection proxy and whose real
|
|
855
|
+
* geometry comes from a `system`). See plans/editor-plugin-trees-example.md → Part D.
|
|
856
|
+
*/
|
|
857
|
+
bakeReplaceRenderer?: BakeReplaceRenderer<z.infer<S>>;
|
|
567
858
|
/**
|
|
568
859
|
* Pure geometry builder. When set, the framework's generic
|
|
569
860
|
* `<GeometrySystem>` calls this on every dirty mark — `nodes` keyed by
|
|
@@ -577,6 +868,14 @@ export type NodeDefinition<S extends ZodObject<any>> = {
|
|
|
577
868
|
* work (animations, named-mesh material poking).
|
|
578
869
|
*/
|
|
579
870
|
geometry?: (node: z.infer<S>, ctx: GeometryContext) => Object3D;
|
|
871
|
+
/**
|
|
872
|
+
* Optional GLB export animation hook for kind-owned moving parts. The
|
|
873
|
+
* exporter calls this against the cloned export subtree after material/mesh
|
|
874
|
+
* cleanup; implementations should leave `object` in its intended rest pose
|
|
875
|
+
* and return engine-agnostic Three.js clips that target objects inside that
|
|
876
|
+
* subtree.
|
|
877
|
+
*/
|
|
878
|
+
exportAnimation?: (ctx: ExportAnimationContext<z.infer<S>>) => AnimationClip | AnimationClip[] | null | undefined;
|
|
580
879
|
/**
|
|
581
880
|
* Optional cache key over the geometry-relevant inputs of `node`. When
|
|
582
881
|
* set, `<GeometrySystem>` skips the rebuild (dispose + re-create the
|
|
@@ -608,6 +907,21 @@ export type NodeDefinition<S extends ZodObject<any>> = {
|
|
|
608
907
|
* runs once even when many walls are dirty in the same frame.
|
|
609
908
|
*/
|
|
610
909
|
computeLevelData?: (siblings: ReadonlyArray<z.infer<S>>) => unknown;
|
|
910
|
+
/**
|
|
911
|
+
* Floor-plan level-batch precompute hook. The floor-plan layer calls this
|
|
912
|
+
* once per level per render pass, de-duplicated by kind, before the
|
|
913
|
+
* per-node `def.floorplan` calls. The result lands in `ctx.levelData` for
|
|
914
|
+
* every node of this kind in the level.
|
|
915
|
+
*
|
|
916
|
+
* Used to hoist cross-sibling floor-plan work that would otherwise be
|
|
917
|
+
* O(N²) when rebuilding every node in a kind — e.g. wall mitering. `nodes`
|
|
918
|
+
* is the live-merged scene snapshot; `siblings` is every node of this kind
|
|
919
|
+
* in the level, also live-merged.
|
|
920
|
+
*/
|
|
921
|
+
computeFloorplanLevelData?: (args: {
|
|
922
|
+
siblings: ReadonlyArray<z.infer<S>>;
|
|
923
|
+
nodes: Record<string, AnyNode>;
|
|
924
|
+
}) => unknown;
|
|
611
925
|
/**
|
|
612
926
|
* Pure 2D builder for floor-plan rendering. Mirrors `geometry` but emits
|
|
613
927
|
* plain `FloorplanGeometry` data (SVG-renderable) rather than three.js
|
|
@@ -623,6 +937,10 @@ export type NodeDefinition<S extends ZodObject<any>> = {
|
|
|
623
937
|
* the legacy `floorplan-panel.tsx` monolith.
|
|
624
938
|
*/
|
|
625
939
|
floorplan?: (node: z.infer<S>, ctx: GeometryContext) => FloorplanGeometry | null;
|
|
940
|
+
/** Extra node IDs whose committed changes invalidate this node's floor-plan cache. */
|
|
941
|
+
floorplanDependencies?: (node: z.infer<S>) => readonly AnyNodeId[];
|
|
942
|
+
/** Stable semantic geometry that associative measurement anchors may reference. */
|
|
943
|
+
measurement?: MeasurementContribution<z.infer<S>>;
|
|
626
944
|
/**
|
|
627
945
|
* Which scope the floor-plan layer walks to find instances of this
|
|
628
946
|
* kind. Default `'level'` — the layer's DFS from the active level id
|
|
@@ -642,8 +960,9 @@ export type NodeDefinition<S extends ZodObject<any>> = {
|
|
|
642
960
|
* `def.floorplanAffordances?.[affordance].start({...})` on pointer-down,
|
|
643
961
|
* receives a session, calls `apply(...)` on pointer-move and
|
|
644
962
|
* `commit()` / `cancel()` on pointer-up / pointer-cancel. The session
|
|
645
|
-
*
|
|
646
|
-
*
|
|
963
|
+
* previews through live override/transform stores during `apply`. Legacy
|
|
964
|
+
* sessions that still write preview state into `useScene` are handled by
|
|
965
|
+
* the dispatcher's snapshot + single-undo compatibility path.
|
|
647
966
|
*
|
|
648
967
|
* Mirrors the existing 3D `affordanceTools` map but for 2D SVG events,
|
|
649
968
|
* and operates on plain JS data instead of mounting React. Kinds with
|
|
@@ -665,6 +984,73 @@ export type NodeDefinition<S extends ZodObject<any>> = {
|
|
|
665
984
|
* unset and rely on the generic overlay path.
|
|
666
985
|
*/
|
|
667
986
|
floorplanMoveTarget?: FloorplanMoveTarget<z.infer<S>>;
|
|
987
|
+
/**
|
|
988
|
+
* Extra floating-menu actions contributed by this kind. The editor renders
|
|
989
|
+
* the returned descriptors generically; kind-specific mutation stays here
|
|
990
|
+
* and runs through `SceneApi`.
|
|
991
|
+
*/
|
|
992
|
+
quickActions?: NodeQuickActionProvider<z.infer<S>>;
|
|
993
|
+
/** Scene-graph scope the quick-action provider needs for derived availability. */
|
|
994
|
+
quickActionNodeScope?: NodeQuickActionNodeScope;
|
|
995
|
+
/**
|
|
996
|
+
* Sidebar-tree presentation hooks. Lets a kind reshape how the generic
|
|
997
|
+
* scene tree walks its subtree — hiding derived/managed nodes and
|
|
998
|
+
* flattening intermediate containers — without the tree hardcoding any
|
|
999
|
+
* kind. The tree consults these for every node whose kind declares them;
|
|
1000
|
+
* kinds whose scene-graph shape matches their desired tree shape omit
|
|
1001
|
+
* this entirely.
|
|
1002
|
+
*/
|
|
1003
|
+
tree?: {
|
|
1004
|
+
/**
|
|
1005
|
+
* Hide this node's row in the sidebar tree (e.g. derived/managed nodes
|
|
1006
|
+
* whose contents surface elsewhere via `childIds`).
|
|
1007
|
+
*/
|
|
1008
|
+
hidden?: (node: AnyNode, nodes: Readonly<Partial<Record<AnyNodeId, AnyNode>>>) => boolean;
|
|
1009
|
+
/**
|
|
1010
|
+
* Optional tree-row label override. When unset the host falls back to
|
|
1011
|
+
* `node.name` / `def.presentation.label`.
|
|
1012
|
+
*/
|
|
1013
|
+
label?: (node: AnyNode, nodes: Readonly<Partial<Record<AnyNodeId, AnyNode>>>) => string;
|
|
1014
|
+
/**
|
|
1015
|
+
* Override the child ids the sidebar tree renders under this node.
|
|
1016
|
+
* When unset the tree falls back to the node's own `children`.
|
|
1017
|
+
*/
|
|
1018
|
+
childIds?: (node: AnyNode, nodes: Readonly<Partial<Record<AnyNodeId, AnyNode>>>) => AnyNodeId[];
|
|
1019
|
+
};
|
|
1020
|
+
/**
|
|
1021
|
+
* Selection-proxy behavior overrides. A node opts into proxying by writing
|
|
1022
|
+
* `metadata.nodeSelectionProxyId` (see `lib/selection-proxy.ts` for the
|
|
1023
|
+
* metadata contract); grouped affordances (move / rotate) then key off the
|
|
1024
|
+
* proxy target. `bypassDirectPick` lets a kind keep the proxy for those
|
|
1025
|
+
* grouped affordances while still routing a direct canvas pick to the
|
|
1026
|
+
* clicked node itself — e.g. corner-generated cabinet modules stay
|
|
1027
|
+
* individually selectable even though they proxy to their run.
|
|
1028
|
+
*/
|
|
1029
|
+
selectionProxy?: {
|
|
1030
|
+
/** Return true when a direct pick of `node` should select it instead of
|
|
1031
|
+
* its resolved proxy target. */
|
|
1032
|
+
bypassDirectPick?: (node: AnyNode, proxyTarget: AnyNode) => boolean;
|
|
1033
|
+
};
|
|
1034
|
+
/**
|
|
1035
|
+
* Geometry reads sibling/parent/child nodes (e.g. wall miters, opening
|
|
1036
|
+
* dimensions); the floor-plan layer must rebuild it whenever a
|
|
1037
|
+
* sibling-affecting node is being dragged live.
|
|
1038
|
+
*/
|
|
1039
|
+
floorplanDependsOnSiblings?: boolean;
|
|
1040
|
+
/**
|
|
1041
|
+
* Optional hook for kinds whose floor-plan cache invalidation reaches beyond
|
|
1042
|
+
* the default framework relationships (wall junction neighbours, host wall
|
|
1043
|
+
* opening cuts, gutter siblings under one roof). Called when a node of this
|
|
1044
|
+
* kind has a live drag/override in flight; returns the extra entry ids that
|
|
1045
|
+
* must rebuild this frame.
|
|
1046
|
+
*/
|
|
1047
|
+
floorplanAffectedIds?: (args: {
|
|
1048
|
+
nodeId: AnyNodeId;
|
|
1049
|
+
node: AnyNode;
|
|
1050
|
+
nodes: Record<AnyNodeId, AnyNode>;
|
|
1051
|
+
liveTransforms: Map<string, LiveTransformLike>;
|
|
1052
|
+
liveOverrides: Map<string, Record<string, unknown>>;
|
|
1053
|
+
}) => readonly AnyNodeId[];
|
|
668
1054
|
/**
|
|
669
1055
|
* Optional hook letting a kind project the `useLiveNodeOverrides` map
|
|
670
1056
|
* into a fresh `nodes` snapshot before its `def.floorplan` builder
|
|
@@ -687,8 +1073,18 @@ export type NodeDefinition<S extends ZodObject<any>> = {
|
|
|
687
1073
|
floorplanSiblingOverrides?: (args: {
|
|
688
1074
|
nodeId: AnyNodeId;
|
|
689
1075
|
nodes: Record<AnyNodeId, AnyNode>;
|
|
1076
|
+
liveTransforms: Map<string, LiveTransformLike>;
|
|
690
1077
|
liveOverrides: Map<string, Record<string, unknown>>;
|
|
691
1078
|
}) => Record<AnyNodeId, AnyNode>;
|
|
1079
|
+
/**
|
|
1080
|
+
* Typed connection points this kind exposes (duct/pipe open ends,
|
|
1081
|
+
* fitting collars, equipment plenums). Pure function of the node —
|
|
1082
|
+
* returns LEVEL-LOCAL positions/directions (the kind applies its own
|
|
1083
|
+
* transform). Consumed by placement tools for port-snapping and, in a
|
|
1084
|
+
* later slice, by the system graph for connectivity. Kinds with no
|
|
1085
|
+
* connectable geometry omit this.
|
|
1086
|
+
*/
|
|
1087
|
+
ports?: (node: z.infer<S>) => NodePort[];
|
|
692
1088
|
system?: SystemContribution;
|
|
693
1089
|
tool?: LazyComponent;
|
|
694
1090
|
/**
|
|
@@ -720,6 +1116,27 @@ export type NodeDefinition<S extends ZodObject<any>> = {
|
|
|
720
1116
|
* its bespoke helper component instead.
|
|
721
1117
|
*/
|
|
722
1118
|
toolHints?: ToolHint[];
|
|
1119
|
+
/**
|
|
1120
|
+
* Which snapping profile this kind uses, so the editor's contextual snapping
|
|
1121
|
+
* HUD + snap math + force-place affordance are node-declared rather than
|
|
1122
|
+
* switched on the kind name (`'item'` free object vs `'structural'` wall/slab/
|
|
1123
|
+
* surface — see `SnapProfile`). The angle lock is derived from the *action*
|
|
1124
|
+
* (setting direction), not declared here. Also gates the "force place" hint:
|
|
1125
|
+
* structural kinds don't collision-reject, so they don't show it.
|
|
1126
|
+
* Omit it for kinds whose placement/move tools haven't moved onto the unified
|
|
1127
|
+
* snapping model yet — they get no snapping chip (no Shift-cycle) until they do.
|
|
1128
|
+
*/
|
|
1129
|
+
snapProfile?: SnapProfile;
|
|
1130
|
+
/**
|
|
1131
|
+
* For `structural` kinds: does drafting this kind set a DIRECTION (so the
|
|
1132
|
+
* angle-lock snapping mode is meaningful)? Wall/fence/slab/ceiling drafting
|
|
1133
|
+
* draws directed edges → `true` (the default). Roof/stair/elevator are placed
|
|
1134
|
+
* as axis-aligned footprints, not directional draws → `false`, so their
|
|
1135
|
+
* drafting uses the no-angle `polygon` snap context (grid / lines / off)
|
|
1136
|
+
* instead of the angle-bearing `wall` context. Ignored for `item` kinds
|
|
1137
|
+
* (their context never carries an angle lock).
|
|
1138
|
+
*/
|
|
1139
|
+
snapDraftDirectional?: boolean;
|
|
723
1140
|
/**
|
|
724
1141
|
* Optional translucent preview of the node — used by the move tool to
|
|
725
1142
|
* show where the node will land, and by the placement tool's cursor.
|
|
@@ -773,6 +1190,16 @@ export type KeyboardActions = {
|
|
|
773
1190
|
r?: KeyboardAction;
|
|
774
1191
|
/** T / Shift+T secondary action. */
|
|
775
1192
|
t?: KeyboardAction;
|
|
1193
|
+
/** E interaction action — operate the node (doors, drawers, appliances). */
|
|
1194
|
+
e?: KeyboardAction;
|
|
1195
|
+
/**
|
|
1196
|
+
* Set for kinds whose R/T rotation turns around a user-cyclable world
|
|
1197
|
+
* axis (Alt cycles Y → X → Z) — duct / pipe fittings with full 3D
|
|
1198
|
+
* orientation. The floating action menu reads this to surface the
|
|
1199
|
+
* active-axis pill above the selected node; kinds with plain Y-only
|
|
1200
|
+
* rotation omit it.
|
|
1201
|
+
*/
|
|
1202
|
+
axisCycling?: boolean;
|
|
776
1203
|
};
|
|
777
1204
|
export type KeyboardAction = {
|
|
778
1205
|
/**
|
|
@@ -809,6 +1236,9 @@ export type Presentation = {
|
|
|
809
1236
|
/** Set true for kinds that exist but should NOT appear in the palette
|
|
810
1237
|
* (containers like `site`/`building`/`level`, internal nodes). */
|
|
811
1238
|
hidden?: boolean;
|
|
1239
|
+
/** Set false when selection is edited directly through in-scene affordances
|
|
1240
|
+
* and the generic floating action menu would duplicate or conflict with them. */
|
|
1241
|
+
actionMenu?: boolean;
|
|
812
1242
|
};
|
|
813
1243
|
export type IconRef =
|
|
814
1244
|
/** Iconify identifier, e.g. `lucide:square`. Matches the @iconify-react
|
|
@@ -856,13 +1286,27 @@ export type RendererSource<N> = {
|
|
|
856
1286
|
kind: 'instanced-glb';
|
|
857
1287
|
getAsset: (n: N) => AssetRef;
|
|
858
1288
|
};
|
|
1289
|
+
/**
|
|
1290
|
+
* A collective renderer for the baked `/viewer` (see `NodeDefinition.bakeReplaceRenderer`):
|
|
1291
|
+
* a lazy module whose default export takes all of one level's `replace` nodes and
|
|
1292
|
+
* is portaled into that baked level. Three-free indirection, same as `system`.
|
|
1293
|
+
*/
|
|
1294
|
+
export type BakeReplaceRenderer<N> = {
|
|
1295
|
+
module: () => Promise<{
|
|
1296
|
+
default: ComponentType<{
|
|
1297
|
+
nodes: N[];
|
|
1298
|
+
}>;
|
|
1299
|
+
}>;
|
|
1300
|
+
};
|
|
859
1301
|
export type AssetRef = {
|
|
860
1302
|
id: string;
|
|
861
1303
|
src: string;
|
|
862
1304
|
};
|
|
863
1305
|
export type SystemContribution = {
|
|
864
1306
|
module: () => Promise<{
|
|
865
|
-
default: ComponentType
|
|
1307
|
+
default: ComponentType<{
|
|
1308
|
+
sceneApi: SceneApi;
|
|
1309
|
+
}>;
|
|
866
1310
|
}>;
|
|
867
1311
|
priority?: number;
|
|
868
1312
|
};
|
|
@@ -870,6 +1314,22 @@ export type McpOverrides = {
|
|
|
870
1314
|
description?: string;
|
|
871
1315
|
semantic?: boolean;
|
|
872
1316
|
};
|
|
1317
|
+
export type DuplicateSubtreeCloneArgs = {
|
|
1318
|
+
root: AnyNode;
|
|
1319
|
+
descendants: AnyNode[];
|
|
1320
|
+
rootId: AnyNodeId;
|
|
1321
|
+
rootPatch: Partial<AnyNode>;
|
|
1322
|
+
nodes: Readonly<Record<AnyNodeId, AnyNode>>;
|
|
1323
|
+
};
|
|
1324
|
+
export type DuplicateSubtreeCloneResult = {
|
|
1325
|
+
root?: AnyNode;
|
|
1326
|
+
descendants?: AnyNode[];
|
|
1327
|
+
parentId?: AnyNodeId | null;
|
|
1328
|
+
};
|
|
1329
|
+
export type DuplicableConfig = {
|
|
1330
|
+
subtree?: boolean;
|
|
1331
|
+
prepareSubtreeClone?: (args: DuplicateSubtreeCloneArgs) => DuplicateSubtreeCloneResult;
|
|
1332
|
+
};
|
|
873
1333
|
export type Capabilities = {
|
|
874
1334
|
movable?: MovableConfig;
|
|
875
1335
|
rotatable?: RotatableConfig;
|
|
@@ -878,7 +1338,7 @@ export type Capabilities = {
|
|
|
878
1338
|
cuttable?: CuttableConfig;
|
|
879
1339
|
snappable?: SnappableConfig;
|
|
880
1340
|
surfaces?: SurfacesConfig;
|
|
881
|
-
duplicable?: boolean;
|
|
1341
|
+
duplicable?: boolean | DuplicableConfig;
|
|
882
1342
|
deletable?: boolean;
|
|
883
1343
|
groupable?: boolean;
|
|
884
1344
|
selectable?: SelectableConfig;
|
|
@@ -901,11 +1361,15 @@ export type Capabilities = {
|
|
|
901
1361
|
* the box to wrap just the shaft they're moving.
|
|
902
1362
|
*
|
|
903
1363
|
* `size`: `[width, height, depth]` in the node's local frame.
|
|
1364
|
+
* `center`: optional full local center. Use this when the footprint is
|
|
1365
|
+
* offset from the node origin, such as a composite cabinet run after modules
|
|
1366
|
+
* have been deleted or shifted.
|
|
904
1367
|
* `centerY`: optional Y center; defaults to `size[1] / 2` (box sits on
|
|
905
1368
|
* the ground plane). Override when the local origin isn't at the base.
|
|
906
1369
|
*/
|
|
907
1370
|
dragBounds?: (node: AnyNode, nodes?: Readonly<Record<string, AnyNode>>) => {
|
|
908
1371
|
size: [number, number, number];
|
|
1372
|
+
center?: [number, number, number];
|
|
909
1373
|
centerY?: number;
|
|
910
1374
|
};
|
|
911
1375
|
roofAccessory?: RoofAccessoryConfig;
|
|
@@ -916,6 +1380,25 @@ export type Capabilities = {
|
|
|
916
1380
|
*/
|
|
917
1381
|
ceilingCut?: CeilingCutCapability;
|
|
918
1382
|
paint?: PaintCapability;
|
|
1383
|
+
/**
|
|
1384
|
+
* In-scene click action dispatch (e.g. a cooktop knob toggling its burner).
|
|
1385
|
+
* The editor's selection-manager walks the pointer hit's object chain
|
|
1386
|
+
* through `resolveTarget`; when it returns non-null, `activate` runs and a
|
|
1387
|
+
* `true` return consumes the click (no selection change). Keeps interactive
|
|
1388
|
+
* sub-meshes registry-driven instead of `if (node.type === '<kind>')` arms
|
|
1389
|
+
* in the editor.
|
|
1390
|
+
*/
|
|
1391
|
+
sceneAction?: SceneActionCapability;
|
|
1392
|
+
/**
|
|
1393
|
+
* Declares the kind's paintable slots — the `{ slotId, label, default }`
|
|
1394
|
+
* contract shared by items (scanned from the GLB) and procedural kinds
|
|
1395
|
+
* (declared here). Procedural generators tag their emitted geometry with
|
|
1396
|
+
* `userData.slotId` and resolve each slot's material from
|
|
1397
|
+
* `node.slots[slotId]` → this declaration's `default` → role colour. The
|
|
1398
|
+
* declaration is a function of the node because a kind's slot set can depend
|
|
1399
|
+
* on its parameters (a shelf has a `back` slot only when it has a back).
|
|
1400
|
+
*/
|
|
1401
|
+
slots?: (node: AnyNode) => SlotDeclaration[];
|
|
919
1402
|
/**
|
|
920
1403
|
* Kind is placed by clicking on a wall (door, window). When set, the
|
|
921
1404
|
* floor-plan layer lets wall background clicks pass through during
|
|
@@ -1006,7 +1489,31 @@ export type Capabilities = {
|
|
|
1006
1489
|
* the `selectedMaterialTarget` round-trip, the paint-mode toolbar.
|
|
1007
1490
|
* Kinds with no paint behaviour omit `paint`.
|
|
1008
1491
|
*/
|
|
1492
|
+
/**
|
|
1493
|
+
* One paintable slot a kind exposes. `slotId` is the stable key written into
|
|
1494
|
+
* `node.slots`; `label` is the human name (sentence case). `default` is the
|
|
1495
|
+
* slot's fallback appearance when no override is set — either a `MaterialRef`
|
|
1496
|
+
* (`library:<id>` / `scene:<id>`) or a `#rrggbb` colour. Mirrors the shape
|
|
1497
|
+
* items derive from their GLB material names.
|
|
1498
|
+
*/
|
|
1499
|
+
export type SlotDeclaration = {
|
|
1500
|
+
slotId: string;
|
|
1501
|
+
label: string;
|
|
1502
|
+
default?: string;
|
|
1503
|
+
};
|
|
1009
1504
|
export type PaintCapability = {
|
|
1505
|
+
/**
|
|
1506
|
+
* Material-picker target represented by this paint capability. Omit when
|
|
1507
|
+
* the kind should not show up as a toolbar target from plain selection.
|
|
1508
|
+
*/
|
|
1509
|
+
materialTarget?: MaterialTarget;
|
|
1510
|
+
/**
|
|
1511
|
+
* Opt this kind into the painter's `room` application scope: a paint click
|
|
1512
|
+
* spreads to every same-kind node bounding the clicked node's room (walls and
|
|
1513
|
+
* slabs). The room geometry is resolved by the editor from `Space.polygon`;
|
|
1514
|
+
* this flag only declares that the kind participates.
|
|
1515
|
+
*/
|
|
1516
|
+
roomScope?: boolean;
|
|
1010
1517
|
/**
|
|
1011
1518
|
* Resolve which logical surface the user clicked. Returns `null`
|
|
1012
1519
|
* when the face shouldn't be painted (e.g. interior slot exposed
|
|
@@ -1018,6 +1525,14 @@ export type PaintCapability = {
|
|
|
1018
1525
|
* `role`. Returned partial is merged into the node by the editor.
|
|
1019
1526
|
*/
|
|
1020
1527
|
buildPatch: (args: PaintPatchArgs) => Partial<AnyNode>;
|
|
1528
|
+
/**
|
|
1529
|
+
* Optional: fully own the click-commit instead of the default
|
|
1530
|
+
* `updateNode(node.id, buildPatch(...))`. Kinds whose commit has a side
|
|
1531
|
+
* effect (items create a scene material for one-off colours, then store a
|
|
1532
|
+
* `scene:<id>` ref) implement this; kinds that just patch the node omit it.
|
|
1533
|
+
* Must perform its mutations as a single undo step.
|
|
1534
|
+
*/
|
|
1535
|
+
commit?: (args: PaintPatchArgs) => void;
|
|
1021
1536
|
/**
|
|
1022
1537
|
* Apply a preview to the kind's registered mesh subtree at
|
|
1023
1538
|
* `role`. The kind builds whatever preview material(s) it needs
|
|
@@ -1046,6 +1561,55 @@ export type PaintCapability = {
|
|
|
1046
1561
|
materialPreset: string | undefined;
|
|
1047
1562
|
} | null;
|
|
1048
1563
|
};
|
|
1564
|
+
/**
|
|
1565
|
+
* Per-kind in-scene click actions. A kind that builds interactive sub-meshes
|
|
1566
|
+
* (a gas-hob knob, a switch) tags them via `userData` in its geometry builder,
|
|
1567
|
+
* resolves the tag back out of the pointer hit in `resolveTarget`, and runs
|
|
1568
|
+
* the state change in `activate`. The editor owns only the generic dispatch:
|
|
1569
|
+
* walk the hit object's parent chain, and when `resolveTarget` returns
|
|
1570
|
+
* non-null, call `activate`; a `true` return consumes the click.
|
|
1571
|
+
*
|
|
1572
|
+
* `activate` receives a `SceneApi` so the kind never imports `useScene`
|
|
1573
|
+
* directly; transient animation frames may write through
|
|
1574
|
+
* `useLiveNodeOverrides` + `markDirty` and commit once at the end.
|
|
1575
|
+
*/
|
|
1576
|
+
export type SceneActionCapability<T = unknown> = {
|
|
1577
|
+
/** Extract this kind's action target from one object in the hit chain. */
|
|
1578
|
+
resolveTarget: (object: {
|
|
1579
|
+
userData: Record<string, unknown>;
|
|
1580
|
+
}) => T | null;
|
|
1581
|
+
/** Run the action. Return `true` to consume the click (skip selection). */
|
|
1582
|
+
activate: (node: AnyNode, target: T, sceneApi: SceneApi) => boolean;
|
|
1583
|
+
};
|
|
1584
|
+
export type NodeQuickActionIcon = 'add-left' | 'add-right' | 'add' | 'convert';
|
|
1585
|
+
export type NodeQuickActionResult = {
|
|
1586
|
+
selectedIds?: AnyNodeId[];
|
|
1587
|
+
};
|
|
1588
|
+
export type NodeQuickActionNodeScope = 'family' | 'level';
|
|
1589
|
+
export type NodeQuickAction = {
|
|
1590
|
+
id: string;
|
|
1591
|
+
label: string;
|
|
1592
|
+
title?: string;
|
|
1593
|
+
/**
|
|
1594
|
+
* Builtin glyph token (side-add arrows, convert) or an {@link IconRef}
|
|
1595
|
+
* for kind-owned marks — quick actions with bespoke glyphs ship them
|
|
1596
|
+
* from the kind's package instead of the menus hardcoding per-action
|
|
1597
|
+
* SVG.
|
|
1598
|
+
*/
|
|
1599
|
+
icon?: NodeQuickActionIcon | IconRef;
|
|
1600
|
+
disabled?: boolean;
|
|
1601
|
+
/** Whether pressing a disabled action should acknowledge its blocked state. */
|
|
1602
|
+
blockedFeedback?: boolean;
|
|
1603
|
+
history?: 'single';
|
|
1604
|
+
run: (args: {
|
|
1605
|
+
node: AnyNode;
|
|
1606
|
+
sceneApi: SceneApi;
|
|
1607
|
+
}) => NodeQuickActionResult | undefined;
|
|
1608
|
+
};
|
|
1609
|
+
export type NodeQuickActionProvider<N> = (args: {
|
|
1610
|
+
node: N;
|
|
1611
|
+
nodes: Readonly<Partial<Record<AnyNodeId, AnyNode>>>;
|
|
1612
|
+
}) => NodeQuickAction[];
|
|
1049
1613
|
export type PaintResolveArgs = {
|
|
1050
1614
|
node: AnyNode;
|
|
1051
1615
|
/**
|
|
@@ -1060,6 +1624,16 @@ export type PaintResolveArgs = {
|
|
|
1060
1624
|
localPosition?: readonly [number, number, number];
|
|
1061
1625
|
/** Optional: name of the three.js object that received the hit. Stair uses this. */
|
|
1062
1626
|
hitObjectName?: string;
|
|
1627
|
+
/** Optional: the three.js object that received the pointer hit. Items read userData.slotId off it. */
|
|
1628
|
+
hitObject?: Object3D;
|
|
1629
|
+
/**
|
|
1630
|
+
* Optional: the pointer's world ray, so a kind can re-raycast its OWN subtree
|
|
1631
|
+
* to pick the precise sub-mesh under the cursor — independent of what the
|
|
1632
|
+
* shared scene raycast hit first. Door/window use this: their opening proxy
|
|
1633
|
+
* (a proud invisible cutout) wins the scene raycast over the wall in front of
|
|
1634
|
+
* the recessed door body, then they re-raycast their parts to find the slot.
|
|
1635
|
+
*/
|
|
1636
|
+
ray?: Ray;
|
|
1063
1637
|
};
|
|
1064
1638
|
export type PaintPatchArgs = {
|
|
1065
1639
|
node: AnyNode;
|
|
@@ -1104,6 +1678,23 @@ export type PaintEffectiveMaterialArgs = {
|
|
|
1104
1678
|
*/
|
|
1105
1679
|
export type RoofAccessoryConfig = {
|
|
1106
1680
|
buildCut?: (node: AnyNode, hostSegment: AnyNode) => BufferGeometry | null;
|
|
1681
|
+
/**
|
|
1682
|
+
* Which segment brushes `buildCut` subtracts from. Wall-face openings
|
|
1683
|
+
* (door / window) cut only the wall brush — subtracting the same box
|
|
1684
|
+
* from the shin / deck slabs is pointless work and creates tangential
|
|
1685
|
+
* / coplanar CSG cases near the gable and shed slopes. Defaults to
|
|
1686
|
+
* all three (skylight / dormer genuinely poke through the deck).
|
|
1687
|
+
*/
|
|
1688
|
+
cutScope?: 'all' | 'wall';
|
|
1689
|
+
/**
|
|
1690
|
+
* The kind's own dirty-driven geometry system consumes its dirty
|
|
1691
|
+
* marks (door / window via DoorSystem / WindowSystem, which already
|
|
1692
|
+
* cascade to the host segment through `parentId`). The roof-merge
|
|
1693
|
+
* loop must then leave those marks alone — consuming them would
|
|
1694
|
+
* starve that system whenever it defers a rebuild (mesh not mounted
|
|
1695
|
+
* yet, per-frame rebuild budget exhausted).
|
|
1696
|
+
*/
|
|
1697
|
+
dirtyHandledByOwnSystem?: boolean;
|
|
1107
1698
|
};
|
|
1108
1699
|
/**
|
|
1109
1700
|
* Capability for kinds that cut a hole in their host ceiling when the node is
|
|
@@ -1126,8 +1717,103 @@ export type CapabilityCtx = {
|
|
|
1126
1717
|
export type MovableConfig = {
|
|
1127
1718
|
axes: ReadonlyArray<'x' | 'y' | 'z'>;
|
|
1128
1719
|
gridSnap?: boolean;
|
|
1720
|
+
/**
|
|
1721
|
+
* Pin the dragged node to the cursor (absolute placement) instead of the
|
|
1722
|
+
* default offset-preserving drag, where the node moves by the cursor's
|
|
1723
|
+
* delta from where the drag started. Offset preservation suits large
|
|
1724
|
+
* furniture you grab by an edge; small connector-like kinds (duct
|
|
1725
|
+
* fittings) read as "lagging behind the mouse" — they want the cursor.
|
|
1726
|
+
*/
|
|
1727
|
+
cursorAttached?: boolean;
|
|
1728
|
+
/**
|
|
1729
|
+
* Magnetically snap one of this kind's own ports onto a nearby scene
|
|
1730
|
+
* port while dragging — e.g. a register's collar onto a duct run end.
|
|
1731
|
+
* The dragged node shifts in XZ so its closest matching port lands on
|
|
1732
|
+
* the target port. Alt bypasses the snap. Kinds without `def.ports`
|
|
1733
|
+
* can't use this. Snap takes precedence over grid / alignment snap.
|
|
1734
|
+
*/
|
|
1735
|
+
portSnap?: {
|
|
1736
|
+
/**
|
|
1737
|
+
* Distribution loops a target port must belong to (e.g.
|
|
1738
|
+
* `['supply', 'return']`). A target port with no `system` always
|
|
1739
|
+
* matches. Omit to match every port.
|
|
1740
|
+
*/
|
|
1741
|
+
systems?: readonly string[];
|
|
1742
|
+
/** Snap radius in meters (XZ). Defaults to 0.5. */
|
|
1743
|
+
radius?: number;
|
|
1744
|
+
};
|
|
1745
|
+
/**
|
|
1746
|
+
* The node's `position` lives in a parent node's local frame (a cabinet
|
|
1747
|
+
* module inside its run) rather than the level frame. The generic move
|
|
1748
|
+
* tool converts the plan-frame cursor through these hooks, previews the
|
|
1749
|
+
* child via `useLiveNodeOverrides` (dirtying the parent so its composite
|
|
1750
|
+
* geometry re-flows), and skips the world-frame floor-collision box.
|
|
1751
|
+
*/
|
|
1752
|
+
parentFrame?: MovableParentFrame;
|
|
1753
|
+
/**
|
|
1754
|
+
* Optional group-move snap for the generic multi-selection translate gizmo.
|
|
1755
|
+
* Returns an adjusted candidate position for this node when the moving group
|
|
1756
|
+
* should magnetically settle onto a nearby feature (for example, a cabinet
|
|
1757
|
+
* run snapping flush to a wall while the whole selected kitchen moves as one).
|
|
1758
|
+
*/
|
|
1759
|
+
groupMoveSnap?: (args: GroupMoveSnapArgs) => [number, number, number] | null;
|
|
1129
1760
|
override?: (ctx: CapabilityCtx) => MovableConfig | null;
|
|
1130
1761
|
};
|
|
1762
|
+
export type MovableParentFrame = {
|
|
1763
|
+
/** The parent node owning the local frame; `null` → move in plan frame. */
|
|
1764
|
+
resolveParent: (node: AnyNode, nodes: Readonly<Record<string, AnyNode>>) => AnyNode | null;
|
|
1765
|
+
/** Parent's Y rotation, composed onto the child's preview rotation. */
|
|
1766
|
+
parentRotationY: (parent: AnyNode, nodes?: Readonly<Record<string, AnyNode>>) => number;
|
|
1767
|
+
localToPlan: (parent: AnyNode, local: readonly [number, number, number], nodes?: Readonly<Record<string, AnyNode>>) => [number, number, number];
|
|
1768
|
+
planToLocal: (parent: AnyNode, planX: number, localY: number, planZ: number, nodes?: Readonly<Record<string, AnyNode>>) => [number, number, number];
|
|
1769
|
+
/**
|
|
1770
|
+
* Optional 2D live-transform projection. Used by the floor-plan layer for
|
|
1771
|
+
* nodes whose live position is already in the parent-local frame and must not
|
|
1772
|
+
* be treated as a level-frame / floor-placed position.
|
|
1773
|
+
*/
|
|
1774
|
+
floorplanLiveTransform?: (args: {
|
|
1775
|
+
node: AnyNode;
|
|
1776
|
+
live: LiveTransformLike;
|
|
1777
|
+
}) => AnyNode;
|
|
1778
|
+
/**
|
|
1779
|
+
* Optional magnetic snap in the parent's local frame (e.g. a module edge
|
|
1780
|
+
* mating flush with a sibling module). Runs when magnetic snapping is
|
|
1781
|
+
* active; returns the (possibly unchanged) local position.
|
|
1782
|
+
*/
|
|
1783
|
+
magneticSnap?: (node: AnyNode, parent: AnyNode, local: readonly [number, number, number], nodes: Readonly<Record<string, AnyNode>>) => [number, number, number];
|
|
1784
|
+
/** Optional snap-line matches for the parent-frame magnetic snap result. */
|
|
1785
|
+
magneticSnapMatches?: (node: AnyNode, parent: AnyNode, local: readonly [number, number, number], snappedLocal: readonly [number, number, number], nodes: Readonly<Record<string, AnyNode>>) => ParentFrameSnapMatch[];
|
|
1786
|
+
/**
|
|
1787
|
+
* Called after a move of the child commits, with the LIVE (post-commit)
|
|
1788
|
+
* child and parent. Lets the kind run derived-state maintenance the
|
|
1789
|
+
* generic tool can't know about (a cabinet run re-flowing its layout and
|
|
1790
|
+
* re-anchoring linked corner runs to the moved module's new edge).
|
|
1791
|
+
*/
|
|
1792
|
+
onCommit?: (node: AnyNode, parent: AnyNode, sceneApi: SceneApi) => void;
|
|
1793
|
+
};
|
|
1794
|
+
export type ParentFrameSnapMatch = {
|
|
1795
|
+
axis: 'x' | 'z';
|
|
1796
|
+
candidateNodeId: AnyNodeId;
|
|
1797
|
+
from: {
|
|
1798
|
+
x: number;
|
|
1799
|
+
z: number;
|
|
1800
|
+
};
|
|
1801
|
+
to: {
|
|
1802
|
+
x: number;
|
|
1803
|
+
z: number;
|
|
1804
|
+
};
|
|
1805
|
+
};
|
|
1806
|
+
export type GroupMoveSnapArgs = {
|
|
1807
|
+
node: AnyNode;
|
|
1808
|
+
candidatePosition: [number, number, number];
|
|
1809
|
+
movingIds: readonly AnyNodeId[];
|
|
1810
|
+
nodes: Readonly<Record<string, AnyNode>>;
|
|
1811
|
+
levelId: AnyNodeId | null;
|
|
1812
|
+
};
|
|
1813
|
+
export type LiveTransformLike = {
|
|
1814
|
+
position: [number, number, number];
|
|
1815
|
+
rotation: number;
|
|
1816
|
+
};
|
|
1131
1817
|
export type RotatableConfig = {
|
|
1132
1818
|
axes: ReadonlyArray<'x' | 'y' | 'z'>;
|
|
1133
1819
|
snapAngles?: readonly number[];
|
|
@@ -1198,6 +1884,15 @@ export type FloorPlacedConfig = {
|
|
|
1198
1884
|
footprint?: FloorPlacedFootprintResolver;
|
|
1199
1885
|
footprints?: FloorPlacedFootprintsResolver;
|
|
1200
1886
|
applies?: (node: AnyNode) => boolean;
|
|
1887
|
+
/**
|
|
1888
|
+
* Opt this kind into floor-placement collision: its footprint blocks other
|
|
1889
|
+
* placements (it's an obstacle in `canPlaceOnFloor`) AND its own
|
|
1890
|
+
* placement/move refuses to overlap another colliding footprint (red ghost,
|
|
1891
|
+
* Alt to force). Solid furniture-like kinds (item / shelf / column) set this;
|
|
1892
|
+
* markers and port-mated kinds (spawn / MEP / stair) leave it off so they
|
|
1893
|
+
* neither block nor get blocked. Default off.
|
|
1894
|
+
*/
|
|
1895
|
+
collides?: boolean;
|
|
1201
1896
|
};
|
|
1202
1897
|
/**
|
|
1203
1898
|
* Plan footprint a kind contributes to the alignment-anchor pool when it is
|
|
@@ -1241,7 +1936,54 @@ export type Relations = {
|
|
|
1241
1936
|
export type ParametricDescriptor<N> = {
|
|
1242
1937
|
groups: ParamGroup<N>[];
|
|
1243
1938
|
invariants?: ReadonlyArray<(n: N) => Issue[]>;
|
|
1244
|
-
|
|
1939
|
+
/**
|
|
1940
|
+
* Co-update hook for fields that must stay consistent when edited
|
|
1941
|
+
* from the inspector. Called with the node AFTER `patch` is merged
|
|
1942
|
+
* plus the patch itself (so the hook can tell which field the user
|
|
1943
|
+
* touched); whatever it returns is folded into the same update.
|
|
1944
|
+
* Direct store/MCP writes bypass it — keep real invariants in
|
|
1945
|
+
* `invariants`.
|
|
1946
|
+
*/
|
|
1947
|
+
derive?: (next: N, patch: Partial<N>) => Partial<N>;
|
|
1948
|
+
/**
|
|
1949
|
+
* Cross-node companion to `derive`: after an inspector edit lands on
|
|
1950
|
+
* this node, return patches for OTHER nodes that must follow to keep
|
|
1951
|
+
* the scene consistent — e.g. duct runs re-trimmed onto a resized
|
|
1952
|
+
* fitting's collars. `prev` is the node before the edit, `next` after
|
|
1953
|
+
* (with `derive` already folded in). Applied in the same gesture via
|
|
1954
|
+
* `updateNodes`.
|
|
1955
|
+
*/
|
|
1956
|
+
reconcile?: (prev: N, next: N) => Array<{
|
|
1957
|
+
id: AnyNodeId;
|
|
1958
|
+
data: Partial<AnyNode>;
|
|
1959
|
+
}>;
|
|
1960
|
+
/**
|
|
1961
|
+
* Deletion companion to `reconcile`: when a node of this kind is about
|
|
1962
|
+
* to be removed, return patches for OTHER nodes that must follow to
|
|
1963
|
+
* undo whatever the node imposed on its neighbours — e.g. an
|
|
1964
|
+
* auto-inserted elbow re-extends the duct runs it trimmed back onto the
|
|
1965
|
+
* corner it replaced. Called with the node and the live scene `nodes`
|
|
1966
|
+
* map BEFORE the deletion lands; patches targeting nodes also being
|
|
1967
|
+
* deleted are ignored. Applied in the same `set` as the delete so it's
|
|
1968
|
+
* one undo step. Fires only on `deleteNodes` (user-intent deletes) —
|
|
1969
|
+
* NOT on `applyNodeChanges`, whose deletes are internal re-routes that
|
|
1970
|
+
* rewrite neighbours explicitly in the same batch and would fight a
|
|
1971
|
+
* restore.
|
|
1972
|
+
*/
|
|
1973
|
+
onDelete?: (node: N, nodes: Record<AnyNodeId, AnyNode>) => Array<{
|
|
1974
|
+
id: AnyNodeId;
|
|
1975
|
+
data: Partial<AnyNode>;
|
|
1976
|
+
}>;
|
|
1977
|
+
/**
|
|
1978
|
+
* Companion deletes that should be folded into the same user-intent delete
|
|
1979
|
+
* gesture — e.g. deleting the last module of a cabinet run should remove
|
|
1980
|
+
* the now-empty run node too. Called against the live scene BEFORE
|
|
1981
|
+
* deletion; returned ids are recursively expanded through the normal
|
|
1982
|
+
* descendant cascade. `pendingDeleteIds` holds every id already part of
|
|
1983
|
+
* the gesture so "would my parent become empty?" checks see sibling
|
|
1984
|
+
* deletes from the same multi-select.
|
|
1985
|
+
*/
|
|
1986
|
+
onDeleteCascade?: (node: N, nodes: Record<AnyNodeId, AnyNode>, pendingDeleteIds: ReadonlySet<AnyNodeId>) => AnyNodeId[];
|
|
1245
1987
|
customPanel?: () => Promise<{
|
|
1246
1988
|
default: ComponentType<{
|
|
1247
1989
|
node: N;
|