@pascal-app/core 1.0.0-beta.4 → 1.0.0

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.
Files changed (263) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +23 -0
  3. package/dist/capture/index.d.ts +3 -0
  4. package/dist/capture/index.d.ts.map +1 -0
  5. package/dist/capture/index.js +2 -0
  6. package/dist/capture/schema.d.ts +417 -0
  7. package/dist/capture/schema.d.ts.map +1 -0
  8. package/dist/capture/schema.js +348 -0
  9. package/dist/capture/source.d.ts +60 -0
  10. package/dist/capture/source.d.ts.map +1 -0
  11. package/dist/capture/source.js +254 -0
  12. package/dist/events/bus.d.ts +55 -7
  13. package/dist/events/bus.d.ts.map +1 -1
  14. package/dist/events/hidden-wall-pointer-hold.d.ts +28 -0
  15. package/dist/events/hidden-wall-pointer-hold.d.ts.map +1 -0
  16. package/dist/events/hidden-wall-pointer-hold.js +37 -0
  17. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +12 -0
  18. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  19. package/dist/hooks/spatial-grid/spatial-grid-manager.js +39 -1
  20. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +2785 -1
  21. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  22. package/dist/hooks/spatial-grid/spatial-grid-sync.js +132 -19
  23. package/dist/hooks/spatial-grid/support-host-patch.d.ts +45 -1
  24. package/dist/hooks/spatial-grid/support-host-patch.d.ts.map +1 -1
  25. package/dist/hooks/spatial-grid/support-host-patch.js +171 -6
  26. package/dist/index.d.ts +15 -6
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +17 -5
  29. package/dist/lib/asset-storage.d.ts.map +1 -1
  30. package/dist/lib/asset-storage.js +4 -1
  31. package/dist/lib/polygon-union.d.ts +4 -0
  32. package/dist/lib/polygon-union.d.ts.map +1 -0
  33. package/dist/lib/polygon-union.js +306 -0
  34. package/dist/lib/roof-overlap.d.ts +32 -0
  35. package/dist/lib/roof-overlap.d.ts.map +1 -0
  36. package/dist/lib/roof-overlap.js +72 -0
  37. package/dist/lib/room-topology-index.d.ts +50 -0
  38. package/dist/lib/room-topology-index.d.ts.map +1 -0
  39. package/dist/lib/room-topology-index.js +237 -0
  40. package/dist/lib/slab-polygon.d.ts +35 -2
  41. package/dist/lib/slab-polygon.d.ts.map +1 -1
  42. package/dist/lib/slab-polygon.js +133 -27
  43. package/dist/lib/space-detection.d.ts +40 -3
  44. package/dist/lib/space-detection.d.ts.map +1 -1
  45. package/dist/lib/space-detection.js +890 -246
  46. package/dist/registry/handles.d.ts +24 -3
  47. package/dist/registry/handles.d.ts.map +1 -1
  48. package/dist/registry/index.d.ts +3 -3
  49. package/dist/registry/index.d.ts.map +1 -1
  50. package/dist/registry/index.js +1 -1
  51. package/dist/registry/registry.d.ts +29 -6
  52. package/dist/registry/registry.d.ts.map +1 -1
  53. package/dist/registry/registry.js +111 -4
  54. package/dist/registry/scene-api.d.ts +16 -0
  55. package/dist/registry/scene-api.d.ts.map +1 -1
  56. package/dist/registry/scene-api.js +49 -1
  57. package/dist/registry/types.d.ts +261 -13
  58. package/dist/registry/types.d.ts.map +1 -1
  59. package/dist/registry/use-registry-version.d.ts +11 -0
  60. package/dist/registry/use-registry-version.d.ts.map +1 -0
  61. package/dist/registry/use-registry-version.js +15 -0
  62. package/dist/schema/__bench__/node-parsers.bench.d.ts +24 -0
  63. package/dist/schema/__bench__/node-parsers.bench.d.ts.map +1 -0
  64. package/dist/schema/__bench__/node-parsers.bench.js +314 -0
  65. package/dist/schema/__fixtures__/node-fixtures.d.ts +28 -0
  66. package/dist/schema/__fixtures__/node-fixtures.d.ts.map +1 -0
  67. package/dist/schema/__fixtures__/node-fixtures.js +125 -0
  68. package/dist/schema/base.d.ts +1 -1
  69. package/dist/schema/base.d.ts.map +1 -1
  70. package/dist/schema/base.js +8 -1
  71. package/dist/schema/compiled-node-parsers.d.ts +40 -0
  72. package/dist/schema/compiled-node-parsers.d.ts.map +1 -0
  73. package/dist/schema/compiled-node-parsers.js +95 -0
  74. package/dist/schema/index.d.ts +20 -15
  75. package/dist/schema/index.d.ts.map +1 -1
  76. package/dist/schema/index.js +19 -12
  77. package/dist/schema/material.d.ts +2 -1
  78. package/dist/schema/material.d.ts.map +1 -1
  79. package/dist/schema/material.js +1 -0
  80. package/dist/schema/nodes/automatic-downspout.d.ts +17 -0
  81. package/dist/schema/nodes/automatic-downspout.d.ts.map +1 -0
  82. package/dist/schema/nodes/automatic-downspout.js +237 -0
  83. package/dist/schema/nodes/block.d.ts +107 -0
  84. package/dist/schema/nodes/block.d.ts.map +1 -0
  85. package/dist/schema/nodes/block.js +207 -0
  86. package/dist/schema/nodes/box-vent.d.ts +8 -2
  87. package/dist/schema/nodes/box-vent.d.ts.map +1 -1
  88. package/dist/schema/nodes/box-vent.js +2 -0
  89. package/dist/schema/nodes/building.d.ts +1 -1
  90. package/dist/schema/nodes/cabinet.d.ts +25 -3
  91. package/dist/schema/nodes/cabinet.d.ts.map +1 -1
  92. package/dist/schema/nodes/cabinet.js +26 -5
  93. package/dist/schema/nodes/ceiling.d.ts +1 -1
  94. package/dist/schema/nodes/chimney.d.ts +2 -2
  95. package/dist/schema/nodes/column.d.ts +6 -6
  96. package/dist/schema/nodes/construction-dimension.d.ts +4 -4
  97. package/dist/schema/nodes/cupola.d.ts +9 -1
  98. package/dist/schema/nodes/cupola.d.ts.map +1 -1
  99. package/dist/schema/nodes/cupola.js +2 -0
  100. package/dist/schema/nodes/door.d.ts +1 -1
  101. package/dist/schema/nodes/dormer.d.ts +44 -1
  102. package/dist/schema/nodes/dormer.d.ts.map +1 -1
  103. package/dist/schema/nodes/dormer.js +131 -7
  104. package/dist/schema/nodes/downspout.d.ts +11 -1
  105. package/dist/schema/nodes/downspout.d.ts.map +1 -1
  106. package/dist/schema/nodes/downspout.js +24 -0
  107. package/dist/schema/nodes/duct-fitting.d.ts +18 -1
  108. package/dist/schema/nodes/duct-fitting.d.ts.map +1 -1
  109. package/dist/schema/nodes/duct-fitting.js +25 -4
  110. package/dist/schema/nodes/duct-segment.d.ts +23 -1
  111. package/dist/schema/nodes/duct-segment.d.ts.map +1 -1
  112. package/dist/schema/nodes/duct-segment.js +20 -0
  113. package/dist/schema/nodes/duct-terminal.d.ts +1 -1
  114. package/dist/schema/nodes/elevator.d.ts +1 -1
  115. package/dist/schema/nodes/eyebrow-vent.d.ts +8 -2
  116. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -1
  117. package/dist/schema/nodes/eyebrow-vent.js +2 -0
  118. package/dist/schema/nodes/fence.d.ts +1 -1
  119. package/dist/schema/nodes/guide.d.ts +1 -1
  120. package/dist/schema/nodes/gutter.d.ts +36 -2
  121. package/dist/schema/nodes/gutter.d.ts.map +1 -1
  122. package/dist/schema/nodes/gutter.js +308 -1
  123. package/dist/schema/nodes/hanger-overrides.d.ts +7 -0
  124. package/dist/schema/nodes/hanger-overrides.d.ts.map +1 -0
  125. package/dist/schema/nodes/hanger-overrides.js +6 -0
  126. package/dist/schema/nodes/hvac-equipment.d.ts +1 -1
  127. package/dist/schema/nodes/item.d.ts +4 -3
  128. package/dist/schema/nodes/item.d.ts.map +1 -1
  129. package/dist/schema/nodes/item.js +5 -0
  130. package/dist/schema/nodes/lean-to-extension.d.ts +212 -0
  131. package/dist/schema/nodes/lean-to-extension.d.ts.map +1 -0
  132. package/dist/schema/nodes/lean-to-extension.js +114 -0
  133. package/dist/schema/nodes/level.d.ts +5 -2
  134. package/dist/schema/nodes/level.d.ts.map +1 -1
  135. package/dist/schema/nodes/level.js +9 -0
  136. package/dist/schema/nodes/lineset.d.ts +1 -1
  137. package/dist/schema/nodes/liquid-line.d.ts +1 -1
  138. package/dist/schema/nodes/measurement.d.ts +1 -1
  139. package/dist/schema/nodes/pipe-fitting.d.ts +9 -1
  140. package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -1
  141. package/dist/schema/nodes/pipe-fitting.js +6 -2
  142. package/dist/schema/nodes/pipe-segment.d.ts +23 -1
  143. package/dist/schema/nodes/pipe-segment.d.ts.map +1 -1
  144. package/dist/schema/nodes/pipe-segment.js +20 -0
  145. package/dist/schema/nodes/pipe-trap.d.ts +1 -1
  146. package/dist/schema/nodes/ridge-vent.d.ts +1 -1
  147. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
  148. package/dist/schema/nodes/ridge-vent.js +4 -1
  149. package/dist/schema/nodes/roof-segment-shape.d.ts +4 -0
  150. package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -1
  151. package/dist/schema/nodes/roof-segment-shape.js +64 -1
  152. package/dist/schema/nodes/roof-segment-walls.d.ts +3 -1
  153. package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -1
  154. package/dist/schema/nodes/roof-segment-walls.js +1 -1
  155. package/dist/schema/nodes/roof-segment.d.ts +41 -1
  156. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  157. package/dist/schema/nodes/roof-segment.js +79 -2
  158. package/dist/schema/nodes/roof.d.ts +22 -1
  159. package/dist/schema/nodes/roof.d.ts.map +1 -1
  160. package/dist/schema/nodes/roof.js +14 -0
  161. package/dist/schema/nodes/scan.d.ts +19 -2
  162. package/dist/schema/nodes/scan.d.ts.map +1 -1
  163. package/dist/schema/nodes/scan.js +11 -1
  164. package/dist/schema/nodes/shelf.d.ts +2 -2
  165. package/dist/schema/nodes/site.d.ts +1 -1
  166. package/dist/schema/nodes/skylight.d.ts +1 -1
  167. package/dist/schema/nodes/slab.d.ts +1 -1
  168. package/dist/schema/nodes/solar-panel.d.ts +1 -1
  169. package/dist/schema/nodes/spawn.d.ts +1 -1
  170. package/dist/schema/nodes/stair-segment.d.ts +1 -1
  171. package/dist/schema/nodes/stair.d.ts +1 -1
  172. package/dist/schema/nodes/structural-grid.d.ts +1 -1
  173. package/dist/schema/nodes/turbine-vent.d.ts +7 -1
  174. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -1
  175. package/dist/schema/nodes/turbine-vent.js +2 -0
  176. package/dist/schema/nodes/wall.d.ts +4 -4
  177. package/dist/schema/nodes/wall.d.ts.map +1 -1
  178. package/dist/schema/nodes/wall.js +7 -1
  179. package/dist/schema/nodes/window.d.ts +10 -3
  180. package/dist/schema/nodes/window.d.ts.map +1 -1
  181. package/dist/schema/nodes/window.js +4 -0
  182. package/dist/schema/nodes/zone.d.ts +2 -2
  183. package/dist/schema/nodes/zone.js +1 -1
  184. package/dist/schema/types.d.ts +485 -109
  185. package/dist/schema/types.d.ts.map +1 -1
  186. package/dist/schema/types.js +27 -1
  187. package/dist/services/hosting.d.ts +2 -2
  188. package/dist/services/hosting.d.ts.map +1 -1
  189. package/dist/services/hosting.js +4 -4
  190. package/dist/services/index.d.ts +2 -2
  191. package/dist/services/index.d.ts.map +1 -1
  192. package/dist/services/index.js +2 -2
  193. package/dist/services/storey.d.ts +3 -10
  194. package/dist/services/storey.d.ts.map +1 -1
  195. package/dist/services/storey.js +45 -15
  196. package/dist/services/system-graph.d.ts +11 -0
  197. package/dist/services/system-graph.d.ts.map +1 -1
  198. package/dist/services/system-graph.js +35 -5
  199. package/dist/store/actions/node-actions.d.ts +15 -4
  200. package/dist/store/actions/node-actions.d.ts.map +1 -1
  201. package/dist/store/actions/node-actions.js +458 -19
  202. package/dist/store/history-control.d.ts +5 -0
  203. package/dist/store/history-control.d.ts.map +1 -1
  204. package/dist/store/history-control.js +68 -6
  205. package/dist/store/history-invalidation.d.ts +3 -0
  206. package/dist/store/history-invalidation.d.ts.map +1 -0
  207. package/dist/store/history-invalidation.js +72 -0
  208. package/dist/store/scene-hydration.d.ts +5 -0
  209. package/dist/store/scene-hydration.d.ts.map +1 -0
  210. package/dist/store/scene-hydration.js +57 -0
  211. package/dist/store/use-scene.d.ts +3 -0
  212. package/dist/store/use-scene.d.ts.map +1 -1
  213. package/dist/store/use-scene.js +407 -227
  214. package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
  215. package/dist/systems/elevator/elevator-service.js +16 -11
  216. package/dist/systems/roof/roof-elevation-system.d.ts +3 -0
  217. package/dist/systems/roof/roof-elevation-system.d.ts.map +1 -0
  218. package/dist/systems/roof/roof-elevation-system.js +94 -0
  219. package/dist/systems/roof/roof-elevation.d.ts +4 -0
  220. package/dist/systems/roof/roof-elevation.d.ts.map +1 -0
  221. package/dist/systems/roof/roof-elevation.js +95 -0
  222. package/dist/systems/roof/roof-footprint.d.ts +17 -0
  223. package/dist/systems/roof/roof-footprint.d.ts.map +1 -0
  224. package/dist/systems/roof/roof-footprint.js +83 -0
  225. package/dist/systems/slab/slab-support.d.ts +0 -31
  226. package/dist/systems/slab/slab-support.d.ts.map +1 -1
  227. package/dist/systems/slab/slab-support.js +26 -4
  228. package/dist/systems/stair/stair-flight.d.ts +17 -0
  229. package/dist/systems/stair/stair-flight.d.ts.map +1 -0
  230. package/dist/systems/stair/stair-flight.js +27 -0
  231. package/dist/systems/stair/stair-opening-preview.d.ts +1556 -1328
  232. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
  233. package/dist/systems/stair/stair-opening-system.d.ts +1 -0
  234. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
  235. package/dist/systems/stair/stair-opening-system.js +93 -96
  236. package/dist/systems/stair/stair-rise.js +21 -20
  237. package/dist/systems/wall/wall-curve.d.ts +1 -0
  238. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  239. package/dist/systems/wall/wall-curve.js +73 -0
  240. package/dist/systems/wall/wall-footprint.d.ts +1 -0
  241. package/dist/systems/wall/wall-footprint.d.ts.map +1 -1
  242. package/dist/systems/wall/wall-footprint.js +1 -0
  243. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  244. package/dist/systems/wall/wall-mitering.js +88 -9
  245. package/dist/systems/wall/wall-topology.d.ts +53 -0
  246. package/dist/systems/wall/wall-topology.d.ts.map +1 -0
  247. package/dist/systems/wall/wall-topology.js +414 -0
  248. package/dist/utils/clone-scene-graph.d.ts +2 -0
  249. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  250. package/dist/utils/clone-scene-graph.js +39 -2
  251. package/dist/utils/scene-migrations.d.ts +1 -0
  252. package/dist/utils/scene-migrations.d.ts.map +1 -1
  253. package/dist/utils/scene-migrations.js +1 -0
  254. package/dist/utils/vertical-scene-migration.d.ts +13 -0
  255. package/dist/utils/vertical-scene-migration.d.ts.map +1 -0
  256. package/dist/utils/vertical-scene-migration.js +179 -0
  257. package/dist/validation/validate-build-json.d.ts +3 -0
  258. package/dist/validation/validate-build-json.d.ts.map +1 -1
  259. package/dist/validation/validate-build-json.js +49 -2
  260. package/package.json +10 -3
  261. package/dist/systems/elevator/elevator-runtime-system.d.ts +0 -2
  262. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +0 -1
  263. package/dist/systems/elevator/elevator-runtime-system.js +0 -8
