@pascal-app/core 1.0.3 → 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.
Files changed (1098) hide show
  1. package/dist/agent-operations/__fixtures__/add-object-cases.d.ts +235 -0
  2. package/dist/agent-operations/__fixtures__/add-object-cases.d.ts.map +1 -0
  3. package/dist/agent-operations/__fixtures__/add-object-cases.js +129 -0
  4. package/dist/agent-operations/__fixtures__/build-room-cases.d.ts +4 -0
  5. package/dist/agent-operations/__fixtures__/build-room-cases.d.ts.map +1 -0
  6. package/dist/agent-operations/__fixtures__/build-room-cases.js +635 -0
  7. package/dist/agent-operations/__fixtures__/cases.d.ts +52 -0
  8. package/dist/agent-operations/__fixtures__/cases.d.ts.map +1 -0
  9. package/dist/agent-operations/__fixtures__/cases.js +689 -0
  10. package/dist/agent-operations/__fixtures__/find-by-type-cases.d.ts +7 -0
  11. package/dist/agent-operations/__fixtures__/find-by-type-cases.d.ts.map +1 -0
  12. package/dist/agent-operations/__fixtures__/find-by-type-cases.js +21 -0
  13. package/dist/agent-operations/__fixtures__/place-items-cases.d.ts +17 -0
  14. package/dist/agent-operations/__fixtures__/place-items-cases.d.ts.map +1 -0
  15. package/dist/agent-operations/__fixtures__/place-items-cases.js +506 -0
  16. package/dist/agent-operations/__fixtures__/room-cases.d.ts +3 -0
  17. package/dist/agent-operations/__fixtures__/room-cases.d.ts.map +1 -0
  18. package/dist/agent-operations/__fixtures__/room-cases.js +338 -0
  19. package/dist/agent-operations/__fixtures__/search-assets-cases.d.ts +5 -0
  20. package/dist/agent-operations/__fixtures__/search-assets-cases.d.ts.map +1 -0
  21. package/dist/agent-operations/__fixtures__/search-assets-cases.js +137 -0
  22. package/dist/agent-operations/__fixtures__/structure-cases.d.ts +6 -0
  23. package/dist/agent-operations/__fixtures__/structure-cases.d.ts.map +1 -0
  24. package/dist/agent-operations/__fixtures__/structure-cases.js +588 -0
  25. package/dist/agent-operations/__fixtures__/verify-scene-cases.d.ts +3 -0
  26. package/dist/agent-operations/__fixtures__/verify-scene-cases.d.ts.map +1 -0
  27. package/dist/agent-operations/__fixtures__/verify-scene-cases.js +302 -0
  28. package/dist/agent-operations/achieved.d.ts +16 -0
  29. package/dist/agent-operations/achieved.d.ts.map +1 -0
  30. package/dist/agent-operations/achieved.js +28 -0
  31. package/dist/agent-operations/add-column.d.ts +23 -0
  32. package/dist/agent-operations/add-column.d.ts.map +1 -0
  33. package/dist/agent-operations/add-column.js +97 -0
  34. package/dist/agent-operations/add-level.d.ts +14 -0
  35. package/dist/agent-operations/add-level.d.ts.map +1 -0
  36. package/dist/agent-operations/add-level.js +94 -0
  37. package/dist/agent-operations/add-object.d.ts +114 -0
  38. package/dist/agent-operations/add-object.d.ts.map +1 -0
  39. package/dist/agent-operations/add-object.js +361 -0
  40. package/dist/agent-operations/add-wall.d.ts +15 -0
  41. package/dist/agent-operations/add-wall.d.ts.map +1 -0
  42. package/dist/agent-operations/add-wall.js +47 -0
  43. package/dist/agent-operations/apply-changes.d.ts +16 -0
  44. package/dist/agent-operations/apply-changes.d.ts.map +1 -0
  45. package/dist/agent-operations/apply-changes.js +93 -0
  46. package/dist/agent-operations/apply-outcome.d.ts +15 -0
  47. package/dist/agent-operations/apply-outcome.d.ts.map +1 -0
  48. package/dist/agent-operations/apply-outcome.js +18 -0
  49. package/dist/agent-operations/collections.d.ts +28 -0
  50. package/dist/agent-operations/collections.d.ts.map +1 -0
  51. package/dist/agent-operations/collections.js +130 -0
  52. package/dist/agent-operations/create-room.d.ts +36 -0
  53. package/dist/agent-operations/create-room.d.ts.map +1 -0
  54. package/dist/agent-operations/create-room.js +177 -0
  55. package/dist/agent-operations/create-stair.d.ts +33 -0
  56. package/dist/agent-operations/create-stair.d.ts.map +1 -0
  57. package/dist/agent-operations/create-stair.js +247 -0
  58. package/dist/agent-operations/delete-node.d.ts +6 -0
  59. package/dist/agent-operations/delete-node.d.ts.map +1 -0
  60. package/dist/agent-operations/delete-node.js +9 -0
  61. package/dist/agent-operations/door-clearance.d.ts +135 -0
  62. package/dist/agent-operations/door-clearance.d.ts.map +1 -0
  63. package/dist/agent-operations/door-clearance.js +334 -0
  64. package/dist/agent-operations/duplicate-level.d.ts +12 -0
  65. package/dist/agent-operations/duplicate-level.d.ts.map +1 -0
  66. package/dist/agent-operations/duplicate-level.js +54 -0
  67. package/dist/agent-operations/find-by-type.d.ts +11 -0
  68. package/dist/agent-operations/find-by-type.d.ts.map +1 -0
  69. package/dist/agent-operations/find-by-type.js +94 -0
  70. package/dist/agent-operations/furnish-room.d.ts +18 -0
  71. package/dist/agent-operations/furnish-room.d.ts.map +1 -0
  72. package/dist/agent-operations/furnish-room.js +259 -0
  73. package/dist/agent-operations/get-node.d.ts +9 -0
  74. package/dist/agent-operations/get-node.d.ts.map +1 -0
  75. package/dist/agent-operations/get-node.js +33 -0
  76. package/dist/agent-operations/hosted-services.d.ts +13 -0
  77. package/dist/agent-operations/hosted-services.d.ts.map +1 -0
  78. package/dist/agent-operations/hosted-services.js +22 -0
  79. package/dist/agent-operations/index.d.ts +277 -0
  80. package/dist/agent-operations/index.d.ts.map +1 -0
  81. package/dist/agent-operations/index.js +80 -0
  82. package/dist/agent-operations/layout-clearance.d.ts +108 -0
  83. package/dist/agent-operations/layout-clearance.d.ts.map +1 -0
  84. package/dist/agent-operations/layout-clearance.js +190 -0
  85. package/dist/agent-operations/level-reads.d.ts +33 -0
  86. package/dist/agent-operations/level-reads.d.ts.map +1 -0
  87. package/dist/agent-operations/level-reads.js +161 -0
  88. package/dist/agent-operations/level-target.d.ts +11 -0
  89. package/dist/agent-operations/level-target.d.ts.map +1 -0
  90. package/dist/agent-operations/level-target.js +29 -0
  91. package/dist/agent-operations/list-levels.d.ts +4 -0
  92. package/dist/agent-operations/list-levels.d.ts.map +1 -0
  93. package/dist/agent-operations/list-levels.js +31 -0
  94. package/dist/agent-operations/material-preset.d.ts +11 -0
  95. package/dist/agent-operations/material-preset.d.ts.map +1 -0
  96. package/dist/agent-operations/material-preset.js +74 -0
  97. package/dist/agent-operations/material-refs.d.ts +24 -0
  98. package/dist/agent-operations/material-refs.d.ts.map +1 -0
  99. package/dist/agent-operations/material-refs.js +100 -0
  100. package/dist/agent-operations/node-patch.d.ts +3 -0
  101. package/dist/agent-operations/node-patch.d.ts.map +1 -0
  102. package/dist/agent-operations/node-patch.js +114 -0
  103. package/dist/agent-operations/paint.d.ts +21 -0
  104. package/dist/agent-operations/paint.d.ts.map +1 -0
  105. package/dist/agent-operations/paint.js +219 -0
  106. package/dist/agent-operations/place-items.d.ts +20 -0
  107. package/dist/agent-operations/place-items.d.ts.map +1 -0
  108. package/dist/agent-operations/place-items.js +226 -0
  109. package/dist/agent-operations/plan-geometry.d.ts +24 -0
  110. package/dist/agent-operations/plan-geometry.d.ts.map +1 -0
  111. package/dist/agent-operations/plan-geometry.js +94 -0
  112. package/dist/agent-operations/room-structure.d.ts +87 -0
  113. package/dist/agent-operations/room-structure.d.ts.map +1 -0
  114. package/dist/agent-operations/room-structure.js +144 -0
  115. package/dist/agent-operations/scene-materials.d.ts +15 -0
  116. package/dist/agent-operations/scene-materials.d.ts.map +1 -0
  117. package/dist/agent-operations/scene-materials.js +62 -0
  118. package/dist/agent-operations/scene-measure.d.ts +73 -0
  119. package/dist/agent-operations/scene-measure.d.ts.map +1 -0
  120. package/dist/agent-operations/scene-measure.js +73 -0
  121. package/dist/agent-operations/scene-queries.d.ts +4510 -0
  122. package/dist/agent-operations/scene-queries.d.ts.map +1 -0
  123. package/dist/agent-operations/scene-queries.js +84 -0
  124. package/dist/agent-operations/scene-view.d.ts +98 -0
  125. package/dist/agent-operations/scene-view.d.ts.map +1 -0
  126. package/dist/agent-operations/scene-view.js +559 -0
  127. package/dist/agent-operations/search-assets.d.ts +14 -0
  128. package/dist/agent-operations/search-assets.d.ts.map +1 -0
  129. package/dist/agent-operations/search-assets.js +42 -0
  130. package/dist/agent-operations/search-materials.d.ts +12 -0
  131. package/dist/agent-operations/search-materials.d.ts.map +1 -0
  132. package/dist/agent-operations/search-materials.js +111 -0
  133. package/dist/agent-operations/stairs.d.ts +5 -0
  134. package/dist/agent-operations/stairs.d.ts.map +1 -0
  135. package/dist/agent-operations/stairs.js +70 -0
  136. package/dist/agent-operations/types.d.ts +59 -0
  137. package/dist/agent-operations/types.d.ts.map +1 -0
  138. package/dist/agent-operations/types.js +1 -0
  139. package/dist/agent-operations/verify-scene.d.ts +50 -0
  140. package/dist/agent-operations/verify-scene.d.ts.map +1 -0
  141. package/dist/agent-operations/verify-scene.js +369 -0
  142. package/dist/agent-operations/wall-opening.d.ts +65 -0
  143. package/dist/agent-operations/wall-opening.d.ts.map +1 -0
  144. package/dist/agent-operations/wall-opening.js +55 -0
  145. package/dist/agent-tools/achieved.d.ts +9 -0
  146. package/dist/agent-tools/achieved.d.ts.map +1 -0
  147. package/dist/agent-tools/achieved.js +8 -0
  148. package/dist/agent-tools/add-object.d.ts +43 -0
  149. package/dist/agent-tools/add-object.d.ts.map +1 -0
  150. package/dist/agent-tools/add-object.js +86 -0
  151. package/dist/agent-tools/assets.d.ts +13 -0
  152. package/dist/agent-tools/assets.d.ts.map +1 -0
  153. package/dist/agent-tools/assets.js +18 -0
  154. package/dist/agent-tools/clear-scene.d.ts +10 -0
  155. package/dist/agent-tools/clear-scene.d.ts.map +1 -0
  156. package/dist/agent-tools/clear-scene.js +9 -0
  157. package/dist/agent-tools/collections.d.ts +35 -0
  158. package/dist/agent-tools/collections.d.ts.map +1 -0
  159. package/dist/agent-tools/collections.js +31 -0
  160. package/dist/agent-tools/columns.d.ts +44 -0
  161. package/dist/agent-tools/columns.d.ts.map +1 -0
  162. package/dist/agent-tools/columns.js +59 -0
  163. package/dist/agent-tools/contract.d.ts +68 -0
  164. package/dist/agent-tools/contract.d.ts.map +1 -0
  165. package/dist/agent-tools/contract.js +1 -0
  166. package/dist/agent-tools/create-room.d.ts +63 -0
  167. package/dist/agent-tools/create-room.d.ts.map +1 -0
  168. package/dist/agent-tools/create-room.js +90 -0
  169. package/dist/agent-tools/find-by-type.d.ts +11 -0
  170. package/dist/agent-tools/find-by-type.d.ts.map +1 -0
  171. package/dist/agent-tools/find-by-type.js +14 -0
  172. package/dist/agent-tools/furnish-room.d.ts +26 -0
  173. package/dist/agent-tools/furnish-room.d.ts.map +1 -0
  174. package/dist/agent-tools/furnish-room.js +35 -0
  175. package/dist/agent-tools/hosted-services.d.ts +402 -0
  176. package/dist/agent-tools/hosted-services.d.ts.map +1 -0
  177. package/dist/agent-tools/hosted-services.js +148 -0
  178. package/dist/agent-tools/index.d.ts +864 -0
  179. package/dist/agent-tools/index.d.ts.map +1 -0
  180. package/dist/agent-tools/index.js +82 -0
  181. package/dist/agent-tools/levels.d.ts +72 -0
  182. package/dist/agent-tools/levels.d.ts.map +1 -0
  183. package/dist/agent-tools/levels.js +76 -0
  184. package/dist/agent-tools/measurement.d.ts +37 -0
  185. package/dist/agent-tools/measurement.d.ts.map +1 -0
  186. package/dist/agent-tools/measurement.js +94 -0
  187. package/dist/agent-tools/node-id.d.ts +3 -0
  188. package/dist/agent-tools/node-id.d.ts.map +1 -0
  189. package/dist/agent-tools/node-id.js +2 -0
  190. package/dist/agent-tools/nodes.d.ts +17 -0
  191. package/dist/agent-tools/nodes.d.ts.map +1 -0
  192. package/dist/agent-tools/nodes.js +13 -0
  193. package/dist/agent-tools/paint.d.ts +46 -0
  194. package/dist/agent-tools/paint.d.ts.map +1 -0
  195. package/dist/agent-tools/paint.js +29 -0
  196. package/dist/agent-tools/place-items.d.ts +19 -0
  197. package/dist/agent-tools/place-items.d.ts.map +1 -0
  198. package/dist/agent-tools/place-items.js +29 -0
  199. package/dist/agent-tools/refusal.d.ts +20 -0
  200. package/dist/agent-tools/refusal.d.ts.map +1 -0
  201. package/dist/agent-tools/refusal.js +29 -0
  202. package/dist/agent-tools/room-structure.d.ts +438 -0
  203. package/dist/agent-tools/room-structure.d.ts.map +1 -0
  204. package/dist/agent-tools/room-structure.js +207 -0
  205. package/dist/agent-tools/search-materials.d.ts +20 -0
  206. package/dist/agent-tools/search-materials.d.ts.map +1 -0
  207. package/dist/agent-tools/search-materials.js +32 -0
  208. package/dist/agent-tools/stairs.d.ts +86 -0
  209. package/dist/agent-tools/stairs.d.ts.map +1 -0
  210. package/dist/agent-tools/stairs.js +103 -0
  211. package/dist/agent-tools/verify-scene.d.ts +13 -0
  212. package/dist/agent-tools/verify-scene.d.ts.map +1 -0
  213. package/dist/agent-tools/verify-scene.js +21 -0
  214. package/dist/agent-tools/view-scene.d.ts +43 -0
  215. package/dist/agent-tools/view-scene.d.ts.map +1 -0
  216. package/dist/agent-tools/view-scene.js +80 -0
  217. package/dist/agent-tools/wall-openings.d.ts +163 -0
  218. package/dist/agent-tools/wall-openings.d.ts.map +1 -0
  219. package/dist/agent-tools/wall-openings.js +144 -0
  220. package/dist/agent-tools/walls.d.ts +21 -0
  221. package/dist/agent-tools/walls.d.ts.map +1 -0
  222. package/dist/agent-tools/walls.js +30 -0
  223. package/dist/agent-tools/write-target.d.ts +12 -0
  224. package/dist/agent-tools/write-target.d.ts.map +1 -0
  225. package/dist/agent-tools/write-target.js +10 -0
  226. package/dist/building/__fixtures__/wall-opening-cases.d.ts +736 -0
  227. package/dist/building/__fixtures__/wall-opening-cases.d.ts.map +1 -0
  228. package/dist/building/__fixtures__/wall-opening-cases.js +307 -0
  229. package/dist/building/door-types.d.ts +22 -0
  230. package/dist/building/door-types.d.ts.map +1 -0
  231. package/dist/building/door-types.js +167 -0
  232. package/dist/building/floor-item-fit.d.ts +38 -0
  233. package/dist/building/floor-item-fit.d.ts.map +1 -0
  234. package/dist/building/floor-item-fit.js +47 -0
  235. package/dist/building/index.d.ts +7 -0
  236. package/dist/building/index.d.ts.map +1 -0
  237. package/dist/building/index.js +6 -0
  238. package/dist/building/level-duplication.d.ts +8983 -0
  239. package/dist/building/level-duplication.d.ts.map +1 -0
  240. package/dist/building/level-duplication.js +314 -0
  241. package/dist/building/opening-style-presets.d.ts +46 -0
  242. package/dist/building/opening-style-presets.d.ts.map +1 -0
  243. package/dist/building/opening-style-presets.js +195 -0
  244. package/dist/building/wall-openings.d.ts +445 -0
  245. package/dist/building/wall-openings.d.ts.map +1 -0
  246. package/dist/building/wall-openings.js +231 -0
  247. package/dist/building/window-types.d.ts +17 -0
  248. package/dist/building/window-types.d.ts.map +1 -0
  249. package/dist/building/window-types.js +31 -0
  250. package/dist/commands/structure/apply-zone-transform.d.ts +23 -0
  251. package/dist/commands/structure/apply-zone-transform.d.ts.map +1 -0
  252. package/dist/commands/structure/apply-zone-transform.js +55 -0
  253. package/dist/commands/structure/create-mezzanine.d.ts +12 -0
  254. package/dist/commands/structure/create-mezzanine.d.ts.map +1 -0
  255. package/dist/commands/structure/create-mezzanine.js +46 -0
  256. package/dist/commands/structure/create-zone.d.ts +32 -0
  257. package/dist/commands/structure/create-zone.d.ts.map +1 -0
  258. package/dist/commands/structure/create-zone.js +126 -0
  259. package/dist/commands/structure/delete-zone.d.ts +51 -0
  260. package/dist/commands/structure/delete-zone.d.ts.map +1 -0
  261. package/dist/commands/structure/delete-zone.js +354 -0
  262. package/dist/commands/structure/divide-zone.d.ts +38 -0
  263. package/dist/commands/structure/divide-zone.d.ts.map +1 -0
  264. package/dist/commands/structure/divide-zone.js +186 -0
  265. package/dist/commands/structure/duplicate-zone.d.ts +7 -0
  266. package/dist/commands/structure/duplicate-zone.d.ts.map +1 -0
  267. package/dist/commands/structure/duplicate-zone.js +21 -0
  268. package/dist/commands/structure/floor-opening.d.ts +36 -0
  269. package/dist/commands/structure/floor-opening.d.ts.map +1 -0
  270. package/dist/commands/structure/floor-opening.js +124 -0
  271. package/dist/commands/structure/index.d.ts +22 -0
  272. package/dist/commands/structure/index.d.ts.map +1 -0
  273. package/dist/commands/structure/index.js +20 -0
  274. package/dist/commands/structure/lock-outside-faces.d.ts +12 -0
  275. package/dist/commands/structure/lock-outside-faces.d.ts.map +1 -0
  276. package/dist/commands/structure/lock-outside-faces.js +32 -0
  277. package/dist/commands/structure/merge-zones.d.ts +5 -0
  278. package/dist/commands/structure/merge-zones.d.ts.map +1 -0
  279. package/dist/commands/structure/merge-zones.js +22 -0
  280. package/dist/commands/structure/mezzanine-content.d.ts +16 -0
  281. package/dist/commands/structure/mezzanine-content.d.ts.map +1 -0
  282. package/dist/commands/structure/mezzanine-content.js +109 -0
  283. package/dist/commands/structure/plan-mezzanine-stair.d.ts +7 -0
  284. package/dist/commands/structure/plan-mezzanine-stair.d.ts.map +1 -0
  285. package/dist/commands/structure/plan-mezzanine-stair.js +127 -0
  286. package/dist/commands/structure/plan-wall-deletion.d.ts +9 -0
  287. package/dist/commands/structure/plan-wall-deletion.d.ts.map +1 -0
  288. package/dist/commands/structure/plan-wall-deletion.js +151 -0
  289. package/dist/commands/structure/rebase-floor-reference.d.ts +7 -0
  290. package/dist/commands/structure/rebase-floor-reference.d.ts.map +1 -0
  291. package/dist/commands/structure/rebase-floor-reference.js +90 -0
  292. package/dist/commands/structure/resize-mezzanine.d.ts +8 -0
  293. package/dist/commands/structure/resize-mezzanine.d.ts.map +1 -0
  294. package/dist/commands/structure/resize-mezzanine.js +43 -0
  295. package/dist/commands/structure/rotate-zone.d.ts +8 -0
  296. package/dist/commands/structure/rotate-zone.d.ts.map +1 -0
  297. package/dist/commands/structure/rotate-zone.js +28 -0
  298. package/dist/commands/structure/set-floor-foundation.d.ts +62 -0
  299. package/dist/commands/structure/set-floor-foundation.d.ts.map +1 -0
  300. package/dist/commands/structure/set-floor-foundation.js +162 -0
  301. package/dist/commands/structure/set-room-floor-construction.d.ts +8 -0
  302. package/dist/commands/structure/set-room-floor-construction.d.ts.map +1 -0
  303. package/dist/commands/structure/set-room-floor-construction.js +72 -0
  304. package/dist/commands/structure/set-wall-geometry.d.ts +10 -0
  305. package/dist/commands/structure/set-wall-geometry.d.ts.map +1 -0
  306. package/dist/commands/structure/set-wall-geometry.js +52 -0
  307. package/dist/commands/structure/set-zone-edges.d.ts +14 -0
  308. package/dist/commands/structure/set-zone-edges.d.ts.map +1 -0
  309. package/dist/commands/structure/set-zone-edges.js +143 -0
  310. package/dist/commands/structure/set-zone-intent.d.ts +39 -0
  311. package/dist/commands/structure/set-zone-intent.d.ts.map +1 -0
  312. package/dist/commands/structure/set-zone-intent.js +125 -0
  313. package/dist/commands/structure/shared.d.ts +4512 -0
  314. package/dist/commands/structure/shared.d.ts.map +1 -0
  315. package/dist/commands/structure/shared.js +102 -0
  316. package/dist/commands/structure/transform-zone.d.ts +23 -0
  317. package/dist/commands/structure/transform-zone.d.ts.map +1 -0
  318. package/dist/commands/structure/transform-zone.js +472 -0
  319. package/dist/commands/structure/validate-mezzanine.d.ts +5 -0
  320. package/dist/commands/structure/validate-mezzanine.d.ts.map +1 -0
  321. package/dist/commands/structure/validate-mezzanine.js +46 -0
  322. package/dist/commands/structure/zone-crossings.d.ts +8 -0
  323. package/dist/commands/structure/zone-crossings.d.ts.map +1 -0
  324. package/dist/commands/structure/zone-crossings.js +162 -0
  325. package/dist/commands/structure/zone-drop.d.ts +4 -0
  326. package/dist/commands/structure/zone-drop.d.ts.map +1 -0
  327. package/dist/commands/structure/zone-drop.js +68 -0
  328. package/dist/commands/structure/zone-insertion.d.ts +4 -0
  329. package/dist/commands/structure/zone-insertion.d.ts.map +1 -0
  330. package/dist/commands/structure/zone-insertion.js +200 -0
  331. package/dist/commands/structure/zone-transform-content.d.ts +6 -0
  332. package/dist/commands/structure/zone-transform-content.d.ts.map +1 -0
  333. package/dist/commands/structure/zone-transform-content.js +117 -0
  334. package/dist/commands/structure/zone-wall-merge.d.ts +4977 -0
  335. package/dist/commands/structure/zone-wall-merge.d.ts.map +1 -0
  336. package/dist/commands/structure/zone-wall-merge.js +135 -0
  337. package/dist/contracts/reference-discovery.d.ts +18 -0
  338. package/dist/contracts/reference-discovery.d.ts.map +1 -0
  339. package/dist/contracts/reference-discovery.js +100 -0
  340. package/dist/contracts/reference-inventory.d.ts +44 -0
  341. package/dist/contracts/reference-inventory.d.ts.map +1 -0
  342. package/dist/contracts/reference-inventory.js +872 -0
  343. package/dist/events/bus.d.ts +70 -9
  344. package/dist/events/bus.d.ts.map +1 -1
  345. package/dist/hooks/scene-registry/item-clip-registry.d.ts +6 -3
  346. package/dist/hooks/scene-registry/item-clip-registry.d.ts.map +1 -1
  347. package/dist/hooks/scene-registry/item-clip-registry.js +2 -1
  348. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -1
  349. package/dist/hooks/spatial-grid/floor-placed-elevation.js +15 -1
  350. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +3 -2
  351. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  352. package/dist/hooks/spatial-grid/spatial-grid-manager.js +53 -66
  353. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +1612 -127
  354. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  355. package/dist/hooks/spatial-grid/spatial-grid-sync.js +86 -83
  356. package/dist/hooks/spatial-grid/support-host-patch.d.ts +1 -0
  357. package/dist/hooks/spatial-grid/support-host-patch.d.ts.map +1 -1
  358. package/dist/hooks/spatial-grid/support-host-patch.js +12 -5
  359. package/dist/index.d.ts +80 -13
  360. package/dist/index.d.ts.map +1 -1
  361. package/dist/index.js +75 -11
  362. package/dist/lib/__bench__/near-linear-walls.d.ts +2 -0
  363. package/dist/lib/__bench__/near-linear-walls.d.ts.map +1 -0
  364. package/dist/lib/__bench__/near-linear-walls.js +24 -0
  365. package/dist/lib/__bench__/structure-preview.bench.d.ts +2 -0
  366. package/dist/lib/__bench__/structure-preview.bench.d.ts.map +1 -0
  367. package/dist/lib/__bench__/structure-preview.bench.js +72 -0
  368. package/dist/lib/__fixtures__/mezzanine.d.ts +13968 -0
  369. package/dist/lib/__fixtures__/mezzanine.d.ts.map +1 -0
  370. package/dist/lib/__fixtures__/mezzanine.js +43 -0
  371. package/dist/lib/absorb-wall-separators.d.ts +4483 -0
  372. package/dist/lib/absorb-wall-separators.d.ts.map +1 -0
  373. package/dist/lib/absorb-wall-separators.js +65 -0
  374. package/dist/lib/angle-accumulator.d.ts +3 -0
  375. package/dist/lib/angle-accumulator.d.ts.map +1 -0
  376. package/dist/lib/angle-accumulator.js +13 -0
  377. package/dist/lib/artifact-store.d.ts +31 -0
  378. package/dist/lib/artifact-store.d.ts.map +1 -0
  379. package/dist/lib/artifact-store.js +42 -0
  380. package/dist/lib/assembly-stack.d.ts +54 -0
  381. package/dist/lib/assembly-stack.d.ts.map +1 -0
  382. package/dist/lib/assembly-stack.js +52 -0
  383. package/dist/lib/asset-storage.d.ts.map +1 -1
  384. package/dist/lib/asset-storage.js +3 -0
  385. package/dist/lib/canonical-opening-ring.d.ts +3 -0
  386. package/dist/lib/canonical-opening-ring.d.ts.map +1 -0
  387. package/dist/lib/canonical-opening-ring.js +14 -0
  388. package/dist/lib/ceiling-children.d.ts +8 -0
  389. package/dist/lib/ceiling-children.d.ts.map +1 -0
  390. package/dist/lib/ceiling-children.js +36 -0
  391. package/dist/lib/ceiling-surface.d.ts +40 -0
  392. package/dist/lib/ceiling-surface.d.ts.map +1 -0
  393. package/dist/lib/ceiling-surface.js +65 -0
  394. package/dist/lib/cutter-host.d.ts +5 -0
  395. package/dist/lib/cutter-host.d.ts.map +1 -0
  396. package/dist/lib/cutter-host.js +30 -0
  397. package/dist/lib/floor-construction-lift.d.ts +4 -0
  398. package/dist/lib/floor-construction-lift.d.ts.map +1 -0
  399. package/dist/lib/floor-construction-lift.js +54 -0
  400. package/dist/lib/floor-footprint-key.d.ts +3 -0
  401. package/dist/lib/floor-footprint-key.d.ts.map +1 -0
  402. package/dist/lib/floor-footprint-key.js +9 -0
  403. package/dist/lib/floor-footprint-name.d.ts +11 -0
  404. package/dist/lib/floor-footprint-name.d.ts.map +1 -0
  405. package/dist/lib/floor-footprint-name.js +52 -0
  406. package/dist/lib/floor-foundation-datum.d.ts +31 -0
  407. package/dist/lib/floor-foundation-datum.d.ts.map +1 -0
  408. package/dist/lib/floor-foundation-datum.js +226 -0
  409. package/dist/lib/floor-foundation-stack.d.ts +16 -0
  410. package/dist/lib/floor-foundation-stack.d.ts.map +1 -0
  411. package/dist/lib/floor-foundation-stack.js +214 -0
  412. package/dist/lib/floor-intent-changes.d.ts +10 -0
  413. package/dist/lib/floor-intent-changes.d.ts.map +1 -0
  414. package/dist/lib/floor-intent-changes.js +461 -0
  415. package/dist/lib/floor-opening-footprints.d.ts +24 -0
  416. package/dist/lib/floor-opening-footprints.d.ts.map +1 -0
  417. package/dist/lib/floor-opening-footprints.js +127 -0
  418. package/dist/lib/floor-opening-intent.d.ts +16 -0
  419. package/dist/lib/floor-opening-intent.d.ts.map +1 -0
  420. package/dist/lib/floor-opening-intent.js +82 -0
  421. package/dist/lib/floor-plate-id.d.ts +5 -0
  422. package/dist/lib/floor-plate-id.d.ts.map +1 -0
  423. package/dist/lib/floor-plate-id.js +20 -0
  424. package/dist/lib/floor-plate-paint.d.ts +8 -0
  425. package/dist/lib/floor-plate-paint.d.ts.map +1 -0
  426. package/dist/lib/floor-plate-paint.js +11 -0
  427. package/dist/lib/floor-plates.d.ts +211 -0
  428. package/dist/lib/floor-plates.d.ts.map +1 -0
  429. package/dist/lib/floor-plates.js +729 -0
  430. package/dist/lib/floor-room-faces.d.ts +5 -0
  431. package/dist/lib/floor-room-faces.d.ts.map +1 -0
  432. package/dist/lib/floor-room-faces.js +29 -0
  433. package/dist/lib/floor-step-finish.d.ts +49 -0
  434. package/dist/lib/floor-step-finish.d.ts.map +1 -0
  435. package/dist/lib/floor-step-finish.js +128 -0
  436. package/dist/lib/geometry-script-node.d.ts +82 -0
  437. package/dist/lib/geometry-script-node.d.ts.map +1 -0
  438. package/dist/lib/geometry-script-node.js +147 -0
  439. package/dist/lib/geometry-surfaces.d.ts +61 -0
  440. package/dist/lib/geometry-surfaces.d.ts.map +1 -0
  441. package/dist/lib/geometry-surfaces.js +156 -0
  442. package/dist/lib/item-slab-support.d.ts +14 -0
  443. package/dist/lib/item-slab-support.d.ts.map +1 -0
  444. package/dist/lib/item-slab-support.js +40 -0
  445. package/dist/lib/level-footprints.d.ts +23 -0
  446. package/dist/lib/level-footprints.d.ts.map +1 -0
  447. package/dist/lib/level-footprints.js +398 -0
  448. package/dist/lib/level-name.d.ts +7 -0
  449. package/dist/lib/level-name.d.ts.map +1 -1
  450. package/dist/lib/level-name.js +11 -3
  451. package/dist/lib/new-zone.d.ts +10 -0
  452. package/dist/lib/new-zone.d.ts.map +1 -0
  453. package/dist/lib/new-zone.js +10 -0
  454. package/dist/lib/node-ancestry.d.ts +30 -0
  455. package/dist/lib/node-ancestry.d.ts.map +1 -0
  456. package/dist/lib/node-ancestry.js +76 -0
  457. package/dist/lib/object-cuts.d.ts +11 -0
  458. package/dist/lib/object-cuts.d.ts.map +1 -0
  459. package/dist/lib/object-cuts.js +63 -0
  460. package/dist/lib/opening-floor-datum.d.ts +24 -0
  461. package/dist/lib/opening-floor-datum.d.ts.map +1 -0
  462. package/dist/lib/opening-floor-datum.js +145 -0
  463. package/dist/lib/own-floor-intent.d.ts +15 -0
  464. package/dist/lib/own-floor-intent.d.ts.map +1 -0
  465. package/dist/lib/own-floor-intent.js +201 -0
  466. package/dist/lib/plate-reference.d.ts +3 -0
  467. package/dist/lib/plate-reference.d.ts.map +1 -0
  468. package/dist/lib/plate-reference.js +91 -0
  469. package/dist/lib/plate-surface.d.ts +120 -0
  470. package/dist/lib/plate-surface.d.ts.map +1 -0
  471. package/dist/lib/plate-surface.js +725 -0
  472. package/dist/lib/polygon-boolean.d.ts +17 -0
  473. package/dist/lib/polygon-boolean.d.ts.map +1 -0
  474. package/dist/lib/polygon-boolean.js +195 -0
  475. package/dist/lib/polygon-label.d.ts +7 -0
  476. package/dist/lib/polygon-label.d.ts.map +1 -0
  477. package/dist/lib/polygon-label.js +94 -0
  478. package/dist/lib/room-drawn-floor.d.ts +29 -0
  479. package/dist/lib/room-drawn-floor.d.ts.map +1 -0
  480. package/dist/lib/room-drawn-floor.js +88 -0
  481. package/dist/lib/room-floor-choices.d.ts +11 -0
  482. package/dist/lib/room-floor-choices.d.ts.map +1 -0
  483. package/dist/lib/room-floor-choices.js +60 -0
  484. package/dist/lib/room-floor-feasibility.d.ts +25 -0
  485. package/dist/lib/room-floor-feasibility.d.ts.map +1 -0
  486. package/dist/lib/room-floor-feasibility.js +293 -0
  487. package/dist/lib/room-floor-plate.d.ts +3 -0
  488. package/dist/lib/room-floor-plate.d.ts.map +1 -0
  489. package/dist/lib/room-floor-plate.js +4 -0
  490. package/dist/lib/room-graph.d.ts +79 -0
  491. package/dist/lib/room-graph.d.ts.map +1 -0
  492. package/dist/lib/room-graph.js +1265 -0
  493. package/dist/lib/room-name.d.ts +10 -0
  494. package/dist/lib/room-name.d.ts.map +1 -0
  495. package/dist/lib/room-name.js +22 -0
  496. package/dist/lib/room-topology-index.d.ts +53 -5
  497. package/dist/lib/room-topology-index.d.ts.map +1 -1
  498. package/dist/lib/room-topology-index.js +176 -30
  499. package/dist/lib/room-vertical-placement.d.ts +12 -0
  500. package/dist/lib/room-vertical-placement.d.ts.map +1 -0
  501. package/dist/lib/room-vertical-placement.js +28 -0
  502. package/dist/lib/room-zone-adoption.d.ts +74 -0
  503. package/dist/lib/room-zone-adoption.d.ts.map +1 -0
  504. package/dist/lib/room-zone-adoption.js +150 -0
  505. package/dist/lib/setback-envelope.d.ts +73 -0
  506. package/dist/lib/setback-envelope.d.ts.map +1 -0
  507. package/dist/lib/setback-envelope.js +506 -0
  508. package/dist/lib/slab-polygon.d.ts.map +1 -1
  509. package/dist/lib/slab-polygon.js +33 -12
  510. package/dist/lib/slots.d.ts +20 -0
  511. package/dist/lib/slots.d.ts.map +1 -1
  512. package/dist/lib/slots.js +44 -0
  513. package/dist/lib/space-detection.d.ts +14 -94
  514. package/dist/lib/space-detection.d.ts.map +1 -1
  515. package/dist/lib/space-detection.js +244 -1616
  516. package/dist/lib/structure-commit.d.ts +38 -0
  517. package/dist/lib/structure-commit.d.ts.map +1 -0
  518. package/dist/lib/structure-commit.js +52 -0
  519. package/dist/lib/structure-kernel.d.ts +66 -0
  520. package/dist/lib/structure-kernel.d.ts.map +1 -0
  521. package/dist/lib/structure-kernel.js +735 -0
  522. package/dist/lib/structure-reconcile.d.ts +15 -0
  523. package/dist/lib/structure-reconcile.d.ts.map +1 -0
  524. package/dist/lib/structure-reconcile.js +218 -0
  525. package/dist/lib/terrain-contours.d.ts +29 -0
  526. package/dist/lib/terrain-contours.d.ts.map +1 -0
  527. package/dist/lib/terrain-contours.js +199 -0
  528. package/dist/lib/terrain-source-persisted.d.ts +42 -0
  529. package/dist/lib/terrain-source-persisted.d.ts.map +1 -0
  530. package/dist/lib/terrain-source-persisted.js +68 -0
  531. package/dist/lib/terrain-source.d.ts +2 -40
  532. package/dist/lib/terrain-source.d.ts.map +1 -1
  533. package/dist/lib/terrain-source.js +2 -68
  534. package/dist/lib/terrain-support-query.d.ts +83 -0
  535. package/dist/lib/terrain-support-query.d.ts.map +1 -0
  536. package/dist/lib/terrain-support-query.js +187 -0
  537. package/dist/lib/terrain-support.d.ts +1 -77
  538. package/dist/lib/terrain-support.d.ts.map +1 -1
  539. package/dist/lib/terrain-support.js +3 -181
  540. package/dist/lib/unit-containment.js +2 -2
  541. package/dist/lib/unit-report.d.ts.map +1 -1
  542. package/dist/lib/unit-report.js +5 -4
  543. package/dist/lib/zone-colors.d.ts +11 -0
  544. package/dist/lib/zone-colors.d.ts.map +1 -0
  545. package/dist/lib/zone-colors.js +32 -0
  546. package/dist/lib/zone-quantities.d.ts.map +1 -1
  547. package/dist/lib/zone-quantities.js +113 -223
  548. package/dist/material-library.d.ts +9 -1
  549. package/dist/material-library.d.ts.map +1 -1
  550. package/dist/material-library.js +550 -1
  551. package/dist/node-slots/block.d.ts +7 -0
  552. package/dist/node-slots/block.d.ts.map +1 -0
  553. package/dist/node-slots/block.js +27 -0
  554. package/dist/node-slots/cabinet.d.ts +4 -0
  555. package/dist/node-slots/cabinet.d.ts.map +1 -0
  556. package/dist/node-slots/cabinet.js +24 -0
  557. package/dist/node-slots/ceiling.d.ts +6 -0
  558. package/dist/node-slots/ceiling.d.ts.map +1 -0
  559. package/dist/node-slots/ceiling.js +7 -0
  560. package/dist/node-slots/column.d.ts +9 -0
  561. package/dist/node-slots/column.d.ts.map +1 -0
  562. package/dist/node-slots/column.js +24 -0
  563. package/dist/node-slots/door.d.ts +9 -0
  564. package/dist/node-slots/door.d.ts.map +1 -0
  565. package/dist/node-slots/door.js +20 -0
  566. package/dist/node-slots/duct.d.ts +5 -0
  567. package/dist/node-slots/duct.d.ts.map +1 -0
  568. package/dist/node-slots/duct.js +5 -0
  569. package/dist/node-slots/elevator.d.ts +9 -0
  570. package/dist/node-slots/elevator.d.ts.map +1 -0
  571. package/dist/node-slots/elevator.js +17 -0
  572. package/dist/node-slots/fence.d.ts +10 -0
  573. package/dist/node-slots/fence.d.ts.map +1 -0
  574. package/dist/node-slots/fence.js +23 -0
  575. package/dist/node-slots/index.d.ts +25 -0
  576. package/dist/node-slots/index.d.ts.map +1 -0
  577. package/dist/node-slots/index.js +92 -0
  578. package/dist/node-slots/lean-to-extension.d.ts +5 -0
  579. package/dist/node-slots/lean-to-extension.d.ts.map +1 -0
  580. package/dist/node-slots/lean-to-extension.js +15 -0
  581. package/dist/node-slots/procedural-item.d.ts +4 -0
  582. package/dist/node-slots/procedural-item.d.ts.map +1 -0
  583. package/dist/node-slots/procedural-item.js +3 -0
  584. package/dist/node-slots/roof-accessories.d.ts +9 -0
  585. package/dist/node-slots/roof-accessories.d.ts.map +1 -0
  586. package/dist/node-slots/roof-accessories.js +34 -0
  587. package/dist/node-slots/shelf.d.ts +9 -0
  588. package/dist/node-slots/shelf.d.ts.map +1 -0
  589. package/dist/node-slots/shelf.js +30 -0
  590. package/dist/node-slots/slab.d.ts +7 -0
  591. package/dist/node-slots/slab.d.ts.map +1 -0
  592. package/dist/node-slots/slab.js +21 -0
  593. package/dist/node-slots/stair.d.ts +8 -0
  594. package/dist/node-slots/stair.d.ts.map +1 -0
  595. package/dist/node-slots/stair.js +17 -0
  596. package/dist/node-slots/wall.d.ts +12 -0
  597. package/dist/node-slots/wall.d.ts.map +1 -0
  598. package/dist/node-slots/wall.js +55 -0
  599. package/dist/node-slots/window.d.ts +5 -0
  600. package/dist/node-slots/window.d.ts.map +1 -0
  601. package/dist/node-slots/window.js +11 -0
  602. package/dist/procedural-items/design-placement.d.ts +49 -0
  603. package/dist/procedural-items/design-placement.d.ts.map +1 -0
  604. package/dist/procedural-items/design-placement.js +129 -0
  605. package/dist/procedural-items/design.d.ts +261 -0
  606. package/dist/procedural-items/design.d.ts.map +1 -0
  607. package/dist/procedural-items/design.js +707 -0
  608. package/dist/procedural-items/fixtures.d.ts +464 -24
  609. package/dist/procedural-items/fixtures.d.ts.map +1 -1
  610. package/dist/procedural-items/index.d.ts +5 -0
  611. package/dist/procedural-items/index.d.ts.map +1 -1
  612. package/dist/procedural-items/index.js +5 -0
  613. package/dist/procedural-items/integration.d.ts +127 -6
  614. package/dist/procedural-items/integration.d.ts.map +1 -1
  615. package/dist/procedural-items/joints.d.ts +56 -0
  616. package/dist/procedural-items/joints.d.ts.map +1 -0
  617. package/dist/procedural-items/joints.js +251 -0
  618. package/dist/procedural-items/library-colors.d.ts +12 -0
  619. package/dist/procedural-items/library-colors.d.ts.map +1 -1
  620. package/dist/procedural-items/library-colors.js +37 -3
  621. package/dist/procedural-items/motion-controller.d.ts +29 -0
  622. package/dist/procedural-items/motion-controller.d.ts.map +1 -0
  623. package/dist/procedural-items/motion-controller.js +130 -0
  624. package/dist/procedural-items/node.d.ts +490 -24
  625. package/dist/procedural-items/node.d.ts.map +1 -1
  626. package/dist/procedural-items/query.d.ts +18 -1
  627. package/dist/procedural-items/query.d.ts.map +1 -1
  628. package/dist/procedural-items/query.js +78 -39
  629. package/dist/procedural-items/recipe.d.ts +258 -14
  630. package/dist/procedural-items/recipe.d.ts.map +1 -1
  631. package/dist/procedural-items/recipe.js +933 -61
  632. package/dist/procedural-items/section.d.ts +27 -0
  633. package/dist/procedural-items/section.d.ts.map +1 -0
  634. package/dist/procedural-items/section.js +237 -0
  635. package/dist/procedural-items/spatial.d.ts +21 -0
  636. package/dist/procedural-items/spatial.d.ts.map +1 -1
  637. package/dist/procedural-items/spatial.js +90 -0
  638. package/dist/registry/handles.d.ts +2 -0
  639. package/dist/registry/handles.d.ts.map +1 -1
  640. package/dist/registry/index.d.ts +6 -4
  641. package/dist/registry/index.d.ts.map +1 -1
  642. package/dist/registry/index.js +4 -2
  643. package/dist/registry/mechanism.d.ts +9 -0
  644. package/dist/registry/mechanism.d.ts.map +1 -0
  645. package/dist/registry/mechanism.js +21 -0
  646. package/dist/registry/registry.d.ts +6 -3
  647. package/dist/registry/registry.d.ts.map +1 -1
  648. package/dist/registry/registry.js +59 -22
  649. package/dist/registry/scene-api.d.ts +20 -0
  650. package/dist/registry/scene-api.d.ts.map +1 -1
  651. package/dist/registry/scene-api.js +26 -13
  652. package/dist/registry/subtree.d.ts +8 -0
  653. package/dist/registry/subtree.d.ts.map +1 -1
  654. package/dist/registry/subtree.js +53 -0
  655. package/dist/registry/types.d.ts +340 -11
  656. package/dist/registry/types.d.ts.map +1 -1
  657. package/dist/schema/__fixtures__/node-fixtures.d.ts.map +1 -1
  658. package/dist/schema/__fixtures__/node-fixtures.js +2 -0
  659. package/dist/schema/assembly.d.ts +153 -0
  660. package/dist/schema/assembly.d.ts.map +1 -0
  661. package/dist/schema/assembly.js +155 -0
  662. package/dist/schema/asset-url.d.ts +2 -1
  663. package/dist/schema/asset-url.d.ts.map +1 -1
  664. package/dist/schema/asset-url.js +5 -2
  665. package/dist/schema/base.d.ts +26 -0
  666. package/dist/schema/base.d.ts.map +1 -1
  667. package/dist/schema/base.js +2 -0
  668. package/dist/schema/collection-templates.d.ts +46 -0
  669. package/dist/schema/collection-templates.d.ts.map +1 -0
  670. package/dist/schema/collection-templates.js +14 -0
  671. package/dist/schema/collections.d.ts +7 -0
  672. package/dist/schema/collections.d.ts.map +1 -1
  673. package/dist/schema/cut.d.ts +29 -0
  674. package/dist/schema/cut.d.ts.map +1 -0
  675. package/dist/schema/cut.js +50 -0
  676. package/dist/schema/geometry-metadata.d.ts +51 -0
  677. package/dist/schema/geometry-metadata.d.ts.map +1 -0
  678. package/dist/schema/geometry-metadata.js +57 -0
  679. package/dist/schema/geometry-source.d.ts +250 -0
  680. package/dist/schema/geometry-source.d.ts.map +1 -0
  681. package/dist/schema/geometry-source.js +130 -0
  682. package/dist/schema/index.d.ts +19 -7
  683. package/dist/schema/index.d.ts.map +1 -1
  684. package/dist/schema/index.js +21 -5
  685. package/dist/schema/nodes/block.d.ts +26 -0
  686. package/dist/schema/nodes/block.d.ts.map +1 -1
  687. package/dist/schema/nodes/block.js +1 -1
  688. package/dist/schema/nodes/box-vent.d.ts +26 -0
  689. package/dist/schema/nodes/box-vent.d.ts.map +1 -1
  690. package/dist/schema/nodes/building.d.ts +26 -0
  691. package/dist/schema/nodes/building.d.ts.map +1 -1
  692. package/dist/schema/nodes/cabinet.d.ts +61 -9
  693. package/dist/schema/nodes/cabinet.d.ts.map +1 -1
  694. package/dist/schema/nodes/ceiling.d.ts +37 -0
  695. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  696. package/dist/schema/nodes/ceiling.js +8 -0
  697. package/dist/schema/nodes/chimney.d.ts +26 -0
  698. package/dist/schema/nodes/chimney.d.ts.map +1 -1
  699. package/dist/schema/nodes/column.d.ts +126 -0
  700. package/dist/schema/nodes/column.d.ts.map +1 -1
  701. package/dist/schema/nodes/column.js +2 -0
  702. package/dist/schema/nodes/construction-dimension.d.ts +26 -0
  703. package/dist/schema/nodes/construction-dimension.d.ts.map +1 -1
  704. package/dist/schema/nodes/cupola.d.ts +26 -0
  705. package/dist/schema/nodes/cupola.d.ts.map +1 -1
  706. package/dist/schema/nodes/curtain-wall.d.ts +5 -5
  707. package/dist/schema/nodes/door.d.ts +139 -20
  708. package/dist/schema/nodes/door.d.ts.map +1 -1
  709. package/dist/schema/nodes/door.js +18 -33
  710. package/dist/schema/nodes/dormer.d.ts +28 -2
  711. package/dist/schema/nodes/dormer.d.ts.map +1 -1
  712. package/dist/schema/nodes/downspout.d.ts +27 -1
  713. package/dist/schema/nodes/downspout.d.ts.map +1 -1
  714. package/dist/schema/nodes/duct-fitting.d.ts +27 -1
  715. package/dist/schema/nodes/duct-fitting.d.ts.map +1 -1
  716. package/dist/schema/nodes/duct-segment.d.ts +27 -1
  717. package/dist/schema/nodes/duct-segment.d.ts.map +1 -1
  718. package/dist/schema/nodes/duct-terminal.d.ts +26 -0
  719. package/dist/schema/nodes/duct-terminal.d.ts.map +1 -1
  720. package/dist/schema/nodes/elevator.d.ts +26 -0
  721. package/dist/schema/nodes/elevator.d.ts.map +1 -1
  722. package/dist/schema/nodes/eyebrow-vent.d.ts +26 -0
  723. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -1
  724. package/dist/schema/nodes/fence-feature.d.ts +141 -0
  725. package/dist/schema/nodes/fence-feature.d.ts.map +1 -0
  726. package/dist/schema/nodes/fence-feature.js +21 -0
  727. package/dist/schema/nodes/fence.d.ts +186 -0
  728. package/dist/schema/nodes/fence.d.ts.map +1 -1
  729. package/dist/schema/nodes/fence.js +92 -7
  730. package/dist/schema/nodes/floor-opening.d.ts +61 -0
  731. package/dist/schema/nodes/floor-opening.d.ts.map +1 -0
  732. package/dist/schema/nodes/floor-opening.js +17 -0
  733. package/dist/schema/nodes/guide.d.ts +26 -0
  734. package/dist/schema/nodes/guide.d.ts.map +1 -1
  735. package/dist/schema/nodes/gutter.d.ts +26 -0
  736. package/dist/schema/nodes/gutter.d.ts.map +1 -1
  737. package/dist/schema/nodes/hvac-equipment.d.ts +26 -0
  738. package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -1
  739. package/dist/schema/nodes/imported-mesh.d.ts +26 -0
  740. package/dist/schema/nodes/imported-mesh.d.ts.map +1 -1
  741. package/dist/schema/nodes/item.d.ts +153 -2
  742. package/dist/schema/nodes/item.d.ts.map +1 -1
  743. package/dist/schema/nodes/item.js +13 -0
  744. package/dist/schema/nodes/lean-to-extension.d.ts +26 -0
  745. package/dist/schema/nodes/lean-to-extension.d.ts.map +1 -1
  746. package/dist/schema/nodes/level.d.ts +27 -1
  747. package/dist/schema/nodes/level.d.ts.map +1 -1
  748. package/dist/schema/nodes/lineset.d.ts +26 -0
  749. package/dist/schema/nodes/lineset.d.ts.map +1 -1
  750. package/dist/schema/nodes/liquid-line.d.ts +26 -0
  751. package/dist/schema/nodes/liquid-line.d.ts.map +1 -1
  752. package/dist/schema/nodes/measurement.d.ts +26 -0
  753. package/dist/schema/nodes/measurement.d.ts.map +1 -1
  754. package/dist/schema/nodes/opening-types.d.ts +41 -0
  755. package/dist/schema/nodes/opening-types.d.ts.map +1 -0
  756. package/dist/schema/nodes/opening-types.js +48 -0
  757. package/dist/schema/nodes/pipe-fitting.d.ts +26 -0
  758. package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -1
  759. package/dist/schema/nodes/pipe-segment.d.ts +26 -0
  760. package/dist/schema/nodes/pipe-segment.d.ts.map +1 -1
  761. package/dist/schema/nodes/pipe-trap.d.ts +26 -0
  762. package/dist/schema/nodes/pipe-trap.d.ts.map +1 -1
  763. package/dist/schema/nodes/ridge-vent.d.ts +26 -0
  764. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
  765. package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -1
  766. package/dist/schema/nodes/roof-segment-walls.js +2 -1
  767. package/dist/schema/nodes/roof-segment.d.ts +30 -2
  768. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  769. package/dist/schema/nodes/roof-segment.js +9 -0
  770. package/dist/schema/nodes/roof.d.ts +101 -0
  771. package/dist/schema/nodes/roof.d.ts.map +1 -1
  772. package/dist/schema/nodes/roof.js +6 -0
  773. package/dist/schema/nodes/scan.d.ts +26 -0
  774. package/dist/schema/nodes/scan.d.ts.map +1 -1
  775. package/dist/schema/nodes/separator.d.ts +50 -0
  776. package/dist/schema/nodes/separator.d.ts.map +1 -0
  777. package/dist/schema/nodes/separator.js +8 -0
  778. package/dist/schema/nodes/shelf.d.ts +26 -0
  779. package/dist/schema/nodes/shelf.d.ts.map +1 -1
  780. package/dist/schema/nodes/site.d.ts +192 -0
  781. package/dist/schema/nodes/site.d.ts.map +1 -1
  782. package/dist/schema/nodes/site.js +178 -1
  783. package/dist/schema/nodes/skylight.d.ts +30 -4
  784. package/dist/schema/nodes/skylight.d.ts.map +1 -1
  785. package/dist/schema/nodes/slab.d.ts +84 -0
  786. package/dist/schema/nodes/slab.d.ts.map +1 -1
  787. package/dist/schema/nodes/slab.js +37 -2
  788. package/dist/schema/nodes/solar-panel.d.ts +26 -0
  789. package/dist/schema/nodes/solar-panel.d.ts.map +1 -1
  790. package/dist/schema/nodes/spawn.d.ts +26 -0
  791. package/dist/schema/nodes/spawn.d.ts.map +1 -1
  792. package/dist/schema/nodes/stair-construction.d.ts +21 -0
  793. package/dist/schema/nodes/stair-construction.d.ts.map +1 -0
  794. package/dist/schema/nodes/stair-construction.js +13 -0
  795. package/dist/schema/nodes/stair-design-targets.d.ts +9 -0
  796. package/dist/schema/nodes/stair-design-targets.d.ts.map +1 -0
  797. package/dist/schema/nodes/stair-design-targets.js +7 -0
  798. package/dist/schema/nodes/stair-segment.d.ts +72 -4
  799. package/dist/schema/nodes/stair-segment.d.ts.map +1 -1
  800. package/dist/schema/nodes/stair-segment.js +13 -0
  801. package/dist/schema/nodes/stair.d.ts +119 -2
  802. package/dist/schema/nodes/stair.d.ts.map +1 -1
  803. package/dist/schema/nodes/stair.js +59 -2
  804. package/dist/schema/nodes/structural-grid.d.ts +26 -0
  805. package/dist/schema/nodes/structural-grid.d.ts.map +1 -1
  806. package/dist/schema/nodes/surface-hole-metadata.d.ts +3 -0
  807. package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
  808. package/dist/schema/nodes/surface-hole-metadata.js +2 -1
  809. package/dist/schema/nodes/surface-paint-region.d.ts +14 -0
  810. package/dist/schema/nodes/surface-paint-region.d.ts.map +1 -0
  811. package/dist/schema/nodes/surface-paint-region.js +12 -0
  812. package/dist/schema/nodes/turbine-vent.d.ts +26 -0
  813. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -1
  814. package/dist/schema/nodes/unit.d.ts +26 -0
  815. package/dist/schema/nodes/unit.d.ts.map +1 -1
  816. package/dist/schema/nodes/wall.d.ts +352 -61
  817. package/dist/schema/nodes/wall.d.ts.map +1 -1
  818. package/dist/schema/nodes/wall.js +185 -186
  819. package/dist/schema/nodes/window.d.ts +139 -20
  820. package/dist/schema/nodes/window.d.ts.map +1 -1
  821. package/dist/schema/nodes/window.js +12 -14
  822. package/dist/schema/nodes/zone.d.ts +69 -1
  823. package/dist/schema/nodes/zone.d.ts.map +1 -1
  824. package/dist/schema/nodes/zone.js +46 -0
  825. package/dist/schema/provenance.d.ts +118 -0
  826. package/dist/schema/provenance.d.ts.map +1 -0
  827. package/dist/schema/provenance.js +76 -0
  828. package/dist/schema/source-ref.d.ts +23 -0
  829. package/dist/schema/source-ref.d.ts.map +1 -0
  830. package/dist/schema/source-ref.js +29 -0
  831. package/dist/schema/types.d.ts +3317 -68
  832. package/dist/schema/types.d.ts.map +1 -1
  833. package/dist/schema/types.js +7 -0
  834. package/dist/services/alignment-anchors.d.ts +2 -1
  835. package/dist/services/alignment-anchors.d.ts.map +1 -1
  836. package/dist/services/alignment-anchors.js +9 -6
  837. package/dist/services/index.d.ts +1 -1
  838. package/dist/services/index.d.ts.map +1 -1
  839. package/dist/services/index.js +1 -1
  840. package/dist/services/level-height.d.ts.map +1 -1
  841. package/dist/services/level-height.js +6 -4
  842. package/dist/services/storey.d.ts +21 -1
  843. package/dist/services/storey.d.ts.map +1 -1
  844. package/dist/services/storey.js +94 -8
  845. package/dist/services/surface-hosting.d.ts.map +1 -1
  846. package/dist/services/surface-hosting.js +4 -1
  847. package/dist/store/actions/node-actions.d.ts +4563 -8
  848. package/dist/store/actions/node-actions.d.ts.map +1 -1
  849. package/dist/store/actions/node-actions.js +495 -174
  850. package/dist/store/derived-node-guard.d.ts +84 -0
  851. package/dist/store/derived-node-guard.d.ts.map +1 -0
  852. package/dist/store/derived-node-guard.js +586 -0
  853. package/dist/store/history-control.d.ts +34 -0
  854. package/dist/store/history-control.d.ts.map +1 -1
  855. package/dist/store/history-control.js +99 -1
  856. package/dist/store/history-drafts.d.ts +48 -0
  857. package/dist/store/history-drafts.d.ts.map +1 -0
  858. package/dist/store/history-drafts.js +403 -0
  859. package/dist/store/history-invalidation.d.ts.map +1 -1
  860. package/dist/store/history-invalidation.js +1 -0
  861. package/dist/store/item-interaction.d.ts +28 -0
  862. package/dist/store/item-interaction.d.ts.map +1 -0
  863. package/dist/store/item-interaction.js +34 -0
  864. package/dist/store/registered-node-defaults.d.ts +11 -0
  865. package/dist/store/registered-node-defaults.d.ts.map +1 -0
  866. package/dist/store/registered-node-defaults.js +25 -0
  867. package/dist/store/scene-annotations.d.ts +8969 -0
  868. package/dist/store/scene-annotations.d.ts.map +1 -0
  869. package/dist/store/scene-annotations.js +107 -0
  870. package/dist/store/use-interactive.d.ts +23 -1
  871. package/dist/store/use-interactive.d.ts.map +1 -1
  872. package/dist/store/use-interactive.js +128 -5
  873. package/dist/store/use-scene.d.ts +68 -12
  874. package/dist/store/use-scene.d.ts.map +1 -1
  875. package/dist/store/use-scene.js +416 -305
  876. package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -1
  877. package/dist/systems/elevator/elevator-opening-sync.js +4 -6
  878. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -1
  879. package/dist/systems/elevator/elevator-opening-system.js +12 -6
  880. package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
  881. package/dist/systems/elevator/elevator-service.js +8 -3
  882. package/dist/systems/fence/fence-centerline.d.ts.map +1 -1
  883. package/dist/systems/fence/fence-centerline.js +6 -5
  884. package/dist/systems/fence/fence-features.d.ts +36 -0
  885. package/dist/systems/fence/fence-features.d.ts.map +1 -0
  886. package/dist/systems/fence/fence-features.js +105 -0
  887. package/dist/systems/fence/fence-spline.d.ts +8 -5
  888. package/dist/systems/fence/fence-spline.d.ts.map +1 -1
  889. package/dist/systems/fence/fence-spline.js +51 -16
  890. package/dist/systems/owned-floor-openings.d.ts +9 -0
  891. package/dist/systems/owned-floor-openings.d.ts.map +1 -0
  892. package/dist/systems/owned-floor-openings.js +326 -0
  893. package/dist/systems/reconcile-owned-floor-openings.d.ts +3 -0
  894. package/dist/systems/reconcile-owned-floor-openings.d.ts.map +1 -0
  895. package/dist/systems/reconcile-owned-floor-openings.js +25 -0
  896. package/dist/systems/slab/__fixtures__/doorway-steps.d.ts +8 -0
  897. package/dist/systems/slab/__fixtures__/doorway-steps.d.ts.map +1 -0
  898. package/dist/systems/slab/__fixtures__/doorway-steps.js +62 -0
  899. package/dist/systems/slab/__fixtures__/floor-step.d.ts +5636 -0
  900. package/dist/systems/slab/__fixtures__/floor-step.d.ts.map +1 -0
  901. package/dist/systems/slab/__fixtures__/floor-step.js +66 -0
  902. package/dist/systems/slab/__fixtures__/raised-room.d.ts +5775 -0
  903. package/dist/systems/slab/__fixtures__/raised-room.d.ts.map +1 -0
  904. package/dist/systems/slab/__fixtures__/raised-room.js +54 -0
  905. package/dist/systems/slab/ensure-slab-openings.d.ts +9 -0
  906. package/dist/systems/slab/ensure-slab-openings.d.ts.map +1 -0
  907. package/dist/systems/slab/ensure-slab-openings.js +41 -0
  908. package/dist/systems/slab/slab-support.d.ts +25 -6
  909. package/dist/systems/slab/slab-support.d.ts.map +1 -1
  910. package/dist/systems/slab/slab-support.js +471 -54
  911. package/dist/systems/stair/stair-base-elevation.d.ts +3 -0
  912. package/dist/systems/stair/stair-base-elevation.d.ts.map +1 -0
  913. package/dist/systems/stair/stair-base-elevation.js +49 -0
  914. package/dist/systems/stair/stair-clearance.d.ts +39 -0
  915. package/dist/systems/stair/stair-clearance.d.ts.map +1 -0
  916. package/dist/systems/stair/stair-clearance.js +508 -0
  917. package/dist/systems/stair/stair-construction.d.ts +37 -0
  918. package/dist/systems/stair/stair-construction.d.ts.map +1 -0
  919. package/dist/systems/stair/stair-construction.js +314 -0
  920. package/dist/systems/stair/stair-detail-budget.d.ts +13 -0
  921. package/dist/systems/stair/stair-detail-budget.d.ts.map +1 -0
  922. package/dist/systems/stair/stair-detail-budget.js +64 -0
  923. package/dist/systems/stair/stair-flight.d.ts +2 -2
  924. package/dist/systems/stair/stair-flight.d.ts.map +1 -1
  925. package/dist/systems/stair/stair-flight.js +4 -3
  926. package/dist/systems/stair/stair-floor-footprints.d.ts +7 -0
  927. package/dist/systems/stair/stair-floor-footprints.d.ts.map +1 -0
  928. package/dist/systems/stair/stair-floor-footprints.js +113 -0
  929. package/dist/systems/stair/stair-footprint.d.ts +3 -1
  930. package/dist/systems/stair/stair-footprint.d.ts.map +1 -1
  931. package/dist/systems/stair/stair-footprint.js +97 -44
  932. package/dist/systems/stair/stair-layout.d.ts +39 -0
  933. package/dist/systems/stair/stair-layout.d.ts.map +1 -0
  934. package/dist/systems/stair/stair-layout.js +58 -0
  935. package/dist/systems/stair/stair-opening-preview.d.ts +1612 -80
  936. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
  937. package/dist/systems/stair/stair-opening-sync.d.ts +8 -2
  938. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  939. package/dist/systems/stair/stair-opening-sync.js +136 -315
  940. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
  941. package/dist/systems/stair/stair-opening-system.js +40 -8
  942. package/dist/systems/stair/stair-presets.d.ts +276 -0
  943. package/dist/systems/stair/stair-presets.d.ts.map +1 -0
  944. package/dist/systems/stair/stair-presets.js +151 -0
  945. package/dist/systems/stair/stair-rail-budget.d.ts +6 -0
  946. package/dist/systems/stair/stair-rail-budget.d.ts.map +1 -0
  947. package/dist/systems/stair/stair-rail-budget.js +36 -0
  948. package/dist/systems/stair/stair-rail-path.d.ts +22 -0
  949. package/dist/systems/stair/stair-rail-path.d.ts.map +1 -0
  950. package/dist/systems/stair/stair-rail-path.js +497 -0
  951. package/dist/systems/stair/stair-rise-edit.d.ts +9 -0
  952. package/dist/systems/stair/stair-rise-edit.d.ts.map +1 -0
  953. package/dist/systems/stair/stair-rise-edit.js +46 -0
  954. package/dist/systems/stair/stair-rise-query.d.ts +24 -0
  955. package/dist/systems/stair/stair-rise-query.d.ts.map +1 -0
  956. package/dist/systems/stair/stair-rise-query.js +125 -0
  957. package/dist/systems/stair/stair-rise.d.ts +4 -19
  958. package/dist/systems/stair/stair-rise.d.ts.map +1 -1
  959. package/dist/systems/stair/stair-rise.js +8 -71
  960. package/dist/systems/stair/stair-sizing.d.ts +414 -0
  961. package/dist/systems/stair/stair-sizing.d.ts.map +1 -0
  962. package/dist/systems/stair/stair-sizing.js +255 -0
  963. package/dist/systems/stair/stair-sweep-edit.d.ts +7 -0
  964. package/dist/systems/stair/stair-sweep-edit.d.ts.map +1 -0
  965. package/dist/systems/stair/stair-sweep-edit.js +8 -0
  966. package/dist/systems/stair/stair-walking-line.d.ts +5 -0
  967. package/dist/systems/stair/stair-walking-line.d.ts.map +1 -0
  968. package/dist/systems/stair/stair-walking-line.js +91 -0
  969. package/dist/systems/stair/stair-winder.d.ts +36 -0
  970. package/dist/systems/stair/stair-winder.d.ts.map +1 -0
  971. package/dist/systems/stair/stair-winder.js +173 -0
  972. package/dist/systems/wall/wall-assembly.d.ts +234 -0
  973. package/dist/systems/wall/wall-assembly.d.ts.map +1 -0
  974. package/dist/systems/wall/wall-assembly.js +854 -0
  975. package/dist/systems/wall/wall-curve.d.ts +2 -1
  976. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  977. package/dist/systems/wall/wall-curve.js +21 -5
  978. package/dist/systems/wall/wall-finish.d.ts +102 -0
  979. package/dist/systems/wall/wall-finish.d.ts.map +1 -0
  980. package/dist/systems/wall/wall-finish.js +264 -0
  981. package/dist/systems/wall/wall-footprint.d.ts +3 -0
  982. package/dist/systems/wall/wall-footprint.d.ts.map +1 -1
  983. package/dist/systems/wall/wall-footprint.js +15 -13
  984. package/dist/systems/wall/wall-frame.d.ts +45 -0
  985. package/dist/systems/wall/wall-frame.d.ts.map +1 -0
  986. package/dist/systems/wall/wall-frame.js +162 -0
  987. package/dist/systems/wall/wall-layer-bands.d.ts +38 -0
  988. package/dist/systems/wall/wall-layer-bands.d.ts.map +1 -0
  989. package/dist/systems/wall/wall-layer-bands.js +109 -0
  990. package/dist/systems/wall/wall-merge.d.ts +20 -1
  991. package/dist/systems/wall/wall-merge.d.ts.map +1 -1
  992. package/dist/systems/wall/wall-merge.js +189 -18
  993. package/dist/systems/wall/wall-mitering.d.ts +6 -5
  994. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  995. package/dist/systems/wall/wall-mitering.js +70 -20
  996. package/dist/systems/wall/wall-operations.d.ts +1 -1
  997. package/dist/systems/wall/wall-operations.d.ts.map +1 -1
  998. package/dist/systems/wall/wall-operations.js +2 -1
  999. package/dist/systems/wall/wall-paint-plan.d.ts +30 -0
  1000. package/dist/systems/wall/wall-paint-plan.d.ts.map +1 -0
  1001. package/dist/systems/wall/wall-paint-plan.js +167 -0
  1002. package/dist/systems/wall/wall-room-sides.d.ts +6 -0
  1003. package/dist/systems/wall/wall-room-sides.d.ts.map +1 -0
  1004. package/dist/systems/wall/wall-room-sides.js +14 -0
  1005. package/dist/systems/wall/wall-step-openings.d.ts +17 -0
  1006. package/dist/systems/wall/wall-step-openings.d.ts.map +1 -0
  1007. package/dist/systems/wall/wall-step-openings.js +32 -0
  1008. package/dist/systems/wall/wall-top.d.ts +2 -6
  1009. package/dist/systems/wall/wall-top.d.ts.map +1 -1
  1010. package/dist/systems/wall/wall-top.js +3 -9
  1011. package/dist/systems/wall/wall-topology.d.ts +21 -0
  1012. package/dist/systems/wall/wall-topology.d.ts.map +1 -1
  1013. package/dist/systems/wall/wall-topology.js +405 -4
  1014. package/dist/utils/clone-scene-graph.d.ts +6 -0
  1015. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  1016. package/dist/utils/clone-scene-graph.js +123 -15
  1017. package/dist/utils/ensure-scene-openings.d.ts +8965 -0
  1018. package/dist/utils/ensure-scene-openings.d.ts.map +1 -0
  1019. package/dist/utils/ensure-scene-openings.js +29 -0
  1020. package/dist/utils/exterior-threshold-migration.d.ts +4 -0
  1021. package/dist/utils/exterior-threshold-migration.d.ts.map +1 -0
  1022. package/dist/utils/exterior-threshold-migration.js +114 -0
  1023. package/dist/utils/floor-follow-migration.d.ts +3 -0
  1024. package/dist/utils/floor-follow-migration.d.ts.map +1 -0
  1025. package/dist/utils/floor-follow-migration.js +29 -0
  1026. package/dist/utils/floor-opening-migration.d.ts +13 -0
  1027. package/dist/utils/floor-opening-migration.d.ts.map +1 -0
  1028. package/dist/utils/floor-opening-migration.js +148 -0
  1029. package/dist/utils/floor-piece-adoption.d.ts +50 -0
  1030. package/dist/utils/floor-piece-adoption.d.ts.map +1 -0
  1031. package/dist/utils/floor-piece-adoption.js +182 -0
  1032. package/dist/utils/floor-plate-migration.d.ts +4507 -0
  1033. package/dist/utils/floor-plate-migration.d.ts.map +1 -0
  1034. package/dist/utils/floor-plate-migration.js +1360 -0
  1035. package/dist/utils/heal-plan-coordinates.d.ts +2 -0
  1036. package/dist/utils/heal-plan-coordinates.d.ts.map +1 -0
  1037. package/dist/utils/heal-plan-coordinates.js +69 -0
  1038. package/dist/utils/heal-scene-graph.d.ts +20 -6
  1039. package/dist/utils/heal-scene-graph.d.ts.map +1 -1
  1040. package/dist/utils/heal-scene-graph.js +61 -16
  1041. package/dist/utils/legacy-material-slots.d.ts +23 -0
  1042. package/dist/utils/legacy-material-slots.d.ts.map +1 -0
  1043. package/dist/utils/legacy-material-slots.js +120 -0
  1044. package/dist/utils/legacy-opening-datums.d.ts +11 -0
  1045. package/dist/utils/legacy-opening-datums.d.ts.map +1 -0
  1046. package/dist/utils/legacy-opening-datums.js +92 -0
  1047. package/dist/utils/legacy-wall-datums.d.ts +6 -0
  1048. package/dist/utils/legacy-wall-datums.d.ts.map +1 -0
  1049. package/dist/utils/legacy-wall-datums.js +199 -0
  1050. package/dist/utils/load-migration.d.ts +28 -0
  1051. package/dist/utils/load-migration.d.ts.map +1 -0
  1052. package/dist/utils/load-migration.js +89 -0
  1053. package/dist/utils/node-defaults.d.ts +37 -0
  1054. package/dist/utils/node-defaults.d.ts.map +1 -0
  1055. package/dist/utils/node-defaults.js +96 -0
  1056. package/dist/utils/normalize-legacy-structure.d.ts +2 -0
  1057. package/dist/utils/normalize-legacy-structure.d.ts.map +1 -0
  1058. package/dist/utils/normalize-legacy-structure.js +61 -0
  1059. package/dist/utils/omit-undefined.d.ts +2 -0
  1060. package/dist/utils/omit-undefined.d.ts.map +1 -0
  1061. package/dist/utils/omit-undefined.js +17 -0
  1062. package/dist/utils/owned-floor-opening-migration.d.ts +4489 -0
  1063. package/dist/utils/owned-floor-opening-migration.d.ts.map +1 -0
  1064. package/dist/utils/owned-floor-opening-migration.js +365 -0
  1065. package/dist/utils/reconcile-structure-on-load.d.ts +8965 -0
  1066. package/dist/utils/reconcile-structure-on-load.d.ts.map +1 -0
  1067. package/dist/utils/reconcile-structure-on-load.js +307 -0
  1068. package/dist/utils/room-zone-migration.d.ts +18 -0
  1069. package/dist/utils/room-zone-migration.d.ts.map +1 -0
  1070. package/dist/utils/room-zone-migration.js +258 -0
  1071. package/dist/utils/scene-migrations.d.ts +16 -1
  1072. package/dist/utils/scene-migrations.d.ts.map +1 -1
  1073. package/dist/utils/scene-migrations.js +15 -1
  1074. package/dist/utils/structure-id.d.ts +4 -0
  1075. package/dist/utils/structure-id.d.ts.map +1 -0
  1076. package/dist/utils/structure-id.js +115 -0
  1077. package/dist/utils/vertical-scene-migration.d.ts +8 -8
  1078. package/dist/utils/vertical-scene-migration.d.ts.map +1 -1
  1079. package/dist/utils/vertical-scene-migration.js +18 -8
  1080. package/dist/utils/wall-assembly-migration.d.ts +14 -0
  1081. package/dist/utils/wall-assembly-migration.d.ts.map +1 -0
  1082. package/dist/utils/wall-assembly-migration.js +23 -0
  1083. package/dist/utils/wall-face-migration.d.ts +23 -0
  1084. package/dist/utils/wall-face-migration.d.ts.map +1 -0
  1085. package/dist/utils/wall-face-migration.js +246 -0
  1086. package/dist/validation/opening-bounds.d.ts +36 -0
  1087. package/dist/validation/opening-bounds.d.ts.map +1 -0
  1088. package/dist/validation/opening-bounds.js +34 -0
  1089. package/dist/validation/validate-build-json.d.ts +2 -0
  1090. package/dist/validation/validate-build-json.d.ts.map +1 -1
  1091. package/dist/validation/validate-build-json.js +36 -1
  1092. package/package.json +36 -3
  1093. package/dist/services/surface-hosting-procedural-parity.fixtures.d.ts +0 -13
  1094. package/dist/services/surface-hosting-procedural-parity.fixtures.d.ts.map +0 -1
  1095. package/dist/services/surface-hosting-procedural-parity.fixtures.js +0 -4691
  1096. package/dist/services/surface-hosting-shelf-parity.fixtures.d.ts +0 -1060
  1097. package/dist/services/surface-hosting-shelf-parity.fixtures.d.ts.map +0 -1
  1098. package/dist/services/surface-hosting-shelf-parity.fixtures.js +0 -3127
