@pascal-app/core 0.7.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/events/bus.d.ts +47 -18
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/events/bus.js +1 -1
- package/dist/hooks/scene-registry/scene-registry.d.ts +6 -22
- package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
- package/dist/hooks/scene-registry/scene-registry.js +33 -26
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +273 -0
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +13 -2
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-manager.js +95 -9
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.js +91 -18
- package/dist/index.d.ts +22 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -2
- package/dist/lib/polygon-geometry.d.ts.map +1 -1
- package/dist/lib/polygon-relations.d.ts +10 -0
- package/dist/lib/polygon-relations.d.ts.map +1 -0
- package/dist/lib/polygon-relations.js +73 -0
- package/dist/lib/space-detection-pause.test.d.ts +2 -0
- package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
- package/dist/lib/space-detection-pause.test.js +42 -0
- package/dist/lib/space-detection.d.ts +30 -1
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +222 -41
- package/dist/lib/space-detection.test.d.ts +2 -0
- package/dist/lib/space-detection.test.d.ts.map +1 -0
- package/dist/lib/space-detection.test.js +77 -0
- package/dist/material-library.d.ts +1 -1
- package/dist/material-library.d.ts.map +1 -1
- package/dist/material-library.js +1957 -141
- package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
- package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
- package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
- package/dist/registry/handles.d.ts +321 -0
- package/dist/registry/handles.d.ts.map +1 -0
- package/dist/registry/handles.js +24 -0
- package/dist/registry/index.d.ts +7 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +4 -0
- package/dist/registry/registry.d.ts +93 -0
- package/dist/registry/registry.d.ts.map +1 -0
- package/dist/registry/registry.js +201 -0
- package/dist/registry/registry.test.d.ts +2 -0
- package/dist/registry/registry.test.d.ts.map +1 -0
- package/dist/registry/registry.test.js +183 -0
- package/dist/registry/relations-resolver.d.ts +42 -0
- package/dist/registry/relations-resolver.d.ts.map +1 -0
- package/dist/registry/relations-resolver.js +77 -0
- package/dist/registry/relations-resolver.test.d.ts +2 -0
- package/dist/registry/relations-resolver.test.d.ts.map +1 -0
- package/dist/registry/relations-resolver.test.js +183 -0
- package/dist/registry/scene-api.d.ts +44 -0
- package/dist/registry/scene-api.d.ts.map +1 -0
- package/dist/registry/scene-api.js +111 -0
- package/dist/registry/scene-api.test.d.ts +2 -0
- package/dist/registry/scene-api.test.d.ts.map +1 -0
- package/dist/registry/scene-api.test.js +183 -0
- package/dist/registry/subtree.d.ts +67 -0
- package/dist/registry/subtree.d.ts.map +1 -0
- package/dist/registry/subtree.js +119 -0
- package/dist/registry/subtree.test.d.ts +2 -0
- package/dist/registry/subtree.test.d.ts.map +1 -0
- package/dist/registry/subtree.test.js +133 -0
- package/dist/registry/types.d.ts +1420 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +1 -0
- package/dist/schema/asset-url.test.js +0 -4
- package/dist/schema/index.d.ts +17 -2
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +17 -3
- package/dist/schema/material.d.ts +12 -2
- package/dist/schema/material.d.ts.map +1 -1
- package/dist/schema/material.js +10 -0
- package/dist/schema/nodes/box-vent.d.ts +74 -0
- package/dist/schema/nodes/box-vent.d.ts.map +1 -0
- package/dist/schema/nodes/box-vent.js +63 -0
- package/dist/schema/nodes/building.d.ts +1 -1
- package/dist/schema/nodes/building.d.ts.map +1 -1
- package/dist/schema/nodes/building.js +3 -2
- package/dist/schema/nodes/ceiling.d.ts +3 -1
- package/dist/schema/nodes/ceiling.d.ts.map +1 -1
- package/dist/schema/nodes/ceiling.js +1 -1
- package/dist/schema/nodes/chimney.d.ts +158 -0
- package/dist/schema/nodes/chimney.d.ts.map +1 -0
- package/dist/schema/nodes/chimney.js +67 -0
- package/dist/schema/nodes/column.d.ts +598 -6
- package/dist/schema/nodes/column.d.ts.map +1 -1
- package/dist/schema/nodes/column.js +586 -6
- package/dist/schema/nodes/cupola.d.ts +66 -0
- package/dist/schema/nodes/cupola.d.ts.map +1 -0
- package/dist/schema/nodes/cupola.js +28 -0
- package/dist/schema/nodes/door.d.ts +1 -1
- package/dist/schema/nodes/dormer.d.ts +228 -0
- package/dist/schema/nodes/dormer.d.ts.map +1 -0
- package/dist/schema/nodes/dormer.js +119 -0
- package/dist/schema/nodes/downspout.d.ts +75 -0
- package/dist/schema/nodes/downspout.d.ts.map +1 -0
- package/dist/schema/nodes/downspout.js +63 -0
- package/dist/schema/nodes/elevator.d.ts +105 -0
- package/dist/schema/nodes/elevator.d.ts.map +1 -0
- package/dist/schema/nodes/elevator.js +54 -0
- package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
- package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
- package/dist/schema/nodes/eyebrow-vent.js +40 -0
- package/dist/schema/nodes/fence.d.ts +3 -2
- package/dist/schema/nodes/fence.d.ts.map +1 -1
- package/dist/schema/nodes/fence.js +4 -2
- package/dist/schema/nodes/gutter.d.ts +84 -0
- package/dist/schema/nodes/gutter.d.ts.map +1 -0
- package/dist/schema/nodes/gutter.js +76 -0
- package/dist/schema/nodes/item.d.ts +16 -0
- package/dist/schema/nodes/item.d.ts.map +1 -1
- package/dist/schema/nodes/item.js +21 -0
- package/dist/schema/nodes/level.d.ts +1 -1
- package/dist/schema/nodes/level.d.ts.map +1 -1
- package/dist/schema/nodes/level.js +2 -0
- package/dist/schema/nodes/ridge-vent.d.ts +67 -0
- package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
- package/dist/schema/nodes/ridge-vent.js +30 -0
- package/dist/schema/nodes/roof-segment.d.ts +198 -4
- package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment.js +254 -3
- package/dist/schema/nodes/roof.d.ts +6 -6
- package/dist/schema/nodes/roof.d.ts.map +1 -1
- package/dist/schema/nodes/roof.js +9 -21
- package/dist/schema/nodes/shelf.d.ts +105 -0
- package/dist/schema/nodes/shelf.d.ts.map +1 -0
- package/dist/schema/nodes/shelf.js +84 -0
- package/dist/schema/nodes/site.d.ts +1 -116
- package/dist/schema/nodes/site.d.ts.map +1 -1
- package/dist/schema/nodes/site.js +2 -6
- package/dist/schema/nodes/skylight.d.ts +279 -0
- package/dist/schema/nodes/skylight.d.ts.map +1 -0
- package/dist/schema/nodes/skylight.js +152 -0
- package/dist/schema/nodes/slab.d.ts +3 -1
- package/dist/schema/nodes/slab.d.ts.map +1 -1
- package/dist/schema/nodes/slab.js +1 -1
- package/dist/schema/nodes/solar-panel.d.ts +117 -0
- package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
- package/dist/schema/nodes/solar-panel.js +47 -0
- package/dist/schema/nodes/stair-segment.d.ts +1 -1
- package/dist/schema/nodes/stair.d.ts +6 -6
- package/dist/schema/nodes/stair.d.ts.map +1 -1
- package/dist/schema/nodes/stair.js +9 -7
- package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
- package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
- package/dist/schema/nodes/surface-hole-metadata.js +3 -1
- package/dist/schema/nodes/turbine-vent.d.ts +68 -0
- package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
- package/dist/schema/nodes/turbine-vent.js +41 -0
- package/dist/schema/nodes/wall.d.ts +3 -3
- package/dist/schema/nodes/window.d.ts +1 -1
- package/dist/schema/types.d.ts +1448 -236
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +26 -0
- package/dist/services/alignment-anchors.d.ts +100 -0
- package/dist/services/alignment-anchors.d.ts.map +1 -0
- package/dist/services/alignment-anchors.js +283 -0
- package/dist/services/alignment-anchors.test.d.ts +2 -0
- package/dist/services/alignment-anchors.test.d.ts.map +1 -0
- package/dist/services/alignment-anchors.test.js +383 -0
- package/dist/services/alignment.d.ts +126 -0
- package/dist/services/alignment.d.ts.map +1 -0
- package/dist/services/alignment.js +169 -0
- package/dist/services/alignment.test.d.ts +2 -0
- package/dist/services/alignment.test.d.ts.map +1 -0
- package/dist/services/alignment.test.js +92 -0
- package/dist/services/drag-session.d.ts +47 -0
- package/dist/services/drag-session.d.ts.map +1 -0
- package/dist/services/drag-session.js +100 -0
- package/dist/services/drag-session.test.d.ts +2 -0
- package/dist/services/drag-session.test.d.ts.map +1 -0
- package/dist/services/drag-session.test.js +219 -0
- package/dist/services/hosting.d.ts +80 -0
- package/dist/services/hosting.d.ts.map +1 -0
- package/dist/services/hosting.js +122 -0
- package/dist/services/hosting.test.d.ts +2 -0
- package/dist/services/hosting.test.d.ts.map +1 -0
- package/dist/services/hosting.test.js +199 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +6 -0
- package/dist/services/movement.d.ts +48 -0
- package/dist/services/movement.d.ts.map +1 -0
- package/dist/services/movement.js +65 -0
- package/dist/services/movement.test.d.ts +2 -0
- package/dist/services/movement.test.d.ts.map +1 -0
- package/dist/services/movement.test.js +133 -0
- package/dist/services/single-undo-dance.test.d.ts +2 -0
- package/dist/services/single-undo-dance.test.d.ts.map +1 -0
- package/dist/services/single-undo-dance.test.js +210 -0
- package/dist/services/snap.d.ts +72 -0
- package/dist/services/snap.d.ts.map +1 -0
- package/dist/services/snap.js +120 -0
- package/dist/services/snap.test.d.ts +2 -0
- package/dist/services/snap.test.d.ts.map +1 -0
- package/dist/services/snap.test.js +95 -0
- package/dist/solar-panel-presets.d.ts +17 -0
- package/dist/solar-panel-presets.d.ts.map +1 -0
- package/dist/solar-panel-presets.js +43 -0
- package/dist/store/actions/node-actions.d.ts +14 -2
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +499 -20
- package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
- package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
- package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
- package/dist/store/actions/reparent.test.d.ts +2 -0
- package/dist/store/actions/reparent.test.d.ts.map +1 -0
- package/dist/store/actions/reparent.test.js +212 -0
- package/dist/store/use-interactive.d.ts +39 -0
- package/dist/store/use-interactive.d.ts.map +1 -1
- package/dist/store/use-interactive.js +75 -0
- package/dist/store/use-live-node-overrides.d.ts +22 -0
- package/dist/store/use-live-node-overrides.d.ts.map +1 -0
- package/dist/store/use-live-node-overrides.js +59 -0
- package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
- package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
- package/dist/store/use-scene-elevator-migration.test.js +109 -0
- package/dist/store/use-scene.d.ts +11 -0
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +207 -11
- package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
- package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-dispatch.js +57 -0
- package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
- package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-geometry.js +50 -0
- package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
- package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-opening-sync.js +218 -0
- package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
- package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
- package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
- package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-opening-system.js +48 -0
- package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
- package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-runtime-system.js +8 -0
- package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
- package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-runtime.js +225 -0
- package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
- package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-runtime.test.js +64 -0
- package/dist/systems/elevator/elevator-service.d.ts +19 -0
- package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-service.js +111 -0
- package/dist/systems/stair/stair-footprint.d.ts +44 -0
- package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
- package/dist/systems/stair/stair-footprint.js +168 -0
- package/dist/systems/stair/stair-opening-preview.d.ts +1777 -0
- package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-preview.js +93 -0
- package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
- package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-preview.test.js +80 -0
- package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-sync.js +183 -185
- package/dist/systems/stair/stair-opening-sync.test.js +385 -4
- package/dist/systems/stair/stair-opening-system.d.ts +2 -0
- package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-system.js +104 -0
- package/dist/systems/wall/wall-curve.d.ts +1 -1
- package/dist/systems/wall/wall-curve.d.ts.map +1 -1
- package/dist/systems/wall/wall-curve.js +1 -1
- package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
- package/dist/systems/wall/wall-mitering.js +2 -6
- package/dist/systems/wall/wall-move.d.ts +48 -0
- package/dist/systems/wall/wall-move.d.ts.map +1 -0
- package/dist/systems/wall/wall-move.js +193 -0
- package/dist/utils/clone-scene-graph.d.ts +6 -4
- package/dist/utils/clone-scene-graph.d.ts.map +1 -1
- package/dist/utils/clone-scene-graph.js +6 -4
- package/dist/utils/clone-scene-graph.test.d.ts +2 -0
- package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
- package/dist/utils/clone-scene-graph.test.js +59 -0
- package/dist/validation/validate-build-json.d.ts +43 -0
- package/dist/validation/validate-build-json.d.ts.map +1 -0
- package/dist/validation/validate-build-json.js +227 -0
- package/package.json +11 -3
- package/dist/materials.d.ts +0 -10
- package/dist/materials.d.ts.map +0 -1
- package/dist/materials.js +0 -22
- package/dist/systems/ceiling/ceiling-system.d.ts +0 -8
- package/dist/systems/ceiling/ceiling-system.d.ts.map +0 -1
- package/dist/systems/ceiling/ceiling-system.js +0 -92
- package/dist/systems/door/door-system.d.ts +0 -2
- package/dist/systems/door/door-system.d.ts.map +0 -1
- package/dist/systems/door/door-system.js +0 -195
- package/dist/systems/fence/fence-system.d.ts +0 -2
- package/dist/systems/fence/fence-system.d.ts.map +0 -1
- package/dist/systems/fence/fence-system.js +0 -187
- package/dist/systems/item/item-system.d.ts +0 -2
- package/dist/systems/item/item-system.d.ts.map +0 -1
- package/dist/systems/item/item-system.js +0 -48
- package/dist/systems/roof/roof-system.d.ts +0 -16
- package/dist/systems/roof/roof-system.d.ts.map +0 -1
- package/dist/systems/roof/roof-system.js +0 -797
- package/dist/systems/slab/slab-system.d.ts +0 -8
- package/dist/systems/slab/slab-system.d.ts.map +0 -1
- package/dist/systems/slab/slab-system.js +0 -214
- package/dist/systems/stair/stair-system.d.ts +0 -2
- package/dist/systems/stair/stair-system.d.ts.map +0 -1
- package/dist/systems/stair/stair-system.js +0 -776
- package/dist/systems/wall/wall-system.d.ts +0 -12
- package/dist/systems/wall/wall-system.d.ts.map +0 -1
- package/dist/systems/wall/wall-system.js +0 -455
- package/dist/systems/window/window-system.d.ts +0 -2
- package/dist/systems/window/window-system.d.ts.map +0 -1
- package/dist/systems/window/window-system.js +0 -131
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
2
|
+
import { isSpaceDetectionPaused, pauseSpaceDetection, resumeSpaceDetection, } from './space-detection';
|
|
3
|
+
// The pause flag is module-level (matching the existing pauseSceneHistory
|
|
4
|
+
// refcount in store/history-control.ts). Reset it before/after each test so
|
|
5
|
+
// leftover depth from one case can't bleed into another.
|
|
6
|
+
function drain() {
|
|
7
|
+
for (let i = 0; i < 64 && isSpaceDetectionPaused(); i += 1) {
|
|
8
|
+
resumeSpaceDetection();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
beforeEach(drain);
|
|
12
|
+
afterEach(drain);
|
|
13
|
+
describe('space-detection pause primitive', () => {
|
|
14
|
+
test('defaults to not paused', () => {
|
|
15
|
+
expect(isSpaceDetectionPaused()).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
test('pauseSpaceDetection flips the flag, resumeSpaceDetection clears it', () => {
|
|
18
|
+
pauseSpaceDetection();
|
|
19
|
+
expect(isSpaceDetectionPaused()).toBe(true);
|
|
20
|
+
resumeSpaceDetection();
|
|
21
|
+
expect(isSpaceDetectionPaused()).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
test('refcount — pause depth survives mismatched resumes from a second source', () => {
|
|
24
|
+
pauseSpaceDetection();
|
|
25
|
+
pauseSpaceDetection();
|
|
26
|
+
expect(isSpaceDetectionPaused()).toBe(true);
|
|
27
|
+
resumeSpaceDetection();
|
|
28
|
+
expect(isSpaceDetectionPaused()).toBe(true);
|
|
29
|
+
resumeSpaceDetection();
|
|
30
|
+
expect(isSpaceDetectionPaused()).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
test('resume is a no-op when not currently paused', () => {
|
|
33
|
+
expect(isSpaceDetectionPaused()).toBe(false);
|
|
34
|
+
resumeSpaceDetection();
|
|
35
|
+
resumeSpaceDetection();
|
|
36
|
+
expect(isSpaceDetectionPaused()).toBe(false);
|
|
37
|
+
pauseSpaceDetection();
|
|
38
|
+
expect(isSpaceDetectionPaused()).toBe(true);
|
|
39
|
+
resumeSpaceDetection();
|
|
40
|
+
expect(isSpaceDetectionPaused()).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type WallNode } from '../schema';
|
|
1
|
+
import { type CeilingNode as CeilingNodeType, type SlabNode as SlabNodeType, type WallNode } from '../schema';
|
|
2
2
|
type Point2D = {
|
|
3
3
|
x: number;
|
|
4
4
|
y: number;
|
|
@@ -15,12 +15,41 @@ type WallSideUpdate = {
|
|
|
15
15
|
frontSide: 'interior' | 'exterior' | 'unknown';
|
|
16
16
|
backSide: 'interior' | 'exterior' | 'unknown';
|
|
17
17
|
};
|
|
18
|
+
export type AutoSlabSyncPlan = {
|
|
19
|
+
create: SlabNodeType[];
|
|
20
|
+
update: Array<{
|
|
21
|
+
id: SlabNodeType['id'];
|
|
22
|
+
data: Partial<SlabNodeType>;
|
|
23
|
+
}>;
|
|
24
|
+
delete: Array<SlabNodeType['id']>;
|
|
25
|
+
};
|
|
26
|
+
export type AutoCeilingSyncPlan = {
|
|
27
|
+
create: CeilingNodeType[];
|
|
28
|
+
update: Array<{
|
|
29
|
+
id: CeilingNodeType['id'];
|
|
30
|
+
data: Partial<CeilingNodeType>;
|
|
31
|
+
}>;
|
|
32
|
+
delete: Array<CeilingNodeType['id']>;
|
|
33
|
+
};
|
|
34
|
+
export type AutoCeilingPlanningContext = {
|
|
35
|
+
walls?: WallNode[];
|
|
36
|
+
slabs?: SlabNodeType[];
|
|
37
|
+
};
|
|
18
38
|
export declare function resolveWallSurfaceSides(wall: Pick<WallNode, 'start' | 'end' | 'thickness' | 'frontSide' | 'backSide'>, roomPolygons: Point2D[][]): Pick<WallSideUpdate, 'frontSide' | 'backSide'>;
|
|
39
|
+
export declare function planAutoSlabsForLevel(roomPolygons: Point2D[][], existingSlabs: SlabNodeType[]): AutoSlabSyncPlan;
|
|
40
|
+
export declare function projectAutoSlabsForPlan(existingSlabs: SlabNodeType[], plan: AutoSlabSyncPlan): SlabNodeType[];
|
|
41
|
+
export declare function planAutoCeilingsForLevel(roomPolygons: Point2D[][], existingCeilings: CeilingNodeType[], context?: AutoCeilingPlanningContext): AutoCeilingSyncPlan;
|
|
19
42
|
export declare function detectSpacesForLevel(levelId: string, walls: WallNode[]): {
|
|
20
43
|
roomPolygons: Point2D[][];
|
|
21
44
|
spaces: Space[];
|
|
22
45
|
wallUpdates: WallSideUpdate[];
|
|
23
46
|
};
|
|
47
|
+
/** Pause the wall-driven auto slab/ceiling sync. Refcounted — pair with `resumeSpaceDetection`. */
|
|
48
|
+
export declare function pauseSpaceDetection(): void;
|
|
49
|
+
/** Resume the wall-driven auto slab/ceiling sync. No-op if not currently paused. */
|
|
50
|
+
export declare function resumeSpaceDetection(): void;
|
|
51
|
+
/** True iff the wall-driven auto slab/ceiling sync is currently paused. */
|
|
52
|
+
export declare function isSpaceDetectionPaused(): boolean;
|
|
24
53
|
export declare function initSpaceDetectionSync(sceneStore: any, editorStore: any): () => void;
|
|
25
54
|
export declare function wallTouchesOthers(wall: WallNode, otherWalls: WallNode[]): boolean;
|
|
26
55
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"space-detection.d.ts","sourceRoot":"","sources":["../../src/lib/space-detection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"space-detection.d.ts","sourceRoot":"","sources":["../../src/lib/space-detection.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,IAAI,eAAe,EAEnC,KAAK,QAAQ,IAAI,YAAY,EAC7B,KAAK,QAAQ,EACd,MAAM,WAAW,CAAA;AAalB,KAAK,OAAO,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvC,MAAM,MAAM,KAAK,GAAG;IAClB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAChC,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,UAAU,EAAE,OAAO,CAAA;CACpB,CAAA;AAED,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,CAAA;IAC9C,QAAQ,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,CAAA;CAC9C,CAAA;AAcD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,YAAY,EAAE,CAAA;IACtB,MAAM,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;KAAE,CAAC,CAAA;IACtE,MAAM,EAAE,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,eAAe,EAAE,CAAA;IACzB,MAAM,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC,eAAe,CAAC,CAAA;KAAE,CAAC,CAAA;IAC5E,MAAM,EAAE,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAA;CACrC,CAAA;AAUD,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;IAClB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAA;CACvB,CAAA;AAkbD,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,KAAK,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,CAAC,EAC9E,YAAY,EAAE,OAAO,EAAE,EAAE,GACxB,IAAI,CAAC,cAAc,EAAE,WAAW,GAAG,UAAU,CAAC,CA4ChD;AAmGD,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,OAAO,EAAE,EAAE,EACzB,aAAa,EAAE,YAAY,EAAE,GAC5B,gBAAgB,CAkIlB;AAyBD,wBAAgB,uBAAuB,CACrC,aAAa,EAAE,YAAY,EAAE,EAC7B,IAAI,EAAE,gBAAgB,GACrB,YAAY,EAAE,CAkBhB;AAED,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,OAAO,EAAE,EAAE,EACzB,gBAAgB,EAAE,eAAe,EAAE,EACnC,OAAO,GAAE,0BAA+B,GACvC,mBAAmB,CAoJrB;AAyCD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE;;;;EAEtE;AA2ED,mGAAmG;AACnG,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED,oFAAoF;AACpF,wBAAgB,oBAAoB,IAAI,IAAI,CAG3C;AAED,2EAA2E;AAC3E,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,GAAG,MAAM,IAAI,CAqDpF;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,OAAO,CAiBjF"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CeilingNode, SlabNode } from '../schema';
|
|
1
|
+
import { CeilingNode, SlabNode, } from '../schema';
|
|
3
2
|
import { getSceneHistoryPauseDepth, pauseSceneHistory, resumeSceneHistory, } from '../store/history-control';
|
|
3
|
+
import { getClampedWallCurveOffset, getWallCurveFrameAt, isCurvedWall, } from '../systems/wall/wall-curve';
|
|
4
4
|
import { simplifyClosedPolygon } from './polygon-geometry';
|
|
5
5
|
const DEFAULT_AUTO_SLAB_ELEVATION = 0.05;
|
|
6
6
|
const DEFAULT_AUTO_CEILING_HEIGHT = 2.5;
|
|
7
|
+
const CEILING_HEIGHT_EPSILON = 1e-6;
|
|
7
8
|
const ROOM_CURVE_TOLERANCE = 0.04;
|
|
8
9
|
const MAX_CURVE_SUBDIVISION_DEPTH = 6;
|
|
9
10
|
const AUTO_SLAB_POLYGON_SIMPLIFY_TOLERANCE = 0.08;
|
|
11
|
+
const WALL_ROOM_BOUNDARY_TOLERANCE = 0.08;
|
|
10
12
|
function pointFromTuple(point) {
|
|
11
13
|
return { x: point[0], y: point[1] };
|
|
12
14
|
}
|
|
@@ -97,10 +99,10 @@ function polygonCentroid(points) {
|
|
|
97
99
|
};
|
|
98
100
|
}
|
|
99
101
|
function bboxOf(points) {
|
|
100
|
-
let minX =
|
|
101
|
-
let minY =
|
|
102
|
-
let maxX =
|
|
103
|
-
let maxY =
|
|
102
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
103
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
104
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
105
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
104
106
|
for (const point of points) {
|
|
105
107
|
minX = Math.min(minX, point.x);
|
|
106
108
|
minY = Math.min(minY, point.y);
|
|
@@ -114,6 +116,80 @@ function bboxOverlapArea(a, b) {
|
|
|
114
116
|
const iy = Math.max(0, Math.min(a.maxY, b.maxY) - Math.max(a.minY, b.minY));
|
|
115
117
|
return ix * iy;
|
|
116
118
|
}
|
|
119
|
+
function pointDistanceToPolygonBoundary(point, polygon) {
|
|
120
|
+
let minDistance = Number.POSITIVE_INFINITY;
|
|
121
|
+
for (let index = 0; index < polygon.length; index += 1) {
|
|
122
|
+
const start = polygon[index];
|
|
123
|
+
const end = polygon[(index + 1) % polygon.length];
|
|
124
|
+
if (!(start && end))
|
|
125
|
+
continue;
|
|
126
|
+
minDistance = Math.min(minDistance, distanceToSegment(pointToTuple(point), pointToTuple(start), pointToTuple(end)));
|
|
127
|
+
}
|
|
128
|
+
return minDistance;
|
|
129
|
+
}
|
|
130
|
+
function wallBoundsRoom(wall, roomPolygon) {
|
|
131
|
+
const sampled = sampleWallPointsForRoomDetection(wall);
|
|
132
|
+
if (sampled.length === 0)
|
|
133
|
+
return false;
|
|
134
|
+
const candidates = sampled.length === 2
|
|
135
|
+
? [
|
|
136
|
+
sampled[0],
|
|
137
|
+
{
|
|
138
|
+
x: (sampled[0].x + sampled[1].x) / 2,
|
|
139
|
+
y: (sampled[0].y + sampled[1].y) / 2,
|
|
140
|
+
},
|
|
141
|
+
sampled[1],
|
|
142
|
+
]
|
|
143
|
+
: sampled;
|
|
144
|
+
const matchingPoints = candidates.filter((point) => pointDistanceToPolygonBoundary(point, roomPolygon) <= WALL_ROOM_BOUNDARY_TOLERANCE);
|
|
145
|
+
return matchingPoints.length >= 2;
|
|
146
|
+
}
|
|
147
|
+
function pointIsOnSlab(point, slab) {
|
|
148
|
+
if (slab.polygon.length < 3)
|
|
149
|
+
return false;
|
|
150
|
+
const slabPolygon = slab.polygon.map(pointFromTuple);
|
|
151
|
+
if (!pointInPolygon(point, slabPolygon))
|
|
152
|
+
return false;
|
|
153
|
+
for (const hole of slab.holes ?? []) {
|
|
154
|
+
if (hole.length >= 3 && pointInPolygon(point, hole.map(pointFromTuple))) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
function slabSupportsRoom(roomPolygon, slab) {
|
|
161
|
+
if (slab.polygon.length < 3)
|
|
162
|
+
return false;
|
|
163
|
+
if (polygonSignature(slab.polygon.map(pointFromTuple)) === polygonSignature(roomPolygon)) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
return pointIsOnSlab(polygonCentroid(roomPolygon), slab);
|
|
167
|
+
}
|
|
168
|
+
function resolveRoomSlabElevation(roomPolygon, slabs = []) {
|
|
169
|
+
let maxElevation = 0;
|
|
170
|
+
for (const slab of slabs) {
|
|
171
|
+
if (!slabSupportsRoom(roomPolygon, slab))
|
|
172
|
+
continue;
|
|
173
|
+
maxElevation = Math.max(maxElevation, slab.elevation ?? DEFAULT_AUTO_SLAB_ELEVATION);
|
|
174
|
+
}
|
|
175
|
+
return maxElevation;
|
|
176
|
+
}
|
|
177
|
+
function resolveRoomWallHeight(roomPolygon, walls = []) {
|
|
178
|
+
let maxHeight = 0;
|
|
179
|
+
for (const wall of walls) {
|
|
180
|
+
if (!wallBoundsRoom(wall, roomPolygon))
|
|
181
|
+
continue;
|
|
182
|
+
const height = wall.height ?? DEFAULT_AUTO_CEILING_HEIGHT;
|
|
183
|
+
if (Number.isFinite(height)) {
|
|
184
|
+
maxHeight = Math.max(maxHeight, height);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return maxHeight > 0 ? maxHeight : DEFAULT_AUTO_CEILING_HEIGHT;
|
|
188
|
+
}
|
|
189
|
+
function resolveAutoCeilingHeight(roomPolygon, context = {}) {
|
|
190
|
+
return (resolveRoomSlabElevation(roomPolygon, context.slabs) +
|
|
191
|
+
resolveRoomWallHeight(roomPolygon, context.walls));
|
|
192
|
+
}
|
|
117
193
|
function getWallDirection(wall) {
|
|
118
194
|
const dx = wall.end[0] - wall.start[0];
|
|
119
195
|
const dy = wall.end[1] - wall.start[1];
|
|
@@ -266,7 +342,7 @@ function extractRoomPolygons(walls) {
|
|
|
266
342
|
const signedArea = polygonArea(polygon);
|
|
267
343
|
if (signedArea <= 0)
|
|
268
344
|
continue;
|
|
269
|
-
if (signedArea < 0.5 || signedArea >
|
|
345
|
+
if (signedArea < 0.5 || signedArea > 10_000)
|
|
270
346
|
continue;
|
|
271
347
|
const signature = polygonSignature(polygon);
|
|
272
348
|
if (faces.some((face) => polygonSignature(face) === signature))
|
|
@@ -340,12 +416,48 @@ function wallGeometrySignature(wall) {
|
|
|
340
416
|
wall.end[0].toFixed(4),
|
|
341
417
|
wall.end[1].toFixed(4),
|
|
342
418
|
(wall.thickness ?? 0.2).toFixed(4),
|
|
419
|
+
(wall.height ?? DEFAULT_AUTO_CEILING_HEIGHT).toFixed(4),
|
|
343
420
|
getClampedWallCurveOffset(wall).toFixed(4),
|
|
344
421
|
].join('|');
|
|
345
422
|
}
|
|
346
423
|
function levelWallSnapshot(walls) {
|
|
347
424
|
return walls.map(wallGeometrySignature).sort().join('||');
|
|
348
425
|
}
|
|
426
|
+
function slabGeometrySignature(slab) {
|
|
427
|
+
const polygon = slab.polygon
|
|
428
|
+
.map((point) => `${point[0].toFixed(4)},${point[1].toFixed(4)}`)
|
|
429
|
+
.join(';');
|
|
430
|
+
const holes = (slab.holes ?? [])
|
|
431
|
+
.map((hole) => hole.map((point) => `${point[0].toFixed(4)},${point[1].toFixed(4)}`).join(';'))
|
|
432
|
+
.join('/');
|
|
433
|
+
return [slab.id, (slab.elevation ?? DEFAULT_AUTO_SLAB_ELEVATION).toFixed(4), polygon, holes].join('|');
|
|
434
|
+
}
|
|
435
|
+
function levelSlabSnapshot(slabs) {
|
|
436
|
+
return slabs.map(slabGeometrySignature).sort().join('||');
|
|
437
|
+
}
|
|
438
|
+
function levelStructureSnapshots(nodes) {
|
|
439
|
+
const byLevel = new Map();
|
|
440
|
+
const getEntry = (levelId) => {
|
|
441
|
+
const entry = byLevel.get(levelId) ?? { walls: [], slabs: [] };
|
|
442
|
+
byLevel.set(levelId, entry);
|
|
443
|
+
return entry;
|
|
444
|
+
};
|
|
445
|
+
for (const node of Object.values(nodes)) {
|
|
446
|
+
if (!(node && typeof node === 'object' && 'parentId' in node && node.parentId))
|
|
447
|
+
continue;
|
|
448
|
+
if (node.type === 'wall') {
|
|
449
|
+
getEntry(node.parentId).walls.push(node);
|
|
450
|
+
}
|
|
451
|
+
else if (node.type === 'slab') {
|
|
452
|
+
getEntry(node.parentId).slabs.push(SlabNode.parse(node));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const snapshots = new Map();
|
|
456
|
+
for (const [levelId, entry] of byLevel.entries()) {
|
|
457
|
+
snapshots.set(levelId, `${levelWallSnapshot(entry.walls)}##${levelSlabSnapshot(entry.slabs)}`);
|
|
458
|
+
}
|
|
459
|
+
return snapshots;
|
|
460
|
+
}
|
|
349
461
|
function buildSpace(levelId, polygon) {
|
|
350
462
|
const signature = polygonSignature(polygon);
|
|
351
463
|
return {
|
|
@@ -356,7 +468,7 @@ function buildSpace(levelId, polygon) {
|
|
|
356
468
|
isExterior: false,
|
|
357
469
|
};
|
|
358
470
|
}
|
|
359
|
-
function
|
|
471
|
+
export function planAutoSlabsForLevel(roomPolygons, existingSlabs) {
|
|
360
472
|
const manualSlabs = existingSlabs.filter((slab) => !slab.autoFromWalls);
|
|
361
473
|
const manualSignatures = new Set(manualSlabs.map((slab) => polygonSignature(slab.polygon.map(pointFromTuple))));
|
|
362
474
|
const detected = roomPolygons
|
|
@@ -459,17 +571,42 @@ function syncAutoSlabsForLevel(levelId, roomPolygons, existingSlabs, sceneStore)
|
|
|
459
571
|
autoFromWalls: true,
|
|
460
572
|
}));
|
|
461
573
|
}
|
|
462
|
-
|
|
463
|
-
|
|
574
|
+
return {
|
|
575
|
+
create: slabsToCreate,
|
|
576
|
+
update: slabsToUpdate,
|
|
577
|
+
delete: slabsToDelete,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function syncAutoSlabsForLevel(levelId, roomPolygons, existingSlabs, sceneStore) {
|
|
581
|
+
const plan = planAutoSlabsForLevel(roomPolygons, existingSlabs);
|
|
582
|
+
if (plan.delete.length > 0) {
|
|
583
|
+
sceneStore.getState().deleteNodes(plan.delete);
|
|
584
|
+
}
|
|
585
|
+
if (plan.update.length > 0) {
|
|
586
|
+
sceneStore.getState().updateNodes(plan.update);
|
|
464
587
|
}
|
|
465
|
-
if (
|
|
466
|
-
sceneStore.getState().
|
|
588
|
+
if (plan.create.length > 0) {
|
|
589
|
+
sceneStore.getState().createNodes(plan.create.map((node) => ({ node, parentId: levelId })));
|
|
467
590
|
}
|
|
468
|
-
|
|
469
|
-
|
|
591
|
+
return plan;
|
|
592
|
+
}
|
|
593
|
+
export function projectAutoSlabsForPlan(existingSlabs, plan) {
|
|
594
|
+
const slabsById = new Map(existingSlabs.map((slab) => [slab.id, slab]));
|
|
595
|
+
for (const id of plan.delete) {
|
|
596
|
+
slabsById.delete(id);
|
|
597
|
+
}
|
|
598
|
+
for (const update of plan.update) {
|
|
599
|
+
const slab = slabsById.get(update.id);
|
|
600
|
+
if (!slab)
|
|
601
|
+
continue;
|
|
602
|
+
slabsById.set(update.id, SlabNode.parse({ ...slab, ...update.data }));
|
|
603
|
+
}
|
|
604
|
+
for (const slab of plan.create) {
|
|
605
|
+
slabsById.set(slab.id, slab);
|
|
470
606
|
}
|
|
607
|
+
return [...slabsById.values()];
|
|
471
608
|
}
|
|
472
|
-
function
|
|
609
|
+
export function planAutoCeilingsForLevel(roomPolygons, existingCeilings, context = {}) {
|
|
473
610
|
const manualCeilings = existingCeilings.filter((ceiling) => !ceiling.autoFromWalls);
|
|
474
611
|
const manualSignatures = new Set(manualCeilings.map((ceiling) => polygonSignature(ceiling.polygon.map(pointFromTuple))));
|
|
475
612
|
const detected = roomPolygons
|
|
@@ -486,6 +623,7 @@ function syncAutoCeilingsForLevel(levelId, roomPolygons, existingCeilings, scene
|
|
|
486
623
|
centroid: polygonCentroid(room.poly),
|
|
487
624
|
area: Math.abs(polygonArea(room.poly)),
|
|
488
625
|
bbox: bboxOf(room.poly),
|
|
626
|
+
ceilingHeight: resolveAutoCeilingHeight(room.poly, context),
|
|
489
627
|
}))
|
|
490
628
|
.filter(({ sig }) => !manualSignatures.has(sig));
|
|
491
629
|
const existingAuto = existingCeilings.filter((ceiling) => ceiling.autoFromWalls);
|
|
@@ -512,7 +650,10 @@ function syncAutoCeilingsForLevel(levelId, roomPolygons, existingCeilings, scene
|
|
|
512
650
|
return;
|
|
513
651
|
matchedDetectedIdx.add(index);
|
|
514
652
|
matchedCeilingIds.add(existing.ceiling.id);
|
|
515
|
-
updatesById.set(existing.ceiling.id,
|
|
653
|
+
updatesById.set(existing.ceiling.id, {
|
|
654
|
+
polygon: room.poly.map(pointToTuple),
|
|
655
|
+
height: room.ceilingHeight,
|
|
656
|
+
});
|
|
516
657
|
});
|
|
517
658
|
const remainingDetected = detected
|
|
518
659
|
.map((room, index) => ({ room, index }))
|
|
@@ -541,7 +682,10 @@ function syncAutoCeilingsForLevel(levelId, roomPolygons, existingCeilings, scene
|
|
|
541
682
|
continue;
|
|
542
683
|
matchedDetectedIdx.add(index);
|
|
543
684
|
matchedCeilingIds.add(bestMatch.entry.ceiling.id);
|
|
544
|
-
updatesById.set(bestMatch.entry.ceiling.id,
|
|
685
|
+
updatesById.set(bestMatch.entry.ceiling.id, {
|
|
686
|
+
polygon: room.poly.map(pointToTuple),
|
|
687
|
+
height: room.ceilingHeight,
|
|
688
|
+
});
|
|
545
689
|
}
|
|
546
690
|
const ceilingsToDelete = existingAuto
|
|
547
691
|
.filter((ceiling) => !updatesById.has(ceiling.id))
|
|
@@ -549,10 +693,18 @@ function syncAutoCeilingsForLevel(levelId, roomPolygons, existingCeilings, scene
|
|
|
549
693
|
const ceilingsToUpdate = existingAuto
|
|
550
694
|
.filter((ceiling) => updatesById.has(ceiling.id))
|
|
551
695
|
.flatMap((ceiling) => {
|
|
552
|
-
const
|
|
553
|
-
if (!
|
|
696
|
+
const update = updatesById.get(ceiling.id);
|
|
697
|
+
if (!update)
|
|
554
698
|
return [];
|
|
555
|
-
|
|
699
|
+
const data = {};
|
|
700
|
+
if (!sameTuplePolygon(ceiling.polygon, update.polygon)) {
|
|
701
|
+
data.polygon = update.polygon;
|
|
702
|
+
}
|
|
703
|
+
if (Math.abs((ceiling.height ?? DEFAULT_AUTO_CEILING_HEIGHT) - update.height) >
|
|
704
|
+
CEILING_HEIGHT_EPSILON) {
|
|
705
|
+
data.height = update.height;
|
|
706
|
+
}
|
|
707
|
+
return Object.keys(data).length === 0 ? [] : [{ id: ceiling.id, data }];
|
|
556
708
|
});
|
|
557
709
|
const plannedCeilingsForNaming = [...existingCeilings];
|
|
558
710
|
const ceilingsToCreate = [];
|
|
@@ -568,18 +720,26 @@ function syncAutoCeilingsForLevel(levelId, roomPolygons, existingCeilings, scene
|
|
|
568
720
|
name,
|
|
569
721
|
polygon: room.poly.map(pointToTuple),
|
|
570
722
|
holes: [],
|
|
571
|
-
height:
|
|
723
|
+
height: room.ceilingHeight,
|
|
572
724
|
autoFromWalls: true,
|
|
573
725
|
}));
|
|
574
726
|
}
|
|
575
|
-
|
|
576
|
-
|
|
727
|
+
return {
|
|
728
|
+
create: ceilingsToCreate,
|
|
729
|
+
update: ceilingsToUpdate,
|
|
730
|
+
delete: ceilingsToDelete,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
function syncAutoCeilingsForLevel(levelId, roomPolygons, existingCeilings, sceneStore, context = {}) {
|
|
734
|
+
const plan = planAutoCeilingsForLevel(roomPolygons, existingCeilings, context);
|
|
735
|
+
if (plan.delete.length > 0) {
|
|
736
|
+
sceneStore.getState().deleteNodes(plan.delete);
|
|
577
737
|
}
|
|
578
|
-
if (
|
|
579
|
-
sceneStore.getState().updateNodes(
|
|
738
|
+
if (plan.update.length > 0) {
|
|
739
|
+
sceneStore.getState().updateNodes(plan.update);
|
|
580
740
|
}
|
|
581
|
-
if (
|
|
582
|
-
sceneStore.getState().createNodes(
|
|
741
|
+
if (plan.create.length > 0) {
|
|
742
|
+
sceneStore.getState().createNodes(plan.create.map((node) => ({ node, parentId: levelId })));
|
|
583
743
|
}
|
|
584
744
|
}
|
|
585
745
|
function detectSpacesFromWalls(levelId, walls) {
|
|
@@ -624,14 +784,36 @@ function runSpaceDetection(levelIds, sceneStore, editorStore, nodes) {
|
|
|
624
784
|
},
|
|
625
785
|
})));
|
|
626
786
|
}
|
|
627
|
-
|
|
628
|
-
|
|
787
|
+
const parsedSlabs = slabs.map((slab) => SlabNode.parse(slab));
|
|
788
|
+
const slabPlan = syncAutoSlabsForLevel(levelId, roomPolygons, parsedSlabs, sceneStore);
|
|
789
|
+
const projectedSlabs = projectAutoSlabsForPlan(parsedSlabs, slabPlan);
|
|
790
|
+
syncAutoCeilingsForLevel(levelId, roomPolygons, ceilings.map((ceiling) => CeilingNode.parse(ceiling)), sceneStore, { walls, slabs: projectedSlabs });
|
|
629
791
|
for (const space of spaces) {
|
|
630
792
|
nextSpaces[space.id] = space;
|
|
631
793
|
}
|
|
632
794
|
}
|
|
633
795
|
editorStore.getState().setSpaces(nextSpaces);
|
|
634
796
|
}
|
|
797
|
+
// Refcount of outstanding pause requests, matching the pauseSceneHistory
|
|
798
|
+
// pattern. The community editor flips this off while the AI is actively
|
|
799
|
+
// mutating the scene so the wall-driven auto slab/ceiling sync doesn't race
|
|
800
|
+
// `create_room`'s explicit slabs/ceilings (see plan
|
|
801
|
+
// `ai-pause-space-detection`).
|
|
802
|
+
let spaceDetectionPauseDepth = 0;
|
|
803
|
+
/** Pause the wall-driven auto slab/ceiling sync. Refcounted — pair with `resumeSpaceDetection`. */
|
|
804
|
+
export function pauseSpaceDetection() {
|
|
805
|
+
spaceDetectionPauseDepth += 1;
|
|
806
|
+
}
|
|
807
|
+
/** Resume the wall-driven auto slab/ceiling sync. No-op if not currently paused. */
|
|
808
|
+
export function resumeSpaceDetection() {
|
|
809
|
+
if (spaceDetectionPauseDepth === 0)
|
|
810
|
+
return;
|
|
811
|
+
spaceDetectionPauseDepth -= 1;
|
|
812
|
+
}
|
|
813
|
+
/** True iff the wall-driven auto slab/ceiling sync is currently paused. */
|
|
814
|
+
export function isSpaceDetectionPaused() {
|
|
815
|
+
return spaceDetectionPauseDepth > 0;
|
|
816
|
+
}
|
|
635
817
|
export function initSpaceDetectionSync(sceneStore, editorStore) {
|
|
636
818
|
const previousSnapshots = new Map();
|
|
637
819
|
let isProcessing = false;
|
|
@@ -641,18 +823,16 @@ export function initSpaceDetectionSync(sceneStore, editorStore) {
|
|
|
641
823
|
if (getSceneHistoryPauseDepth() > 0)
|
|
642
824
|
return;
|
|
643
825
|
const nodes = state.nodes;
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
826
|
+
const currentSnapshots = levelStructureSnapshots(nodes);
|
|
827
|
+
// Paused: roll the snapshot forward so we don't backfill (and re-duplicate)
|
|
828
|
+
// every paused change once detection resumes. Whatever the AI built while
|
|
829
|
+
// paused becomes the new baseline; only future changes will reconcile.
|
|
830
|
+
if (spaceDetectionPauseDepth > 0) {
|
|
831
|
+
previousSnapshots.clear();
|
|
832
|
+
for (const [levelId, snapshot] of currentSnapshots.entries()) {
|
|
833
|
+
previousSnapshots.set(levelId, snapshot);
|
|
651
834
|
}
|
|
652
|
-
|
|
653
|
-
const currentSnapshots = new Map();
|
|
654
|
-
for (const [levelId, walls] of wallsByLevel.entries()) {
|
|
655
|
-
currentSnapshots.set(levelId, levelWallSnapshot(walls));
|
|
835
|
+
return;
|
|
656
836
|
}
|
|
657
837
|
const levelsToUpdate = new Set();
|
|
658
838
|
for (const levelId of new Set([...previousSnapshots.keys(), ...currentSnapshots.keys()])) {
|
|
@@ -675,7 +855,8 @@ export function initSpaceDetectionSync(sceneStore, editorStore) {
|
|
|
675
855
|
finally {
|
|
676
856
|
resumeSceneHistory(sceneStore);
|
|
677
857
|
previousSnapshots.clear();
|
|
678
|
-
|
|
858
|
+
const postRunSnapshots = levelStructureSnapshots(sceneStore.getState().nodes);
|
|
859
|
+
for (const [levelId, snapshot] of postRunSnapshots.entries()) {
|
|
679
860
|
previousSnapshots.set(levelId, snapshot);
|
|
680
861
|
}
|
|
681
862
|
isProcessing = false;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"space-detection.test.d.ts","sourceRoot":"","sources":["../../src/lib/space-detection.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { CeilingNode, SlabNode, WallNode } from '../schema';
|
|
3
|
+
import { planAutoCeilingsForLevel } from './space-detection';
|
|
4
|
+
const square = [
|
|
5
|
+
[0, 0],
|
|
6
|
+
[4, 0],
|
|
7
|
+
[4, 3],
|
|
8
|
+
[0, 3],
|
|
9
|
+
];
|
|
10
|
+
function roomPolygon() {
|
|
11
|
+
return square.map(([x, y]) => ({ x, y }));
|
|
12
|
+
}
|
|
13
|
+
function squareWalls(height = 2.5) {
|
|
14
|
+
return [
|
|
15
|
+
WallNode.parse({ start: [0, 0], end: [4, 0], height }),
|
|
16
|
+
WallNode.parse({ start: [4, 0], end: [4, 3], height }),
|
|
17
|
+
WallNode.parse({ start: [4, 3], end: [0, 3], height }),
|
|
18
|
+
WallNode.parse({ start: [0, 3], end: [0, 0], height }),
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
function slab(elevation) {
|
|
22
|
+
return SlabNode.parse({
|
|
23
|
+
polygon: square,
|
|
24
|
+
elevation,
|
|
25
|
+
autoFromWalls: true,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
describe('planAutoCeilingsForLevel', () => {
|
|
29
|
+
test('creates auto ceilings at the top of the room walls', () => {
|
|
30
|
+
const created = planAutoCeilingsForLevel([roomPolygon()], [], {
|
|
31
|
+
walls: squareWalls(),
|
|
32
|
+
slabs: [slab(0.05)],
|
|
33
|
+
}).create[0];
|
|
34
|
+
expect(created?.height).toBeCloseTo(2.55);
|
|
35
|
+
});
|
|
36
|
+
test('updates existing auto ceiling height when the slab elevation changes', () => {
|
|
37
|
+
const ceiling = CeilingNode.parse({
|
|
38
|
+
polygon: square,
|
|
39
|
+
height: 2.55,
|
|
40
|
+
autoFromWalls: true,
|
|
41
|
+
});
|
|
42
|
+
const plan = planAutoCeilingsForLevel([roomPolygon()], [ceiling], {
|
|
43
|
+
walls: squareWalls(),
|
|
44
|
+
slabs: [slab(0.4)],
|
|
45
|
+
});
|
|
46
|
+
expect(plan.update).toHaveLength(1);
|
|
47
|
+
expect(plan.update[0]?.id).toBe(ceiling.id);
|
|
48
|
+
expect(plan.update[0]?.data.polygon).toBeUndefined();
|
|
49
|
+
expect(plan.update[0]?.data.height).toBeCloseTo(2.9);
|
|
50
|
+
});
|
|
51
|
+
test('updates existing auto ceiling height when wall height changes', () => {
|
|
52
|
+
const ceiling = CeilingNode.parse({
|
|
53
|
+
polygon: square,
|
|
54
|
+
height: 2.55,
|
|
55
|
+
autoFromWalls: true,
|
|
56
|
+
});
|
|
57
|
+
const plan = planAutoCeilingsForLevel([roomPolygon()], [ceiling], {
|
|
58
|
+
walls: squareWalls(3),
|
|
59
|
+
slabs: [slab(0.05)],
|
|
60
|
+
});
|
|
61
|
+
expect(plan.update).toHaveLength(1);
|
|
62
|
+
expect(plan.update[0]?.data.height).toBeCloseTo(3.05);
|
|
63
|
+
});
|
|
64
|
+
test('does not replace a manual ceiling with an auto ceiling', () => {
|
|
65
|
+
const manualCeiling = CeilingNode.parse({
|
|
66
|
+
polygon: square,
|
|
67
|
+
height: 2.5,
|
|
68
|
+
autoFromWalls: false,
|
|
69
|
+
});
|
|
70
|
+
const plan = planAutoCeilingsForLevel([roomPolygon()], [manualCeiling], {
|
|
71
|
+
walls: squareWalls(),
|
|
72
|
+
slabs: [slab(0.4)],
|
|
73
|
+
});
|
|
74
|
+
expect(plan.create).toHaveLength(0);
|
|
75
|
+
expect(plan.update).toHaveLength(0);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -8,7 +8,7 @@ export type MaterialCatalogItem = {
|
|
|
8
8
|
previewColor?: string;
|
|
9
9
|
preset: MaterialPresetPayload;
|
|
10
10
|
};
|
|
11
|
-
export declare const MATERIAL_CATEGORIES: readonly ["wood", "
|
|
11
|
+
export declare const MATERIAL_CATEGORIES: readonly ["wood", "flooring", "roof", "other"];
|
|
12
12
|
export type MaterialCategory = (typeof MATERIAL_CATEGORIES)[number];
|
|
13
13
|
export declare const MATERIAL_CATALOG: MaterialCatalogItem[];
|
|
14
14
|
export declare function getMaterialsForCategory(category: MaterialCategory): MaterialCatalogItem[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"material-library.d.ts","sourceRoot":"","sources":["../src/material-library.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,qBAAqB,
|
|
1
|
+
{"version":3,"file":"material-library.d.ts","sourceRoot":"","sources":["../src/material-library.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,qBAAqB,EAG3B,MAAM,mBAAmB,CAAA;AAE1B,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,gBAAgB,CAAA;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,qBAAqB,CAAA;CAC9B,CAAA;AAuCD,eAAO,MAAM,mBAAmB,gDAAiD,CAAA;AACjF,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEnE,eAAO,MAAM,gBAAgB,EAAE,mBAAmB,EAqzEjD,CAAA;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,mBAAmB,EAAE,CAEzF;AAED,wBAAgB,sBAAsB,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAGnF;AAED,eAAO,MAAM,2BAA2B,aAAa,CAAA;AAErD,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,UAE9C;AAED,wBAAgB,2BAA2B,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,iBAItE;AAED,wBAAgB,sBAAsB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,qBAAqB,GAAG,IAAI,CAIhG"}
|