@@ -0,0 +1,237 @@
1
+ import { getClampedWallCurveOffset } from '../systems/wall/wall-curve';
2
+ const CELL_SIZE = 2;
3
+ function bboxOf(points) {
4
+ let minX = Number.POSITIVE_INFINITY;
5
+ let minY = Number.POSITIVE_INFINITY;
6
+ let maxX = Number.NEGATIVE_INFINITY;
7
+ let maxY = Number.NEGATIVE_INFINITY;
8
+ for (const [x, y] of points) {
9
+ minX = Math.min(minX, x);
10
+ minY = Math.min(minY, y);
11
+ maxX = Math.max(maxX, x);
12
+ maxY = Math.max(maxY, y);
13
+ }
14
+ return { minX, minY, maxX, maxY };
15
+ }
16
+ function expandedBbox(box, margin) {
17
+ return {
18
+ minX: box.minX - margin,
19
+ minY: box.minY - margin,
20
+ maxX: box.maxX + margin,
21
+ maxY: box.maxY + margin,
22
+ };
23
+ }
24
+ function cellKeysForBbox(box) {
25
+ const keys = [];
26
+ for (let x = Math.floor(box.minX / CELL_SIZE); x <= Math.floor(box.maxX / CELL_SIZE); x += 1) {
27
+ for (let y = Math.floor(box.minY / CELL_SIZE); y <= Math.floor(box.maxY / CELL_SIZE); y += 1) {
28
+ keys.push(`${x},${y}`);
29
+ }
30
+ }
31
+ return keys;
32
+ }
33
+ export function distanceToSegment(point, segStart, segEnd) {
34
+ const [px, py] = point;
35
+ const [x1, y1] = segStart;
36
+ const [x2, y2] = segEnd;
37
+ const dx = x2 - x1;
38
+ const dy = y2 - y1;
39
+ const lenSq = dx * dx + dy * dy;
40
+ if (lenSq < 0.0001)
41
+ return Math.hypot(px - x1, py - y1);
42
+ const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
43
+ return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
44
+ }
45
+ function roomUsesAnyWall(room, wallIds) {
46
+ return room.boundaryFaces.some((boundary) => wallIds.has(boundary.wallId));
47
+ }
48
+ function sameIndexedWall(left, right) {
49
+ if (!(left && right))
50
+ return left === right;
51
+ return (left.parentId === right.parentId &&
52
+ left.start[0] === right.start[0] &&
53
+ left.start[1] === right.start[1] &&
54
+ left.end[0] === right.end[0] &&
55
+ left.end[1] === right.end[1] &&
56
+ getClampedWallCurveOffset(left) === getClampedWallCurveOffset(right));
57
+ }
58
+ export class RoomTopologyIndex {
59
+ options;
60
+ levels = new Map();
61
+ queryMargin;
62
+ constructor(options) {
63
+ this.options = options;
64
+ this.queryMargin = options.junctionTolerance + 0.02;
65
+ }
66
+ rebuild(nodes) {
67
+ this.levels.clear();
68
+ const wallsByLevel = new Map();
69
+ for (const node of Object.values(nodes)) {
70
+ if (node?.type !== 'wall' || !node.parentId)
71
+ continue;
72
+ const walls = wallsByLevel.get(node.parentId) ?? [];
73
+ walls.push(node);
74
+ wallsByLevel.set(node.parentId, walls);
75
+ }
76
+ for (const [levelId, walls] of wallsByLevel) {
77
+ this.levels.set(levelId, this.createLevel(walls));
78
+ }
79
+ }
80
+ rebuildLevel(levelId, nodes) {
81
+ const level = this.createLevel(this.wallsForLevel(nodes, levelId));
82
+ this.levels.set(levelId, level);
83
+ return level;
84
+ }
85
+ applyWallDelta(levelId, changedWallIds, beforeNodes, currentNodes) {
86
+ let strategy = 'indexed';
87
+ let level = this.levels.get(levelId);
88
+ if (!level) {
89
+ level = this.rebuildLevel(levelId, beforeNodes);
90
+ strategy = 'fallback';
91
+ }
92
+ for (const wallId of changedWallIds) {
93
+ const cached = level.walls.get(wallId);
94
+ const previous = beforeNodes[wallId];
95
+ const previousWall = previous?.type === 'wall' && previous.parentId === levelId ? previous : undefined;
96
+ if (!sameIndexedWall(cached, previousWall)) {
97
+ level = this.rebuildLevel(levelId, beforeNodes);
98
+ strategy = 'fallback';
99
+ break;
100
+ }
101
+ }
102
+ const beforeComponentIds = this.connectedWallIds(level, changedWallIds);
103
+ for (const wallId of changedWallIds) {
104
+ const current = currentNodes[wallId];
105
+ if (current?.type === 'wall' && current.parentId === levelId) {
106
+ this.setWall(level, current);
107
+ }
108
+ else {
109
+ this.removeWall(level, wallId);
110
+ }
111
+ }
112
+ const currentSeedIds = new Set(changedWallIds);
113
+ for (const wallId of beforeComponentIds) {
114
+ if (level.walls.has(wallId))
115
+ currentSeedIds.add(wallId);
116
+ }
117
+ const currentComponentIds = this.connectedWallIds(level, currentSeedIds);
118
+ const examinedIds = new Set([...changedWallIds, ...beforeComponentIds, ...currentComponentIds]);
119
+ const beforeRooms = level.rooms.filter((room) => roomUsesAnyWall(room, examinedIds));
120
+ const previousWalls = this.wallsFromNodes(beforeNodes, levelId, examinedIds);
121
+ const currentWalls = this.wallsFromNodes(currentNodes, levelId, examinedIds);
122
+ const currentRooms = this.options.detectRooms(currentWalls);
123
+ const allCurrentRooms = [
124
+ ...level.rooms.filter((room) => !roomUsesAnyWall(room, examinedIds)),
125
+ ...currentRooms,
126
+ ];
127
+ level.rooms = allCurrentRooms;
128
+ return {
129
+ strategy,
130
+ beforeRooms,
131
+ currentRooms,
132
+ allCurrentRooms,
133
+ previousWalls,
134
+ currentWalls,
135
+ examinedWallIds: [...examinedIds].sort(),
136
+ };
137
+ }
138
+ wallsForLevel(nodes, levelId) {
139
+ const level = nodes[levelId];
140
+ if (level?.type !== 'level')
141
+ return [];
142
+ return level.children.flatMap((id) => {
143
+ const node = nodes[id];
144
+ return node?.type === 'wall' && node.parentId === levelId ? [node] : [];
145
+ });
146
+ }
147
+ wallsFromNodes(nodes, levelId, ids) {
148
+ return [...ids].flatMap((wallId) => {
149
+ const node = nodes[wallId];
150
+ return node?.type === 'wall' && node.parentId === levelId ? [node] : [];
151
+ });
152
+ }
153
+ createLevel(walls) {
154
+ const level = {
155
+ walls: new Map(),
156
+ rooms: this.options.detectRooms(walls),
157
+ wallIdsByCell: new Map(),
158
+ cellKeysByWallId: new Map(),
159
+ };
160
+ for (const wall of walls)
161
+ this.setWall(level, wall);
162
+ return level;
163
+ }
164
+ wallBbox(wall) {
165
+ return bboxOf(this.options.sampleWall(wall));
166
+ }
167
+ cellKeysForWall(wall) {
168
+ return cellKeysForBbox(expandedBbox(this.wallBbox(wall), this.queryMargin));
169
+ }
170
+ removeWall(level, wallId) {
171
+ for (const key of level.cellKeysByWallId.get(wallId) ?? []) {
172
+ const ids = level.wallIdsByCell.get(key);
173
+ ids?.delete(wallId);
174
+ if (ids?.size === 0)
175
+ level.wallIdsByCell.delete(key);
176
+ }
177
+ level.cellKeysByWallId.delete(wallId);
178
+ level.walls.delete(wallId);
179
+ }
180
+ setWall(level, wall) {
181
+ this.removeWall(level, wall.id);
182
+ level.walls.set(wall.id, wall);
183
+ const keys = this.cellKeysForWall(wall);
184
+ level.cellKeysByWallId.set(wall.id, keys);
185
+ for (const key of keys) {
186
+ const ids = level.wallIdsByCell.get(key) ?? new Set();
187
+ ids.add(wall.id);
188
+ level.wallIdsByCell.set(key, ids);
189
+ }
190
+ }
191
+ queryWalls(level, wall) {
192
+ const ids = new Set();
193
+ for (const key of this.cellKeysForWall(wall)) {
194
+ for (const id of level.wallIdsByCell.get(key) ?? [])
195
+ ids.add(id);
196
+ }
197
+ return ids;
198
+ }
199
+ wallsTouch(left, right) {
200
+ const leftPoints = this.options.sampleWall(left);
201
+ const rightPoints = this.options.sampleWall(right);
202
+ const touchesPolyline = (points, other) => {
203
+ const endpoints = [points[0], points.at(-1)].filter((point) => Boolean(point));
204
+ for (const endpoint of endpoints) {
205
+ for (let index = 0; index < other.length - 1; index += 1) {
206
+ if (distanceToSegment(endpoint, other[index], other[index + 1]) <=
207
+ this.options.junctionTolerance) {
208
+ return true;
209
+ }
210
+ }
211
+ }
212
+ return false;
213
+ };
214
+ return touchesPolyline(leftPoints, rightPoints) || touchesPolyline(rightPoints, leftPoints);
215
+ }
216
+ connectedWallIds(level, seedIds) {
217
+ const connected = new Set();
218
+ const queue = [...seedIds];
219
+ while (queue.length > 0) {
220
+ const wallId = queue.pop();
221
+ if (connected.has(wallId))
222
+ continue;
223
+ const wall = level.walls.get(wallId);
224
+ if (!wall)
225
+ continue;
226
+ connected.add(wallId);
227
+ for (const neighborId of this.queryWalls(level, wall)) {
228
+ if (connected.has(neighborId))
229
+ continue;
230
+ const neighbor = level.walls.get(neighborId);
231
+ if (neighbor && this.wallsTouch(wall, neighbor))
232
+ queue.push(neighborId);
233
+ }
234
+ }
235
+ return connected;
236
+ }
237
+ }
@@ -1,18 +1,36 @@
1
1
  import type { GeometryContext } from '../registry/types';