@@ -1,890 +1,20 @@
1
- import { GROUND_SUPPORT_ID } from '../hooks/spatial-grid/support-host-id.js';
2
- import { CeilingNode, SlabNode, ZoneNode, } from '../schema/index.js';
3
- import { DEFAULT_LEVEL_HEIGHT } from '../services/level-height.js';
4
- import { CEILING_CLAMP_MARGIN, findLevelAboveId, getCeilingClampBound, getLevelBelow, getLevelElevations, getStoredLevelHeight, } from '../services/storey.js';
5
- import { activeSceneCommitNodeIds, getSceneHistoryPauseDepth, pauseSceneHistory, resumeSceneHistory, subscribeSceneCommits, } from '../store/history-control.js';
6
- import { computeWallSlabSupport } from '../systems/slab/slab-support.js';
7
- import { getClampedWallCurveOffset, getWallCurveFrameAt, isCurvedWall, } from '../systems/wall/wall-curve.js';
8
- import { resolveWallTop } from '../systems/wall/wall-top.js';
9
- import { simplifyClosedPolygon } from './polygon-geometry.js';
10
- import { distanceToSegment, RoomTopologyIndex, } from './room-topology-index.js';
11
- import { levelBaseElevationAt } from './terrain-support.js';
12
- const DEFAULT_AUTO_SLAB_ELEVATION = 0.05;
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
- const roomPolygons = extractRoomPolygons(walls);
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.autoFromWalls ? 'auto' : 'manual',
937
- zone.boundaryWallIds.slice().sort().join(','),
938
- zone.polygon.map(([x, z]) => `${x.toFixed(4)},${z.toFixed(4)}`).join(';'),
939
- ].join('|');
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
- // Slab/ceiling POLYGONS stay out of the trigger signature: including
942
- // generated footprints caused delete/recreate feedback. Zones are included
943
- // only so a newly traced room footprint can adopt its enclosing walls
944
- // without waiting for the next remodel. Slab ELEVATIONS and the level's
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(`${node.id}:${(node.elevation ?? DEFAULT_AUTO_SLAB_ELEVATION).toFixed(4)}`);
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([...wallsByLevel.keys(), ...zonesByLevel.keys()]);
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' && typeof level.height === 'number' ? level.height.toFixed(4) : '';
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
- const lower = getLevelBelow(levelId, nodes);
1141
- if (lower)
1142
- levelIds.add(lower.id);
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 === 'slab') {
1154
- addLevelAndLower(node.parentId, nodes);
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 === 'zone') {
1157
- if (node.parentId)
1158
- levelIds.add(node.parentId);
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
- return levelIds;
1163
- }
1164
- function sameStringSet(a, b) {
1165
- if (a.length !== b.length)
1166
- return false;
1167
- const right = new Set(b);
1168
- return a.every((value) => right.has(value));
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 (update.elevation !== undefined &&
1395
- Math.abs(slab.elevation - update.elevation) > ROOM_VERTICAL_PLANE_EPSILON) {
1396
- data.elevation = update.elevation;
348
+ else if (node.type === 'ceiling') {
349
+ if (!(before[id] && current[id]) && node.parentId)
350
+ levelIds.add(node.parentId);
1397
351
  }
1398
- return Object.keys(data).length > 0 ? [{ id: slab.id, data }] : [];
1399
- }),
1400
- ...slabDemotions,
1401
- ];
1402
- const plannedSlabsForNaming = [...namingSlabs];
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
- const mergeContributors = roomIndex == null ? undefined : compatibleMergeCeilingsByRoomIndex.get(roomIndex);
1526
- const children = mergeContributors
1527
- ? [
1528
- ...new Set(mergeContributors.flatMap((contributor) => contributor.children)),
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
- for (const childId of contributor.children) {
1537
- childReparents.push({ id: childId, parentId: ceiling.id });
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
- return {
1589
- create: ceilingsToCreate,
1590
- update: ceilingsToUpdate,
1591
- delete: ceilingsToDelete,
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, walls) {
1613
- const rooms = extractRooms(walls);
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
- ...resolveWallSurfaceSides(wall, roomPolygons),
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 runSpaceDetection(levelIds, sceneStore, editorStore, nodes, previousNodes, previousRoomsByLevel) {
1630
- const { updateNodes } = sceneStore.getState();
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 children = levelChildren(nodes, levelId);
1640
- const walls = children.filter((node) => node?.type === 'wall' && node.parentId === levelId);
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
- // Refcount of outstanding pause requests, matching the pauseSceneHistory
1807
- // pattern. The community editor flips this off while the AI is actively
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
- /** Pause the wall-driven auto slab/ceiling sync. Refcounted — pair with `resumeSpaceDetection`. */
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 the wall-driven auto slab/ceiling sync. No-op if not currently paused. */
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 the wall-driven auto slab/ceiling sync is currently paused. */
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 (getSceneHistoryPauseDepth() > 0)
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
- // Paused: roll the snapshot forward so we don't backfill (and re-duplicate)
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
- runSpaceDetection([...levelsToUpdate], sceneStore, editorStore, nodes, previousNodes, previousRoomsByLevel);
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) {