@pascal-app/core 1.0.1 → 1.0.4
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/agent-operations/__fixtures__/add-object-cases.d.ts +235 -0
- package/dist/agent-operations/__fixtures__/add-object-cases.d.ts.map +1 -0
- package/dist/agent-operations/__fixtures__/add-object-cases.js +129 -0
- package/dist/agent-operations/__fixtures__/build-room-cases.d.ts +4 -0
- package/dist/agent-operations/__fixtures__/build-room-cases.d.ts.map +1 -0
- package/dist/agent-operations/__fixtures__/build-room-cases.js +635 -0
- package/dist/agent-operations/__fixtures__/cases.d.ts +52 -0
- package/dist/agent-operations/__fixtures__/cases.d.ts.map +1 -0
- package/dist/agent-operations/__fixtures__/cases.js +689 -0
- package/dist/agent-operations/__fixtures__/find-by-type-cases.d.ts +7 -0
- package/dist/agent-operations/__fixtures__/find-by-type-cases.d.ts.map +1 -0
- package/dist/agent-operations/__fixtures__/find-by-type-cases.js +21 -0
- package/dist/agent-operations/__fixtures__/place-items-cases.d.ts +17 -0
- package/dist/agent-operations/__fixtures__/place-items-cases.d.ts.map +1 -0
- package/dist/agent-operations/__fixtures__/place-items-cases.js +506 -0
- package/dist/agent-operations/__fixtures__/room-cases.d.ts +3 -0
- package/dist/agent-operations/__fixtures__/room-cases.d.ts.map +1 -0
- package/dist/agent-operations/__fixtures__/room-cases.js +338 -0
- package/dist/agent-operations/__fixtures__/search-assets-cases.d.ts +5 -0
- package/dist/agent-operations/__fixtures__/search-assets-cases.d.ts.map +1 -0
- package/dist/agent-operations/__fixtures__/search-assets-cases.js +137 -0
- package/dist/agent-operations/__fixtures__/structure-cases.d.ts +6 -0
- package/dist/agent-operations/__fixtures__/structure-cases.d.ts.map +1 -0
- package/dist/agent-operations/__fixtures__/structure-cases.js +588 -0
- package/dist/agent-operations/__fixtures__/verify-scene-cases.d.ts +3 -0
- package/dist/agent-operations/__fixtures__/verify-scene-cases.d.ts.map +1 -0
- package/dist/agent-operations/__fixtures__/verify-scene-cases.js +302 -0
- package/dist/agent-operations/achieved.d.ts +16 -0
- package/dist/agent-operations/achieved.d.ts.map +1 -0
- package/dist/agent-operations/achieved.js +28 -0
- package/dist/agent-operations/add-column.d.ts +23 -0
- package/dist/agent-operations/add-column.d.ts.map +1 -0
- package/dist/agent-operations/add-column.js +97 -0
- package/dist/agent-operations/add-level.d.ts +14 -0
- package/dist/agent-operations/add-level.d.ts.map +1 -0
- package/dist/agent-operations/add-level.js +94 -0
- package/dist/agent-operations/add-object.d.ts +114 -0
- package/dist/agent-operations/add-object.d.ts.map +1 -0
- package/dist/agent-operations/add-object.js +361 -0
- package/dist/agent-operations/add-wall.d.ts +15 -0
- package/dist/agent-operations/add-wall.d.ts.map +1 -0
- package/dist/agent-operations/add-wall.js +47 -0
- package/dist/agent-operations/apply-changes.d.ts +16 -0
- package/dist/agent-operations/apply-changes.d.ts.map +1 -0
- package/dist/agent-operations/apply-changes.js +93 -0
- package/dist/agent-operations/apply-outcome.d.ts +15 -0
- package/dist/agent-operations/apply-outcome.d.ts.map +1 -0
- package/dist/agent-operations/apply-outcome.js +18 -0
- package/dist/agent-operations/collections.d.ts +28 -0
- package/dist/agent-operations/collections.d.ts.map +1 -0
- package/dist/agent-operations/collections.js +130 -0
- package/dist/agent-operations/create-room.d.ts +36 -0
- package/dist/agent-operations/create-room.d.ts.map +1 -0
- package/dist/agent-operations/create-room.js +177 -0
- package/dist/agent-operations/create-stair.d.ts +33 -0
- package/dist/agent-operations/create-stair.d.ts.map +1 -0
- package/dist/agent-operations/create-stair.js +247 -0
- package/dist/agent-operations/delete-node.d.ts +6 -0
- package/dist/agent-operations/delete-node.d.ts.map +1 -0
- package/dist/agent-operations/delete-node.js +9 -0
- package/dist/agent-operations/door-clearance.d.ts +135 -0
- package/dist/agent-operations/door-clearance.d.ts.map +1 -0
- package/dist/agent-operations/door-clearance.js +334 -0
- package/dist/agent-operations/duplicate-level.d.ts +12 -0
- package/dist/agent-operations/duplicate-level.d.ts.map +1 -0
- package/dist/agent-operations/duplicate-level.js +54 -0
- package/dist/agent-operations/find-by-type.d.ts +11 -0
- package/dist/agent-operations/find-by-type.d.ts.map +1 -0
- package/dist/agent-operations/find-by-type.js +94 -0
- package/dist/agent-operations/furnish-room.d.ts +18 -0
- package/dist/agent-operations/furnish-room.d.ts.map +1 -0
- package/dist/agent-operations/furnish-room.js +259 -0
- package/dist/agent-operations/get-node.d.ts +9 -0
- package/dist/agent-operations/get-node.d.ts.map +1 -0
- package/dist/agent-operations/get-node.js +33 -0
- package/dist/agent-operations/hosted-services.d.ts +13 -0
- package/dist/agent-operations/hosted-services.d.ts.map +1 -0
- package/dist/agent-operations/hosted-services.js +22 -0
- package/dist/agent-operations/index.d.ts +277 -0
- package/dist/agent-operations/index.d.ts.map +1 -0
- package/dist/agent-operations/index.js +80 -0
- package/dist/agent-operations/layout-clearance.d.ts +108 -0
- package/dist/agent-operations/layout-clearance.d.ts.map +1 -0
- package/dist/agent-operations/layout-clearance.js +190 -0
- package/dist/agent-operations/level-reads.d.ts +33 -0
- package/dist/agent-operations/level-reads.d.ts.map +1 -0
- package/dist/agent-operations/level-reads.js +161 -0
- package/dist/agent-operations/level-target.d.ts +11 -0
- package/dist/agent-operations/level-target.d.ts.map +1 -0
- package/dist/agent-operations/level-target.js +29 -0
- package/dist/agent-operations/list-levels.d.ts +4 -0
- package/dist/agent-operations/list-levels.d.ts.map +1 -0
- package/dist/agent-operations/list-levels.js +31 -0
- package/dist/agent-operations/material-preset.d.ts +11 -0
- package/dist/agent-operations/material-preset.d.ts.map +1 -0
- package/dist/agent-operations/material-preset.js +74 -0
- package/dist/agent-operations/material-refs.d.ts +24 -0
- package/dist/agent-operations/material-refs.d.ts.map +1 -0
- package/dist/agent-operations/material-refs.js +100 -0
- package/dist/agent-operations/node-patch.d.ts +3 -0
- package/dist/agent-operations/node-patch.d.ts.map +1 -0
- package/dist/agent-operations/node-patch.js +114 -0
- package/dist/agent-operations/paint.d.ts +21 -0
- package/dist/agent-operations/paint.d.ts.map +1 -0
- package/dist/agent-operations/paint.js +219 -0
- package/dist/agent-operations/place-items.d.ts +20 -0
- package/dist/agent-operations/place-items.d.ts.map +1 -0
- package/dist/agent-operations/place-items.js +226 -0
- package/dist/agent-operations/plan-geometry.d.ts +24 -0
- package/dist/agent-operations/plan-geometry.d.ts.map +1 -0
- package/dist/agent-operations/plan-geometry.js +94 -0
- package/dist/agent-operations/room-structure.d.ts +87 -0
- package/dist/agent-operations/room-structure.d.ts.map +1 -0
- package/dist/agent-operations/room-structure.js +144 -0
- package/dist/agent-operations/scene-materials.d.ts +15 -0
- package/dist/agent-operations/scene-materials.d.ts.map +1 -0
- package/dist/agent-operations/scene-materials.js +62 -0
- package/dist/agent-operations/scene-measure.d.ts +73 -0
- package/dist/agent-operations/scene-measure.d.ts.map +1 -0
- package/dist/agent-operations/scene-measure.js +73 -0
- package/dist/agent-operations/scene-queries.d.ts +4510 -0
- package/dist/agent-operations/scene-queries.d.ts.map +1 -0
- package/dist/agent-operations/scene-queries.js +84 -0
- package/dist/agent-operations/scene-view.d.ts +98 -0
- package/dist/agent-operations/scene-view.d.ts.map +1 -0
- package/dist/agent-operations/scene-view.js +559 -0
- package/dist/agent-operations/search-assets.d.ts +14 -0
- package/dist/agent-operations/search-assets.d.ts.map +1 -0
- package/dist/agent-operations/search-assets.js +42 -0
- package/dist/agent-operations/search-materials.d.ts +12 -0
- package/dist/agent-operations/search-materials.d.ts.map +1 -0
- package/dist/agent-operations/search-materials.js +111 -0
- package/dist/agent-operations/stairs.d.ts +5 -0
- package/dist/agent-operations/stairs.d.ts.map +1 -0
- package/dist/agent-operations/stairs.js +70 -0
- package/dist/agent-operations/types.d.ts +59 -0
- package/dist/agent-operations/types.d.ts.map +1 -0
- package/dist/agent-operations/types.js +1 -0
- package/dist/agent-operations/verify-scene.d.ts +50 -0
- package/dist/agent-operations/verify-scene.d.ts.map +1 -0
- package/dist/agent-operations/verify-scene.js +369 -0
- package/dist/agent-operations/wall-opening.d.ts +65 -0
- package/dist/agent-operations/wall-opening.d.ts.map +1 -0
- package/dist/agent-operations/wall-opening.js +55 -0
- package/dist/agent-tools/achieved.d.ts +9 -0
- package/dist/agent-tools/achieved.d.ts.map +1 -0
- package/dist/agent-tools/achieved.js +8 -0
- package/dist/agent-tools/add-object.d.ts +43 -0
- package/dist/agent-tools/add-object.d.ts.map +1 -0
- package/dist/agent-tools/add-object.js +86 -0
- package/dist/agent-tools/assets.d.ts +13 -0
- package/dist/agent-tools/assets.d.ts.map +1 -0
- package/dist/agent-tools/assets.js +18 -0
- package/dist/agent-tools/clear-scene.d.ts +10 -0
- package/dist/agent-tools/clear-scene.d.ts.map +1 -0
- package/dist/agent-tools/clear-scene.js +9 -0
- package/dist/agent-tools/collections.d.ts +35 -0
- package/dist/agent-tools/collections.d.ts.map +1 -0
- package/dist/agent-tools/collections.js +31 -0
- package/dist/agent-tools/columns.d.ts +44 -0
- package/dist/agent-tools/columns.d.ts.map +1 -0
- package/dist/agent-tools/columns.js +59 -0
- package/dist/agent-tools/contract.d.ts +68 -0
- package/dist/agent-tools/contract.d.ts.map +1 -0
- package/dist/agent-tools/contract.js +1 -0
- package/dist/agent-tools/create-room.d.ts +63 -0
- package/dist/agent-tools/create-room.d.ts.map +1 -0
- package/dist/agent-tools/create-room.js +90 -0
- package/dist/agent-tools/find-by-type.d.ts +11 -0
- package/dist/agent-tools/find-by-type.d.ts.map +1 -0
- package/dist/agent-tools/find-by-type.js +14 -0
- package/dist/agent-tools/furnish-room.d.ts +26 -0
- package/dist/agent-tools/furnish-room.d.ts.map +1 -0
- package/dist/agent-tools/furnish-room.js +35 -0
- package/dist/agent-tools/hosted-services.d.ts +402 -0
- package/dist/agent-tools/hosted-services.d.ts.map +1 -0
- package/dist/agent-tools/hosted-services.js +148 -0
- package/dist/agent-tools/index.d.ts +864 -0
- package/dist/agent-tools/index.d.ts.map +1 -0
- package/dist/agent-tools/index.js +82 -0
- package/dist/agent-tools/levels.d.ts +72 -0
- package/dist/agent-tools/levels.d.ts.map +1 -0
- package/dist/agent-tools/levels.js +76 -0
- package/dist/agent-tools/measurement.d.ts +37 -0
- package/dist/agent-tools/measurement.d.ts.map +1 -0
- package/dist/agent-tools/measurement.js +94 -0
- package/dist/agent-tools/node-id.d.ts +3 -0
- package/dist/agent-tools/node-id.d.ts.map +1 -0
- package/dist/agent-tools/node-id.js +2 -0
- package/dist/agent-tools/nodes.d.ts +17 -0
- package/dist/agent-tools/nodes.d.ts.map +1 -0
- package/dist/agent-tools/nodes.js +13 -0
- package/dist/agent-tools/paint.d.ts +46 -0
- package/dist/agent-tools/paint.d.ts.map +1 -0
- package/dist/agent-tools/paint.js +29 -0
- package/dist/agent-tools/place-items.d.ts +19 -0
- package/dist/agent-tools/place-items.d.ts.map +1 -0
- package/dist/agent-tools/place-items.js +29 -0
- package/dist/agent-tools/refusal.d.ts +20 -0
- package/dist/agent-tools/refusal.d.ts.map +1 -0
- package/dist/agent-tools/refusal.js +29 -0
- package/dist/agent-tools/room-structure.d.ts +438 -0
- package/dist/agent-tools/room-structure.d.ts.map +1 -0
- package/dist/agent-tools/room-structure.js +207 -0
- package/dist/agent-tools/search-materials.d.ts +20 -0
- package/dist/agent-tools/search-materials.d.ts.map +1 -0
- package/dist/agent-tools/search-materials.js +32 -0
- package/dist/agent-tools/stairs.d.ts +86 -0
- package/dist/agent-tools/stairs.d.ts.map +1 -0
- package/dist/agent-tools/stairs.js +103 -0
- package/dist/agent-tools/verify-scene.d.ts +13 -0
- package/dist/agent-tools/verify-scene.d.ts.map +1 -0
- package/dist/agent-tools/verify-scene.js +21 -0
- package/dist/agent-tools/view-scene.d.ts +43 -0
- package/dist/agent-tools/view-scene.d.ts.map +1 -0
- package/dist/agent-tools/view-scene.js +80 -0
- package/dist/agent-tools/wall-openings.d.ts +163 -0
- package/dist/agent-tools/wall-openings.d.ts.map +1 -0
- package/dist/agent-tools/wall-openings.js +144 -0
- package/dist/agent-tools/walls.d.ts +21 -0
- package/dist/agent-tools/walls.d.ts.map +1 -0
- package/dist/agent-tools/walls.js +30 -0
- package/dist/agent-tools/write-target.d.ts +12 -0
- package/dist/agent-tools/write-target.d.ts.map +1 -0
- package/dist/agent-tools/write-target.js +10 -0
- package/dist/building/__fixtures__/wall-opening-cases.d.ts +736 -0
- package/dist/building/__fixtures__/wall-opening-cases.d.ts.map +1 -0
- package/dist/building/__fixtures__/wall-opening-cases.js +307 -0
- package/dist/building/door-types.d.ts +22 -0
- package/dist/building/door-types.d.ts.map +1 -0
- package/dist/building/door-types.js +167 -0
- package/dist/building/floor-item-fit.d.ts +38 -0
- package/dist/building/floor-item-fit.d.ts.map +1 -0
- package/dist/building/floor-item-fit.js +47 -0
- package/dist/building/index.d.ts +7 -0
- package/dist/building/index.d.ts.map +1 -0
- package/dist/building/index.js +6 -0
- package/dist/building/level-duplication.d.ts +8983 -0
- package/dist/building/level-duplication.d.ts.map +1 -0
- package/dist/building/level-duplication.js +314 -0
- package/dist/building/opening-style-presets.d.ts +46 -0
- package/dist/building/opening-style-presets.d.ts.map +1 -0
- package/dist/building/opening-style-presets.js +195 -0
- package/dist/building/wall-openings.d.ts +445 -0
- package/dist/building/wall-openings.d.ts.map +1 -0
- package/dist/building/wall-openings.js +231 -0
- package/dist/building/window-types.d.ts +17 -0
- package/dist/building/window-types.d.ts.map +1 -0
- package/dist/building/window-types.js +31 -0
- package/dist/commands/structure/apply-zone-transform.d.ts +23 -0
- package/dist/commands/structure/apply-zone-transform.d.ts.map +1 -0
- package/dist/commands/structure/apply-zone-transform.js +55 -0
- package/dist/commands/structure/create-mezzanine.d.ts +12 -0
- package/dist/commands/structure/create-mezzanine.d.ts.map +1 -0
- package/dist/commands/structure/create-mezzanine.js +46 -0
- package/dist/commands/structure/create-zone.d.ts +32 -0
- package/dist/commands/structure/create-zone.d.ts.map +1 -0
- package/dist/commands/structure/create-zone.js +126 -0
- package/dist/commands/structure/delete-zone.d.ts +51 -0
- package/dist/commands/structure/delete-zone.d.ts.map +1 -0
- package/dist/commands/structure/delete-zone.js +354 -0
- package/dist/commands/structure/divide-zone.d.ts +38 -0
- package/dist/commands/structure/divide-zone.d.ts.map +1 -0
- package/dist/commands/structure/divide-zone.js +186 -0
- package/dist/commands/structure/duplicate-zone.d.ts +7 -0
- package/dist/commands/structure/duplicate-zone.d.ts.map +1 -0
- package/dist/commands/structure/duplicate-zone.js +21 -0
- package/dist/commands/structure/floor-opening.d.ts +36 -0
- package/dist/commands/structure/floor-opening.d.ts.map +1 -0
- package/dist/commands/structure/floor-opening.js +124 -0
- package/dist/commands/structure/index.d.ts +22 -0
- package/dist/commands/structure/index.d.ts.map +1 -0
- package/dist/commands/structure/index.js +20 -0
- package/dist/commands/structure/lock-outside-faces.d.ts +12 -0
- package/dist/commands/structure/lock-outside-faces.d.ts.map +1 -0
- package/dist/commands/structure/lock-outside-faces.js +32 -0
- package/dist/commands/structure/merge-zones.d.ts +5 -0
- package/dist/commands/structure/merge-zones.d.ts.map +1 -0
- package/dist/commands/structure/merge-zones.js +22 -0
- package/dist/commands/structure/mezzanine-content.d.ts +16 -0
- package/dist/commands/structure/mezzanine-content.d.ts.map +1 -0
- package/dist/commands/structure/mezzanine-content.js +109 -0
- package/dist/commands/structure/plan-mezzanine-stair.d.ts +7 -0
- package/dist/commands/structure/plan-mezzanine-stair.d.ts.map +1 -0
- package/dist/commands/structure/plan-mezzanine-stair.js +127 -0
- package/dist/commands/structure/plan-wall-deletion.d.ts +9 -0
- package/dist/commands/structure/plan-wall-deletion.d.ts.map +1 -0
- package/dist/commands/structure/plan-wall-deletion.js +151 -0
- package/dist/commands/structure/rebase-floor-reference.d.ts +7 -0
- package/dist/commands/structure/rebase-floor-reference.d.ts.map +1 -0
- package/dist/commands/structure/rebase-floor-reference.js +90 -0
- package/dist/commands/structure/resize-mezzanine.d.ts +8 -0
- package/dist/commands/structure/resize-mezzanine.d.ts.map +1 -0
- package/dist/commands/structure/resize-mezzanine.js +43 -0
- package/dist/commands/structure/rotate-zone.d.ts +8 -0
- package/dist/commands/structure/rotate-zone.d.ts.map +1 -0
- package/dist/commands/structure/rotate-zone.js +28 -0
- package/dist/commands/structure/set-floor-foundation.d.ts +62 -0
- package/dist/commands/structure/set-floor-foundation.d.ts.map +1 -0
- package/dist/commands/structure/set-floor-foundation.js +162 -0
- package/dist/commands/structure/set-room-floor-construction.d.ts +8 -0
- package/dist/commands/structure/set-room-floor-construction.d.ts.map +1 -0
- package/dist/commands/structure/set-room-floor-construction.js +72 -0
- package/dist/commands/structure/set-wall-geometry.d.ts +10 -0
- package/dist/commands/structure/set-wall-geometry.d.ts.map +1 -0
- package/dist/commands/structure/set-wall-geometry.js +52 -0
- package/dist/commands/structure/set-zone-edges.d.ts +14 -0
- package/dist/commands/structure/set-zone-edges.d.ts.map +1 -0
- package/dist/commands/structure/set-zone-edges.js +143 -0
- package/dist/commands/structure/set-zone-intent.d.ts +39 -0
- package/dist/commands/structure/set-zone-intent.d.ts.map +1 -0
- package/dist/commands/structure/set-zone-intent.js +125 -0
- package/dist/commands/structure/shared.d.ts +4512 -0
- package/dist/commands/structure/shared.d.ts.map +1 -0
- package/dist/commands/structure/shared.js +102 -0
- package/dist/commands/structure/transform-zone.d.ts +23 -0
- package/dist/commands/structure/transform-zone.d.ts.map +1 -0
- package/dist/commands/structure/transform-zone.js +472 -0
- package/dist/commands/structure/validate-mezzanine.d.ts +5 -0
- package/dist/commands/structure/validate-mezzanine.d.ts.map +1 -0
- package/dist/commands/structure/validate-mezzanine.js +46 -0
- package/dist/commands/structure/zone-crossings.d.ts +8 -0
- package/dist/commands/structure/zone-crossings.d.ts.map +1 -0
- package/dist/commands/structure/zone-crossings.js +162 -0
- package/dist/commands/structure/zone-drop.d.ts +4 -0
- package/dist/commands/structure/zone-drop.d.ts.map +1 -0
- package/dist/commands/structure/zone-drop.js +68 -0
- package/dist/commands/structure/zone-insertion.d.ts +4 -0
- package/dist/commands/structure/zone-insertion.d.ts.map +1 -0
- package/dist/commands/structure/zone-insertion.js +200 -0
- package/dist/commands/structure/zone-transform-content.d.ts +6 -0
- package/dist/commands/structure/zone-transform-content.d.ts.map +1 -0
- package/dist/commands/structure/zone-transform-content.js +117 -0
- package/dist/commands/structure/zone-wall-merge.d.ts +4977 -0
- package/dist/commands/structure/zone-wall-merge.d.ts.map +1 -0
- package/dist/commands/structure/zone-wall-merge.js +135 -0
- package/dist/contracts/reference-discovery.d.ts +18 -0
- package/dist/contracts/reference-discovery.d.ts.map +1 -0
- package/dist/contracts/reference-discovery.js +100 -0
- package/dist/contracts/reference-inventory.d.ts +44 -0
- package/dist/contracts/reference-inventory.d.ts.map +1 -0
- package/dist/contracts/reference-inventory.js +872 -0
- package/dist/events/bus.d.ts +70 -9
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/hooks/scene-registry/item-clip-registry.d.ts +6 -3
- package/dist/hooks/scene-registry/item-clip-registry.d.ts.map +1 -1
- package/dist/hooks/scene-registry/item-clip-registry.js +2 -1
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/floor-placed-elevation.js +15 -1
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +3 -2
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-manager.js +53 -66
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +1650 -131
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.js +86 -83
- package/dist/hooks/spatial-grid/support-host-patch.d.ts +1 -0
- package/dist/hooks/spatial-grid/support-host-patch.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/support-host-patch.js +12 -5
- package/dist/index.d.ts +83 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +78 -11
- package/dist/lib/__bench__/near-linear-walls.d.ts +2 -0
- package/dist/lib/__bench__/near-linear-walls.d.ts.map +1 -0
- package/dist/lib/__bench__/near-linear-walls.js +24 -0
- package/dist/lib/__bench__/structure-preview.bench.d.ts +2 -0
- package/dist/lib/__bench__/structure-preview.bench.d.ts.map +1 -0
- package/dist/lib/__bench__/structure-preview.bench.js +72 -0
- package/dist/lib/__fixtures__/mezzanine.d.ts +13968 -0
- package/dist/lib/__fixtures__/mezzanine.d.ts.map +1 -0
- package/dist/lib/__fixtures__/mezzanine.js +43 -0
- package/dist/lib/absorb-wall-separators.d.ts +4483 -0
- package/dist/lib/absorb-wall-separators.d.ts.map +1 -0
- package/dist/lib/absorb-wall-separators.js +65 -0
- package/dist/lib/angle-accumulator.d.ts +3 -0
- package/dist/lib/angle-accumulator.d.ts.map +1 -0
- package/dist/lib/angle-accumulator.js +13 -0
- package/dist/lib/artifact-store.d.ts +31 -0
- package/dist/lib/artifact-store.d.ts.map +1 -0
- package/dist/lib/artifact-store.js +42 -0
- package/dist/lib/assembly-stack.d.ts +54 -0
- package/dist/lib/assembly-stack.d.ts.map +1 -0
- package/dist/lib/assembly-stack.js +52 -0
- package/dist/lib/asset-storage.d.ts.map +1 -1
- package/dist/lib/asset-storage.js +3 -0
- package/dist/lib/canonical-opening-ring.d.ts +3 -0
- package/dist/lib/canonical-opening-ring.d.ts.map +1 -0
- package/dist/lib/canonical-opening-ring.js +14 -0
- package/dist/lib/ceiling-children.d.ts +8 -0
- package/dist/lib/ceiling-children.d.ts.map +1 -0
- package/dist/lib/ceiling-children.js +36 -0
- package/dist/lib/ceiling-surface.d.ts +40 -0
- package/dist/lib/ceiling-surface.d.ts.map +1 -0
- package/dist/lib/ceiling-surface.js +65 -0
- package/dist/lib/cutter-host.d.ts +5 -0
- package/dist/lib/cutter-host.d.ts.map +1 -0
- package/dist/lib/cutter-host.js +30 -0
- package/dist/lib/floor-construction-lift.d.ts +4 -0
- package/dist/lib/floor-construction-lift.d.ts.map +1 -0
- package/dist/lib/floor-construction-lift.js +54 -0
- package/dist/lib/floor-footprint-key.d.ts +3 -0
- package/dist/lib/floor-footprint-key.d.ts.map +1 -0
- package/dist/lib/floor-footprint-key.js +9 -0
- package/dist/lib/floor-footprint-name.d.ts +11 -0
- package/dist/lib/floor-footprint-name.d.ts.map +1 -0
- package/dist/lib/floor-footprint-name.js +52 -0
- package/dist/lib/floor-foundation-datum.d.ts +31 -0
- package/dist/lib/floor-foundation-datum.d.ts.map +1 -0
- package/dist/lib/floor-foundation-datum.js +226 -0
- package/dist/lib/floor-foundation-stack.d.ts +16 -0
- package/dist/lib/floor-foundation-stack.d.ts.map +1 -0
- package/dist/lib/floor-foundation-stack.js +214 -0
- package/dist/lib/floor-intent-changes.d.ts +10 -0
- package/dist/lib/floor-intent-changes.d.ts.map +1 -0
- package/dist/lib/floor-intent-changes.js +461 -0
- package/dist/lib/floor-opening-footprints.d.ts +24 -0
- package/dist/lib/floor-opening-footprints.d.ts.map +1 -0
- package/dist/lib/floor-opening-footprints.js +127 -0
- package/dist/lib/floor-opening-intent.d.ts +16 -0
- package/dist/lib/floor-opening-intent.d.ts.map +1 -0
- package/dist/lib/floor-opening-intent.js +82 -0
- package/dist/lib/floor-plate-id.d.ts +5 -0
- package/dist/lib/floor-plate-id.d.ts.map +1 -0
- package/dist/lib/floor-plate-id.js +20 -0
- package/dist/lib/floor-plate-paint.d.ts +8 -0
- package/dist/lib/floor-plate-paint.d.ts.map +1 -0
- package/dist/lib/floor-plate-paint.js +11 -0
- package/dist/lib/floor-plates.d.ts +211 -0
- package/dist/lib/floor-plates.d.ts.map +1 -0
- package/dist/lib/floor-plates.js +729 -0
- package/dist/lib/floor-room-faces.d.ts +5 -0
- package/dist/lib/floor-room-faces.d.ts.map +1 -0
- package/dist/lib/floor-room-faces.js +29 -0
- package/dist/lib/floor-step-finish.d.ts +49 -0
- package/dist/lib/floor-step-finish.d.ts.map +1 -0
- package/dist/lib/floor-step-finish.js +128 -0
- package/dist/lib/geometry-script-node.d.ts +82 -0
- package/dist/lib/geometry-script-node.d.ts.map +1 -0
- package/dist/lib/geometry-script-node.js +147 -0
- package/dist/lib/geometry-surfaces.d.ts +61 -0
- package/dist/lib/geometry-surfaces.d.ts.map +1 -0
- package/dist/lib/geometry-surfaces.js +156 -0
- package/dist/lib/item-slab-support.d.ts +14 -0
- package/dist/lib/item-slab-support.d.ts.map +1 -0
- package/dist/lib/item-slab-support.js +40 -0
- package/dist/lib/level-footprints.d.ts +23 -0
- package/dist/lib/level-footprints.d.ts.map +1 -0
- package/dist/lib/level-footprints.js +398 -0
- package/dist/lib/level-name.d.ts +7 -0
- package/dist/lib/level-name.d.ts.map +1 -1
- package/dist/lib/level-name.js +11 -3
- package/dist/lib/new-zone.d.ts +10 -0
- package/dist/lib/new-zone.d.ts.map +1 -0
- package/dist/lib/new-zone.js +10 -0
- package/dist/lib/node-ancestry.d.ts +30 -0
- package/dist/lib/node-ancestry.d.ts.map +1 -0
- package/dist/lib/node-ancestry.js +76 -0
- package/dist/lib/node-visibility.d.ts +12 -0
- package/dist/lib/node-visibility.d.ts.map +1 -0
- package/dist/lib/node-visibility.js +12 -0
- package/dist/lib/object-cuts.d.ts +11 -0
- package/dist/lib/object-cuts.d.ts.map +1 -0
- package/dist/lib/object-cuts.js +63 -0
- package/dist/lib/opening-floor-datum.d.ts +24 -0
- package/dist/lib/opening-floor-datum.d.ts.map +1 -0
- package/dist/lib/opening-floor-datum.js +145 -0
- package/dist/lib/own-floor-intent.d.ts +15 -0
- package/dist/lib/own-floor-intent.d.ts.map +1 -0
- package/dist/lib/own-floor-intent.js +201 -0
- package/dist/lib/plate-reference.d.ts +3 -0
- package/dist/lib/plate-reference.d.ts.map +1 -0
- package/dist/lib/plate-reference.js +91 -0
- package/dist/lib/plate-surface.d.ts +120 -0
- package/dist/lib/plate-surface.d.ts.map +1 -0
- package/dist/lib/plate-surface.js +725 -0
- package/dist/lib/polygon-boolean.d.ts +17 -0
- package/dist/lib/polygon-boolean.d.ts.map +1 -0
- package/dist/lib/polygon-boolean.js +195 -0
- package/dist/lib/polygon-label.d.ts +7 -0
- package/dist/lib/polygon-label.d.ts.map +1 -0
- package/dist/lib/polygon-label.js +94 -0
- package/dist/lib/room-drawn-floor.d.ts +29 -0
- package/dist/lib/room-drawn-floor.d.ts.map +1 -0
- package/dist/lib/room-drawn-floor.js +88 -0
- package/dist/lib/room-floor-choices.d.ts +11 -0
- package/dist/lib/room-floor-choices.d.ts.map +1 -0
- package/dist/lib/room-floor-choices.js +60 -0
- package/dist/lib/room-floor-feasibility.d.ts +25 -0
- package/dist/lib/room-floor-feasibility.d.ts.map +1 -0
- package/dist/lib/room-floor-feasibility.js +293 -0
- package/dist/lib/room-floor-plate.d.ts +3 -0
- package/dist/lib/room-floor-plate.d.ts.map +1 -0
- package/dist/lib/room-floor-plate.js +4 -0
- package/dist/lib/room-graph.d.ts +79 -0
- package/dist/lib/room-graph.d.ts.map +1 -0
- package/dist/lib/room-graph.js +1265 -0
- package/dist/lib/room-name.d.ts +10 -0
- package/dist/lib/room-name.d.ts.map +1 -0
- package/dist/lib/room-name.js +22 -0
- package/dist/lib/room-topology-index.d.ts +53 -5
- package/dist/lib/room-topology-index.d.ts.map +1 -1
- package/dist/lib/room-topology-index.js +176 -30
- package/dist/lib/room-vertical-placement.d.ts +12 -0
- package/dist/lib/room-vertical-placement.d.ts.map +1 -0
- package/dist/lib/room-vertical-placement.js +28 -0
- package/dist/lib/room-zone-adoption.d.ts +74 -0
- package/dist/lib/room-zone-adoption.d.ts.map +1 -0
- package/dist/lib/room-zone-adoption.js +150 -0
- package/dist/lib/setback-envelope.d.ts +73 -0
- package/dist/lib/setback-envelope.d.ts.map +1 -0
- package/dist/lib/setback-envelope.js +506 -0
- package/dist/lib/slab-polygon.d.ts.map +1 -1
- package/dist/lib/slab-polygon.js +33 -12
- package/dist/lib/slots.d.ts +20 -0
- package/dist/lib/slots.d.ts.map +1 -1
- package/dist/lib/slots.js +44 -0
- package/dist/lib/space-detection.d.ts +14 -94
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +244 -1616
- package/dist/lib/structure-commit.d.ts +38 -0
- package/dist/lib/structure-commit.d.ts.map +1 -0
- package/dist/lib/structure-commit.js +52 -0
- package/dist/lib/structure-kernel.d.ts +66 -0
- package/dist/lib/structure-kernel.d.ts.map +1 -0
- package/dist/lib/structure-kernel.js +735 -0
- package/dist/lib/structure-reconcile.d.ts +15 -0
- package/dist/lib/structure-reconcile.d.ts.map +1 -0
- package/dist/lib/structure-reconcile.js +218 -0
- package/dist/lib/terrain-contours.d.ts +29 -0
- package/dist/lib/terrain-contours.d.ts.map +1 -0
- package/dist/lib/terrain-contours.js +199 -0
- package/dist/lib/terrain-source-persisted.d.ts +42 -0
- package/dist/lib/terrain-source-persisted.d.ts.map +1 -0
- package/dist/lib/terrain-source-persisted.js +68 -0
- package/dist/lib/terrain-source.d.ts +2 -40
- package/dist/lib/terrain-source.d.ts.map +1 -1
- package/dist/lib/terrain-source.js +2 -68
- package/dist/lib/terrain-support-query.d.ts +83 -0
- package/dist/lib/terrain-support-query.d.ts.map +1 -0
- package/dist/lib/terrain-support-query.js +187 -0
- package/dist/lib/terrain-support.d.ts +1 -77
- package/dist/lib/terrain-support.d.ts.map +1 -1
- package/dist/lib/terrain-support.js +3 -181
- package/dist/lib/unit-containment.js +2 -2
- package/dist/lib/unit-report.d.ts.map +1 -1
- package/dist/lib/unit-report.js +5 -4
- package/dist/lib/zone-colors.d.ts +11 -0
- package/dist/lib/zone-colors.d.ts.map +1 -0
- package/dist/lib/zone-colors.js +32 -0
- package/dist/lib/zone-quantities.d.ts.map +1 -1
- package/dist/lib/zone-quantities.js +113 -223
- package/dist/material-library.d.ts +9 -1
- package/dist/material-library.d.ts.map +1 -1
- package/dist/material-library.js +550 -1
- package/dist/node-slots/block.d.ts +7 -0
- package/dist/node-slots/block.d.ts.map +1 -0
- package/dist/node-slots/block.js +27 -0
- package/dist/node-slots/cabinet.d.ts +4 -0
- package/dist/node-slots/cabinet.d.ts.map +1 -0
- package/dist/node-slots/cabinet.js +24 -0
- package/dist/node-slots/ceiling.d.ts +6 -0
- package/dist/node-slots/ceiling.d.ts.map +1 -0
- package/dist/node-slots/ceiling.js +7 -0
- package/dist/node-slots/column.d.ts +9 -0
- package/dist/node-slots/column.d.ts.map +1 -0
- package/dist/node-slots/column.js +24 -0
- package/dist/node-slots/door.d.ts +9 -0
- package/dist/node-slots/door.d.ts.map +1 -0
- package/dist/node-slots/door.js +20 -0
- package/dist/node-slots/duct.d.ts +5 -0
- package/dist/node-slots/duct.d.ts.map +1 -0
- package/dist/node-slots/duct.js +5 -0
- package/dist/node-slots/elevator.d.ts +9 -0
- package/dist/node-slots/elevator.d.ts.map +1 -0
- package/dist/node-slots/elevator.js +17 -0
- package/dist/node-slots/fence.d.ts +10 -0
- package/dist/node-slots/fence.d.ts.map +1 -0
- package/dist/node-slots/fence.js +23 -0
- package/dist/node-slots/index.d.ts +25 -0
- package/dist/node-slots/index.d.ts.map +1 -0
- package/dist/node-slots/index.js +92 -0
- package/dist/node-slots/lean-to-extension.d.ts +5 -0
- package/dist/node-slots/lean-to-extension.d.ts.map +1 -0
- package/dist/node-slots/lean-to-extension.js +15 -0
- package/dist/node-slots/procedural-item.d.ts +4 -0
- package/dist/node-slots/procedural-item.d.ts.map +1 -0
- package/dist/node-slots/procedural-item.js +3 -0
- package/dist/node-slots/roof-accessories.d.ts +9 -0
- package/dist/node-slots/roof-accessories.d.ts.map +1 -0
- package/dist/node-slots/roof-accessories.js +34 -0
- package/dist/node-slots/shelf.d.ts +9 -0
- package/dist/node-slots/shelf.d.ts.map +1 -0
- package/dist/node-slots/shelf.js +30 -0
- package/dist/node-slots/slab.d.ts +7 -0
- package/dist/node-slots/slab.d.ts.map +1 -0
- package/dist/node-slots/slab.js +21 -0
- package/dist/node-slots/stair.d.ts +8 -0
- package/dist/node-slots/stair.d.ts.map +1 -0
- package/dist/node-slots/stair.js +17 -0
- package/dist/node-slots/wall.d.ts +12 -0
- package/dist/node-slots/wall.d.ts.map +1 -0
- package/dist/node-slots/wall.js +55 -0
- package/dist/node-slots/window.d.ts +5 -0
- package/dist/node-slots/window.d.ts.map +1 -0
- package/dist/node-slots/window.js +11 -0
- package/dist/procedural-items/design-placement.d.ts +49 -0
- package/dist/procedural-items/design-placement.d.ts.map +1 -0
- package/dist/procedural-items/design-placement.js +129 -0
- package/dist/procedural-items/design.d.ts +261 -0
- package/dist/procedural-items/design.d.ts.map +1 -0
- package/dist/procedural-items/design.js +707 -0
- package/dist/procedural-items/fixtures.d.ts +464 -24
- package/dist/procedural-items/fixtures.d.ts.map +1 -1
- package/dist/procedural-items/index.d.ts +5 -0
- package/dist/procedural-items/index.d.ts.map +1 -1
- package/dist/procedural-items/index.js +5 -0
- package/dist/procedural-items/integration.d.ts +127 -6
- package/dist/procedural-items/integration.d.ts.map +1 -1
- package/dist/procedural-items/joints.d.ts +56 -0
- package/dist/procedural-items/joints.d.ts.map +1 -0
- package/dist/procedural-items/joints.js +251 -0
- package/dist/procedural-items/library-colors.d.ts +12 -0
- package/dist/procedural-items/library-colors.d.ts.map +1 -1
- package/dist/procedural-items/library-colors.js +37 -3
- package/dist/procedural-items/motion-controller.d.ts +29 -0
- package/dist/procedural-items/motion-controller.d.ts.map +1 -0
- package/dist/procedural-items/motion-controller.js +130 -0
- package/dist/procedural-items/node.d.ts +490 -24
- package/dist/procedural-items/node.d.ts.map +1 -1
- package/dist/procedural-items/query.d.ts +19 -1
- package/dist/procedural-items/query.d.ts.map +1 -1
- package/dist/procedural-items/query.js +131 -48
- package/dist/procedural-items/recipe.d.ts +258 -14
- package/dist/procedural-items/recipe.d.ts.map +1 -1
- package/dist/procedural-items/recipe.js +933 -61
- package/dist/procedural-items/section.d.ts +27 -0
- package/dist/procedural-items/section.d.ts.map +1 -0
- package/dist/procedural-items/section.js +237 -0
- package/dist/procedural-items/spatial.d.ts +21 -0
- package/dist/procedural-items/spatial.d.ts.map +1 -1
- package/dist/procedural-items/spatial.js +90 -0
- package/dist/registry/handles.d.ts +30 -1
- package/dist/registry/handles.d.ts.map +1 -1
- package/dist/registry/index.d.ts +7 -5
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +4 -2
- package/dist/registry/mechanism.d.ts +9 -0
- package/dist/registry/mechanism.d.ts.map +1 -0
- package/dist/registry/mechanism.js +21 -0
- package/dist/registry/registry.d.ts +6 -3
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +59 -22
- package/dist/registry/scene-api.d.ts +20 -0
- package/dist/registry/scene-api.d.ts.map +1 -1
- package/dist/registry/scene-api.js +26 -13
- package/dist/registry/subtree.d.ts +8 -0
- package/dist/registry/subtree.d.ts.map +1 -1
- package/dist/registry/subtree.js +59 -0
- package/dist/registry/types.d.ts +352 -13
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/schema/__fixtures__/node-fixtures.d.ts.map +1 -1
- package/dist/schema/__fixtures__/node-fixtures.js +2 -0
- package/dist/schema/assembly.d.ts +153 -0
- package/dist/schema/assembly.d.ts.map +1 -0
- package/dist/schema/assembly.js +155 -0
- package/dist/schema/asset-url.d.ts +2 -1
- package/dist/schema/asset-url.d.ts.map +1 -1
- package/dist/schema/asset-url.js +5 -2
- package/dist/schema/base.d.ts +26 -0
- package/dist/schema/base.d.ts.map +1 -1
- package/dist/schema/base.js +2 -0
- package/dist/schema/collection-templates.d.ts +46 -0
- package/dist/schema/collection-templates.d.ts.map +1 -0
- package/dist/schema/collection-templates.js +14 -0
- package/dist/schema/collections.d.ts +7 -0
- package/dist/schema/collections.d.ts.map +1 -1
- package/dist/schema/cut.d.ts +29 -0
- package/dist/schema/cut.d.ts.map +1 -0
- package/dist/schema/cut.js +50 -0
- package/dist/schema/geometry-metadata.d.ts +51 -0
- package/dist/schema/geometry-metadata.d.ts.map +1 -0
- package/dist/schema/geometry-metadata.js +57 -0
- package/dist/schema/geometry-source.d.ts +250 -0
- package/dist/schema/geometry-source.d.ts.map +1 -0
- package/dist/schema/geometry-source.js +130 -0
- package/dist/schema/index.d.ts +20 -7
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +22 -5
- package/dist/schema/nodes/block.d.ts +28 -2
- package/dist/schema/nodes/block.d.ts.map +1 -1
- package/dist/schema/nodes/block.js +22 -9
- package/dist/schema/nodes/box-vent.d.ts +26 -0
- package/dist/schema/nodes/box-vent.d.ts.map +1 -1
- package/dist/schema/nodes/building.d.ts +26 -0
- package/dist/schema/nodes/building.d.ts.map +1 -1
- package/dist/schema/nodes/cabinet.d.ts +61 -9
- package/dist/schema/nodes/cabinet.d.ts.map +1 -1
- package/dist/schema/nodes/ceiling.d.ts +37 -0
- package/dist/schema/nodes/ceiling.d.ts.map +1 -1
- package/dist/schema/nodes/ceiling.js +8 -0
- package/dist/schema/nodes/chimney.d.ts +26 -0
- package/dist/schema/nodes/chimney.d.ts.map +1 -1
- package/dist/schema/nodes/column.d.ts +126 -0
- package/dist/schema/nodes/column.d.ts.map +1 -1
- package/dist/schema/nodes/column.js +2 -0
- package/dist/schema/nodes/construction-dimension.d.ts +26 -0
- package/dist/schema/nodes/construction-dimension.d.ts.map +1 -1
- package/dist/schema/nodes/cupola.d.ts +26 -0
- package/dist/schema/nodes/cupola.d.ts.map +1 -1
- package/dist/schema/nodes/curtain-wall.d.ts +129 -0
- package/dist/schema/nodes/curtain-wall.d.ts.map +1 -0
- package/dist/schema/nodes/curtain-wall.js +49 -0
- package/dist/schema/nodes/door.d.ts +141 -22
- package/dist/schema/nodes/door.d.ts.map +1 -1
- package/dist/schema/nodes/door.js +18 -33
- package/dist/schema/nodes/dormer.d.ts +28 -2
- package/dist/schema/nodes/dormer.d.ts.map +1 -1
- package/dist/schema/nodes/downspout.d.ts +27 -1
- package/dist/schema/nodes/downspout.d.ts.map +1 -1
- package/dist/schema/nodes/duct-fitting.d.ts +27 -1
- package/dist/schema/nodes/duct-fitting.d.ts.map +1 -1
- package/dist/schema/nodes/duct-segment.d.ts +27 -1
- package/dist/schema/nodes/duct-segment.d.ts.map +1 -1
- package/dist/schema/nodes/duct-terminal.d.ts +26 -0
- package/dist/schema/nodes/duct-terminal.d.ts.map +1 -1
- package/dist/schema/nodes/elevator.d.ts +26 -0
- package/dist/schema/nodes/elevator.d.ts.map +1 -1
- package/dist/schema/nodes/eyebrow-vent.d.ts +26 -0
- package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -1
- package/dist/schema/nodes/fence-feature.d.ts +141 -0
- package/dist/schema/nodes/fence-feature.d.ts.map +1 -0
- package/dist/schema/nodes/fence-feature.js +21 -0
- package/dist/schema/nodes/fence.d.ts +186 -0
- package/dist/schema/nodes/fence.d.ts.map +1 -1
- package/dist/schema/nodes/fence.js +92 -7
- package/dist/schema/nodes/floor-opening.d.ts +61 -0
- package/dist/schema/nodes/floor-opening.d.ts.map +1 -0
- package/dist/schema/nodes/floor-opening.js +17 -0
- package/dist/schema/nodes/guide.d.ts +26 -0
- package/dist/schema/nodes/guide.d.ts.map +1 -1
- package/dist/schema/nodes/gutter.d.ts +26 -0
- package/dist/schema/nodes/gutter.d.ts.map +1 -1
- package/dist/schema/nodes/hvac-equipment.d.ts +26 -0
- package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -1
- package/dist/schema/nodes/imported-mesh.d.ts +26 -0
- package/dist/schema/nodes/imported-mesh.d.ts.map +1 -1
- package/dist/schema/nodes/item.d.ts +153 -2
- package/dist/schema/nodes/item.d.ts.map +1 -1
- package/dist/schema/nodes/item.js +13 -0
- package/dist/schema/nodes/lean-to-extension.d.ts +26 -0
- package/dist/schema/nodes/lean-to-extension.d.ts.map +1 -1
- package/dist/schema/nodes/level.d.ts +27 -1
- package/dist/schema/nodes/level.d.ts.map +1 -1
- package/dist/schema/nodes/lineset.d.ts +26 -0
- package/dist/schema/nodes/lineset.d.ts.map +1 -1
- package/dist/schema/nodes/liquid-line.d.ts +26 -0
- package/dist/schema/nodes/liquid-line.d.ts.map +1 -1
- package/dist/schema/nodes/measurement.d.ts +26 -0
- package/dist/schema/nodes/measurement.d.ts.map +1 -1
- package/dist/schema/nodes/opening-types.d.ts +41 -0
- package/dist/schema/nodes/opening-types.d.ts.map +1 -0
- package/dist/schema/nodes/opening-types.js +48 -0
- package/dist/schema/nodes/pipe-fitting.d.ts +26 -0
- package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -1
- package/dist/schema/nodes/pipe-segment.d.ts +26 -0
- package/dist/schema/nodes/pipe-segment.d.ts.map +1 -1
- package/dist/schema/nodes/pipe-trap.d.ts +26 -0
- package/dist/schema/nodes/pipe-trap.d.ts.map +1 -1
- package/dist/schema/nodes/ridge-vent.d.ts +26 -0
- package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment-walls.js +2 -1
- package/dist/schema/nodes/roof-segment.d.ts +30 -2
- package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment.js +9 -0
- package/dist/schema/nodes/roof.d.ts +101 -0
- package/dist/schema/nodes/roof.d.ts.map +1 -1
- package/dist/schema/nodes/roof.js +6 -0
- package/dist/schema/nodes/scan.d.ts +26 -0
- package/dist/schema/nodes/scan.d.ts.map +1 -1
- package/dist/schema/nodes/separator.d.ts +50 -0
- package/dist/schema/nodes/separator.d.ts.map +1 -0
- package/dist/schema/nodes/separator.js +8 -0
- package/dist/schema/nodes/shelf.d.ts +26 -0
- package/dist/schema/nodes/shelf.d.ts.map +1 -1
- package/dist/schema/nodes/site.d.ts +192 -0
- package/dist/schema/nodes/site.d.ts.map +1 -1
- package/dist/schema/nodes/site.js +178 -1
- package/dist/schema/nodes/skylight.d.ts +30 -4
- package/dist/schema/nodes/skylight.d.ts.map +1 -1
- package/dist/schema/nodes/slab.d.ts +84 -0
- package/dist/schema/nodes/slab.d.ts.map +1 -1
- package/dist/schema/nodes/slab.js +37 -2
- package/dist/schema/nodes/solar-panel.d.ts +26 -0
- package/dist/schema/nodes/solar-panel.d.ts.map +1 -1
- package/dist/schema/nodes/spawn.d.ts +26 -0
- package/dist/schema/nodes/spawn.d.ts.map +1 -1
- package/dist/schema/nodes/stair-construction.d.ts +21 -0
- package/dist/schema/nodes/stair-construction.d.ts.map +1 -0
- package/dist/schema/nodes/stair-construction.js +13 -0
- package/dist/schema/nodes/stair-design-targets.d.ts +9 -0
- package/dist/schema/nodes/stair-design-targets.d.ts.map +1 -0
- package/dist/schema/nodes/stair-design-targets.js +7 -0
- package/dist/schema/nodes/stair-segment.d.ts +72 -4
- package/dist/schema/nodes/stair-segment.d.ts.map +1 -1
- package/dist/schema/nodes/stair-segment.js +13 -0
- package/dist/schema/nodes/stair.d.ts +119 -2
- package/dist/schema/nodes/stair.d.ts.map +1 -1
- package/dist/schema/nodes/stair.js +59 -2
- package/dist/schema/nodes/structural-grid.d.ts +26 -0
- package/dist/schema/nodes/structural-grid.d.ts.map +1 -1
- package/dist/schema/nodes/surface-hole-metadata.d.ts +3 -0
- package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
- package/dist/schema/nodes/surface-hole-metadata.js +2 -1
- package/dist/schema/nodes/surface-paint-region.d.ts +14 -0
- package/dist/schema/nodes/surface-paint-region.d.ts.map +1 -0
- package/dist/schema/nodes/surface-paint-region.js +12 -0
- package/dist/schema/nodes/turbine-vent.d.ts +26 -0
- package/dist/schema/nodes/turbine-vent.d.ts.map +1 -1
- package/dist/schema/nodes/unit.d.ts +26 -0
- package/dist/schema/nodes/unit.d.ts.map +1 -1
- package/dist/schema/nodes/wall.d.ts +423 -59
- package/dist/schema/nodes/wall.d.ts.map +1 -1
- package/dist/schema/nodes/wall.js +188 -186
- package/dist/schema/nodes/window.d.ts +139 -20
- package/dist/schema/nodes/window.d.ts.map +1 -1
- package/dist/schema/nodes/window.js +12 -14
- package/dist/schema/nodes/zone.d.ts +69 -1
- package/dist/schema/nodes/zone.d.ts.map +1 -1
- package/dist/schema/nodes/zone.js +46 -0
- package/dist/schema/provenance.d.ts +118 -0
- package/dist/schema/provenance.d.ts.map +1 -0
- package/dist/schema/provenance.js +76 -0
- package/dist/schema/source-ref.d.ts +23 -0
- package/dist/schema/source-ref.d.ts.map +1 -0
- package/dist/schema/source-ref.js +29 -0
- package/dist/schema/types.d.ts +3389 -67
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +7 -0
- package/dist/services/__fixtures__/hosting.d.ts +3 -0
- package/dist/services/__fixtures__/hosting.d.ts.map +1 -0
- package/dist/services/__fixtures__/hosting.js +9 -0
- package/dist/services/__fixtures__/shelf-surface-rows.d.ts +4 -0
- package/dist/services/__fixtures__/shelf-surface-rows.d.ts.map +1 -0
- package/dist/services/__fixtures__/shelf-surface-rows.js +18 -0
- package/dist/services/alignment-anchors.d.ts +2 -1
- package/dist/services/alignment-anchors.d.ts.map +1 -1
- package/dist/services/alignment-anchors.js +9 -6
- package/dist/services/hosting.d.ts +3 -0
- package/dist/services/hosting.d.ts.map +1 -1
- package/dist/services/hosting.js +24 -7
- package/dist/services/index.d.ts +4 -4
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +4 -4
- package/dist/services/level-height.d.ts.map +1 -1
- package/dist/services/level-height.js +6 -4
- package/dist/services/snap.d.ts +3 -0
- package/dist/services/snap.d.ts.map +1 -1
- package/dist/services/snap.js +11 -0
- package/dist/services/storey.d.ts +21 -1
- package/dist/services/storey.d.ts.map +1 -1
- package/dist/services/storey.js +94 -8
- package/dist/services/surface-hosting.d.ts +6 -2
- package/dist/services/surface-hosting.d.ts.map +1 -1
- package/dist/services/surface-hosting.js +51 -11
- package/dist/store/actions/node-actions.d.ts +4563 -8
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +497 -269
- package/dist/store/derived-node-guard.d.ts +84 -0
- package/dist/store/derived-node-guard.d.ts.map +1 -0
- package/dist/store/derived-node-guard.js +586 -0
- package/dist/store/history-control.d.ts +34 -0
- package/dist/store/history-control.d.ts.map +1 -1
- package/dist/store/history-control.js +99 -1
- package/dist/store/history-drafts.d.ts +48 -0
- package/dist/store/history-drafts.d.ts.map +1 -0
- package/dist/store/history-drafts.js +403 -0
- package/dist/store/history-invalidation.d.ts.map +1 -1
- package/dist/store/history-invalidation.js +1 -0
- package/dist/store/item-interaction.d.ts +28 -0
- package/dist/store/item-interaction.d.ts.map +1 -0
- package/dist/store/item-interaction.js +34 -0
- package/dist/store/registered-node-defaults.d.ts +11 -0
- package/dist/store/registered-node-defaults.d.ts.map +1 -0
- package/dist/store/registered-node-defaults.js +25 -0
- package/dist/store/scene-annotations.d.ts +8969 -0
- package/dist/store/scene-annotations.d.ts.map +1 -0
- package/dist/store/scene-annotations.js +107 -0
- package/dist/store/use-interactive.d.ts +23 -1
- package/dist/store/use-interactive.d.ts.map +1 -1
- package/dist/store/use-interactive.js +128 -5
- package/dist/store/use-scene.d.ts +68 -12
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +429 -306
- package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-opening-sync.js +4 -6
- package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-opening-system.js +12 -6
- package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-service.js +8 -3
- package/dist/systems/fence/fence-centerline.d.ts.map +1 -1
- package/dist/systems/fence/fence-centerline.js +6 -5
- package/dist/systems/fence/fence-features.d.ts +36 -0
- package/dist/systems/fence/fence-features.d.ts.map +1 -0
- package/dist/systems/fence/fence-features.js +105 -0
- package/dist/systems/fence/fence-spline.d.ts +8 -5
- package/dist/systems/fence/fence-spline.d.ts.map +1 -1
- package/dist/systems/fence/fence-spline.js +51 -16
- package/dist/systems/owned-floor-openings.d.ts +9 -0
- package/dist/systems/owned-floor-openings.d.ts.map +1 -0
- package/dist/systems/owned-floor-openings.js +326 -0
- package/dist/systems/reconcile-owned-floor-openings.d.ts +3 -0
- package/dist/systems/reconcile-owned-floor-openings.d.ts.map +1 -0
- package/dist/systems/reconcile-owned-floor-openings.js +25 -0
- package/dist/systems/slab/__fixtures__/doorway-steps.d.ts +8 -0
- package/dist/systems/slab/__fixtures__/doorway-steps.d.ts.map +1 -0
- package/dist/systems/slab/__fixtures__/doorway-steps.js +62 -0
- package/dist/systems/slab/__fixtures__/floor-step.d.ts +5636 -0
- package/dist/systems/slab/__fixtures__/floor-step.d.ts.map +1 -0
- package/dist/systems/slab/__fixtures__/floor-step.js +66 -0
- package/dist/systems/slab/__fixtures__/raised-room.d.ts +5775 -0
- package/dist/systems/slab/__fixtures__/raised-room.d.ts.map +1 -0
- package/dist/systems/slab/__fixtures__/raised-room.js +54 -0
- package/dist/systems/slab/ensure-slab-openings.d.ts +9 -0
- package/dist/systems/slab/ensure-slab-openings.d.ts.map +1 -0
- package/dist/systems/slab/ensure-slab-openings.js +41 -0
- package/dist/systems/slab/slab-support.d.ts +25 -6
- package/dist/systems/slab/slab-support.d.ts.map +1 -1
- package/dist/systems/slab/slab-support.js +471 -54
- package/dist/systems/stair/stair-base-elevation.d.ts +3 -0
- package/dist/systems/stair/stair-base-elevation.d.ts.map +1 -0
- package/dist/systems/stair/stair-base-elevation.js +49 -0
- package/dist/systems/stair/stair-clearance.d.ts +39 -0
- package/dist/systems/stair/stair-clearance.d.ts.map +1 -0
- package/dist/systems/stair/stair-clearance.js +508 -0
- package/dist/systems/stair/stair-construction.d.ts +37 -0
- package/dist/systems/stair/stair-construction.d.ts.map +1 -0
- package/dist/systems/stair/stair-construction.js +314 -0
- package/dist/systems/stair/stair-detail-budget.d.ts +13 -0
- package/dist/systems/stair/stair-detail-budget.d.ts.map +1 -0
- package/dist/systems/stair/stair-detail-budget.js +64 -0
- package/dist/systems/stair/stair-flight.d.ts +2 -2
- package/dist/systems/stair/stair-flight.d.ts.map +1 -1
- package/dist/systems/stair/stair-flight.js +4 -3
- package/dist/systems/stair/stair-floor-footprints.d.ts +7 -0
- package/dist/systems/stair/stair-floor-footprints.d.ts.map +1 -0
- package/dist/systems/stair/stair-floor-footprints.js +113 -0
- package/dist/systems/stair/stair-footprint.d.ts +3 -1
- package/dist/systems/stair/stair-footprint.d.ts.map +1 -1
- package/dist/systems/stair/stair-footprint.js +97 -44
- package/dist/systems/stair/stair-layout.d.ts +39 -0
- package/dist/systems/stair/stair-layout.d.ts.map +1 -0
- package/dist/systems/stair/stair-layout.js +58 -0
- package/dist/systems/stair/stair-opening-preview.d.ts +1650 -84
- package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-sync.d.ts +8 -2
- package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-sync.js +136 -315
- package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-system.js +40 -8
- package/dist/systems/stair/stair-presets.d.ts +276 -0
- package/dist/systems/stair/stair-presets.d.ts.map +1 -0
- package/dist/systems/stair/stair-presets.js +151 -0
- package/dist/systems/stair/stair-rail-budget.d.ts +6 -0
- package/dist/systems/stair/stair-rail-budget.d.ts.map +1 -0
- package/dist/systems/stair/stair-rail-budget.js +36 -0
- package/dist/systems/stair/stair-rail-path.d.ts +22 -0
- package/dist/systems/stair/stair-rail-path.d.ts.map +1 -0
- package/dist/systems/stair/stair-rail-path.js +497 -0
- package/dist/systems/stair/stair-rise-edit.d.ts +9 -0
- package/dist/systems/stair/stair-rise-edit.d.ts.map +1 -0
- package/dist/systems/stair/stair-rise-edit.js +46 -0
- package/dist/systems/stair/stair-rise-query.d.ts +24 -0
- package/dist/systems/stair/stair-rise-query.d.ts.map +1 -0
- package/dist/systems/stair/stair-rise-query.js +125 -0
- package/dist/systems/stair/stair-rise.d.ts +4 -19
- package/dist/systems/stair/stair-rise.d.ts.map +1 -1
- package/dist/systems/stair/stair-rise.js +8 -71
- package/dist/systems/stair/stair-sizing.d.ts +414 -0
- package/dist/systems/stair/stair-sizing.d.ts.map +1 -0
- package/dist/systems/stair/stair-sizing.js +255 -0
- package/dist/systems/stair/stair-sweep-edit.d.ts +7 -0
- package/dist/systems/stair/stair-sweep-edit.d.ts.map +1 -0
- package/dist/systems/stair/stair-sweep-edit.js +8 -0
- package/dist/systems/stair/stair-walking-line.d.ts +5 -0
- package/dist/systems/stair/stair-walking-line.d.ts.map +1 -0
- package/dist/systems/stair/stair-walking-line.js +91 -0
- package/dist/systems/stair/stair-winder.d.ts +36 -0
- package/dist/systems/stair/stair-winder.d.ts.map +1 -0
- package/dist/systems/stair/stair-winder.js +173 -0
- package/dist/systems/wall/wall-assembly.d.ts +234 -0
- package/dist/systems/wall/wall-assembly.d.ts.map +1 -0
- package/dist/systems/wall/wall-assembly.js +854 -0
- package/dist/systems/wall/wall-curve.d.ts +2 -1
- package/dist/systems/wall/wall-curve.d.ts.map +1 -1
- package/dist/systems/wall/wall-curve.js +21 -5
- package/dist/systems/wall/wall-finish.d.ts +102 -0
- package/dist/systems/wall/wall-finish.d.ts.map +1 -0
- package/dist/systems/wall/wall-finish.js +264 -0
- package/dist/systems/wall/wall-footprint.d.ts +3 -0
- package/dist/systems/wall/wall-footprint.d.ts.map +1 -1
- package/dist/systems/wall/wall-footprint.js +15 -13
- package/dist/systems/wall/wall-frame.d.ts +45 -0
- package/dist/systems/wall/wall-frame.d.ts.map +1 -0
- package/dist/systems/wall/wall-frame.js +162 -0
- package/dist/systems/wall/wall-layer-bands.d.ts +38 -0
- package/dist/systems/wall/wall-layer-bands.d.ts.map +1 -0
- package/dist/systems/wall/wall-layer-bands.js +109 -0
- package/dist/systems/wall/wall-merge.d.ts +57 -0
- package/dist/systems/wall/wall-merge.d.ts.map +1 -0
- package/dist/systems/wall/wall-merge.js +411 -0
- package/dist/systems/wall/wall-mitering.d.ts +6 -5
- package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
- package/dist/systems/wall/wall-mitering.js +70 -20
- package/dist/systems/wall/wall-operations.d.ts +28 -0
- package/dist/systems/wall/wall-operations.d.ts.map +1 -0
- package/dist/systems/wall/wall-operations.js +146 -0
- package/dist/systems/wall/wall-paint-plan.d.ts +30 -0
- package/dist/systems/wall/wall-paint-plan.d.ts.map +1 -0
- package/dist/systems/wall/wall-paint-plan.js +167 -0
- package/dist/systems/wall/wall-room-sides.d.ts +6 -0
- package/dist/systems/wall/wall-room-sides.d.ts.map +1 -0
- package/dist/systems/wall/wall-room-sides.js +14 -0
- package/dist/systems/wall/wall-step-openings.d.ts +17 -0
- package/dist/systems/wall/wall-step-openings.d.ts.map +1 -0
- package/dist/systems/wall/wall-step-openings.js +32 -0
- package/dist/systems/wall/wall-top.d.ts +2 -6
- package/dist/systems/wall/wall-top.d.ts.map +1 -1
- package/dist/systems/wall/wall-top.js +3 -9
- package/dist/systems/wall/wall-topology.d.ts +28 -0
- package/dist/systems/wall/wall-topology.d.ts.map +1 -1
- package/dist/systems/wall/wall-topology.js +458 -9
- package/dist/utils/clone-scene-graph.d.ts +6 -0
- package/dist/utils/clone-scene-graph.d.ts.map +1 -1
- package/dist/utils/clone-scene-graph.js +123 -15
- package/dist/utils/ensure-scene-openings.d.ts +8965 -0
- package/dist/utils/ensure-scene-openings.d.ts.map +1 -0
- package/dist/utils/ensure-scene-openings.js +29 -0
- package/dist/utils/exterior-threshold-migration.d.ts +4 -0
- package/dist/utils/exterior-threshold-migration.d.ts.map +1 -0
- package/dist/utils/exterior-threshold-migration.js +114 -0
- package/dist/utils/floor-follow-migration.d.ts +3 -0
- package/dist/utils/floor-follow-migration.d.ts.map +1 -0
- package/dist/utils/floor-follow-migration.js +29 -0
- package/dist/utils/floor-opening-migration.d.ts +13 -0
- package/dist/utils/floor-opening-migration.d.ts.map +1 -0
- package/dist/utils/floor-opening-migration.js +148 -0
- package/dist/utils/floor-piece-adoption.d.ts +50 -0
- package/dist/utils/floor-piece-adoption.d.ts.map +1 -0
- package/dist/utils/floor-piece-adoption.js +182 -0
- package/dist/utils/floor-plate-migration.d.ts +4507 -0
- package/dist/utils/floor-plate-migration.d.ts.map +1 -0
- package/dist/utils/floor-plate-migration.js +1360 -0
- package/dist/utils/heal-plan-coordinates.d.ts +2 -0
- package/dist/utils/heal-plan-coordinates.d.ts.map +1 -0
- package/dist/utils/heal-plan-coordinates.js +69 -0
- package/dist/utils/heal-scene-graph.d.ts +20 -6
- package/dist/utils/heal-scene-graph.d.ts.map +1 -1
- package/dist/utils/heal-scene-graph.js +61 -16
- package/dist/utils/legacy-material-slots.d.ts +23 -0
- package/dist/utils/legacy-material-slots.d.ts.map +1 -0
- package/dist/utils/legacy-material-slots.js +120 -0
- package/dist/utils/legacy-opening-datums.d.ts +11 -0
- package/dist/utils/legacy-opening-datums.d.ts.map +1 -0
- package/dist/utils/legacy-opening-datums.js +92 -0
- package/dist/utils/legacy-wall-datums.d.ts +6 -0
- package/dist/utils/legacy-wall-datums.d.ts.map +1 -0
- package/dist/utils/legacy-wall-datums.js +199 -0
- package/dist/utils/load-migration.d.ts +28 -0
- package/dist/utils/load-migration.d.ts.map +1 -0
- package/dist/utils/load-migration.js +89 -0
- package/dist/utils/node-defaults.d.ts +37 -0
- package/dist/utils/node-defaults.d.ts.map +1 -0
- package/dist/utils/node-defaults.js +96 -0
- package/dist/utils/normalize-legacy-structure.d.ts +2 -0
- package/dist/utils/normalize-legacy-structure.d.ts.map +1 -0
- package/dist/utils/normalize-legacy-structure.js +61 -0
- package/dist/utils/omit-undefined.d.ts +2 -0
- package/dist/utils/omit-undefined.d.ts.map +1 -0
- package/dist/utils/omit-undefined.js +17 -0
- package/dist/utils/owned-floor-opening-migration.d.ts +4489 -0
- package/dist/utils/owned-floor-opening-migration.d.ts.map +1 -0
- package/dist/utils/owned-floor-opening-migration.js +365 -0
- package/dist/utils/reconcile-structure-on-load.d.ts +8965 -0
- package/dist/utils/reconcile-structure-on-load.d.ts.map +1 -0
- package/dist/utils/reconcile-structure-on-load.js +307 -0
- package/dist/utils/room-zone-migration.d.ts +18 -0
- package/dist/utils/room-zone-migration.d.ts.map +1 -0
- package/dist/utils/room-zone-migration.js +258 -0
- package/dist/utils/scene-migrations.d.ts +16 -1
- package/dist/utils/scene-migrations.d.ts.map +1 -1
- package/dist/utils/scene-migrations.js +15 -1
- package/dist/utils/structure-id.d.ts +4 -0
- package/dist/utils/structure-id.d.ts.map +1 -0
- package/dist/utils/structure-id.js +115 -0
- package/dist/utils/vertical-scene-migration.d.ts +8 -8
- package/dist/utils/vertical-scene-migration.d.ts.map +1 -1
- package/dist/utils/vertical-scene-migration.js +18 -8
- package/dist/utils/wall-assembly-migration.d.ts +14 -0
- package/dist/utils/wall-assembly-migration.d.ts.map +1 -0
- package/dist/utils/wall-assembly-migration.js +23 -0
- package/dist/utils/wall-face-migration.d.ts +23 -0
- package/dist/utils/wall-face-migration.d.ts.map +1 -0
- package/dist/utils/wall-face-migration.js +246 -0
- package/dist/validation/opening-bounds.d.ts +36 -0
- package/dist/validation/opening-bounds.d.ts.map +1 -0
- package/dist/validation/opening-bounds.js +34 -0
- package/dist/validation/validate-build-json.d.ts +2 -0
- package/dist/validation/validate-build-json.d.ts.map +1 -1
- package/dist/validation/validate-build-json.js +48 -1
- package/package.json +37 -4
- package/dist/services/surface-hosting-procedural-parity.fixtures.d.ts +0 -13
- package/dist/services/surface-hosting-procedural-parity.fixtures.d.ts.map +0 -1
- package/dist/services/surface-hosting-procedural-parity.fixtures.js +0 -4691
- package/dist/services/surface-hosting-shelf-parity.fixtures.d.ts +0 -1060
- package/dist/services/surface-hosting-shelf-parity.fixtures.d.ts.map +0 -1
- package/dist/services/surface-hosting-shelf-parity.fixtures.js +0 -3127
|
@@ -1,890 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
const CEILING_HEIGHT_EPSILON = 1e-6;
|
|
14
|
-
const ROOM_CURVE_TOLERANCE = 0.04;
|
|
15
|
-
const MAX_CURVE_SUBDIVISION_DEPTH = 6;
|
|
16
|
-
const AUTO_SLAB_POLYGON_SIMPLIFY_TOLERANCE = 0.08;
|
|
17
|
-
const WALL_ROOM_BOUNDARY_TOLERANCE = 0.08;
|
|
18
|
-
// A wall endpoint within this distance of another wall's interior is treated as a
|
|
19
|
-
// T-junction and splits that wall (see `splitStraightWallAtVertices`).
|
|
20
|
-
const WALL_JUNCTION_TOLERANCE = 0.08;
|
|
21
|
-
// An unmatched auto slab/ceiling whose polygon is still substantially covered
|
|
22
|
-
// by a detected room was absorbed by a room merge — the surviving auto surface
|
|
23
|
-
// owns that area, so keeping it would z-fight and it is deleted. Below this
|
|
24
|
-
// coverage the room genuinely ceased to exist (e.g. an enclosing wall was
|
|
25
|
-
// deleted) and the node is demoted to manual so user data survives.
|
|
26
|
-
const ORPHAN_MERGE_COVERAGE_THRESHOLD = 0.6;
|
|
27
|
-
const COVERAGE_SAMPLE_STEPS = 12;
|
|
28
|
-
// Rewrite deadband for an existing auto surface's elevation/height: below this
|
|
29
|
-
// the derived plane is the same plane and writing it would churn history.
|
|
30
|
-
const ROOM_VERTICAL_PLANE_EPSILON = 1e-3;
|
|
31
|
-
function pointFromTuple(point) {
|
|
32
|
-
return { x: point[0], y: point[1] };
|
|
33
|
-
}
|
|
34
|
-
function pointToTuple(point) {
|
|
35
|
-
return [point.x, point.y];
|
|
36
|
-
}
|
|
37
|
-
function pointKey(point) {
|
|
38
|
-
return `${point.x.toFixed(3)},${point.y.toFixed(3)}`;
|
|
39
|
-
}
|
|
40
|
-
function polygonArea(points) {
|
|
41
|
-
let area = 0;
|
|
42
|
-
for (let i = 0; i < points.length; i++) {
|
|
43
|
-
const a = points[i];
|
|
44
|
-
const b = points[(i + 1) % points.length];
|
|
45
|
-
if (!(a && b))
|
|
46
|
-
continue;
|
|
47
|
-
area += a.x * b.y - b.x * a.y;
|
|
48
|
-
}
|
|
49
|
-
return area / 2;
|
|
50
|
-
}
|
|
51
|
-
function minRotationSignature(keys) {
|
|
52
|
-
if (keys.length === 0)
|
|
53
|
-
return '';
|
|
54
|
-
let best = '';
|
|
55
|
-
for (let i = 0; i < keys.length; i++) {
|
|
56
|
-
const rotated = [...keys.slice(i), ...keys.slice(0, i)];
|
|
57
|
-
const value = rotated.join('|');
|
|
58
|
-
if (!best || value < best)
|
|
59
|
-
best = value;
|
|
60
|
-
}
|
|
61
|
-
return best;
|
|
62
|
-
}
|
|
63
|
-
function polygonSignature(points) {
|
|
64
|
-
const keys = points.map(pointKey);
|
|
65
|
-
const forward = minRotationSignature(keys);
|
|
66
|
-
const reversed = minRotationSignature([...keys].reverse());
|
|
67
|
-
return forward < reversed ? forward : reversed;
|
|
68
|
-
}
|
|
69
|
-
function samePointWithinTolerance(a, b, tolerance = 1e-4) {
|
|
70
|
-
return Math.hypot(a.x - b.x, a.y - b.y) <= tolerance;
|
|
71
|
-
}
|
|
72
|
-
function dedupeSequentialPoints(points, tolerance = 1e-4) {
|
|
73
|
-
const deduped = [];
|
|
74
|
-
for (const point of points) {
|
|
75
|
-
const previous = deduped[deduped.length - 1];
|
|
76
|
-
if (previous && samePointWithinTolerance(previous, point, tolerance)) {
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
deduped.push(point);
|
|
80
|
-
}
|
|
81
|
-
const firstPoint = deduped[0];
|
|
82
|
-
const lastPoint = deduped[deduped.length - 1];
|
|
83
|
-
if (deduped.length > 2 &&
|
|
84
|
-
firstPoint &&
|
|
85
|
-
lastPoint &&
|
|
86
|
-
samePointWithinTolerance(firstPoint, lastPoint, tolerance)) {
|
|
87
|
-
deduped.pop();
|
|
88
|
-
}
|
|
89
|
-
return deduped;
|
|
90
|
-
}
|
|
91
|
-
function pointInPolygon(point, polygon) {
|
|
92
|
-
if (polygon.length < 3)
|
|
93
|
-
return false;
|
|
94
|
-
let inside = false;
|
|
95
|
-
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
96
|
-
const xi = polygon[i]?.x ?? 0;
|
|
97
|
-
const yi = polygon[i]?.y ?? 0;
|
|
98
|
-
const xj = polygon[j]?.x ?? 0;
|
|
99
|
-
const yj = polygon[j]?.y ?? 0;
|
|
100
|
-
const intersect = yi > point.y !== yj > point.y &&
|
|
101
|
-
point.x < ((xj - xi) * (point.y - yi)) / (yj - yi + 1e-12) + xi;
|
|
102
|
-
if (intersect)
|
|
103
|
-
inside = !inside;
|
|
104
|
-
}
|
|
105
|
-
return inside;
|
|
106
|
-
}
|
|
107
|
-
function pointInAnyPolygon(point, polygons) {
|
|
108
|
-
return polygons.some((polygon) => pointInPolygon(point, polygon));
|
|
109
|
-
}
|
|
110
|
-
function polygonCentroid(points) {
|
|
111
|
-
const sum = points.reduce((acc, point) => ({ x: acc.x + point.x, y: acc.y + point.y }), {
|
|
112
|
-
x: 0,
|
|
113
|
-
y: 0,
|
|
114
|
-
});
|
|
115
|
-
return {
|
|
116
|
-
x: sum.x / Math.max(points.length, 1),
|
|
117
|
-
y: sum.y / Math.max(points.length, 1),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
function bboxOf(points) {
|
|
121
|
-
let minX = Number.POSITIVE_INFINITY;
|
|
122
|
-
let minY = Number.POSITIVE_INFINITY;
|
|
123
|
-
let maxX = Number.NEGATIVE_INFINITY;
|
|
124
|
-
let maxY = Number.NEGATIVE_INFINITY;
|
|
125
|
-
for (const point of points) {
|
|
126
|
-
minX = Math.min(minX, point.x);
|
|
127
|
-
minY = Math.min(minY, point.y);
|
|
128
|
-
maxX = Math.max(maxX, point.x);
|
|
129
|
-
maxY = Math.max(maxY, point.y);
|
|
130
|
-
}
|
|
131
|
-
return { minX, minY, maxX, maxY };
|
|
132
|
-
}
|
|
133
|
-
function bboxOverlapArea(a, b) {
|
|
134
|
-
const ix = Math.max(0, Math.min(a.maxX, b.maxX) - Math.max(a.minX, b.minX));
|
|
135
|
-
const iy = Math.max(0, Math.min(a.maxY, b.maxY) - Math.max(a.minY, b.minY));
|
|
136
|
-
return ix * iy;
|
|
137
|
-
}
|
|
138
|
-
// Fraction of `subject`'s area lying inside any of `covers`, estimated by
|
|
139
|
-
// sampling a grid of cell centers over the subject's bbox. Cheap and robust
|
|
140
|
-
// enough for the merge-vs-demote decision; exact polygon clipping would be a
|
|
141
|
-
// heavy dependency for a 60% threshold.
|
|
142
|
-
function polygonCoverageRatio(subject, covers, subjectBounds) {
|
|
143
|
-
if (subject.length < 3 || covers.length === 0)
|
|
144
|
-
return 0;
|
|
145
|
-
const bbox = subjectBounds ?? bboxOf(subject);
|
|
146
|
-
const width = bbox.maxX - bbox.minX;
|
|
147
|
-
const height = bbox.maxY - bbox.minY;
|
|
148
|
-
let inside = 0;
|
|
149
|
-
let covered = 0;
|
|
150
|
-
for (let i = 0; i < COVERAGE_SAMPLE_STEPS; i += 1) {
|
|
151
|
-
for (let j = 0; j < COVERAGE_SAMPLE_STEPS; j += 1) {
|
|
152
|
-
const point = {
|
|
153
|
-
x: bbox.minX + ((i + 0.5) / COVERAGE_SAMPLE_STEPS) * width,
|
|
154
|
-
y: bbox.minY + ((j + 0.5) / COVERAGE_SAMPLE_STEPS) * height,
|
|
155
|
-
};
|
|
156
|
-
if (!pointInPolygon(point, subject))
|
|
157
|
-
continue;
|
|
158
|
-
inside += 1;
|
|
159
|
-
if (pointInAnyPolygon(point, covers))
|
|
160
|
-
covered += 1;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
if (inside === 0) {
|
|
164
|
-
return pointInAnyPolygon(polygonCentroid(subject), covers) ? 1 : 0;
|
|
165
|
-
}
|
|
166
|
-
return covered / inside;
|
|
167
|
-
}
|
|
168
|
-
// Demoted auto surfaces keep their polygon untouched, so a re-closed room
|
|
169
|
-
// usually hits the exact-signature manual check. Coverage handles the rest:
|
|
170
|
-
// a room split across multiple manual surfaces AND a single manual surface
|
|
171
|
-
// spanning multiple rooms both suppress a replacement auto surface — what
|
|
172
|
-
// matters is that the ROOM is already substantially covered, not that any
|
|
173
|
-
// one manual surface belongs to it (a per-surface "mostly inside the room"
|
|
174
|
-
// filter dropped multi-room slabs and resurrected deleted auto slabs).
|
|
175
|
-
function matchesManualFootprint(roomPolygon, manualPolygons) {
|
|
176
|
-
return polygonCoverageRatio(roomPolygon, manualPolygons) >= ORPHAN_MERGE_COVERAGE_THRESHOLD;
|
|
177
|
-
}
|
|
178
|
-
function pointDistanceToPolygonBoundary(point, polygon) {
|
|
179
|
-
let minDistance = Number.POSITIVE_INFINITY;
|
|
180
|
-
for (let index = 0; index < polygon.length; index += 1) {
|
|
181
|
-
const start = polygon[index];
|
|
182
|
-
const end = polygon[(index + 1) % polygon.length];
|
|
183
|
-
if (!(start && end))
|
|
184
|
-
continue;
|
|
185
|
-
minDistance = Math.min(minDistance, distanceToSegment(pointToTuple(point), pointToTuple(start), pointToTuple(end)));
|
|
186
|
-
}
|
|
187
|
-
return minDistance;
|
|
188
|
-
}
|
|
189
|
-
function wallBoundsRoom(wall, roomPolygon) {
|
|
190
|
-
const sampled = sampleWallPointsForRoomDetection(wall);
|
|
191
|
-
if (sampled.length === 0)
|
|
192
|
-
return false;
|
|
193
|
-
const candidates = sampled.length === 2
|
|
194
|
-
? [
|
|
195
|
-
sampled[0],
|
|
196
|
-
{
|
|
197
|
-
x: (sampled[0].x + sampled[1].x) / 2,
|
|
198
|
-
y: (sampled[0].y + sampled[1].y) / 2,
|
|
199
|
-
},
|
|
200
|
-
sampled[1],
|
|
201
|
-
]
|
|
202
|
-
: sampled;
|
|
203
|
-
const matchingPoints = candidates.filter((point) => pointDistanceToPolygonBoundary(point, roomPolygon) <= WALL_ROOM_BOUNDARY_TOLERANCE);
|
|
204
|
-
return matchingPoints.length >= 2;
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* The clamp bound for a ceiling polygon under this planning context —
|
|
208
|
-
* the context's cross-level resolver when provided, else the plane-only
|
|
209
|
-
* `storeyHeight - CEILING_CLAMP_MARGIN` degradation.
|
|
210
|
-
*/
|
|
211
|
-
function resolveCeilingClampBound(polygon, context) {
|
|
212
|
-
if (context.ceilingClampBound)
|
|
213
|
-
return context.ceilingClampBound(polygon);
|
|
214
|
-
return (context.storeyHeight ?? DEFAULT_LEVEL_HEIGHT) - CEILING_CLAMP_MARGIN;
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* The base a boundary wall actually stands on, in level-local metres.
|
|
218
|
-
*
|
|
219
|
-
* Resolved the same way the wall renderer resolves it — the level base under
|
|
220
|
-
* the wall's own start point (sculpted ground or the flat plane), the slab
|
|
221
|
-
* election on top of that, plus the wall's stored `supportOffset`. Reading the
|
|
222
|
-
* ground for `GROUND_SUPPORT_ID` walls only is what left stamped room presets
|
|
223
|
-
* flat: `resolveWallSupportSlabPatch` writes no host at all for a wall on bare
|
|
224
|
-
* terrain, so the sentinel is a hint about pointer intent, never a precondition
|
|
225
|
-
* for standing on the ground.
|
|
226
|
-
*/
|
|
227
|
-
function boundaryWallBase(wall, walls, supportSlabs, nodes, levelId) {
|
|
228
|
-
const levelBase = levelBaseElevationAt(nodes, levelId, wall.start[0], wall.start[1]);
|
|
229
|
-
const offset = wall.supportOffset ?? 0;
|
|
230
|
-
if (wall.supportSlabId === GROUND_SUPPORT_ID)
|
|
231
|
-
return levelBase + offset;
|
|
232
|
-
return (computeWallSlabSupport(wall, supportSlabs, walls, wall.supportSlabId ?? null, null, levelBase)
|
|
233
|
-
.elevation + offset);
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* The plane an auto floor/ceiling takes when its enclosing walls disagree.
|
|
237
|
-
*
|
|
238
|
-
* `floor` takes the HIGHEST wall base and `ceiling` the LOWEST wall top —
|
|
239
|
-
* the only pair that cannot open a hole: a floor at the lowest base would
|
|
240
|
-
* leave daylight under every wall standing higher, and a ceiling at the
|
|
241
|
-
* highest top would poke out through the shortest wall. Both surfaces stay
|
|
242
|
-
* flat (a slab is one scalar elevation by schema; see `vertical-model.md`),
|
|
243
|
-
* so a room on a slope is a level room cut into the hillside — the walls on
|
|
244
|
-
* the low side extend down to meet it, which is what their `baseSegments`
|
|
245
|
-
* fill-down already does.
|
|
246
|
-
*
|
|
247
|
-
* Non-finite inputs are the one abstain: a broken graph should keep the
|
|
248
|
-
* existing placement rather than move a surface to NaN.
|
|
249
|
-
*/
|
|
250
|
-
function roomFloorPlane(wallBases) {
|
|
251
|
-
if (wallBases.length === 0 || wallBases.some((value) => !Number.isFinite(value)))
|
|
252
|
-
return undefined;
|
|
253
|
-
return Math.max(...wallBases);
|
|
254
|
-
}
|
|
255
|
-
function roomCeilingPlane(wallTops) {
|
|
256
|
-
if (wallTops.length === 0 || wallTops.some((value) => !Number.isFinite(value)))
|
|
257
|
-
return undefined;
|
|
258
|
-
return Math.min(...wallTops);
|
|
259
|
-
}
|
|
260
|
-
function autoRoomVerticalPlacements(spaces, walls, supportSlabs, nodes, storeyHeight) {
|
|
261
|
-
const wallsById = new Map(walls.map((wall) => [wall.id, wall]));
|
|
262
|
-
const placements = new Map();
|
|
263
|
-
for (const space of spaces) {
|
|
264
|
-
const boundaryWalls = space.wallIds.flatMap((id) => {
|
|
265
|
-
const wall = wallsById.get(id);
|
|
266
|
-
return wall ? [wall] : [];
|
|
267
|
-
});
|
|
268
|
-
if (boundaryWalls.length !== space.wallIds.length)
|
|
269
|
-
continue;
|
|
270
|
-
const wallBases = boundaryWalls.map((wall) => boundaryWallBase(wall, walls, supportSlabs, nodes, space.levelId));
|
|
271
|
-
const base = roomFloorPlane(wallBases);
|
|
272
|
-
if (base === undefined)
|
|
273
|
-
continue;
|
|
274
|
-
const wallTops = boundaryWalls.map((wall, index) => resolveWallTop(wall, storeyHeight, wallBases[index] ?? base));
|
|
275
|
-
const top = roomCeilingPlane(wallTops);
|
|
276
|
-
if (top === undefined)
|
|
277
|
-
continue;
|
|
278
|
-
placements.set(polygonSignature(space.polygon.map(pointFromTuple)), {
|
|
279
|
-
slabElevation: base + DEFAULT_AUTO_SLAB_ELEVATION,
|
|
280
|
-
ceilingHeight: top - CEILING_CLAMP_MARGIN,
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
return placements;
|
|
284
|
-
}
|
|
285
|
-
function getWallDirection(wall) {
|
|
286
|
-
const dx = wall.end[0] - wall.start[0];
|
|
287
|
-
const dy = wall.end[1] - wall.start[1];
|
|
288
|
-
const length = Math.hypot(dx, dy);
|
|
289
|
-
if (length < 1e-9) {
|
|
290
|
-
return {
|
|
291
|
-
point: pointFromTuple(wall.start),
|
|
292
|
-
tangent: { x: 1, y: 0 },
|
|
293
|
-
normal: { x: 0, y: 1 },
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
const tangent = { x: dx / length, y: dy / length };
|
|
297
|
-
return {
|
|
298
|
-
point: {
|
|
299
|
-
x: (wall.start[0] + wall.end[0]) / 2,
|
|
300
|
-
y: (wall.start[1] + wall.end[1]) / 2,
|
|
301
|
-
},
|
|
302
|
-
tangent,
|
|
303
|
-
normal: { x: -tangent.y, y: tangent.x },
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
function pointLineDistance(point, start, end) {
|
|
307
|
-
const dx = end.x - start.x;
|
|
308
|
-
const dy = end.y - start.y;
|
|
309
|
-
const lengthSquared = dx * dx + dy * dy;
|
|
310
|
-
if (lengthSquared < 1e-9) {
|
|
311
|
-
return Math.hypot(point.x - start.x, point.y - start.y);
|
|
312
|
-
}
|
|
313
|
-
const cross = (point.x - start.x) * dy - (point.y - start.y) * dx;
|
|
314
|
-
return Math.abs(cross) / Math.sqrt(lengthSquared);
|
|
315
|
-
}
|
|
316
|
-
function sampleWallPointsForRoomDetection(wall, tolerance = ROOM_CURVE_TOLERANCE) {
|
|
317
|
-
const start = { x: wall.start[0], y: wall.start[1] };
|
|
318
|
-
const end = { x: wall.end[0], y: wall.end[1] };
|
|
319
|
-
if (!isCurvedWall(wall)) {
|
|
320
|
-
return [start, end];
|
|
321
|
-
}
|
|
322
|
-
const subdivide = (t0, p0, t1, p1, depth) => {
|
|
323
|
-
const midT = (t0 + t1) / 2;
|
|
324
|
-
const midPoint = getWallCurveFrameAt(wall, midT).point;
|
|
325
|
-
const deviation = pointLineDistance(midPoint, p0, p1);
|
|
326
|
-
if (depth >= MAX_CURVE_SUBDIVISION_DEPTH || deviation <= tolerance) {
|
|
327
|
-
return [p0, p1];
|
|
328
|
-
}
|
|
329
|
-
const left = subdivide(t0, p0, midT, midPoint, depth + 1);
|
|
330
|
-
const right = subdivide(midT, midPoint, t1, p1, depth + 1);
|
|
331
|
-
return [...left.slice(0, -1), ...right];
|
|
332
|
-
};
|
|
333
|
-
return subdivide(0, start, 1, end, 0);
|
|
334
|
-
}
|
|
335
|
-
function segmentProjection(point, start, end) {
|
|
336
|
-
const dx = end.x - start.x;
|
|
337
|
-
const dy = end.y - start.y;
|
|
338
|
-
const lengthSquared = dx * dx + dy * dy;
|
|
339
|
-
if (lengthSquared < 1e-12) {
|
|
340
|
-
return { t: 0, distance: Math.hypot(point.x - start.x, point.y - start.y) };
|
|
341
|
-
}
|
|
342
|
-
const t = ((point.x - start.x) * dx + (point.y - start.y) * dy) / lengthSquared;
|
|
343
|
-
const clampedT = Math.max(0, Math.min(1, t));
|
|
344
|
-
const projX = start.x + clampedT * dx;
|
|
345
|
-
const projY = start.y + clampedT * dy;
|
|
346
|
-
return { t, distance: Math.hypot(point.x - projX, point.y - projY) };
|
|
347
|
-
}
|
|
348
|
-
// Break a straight wall at any junction vertex (another wall's endpoint) that
|
|
349
|
-
// lands on its interior, returning the ordered polyline [start, …splits, end].
|
|
350
|
-
// Splitting at the *vertex* position (not the projection) keeps the split node's
|
|
351
|
-
// key identical to the touching wall's endpoint so the two share a graph node.
|
|
352
|
-
function splitStraightWallAtVertices(start, end, vertices) {
|
|
353
|
-
const length = Math.hypot(end.x - start.x, end.y - start.y);
|
|
354
|
-
if (length < 1e-9)
|
|
355
|
-
return [start, end];
|
|
356
|
-
const interior = [];
|
|
357
|
-
for (const vertex of vertices) {
|
|
358
|
-
const { t, distance } = segmentProjection(vertex, start, end);
|
|
359
|
-
if (distance > WALL_JUNCTION_TOLERANCE)
|
|
360
|
-
continue;
|
|
361
|
-
const along = t * length;
|
|
362
|
-
if (along <= WALL_JUNCTION_TOLERANCE || along >= length - WALL_JUNCTION_TOLERANCE)
|
|
363
|
-
continue;
|
|
364
|
-
interior.push({ point: vertex, t });
|
|
365
|
-
}
|
|
366
|
-
interior.sort((a, b) => a.t - b.t);
|
|
367
|
-
const ordered = [start];
|
|
368
|
-
let lastKey = pointKey(start);
|
|
369
|
-
for (const { point } of interior) {
|
|
370
|
-
const key = pointKey(point);
|
|
371
|
-
if (key === lastKey)
|
|
372
|
-
continue;
|
|
373
|
-
ordered.push(point);
|
|
374
|
-
lastKey = key;
|
|
375
|
-
}
|
|
376
|
-
if (lastKey !== pointKey(end))
|
|
377
|
-
ordered.push(end);
|
|
378
|
-
return ordered;
|
|
379
|
-
}
|
|
380
|
-
function extractRooms(walls) {
|
|
381
|
-
if (walls.length < 3)
|
|
382
|
-
return [];
|
|
383
|
-
const graph = new Map();
|
|
384
|
-
const halfEdges = new Map();
|
|
385
|
-
const upsertNode = (point) => {
|
|
386
|
-
const key = pointKey(point);
|
|
387
|
-
if (!graph.has(key)) {
|
|
388
|
-
graph.set(key, { point: { ...point }, outgoing: [] });
|
|
389
|
-
}
|
|
390
|
-
return key;
|
|
391
|
-
};
|
|
392
|
-
// Planarize first: collect every wall endpoint as a candidate graph vertex so
|
|
393
|
-
// straight walls can be split at T-junctions where another wall ends mid-span.
|
|
394
|
-
// Without this the touching wall's endpoint is a dangling degree-1 node and the
|
|
395
|
-
// enclosed area (e.g. a room added against the middle of an existing wall)
|
|
396
|
-
// never forms a cycle.
|
|
397
|
-
const vertexByKey = new Map();
|
|
398
|
-
for (const wall of walls) {
|
|
399
|
-
for (const tuple of [wall.start, wall.end]) {
|
|
400
|
-
const point = pointFromTuple(tuple);
|
|
401
|
-
const key = pointKey(point);
|
|
402
|
-
if (!vertexByKey.has(key))
|
|
403
|
-
vertexByKey.set(key, point);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
const vertices = [...vertexByKey.values()];
|
|
407
|
-
for (const wall of walls) {
|
|
408
|
-
const start = pointFromTuple(wall.start);
|
|
409
|
-
const end = pointFromTuple(wall.end);
|
|
410
|
-
if (samePointWithinTolerance(start, end))
|
|
411
|
-
continue;
|
|
412
|
-
// Curved walls keep their sampled polyline as one edge; straight walls split
|
|
413
|
-
// into consecutive sub-edges at their interior junction vertices.
|
|
414
|
-
const subPolylines = isCurvedWall(wall)
|
|
415
|
-
? [sampleWallPointsForRoomDetection(wall)]
|
|
416
|
-
: (() => {
|
|
417
|
-
const ordered = splitStraightWallAtVertices(start, end, vertices);
|
|
418
|
-
const parts = [];
|
|
419
|
-
for (let index = 0; index < ordered.length - 1; index += 1) {
|
|
420
|
-
parts.push([ordered[index], ordered[index + 1]]);
|
|
421
|
-
}
|
|
422
|
-
return parts;
|
|
423
|
-
})();
|
|
424
|
-
subPolylines.forEach((points, subIndex) => {
|
|
425
|
-
const from = points[0];
|
|
426
|
-
const to = points[points.length - 1];
|
|
427
|
-
const fromKey = upsertNode(from);
|
|
428
|
-
const toKey = upsertNode(to);
|
|
429
|
-
if (fromKey === toKey)
|
|
430
|
-
return;
|
|
431
|
-
const reversePoints = [...points].reverse();
|
|
432
|
-
const forwardId = `${wall.id}#${subIndex}:f`;
|
|
433
|
-
const reverseId = `${wall.id}#${subIndex}:r`;
|
|
434
|
-
halfEdges.set(forwardId, {
|
|
435
|
-
id: forwardId,
|
|
436
|
-
reverseId,
|
|
437
|
-
fromKey,
|
|
438
|
-
toKey,
|
|
439
|
-
angle: Math.atan2(points[1].y - from.y, points[1].x - from.x),
|
|
440
|
-
points,
|
|
441
|
-
wallId: wall.id,
|
|
442
|
-
face: 'front',
|
|
443
|
-
});
|
|
444
|
-
halfEdges.set(reverseId, {
|
|
445
|
-
id: reverseId,
|
|
446
|
-
reverseId: forwardId,
|
|
447
|
-
fromKey: toKey,
|
|
448
|
-
toKey: fromKey,
|
|
449
|
-
angle: Math.atan2(reversePoints[1].y - to.y, reversePoints[1].x - to.x),
|
|
450
|
-
points: reversePoints,
|
|
451
|
-
wallId: wall.id,
|
|
452
|
-
face: 'back',
|
|
453
|
-
});
|
|
454
|
-
graph.get(fromKey)?.outgoing.push(forwardId);
|
|
455
|
-
graph.get(toKey)?.outgoing.push(reverseId);
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
const sortedOutgoing = new Map();
|
|
459
|
-
for (const [key, node] of graph.entries()) {
|
|
460
|
-
const outgoing = [...node.outgoing];
|
|
461
|
-
outgoing.sort((a, b) => (halfEdges.get(a)?.angle ?? 0) - (halfEdges.get(b)?.angle ?? 0));
|
|
462
|
-
sortedOutgoing.set(key, outgoing);
|
|
463
|
-
}
|
|
464
|
-
const nextEdge = (edgeId) => {
|
|
465
|
-
const edge = halfEdges.get(edgeId);
|
|
466
|
-
if (!edge)
|
|
467
|
-
return null;
|
|
468
|
-
const outgoing = sortedOutgoing.get(edge.toKey);
|
|
469
|
-
if (!outgoing || outgoing.length === 0)
|
|
470
|
-
return null;
|
|
471
|
-
const idx = outgoing.indexOf(edge.reverseId);
|
|
472
|
-
if (idx === -1)
|
|
473
|
-
return null;
|
|
474
|
-
const nextIdx = (idx - 1 + outgoing.length) % outgoing.length;
|
|
475
|
-
return outgoing[nextIdx] ?? null;
|
|
476
|
-
};
|
|
477
|
-
const splitIntoSimpleCycles = (walkEdgeIds) => {
|
|
478
|
-
const cycles = [];
|
|
479
|
-
const firstEdge = halfEdges.get(walkEdgeIds[0] ?? '');
|
|
480
|
-
if (!firstEdge)
|
|
481
|
-
return cycles;
|
|
482
|
-
const pathEdges = [];
|
|
483
|
-
const pathVertices = [firstEdge.fromKey];
|
|
484
|
-
const vertexIndex = new Map([[firstEdge.fromKey, 0]]);
|
|
485
|
-
for (const edgeId of walkEdgeIds) {
|
|
486
|
-
const edge = halfEdges.get(edgeId);
|
|
487
|
-
if (!edge || edge.fromKey !== pathVertices[pathVertices.length - 1])
|
|
488
|
-
return [];
|
|
489
|
-
pathEdges.push(edgeId);
|
|
490
|
-
const repeatedIndex = vertexIndex.get(edge.toKey);
|
|
491
|
-
if (repeatedIndex === undefined) {
|
|
492
|
-
pathVertices.push(edge.toKey);
|
|
493
|
-
vertexIndex.set(edge.toKey, pathVertices.length - 1);
|
|
494
|
-
continue;
|
|
495
|
-
}
|
|
496
|
-
const cycle = pathEdges.slice(repeatedIndex);
|
|
497
|
-
if (cycle.length >= 3)
|
|
498
|
-
cycles.push(cycle);
|
|
499
|
-
for (let index = repeatedIndex + 1; index < pathVertices.length; index += 1) {
|
|
500
|
-
vertexIndex.delete(pathVertices[index]);
|
|
501
|
-
}
|
|
502
|
-
pathVertices.length = repeatedIndex + 1;
|
|
503
|
-
pathEdges.length = repeatedIndex;
|
|
504
|
-
}
|
|
505
|
-
return pathEdges.length === 0 && pathVertices.length === 1 ? cycles : [];
|
|
506
|
-
};
|
|
507
|
-
const visitedDirected = new Set();
|
|
508
|
-
const rooms = [];
|
|
509
|
-
// A face walk cannot revisit a half-edge, so the half-edge count bounds its
|
|
510
|
-
// length. It can revisit a vertex when dangling walls or other graph bridges
|
|
511
|
-
// are traced out and back; those excursions are removed below.
|
|
512
|
-
const maxSteps = Math.min(2000, halfEdges.size + 10);
|
|
513
|
-
for (const edgeId of halfEdges.keys()) {
|
|
514
|
-
if (visitedDirected.has(edgeId))
|
|
515
|
-
continue;
|
|
516
|
-
const cycleEdgeIds = [];
|
|
517
|
-
let currentEdgeId = edgeId;
|
|
518
|
-
let valid = true;
|
|
519
|
-
let closed = false;
|
|
520
|
-
for (let step = 0; step < maxSteps; step += 1) {
|
|
521
|
-
const currentEdge = halfEdges.get(currentEdgeId);
|
|
522
|
-
if (!currentEdge) {
|
|
523
|
-
valid = false;
|
|
524
|
-
break;
|
|
525
|
-
}
|
|
526
|
-
visitedDirected.add(currentEdgeId);
|
|
527
|
-
cycleEdgeIds.push(currentEdgeId);
|
|
528
|
-
const next = nextEdge(currentEdgeId);
|
|
529
|
-
if (!next) {
|
|
530
|
-
valid = false;
|
|
531
|
-
break;
|
|
532
|
-
}
|
|
533
|
-
currentEdgeId = next;
|
|
534
|
-
if (currentEdgeId === edgeId) {
|
|
535
|
-
closed = true;
|
|
536
|
-
break;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
if (!(valid && closed) || cycleEdgeIds.length < 3)
|
|
540
|
-
continue;
|
|
541
|
-
for (const simpleCycleEdgeIds of splitIntoSimpleCycles(cycleEdgeIds)) {
|
|
542
|
-
const polygon = dedupeSequentialPoints(simpleCycleEdgeIds.flatMap((id, index) => {
|
|
543
|
-
const points = halfEdges.get(id)?.points ?? [];
|
|
544
|
-
return index === simpleCycleEdgeIds.length - 1 ? points : points.slice(0, -1);
|
|
545
|
-
}));
|
|
546
|
-
if (polygon.length < 3)
|
|
547
|
-
continue;
|
|
548
|
-
const signedArea = polygonArea(polygon);
|
|
549
|
-
if (signedArea <= 0)
|
|
550
|
-
continue;
|
|
551
|
-
if (signedArea < 0.5 || signedArea > 10_000)
|
|
552
|
-
continue;
|
|
553
|
-
const signature = polygonSignature(polygon);
|
|
554
|
-
if (rooms.some((room) => polygonSignature(room.polygon) === signature))
|
|
555
|
-
continue;
|
|
556
|
-
rooms.push({
|
|
557
|
-
polygon,
|
|
558
|
-
boundaryFaces: simpleCycleEdgeIds.flatMap((id) => {
|
|
559
|
-
const edge = halfEdges.get(id);
|
|
560
|
-
if (!edge)
|
|
561
|
-
return [];
|
|
562
|
-
return [
|
|
563
|
-
{
|
|
564
|
-
wallId: edge.wallId,
|
|
565
|
-
face: edge.face,
|
|
566
|
-
points: edge.points.map(pointToTuple),
|
|
567
|
-
},
|
|
568
|
-
];
|
|
569
|
-
}),
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
rooms.sort((a, b) => Math.abs(polygonArea(b.polygon)) - Math.abs(polygonArea(a.polygon)));
|
|
574
|
-
return rooms;
|
|
575
|
-
}
|
|
576
|
-
function extractRoomPolygons(walls) {
|
|
577
|
-
return extractRooms(walls).map((room) => room.polygon);
|
|
578
|
-
}
|
|
579
|
-
/**
|
|
580
|
-
* True when `wall` lies on the boundary of a room enclosed by `walls`, using the
|
|
581
|
-
* same planar room graph the auto slab/ceiling sync uses. The wall builder's
|
|
582
|
-
* "Room (auto-close)" mode calls this so drafting stops the moment a segment
|
|
583
|
-
* closes a room — whether the chain loops back to its own start or seals a bay
|
|
584
|
-
* against the middle of an existing wall (a T-junction). Sharing one graph means
|
|
585
|
-
* auto-close and auto-slab detection can never disagree about what is "closed".
|
|
586
|
-
*/
|
|
1
|
+
import { ZoneNode, } from '../schema/index.js';
|
|
2
|
+
import { generateId } from '../schema/base.js';
|
|
3
|
+
import { findLevelAboveId, getLevelBelow, getLevelElevations } from '../services/storey.js';
|
|
4
|
+
import { activeSceneCommitNodeIds, getSceneHistoryPauseDepth, isRestoringSceneHistory, notifySceneCommit, pauseSceneHistory, resumeSceneHistory, subscribeSceneCommits, } from '../store/history-control.js';
|
|
5
|
+
import { getClampedWallCurveOffset } from '../systems/wall/wall-curve.js';
|
|
6
|
+
import { changedLevelConstructionDisplacements } from './floor-foundation-stack.js';
|
|
7
|
+
import { createFloorOpeningIndex, floorOpeningTargets } from './floor-opening-intent.js';
|
|
8
|
+
import { extractRooms, pointToTuple, polygonSignature, sampleWallPointsForRoomDetection, WALL_JUNCTION_TOLERANCE, } from './room-graph.js';
|
|
9
|
+
import { distanceToSegment, RoomTopologyIndex } from './room-topology-index.js';
|
|
10
|
+
import { applyStructureReconciliation } from './structure-commit.js';
|
|
11
|
+
import { classifyWallSides } from './structure-kernel.js';
|
|
12
|
+
import { levelBaseElevationAt } from './terrain-support-query.js';
|
|
587
13
|
export function wallClosesRoom(walls, wall) {
|
|
588
|
-
|
|
589
|
-
if (roomPolygons.length === 0)
|
|
590
|
-
return false;
|
|
591
|
-
return roomPolygons.some((polygon) => wallBoundsRoom(wall, polygon));
|
|
592
|
-
}
|
|
593
|
-
export function resolveWallSurfaceSides(wall, roomPolygons) {
|
|
594
|
-
if (roomPolygons.length === 0) {
|
|
595
|
-
return {
|
|
596
|
-
frontSide: 'unknown',
|
|
597
|
-
backSide: 'unknown',
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
const frame = getWallDirection(wall);
|
|
601
|
-
const normalLength = Math.hypot(frame.normal.x, frame.normal.y);
|
|
602
|
-
if (normalLength < 1e-9) {
|
|
603
|
-
return {
|
|
604
|
-
frontSide: wall.frontSide,
|
|
605
|
-
backSide: wall.backSide,
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
const normalX = frame.normal.x / normalLength;
|
|
609
|
-
const normalY = frame.normal.y / normalLength;
|
|
610
|
-
const sampleDistance = Math.max((wall.thickness ?? 0.2) / 2 + 0.08, 0.16);
|
|
611
|
-
const frontPoint = {
|
|
612
|
-
x: frame.point.x + normalX * sampleDistance,
|
|
613
|
-
y: frame.point.y + normalY * sampleDistance,
|
|
614
|
-
};
|
|
615
|
-
const backPoint = {
|
|
616
|
-
x: frame.point.x - normalX * sampleDistance,
|
|
617
|
-
y: frame.point.y - normalY * sampleDistance,
|
|
618
|
-
};
|
|
619
|
-
const frontInside = pointInAnyPolygon(frontPoint, roomPolygons);
|
|
620
|
-
const backInside = pointInAnyPolygon(backPoint, roomPolygons);
|
|
621
|
-
if (frontInside === backInside) {
|
|
622
|
-
return {
|
|
623
|
-
frontSide: wall.frontSide,
|
|
624
|
-
backSide: wall.backSide,
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
return {
|
|
628
|
-
frontSide: frontInside ? 'interior' : 'exterior',
|
|
629
|
-
backSide: backInside ? 'interior' : 'exterior',
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
function nextAutoRoomName(nodes, suffix) {
|
|
633
|
-
let maxIndex = 0;
|
|
634
|
-
for (const node of nodes) {
|
|
635
|
-
const match = /^Room\s+(\d+)(?:\s+(?:Slab|Ceiling))?$/i.exec((node.name ?? '').trim());
|
|
636
|
-
if (!match)
|
|
637
|
-
continue;
|
|
638
|
-
const index = Number(match[1]);
|
|
639
|
-
if (Number.isFinite(index)) {
|
|
640
|
-
maxIndex = Math.max(maxIndex, index);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
return `Room ${maxIndex + 1} ${suffix}`;
|
|
644
|
-
}
|
|
645
|
-
function sameTuplePolygon(current, next) {
|
|
646
|
-
return (current.length === next.length &&
|
|
647
|
-
current.every((point, index) => point[0] === next[index]?.[0] && point[1] === next[index]?.[1]));
|
|
648
|
-
}
|
|
649
|
-
function sameTuplePolygonRotation(current, next) {
|
|
650
|
-
if (sameTuplePolygon(current, next))
|
|
651
|
-
return true;
|
|
652
|
-
if (current.length !== next.length)
|
|
653
|
-
return false;
|
|
654
|
-
// Preserve the stored ring when extraction only changes its starting vertex.
|
|
655
|
-
return next.some((_, offset) => current.every((point, index) => {
|
|
656
|
-
const candidate = next[(index + offset) % next.length];
|
|
657
|
-
return point[0] === candidate[0] && point[1] === candidate[1];
|
|
658
|
-
}));
|
|
659
|
-
}
|
|
660
|
-
function sameTuplePolygons(current, next) {
|
|
661
|
-
return (current.length === next.length &&
|
|
662
|
-
current.every((polygon, index) => {
|
|
663
|
-
const nextPolygon = next[index];
|
|
664
|
-
return nextPolygon ? sameTuplePolygon(polygon, nextPolygon) : false;
|
|
665
|
-
}));
|
|
666
|
-
}
|
|
667
|
-
function crossProduct(a, b, c) {
|
|
668
|
-
return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
|
|
669
|
-
}
|
|
670
|
-
function lineIntersection(start, end, clipStart, clipEnd) {
|
|
671
|
-
const segment = { x: end.x - start.x, y: end.y - start.y };
|
|
672
|
-
const clip = { x: clipEnd.x - clipStart.x, y: clipEnd.y - clipStart.y };
|
|
673
|
-
const denominator = segment.x * clip.y - segment.y * clip.x;
|
|
674
|
-
if (Math.abs(denominator) < 1e-9)
|
|
675
|
-
return end;
|
|
676
|
-
const offset = { x: clipStart.x - start.x, y: clipStart.y - start.y };
|
|
677
|
-
const t = (offset.x * clip.y - offset.y * clip.x) / denominator;
|
|
678
|
-
return { x: start.x + segment.x * t, y: start.y + segment.y * t };
|
|
679
|
-
}
|
|
680
|
-
function clipPolygonToConvex(subject, clipPolygon) {
|
|
681
|
-
if (subject.length < 3 || clipPolygon.length < 3)
|
|
682
|
-
return [];
|
|
683
|
-
const orientation = polygonArea(clipPolygon) >= 0 ? 1 : -1;
|
|
684
|
-
let output = [...subject];
|
|
685
|
-
for (let index = 0; index < clipPolygon.length; index += 1) {
|
|
686
|
-
const clipStart = clipPolygon[index];
|
|
687
|
-
const clipEnd = clipPolygon[(index + 1) % clipPolygon.length];
|
|
688
|
-
const input = output;
|
|
689
|
-
output = [];
|
|
690
|
-
if (input.length === 0)
|
|
691
|
-
break;
|
|
692
|
-
let previous = input[input.length - 1];
|
|
693
|
-
let previousInside = orientation * crossProduct(clipStart, clipEnd, previous) >= -1e-8;
|
|
694
|
-
for (const current of input) {
|
|
695
|
-
const currentInside = orientation * crossProduct(clipStart, clipEnd, current) >= -1e-8;
|
|
696
|
-
if (currentInside !== previousInside) {
|
|
697
|
-
output.push(lineIntersection(previous, current, clipStart, clipEnd));
|
|
698
|
-
}
|
|
699
|
-
if (currentInside)
|
|
700
|
-
output.push(current);
|
|
701
|
-
previous = current;
|
|
702
|
-
previousInside = currentInside;
|
|
703
|
-
}
|
|
704
|
-
output = dedupeSequentialPoints(output, 1e-7);
|
|
705
|
-
}
|
|
706
|
-
return output.length >= 3 && Math.abs(polygonArea(output)) > 1e-8 ? output : [];
|
|
707
|
-
}
|
|
708
|
-
function isConvexPolygon(polygon) {
|
|
709
|
-
let direction = 0;
|
|
710
|
-
for (let index = 0; index < polygon.length; index += 1) {
|
|
711
|
-
const cross = crossProduct(polygon[index], polygon[(index + 1) % polygon.length], polygon[(index + 2) % polygon.length]);
|
|
712
|
-
if (Math.abs(cross) < 1e-8)
|
|
713
|
-
continue;
|
|
714
|
-
const nextDirection = Math.sign(cross);
|
|
715
|
-
if (direction !== 0 && nextDirection !== direction)
|
|
716
|
-
return false;
|
|
717
|
-
direction = nextDirection;
|
|
718
|
-
}
|
|
719
|
-
return true;
|
|
720
|
-
}
|
|
721
|
-
function pointInTriangle(point, a, b, c) {
|
|
722
|
-
return (crossProduct(a, b, point) >= -1e-8 &&
|
|
723
|
-
crossProduct(b, c, point) >= -1e-8 &&
|
|
724
|
-
crossProduct(c, a, point) >= -1e-8);
|
|
725
|
-
}
|
|
726
|
-
function triangulatePolygon(polygon) {
|
|
727
|
-
const points = polygonArea(polygon) >= 0 ? [...polygon] : [...polygon].reverse();
|
|
728
|
-
const indices = points.map((_, index) => index);
|
|
729
|
-
const triangles = [];
|
|
730
|
-
let attempts = 0;
|
|
731
|
-
while (indices.length > 3 && attempts < points.length * points.length) {
|
|
732
|
-
let clippedEar = false;
|
|
733
|
-
for (let index = 0; index < indices.length; index += 1) {
|
|
734
|
-
const previousIndex = indices[(index - 1 + indices.length) % indices.length];
|
|
735
|
-
const currentIndex = indices[index];
|
|
736
|
-
const nextIndex = indices[(index + 1) % indices.length];
|
|
737
|
-
const previous = points[previousIndex];
|
|
738
|
-
const current = points[currentIndex];
|
|
739
|
-
const next = points[nextIndex];
|
|
740
|
-
if (crossProduct(previous, current, next) <= 1e-8)
|
|
741
|
-
continue;
|
|
742
|
-
if (indices.some((candidateIndex) => candidateIndex !== previousIndex &&
|
|
743
|
-
candidateIndex !== currentIndex &&
|
|
744
|
-
candidateIndex !== nextIndex &&
|
|
745
|
-
pointInTriangle(points[candidateIndex], previous, current, next))) {
|
|
746
|
-
continue;
|
|
747
|
-
}
|
|
748
|
-
triangles.push([previous, current, next]);
|
|
749
|
-
indices.splice(index, 1);
|
|
750
|
-
clippedEar = true;
|
|
751
|
-
break;
|
|
752
|
-
}
|
|
753
|
-
if (!clippedEar)
|
|
754
|
-
break;
|
|
755
|
-
attempts += 1;
|
|
756
|
-
}
|
|
757
|
-
if (indices.length === 3)
|
|
758
|
-
triangles.push(indices.map((index) => points[index]));
|
|
759
|
-
return triangles;
|
|
760
|
-
}
|
|
761
|
-
function clipOpeningToRoom(opening, room) {
|
|
762
|
-
const openingIsInside = opening.every((point) => pointInPolygon(point, room) || pointDistanceToPolygonBoundary(point, room) <= 1e-7);
|
|
763
|
-
if (openingIsInside)
|
|
764
|
-
return [opening];
|
|
765
|
-
const clipRegions = isConvexPolygon(room) ? [room] : triangulatePolygon(room);
|
|
766
|
-
return clipRegions
|
|
767
|
-
.map((region) => clipPolygonToConvex(opening, region))
|
|
768
|
-
.filter((polygon) => polygon.length >= 3);
|
|
769
|
-
}
|
|
770
|
-
function partitionSurfaceOpenings(surface, roomIndices, detected) {
|
|
771
|
-
const assignments = new Map();
|
|
772
|
-
for (const roomIndex of roomIndices) {
|
|
773
|
-
assignments.set(roomIndex, { holes: [], holeMetadata: [] });
|
|
774
|
-
}
|
|
775
|
-
surface.holes.forEach((hole, holeIndex) => {
|
|
776
|
-
const holePolygon = hole.map(pointFromTuple);
|
|
777
|
-
for (const roomIndex of roomIndices) {
|
|
778
|
-
const room = detected[roomIndex];
|
|
779
|
-
const assignment = assignments.get(roomIndex);
|
|
780
|
-
if (!(room && assignment))
|
|
781
|
-
continue;
|
|
782
|
-
for (const clipped of clipOpeningToRoom(holePolygon, room.poly)) {
|
|
783
|
-
assignment.holes.push(clipped.map(pointToTuple));
|
|
784
|
-
assignment.holeMetadata.push(surface.holeMetadata[holeIndex] ?? { source: 'manual' });
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
});
|
|
788
|
-
return assignments;
|
|
789
|
-
}
|
|
790
|
-
function partitionCeilingChildren(ceiling, roomIndices, detected, fallbackRoomIndex, childPosition) {
|
|
791
|
-
const assignments = new Map();
|
|
792
|
-
for (const roomIndex of roomIndices)
|
|
793
|
-
assignments.set(roomIndex, []);
|
|
794
|
-
for (const childId of ceiling.children) {
|
|
795
|
-
const tuple = childPosition?.(childId);
|
|
796
|
-
const point = tuple ? pointFromTuple(tuple) : undefined;
|
|
797
|
-
const boundaryRoomIndices = point
|
|
798
|
-
? roomIndices.filter((roomIndex) => {
|
|
799
|
-
const room = detected[roomIndex];
|
|
800
|
-
return room ? pointDistanceToPolygonBoundary(point, room.poly) <= 1e-7 : false;
|
|
801
|
-
})
|
|
802
|
-
: [];
|
|
803
|
-
const interiorRoomIndex = point && boundaryRoomIndices.length === 0
|
|
804
|
-
? roomIndices.find((roomIndex) => {
|
|
805
|
-
const room = detected[roomIndex];
|
|
806
|
-
return room ? pointInPolygon(point, room.poly) : false;
|
|
807
|
-
})
|
|
808
|
-
: undefined;
|
|
809
|
-
const roomIndex = interiorRoomIndex ??
|
|
810
|
-
(fallbackRoomIndex !== undefined && boundaryRoomIndices.includes(fallbackRoomIndex)
|
|
811
|
-
? fallbackRoomIndex
|
|
812
|
-
: boundaryRoomIndices[0]) ??
|
|
813
|
-
fallbackRoomIndex ??
|
|
814
|
-
roomIndices[0];
|
|
815
|
-
if (roomIndex !== undefined)
|
|
816
|
-
assignments.get(roomIndex)?.push(childId);
|
|
817
|
-
}
|
|
818
|
-
return assignments;
|
|
819
|
-
}
|
|
820
|
-
function sameHoleMetadata(current, next) {
|
|
821
|
-
return (current.length === next.length &&
|
|
822
|
-
current.every((metadata, index) => {
|
|
823
|
-
const candidate = next[index];
|
|
824
|
-
return (candidate?.source === metadata.source &&
|
|
825
|
-
candidate.stairId === metadata.stairId &&
|
|
826
|
-
candidate.elevatorId === metadata.elevatorId);
|
|
827
|
-
}));
|
|
828
|
-
}
|
|
829
|
-
function mergedSurfaceOpenings(surfaces) {
|
|
830
|
-
const holes = [];
|
|
831
|
-
const holeMetadata = [];
|
|
832
|
-
const seen = new Set();
|
|
833
|
-
for (const surface of surfaces) {
|
|
834
|
-
surface.holes.forEach((hole, index) => {
|
|
835
|
-
const metadata = surface.holeMetadata[index] ?? { source: 'manual' };
|
|
836
|
-
const key = JSON.stringify([hole, metadata]);
|
|
837
|
-
if (seen.has(key))
|
|
838
|
-
return;
|
|
839
|
-
seen.add(key);
|
|
840
|
-
holes.push(hole);
|
|
841
|
-
holeMetadata.push(metadata);
|
|
842
|
-
});
|
|
843
|
-
}
|
|
844
|
-
return { holes, holeMetadata };
|
|
845
|
-
}
|
|
846
|
-
function ceilingMergeSettingsSignature(ceiling) {
|
|
847
|
-
return JSON.stringify([
|
|
848
|
-
ceiling.height ?? null,
|
|
849
|
-
ceiling.material ?? null,
|
|
850
|
-
ceiling.materialPreset ?? null,
|
|
851
|
-
ceiling.slots ?? null,
|
|
852
|
-
ceiling.visible,
|
|
853
|
-
]);
|
|
854
|
-
}
|
|
855
|
-
function slabMergeSettingsSignature(slab) {
|
|
856
|
-
return JSON.stringify([
|
|
857
|
-
slab.elevation,
|
|
858
|
-
slab.thickness,
|
|
859
|
-
slab.recessed,
|
|
860
|
-
slab.recessedRimElevation ?? null,
|
|
861
|
-
slab.fillToTerrain ?? null,
|
|
862
|
-
slab.material ?? null,
|
|
863
|
-
slab.materialPreset ?? null,
|
|
864
|
-
slab.slots ?? null,
|
|
865
|
-
slab.visible,
|
|
866
|
-
]);
|
|
867
|
-
}
|
|
868
|
-
function slabElevationForReconciledRoom(source, polygon, context) {
|
|
869
|
-
const currentDerived = context.elevationForRoom?.(polygon);
|
|
870
|
-
if (!context.previousElevationForRoom)
|
|
871
|
-
return currentDerived ?? source.elevation;
|
|
872
|
-
const previousDerived = context.previousElevationForRoom(source.polygon);
|
|
873
|
-
const sourceWasDerived = previousDerived !== undefined &&
|
|
874
|
-
Math.abs(source.elevation - previousDerived) <= ROOM_VERTICAL_PLANE_EPSILON;
|
|
875
|
-
return sourceWasDerived ? (currentDerived ?? source.elevation) : source.elevation;
|
|
876
|
-
}
|
|
877
|
-
function ceilingHeightForReconciledRoom(source, polygon, context) {
|
|
878
|
-
if (source.height === undefined)
|
|
879
|
-
return undefined;
|
|
880
|
-
const currentDerived = context.heightForRoom?.(polygon);
|
|
881
|
-
if (!context.previousHeightForRoom)
|
|
882
|
-
return currentDerived ?? source.height;
|
|
883
|
-
const previousDerived = context.previousHeightForRoom(source.polygon);
|
|
884
|
-
const sourceWasDerived = previousDerived !== undefined &&
|
|
885
|
-
Math.abs(source.height - previousDerived) <= ROOM_VERTICAL_PLANE_EPSILON;
|
|
886
|
-
return sourceWasDerived ? (currentDerived ?? source.height) : source.height;
|
|
14
|
+
return extractRooms(walls).some((room) => room.spans.some((span) => span.boundaryId === wall.id));
|
|
887
15
|
}
|
|
16
|
+
const DEFAULT_AUTO_SLAB_ELEVATION = 0.05;
|
|
17
|
+
export { extractRooms } from './room-graph.js';
|
|
888
18
|
function wallGeometrySignature(wall, nodes, levelId) {
|
|
889
19
|
return [
|
|
890
20
|
wall.id,
|
|
@@ -893,6 +23,11 @@ function wallGeometrySignature(wall, nodes, levelId) {
|
|
|
893
23
|
wall.end[0].toFixed(4),
|
|
894
24
|
wall.end[1].toFixed(4),
|
|
895
25
|
(wall.thickness ?? 0.2).toFixed(4),
|
|
26
|
+
wall.justification,
|
|
27
|
+
JSON.stringify((wall.children ?? [])
|
|
28
|
+
.map((id) => nodes[id])
|
|
29
|
+
.filter((node) => node?.type === 'door' || node?.type === 'window')
|
|
30
|
+
.map((node) => [node.id, node.position, node.width, node.height, node.verticalAnchor])),
|
|
896
31
|
// Plane-bound (no stored height) is a distinct state, not a default
|
|
897
32
|
// value: it resolves to the storey plane, so it must not alias an
|
|
898
33
|
// explicit height of the same magnitude in the trigger signature.
|
|
@@ -931,48 +66,94 @@ function levelWallSnapshot(walls, nodes, levelId) {
|
|
|
931
66
|
.join('||');
|
|
932
67
|
}
|
|
933
68
|
function zoneGeometrySignature(zone) {
|
|
934
|
-
return [
|
|
69
|
+
return JSON.stringify([
|
|
935
70
|
zone.id,
|
|
936
|
-
zone.
|
|
937
|
-
zone.
|
|
938
|
-
zone.
|
|
939
|
-
|
|
71
|
+
zone.hasCeiling,
|
|
72
|
+
zone.hasFloor,
|
|
73
|
+
zone.floor,
|
|
74
|
+
zone.wallMaterial,
|
|
75
|
+
zone.wallOverrides,
|
|
76
|
+
zone.seed,
|
|
77
|
+
// A drawn zone's outline is intent (it may be adopted); a room's outline is
|
|
78
|
+
// derived, and an edit to it must be re-derived here as the hosted path does.
|
|
79
|
+
// The kernel's own write is not a change: the baseline is taken after it.
|
|
80
|
+
[zone.polygon, zone.holes],
|
|
81
|
+
]);
|
|
940
82
|
}
|
|
941
|
-
//
|
|
942
|
-
// generated footprints caused delete/recreate feedback.
|
|
943
|
-
//
|
|
944
|
-
//
|
|
945
|
-
// stored storey height ARE included — both feed the explicit-ceiling
|
|
946
|
-
// re-clamp bound (the storey plane), and neither is rewritten by
|
|
947
|
-
// the sync, so regeneration triggers when they change without feedback.
|
|
948
|
-
// Stage 3-B adds the LEVEL-ABOVE's covering-slab undersides (elevation −
|
|
949
|
-
// thickness, recessed pools excluded): a deck created, lowered, or
|
|
950
|
-
// thickened above must re-run the sync below so ceilings re-clamp under
|
|
951
|
-
// it. Same polygon exclusion applies — the level-above's own auto sync
|
|
952
|
-
// rewrites its slab footprints, and hashing them here would re-trigger
|
|
953
|
-
// this level on every remodel above.
|
|
83
|
+
// Derived slab and ceiling footprints and boundary ids stay out of the trigger
|
|
84
|
+
// signature: hashing generated footprints caused delete/recreate feedback. Zone
|
|
85
|
+
// intent and outlines, separators, wall geometry and slab construction invalidate
|
|
86
|
+
// the kernel.
|
|
954
87
|
function levelStructureSnapshots(nodes) {
|
|
88
|
+
const arrivalsByLevel = new Map();
|
|
89
|
+
const ceilingIdsByLevel = new Map();
|
|
90
|
+
const separatorsByLevel = new Map();
|
|
955
91
|
const wallsByLevel = new Map();
|
|
956
92
|
const zonesByLevel = new Map();
|
|
957
93
|
const slabElevationsByLevel = new Map();
|
|
958
94
|
const coveringUndersidesByLevel = new Map();
|
|
95
|
+
const floorOpenings = [];
|
|
959
96
|
for (const node of Object.values(nodes)) {
|
|
960
97
|
if (!(node && typeof node === 'object' && 'parentId' in node && node.parentId))
|
|
961
98
|
continue;
|
|
962
99
|
const levelId = node.parentId;
|
|
100
|
+
if (node.type === 'stair' && nodes[node.deckSlabId]?.support === 'open') {
|
|
101
|
+
const target = nodes[node.deckSlabId].parentId;
|
|
102
|
+
const arrivals = arrivalsByLevel.get(target) ?? [];
|
|
103
|
+
arrivals.push(JSON.stringify([
|
|
104
|
+
node.id,
|
|
105
|
+
node.position,
|
|
106
|
+
node.rotation,
|
|
107
|
+
node.visible,
|
|
108
|
+
node.stairType,
|
|
109
|
+
node.width,
|
|
110
|
+
node.innerRadius,
|
|
111
|
+
node.sweepAngle,
|
|
112
|
+
node.topLandingMode,
|
|
113
|
+
node.topLandingDepth,
|
|
114
|
+
node.children.map((id) => nodes[id]),
|
|
115
|
+
]));
|
|
116
|
+
arrivalsByLevel.set(target, arrivals);
|
|
117
|
+
}
|
|
963
118
|
if (node.type === 'wall') {
|
|
964
119
|
const walls = wallsByLevel.get(levelId) ?? [];
|
|
965
120
|
walls.push(node);
|
|
966
121
|
wallsByLevel.set(levelId, walls);
|
|
967
122
|
}
|
|
123
|
+
else if (node.type === 'ceiling') {
|
|
124
|
+
const ids = ceilingIdsByLevel.get(levelId) ?? [];
|
|
125
|
+
ids.push(node.id);
|
|
126
|
+
ceilingIdsByLevel.set(levelId, ids);
|
|
127
|
+
}
|
|
128
|
+
else if (node.type === 'separator') {
|
|
129
|
+
const values = separatorsByLevel.get(levelId) ?? [];
|
|
130
|
+
values.push(JSON.stringify([node.id, node.start, node.end]));
|
|
131
|
+
separatorsByLevel.set(levelId, values);
|
|
132
|
+
}
|
|
968
133
|
else if (node.type === 'zone') {
|
|
969
134
|
const zones = zonesByLevel.get(levelId) ?? [];
|
|
970
135
|
zones.push(ZoneNode.parse(node));
|
|
971
136
|
zonesByLevel.set(levelId, zones);
|
|
972
137
|
}
|
|
138
|
+
else if (node.type === 'floor-opening') {
|
|
139
|
+
floorOpenings.push(node);
|
|
140
|
+
}
|
|
973
141
|
else if (node.type === 'slab') {
|
|
974
142
|
const elevations = slabElevationsByLevel.get(levelId) ?? [];
|
|
975
|
-
elevations.push(
|
|
143
|
+
elevations.push(JSON.stringify([
|
|
144
|
+
node.id,
|
|
145
|
+
node.elevation ?? DEFAULT_AUTO_SLAB_ELEVATION,
|
|
146
|
+
node.thickness,
|
|
147
|
+
node.recessed,
|
|
148
|
+
node.fillToTerrain,
|
|
149
|
+
node.floorHeight,
|
|
150
|
+
node.foundation,
|
|
151
|
+
node.boundary,
|
|
152
|
+
node.autoFromWalls,
|
|
153
|
+
node.boundary === 'auto'
|
|
154
|
+
? node.holes?.filter((_, i) => node.holeMetadata?.[i]?.source !== 'room')
|
|
155
|
+
: [node.polygon, node.holes],
|
|
156
|
+
]));
|
|
976
157
|
slabElevationsByLevel.set(levelId, elevations);
|
|
977
158
|
if (node.recessed !== true) {
|
|
978
159
|
const undersides = coveringUndersidesByLevel.get(levelId) ?? [];
|
|
@@ -983,20 +164,50 @@ function levelStructureSnapshots(nodes) {
|
|
|
983
164
|
}
|
|
984
165
|
}
|
|
985
166
|
}
|
|
167
|
+
const openingsByLevel = new Map();
|
|
168
|
+
if (floorOpenings.length) {
|
|
169
|
+
const index = createFloorOpeningIndex(nodes);
|
|
170
|
+
for (const opening of floorOpenings) {
|
|
171
|
+
const signature = JSON.stringify([
|
|
172
|
+
opening.id,
|
|
173
|
+
opening.parentId,
|
|
174
|
+
opening.polygon,
|
|
175
|
+
opening.hostZoneId,
|
|
176
|
+
opening.drawnOn,
|
|
177
|
+
opening.cutsPrimary,
|
|
178
|
+
opening.cutsAdjacent,
|
|
179
|
+
opening.source,
|
|
180
|
+
opening.ownerId,
|
|
181
|
+
opening.legacyPlateCuts,
|
|
182
|
+
]);
|
|
183
|
+
for (const target of floorOpeningTargets(nodes, opening, index)) {
|
|
184
|
+
const signatures = openingsByLevel.get(target.levelId) ?? [];
|
|
185
|
+
signatures.push(signature);
|
|
186
|
+
openingsByLevel.set(target.levelId, signatures);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
986
190
|
const levelElevations = getLevelElevations(nodes);
|
|
987
191
|
const snapshots = new Map();
|
|
988
|
-
const levelIds = new Set([
|
|
192
|
+
const levelIds = new Set([
|
|
193
|
+
...arrivalsByLevel.keys(),
|
|
194
|
+
...ceilingIdsByLevel.keys(),
|
|
195
|
+
...wallsByLevel.keys(),
|
|
196
|
+
...zonesByLevel.keys(),
|
|
197
|
+
...separatorsByLevel.keys(),
|
|
198
|
+
...openingsByLevel.keys(),
|
|
199
|
+
]);
|
|
989
200
|
for (const levelId of levelIds) {
|
|
990
201
|
const walls = wallsByLevel.get(levelId) ?? [];
|
|
991
202
|
const zones = zonesByLevel.get(levelId) ?? [];
|
|
992
203
|
const level = nodes[levelId];
|
|
993
|
-
const storeyKey = level?.type === 'level'
|
|
204
|
+
const storeyKey = level?.type === 'level' ? JSON.stringify(level.height) : '';
|
|
994
205
|
const slabKey = (slabElevationsByLevel.get(levelId) ?? []).sort().join(';');
|
|
995
206
|
const aboveId = findLevelAboveId(levelId, levelElevations);
|
|
996
207
|
const aboveSlabKey = aboveId
|
|
997
208
|
? (coveringUndersidesByLevel.get(aboveId) ?? []).sort().join(';')
|
|
998
209
|
: '';
|
|
999
|
-
snapshots.set(levelId, `${storeyKey}#${levelWallSnapshot(walls, nodes, levelId)}##${zones.map(zoneGeometrySignature).sort().join('||')}##${slabKey}##${aboveSlabKey}`);
|
|
210
|
+
snapshots.set(levelId, `${(arrivalsByLevel.get(levelId) ?? []).sort().join('|')}#${(separatorsByLevel.get(levelId) ?? []).sort().join('|')}#${storeyKey}#${levelWallSnapshot(walls, nodes, levelId)}##${zones.map(zoneGeometrySignature).sort().join('||')}##${slabKey}##${aboveSlabKey}##${(ceilingIdsByLevel.get(levelId) ?? []).sort().join('|')}##${(openingsByLevel.get(levelId) ?? []).sort().join('|')}`);
|
|
1000
211
|
}
|
|
1001
212
|
return snapshots;
|
|
1002
213
|
}
|
|
@@ -1006,62 +217,12 @@ function buildSpace(levelId, room) {
|
|
|
1006
217
|
id: `space-${levelId}-${signature.slice(0, 12)}`,
|
|
1007
218
|
levelId,
|
|
1008
219
|
polygon: room.polygon.map(pointToTuple),
|
|
220
|
+
holes: room.holes,
|
|
1009
221
|
wallIds: [...new Set(room.boundaryFaces.map((boundary) => boundary.wallId))],
|
|
1010
222
|
boundaryFaces: room.boundaryFaces,
|
|
1011
223
|
isExterior: false,
|
|
1012
224
|
};
|
|
1013
225
|
}
|
|
1014
|
-
export function surfaceTouchesRooms(surface, rooms) {
|
|
1015
|
-
const { polygon, bbox } = surface;
|
|
1016
|
-
return rooms.some((room) => {
|
|
1017
|
-
if (bbox.maxX < room.bbox.minX ||
|
|
1018
|
-
room.bbox.maxX < bbox.minX ||
|
|
1019
|
-
bbox.maxY < room.bbox.minY ||
|
|
1020
|
-
room.bbox.maxY < bbox.minY) {
|
|
1021
|
-
return false;
|
|
1022
|
-
}
|
|
1023
|
-
return (polygonCoverageRatio(polygon, [room.polygon], bbox) > 0 ||
|
|
1024
|
-
polygonCoverageRatio(room.polygon, [polygon], room.bbox) > 0);
|
|
1025
|
-
});
|
|
1026
|
-
}
|
|
1027
|
-
function roomsAreRelated(beforeRoom, currentRoom) {
|
|
1028
|
-
const beforeIds = new Set(beforeRoom.boundaryFaces.map((boundary) => boundary.wallId));
|
|
1029
|
-
const currentIds = new Set(currentRoom.boundaryFaces.map((boundary) => boundary.wallId));
|
|
1030
|
-
const sharedWallCount = [...currentIds].filter((wallId) => beforeIds.has(wallId)).length;
|
|
1031
|
-
const smallerBoundarySize = Math.min(beforeIds.size, currentIds.size);
|
|
1032
|
-
if (sharedWallCount >= 2 && sharedWallCount >= Math.ceil(smallerBoundarySize / 2))
|
|
1033
|
-
return true;
|
|
1034
|
-
if (bboxOverlapArea(bboxOf(beforeRoom.polygon), bboxOf(currentRoom.polygon)) <= 1e-6)
|
|
1035
|
-
return false;
|
|
1036
|
-
return (polygonCoverageRatio(beforeRoom.polygon, [currentRoom.polygon]) > 0 ||
|
|
1037
|
-
polygonCoverageRatio(currentRoom.polygon, [beforeRoom.polygon]) > 0);
|
|
1038
|
-
}
|
|
1039
|
-
function roomHasAutoSurface(room, surfaces) {
|
|
1040
|
-
return matchesManualFootprint(room.polygon, surfaces
|
|
1041
|
-
.filter((surface) => surface.autoFromWalls)
|
|
1042
|
-
.map((surface) => surface.polygon.map(pointFromTuple)));
|
|
1043
|
-
}
|
|
1044
|
-
function roomsEligibleForAutoSurface(beforeRooms, currentRooms, currentSurfaces) {
|
|
1045
|
-
return currentRooms.filter((currentRoom) => {
|
|
1046
|
-
const related = beforeRooms.flatMap((beforeRoom) => {
|
|
1047
|
-
if (!roomsAreRelated(beforeRoom, currentRoom))
|
|
1048
|
-
return [];
|
|
1049
|
-
return [
|
|
1050
|
-
{
|
|
1051
|
-
room: beforeRoom,
|
|
1052
|
-
coverage: polygonCoverageRatio(currentRoom.polygon, [beforeRoom.polygon]),
|
|
1053
|
-
},
|
|
1054
|
-
];
|
|
1055
|
-
});
|
|
1056
|
-
const maxCoverage = Math.max(0, ...related.map(({ coverage }) => coverage));
|
|
1057
|
-
const predecessors = currentRooms.length >= beforeRooms.length && maxCoverage > 0
|
|
1058
|
-
? related.filter(({ coverage }) => coverage >= maxCoverage - 1e-6).map(({ room }) => room)
|
|
1059
|
-
: related.map(({ room }) => room);
|
|
1060
|
-
if (predecessors.length === 0)
|
|
1061
|
-
return true;
|
|
1062
|
-
return predecessors.every((beforeRoom) => roomHasAutoSurface(beforeRoom, currentSurfaces));
|
|
1063
|
-
});
|
|
1064
|
-
}
|
|
1065
226
|
function detectedRoomsByLevel(nodes) {
|
|
1066
227
|
const wallsByLevel = new Map();
|
|
1067
228
|
for (const node of Object.values(nodes)) {
|
|
@@ -1133,14 +294,17 @@ function descendantLevelIds(nodes, rootId) {
|
|
|
1133
294
|
}
|
|
1134
295
|
function fallbackLevelIdsForCandidates(before, current, candidateIds) {
|
|
1135
296
|
const levelIds = new Set();
|
|
1136
|
-
const addLevelAndLower = (levelId, nodes) => {
|
|
297
|
+
const addLevelAndLower = (levelId, nodes, includeBelow) => {
|
|
1137
298
|
if (!levelId)
|
|
1138
299
|
return;
|
|
1139
300
|
levelIds.add(levelId);
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
301
|
+
if (includeBelow) {
|
|
302
|
+
const lower = getLevelBelow(levelId, nodes);
|
|
303
|
+
if (lower)
|
|
304
|
+
levelIds.add(lower.id);
|
|
305
|
+
}
|
|
1143
306
|
};
|
|
307
|
+
let checkDisplacements = false;
|
|
1144
308
|
for (const id of candidateIds) {
|
|
1145
309
|
for (const nodes of [before, current]) {
|
|
1146
310
|
const node = nodes[id];
|
|
@@ -1149,472 +313,71 @@ function fallbackLevelIdsForCandidates(before, current, candidateIds) {
|
|
|
1149
313
|
if (node.type === 'level' || node.type === 'building' || node.type === 'site') {
|
|
1150
314
|
for (const levelId of descendantLevelIds(nodes, node.id))
|
|
1151
315
|
levelIds.add(levelId);
|
|
316
|
+
if (node.type === 'level') {
|
|
317
|
+
const lower = getLevelBelow(node.id, nodes);
|
|
318
|
+
if (lower)
|
|
319
|
+
levelIds.add(lower.id);
|
|
320
|
+
}
|
|
1152
321
|
}
|
|
1153
|
-
else if (node.type === '
|
|
1154
|
-
|
|
322
|
+
else if (node.type === 'stair' || node.type === 'stair-segment') {
|
|
323
|
+
const stair = node.type === 'stair' ? node : nodes[node.parentId];
|
|
324
|
+
const deck = nodes[stair?.deckSlabId];
|
|
325
|
+
if (deck?.support === 'open' && deck.parentId)
|
|
326
|
+
levelIds.add(deck.parentId);
|
|
327
|
+
if (node.type === 'stair')
|
|
328
|
+
for (const opening of Object.values(nodes))
|
|
329
|
+
if (opening.type === 'floor-opening' && opening.ownerId === node.id)
|
|
330
|
+
for (const target of floorOpeningTargets(nodes, opening))
|
|
331
|
+
levelIds.add(target.levelId);
|
|
1155
332
|
}
|
|
1156
|
-
else if (node.type === '
|
|
1157
|
-
|
|
1158
|
-
|
|
333
|
+
else if (node.type === 'door' || node.type === 'window') {
|
|
334
|
+
const wall = nodes[node.parentId];
|
|
335
|
+
if (wall?.parentId)
|
|
336
|
+
levelIds.add(wall.parentId);
|
|
1159
337
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
}
|
|
1170
|
-
function matchAutoSurfaces(roomPolygons, existingSurfaces, mergeSettingsSignature) {
|
|
1171
|
-
const manualSurfaces = existingSurfaces.filter((surface) => !surface.autoFromWalls);
|
|
1172
|
-
const manualSignatures = new Set(manualSurfaces.map((surface) => polygonSignature(surface.polygon.map(pointFromTuple))));
|
|
1173
|
-
const manualPolygons = manualSurfaces.map((surface) => surface.polygon.map(pointFromTuple));
|
|
1174
|
-
const detectedAll = roomPolygons
|
|
1175
|
-
.map((poly) => ({
|
|
1176
|
-
poly: simplifyClosedPolygon(poly.map(pointToTuple), AUTO_SLAB_POLYGON_SIMPLIFY_TOLERANCE).map(pointFromTuple),
|
|
1177
|
-
sig: '',
|
|
1178
|
-
centroid: { x: 0, y: 0 },
|
|
1179
|
-
area: 0,
|
|
1180
|
-
bbox: bboxOf([]),
|
|
1181
|
-
}))
|
|
1182
|
-
.map((room) => ({
|
|
1183
|
-
...room,
|
|
1184
|
-
sig: polygonSignature(room.poly),
|
|
1185
|
-
centroid: polygonCentroid(room.poly),
|
|
1186
|
-
area: Math.abs(polygonArea(room.poly)),
|
|
1187
|
-
bbox: bboxOf(room.poly),
|
|
1188
|
-
}));
|
|
1189
|
-
const detected = detectedAll.filter(({ sig, poly }) => !manualSignatures.has(sig) && !matchesManualFootprint(poly, manualPolygons));
|
|
1190
|
-
const existingAuto = existingSurfaces.filter((surface) => surface.autoFromWalls);
|
|
1191
|
-
const metadata = existingAuto.map((surface) => {
|
|
1192
|
-
const poly = surface.polygon.map(pointFromTuple);
|
|
1193
|
-
return {
|
|
1194
|
-
surface,
|
|
1195
|
-
sig: polygonSignature(poly),
|
|
1196
|
-
centroid: polygonCentroid(poly),
|
|
1197
|
-
area: Math.abs(polygonArea(poly)),
|
|
1198
|
-
bbox: bboxOf(poly),
|
|
1199
|
-
};
|
|
1200
|
-
});
|
|
1201
|
-
const conflictingSurfaceIds = new Set();
|
|
1202
|
-
const conflictingRoomIndices = new Set();
|
|
1203
|
-
const compatibleMergesByRoomIndex = new Map();
|
|
1204
|
-
detected.forEach((room, roomIndex) => {
|
|
1205
|
-
const contributors = existingAuto.filter((surface) => polygonCoverageRatio(surface.polygon.map(pointFromTuple), [room.poly]) >=
|
|
1206
|
-
ORPHAN_MERGE_COVERAGE_THRESHOLD);
|
|
1207
|
-
if (contributors.length < 2)
|
|
1208
|
-
return;
|
|
1209
|
-
if (new Set(contributors.map(mergeSettingsSignature)).size > 1) {
|
|
1210
|
-
conflictingRoomIndices.add(roomIndex);
|
|
1211
|
-
for (const surface of contributors)
|
|
1212
|
-
conflictingSurfaceIds.add(surface.id);
|
|
1213
|
-
return;
|
|
1214
|
-
}
|
|
1215
|
-
compatibleMergesByRoomIndex.set(roomIndex, contributors);
|
|
1216
|
-
});
|
|
1217
|
-
const matchedSurfaceIds = new Set();
|
|
1218
|
-
const matchedDetectedIndices = new Set();
|
|
1219
|
-
const roomIndexBySurfaceId = new Map();
|
|
1220
|
-
const sourceSurfaceIdByRoomIndex = new Map();
|
|
1221
|
-
const polygonBySurfaceId = new Map();
|
|
1222
|
-
const autoBySignature = new Map();
|
|
1223
|
-
for (const entry of metadata) {
|
|
1224
|
-
const bucket = autoBySignature.get(entry.sig) ?? [];
|
|
1225
|
-
bucket.push(entry);
|
|
1226
|
-
autoBySignature.set(entry.sig, bucket);
|
|
1227
|
-
}
|
|
1228
|
-
detected.forEach((room, index) => {
|
|
1229
|
-
if (conflictingRoomIndices.has(index)) {
|
|
1230
|
-
matchedDetectedIndices.add(index);
|
|
1231
|
-
return;
|
|
1232
|
-
}
|
|
1233
|
-
const existing = autoBySignature.get(room.sig)?.shift();
|
|
1234
|
-
if (!existing)
|
|
1235
|
-
return;
|
|
1236
|
-
matchedDetectedIndices.add(index);
|
|
1237
|
-
matchedSurfaceIds.add(existing.surface.id);
|
|
1238
|
-
roomIndexBySurfaceId.set(existing.surface.id, index);
|
|
1239
|
-
sourceSurfaceIdByRoomIndex.set(index, existing.surface.id);
|
|
1240
|
-
polygonBySurfaceId.set(existing.surface.id, room.poly.map(pointToTuple));
|
|
1241
|
-
});
|
|
1242
|
-
const remainingDetected = detected
|
|
1243
|
-
.map((room, index) => ({ room, index }))
|
|
1244
|
-
.filter(({ index }) => !matchedDetectedIndices.has(index))
|
|
1245
|
-
.sort((left, right) => right.room.area - left.room.area);
|
|
1246
|
-
const remainingAuto = metadata.filter((entry) => !matchedSurfaceIds.has(entry.surface.id));
|
|
1247
|
-
for (const { room, index } of remainingDetected) {
|
|
1248
|
-
let bestMatch = null;
|
|
1249
|
-
for (const entry of remainingAuto) {
|
|
1250
|
-
if (matchedSurfaceIds.has(entry.surface.id))
|
|
1251
|
-
continue;
|
|
1252
|
-
const distance = Math.hypot(room.centroid.x - entry.centroid.x, room.centroid.y - entry.centroid.y);
|
|
1253
|
-
const areaRatio = entry.area > 1e-6 ? room.area / entry.area : 999;
|
|
1254
|
-
const areaPenalty = Math.abs(Math.log(Math.max(1e-6, areaRatio)));
|
|
1255
|
-
if (bboxOverlapArea(room.bbox, entry.bbox) <= 0.0001 && distance > 1.5)
|
|
1256
|
-
continue;
|
|
1257
|
-
const score = distance + areaPenalty * 0.35;
|
|
1258
|
-
if (!bestMatch || score < bestMatch.score)
|
|
1259
|
-
bestMatch = { entry, score };
|
|
1260
|
-
}
|
|
1261
|
-
if (!bestMatch)
|
|
1262
|
-
continue;
|
|
1263
|
-
matchedDetectedIndices.add(index);
|
|
1264
|
-
matchedSurfaceIds.add(bestMatch.entry.surface.id);
|
|
1265
|
-
roomIndexBySurfaceId.set(bestMatch.entry.surface.id, index);
|
|
1266
|
-
sourceSurfaceIdByRoomIndex.set(index, bestMatch.entry.surface.id);
|
|
1267
|
-
polygonBySurfaceId.set(bestMatch.entry.surface.id, room.poly.map(pointToTuple));
|
|
1268
|
-
}
|
|
1269
|
-
detected.forEach((room, index) => {
|
|
1270
|
-
if (sourceSurfaceIdByRoomIndex.has(index))
|
|
1271
|
-
return;
|
|
1272
|
-
let bestSource = null;
|
|
1273
|
-
for (const entry of metadata) {
|
|
1274
|
-
const coverage = polygonCoverageRatio(room.poly, [entry.surface.polygon.map(pointFromTuple)]);
|
|
1275
|
-
if (coverage <= 0 || (bestSource && coverage <= bestSource.coverage))
|
|
1276
|
-
continue;
|
|
1277
|
-
bestSource = { id: entry.surface.id, coverage };
|
|
1278
|
-
}
|
|
1279
|
-
if (bestSource)
|
|
1280
|
-
sourceSurfaceIdByRoomIndex.set(index, bestSource.id);
|
|
1281
|
-
});
|
|
1282
|
-
const detectedRoomPolygons = detectedAll.map((room) => room.poly);
|
|
1283
|
-
const deleted = [];
|
|
1284
|
-
const demote = [];
|
|
1285
|
-
for (const surface of existingAuto) {
|
|
1286
|
-
if (polygonBySurfaceId.has(surface.id))
|
|
1287
|
-
continue;
|
|
1288
|
-
if (conflictingSurfaceIds.has(surface.id)) {
|
|
1289
|
-
demote.push({ id: surface.id, data: { autoFromWalls: false } });
|
|
1290
|
-
continue;
|
|
1291
|
-
}
|
|
1292
|
-
const coverage = polygonCoverageRatio(surface.polygon.map(pointFromTuple), detectedRoomPolygons);
|
|
1293
|
-
if (coverage >= ORPHAN_MERGE_COVERAGE_THRESHOLD)
|
|
1294
|
-
deleted.push(surface.id);
|
|
1295
|
-
else
|
|
1296
|
-
demote.push({ id: surface.id, data: { autoFromWalls: false } });
|
|
1297
|
-
}
|
|
1298
|
-
return {
|
|
1299
|
-
detectedAll,
|
|
1300
|
-
detected,
|
|
1301
|
-
existingAuto,
|
|
1302
|
-
compatibleMergesByRoomIndex,
|
|
1303
|
-
matchedDetectedIndices,
|
|
1304
|
-
roomIndexBySurfaceId,
|
|
1305
|
-
sourceSurfaceIdByRoomIndex,
|
|
1306
|
-
polygonBySurfaceId,
|
|
1307
|
-
delete: deleted,
|
|
1308
|
-
demote,
|
|
1309
|
-
};
|
|
1310
|
-
}
|
|
1311
|
-
export function planAutoZonesForLevel(spaces, existingZones) {
|
|
1312
|
-
const update = [];
|
|
1313
|
-
for (const zone of existingZones) {
|
|
1314
|
-
const storedSignature = polygonSignature(zone.polygon.map(pointFromTuple));
|
|
1315
|
-
const matchingSpace = zone.autoFromWalls && zone.boundaryWallIds.length >= 3
|
|
1316
|
-
? spaces.find((space) => sameStringSet(space.wallIds, zone.boundaryWallIds))
|
|
1317
|
-
: spaces.find((space) => polygonSignature(space.polygon.map(pointFromTuple)) === storedSignature);
|
|
1318
|
-
if (!matchingSpace)
|
|
1319
|
-
continue;
|
|
1320
|
-
const data = {};
|
|
1321
|
-
if (!zone.autoFromWalls)
|
|
1322
|
-
data.autoFromWalls = true;
|
|
1323
|
-
if (!sameStringSet(zone.boundaryWallIds, matchingSpace.wallIds)) {
|
|
1324
|
-
data.boundaryWallIds = matchingSpace.wallIds;
|
|
1325
|
-
}
|
|
1326
|
-
if (!sameTuplePolygon(zone.polygon, matchingSpace.polygon)) {
|
|
1327
|
-
data.polygon = matchingSpace.polygon;
|
|
1328
|
-
}
|
|
1329
|
-
if (Object.keys(data).length > 0)
|
|
1330
|
-
update.push({ id: zone.id, data });
|
|
1331
|
-
}
|
|
1332
|
-
return { update };
|
|
1333
|
-
}
|
|
1334
|
-
export function resolveAutoZonePolygon(zone, resolve) {
|
|
1335
|
-
if (!zone.autoFromWalls || zone.boundaryWallIds.length < 3)
|
|
1336
|
-
return zone.polygon;
|
|
1337
|
-
const walls = zone.boundaryWallIds.flatMap((id) => {
|
|
1338
|
-
const node = resolve(id);
|
|
1339
|
-
return node && typeof node === 'object' && 'type' in node && node.type === 'wall'
|
|
1340
|
-
? [node]
|
|
1341
|
-
: [];
|
|
1342
|
-
});
|
|
1343
|
-
if (walls.length !== zone.boundaryWallIds.length)
|
|
1344
|
-
return zone.polygon;
|
|
1345
|
-
const room = extractRooms(walls).find((candidate) => sameStringSet([...new Set(candidate.boundaryFaces.map((boundary) => boundary.wallId))], zone.boundaryWallIds));
|
|
1346
|
-
return room ? room.polygon.map(pointToTuple) : zone.polygon;
|
|
1347
|
-
}
|
|
1348
|
-
export function planAutoSlabsForLevel(roomPolygons, existingSlabs, context = {}, namingSlabs = existingSlabs) {
|
|
1349
|
-
const match = matchAutoSurfaces(roomPolygons, existingSlabs, slabMergeSettingsSignature);
|
|
1350
|
-
const { detected, existingAuto, compatibleMergesByRoomIndex: compatibleMergeSlabsByRoomIndex, matchedDetectedIndices: matchedDetectedIdx, roomIndexBySurfaceId: roomIndexBySlabId, sourceSurfaceIdByRoomIndex: sourceSlabIdByRoomIndex, delete: slabsToDelete, demote: slabDemotions, } = match;
|
|
1351
|
-
const updatesById = new Map();
|
|
1352
|
-
for (const slab of existingAuto) {
|
|
1353
|
-
const polygon = match.polygonBySurfaceId.get(slab.id);
|
|
1354
|
-
if (!polygon)
|
|
1355
|
-
continue;
|
|
1356
|
-
updatesById.set(slab.id, {
|
|
1357
|
-
polygon,
|
|
1358
|
-
elevation: slabElevationForReconciledRoom(slab, polygon, context),
|
|
1359
|
-
});
|
|
1360
|
-
}
|
|
1361
|
-
const openingAssignmentsBySlabId = new Map();
|
|
1362
|
-
for (const slab of existingAuto) {
|
|
1363
|
-
const roomIndices = [...sourceSlabIdByRoomIndex.entries()]
|
|
1364
|
-
.filter(([, slabId]) => slabId === slab.id)
|
|
1365
|
-
.map(([roomIndex]) => roomIndex);
|
|
1366
|
-
if (roomIndices.length === 0)
|
|
1367
|
-
continue;
|
|
1368
|
-
openingAssignmentsBySlabId.set(slab.id, partitionSurfaceOpenings(slab, roomIndices, detected));
|
|
1369
|
-
}
|
|
1370
|
-
const slabsToUpdate = [
|
|
1371
|
-
...existingAuto
|
|
1372
|
-
.filter((slab) => updatesById.has(slab.id))
|
|
1373
|
-
.flatMap((slab) => {
|
|
1374
|
-
const update = updatesById.get(slab.id);
|
|
1375
|
-
if (!update)
|
|
1376
|
-
return [];
|
|
1377
|
-
const roomIndex = roomIndexBySlabId.get(slab.id);
|
|
1378
|
-
const openings = roomIndex == null
|
|
1379
|
-
? { holes: slab.holes, holeMetadata: slab.holeMetadata }
|
|
1380
|
-
: compatibleMergeSlabsByRoomIndex.has(roomIndex)
|
|
1381
|
-
? mergedSurfaceOpenings(compatibleMergeSlabsByRoomIndex.get(roomIndex) ?? [])
|
|
1382
|
-
: (openingAssignmentsBySlabId.get(slab.id)?.get(roomIndex) ?? {
|
|
1383
|
-
holes: [],
|
|
1384
|
-
holeMetadata: [],
|
|
1385
|
-
});
|
|
1386
|
-
const data = {};
|
|
1387
|
-
if (!sameTuplePolygonRotation(slab.polygon, update.polygon))
|
|
1388
|
-
data.polygon = update.polygon;
|
|
1389
|
-
if (!sameTuplePolygons(slab.holes, openings.holes))
|
|
1390
|
-
data.holes = openings.holes;
|
|
1391
|
-
if (!sameHoleMetadata(slab.holeMetadata, openings.holeMetadata)) {
|
|
1392
|
-
data.holeMetadata = openings.holeMetadata;
|
|
338
|
+
else if (node.type === 'slab') {
|
|
339
|
+
const old = before[id], next = current[id];
|
|
340
|
+
const changed = !old ||
|
|
341
|
+
!next ||
|
|
342
|
+
['thickness', 'floorHeight', 'polygon', 'holes', 'elevation'].some((field) => JSON.stringify(old[field]) !==
|
|
343
|
+
JSON.stringify(next[field]));
|
|
344
|
+
addLevelAndLower(node.parentId, nodes, changed);
|
|
345
|
+
if (node.plateRole === 'base' && changed)
|
|
346
|
+
checkDisplacements = true;
|
|
1393
347
|
}
|
|
1394
|
-
if (
|
|
1395
|
-
|
|
1396
|
-
|
|
348
|
+
else if (node.type === 'ceiling') {
|
|
349
|
+
if (!(before[id] && current[id]) && node.parentId)
|
|
350
|
+
levelIds.add(node.parentId);
|
|
1397
351
|
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
const slabsToCreate = [];
|
|
1404
|
-
for (let index = 0; index < detected.length; index += 1) {
|
|
1405
|
-
if (matchedDetectedIdx.has(index))
|
|
1406
|
-
continue;
|
|
1407
|
-
const room = detected[index];
|
|
1408
|
-
if (!room)
|
|
1409
|
-
continue;
|
|
1410
|
-
const name = nextAutoRoomName(plannedSlabsForNaming, 'Slab');
|
|
1411
|
-
plannedSlabsForNaming.push({ name });
|
|
1412
|
-
const polygon = room.poly.map(pointToTuple);
|
|
1413
|
-
const sourceId = sourceSlabIdByRoomIndex.get(index);
|
|
1414
|
-
const source = sourceId ? existingAuto.find((slab) => slab.id === sourceId) : undefined;
|
|
1415
|
-
const openings = sourceId ? openingAssignmentsBySlabId.get(sourceId)?.get(index) : undefined;
|
|
1416
|
-
const elevation = source
|
|
1417
|
-
? slabElevationForReconciledRoom(source, polygon, context)
|
|
1418
|
-
: context.elevationForRoom?.(polygon);
|
|
1419
|
-
slabsToCreate.push(SlabNode.parse({
|
|
1420
|
-
name,
|
|
1421
|
-
polygon,
|
|
1422
|
-
holes: openings?.holes ?? [],
|
|
1423
|
-
holeMetadata: openings?.holeMetadata ?? [],
|
|
1424
|
-
elevation: elevation !== undefined && Number.isFinite(elevation)
|
|
1425
|
-
? elevation
|
|
1426
|
-
: DEFAULT_AUTO_SLAB_ELEVATION,
|
|
1427
|
-
thickness: source?.thickness,
|
|
1428
|
-
recessed: source?.recessed,
|
|
1429
|
-
recessedRimElevation: source?.recessedRimElevation,
|
|
1430
|
-
fillToTerrain: source?.fillToTerrain,
|
|
1431
|
-
material: source?.material,
|
|
1432
|
-
materialPreset: source?.materialPreset,
|
|
1433
|
-
slots: source?.slots,
|
|
1434
|
-
visible: source?.visible,
|
|
1435
|
-
autoFromWalls: true,
|
|
1436
|
-
}));
|
|
1437
|
-
}
|
|
1438
|
-
return {
|
|
1439
|
-
create: slabsToCreate,
|
|
1440
|
-
update: slabsToUpdate,
|
|
1441
|
-
delete: slabsToDelete,
|
|
1442
|
-
};
|
|
1443
|
-
}
|
|
1444
|
-
function syncAutoSlabsForLevel(levelId, roomPolygons, existingSlabs, sceneStore, context = {}, namingSlabs = existingSlabs) {
|
|
1445
|
-
const plan = planAutoSlabsForLevel(roomPolygons, existingSlabs, context, namingSlabs);
|
|
1446
|
-
if (plan.delete.length > 0) {
|
|
1447
|
-
sceneStore.getState().deleteNodes(plan.delete);
|
|
1448
|
-
}
|
|
1449
|
-
if (plan.update.length > 0) {
|
|
1450
|
-
sceneStore.getState().updateNodes(plan.update);
|
|
1451
|
-
}
|
|
1452
|
-
if (plan.create.length > 0) {
|
|
1453
|
-
sceneStore.getState().createNodes(plan.create.map((node) => ({ node, parentId: levelId })));
|
|
1454
|
-
}
|
|
1455
|
-
return plan;
|
|
1456
|
-
}
|
|
1457
|
-
export function planAutoCeilingsForLevel(roomPolygons, existingCeilings, context = {}, namingCeilings = existingCeilings) {
|
|
1458
|
-
const manualCeilings = existingCeilings.filter((ceiling) => !ceiling.autoFromWalls);
|
|
1459
|
-
const match = matchAutoSurfaces(roomPolygons, existingCeilings, ceilingMergeSettingsSignature);
|
|
1460
|
-
const { detected, existingAuto, compatibleMergesByRoomIndex: compatibleMergeCeilingsByRoomIndex, matchedDetectedIndices: matchedDetectedIdx, roomIndexBySurfaceId: roomIndexByCeilingId, sourceSurfaceIdByRoomIndex: sourceCeilingIdByRoomIndex, delete: ceilingsToDelete, demote: ceilingDemotions, } = match;
|
|
1461
|
-
const updatesById = new Map();
|
|
1462
|
-
for (const ceiling of existingAuto) {
|
|
1463
|
-
const polygon = match.polygonBySurfaceId.get(ceiling.id);
|
|
1464
|
-
if (!polygon)
|
|
1465
|
-
continue;
|
|
1466
|
-
updatesById.set(ceiling.id, {
|
|
1467
|
-
polygon,
|
|
1468
|
-
height: ceilingHeightForReconciledRoom(ceiling, polygon, context),
|
|
1469
|
-
});
|
|
1470
|
-
}
|
|
1471
|
-
// Stage 3-B reactive re-clamp (clamp-never-ask): a covering slab
|
|
1472
|
-
// created, moved, or thickened on the level above can leave an EXISTING
|
|
1473
|
-
// manual explicit-height ceiling poking into its solid. Clamp explicit
|
|
1474
|
-
// heights down to the bound; never raise them — a user-lowered ceiling
|
|
1475
|
-
// is intent, only an over-bound one is a conflict. Follows-mode
|
|
1476
|
-
// ceilings (absent height) derive under the bound by construction and
|
|
1477
|
-
// are skipped, so the clamp can never convert one to an explicit
|
|
1478
|
-
// height.
|
|
1479
|
-
const manualClamps = manualCeilings.flatMap((ceiling) => {
|
|
1480
|
-
if (ceiling.height == null)
|
|
1481
|
-
return [];
|
|
1482
|
-
const bound = resolveCeilingClampBound(ceiling.polygon, context);
|
|
1483
|
-
if (!Number.isFinite(bound))
|
|
1484
|
-
return [];
|
|
1485
|
-
return ceiling.height > bound + CEILING_HEIGHT_EPSILON
|
|
1486
|
-
? [{ id: ceiling.id, data: { height: bound } }]
|
|
1487
|
-
: [];
|
|
1488
|
-
});
|
|
1489
|
-
const openingAssignmentsByCeilingId = new Map();
|
|
1490
|
-
const childAssignmentsByCeilingId = new Map();
|
|
1491
|
-
for (const ceiling of existingAuto) {
|
|
1492
|
-
const roomIndices = [...sourceCeilingIdByRoomIndex.entries()]
|
|
1493
|
-
.filter(([, ceilingId]) => ceilingId === ceiling.id)
|
|
1494
|
-
.map(([roomIndex]) => roomIndex);
|
|
1495
|
-
if (roomIndices.length === 0)
|
|
1496
|
-
continue;
|
|
1497
|
-
openingAssignmentsByCeilingId.set(ceiling.id, partitionSurfaceOpenings(ceiling, roomIndices, detected));
|
|
1498
|
-
childAssignmentsByCeilingId.set(ceiling.id, partitionCeilingChildren(ceiling, roomIndices, detected, roomIndexByCeilingId.get(ceiling.id), context.childPosition));
|
|
1499
|
-
}
|
|
1500
|
-
const childReparents = [];
|
|
1501
|
-
const ceilingsToUpdate = [
|
|
1502
|
-
...existingAuto
|
|
1503
|
-
.filter((ceiling) => updatesById.has(ceiling.id))
|
|
1504
|
-
.flatMap((ceiling) => {
|
|
1505
|
-
const update = updatesById.get(ceiling.id);
|
|
1506
|
-
if (!update)
|
|
1507
|
-
return [];
|
|
1508
|
-
const roomIndex = roomIndexByCeilingId.get(ceiling.id);
|
|
1509
|
-
const openings = roomIndex == null
|
|
1510
|
-
? { holes: ceiling.holes, holeMetadata: ceiling.holeMetadata }
|
|
1511
|
-
: compatibleMergeCeilingsByRoomIndex.has(roomIndex)
|
|
1512
|
-
? mergedSurfaceOpenings(compatibleMergeCeilingsByRoomIndex.get(roomIndex) ?? [])
|
|
1513
|
-
: (openingAssignmentsByCeilingId.get(ceiling.id)?.get(roomIndex) ?? {
|
|
1514
|
-
holes: [],
|
|
1515
|
-
holeMetadata: [],
|
|
1516
|
-
});
|
|
1517
|
-
const data = {};
|
|
1518
|
-
if (!sameTuplePolygonRotation(ceiling.polygon, update.polygon))
|
|
1519
|
-
data.polygon = update.polygon;
|
|
1520
|
-
if (!sameTuplePolygons(ceiling.holes, openings.holes))
|
|
1521
|
-
data.holes = openings.holes;
|
|
1522
|
-
if (!sameHoleMetadata(ceiling.holeMetadata, openings.holeMetadata)) {
|
|
1523
|
-
data.holeMetadata = openings.holeMetadata;
|
|
352
|
+
else if (node.type === 'floor-opening') {
|
|
353
|
+
if (node.parentId)
|
|
354
|
+
levelIds.add(node.parentId);
|
|
355
|
+
for (const target of floorOpeningTargets(nodes, node))
|
|
356
|
+
levelIds.add(target.levelId);
|
|
1524
357
|
}
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
: roomIndex == null
|
|
1531
|
-
? ceiling.children
|
|
1532
|
-
: (childAssignmentsByCeilingId.get(ceiling.id)?.get(roomIndex) ?? ceiling.children);
|
|
1533
|
-
for (const contributor of mergeContributors ?? []) {
|
|
1534
|
-
if (contributor.id === ceiling.id)
|
|
358
|
+
else if (node.type === 'zone' || node.type === 'separator') {
|
|
359
|
+
if (node.type === 'zone' &&
|
|
360
|
+
before[id]?.type === 'zone' &&
|
|
361
|
+
current[id]?.type === 'zone' &&
|
|
362
|
+
zoneGeometrySignature(before[id]) === zoneGeometrySignature(current[id]))
|
|
1535
363
|
continue;
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
if (!sameStringSet(ceiling.children, children))
|
|
1541
|
-
data.children = children;
|
|
1542
|
-
if (update.height !== undefined &&
|
|
1543
|
-
(ceiling.height === undefined ||
|
|
1544
|
-
Math.abs(ceiling.height - update.height) > ROOM_VERTICAL_PLANE_EPSILON)) {
|
|
1545
|
-
data.height = update.height;
|
|
364
|
+
if (node.parentId)
|
|
365
|
+
levelIds.add(node.parentId);
|
|
1546
366
|
}
|
|
1547
|
-
return Object.keys(data).length > 0 ? [{ id: ceiling.id, data }] : [];
|
|
1548
|
-
}),
|
|
1549
|
-
...ceilingDemotions,
|
|
1550
|
-
...manualClamps,
|
|
1551
|
-
];
|
|
1552
|
-
const plannedCeilingsForNaming = [...namingCeilings];
|
|
1553
|
-
const ceilingsToCreate = [];
|
|
1554
|
-
for (let index = 0; index < detected.length; index += 1) {
|
|
1555
|
-
if (matchedDetectedIdx.has(index))
|
|
1556
|
-
continue;
|
|
1557
|
-
const room = detected[index];
|
|
1558
|
-
if (!room)
|
|
1559
|
-
continue;
|
|
1560
|
-
const name = nextAutoRoomName(plannedCeilingsForNaming, 'Ceiling');
|
|
1561
|
-
plannedCeilingsForNaming.push({ name });
|
|
1562
|
-
const polygon = room.poly.map(pointToTuple);
|
|
1563
|
-
const sourceId = sourceCeilingIdByRoomIndex.get(index);
|
|
1564
|
-
const source = sourceId ? existingAuto.find((ceiling) => ceiling.id === sourceId) : undefined;
|
|
1565
|
-
const openings = sourceId ? openingAssignmentsByCeilingId.get(sourceId)?.get(index) : undefined;
|
|
1566
|
-
const children = sourceId ? childAssignmentsByCeilingId.get(sourceId)?.get(index) : undefined;
|
|
1567
|
-
const height = source
|
|
1568
|
-
? ceilingHeightForReconciledRoom(source, polygon, context)
|
|
1569
|
-
: context.heightForRoom?.(polygon);
|
|
1570
|
-
const created = CeilingNode.parse({
|
|
1571
|
-
name,
|
|
1572
|
-
polygon,
|
|
1573
|
-
children: children ?? [],
|
|
1574
|
-
holes: openings?.holes ?? [],
|
|
1575
|
-
holeMetadata: openings?.holeMetadata ?? [],
|
|
1576
|
-
material: source?.material,
|
|
1577
|
-
materialPreset: source?.materialPreset,
|
|
1578
|
-
slots: source?.slots,
|
|
1579
|
-
visible: source?.visible,
|
|
1580
|
-
...(height !== undefined && Number.isFinite(height) ? { height } : {}),
|
|
1581
|
-
autoFromWalls: true,
|
|
1582
|
-
});
|
|
1583
|
-
ceilingsToCreate.push(created);
|
|
1584
|
-
for (const childId of children ?? []) {
|
|
1585
|
-
childReparents.push({ id: childId, parentId: created.id });
|
|
1586
367
|
}
|
|
1587
368
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
reparent: childReparents,
|
|
1593
|
-
};
|
|
1594
|
-
}
|
|
1595
|
-
function syncAutoCeilingsForLevel(levelId, roomPolygons, existingCeilings, sceneStore, context = {}, namingCeilings = existingCeilings) {
|
|
1596
|
-
const plan = planAutoCeilingsForLevel(roomPolygons, existingCeilings, context, namingCeilings);
|
|
1597
|
-
if (plan.update.length > 0) {
|
|
1598
|
-
sceneStore.getState().updateNodes(plan.update);
|
|
1599
|
-
}
|
|
1600
|
-
if (plan.create.length > 0) {
|
|
1601
|
-
sceneStore.getState().createNodes(plan.create.map((node) => ({ node, parentId: levelId })));
|
|
1602
|
-
}
|
|
1603
|
-
if (plan.reparent.length > 0) {
|
|
1604
|
-
sceneStore
|
|
1605
|
-
.getState()
|
|
1606
|
-
.updateNodes(plan.reparent.map(({ id, parentId }) => ({ id, data: { parentId } })));
|
|
1607
|
-
}
|
|
1608
|
-
if (plan.delete.length > 0) {
|
|
1609
|
-
sceneStore.getState().deleteNodes(plan.delete);
|
|
1610
|
-
}
|
|
369
|
+
if (checkDisplacements)
|
|
370
|
+
for (const levelId of changedLevelConstructionDisplacements(before, current))
|
|
371
|
+
levelIds.add(levelId);
|
|
372
|
+
return levelIds;
|
|
1611
373
|
}
|
|
1612
|
-
function detectSpacesFromWalls(levelId,
|
|
1613
|
-
const rooms = extractRooms(
|
|
374
|
+
function detectSpacesFromWalls(levelId, boundaries) {
|
|
375
|
+
const rooms = extractRooms(boundaries);
|
|
376
|
+
const walls = boundaries.filter((boundary) => boundary.type === 'wall');
|
|
1614
377
|
const roomPolygons = rooms.map((room) => room.polygon);
|
|
1615
378
|
const wallUpdates = walls.map((wall) => ({
|
|
1616
379
|
wallId: wall.id,
|
|
1617
|
-
...
|
|
380
|
+
...classifyWallSides(wall, rooms.flatMap((room) => room.spans)),
|
|
1618
381
|
}));
|
|
1619
382
|
return {
|
|
1620
383
|
rooms,
|
|
@@ -1626,224 +389,67 @@ function detectSpacesFromWalls(levelId, walls) {
|
|
|
1626
389
|
export function detectSpacesForLevel(levelId, walls) {
|
|
1627
390
|
return detectSpacesFromWalls(levelId, walls);
|
|
1628
391
|
}
|
|
1629
|
-
function
|
|
1630
|
-
|
|
392
|
+
function syncSceneStructure(levelIds, sceneStore, previousNodes, mintId) {
|
|
393
|
+
applyStructureReconciliation(sceneStore, { levelIds, previousNodes, mintId });
|
|
394
|
+
}
|
|
395
|
+
function publishSpaces(levelIds, nodes, editorStore) {
|
|
1631
396
|
const existingSpaces = editorStore.getState().spaces;
|
|
1632
|
-
const nextSpaces =
|
|
1633
|
-
for (const [spaceId, space] of Object.entries(existingSpaces)) {
|
|
1634
|
-
if (!levelIds.includes(space.levelId)) {
|
|
1635
|
-
nextSpaces[spaceId] = space;
|
|
1636
|
-
}
|
|
1637
|
-
}
|
|
397
|
+
const nextSpaces = Object.fromEntries(Object.entries(existingSpaces).filter(([, space]) => !levelIds.includes(space.levelId)));
|
|
1638
398
|
for (const levelId of levelIds) {
|
|
1639
|
-
const
|
|
1640
|
-
const
|
|
1641
|
-
const slabs = children.filter((node) => node?.type === 'slab');
|
|
1642
|
-
const ceilings = children.filter((node) => node?.type === 'ceiling');
|
|
1643
|
-
const zones = children.filter((node) => node?.type === 'zone');
|
|
1644
|
-
const { wallUpdates, spaces, rooms } = detectSpacesFromWalls(levelId, walls);
|
|
1645
|
-
const changedWallUpdates = wallUpdates.filter((update) => {
|
|
1646
|
-
const wall = nodes[update.wallId];
|
|
1647
|
-
return wall && (wall.frontSide !== update.frontSide || wall.backSide !== update.backSide);
|
|
1648
|
-
});
|
|
1649
|
-
if (changedWallUpdates.length > 0) {
|
|
1650
|
-
updateNodes(changedWallUpdates.map((update) => ({
|
|
1651
|
-
id: update.wallId,
|
|
1652
|
-
data: {
|
|
1653
|
-
frontSide: update.frontSide,
|
|
1654
|
-
backSide: update.backSide,
|
|
1655
|
-
},
|
|
1656
|
-
})));
|
|
1657
|
-
}
|
|
1658
|
-
const levelNode = nodes[levelId];
|
|
1659
|
-
const storeyHeight = levelNode?.type === 'level'
|
|
1660
|
-
? getStoredLevelHeight(levelNode)
|
|
1661
|
-
: DEFAULT_LEVEL_HEIGHT;
|
|
1662
|
-
const parsedSlabs = slabs.map((slab) => SlabNode.parse(slab));
|
|
1663
|
-
const parsedCeilings = ceilings.map((ceiling) => CeilingNode.parse(ceiling));
|
|
1664
|
-
const previousRooms = previousRoomsByLevel.get(levelId) ?? [];
|
|
1665
|
-
const slabRooms = roomsEligibleForAutoSurface(previousRooms, rooms, parsedSlabs);
|
|
1666
|
-
const ceilingRooms = roomsEligibleForAutoSurface(previousRooms, rooms, parsedCeilings);
|
|
1667
|
-
const verticalPlacements = autoRoomVerticalPlacements(spaces, walls,
|
|
1668
|
-
// A derived floor cannot be evidence for its own next elevation: that
|
|
1669
|
-
// would lift unpinned walls, then lift the floor again on every pass.
|
|
1670
|
-
parsedSlabs.filter((slab) => !slab.autoFromWalls), nodes, storeyHeight);
|
|
1671
|
-
const previousChildren = levelChildren(previousNodes, levelId);
|
|
1672
|
-
const previousWalls = previousChildren.filter((node) => node?.type === 'wall' && node.parentId === levelId);
|
|
1673
|
-
const previousSlabs = previousChildren
|
|
1674
|
-
.filter((node) => node?.type === 'slab')
|
|
1675
|
-
.map((slab) => SlabNode.parse(slab));
|
|
1676
|
-
const previousLevelNode = previousNodes[levelId];
|
|
1677
|
-
const previousStoreyHeight = previousLevelNode?.type === 'level'
|
|
1678
|
-
? getStoredLevelHeight(previousLevelNode)
|
|
1679
|
-
: DEFAULT_LEVEL_HEIGHT;
|
|
1680
|
-
const previousSpaces = detectSpacesFromWalls(levelId, previousWalls).spaces;
|
|
1681
|
-
const previousVerticalPlacements = autoRoomVerticalPlacements(previousSpaces, previousWalls, previousSlabs.filter((slab) => !slab.autoFromWalls), previousNodes, previousStoreyHeight);
|
|
1682
|
-
const placementFor = (polygon) => verticalPlacements.get(polygonSignature(polygon.map(pointFromTuple)));
|
|
1683
|
-
const previousPlacementFor = (polygon) => previousVerticalPlacements.get(polygonSignature(polygon.map(pointFromTuple)));
|
|
1684
|
-
syncAutoSlabsForLevel(levelId, slabRooms.map((room) => room.polygon), parsedSlabs, sceneStore, {
|
|
1685
|
-
elevationForRoom: (polygon) => placementFor(polygon)?.slabElevation,
|
|
1686
|
-
previousElevationForRoom: (polygon) => previousPlacementFor(polygon)?.slabElevation,
|
|
1687
|
-
});
|
|
1688
|
-
syncAutoCeilingsForLevel(levelId, ceilingRooms.map((room) => room.polygon), parsedCeilings, sceneStore, {
|
|
1689
|
-
storeyHeight,
|
|
1690
|
-
ceilingClampBound: (polygon) => getCeilingClampBound(levelId, nodes, polygon),
|
|
1691
|
-
heightForRoom: (polygon) => placementFor(polygon)?.ceilingHeight,
|
|
1692
|
-
previousHeightForRoom: (polygon) => previousPlacementFor(polygon)?.ceilingHeight,
|
|
1693
|
-
childPosition: (childId) => {
|
|
1694
|
-
const child = nodes[childId];
|
|
1695
|
-
return child && Array.isArray(child.position)
|
|
1696
|
-
? [child.position[0], child.position[2]]
|
|
1697
|
-
: undefined;
|
|
1698
|
-
},
|
|
1699
|
-
});
|
|
1700
|
-
const zonePlan = planAutoZonesForLevel(spaces, zones.map((zone) => ZoneNode.parse(zone)));
|
|
1701
|
-
if (zonePlan.update.length > 0)
|
|
1702
|
-
updateNodes(zonePlan.update);
|
|
1703
|
-
for (const space of spaces) {
|
|
399
|
+
const walls = levelChildren(nodes, levelId).filter((node) => node.type === 'wall');
|
|
400
|
+
for (const space of detectSpacesFromWalls(levelId, walls).spaces)
|
|
1704
401
|
nextSpaces[space.id] = space;
|
|
1705
|
-
}
|
|
1706
|
-
previousRoomsByLevel.set(levelId, rooms);
|
|
1707
|
-
}
|
|
1708
|
-
editorStore.getState().setSpaces(nextSpaces);
|
|
1709
|
-
}
|
|
1710
|
-
function runIndexedSpaceDetection(levelId, topologyDelta, sceneStore, editorStore, nodes, previousNodes) {
|
|
1711
|
-
const { updateNodes } = sceneStore.getState();
|
|
1712
|
-
const allRoomPolygons = topologyDelta.allCurrentRooms.map((room) => room.polygon);
|
|
1713
|
-
const changedWallUpdates = topologyDelta.currentWalls
|
|
1714
|
-
.map((wall) => ({
|
|
1715
|
-
wallId: wall.id,
|
|
1716
|
-
...resolveWallSurfaceSides(wall, allRoomPolygons),
|
|
1717
|
-
}))
|
|
1718
|
-
.filter((update) => {
|
|
1719
|
-
const wall = nodes[update.wallId];
|
|
1720
|
-
return (wall?.type === 'wall' &&
|
|
1721
|
-
(wall.frontSide !== update.frontSide || wall.backSide !== update.backSide));
|
|
1722
|
-
});
|
|
1723
|
-
if (changedWallUpdates.length > 0) {
|
|
1724
|
-
updateNodes(changedWallUpdates.map((update) => ({
|
|
1725
|
-
id: update.wallId,
|
|
1726
|
-
data: { frontSide: update.frontSide, backSide: update.backSide },
|
|
1727
|
-
})));
|
|
1728
402
|
}
|
|
1729
|
-
const scopedRooms = [...topologyDelta.beforeRooms, ...topologyDelta.currentRooms].map((room) => ({
|
|
1730
|
-
polygon: room.polygon,
|
|
1731
|
-
bbox: bboxOf(room.polygon),
|
|
1732
|
-
}));
|
|
1733
|
-
if (scopedRooms.length > 0) {
|
|
1734
|
-
const unaffectedRooms = topologyDelta.allCurrentRooms
|
|
1735
|
-
.filter((room) => !topologyDelta.currentRooms.includes(room))
|
|
1736
|
-
.map((room) => ({ polygon: room.polygon, bbox: bboxOf(room.polygon) }));
|
|
1737
|
-
const currentChildren = levelChildren(nodes, levelId);
|
|
1738
|
-
const allSlabs = currentChildren
|
|
1739
|
-
.filter((node) => node.type === 'slab')
|
|
1740
|
-
.map((slab) => SlabNode.parse(slab));
|
|
1741
|
-
const allCeilings = currentChildren
|
|
1742
|
-
.filter((node) => node.type === 'ceiling')
|
|
1743
|
-
.map((ceiling) => CeilingNode.parse(ceiling));
|
|
1744
|
-
const touchesScopedRooms = (surface) => {
|
|
1745
|
-
const polygon = surface.polygon.map(pointFromTuple);
|
|
1746
|
-
const bounded = { polygon, bbox: bboxOf(polygon) };
|
|
1747
|
-
return (surfaceTouchesRooms(bounded, scopedRooms) &&
|
|
1748
|
-
(!surface.autoFromWalls || !surfaceTouchesRooms(bounded, unaffectedRooms)));
|
|
1749
|
-
};
|
|
1750
|
-
const slabs = allSlabs.filter(touchesScopedRooms);
|
|
1751
|
-
const ceilings = allCeilings.filter(touchesScopedRooms);
|
|
1752
|
-
const slabRooms = roomsEligibleForAutoSurface(topologyDelta.beforeRooms, topologyDelta.currentRooms, slabs);
|
|
1753
|
-
const ceilingRooms = roomsEligibleForAutoSurface(topologyDelta.beforeRooms, topologyDelta.currentRooms, ceilings);
|
|
1754
|
-
const levelNode = nodes[levelId];
|
|
1755
|
-
const storeyHeight = levelNode?.type === 'level'
|
|
1756
|
-
? getStoredLevelHeight(levelNode)
|
|
1757
|
-
: DEFAULT_LEVEL_HEIGHT;
|
|
1758
|
-
const currentSpaces = topologyDelta.currentRooms.map((room) => buildSpace(levelId, room));
|
|
1759
|
-
const verticalPlacements = autoRoomVerticalPlacements(currentSpaces, topologyDelta.currentWalls, allSlabs.filter((slab) => !slab.autoFromWalls), nodes, storeyHeight);
|
|
1760
|
-
const previousChildren = levelChildren(previousNodes, levelId);
|
|
1761
|
-
const previousSlabs = previousChildren
|
|
1762
|
-
.filter((node) => node.type === 'slab')
|
|
1763
|
-
.map((slab) => SlabNode.parse(slab));
|
|
1764
|
-
const previousLevelNode = previousNodes[levelId];
|
|
1765
|
-
const previousStoreyHeight = previousLevelNode?.type === 'level'
|
|
1766
|
-
? getStoredLevelHeight(previousLevelNode)
|
|
1767
|
-
: DEFAULT_LEVEL_HEIGHT;
|
|
1768
|
-
const previousSpaces = topologyDelta.beforeRooms.map((room) => buildSpace(levelId, room));
|
|
1769
|
-
const previousVerticalPlacements = autoRoomVerticalPlacements(previousSpaces, topologyDelta.previousWalls, previousSlabs.filter((slab) => !slab.autoFromWalls), previousNodes, previousStoreyHeight);
|
|
1770
|
-
const placementFor = (polygon) => verticalPlacements.get(polygonSignature(polygon.map(pointFromTuple)));
|
|
1771
|
-
const previousPlacementFor = (polygon) => previousVerticalPlacements.get(polygonSignature(polygon.map(pointFromTuple)));
|
|
1772
|
-
syncAutoSlabsForLevel(levelId, slabRooms.map((room) => room.polygon), slabs, sceneStore, {
|
|
1773
|
-
elevationForRoom: (polygon) => placementFor(polygon)?.slabElevation,
|
|
1774
|
-
previousElevationForRoom: (polygon) => previousPlacementFor(polygon)?.slabElevation,
|
|
1775
|
-
}, allSlabs);
|
|
1776
|
-
syncAutoCeilingsForLevel(levelId, ceilingRooms.map((room) => room.polygon), ceilings, sceneStore, {
|
|
1777
|
-
storeyHeight,
|
|
1778
|
-
ceilingClampBound: (polygon) => getCeilingClampBound(levelId, nodes, polygon),
|
|
1779
|
-
heightForRoom: (polygon) => placementFor(polygon)?.ceilingHeight,
|
|
1780
|
-
previousHeightForRoom: (polygon) => previousPlacementFor(polygon)?.ceilingHeight,
|
|
1781
|
-
childPosition: (childId) => {
|
|
1782
|
-
const child = nodes[childId];
|
|
1783
|
-
return child && Array.isArray(child.position)
|
|
1784
|
-
? [child.position[0], child.position[2]]
|
|
1785
|
-
: undefined;
|
|
1786
|
-
},
|
|
1787
|
-
}, allCeilings);
|
|
1788
|
-
}
|
|
1789
|
-
const spaces = topologyDelta.allCurrentRooms.map((room) => buildSpace(levelId, room));
|
|
1790
|
-
const zones = levelChildren(nodes, levelId)
|
|
1791
|
-
.filter((node) => node.type === 'zone')
|
|
1792
|
-
.map((zone) => ZoneNode.parse(zone));
|
|
1793
|
-
const zonePlan = planAutoZonesForLevel(spaces, zones);
|
|
1794
|
-
if (zonePlan.update.length > 0)
|
|
1795
|
-
updateNodes(zonePlan.update);
|
|
1796
|
-
const existingSpaces = editorStore.getState().spaces;
|
|
1797
|
-
const nextSpaces = {};
|
|
1798
|
-
for (const [spaceId, space] of Object.entries(existingSpaces)) {
|
|
1799
|
-
if (space.levelId !== levelId)
|
|
1800
|
-
nextSpaces[spaceId] = space;
|
|
1801
|
-
}
|
|
1802
|
-
for (const space of spaces)
|
|
1803
|
-
nextSpaces[space.id] = space;
|
|
1804
403
|
editorStore.getState().setSpaces(nextSpaces);
|
|
1805
404
|
}
|
|
1806
|
-
//
|
|
1807
|
-
//
|
|
1808
|
-
// mutating the scene so the wall-driven auto slab/ceiling sync doesn't race
|
|
1809
|
-
// `create_room`'s explicit slabs/ceilings (see plan
|
|
1810
|
-
// `ai-pause-space-detection`).
|
|
405
|
+
// Batch writers pause intermediate topology; the final resume reconciles every
|
|
406
|
+
// level whose intent changed, including batches completed outside a store commit.
|
|
1811
407
|
let spaceDetectionPauseDepth = 0;
|
|
1812
|
-
|
|
408
|
+
const resumeListeners = new Set();
|
|
409
|
+
/** Pause structure reconciliation. Refcounted — pair with `resumeSpaceDetection`. */
|
|
1813
410
|
export function pauseSpaceDetection() {
|
|
1814
411
|
spaceDetectionPauseDepth += 1;
|
|
1815
412
|
}
|
|
1816
|
-
/** Resume
|
|
413
|
+
/** Resume structure reconciliation. No-op if not currently paused. */
|
|
1817
414
|
export function resumeSpaceDetection() {
|
|
1818
415
|
if (spaceDetectionPauseDepth === 0)
|
|
1819
416
|
return;
|
|
1820
417
|
spaceDetectionPauseDepth -= 1;
|
|
418
|
+
if (spaceDetectionPauseDepth === 0)
|
|
419
|
+
for (const reconcile of resumeListeners)
|
|
420
|
+
reconcile();
|
|
1821
421
|
}
|
|
1822
|
-
/** True iff
|
|
422
|
+
/** True iff structure reconciliation is currently paused. */
|
|
1823
423
|
export function isSpaceDetectionPaused() {
|
|
1824
424
|
return spaceDetectionPauseDepth > 0;
|
|
1825
425
|
}
|
|
426
|
+
export function createRoomTopologyIndex() {
|
|
427
|
+
return new RoomTopologyIndex({
|
|
428
|
+
detectRooms: extractRooms,
|
|
429
|
+
sampleWall: (wall) => sampleWallPointsForRoomDetection(wall).map(pointToTuple),
|
|
430
|
+
junctionTolerance: WALL_JUNCTION_TOLERANCE,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
1826
433
|
export function initSpaceDetectionSync(sceneStore, editorStore, options = {}) {
|
|
1827
434
|
// Baseline from whatever is already in the store. Detection reacts to wall
|
|
1828
435
|
// edits made IN-SESSION (create / move / delete); it must not re-litigate a
|
|
1829
436
|
// scene that merely loaded — rerunning on hydration resurrected auto slabs
|
|
1830
437
|
// the user had deleted in an earlier session.
|
|
1831
438
|
const initialNodes = sceneStore.getState().nodes;
|
|
1832
|
-
const previousRoomsByLevel = new Map();
|
|
1833
439
|
const topologyIndex = new RoomTopologyIndex({
|
|
1834
440
|
detectRooms: extractRooms,
|
|
1835
441
|
sampleWall: (wall) => sampleWallPointsForRoomDetection(wall).map(pointToTuple),
|
|
1836
442
|
junctionTolerance: WALL_JUNCTION_TOLERANCE,
|
|
443
|
+
includeSeparators: false,
|
|
1837
444
|
});
|
|
1838
445
|
let previousNodes = initialNodes;
|
|
1839
446
|
let isProcessing = false;
|
|
447
|
+
const mintId = options.mintId ?? generateId;
|
|
1840
448
|
const adoptSceneBaseline = (nodes) => {
|
|
1841
449
|
topologyIndex.rebuild(nodes);
|
|
1842
450
|
const roomsByLevel = detectedRoomsByLevel(nodes);
|
|
1843
|
-
previousRoomsByLevel.clear();
|
|
1844
451
|
const spaces = {};
|
|
1845
452
|
for (const [levelId, rooms] of roomsByLevel) {
|
|
1846
|
-
previousRoomsByLevel.set(levelId, rooms);
|
|
1847
453
|
for (const room of rooms) {
|
|
1848
454
|
const space = buildSpace(levelId, room);
|
|
1849
455
|
spaces[space.id] = space;
|
|
@@ -1866,17 +472,16 @@ export function initSpaceDetectionSync(sceneStore, editorStore, options = {}) {
|
|
|
1866
472
|
const unsubscribe = sceneStore.subscribe((state) => {
|
|
1867
473
|
if (isProcessing)
|
|
1868
474
|
return;
|
|
1869
|
-
if (
|
|
475
|
+
if (isRestoringSceneHistory()) {
|
|
476
|
+
adoptSceneBaseline(state.nodes);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (getSceneHistoryPauseDepth() > 0 || sceneStore.temporal.getState().isTracking === false)
|
|
1870
480
|
return;
|
|
1871
481
|
const nodes = state.nodes;
|
|
1872
482
|
const candidateIds = activeSceneCommitNodeIds();
|
|
1873
|
-
|
|
1874
|
-
// every paused change once detection resumes. Whatever the AI built while
|
|
1875
|
-
// paused becomes the new baseline; only future changes will reconcile.
|
|
1876
|
-
if (spaceDetectionPauseDepth > 0) {
|
|
1877
|
-
adoptSceneBaseline(nodes);
|
|
483
|
+
if (spaceDetectionPauseDepth > 0)
|
|
1878
484
|
return;
|
|
1879
|
-
}
|
|
1880
485
|
const changedWalls = changedWallIdsByLevel(previousNodes, nodes, candidateIds);
|
|
1881
486
|
if (candidateIds && changedWalls.size > 0) {
|
|
1882
487
|
const fallbackLevels = fallbackLevelIdsForCandidates(previousNodes, nodes, candidateIds);
|
|
@@ -1885,10 +490,11 @@ export function initSpaceDetectionSync(sceneStore, editorStore, options = {}) {
|
|
|
1885
490
|
isProcessing = true;
|
|
1886
491
|
pauseSceneHistory(sceneStore);
|
|
1887
492
|
try {
|
|
493
|
+
const levels = [...new Set([...changedWalls.keys(), ...fallbackLevels])];
|
|
494
|
+
syncSceneStructure(levels, sceneStore, previousNodes, mintId);
|
|
495
|
+
publishSpaces(levels, sceneStore.getState().nodes, editorStore);
|
|
1888
496
|
for (const [levelId, wallIds] of changedWalls) {
|
|
1889
497
|
const topologyDelta = topologyIndex.applyWallDelta(levelId, wallIds, previousNodes, nodes);
|
|
1890
|
-
runIndexedSpaceDetection(levelId, topologyDelta, sceneStore, editorStore, nodes, previousNodes);
|
|
1891
|
-
previousRoomsByLevel.set(levelId, topologyDelta.allCurrentRooms);
|
|
1892
498
|
options.onTopologyReconcile?.({
|
|
1893
499
|
levelId,
|
|
1894
500
|
strategy: topologyDelta.strategy,
|
|
@@ -1898,7 +504,6 @@ export function initSpaceDetectionSync(sceneStore, editorStore, options = {}) {
|
|
|
1898
504
|
});
|
|
1899
505
|
}
|
|
1900
506
|
if (fallbackLevels.size > 0) {
|
|
1901
|
-
runSpaceDetection([...fallbackLevels], sceneStore, editorStore, sceneStore.getState().nodes, previousNodes, previousRoomsByLevel);
|
|
1902
507
|
const liveNodes = sceneStore.getState().nodes;
|
|
1903
508
|
for (const levelId of fallbackLevels)
|
|
1904
509
|
topologyIndex.rebuildLevel(levelId, liveNodes);
|
|
@@ -1934,22 +539,14 @@ export function initSpaceDetectionSync(sceneStore, editorStore, options = {}) {
|
|
|
1934
539
|
}
|
|
1935
540
|
}
|
|
1936
541
|
if (levelsToUpdate.size === 0) {
|
|
1937
|
-
if (candidateIds) {
|
|
1938
|
-
previousNodes = nodes;
|
|
1939
|
-
return;
|
|
1940
|
-
}
|
|
1941
|
-
const currentRoomsByLevel = detectedRoomsByLevel(nodes);
|
|
1942
|
-
previousRoomsByLevel.clear();
|
|
1943
|
-
for (const [levelId, rooms] of currentRoomsByLevel) {
|
|
1944
|
-
previousRoomsByLevel.set(levelId, rooms);
|
|
1945
|
-
}
|
|
1946
542
|
previousNodes = nodes;
|
|
1947
543
|
return;
|
|
1948
544
|
}
|
|
1949
545
|
isProcessing = true;
|
|
1950
546
|
pauseSceneHistory(sceneStore);
|
|
1951
547
|
try {
|
|
1952
|
-
|
|
548
|
+
syncSceneStructure([...levelsToUpdate], sceneStore, previousNodes, mintId);
|
|
549
|
+
publishSpaces([...levelsToUpdate], sceneStore.getState().nodes, editorStore);
|
|
1953
550
|
}
|
|
1954
551
|
finally {
|
|
1955
552
|
resumeSceneHistory(sceneStore);
|
|
@@ -1960,9 +557,40 @@ export function initSpaceDetectionSync(sceneStore, editorStore, options = {}) {
|
|
|
1960
557
|
isProcessing = false;
|
|
1961
558
|
}
|
|
1962
559
|
});
|
|
560
|
+
const reconcilePaused = () => {
|
|
561
|
+
const capture = () => {
|
|
562
|
+
const { nodes, rootNodeIds = [], collections = {}, materials = {}, installedPlugins = [], } = sceneStore.getState();
|
|
563
|
+
return { nodes, rootNodeIds, collections, materials, installedPlugins };
|
|
564
|
+
};
|
|
565
|
+
const beforeCommit = capture();
|
|
566
|
+
const nodes = beforeCommit.nodes;
|
|
567
|
+
const before = levelStructureSnapshots(previousNodes);
|
|
568
|
+
const after = levelStructureSnapshots(nodes);
|
|
569
|
+
const levels = [...new Set([...before.keys(), ...after.keys()])].filter((id) => before.get(id) !== after.get(id));
|
|
570
|
+
if (!levels.length)
|
|
571
|
+
return;
|
|
572
|
+
isProcessing = true;
|
|
573
|
+
pauseSceneHistory(sceneStore);
|
|
574
|
+
try {
|
|
575
|
+
syncSceneStructure(levels, sceneStore, previousNodes, mintId);
|
|
576
|
+
publishSpaces(levels, sceneStore.getState().nodes, editorStore);
|
|
577
|
+
for (const id of levels)
|
|
578
|
+
topologyIndex.rebuildLevel(id, sceneStore.getState().nodes);
|
|
579
|
+
previousNodes = sceneStore.getState().nodes;
|
|
580
|
+
}
|
|
581
|
+
finally {
|
|
582
|
+
resumeSceneHistory(sceneStore);
|
|
583
|
+
isProcessing = false;
|
|
584
|
+
}
|
|
585
|
+
// A pause may outlive the triggering commit; publish its completed derivation
|
|
586
|
+
// so collaboration receives the originator's ids even for AI/template batches.
|
|
587
|
+
notifySceneCommit({ origin: 'local', before: beforeCommit, current: capture() });
|
|
588
|
+
};
|
|
589
|
+
resumeListeners.add(reconcilePaused);
|
|
1963
590
|
return () => {
|
|
1964
591
|
unsubscribe();
|
|
1965
592
|
unsubscribeCommits();
|
|
593
|
+
resumeListeners.delete(reconcilePaused);
|
|
1966
594
|
};
|
|
1967
595
|
}
|
|
1968
596
|
export function wallTouchesOthers(wall, otherWalls) {
|