2
- import type { SlabNode, WallNode } from '../schema';
2
+ import type { AnyNode, AnyNodeId, SlabNode, WallNode } from '../schema';
3
3
  export type SlabPolygonContext = {
4
4
  /** Walls on the slab's level. */
5
5
  walls: WallNode[];
6
6
  /** Other slabs on the same level — the slab itself must be excluded. */
7
7
  siblingSlabs: SlabNode[];
8
8
  };
9
+ /** [ax, az, bx, bz] segment in plan space. */
10
+ type Segment = [number, number, number, number];
11
+ /** A sibling slab edge and the direction of its polygon interior. */
12
+ type NeighborSegment = {
13
+ slab: SlabNode;
14
+ segment: Segment;
15
+ bounds: Bounds;
16
+ elevation: number;
17
+ /** Unit normal pointing into the sibling polygon at this edge. */
18
+ inwardX: number;
19
+ inwardZ: number;
20
+ };
9
21
  /**
10
22
  * Derive a {@link SlabPolygonContext} from a registry `GeometryContext`:
11
23
  * sibling slabs come from `ctx.siblings` (same kind, same parent, self
12
24
  * excluded) and walls from the parent level's children.
13
25
  */
14
26
  export declare function slabPolygonContextFromGeometry(ctx: GeometryContext | undefined): SlabPolygonContext;
15
- export declare function getRenderableSlabPolygon(slabNode: SlabNode, context: SlabPolygonContext): Array<[number, number]>;
27
+ export declare function slabPolygonContextForLevel(parent: AnyNode | null, resolve: (id: AnyNodeId) => AnyNode | undefined, fallbackSlabs?: SlabNode[]): SlabPolygonContext;
28
+ export declare function getRenderableSlabPolygon(slabNode: SlabNode, context: SlabPolygonContext | PreparedSlabPolygonContext): Array<[number, number]>;
29
+ type WallCandidate = {
30
+ wall: WallNode;
31
+ segments: Segment[];
32
+ halfThickness: number;
33
+ };
16
34
  export type SlabEdgeWallBandSnap = {
17
35
  wallId: WallNode['id'];
18
36
  /** The candidate edge translated perpendicular onto the wall centerline. */
@@ -29,4 +47,19 @@ export type SlabEdgeWallBandSnap = {
29
47
  export declare function snapSlabEdgeToWallBand(a: [number, number], b: [number, number], walls: readonly WallNode[], options?: {
30
48
  maxLateral?: number;
31
49
  }): SlabEdgeWallBandSnap | null;
50
+ type Bounds = [number, number, number, number];
51
+ export declare function scopeSlabPolygonContext(slab: SlabNode, context: PreparedSlabPolygonContext): PreparedSlabPolygonContext;
52
+ export declare function slabPolygonContextChanges(before: PreparedSlabPolygonContext, after: PreparedSlabPolygonContext): (slab: SlabNode) => boolean;
53
+ type PreparedWallCandidate = WallCandidate & {
54
+ bounds: Bounds;
55
+ };
56
+ type PreparedSlabPolygonContext = SlabPolygonContext & {
57
+ wallCache: WeakMap<WallNode, PreparedWallCandidate>;
58
+ neighborCache: WeakMap<SlabNode, NeighborSegment[]>;
59
+ wallCandidates: PreparedWallCandidate[];
60
+ neighborSegments: NeighborSegment[];
61
+ siblingBreakTolerance: number;
62
+ };
63
+ export declare function prepareSlabPolygonContext(context: SlabPolygonContext, previous?: PreparedSlabPolygonContext): PreparedSlabPolygonContext;
64
+ export {};
32
65
  //# sourceMappingURL=slab-polygon.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"slab-polygon.d.ts","sourceRoot":"","sources":["../../src/lib/slab-polygon.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,KAAK,EAAa,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAwG9D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iCAAiC;IACjC,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,wEAAwE;IACxE,YAAY,EAAE,QAAQ,EAAE,CAAA;CACzB,CAAA;AAuBD;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,eAAe,GAAG,SAAS,GAC/B,kBAAkB,CAmBpB;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,kBAAkB,GAC1B,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAgBzB;AAuID,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;IACtB,4EAA4E;IAC5E,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAC3C,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EACnB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EACnB,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,OAAO,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAChC,oBAAoB,GAAG,IAAI,CA4B7B"}
1
+ {"version":3,"file":"slab-polygon.d.ts","sourceRoot":"","sources":["../../src/lib/slab-polygon.ts"],"names":[],"mappings":"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"}
@@ -114,16 +114,24 @@ function polygonWindingSign(polygon) {
114
114
  export function slabPolygonContextFromGeometry(ctx) {
115
115
  if (!ctx)
116
116
  return { walls: [], siblingSlabs: [] };
117
- const siblingSlabs = ctx.siblings.filter((node) => node.type === 'slab');
117
+ return {
118
+ ...slabPolygonContextForLevel(ctx.parent, ctx.resolve),
119
+ siblingSlabs: ctx.siblings.filter((node) => node.type === 'slab'),
120
+ };
121
+ }
122
+ export function slabPolygonContextForLevel(parent, resolve, fallbackSlabs = []) {
118
123
  const walls = [];
119
- const parentChildIds = ctx.parent?.children;
120
- if (Array.isArray(parentChildIds)) {
121
- for (const childId of parentChildIds) {
122
- const child = ctx.resolve(childId);
123
- if (child?.type === 'wall') {
124
- walls.push(child);
125
- }
126
- }
124
+ const siblingSlabs = [];
125
+ const childIds = parent?.children;
126
+ if (!Array.isArray(childIds)) {
127
+ return { walls, siblingSlabs: parent ? fallbackSlabs : [] };
128
+ }
129
+ for (const id of childIds) {
130
+ const child = resolve(id);
131
+ if (child?.type === 'wall')
132
+ walls.push(child);
133
+ else if (child?.type === 'slab')
134
+ siblingSlabs.push(child);
127
135
  }
128
136
  return { walls, siblingSlabs };
129
137
  }
@@ -132,7 +140,7 @@ export function getRenderableSlabPolygon(slabNode, context) {
132
140
  if (polygon.length < 3 || isFloatingSlab(slabNode)) {
133
141
  return polygon.map(([x, z]) => [x, z]);
134
142
  }
135
- const subSpans = computeEdgeSubSpans(polygon, slabNode.elevation ?? DEFAULT_SLAB_ELEVATION, context);
143
+ const subSpans = computeEdgeSubSpans(polygon, slabNode.elevation ?? DEFAULT_SLAB_ELEVATION, 'wallCandidates' in context ? context : prepareSlabPolygonContext(context), slabNode.id);
136
144
  if (subSpans.every((spans) => spans.length === 1 && spans[0].offset === 0)) {
137
145
  return polygon.map(([x, z]) => [x, z]);
138
146
  }
@@ -254,17 +262,71 @@ export function snapSlabEdgeToWallBand(a, b, walls, options) {
254
262
  ],
255
263
  };
256
264
  }
257
- /**
258
- * Split every polygon edge at candidate span boundaries and classify
259
- * each sub-span independently. Returns one non-empty span list per
260
- * edge, covering [0, edgeLength] without gaps.
261
- */
262
- function computeEdgeSubSpans(polygon, selfElevation, context) {
263
- const n = polygon.length;
264
- // Winding sign: the outward normal of an edge with direction `dir` is
265
- // `s * (dirZ, -dirX)`, so a target lateral `L` measured along
266
- // `(dirZ, -dirX)` is `s * L` along the outward normal.
267
- const s = polygonWindingSign(polygon);
265
+ function polygonBounds(polygon) {
266
+ const bounds = [Infinity, Infinity, -Infinity, -Infinity];
267
+ for (const [x, z] of polygon) {
268
+ bounds[0] = Math.min(bounds[0], x);
269
+ bounds[1] = Math.min(bounds[1], z);
270
+ bounds[2] = Math.max(bounds[2], x);
271
+ bounds[3] = Math.max(bounds[3], z);
272
+ }
273
+ return bounds;
274
+ }
275
+ function boundsOverlap(a, b, padding) {
276
+ return (a[0] <= b[2] + padding &&
277
+ a[2] >= b[0] - padding &&
278
+ a[1] <= b[3] + padding &&
279
+ a[3] >= b[1] - padding);
280
+ }
281
+ export function scopeSlabPolygonContext(slab, context) {
282
+ const bounds = polygonBounds(slab.polygon);
283
+ const wallCandidates = context.wallCandidates.filter((candidate) => boundsOverlap(bounds, candidate.bounds, candidate.halfThickness + WALL_ADOPTION_TOLERANCE));
284
+ // Keep the level-wide tolerance: even an unadopted wall can widen sibling
285
+ // breakpoint collection, which may affect short-span fusion.
286
+ const neighborSegments = context.neighborSegments.filter((neighbor) => neighbor.slab.id !== slab.id &&
287
+ boundsOverlap(bounds, neighbor.bounds, context.siblingBreakTolerance));
288
+ return {
289
+ ...context,
290
+ walls: wallCandidates.map((candidate) => candidate.wall),
291
+ siblingSlabs: [...new Set(neighborSegments.map((neighbor) => neighbor.slab))],
292
+ wallCandidates,
293
+ neighborSegments,
294
+ siblingBreakTolerance: context.siblingBreakTolerance,
295
+ };
296
+ }
297
+ export function slabPolygonContextChanges(before, after) {
298
+ if (before.siblingBreakTolerance !== after.siblingBreakTolerance)
299
+ return () => true;
300
+ const changedWalls = new Set();
301
+ const changedSlabs = new Set();
302
+ for (let i = 0; i < Math.max(before.walls.length, after.walls.length); i++) {
303
+ if (before.walls[i] === after.walls[i])
304
+ continue;
305
+ if (before.walls[i])
306
+ changedWalls.add(before.walls[i]);
307
+ if (after.walls[i])
308
+ changedWalls.add(after.walls[i]);
309
+ }
310
+ for (let i = 0; i < Math.max(before.siblingSlabs.length, after.siblingSlabs.length); i++) {
311
+ if (before.siblingSlabs[i] === after.siblingSlabs[i])
312
+ continue;
313
+ if (before.siblingSlabs[i])
314
+ changedSlabs.add(before.siblingSlabs[i]);
315
+ if (after.siblingSlabs[i])
316
+ changedSlabs.add(after.siblingSlabs[i]);
317
+ }
318
+ const walls = [...before.wallCandidates, ...after.wallCandidates].filter((candidate) => changedWalls.has(candidate.wall));
319
+ const neighbors = [...before.neighborSegments, ...after.neighborSegments].filter((neighbor) => changedSlabs.has(neighbor.slab));
320
+ return (slab) => {
321
+ const bounds = polygonBounds(slab.polygon);
322
+ return (walls.some((candidate) => boundsOverlap(bounds, candidate.bounds, candidate.halfThickness + WALL_ADOPTION_TOLERANCE)) ||
323
+ neighbors.some((neighbor) => neighbor.slab.id !== slab.id &&
324
+ boundsOverlap(bounds, neighbor.bounds, after.siblingBreakTolerance)));
325
+ };
326
+ }
327
+ export function prepareSlabPolygonContext(context, previous) {
328
+ const wallCache = previous?.wallCache ?? new WeakMap();
329
+ const neighborCache = previous?.neighborCache ?? new WeakMap();
268
330
  const neighborSegments = [];
269
331
  for (const sibling of context.siblingSlabs) {
270
332
  // A floating deck keeps its drawn polygon, so it can't be a seam
@@ -273,6 +335,12 @@ function computeEdgeSubSpans(polygon, selfElevation, context) {
273
335
  // only describe room floors meeting under a wall.
274
336
  if (isFloatingSlab(sibling))
275
337
  continue;
338
+ const cached = neighborCache.get(sibling);
339
+ if (cached) {
340
+ neighborSegments.push(...cached);
341
+ continue;
342
+ }
343
+ const segments = [];
276
344
  const siblingPolygon = sibling.polygon;
277
345
  if (siblingPolygon.length < 2)
278
346
  continue;
@@ -286,19 +354,35 @@ function computeEdgeSubSpans(polygon, selfElevation, context) {
286
354
  const length = Math.hypot(dx, dz);
287
355
  if (length < 1e-9)
288
356
  continue;
289
- neighborSegments.push({
357
+ segments.push({
358
+ slab: sibling,
359
+ bounds: polygonBounds([from, to]),
290
360
  segment: [from[0], from[1], to[0], to[1]],
291
361
  elevation,
292
362
  inwardX: (-siblingWinding * dz) / length,
293
363
  inwardZ: (siblingWinding * dx) / length,
294
364
  });
295
365
  }
366
+ neighborCache.set(sibling, segments);
367
+ neighborSegments.push(...segments);
296
368
  }
297
- const wallCandidates = context.walls.map((wall) => ({
298
- wall,
299
- segments: wallCenterlineSegments(wall),
300
- halfThickness: getWallThickness(wall) / 2,
301
- }));
369
+ const wallCandidates = context.walls.map((wall) => {
370
+ const cached = wallCache.get(wall);
371
+ if (cached)
372
+ return cached;
373
+ const segments = wallCenterlineSegments(wall);
374
+ const candidate = {
375
+ wall,
376
+ segments,
377
+ halfThickness: getWallThickness(wall) / 2,
378
+ bounds: polygonBounds(segments.flatMap(([ax, az, bx, bz]) => [
379
+ [ax, az],
380
+ [bx, bz],
381
+ ])),
382
+ };
383
+ wallCache.set(wall, candidate);
384
+ return candidate;
385
+ });
302
386
  // Sibling breakpoints also matter for legacy face-aligned polygons up
303
387
  // to a full wall band away from the edge (the band-sibling interior
304
388
  // rule in classifySpan), so clip them with the widest band reach any
@@ -308,6 +392,28 @@ function computeEdgeSubSpans(polygon, selfElevation, context) {
308
392
  for (const candidate of wallCandidates) {
309
393
  siblingBreakTolerance = Math.max(siblingBreakTolerance, 2 * (candidate.halfThickness + WALL_ADOPTION_TOLERANCE));
310
394
  }
395
+ return {
396
+ ...context,
397
+ wallCache,
398
+ neighborCache,
399
+ wallCandidates,
400
+ neighborSegments,
401
+ siblingBreakTolerance,
402
+ };
403
+ }
404
+ /**
405
+ * Split every polygon edge at candidate span boundaries and classify
406
+ * each sub-span independently. Returns one non-empty span list per
407
+ * edge, covering [0, edgeLength] without gaps.
408
+ */
409
+ function computeEdgeSubSpans(polygon, selfElevation, context, slabId) {
410
+ const n = polygon.length;
411
+ // Winding sign: the outward normal of an edge with direction `dir` is
412
+ // `s * (dirZ, -dirX)`, so a target lateral `L` measured along
413
+ // `(dirZ, -dirX)` is `s * L` along the outward normal.
414
+ const s = polygonWindingSign(polygon);
415
+ const { wallCandidates, siblingBreakTolerance } = context;
416
+ const neighborSegments = context.neighborSegments.filter((segment) => segment.slab.id !== slabId);
311
417
  const subSpans = [];
312
418
  for (let index = 0; index < n; index += 1) {
313
419
  const a = polygon[index];
@@ -16,6 +16,20 @@ export type Space = {
16
16
  boundaryFaces: SpaceBoundaryFace[];
17
17
  isExterior: boolean;
18
18
  };
19
+ export type SpaceTopologyReconcileEvent = {
20
+ levelId: string;
21
+ strategy: 'indexed' | 'fallback';
22
+ examinedWallIds: string[];
23
+ affectedBeforeRoomCount: number;
24
+ affectedCurrentRoomCount: number;
25
+ };
26
+ export type SpaceDetectionSyncOptions = {
27
+ onTopologyReconcile?: (event: SpaceTopologyReconcileEvent) => void;
28
+ };
29
+ type ExtractedRoom = {
30
+ polygon: Point2D[];
31
+ boundaryFaces: SpaceBoundaryFace[];
32
+ };
19
33
  type WallSideUpdate = {
20
34
  wallId: string;
21
35
  frontSide: 'interior' | 'exterior' | 'unknown';
@@ -31,6 +45,7 @@ export type AutoSlabSyncPlan = {
31
45
  };
32
46
  export type AutoSlabPlanningContext = {
33
47
  elevationForRoom?: (polygon: Array<[number, number]>) => number | undefined;
48
+ previousElevationForRoom?: (polygon: Array<[number, number]>) => number | undefined;
34
49
  };
35
50
  export type AutoCeilingSyncPlan = {
36
51
  create: CeilingNodeType[];
@@ -39,6 +54,10 @@ export type AutoCeilingSyncPlan = {
39
54
  data: Partial<CeilingNodeType>;
40
55
  }>;
41
56
  delete: Array<CeilingNodeType['id']>;
57
+ reparent: Array<{
58
+ id: AnyNodeId;
59
+ parentId: CeilingNodeType['id'];
60
+ }>;
42
61
  };
43
62
  export type AutoZoneSyncPlan = {
44
63
  update: Array<{
@@ -58,6 +77,14 @@ export type AutoCeilingPlanningContext = {
58
77
  */
59
78
  ceilingClampBound?: (polygon: Array<[number, number]>) => number;
60
79
  heightForRoom?: (polygon: Array<[number, number]>) => number | undefined;
80
+ previousHeightForRoom?: (polygon: Array<[number, number]>) => number | undefined;
81
+ childPosition?: (childId: AnyNodeId) => [number, number] | undefined;
82
+ };
83
+ declare function bboxOf(points: Point2D[]): {
84
+ minX: number;
85
+ minY: number;
86
+ maxX: number;
87
+ maxY: number;
61
88
  };
62
89
  /**
63
90
  * True when `wall` lies on the boundary of a room enclosed by `walls`, using the
@@ -69,11 +96,21 @@ export type AutoCeilingPlanningContext = {
69
96
  */
70
97
  export declare function wallClosesRoom(walls: WallNode[], wall: WallNode): boolean;
71
98
  export declare function resolveWallSurfaceSides(wall: Pick<WallNode, 'start' | 'end' | 'thickness' | 'frontSide' | 'backSide'>, roomPolygons: Point2D[][]): Pick<WallSideUpdate, 'frontSide' | 'backSide'>;
99
+ type BoundedPolygon = {
100
+ polygon: Point2D[];
101
+ bbox: ReturnType<typeof bboxOf>;
102
+ };
103
+ export declare function surfaceTouchesRooms(surface: BoundedPolygon, rooms: BoundedPolygon[]): boolean;
72
104
  export declare function planAutoZonesForLevel(spaces: readonly Space[], existingZones: readonly ZoneNodeType[]): AutoZoneSyncPlan;
73
105
  export declare function resolveAutoZonePolygon(zone: Pick<ZoneNodeType, 'autoFromWalls' | 'boundaryWallIds' | 'polygon'>, resolve: (id: AnyNodeId) => unknown): ZoneNodeType['polygon'];
74
- export declare function planAutoSlabsForLevel(roomPolygons: Point2D[][], existingSlabs: SlabNodeType[], context?: AutoSlabPlanningContext): AutoSlabSyncPlan;
75
- export declare function planAutoCeilingsForLevel(roomPolygons: Point2D[][], existingCeilings: CeilingNodeType[], context?: AutoCeilingPlanningContext): AutoCeilingSyncPlan;
106
+ export declare function planAutoSlabsForLevel(roomPolygons: Point2D[][], existingSlabs: SlabNodeType[], context?: AutoSlabPlanningContext, namingSlabs?: Array<{
107
+ name?: string;
108
+ }>): AutoSlabSyncPlan;
109
+ export declare function planAutoCeilingsForLevel(roomPolygons: Point2D[][], existingCeilings: CeilingNodeType[], context?: AutoCeilingPlanningContext, namingCeilings?: Array<{
110
+ name?: string;
111
+ }>): AutoCeilingSyncPlan;
76
112
  export declare function detectSpacesForLevel(levelId: string, walls: WallNode[]): {
113
+ rooms: ExtractedRoom[];
77
114
  roomPolygons: Point2D[][];
78
115
  spaces: Space[];
79
116
  wallUpdates: WallSideUpdate[];
@@ -84,7 +121,7 @@ export declare function pauseSpaceDetection(): void;
84
121
  export declare function resumeSpaceDetection(): void;
85
122
  /** True iff the wall-driven auto slab/ceiling sync is currently paused. */
86
123
  export declare function isSpaceDetectionPaused(): boolean;
87
- export declare function initSpaceDetectionSync(sceneStore: any, editorStore: any): () => void;
124
+ export declare function initSpaceDetectionSync(sceneStore: any, editorStore: any, options?: SpaceDetectionSyncOptions): () => void;
88
125
  export declare function wallTouchesOthers(wall: WallNode, otherWalls: WallNode[]): boolean;
89
126
  export {};
90
127
  //# sourceMappingURL=space-detection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"space-detection.d.ts","sourceRoot":"","sources":["../../src/lib/space-detection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"space-detection.d.ts","sourceRoot":"","sources":["../../src/lib/space-detection.ts"],"names":[],"mappings":"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"}