@pascal-app/core 0.8.0 → 0.9.2

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 (471) hide show
  1. package/dist/events/bus.d.ts +67 -13
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/hooks/scene-registry/item-clip-registry.d.ts +18 -0
  4. package/dist/hooks/scene-registry/item-clip-registry.d.ts.map +1 -0
  5. package/dist/hooks/scene-registry/item-clip-registry.js +10 -0
  6. package/dist/hooks/scene-registry/scene-registry.d.ts +14 -23
  7. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  8. package/dist/hooks/scene-registry/scene-registry.js +59 -26
  9. package/dist/hooks/scene-registry/scene-registry.test.d.ts +2 -0
  10. package/dist/hooks/scene-registry/scene-registry.test.d.ts.map +1 -0
  11. package/dist/hooks/scene-registry/scene-registry.test.js +26 -0
  12. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
  13. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
  14. package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
  15. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
  16. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
  17. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +366 -0
  18. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +79 -11
  19. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  20. package/dist/hooks/spatial-grid/spatial-grid-manager.js +508 -141
  21. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
  22. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  23. package/dist/hooks/spatial-grid/spatial-grid-sync.js +109 -18
  24. package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts +2 -0
  25. package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts.map +1 -0
  26. package/dist/hooks/spatial-grid/wall-slab-overlap.test.js +363 -0
  27. package/dist/index.d.ts +36 -8
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +33 -6
  30. package/dist/lib/level-name.d.ts +4 -0
  31. package/dist/lib/level-name.d.ts.map +1 -0
  32. package/dist/lib/level-name.js +10 -0
  33. package/dist/lib/measurement-geometry.d.ts +19 -0
  34. package/dist/lib/measurement-geometry.d.ts.map +1 -0
  35. package/dist/lib/measurement-geometry.js +232 -0
  36. package/dist/lib/measurement-geometry.test.d.ts +2 -0
  37. package/dist/lib/measurement-geometry.test.d.ts.map +1 -0
  38. package/dist/lib/measurement-geometry.test.js +102 -0
  39. package/dist/lib/polygon-geometry.d.ts +0 -1
  40. package/dist/lib/polygon-geometry.d.ts.map +1 -1
  41. package/dist/lib/polygon-geometry.js +0 -18
  42. package/dist/lib/polygon-relations.d.ts +10 -0
  43. package/dist/lib/polygon-relations.d.ts.map +1 -0
  44. package/dist/lib/polygon-relations.js +73 -0
  45. package/dist/lib/selection-proxy.d.ts +4 -0
  46. package/dist/lib/selection-proxy.d.ts.map +1 -0
  47. package/dist/lib/selection-proxy.js +12 -0
  48. package/dist/lib/slab-polygon.d.ts +31 -2
  49. package/dist/lib/slab-polygon.d.ts.map +1 -1
  50. package/dist/lib/slab-polygon.js +525 -31
  51. package/dist/lib/slab-polygon.test.d.ts +2 -0
  52. package/dist/lib/slab-polygon.test.d.ts.map +1 -0
  53. package/dist/lib/slab-polygon.test.js +635 -0
  54. package/dist/lib/slots.d.ts +14 -0
  55. package/dist/lib/slots.d.ts.map +1 -0
  56. package/dist/lib/slots.js +26 -0
  57. package/dist/lib/space-detection-pause.test.d.ts +2 -0
  58. package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
  59. package/dist/lib/space-detection-pause.test.js +42 -0
  60. package/dist/lib/space-detection.d.ts +54 -2
  61. package/dist/lib/space-detection.d.ts.map +1 -1
  62. package/dist/lib/space-detection.js +599 -124
  63. package/dist/lib/space-detection.test.d.ts +2 -0
  64. package/dist/lib/space-detection.test.d.ts.map +1 -0
  65. package/dist/lib/space-detection.test.js +376 -0
  66. package/dist/lib/wall-distance.d.ts +60 -0
  67. package/dist/lib/wall-distance.d.ts.map +1 -0
  68. package/dist/lib/wall-distance.js +85 -0
  69. package/dist/lib/zone-quantities.d.ts +21 -0
  70. package/dist/lib/zone-quantities.d.ts.map +1 -0
  71. package/dist/lib/zone-quantities.js +183 -0
  72. package/dist/lib/zone-quantities.test.d.ts +2 -0
  73. package/dist/lib/zone-quantities.test.d.ts.map +1 -0
  74. package/dist/lib/zone-quantities.test.js +82 -0
  75. package/dist/material-library.d.ts +20 -1
  76. package/dist/material-library.d.ts.map +1 -1
  77. package/dist/material-library.js +3737 -200
  78. package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
  79. package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
  80. package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
  81. package/dist/registry/handles.d.ts +394 -0
  82. package/dist/registry/handles.d.ts.map +1 -0
  83. package/dist/registry/handles.js +24 -0
  84. package/dist/registry/index.d.ts +7 -0
  85. package/dist/registry/index.d.ts.map +1 -0
  86. package/dist/registry/index.js +4 -0
  87. package/dist/registry/registry.d.ts +136 -0
  88. package/dist/registry/registry.d.ts.map +1 -0
  89. package/dist/registry/registry.js +282 -0
  90. package/dist/registry/registry.test.d.ts +2 -0
  91. package/dist/registry/registry.test.d.ts.map +1 -0
  92. package/dist/registry/registry.test.js +196 -0
  93. package/dist/registry/relations-resolver.d.ts +42 -0
  94. package/dist/registry/relations-resolver.d.ts.map +1 -0
  95. package/dist/registry/relations-resolver.js +77 -0
  96. package/dist/registry/relations-resolver.test.d.ts +2 -0
  97. package/dist/registry/relations-resolver.test.d.ts.map +1 -0
  98. package/dist/registry/relations-resolver.test.js +183 -0
  99. package/dist/registry/scene-api.d.ts +44 -0
  100. package/dist/registry/scene-api.d.ts.map +1 -0
  101. package/dist/registry/scene-api.js +111 -0
  102. package/dist/registry/scene-api.test.d.ts +2 -0
  103. package/dist/registry/scene-api.test.d.ts.map +1 -0
  104. package/dist/registry/scene-api.test.js +183 -0
  105. package/dist/registry/subtree.d.ts +67 -0
  106. package/dist/registry/subtree.d.ts.map +1 -0
  107. package/dist/registry/subtree.js +123 -0
  108. package/dist/registry/subtree.test.d.ts +2 -0
  109. package/dist/registry/subtree.test.d.ts.map +1 -0
  110. package/dist/registry/subtree.test.js +163 -0
  111. package/dist/registry/types.d.ts +2099 -0
  112. package/dist/registry/types.d.ts.map +1 -0
  113. package/dist/registry/types.js +1 -0
  114. package/dist/schema/index.d.ts +35 -4
  115. package/dist/schema/index.d.ts.map +1 -1
  116. package/dist/schema/index.js +32 -4
  117. package/dist/schema/material.d.ts +13 -2
  118. package/dist/schema/material.d.ts.map +1 -1
  119. package/dist/schema/material.js +17 -5
  120. package/dist/schema/material.test.d.ts +2 -0
  121. package/dist/schema/material.test.d.ts.map +1 -0
  122. package/dist/schema/material.test.js +43 -0
  123. package/dist/schema/nodes/box-vent.d.ts +74 -0
  124. package/dist/schema/nodes/box-vent.d.ts.map +1 -0
  125. package/dist/schema/nodes/box-vent.js +63 -0
  126. package/dist/schema/nodes/building.d.ts +1 -1
  127. package/dist/schema/nodes/building.d.ts.map +1 -1
  128. package/dist/schema/nodes/building.js +3 -2
  129. package/dist/schema/nodes/cabinet.d.ts +534 -0
  130. package/dist/schema/nodes/cabinet.d.ts.map +1 -0
  131. package/dist/schema/nodes/cabinet.js +137 -0
  132. package/dist/schema/nodes/ceiling.d.ts +5 -2
  133. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  134. package/dist/schema/nodes/ceiling.js +5 -1
  135. package/dist/schema/nodes/chimney.d.ts +158 -0
  136. package/dist/schema/nodes/chimney.d.ts.map +1 -0
  137. package/dist/schema/nodes/chimney.js +67 -0
  138. package/dist/schema/nodes/column.d.ts +600 -7
  139. package/dist/schema/nodes/column.d.ts.map +1 -1
  140. package/dist/schema/nodes/column.js +589 -6
  141. package/dist/schema/nodes/cupola.d.ts +66 -0
  142. package/dist/schema/nodes/cupola.d.ts.map +1 -0
  143. package/dist/schema/nodes/cupola.js +28 -0
  144. package/dist/schema/nodes/door.d.ts +13 -5
  145. package/dist/schema/nodes/door.d.ts.map +1 -1
  146. package/dist/schema/nodes/door.js +12 -0
  147. package/dist/schema/nodes/dormer.d.ts +228 -0
  148. package/dist/schema/nodes/dormer.d.ts.map +1 -0
  149. package/dist/schema/nodes/dormer.js +119 -0
  150. package/dist/schema/nodes/downspout.d.ts +75 -0
  151. package/dist/schema/nodes/downspout.d.ts.map +1 -0
  152. package/dist/schema/nodes/downspout.js +63 -0
  153. package/dist/schema/nodes/duct-fitting.d.ts +91 -0
  154. package/dist/schema/nodes/duct-fitting.d.ts.map +1 -0
  155. package/dist/schema/nodes/duct-fitting.js +92 -0
  156. package/dist/schema/nodes/duct-segment.d.ts +61 -0
  157. package/dist/schema/nodes/duct-segment.d.ts.map +1 -0
  158. package/dist/schema/nodes/duct-segment.js +73 -0
  159. package/dist/schema/nodes/duct-terminal.d.ts +57 -0
  160. package/dist/schema/nodes/duct-terminal.d.ts.map +1 -0
  161. package/dist/schema/nodes/duct-terminal.js +51 -0
  162. package/dist/schema/nodes/elevator.d.ts +106 -0
  163. package/dist/schema/nodes/elevator.d.ts.map +1 -0
  164. package/dist/schema/nodes/elevator.js +57 -0
  165. package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
  166. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
  167. package/dist/schema/nodes/eyebrow-vent.js +40 -0
  168. package/dist/schema/nodes/fence.d.ts +19 -2
  169. package/dist/schema/nodes/fence.d.ts.map +1 -1
  170. package/dist/schema/nodes/fence.js +27 -2
  171. package/dist/schema/nodes/gutter.d.ts +84 -0
  172. package/dist/schema/nodes/gutter.d.ts.map +1 -0
  173. package/dist/schema/nodes/gutter.js +76 -0
  174. package/dist/schema/nodes/hvac-equipment.d.ts +63 -0
  175. package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -0
  176. package/dist/schema/nodes/hvac-equipment.js +55 -0
  177. package/dist/schema/nodes/item.d.ts +12 -0
  178. package/dist/schema/nodes/item.d.ts.map +1 -1
  179. package/dist/schema/nodes/item.js +20 -0
  180. package/dist/schema/nodes/level.d.ts +1 -1
  181. package/dist/schema/nodes/level.d.ts.map +1 -1
  182. package/dist/schema/nodes/level.js +4 -0
  183. package/dist/schema/nodes/lineset.d.ts +42 -0
  184. package/dist/schema/nodes/lineset.d.ts.map +1 -0
  185. package/dist/schema/nodes/lineset.js +38 -0
  186. package/dist/schema/nodes/liquid-line.d.ts +34 -0
  187. package/dist/schema/nodes/liquid-line.d.ts.map +1 -0
  188. package/dist/schema/nodes/liquid-line.js +24 -0
  189. package/dist/schema/nodes/measurement.d.ts +306 -0
  190. package/dist/schema/nodes/measurement.d.ts.map +1 -0
  191. package/dist/schema/nodes/measurement.js +87 -0
  192. package/dist/schema/nodes/measurement.test.d.ts +2 -0
  193. package/dist/schema/nodes/measurement.test.d.ts.map +1 -0
  194. package/dist/schema/nodes/measurement.test.js +139 -0
  195. package/dist/schema/nodes/pipe-fitting.d.ts +56 -0
  196. package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -0
  197. package/dist/schema/nodes/pipe-fitting.js +45 -0
  198. package/dist/schema/nodes/pipe-segment.d.ts +48 -0
  199. package/dist/schema/nodes/pipe-segment.d.ts.map +1 -0
  200. package/dist/schema/nodes/pipe-segment.js +36 -0
  201. package/dist/schema/nodes/pipe-trap.d.ts +43 -0
  202. package/dist/schema/nodes/pipe-trap.d.ts.map +1 -0
  203. package/dist/schema/nodes/pipe-trap.js +36 -0
  204. package/dist/schema/nodes/ridge-vent-defaults.test.d.ts +2 -0
  205. package/dist/schema/nodes/ridge-vent-defaults.test.d.ts.map +1 -0
  206. package/dist/schema/nodes/ridge-vent-defaults.test.js +205 -0
  207. package/dist/schema/nodes/ridge-vent.d.ts +82 -0
  208. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
  209. package/dist/schema/nodes/ridge-vent.js +248 -0
  210. package/dist/schema/nodes/roof-segment-shape.d.ts +90 -0
  211. package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -0
  212. package/dist/schema/nodes/roof-segment-shape.js +318 -0
  213. package/dist/schema/nodes/roof-segment-shape.test.d.ts +2 -0
  214. package/dist/schema/nodes/roof-segment-shape.test.d.ts.map +1 -0
  215. package/dist/schema/nodes/roof-segment-shape.test.js +175 -0
  216. package/dist/schema/nodes/roof-segment-surface.test.d.ts +2 -0
  217. package/dist/schema/nodes/roof-segment-surface.test.d.ts.map +1 -0
  218. package/dist/schema/nodes/roof-segment-surface.test.js +48 -0
  219. package/dist/schema/nodes/roof-segment-trim.test.d.ts +2 -0
  220. package/dist/schema/nodes/roof-segment-trim.test.d.ts.map +1 -0
  221. package/dist/schema/nodes/roof-segment-trim.test.js +107 -0
  222. package/dist/schema/nodes/roof-segment-walls.d.ts +101 -0
  223. package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -0
  224. package/dist/schema/nodes/roof-segment-walls.js +330 -0
  225. package/dist/schema/nodes/roof-segment-walls.test.d.ts +2 -0
  226. package/dist/schema/nodes/roof-segment-walls.test.d.ts.map +1 -0
  227. package/dist/schema/nodes/roof-segment-walls.test.js +92 -0
  228. package/dist/schema/nodes/roof-segment.d.ts +273 -3
  229. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  230. package/dist/schema/nodes/roof-segment.js +539 -3
  231. package/dist/schema/nodes/roof.d.ts +8 -8
  232. package/dist/schema/nodes/roof.d.ts.map +1 -1
  233. package/dist/schema/nodes/roof.js +4 -16
  234. package/dist/schema/nodes/shelf.d.ts +106 -0
  235. package/dist/schema/nodes/shelf.d.ts.map +1 -0
  236. package/dist/schema/nodes/shelf.js +90 -0
  237. package/dist/schema/nodes/site.d.ts +1 -122
  238. package/dist/schema/nodes/site.d.ts.map +1 -1
  239. package/dist/schema/nodes/site.js +2 -6
  240. package/dist/schema/nodes/skylight.d.ts +279 -0
  241. package/dist/schema/nodes/skylight.d.ts.map +1 -0
  242. package/dist/schema/nodes/skylight.js +152 -0
  243. package/dist/schema/nodes/slab.d.ts +5 -2
  244. package/dist/schema/nodes/slab.d.ts.map +1 -1
  245. package/dist/schema/nodes/slab.js +5 -1
  246. package/dist/schema/nodes/solar-panel.d.ts +117 -0
  247. package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
  248. package/dist/schema/nodes/solar-panel.js +47 -0
  249. package/dist/schema/nodes/stair-segment.d.ts +4 -4
  250. package/dist/schema/nodes/stair.d.ts +13 -12
  251. package/dist/schema/nodes/stair.d.ts.map +1 -1
  252. package/dist/schema/nodes/stair.js +3 -0
  253. package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
  254. package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
  255. package/dist/schema/nodes/surface-hole-metadata.js +3 -1
  256. package/dist/schema/nodes/turbine-vent.d.ts +68 -0
  257. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
  258. package/dist/schema/nodes/turbine-vent.js +41 -0
  259. package/dist/schema/nodes/wall.d.ts +245 -6
  260. package/dist/schema/nodes/wall.d.ts.map +1 -1
  261. package/dist/schema/nodes/wall.js +257 -0
  262. package/dist/schema/nodes/wall.test.d.ts +2 -0
  263. package/dist/schema/nodes/wall.test.d.ts.map +1 -0
  264. package/dist/schema/nodes/wall.test.js +208 -0
  265. package/dist/schema/nodes/window.d.ts +11 -3
  266. package/dist/schema/nodes/window.d.ts.map +1 -1
  267. package/dist/schema/nodes/window.js +12 -0
  268. package/dist/schema/nodes/zone.d.ts +2 -0
  269. package/dist/schema/nodes/zone.d.ts.map +1 -1
  270. package/dist/schema/nodes/zone.js +6 -0
  271. package/dist/schema/scene-material.d.ts +41 -0
  272. package/dist/schema/scene-material.d.ts.map +1 -0
  273. package/dist/schema/scene-material.js +9 -0
  274. package/dist/schema/types.d.ts +2850 -655
  275. package/dist/schema/types.d.ts.map +1 -1
  276. package/dist/schema/types.js +49 -0
  277. package/dist/services/alignment-anchors.d.ts +100 -0
  278. package/dist/services/alignment-anchors.d.ts.map +1 -0
  279. package/dist/services/alignment-anchors.js +312 -0
  280. package/dist/services/alignment-anchors.test.d.ts +2 -0
  281. package/dist/services/alignment-anchors.test.d.ts.map +1 -0
  282. package/dist/services/alignment-anchors.test.js +383 -0
  283. package/dist/services/alignment.d.ts +131 -0
  284. package/dist/services/alignment.d.ts.map +1 -0
  285. package/dist/services/alignment.js +171 -0
  286. package/dist/services/alignment.test.d.ts +2 -0
  287. package/dist/services/alignment.test.d.ts.map +1 -0
  288. package/dist/services/alignment.test.js +92 -0
  289. package/dist/services/drag-session.d.ts +47 -0
  290. package/dist/services/drag-session.d.ts.map +1 -0
  291. package/dist/services/drag-session.js +100 -0
  292. package/dist/services/drag-session.test.d.ts +2 -0
  293. package/dist/services/drag-session.test.d.ts.map +1 -0
  294. package/dist/services/drag-session.test.js +219 -0
  295. package/dist/services/hosting.d.ts +88 -0
  296. package/dist/services/hosting.d.ts.map +1 -0
  297. package/dist/services/hosting.js +131 -0
  298. package/dist/services/hosting.test.d.ts +2 -0
  299. package/dist/services/hosting.test.d.ts.map +1 -0
  300. package/dist/services/hosting.test.js +230 -0
  301. package/dist/services/index.d.ts +13 -0
  302. package/dist/services/index.d.ts.map +1 -0
  303. package/dist/services/index.js +12 -0
  304. package/dist/services/level-height.d.ts +28 -0
  305. package/dist/services/level-height.d.ts.map +1 -0
  306. package/dist/services/level-height.js +62 -0
  307. package/dist/services/movement.d.ts +48 -0
  308. package/dist/services/movement.d.ts.map +1 -0
  309. package/dist/services/movement.js +65 -0
  310. package/dist/services/movement.test.d.ts +2 -0
  311. package/dist/services/movement.test.d.ts.map +1 -0
  312. package/dist/services/movement.test.js +133 -0
  313. package/dist/services/opening-guides.d.ts +141 -0
  314. package/dist/services/opening-guides.d.ts.map +1 -0
  315. package/dist/services/opening-guides.js +267 -0
  316. package/dist/services/opening-guides.test.d.ts +2 -0
  317. package/dist/services/opening-guides.test.d.ts.map +1 -0
  318. package/dist/services/opening-guides.test.js +208 -0
  319. package/dist/services/port-connectivity.d.ts +115 -0
  320. package/dist/services/port-connectivity.d.ts.map +1 -0
  321. package/dist/services/port-connectivity.js +300 -0
  322. package/dist/services/port-connectivity.test.d.ts +2 -0
  323. package/dist/services/port-connectivity.test.d.ts.map +1 -0
  324. package/dist/services/port-connectivity.test.js +297 -0
  325. package/dist/services/riser-diagram.d.ts +40 -0
  326. package/dist/services/riser-diagram.d.ts.map +1 -0
  327. package/dist/services/riser-diagram.js +110 -0
  328. package/dist/services/riser-diagram.test.d.ts +2 -0
  329. package/dist/services/riser-diagram.test.d.ts.map +1 -0
  330. package/dist/services/riser-diagram.test.js +66 -0
  331. package/dist/services/single-undo-dance.test.d.ts +2 -0
  332. package/dist/services/single-undo-dance.test.d.ts.map +1 -0
  333. package/dist/services/single-undo-dance.test.js +210 -0
  334. package/dist/services/snap.d.ts +80 -0
  335. package/dist/services/snap.d.ts.map +1 -0
  336. package/dist/services/snap.js +140 -0
  337. package/dist/services/snap.test.d.ts +2 -0
  338. package/dist/services/snap.test.d.ts.map +1 -0
  339. package/dist/services/snap.test.js +137 -0
  340. package/dist/services/system-graph.d.ts +30 -0
  341. package/dist/services/system-graph.d.ts.map +1 -0
  342. package/dist/services/system-graph.js +164 -0
  343. package/dist/services/system-graph.test.d.ts +2 -0
  344. package/dist/services/system-graph.test.d.ts.map +1 -0
  345. package/dist/services/system-graph.test.js +138 -0
  346. package/dist/services/validate-dwv.d.ts +32 -0
  347. package/dist/services/validate-dwv.d.ts.map +1 -0
  348. package/dist/services/validate-dwv.js +118 -0
  349. package/dist/services/validate-dwv.test.d.ts +2 -0
  350. package/dist/services/validate-dwv.test.d.ts.map +1 -0
  351. package/dist/services/validate-dwv.test.js +109 -0
  352. package/dist/solar-panel-presets.d.ts +17 -0
  353. package/dist/solar-panel-presets.d.ts.map +1 -0
  354. package/dist/solar-panel-presets.js +43 -0
  355. package/dist/store/actions/node-actions.d.ts +14 -2
  356. package/dist/store/actions/node-actions.d.ts.map +1 -1
  357. package/dist/store/actions/node-actions.js +585 -15
  358. package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
  359. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
  360. package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
  361. package/dist/store/actions/reparent.test.d.ts +2 -0
  362. package/dist/store/actions/reparent.test.d.ts.map +1 -0
  363. package/dist/store/actions/reparent.test.js +212 -0
  364. package/dist/store/actions/ridge-vent-update.test.d.ts +2 -0
  365. package/dist/store/actions/ridge-vent-update.test.d.ts.map +1 -0
  366. package/dist/store/actions/ridge-vent-update.test.js +264 -0
  367. package/dist/store/history-control.d.ts +11 -0
  368. package/dist/store/history-control.d.ts.map +1 -1
  369. package/dist/store/history-control.js +33 -0
  370. package/dist/store/use-interactive.d.ts +39 -0
  371. package/dist/store/use-interactive.d.ts.map +1 -1
  372. package/dist/store/use-interactive.js +75 -0
  373. package/dist/store/use-live-node-overrides.d.ts +22 -0
  374. package/dist/store/use-live-node-overrides.d.ts.map +1 -0
  375. package/dist/store/use-live-node-overrides.js +59 -0
  376. package/dist/store/use-scene-dirty-tracking.test.d.ts +2 -0
  377. package/dist/store/use-scene-dirty-tracking.test.d.ts.map +1 -0
  378. package/dist/store/use-scene-dirty-tracking.test.js +68 -0
  379. package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
  380. package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
  381. package/dist/store/use-scene-elevator-migration.test.js +109 -0
  382. package/dist/store/use-scene-measurement-roundtrip.test.d.ts +2 -0
  383. package/dist/store/use-scene-measurement-roundtrip.test.d.ts.map +1 -0
  384. package/dist/store/use-scene-measurement-roundtrip.test.js +97 -0
  385. package/dist/store/use-scene-plugins.test.d.ts +2 -0
  386. package/dist/store/use-scene-plugins.test.d.ts.map +1 -0
  387. package/dist/store/use-scene-plugins.test.js +51 -0
  388. package/dist/store/use-scene-wall-slot-migration.test.d.ts +2 -0
  389. package/dist/store/use-scene-wall-slot-migration.test.d.ts.map +1 -0
  390. package/dist/store/use-scene-wall-slot-migration.test.js +210 -0
  391. package/dist/store/use-scene-window-migration.test.d.ts +2 -0
  392. package/dist/store/use-scene-window-migration.test.d.ts.map +1 -0
  393. package/dist/store/use-scene-window-migration.test.js +85 -0
  394. package/dist/store/use-scene.d.ts +28 -2
  395. package/dist/store/use-scene.d.ts.map +1 -1
  396. package/dist/store/use-scene.js +546 -49
  397. package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
  398. package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
  399. package/dist/systems/elevator/elevator-dispatch.js +57 -0
  400. package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
  401. package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
  402. package/dist/systems/elevator/elevator-geometry.js +50 -0
  403. package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
  404. package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
  405. package/dist/systems/elevator/elevator-opening-sync.js +218 -0
  406. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
  407. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
  408. package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
  409. package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
  410. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
  411. package/dist/systems/elevator/elevator-opening-system.js +48 -0
  412. package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
  413. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
  414. package/dist/systems/elevator/elevator-runtime-system.js +8 -0
  415. package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
  416. package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
  417. package/dist/systems/elevator/elevator-runtime.js +225 -0
  418. package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
  419. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
  420. package/dist/systems/elevator/elevator-runtime.test.js +64 -0
  421. package/dist/systems/elevator/elevator-service.d.ts +19 -0
  422. package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
  423. package/dist/systems/elevator/elevator-service.js +111 -0
  424. package/dist/systems/fence/fence-centerline.d.ts +12 -0
  425. package/dist/systems/fence/fence-centerline.d.ts.map +1 -0
  426. package/dist/systems/fence/fence-centerline.js +37 -0
  427. package/dist/systems/fence/fence-spline.d.ts +39 -0
  428. package/dist/systems/fence/fence-spline.d.ts.map +1 -0
  429. package/dist/systems/fence/fence-spline.js +196 -0
  430. package/dist/systems/fence/fence-spline.test.d.ts +2 -0
  431. package/dist/systems/fence/fence-spline.test.d.ts.map +1 -0
  432. package/dist/systems/fence/fence-spline.test.js +136 -0
  433. package/dist/systems/stair/stair-footprint.d.ts +44 -0
  434. package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
  435. package/dist/systems/stair/stair-footprint.js +178 -0
  436. package/dist/systems/stair/stair-opening-preview.d.ts +2447 -0
  437. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
  438. package/dist/systems/stair/stair-opening-preview.js +93 -0
  439. package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
  440. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
  441. package/dist/systems/stair/stair-opening-preview.test.js +80 -0
  442. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  443. package/dist/systems/stair/stair-opening-sync.js +155 -147
  444. package/dist/systems/stair/stair-opening-sync.test.js +424 -2
  445. package/dist/systems/stair/stair-opening-system.d.ts +2 -0
  446. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
  447. package/dist/systems/stair/stair-opening-system.js +104 -0
  448. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  449. package/dist/systems/wall/wall-mitering.js +38 -2
  450. package/dist/systems/wall/wall-mitering.test.d.ts +2 -0
  451. package/dist/systems/wall/wall-mitering.test.d.ts.map +1 -0
  452. package/dist/systems/wall/wall-mitering.test.js +63 -0
  453. package/dist/systems/wall/wall-move.d.ts +48 -0
  454. package/dist/systems/wall/wall-move.d.ts.map +1 -0
  455. package/dist/systems/wall/wall-move.js +193 -0
  456. package/dist/utils/clone-scene-graph.d.ts +7 -4
  457. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  458. package/dist/utils/clone-scene-graph.js +29 -7
  459. package/dist/utils/clone-scene-graph.test.d.ts +2 -0
  460. package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
  461. package/dist/utils/clone-scene-graph.test.js +62 -0
  462. package/dist/utils/heal-scene-graph.d.ts +18 -0
  463. package/dist/utils/heal-scene-graph.d.ts.map +1 -0
  464. package/dist/utils/heal-scene-graph.js +90 -0
  465. package/dist/utils/heal-scene-graph.test.d.ts +2 -0
  466. package/dist/utils/heal-scene-graph.test.d.ts.map +1 -0
  467. package/dist/utils/heal-scene-graph.test.js +88 -0
  468. package/dist/validation/validate-build-json.d.ts +44 -0
  469. package/dist/validation/validate-build-json.d.ts.map +1 -0
  470. package/dist/validation/validate-build-json.js +256 -0
  471. package/package.json +11 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stair-opening-preview.d.ts","sourceRoot":"","sources":["../../../src/systems/stair/stair-opening-preview.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC7E,OAA6B,EAAE,KAAK,iBAAiB,EAAE,MAAM,qCAAqC,CAAA;AAClG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AAGpE,KAAK,oBAAoB,GAAG;IAC1B,EAAE,EAAE,SAAS,CAAA;IACb,IAAI,EAAE,OAAO,CAAC,QAAQ,GAAG,WAAW,CAAC,CAAA;CACtC,CAAA;AAoBD,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,EAClD,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACrD,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,WAYvC;AAED,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,EAClD,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACrD,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwBvC;AAED,wBAAgB,qCAAqC;;mBAUlC,oBAAoB,EAAE;;EAiCxC"}
@@ -0,0 +1,93 @@
1
+ import useLiveNodeOverrides, {} from '../../store/use-live-node-overrides';
2
+ import useScene from '../../store/use-scene';
3
+ const SURFACE_OPENING_FIELDS = ['holes', 'holeMetadata'];
4
+ function isSurface(node) {
5
+ return node?.type === 'slab' || node?.type === 'ceiling';
6
+ }
7
+ function isStairOpeningInputNode(node) {
8
+ return node?.type === 'stair' || node?.type === 'stair-segment';
9
+ }
10
+ function omitPreviewSurfaceFields(override) {
11
+ const next = { ...override };
12
+ for (const field of SURFACE_OPENING_FIELDS) {
13
+ delete next[field];
14
+ }
15
+ return next;
16
+ }
17
+ export function hasLiveStairOpeningInputs(nodes, liveTransforms, liveOverrides, previewSurfaceIds) {
18
+ for (const nodeId of liveTransforms.keys()) {
19
+ if (nodes[nodeId]?.type === 'stair')
20
+ return true;
21
+ }
22
+ for (const [nodeId, override] of liveOverrides) {
23
+ if (previewSurfaceIds.has(nodeId))
24
+ continue;
25
+ if (Object.keys(override).length > 0 && isStairOpeningInputNode(nodes[nodeId]))
26
+ return true;
27
+ }
28
+ return false;
29
+ }
30
+ export function getNodesWithLiveStairOpeningInputs(nodes, liveTransforms, liveOverrides, previewSurfaceIds) {
31
+ const nextNodes = { ...nodes };
32
+ for (const [nodeId, override] of liveOverrides) {
33
+ const node = nextNodes[nodeId];
34
+ if (!node)
35
+ continue;
36
+ const values = previewSurfaceIds.has(nodeId) ? omitPreviewSurfaceFields(override) : override;
37
+ if (Object.keys(values).length === 0)
38
+ continue;
39
+ nextNodes[nodeId] = { ...node, ...values };
40
+ }
41
+ for (const [nodeId, transform] of liveTransforms) {
42
+ const node = nextNodes[nodeId];
43
+ if (node?.type !== 'stair')
44
+ continue;
45
+ nextNodes[nodeId] = {
46
+ ...node,
47
+ position: transform.position,
48
+ rotation: transform.rotation,
49
+ };
50
+ }
51
+ return nextNodes;
52
+ }
53
+ export function createSurfaceOpeningPreviewController() {
54
+ const previewSurfaceIds = new Set();
55
+ const clearSurface = (id) => {
56
+ useLiveNodeOverrides.getState().clearFields(id, SURFACE_OPENING_FIELDS);
57
+ useScene.getState().markDirty(id);
58
+ };
59
+ return {
60
+ previewSurfaceIds,
61
+ apply(updates) {
62
+ const scene = useScene.getState();
63
+ const nextSurfaceIds = new Set();
64
+ for (const update of updates) {
65
+ const node = scene.nodes[update.id];
66
+ if (!isSurface(node))
67
+ continue;
68
+ if (!('holes' in update.data || 'holeMetadata' in update.data))
69
+ continue;
70
+ nextSurfaceIds.add(update.id);
71
+ useLiveNodeOverrides.getState().set(update.id, {
72
+ holes: update.data.holes ?? [],
73
+ holeMetadata: update.data.holeMetadata ?? [],
74
+ });
75
+ scene.markDirty(update.id);
76
+ }
77
+ for (const id of previewSurfaceIds) {
78
+ if (!nextSurfaceIds.has(id))
79
+ clearSurface(id);
80
+ }
81
+ previewSurfaceIds.clear();
82
+ for (const id of nextSurfaceIds) {
83
+ previewSurfaceIds.add(id);
84
+ }
85
+ },
86
+ clear() {
87
+ for (const id of previewSurfaceIds) {
88
+ clearSurface(id);
89
+ }
90
+ previewSurfaceIds.clear();
91
+ },
92
+ };
93
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=stair-opening-preview.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stair-opening-preview.test.d.ts","sourceRoot":"","sources":["../../../src/systems/stair/stair-opening-preview.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,80 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { BuildingNode, LevelNode, SlabNode, StairNode, StairSegmentNode } from '../../schema';
3
+ import { getNodesWithLiveStairOpeningInputs, hasLiveStairOpeningInputs, } from './stair-opening-preview';
4
+ import { syncAutoStairOpenings } from './stair-opening-sync';
5
+ describe('stair opening previews', () => {
6
+ test('computes auto openings from live stair transforms', () => {
7
+ const building = BuildingNode.parse({ name: 'Building' });
8
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
9
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
10
+ const slab = SlabNode.parse({
11
+ name: 'Upper Slab',
12
+ parentId: upper.id,
13
+ polygon: [
14
+ [0, 0],
15
+ [5, 0],
16
+ [5, 4],
17
+ [0, 4],
18
+ ],
19
+ });
20
+ const segment = StairSegmentNode.parse({
21
+ parentId: 'stair_live',
22
+ width: 1,
23
+ length: 3,
24
+ height: 2.5,
25
+ stepCount: 12,
26
+ });
27
+ const stair = StairNode.parse({
28
+ id: 'stair_live',
29
+ name: 'Live Stair',
30
+ parentId: ground.id,
31
+ position: [1, 0, 0.2],
32
+ stairType: 'straight',
33
+ fromLevelId: ground.id,
34
+ toLevelId: upper.id,
35
+ slabOpeningMode: 'destination',
36
+ children: [segment.id],
37
+ });
38
+ const nodes = Object.fromEntries([building, ground, upper, slab, stair, { ...segment, parentId: stair.id }].map((node) => [
39
+ node.id,
40
+ node,
41
+ ]));
42
+ const liveTransforms = new Map([
43
+ [stair.id, { position: [3, 0, 0.2], rotation: 0 }],
44
+ ]);
45
+ const liveOverrides = new Map();
46
+ expect(hasLiveStairOpeningInputs(nodes, liveTransforms, liveOverrides, new Set())).toBe(true);
47
+ const previewNodes = getNodesWithLiveStairOpeningInputs(nodes, liveTransforms, liveOverrides, new Set());
48
+ const updates = syncAutoStairOpenings(previewNodes);
49
+ const hole = updates.find((update) => update.id === slab.id)?.data.holes?.[0];
50
+ expect(hole).toBeDefined();
51
+ expect(Math.max(...hole.map(([x]) => x))).toBeGreaterThan(3.4);
52
+ });
53
+ test('ignores its own live surface overrides as preview inputs', () => {
54
+ const slab = SlabNode.parse({
55
+ polygon: [
56
+ [0, 0],
57
+ [4, 0],
58
+ [4, 4],
59
+ [0, 4],
60
+ ],
61
+ });
62
+ const nodes = { [slab.id]: slab };
63
+ const liveOverrides = new Map([
64
+ [
65
+ slab.id,
66
+ {
67
+ holes: [
68
+ [
69
+ [1, 1],
70
+ [2, 1],
71
+ [2, 2],
72
+ [1, 2],
73
+ ],
74
+ ],
75
+ },
76
+ ],
77
+ ]);
78
+ expect(hasLiveStairOpeningInputs(nodes, new Map(), liveOverrides, new Set([slab.id]))).toBe(false);
79
+ });
80
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"stair-opening-sync.d.ts","sourceRoot":"","sources":["../../../src/systems/stair/stair-opening-sync.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EACT,WAAW,EACX,QAAQ,EAGT,MAAM,cAAc,CAAA;AA6pBrB,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;QAQvC,SAAS;UAAQ,OAAO,CAAC,QAAQ,GAAG,WAAW,CAAC;IAuG5E"}
1
+ {"version":3,"file":"stair-opening-sync.d.ts","sourceRoot":"","sources":["../../../src/systems/stair/stair-opening-sync.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EACT,WAAW,EACX,QAAQ,EAIT,MAAM,cAAc,CAAA;AA0qBrB,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;QAQvC,SAAS;UAAQ,OAAO,CAAC,QAAQ,GAAG,WAAW,CAAC;IAqG5E"}
@@ -1,5 +1,7 @@
1
- import { resolveLevelId } from '../../hooks/spatial-grid/spatial-grid-sync';
1
+ import { resolveBuildingForLevel, resolveLevelId } from '../../hooks/spatial-grid/spatial-grid-sync';
2
+ import { polygonContainsPolygon, polygonsOverlap } from '../../lib/polygon-relations';
2
3
  import { DEFAULT_WALL_HEIGHT } from '../wall/wall-footprint';
4
+ import { computeSegmentTransforms, rotateXZ } from './stair-footprint';
3
5
  const CURVED_STAIR_SLAB_OPENING_RATIO = 0.8;
4
6
  const STRAIGHT_STAIR_TARGET_THRESHOLD_MIN = 0.35;
5
7
  const STAIR_SLAB_OPENING_TIGHTENING = 0;
@@ -30,99 +32,100 @@ function metadataEqual(left, right) {
30
32
  if (left.length !== right.length)
31
33
  return false;
32
34
  return left.every((entry, index) => entry.source === right[index]?.source &&
35
+ (entry.elevatorId ?? null) === (right[index]?.elevatorId ?? null) &&
33
36
  (entry.stairId ?? null) === (right[index]?.stairId ?? null));
34
37
  }
35
38
  function normalizeExistingMetadata(holes, metadata) {
36
39
  return holes.map((_, index) => metadata?.[index] ?? { source: 'manual' });
37
40
  }
38
- function expandPolygonFromCentroid(polygon, offset) {
39
- if (Math.abs(offset) < 1e-6) {
40
- return polygon.map(([x, z]) => [x, z]);
41
- }
42
- const centroid = polygon.reduce((acc, [x, z]) => {
43
- acc.x += x;
44
- acc.z += z;
45
- return acc;
46
- }, { x: 0, z: 0 });
47
- centroid.x /= Math.max(polygon.length, 1);
48
- centroid.z /= Math.max(polygon.length, 1);
49
- return polygon.map(([x, z]) => {
50
- const dx = x - centroid.x;
51
- const dz = z - centroid.z;
52
- const length = Math.hypot(dx, dz);
53
- if (length < 1e-6) {
54
- return [x, z];
55
- }
56
- const scale = Math.max(0.1, (length + offset) / length);
57
- return [centroid.x + dx * scale, centroid.z + dz * scale];
58
- });
59
- }
60
- function rotateXZ(x, z, angle) {
61
- const cos = Math.cos(angle);
62
- const sin = Math.sin(angle);
63
- return [x * cos + z * sin, -x * sin + z * cos];
64
- }
65
- function computeSegmentTransforms(segments) {
66
- const transforms = [];
67
- let currentX = 0;
68
- let currentY = 0;
69
- let currentZ = 0;
70
- let currentRot = 0;
71
- for (let index = 0; index < segments.length; index++) {
72
- const segment = segments[index];
73
- if (!segment)
74
- continue;
75
- if (index === 0) {
76
- transforms.push({
77
- position: [currentX, currentY, currentZ],
78
- rotation: currentRot,
79
- });
80
- continue;
81
- }
82
- const previous = segments[index - 1];
83
- if (!previous)
84
- continue;
85
- let attachX = 0;
86
- let attachZ = 0;
87
- let rotationDelta = 0;
88
- switch (segment.attachmentSide) {
89
- case 'front':
90
- attachX = 0;
91
- attachZ = previous.length;
92
- break;
93
- case 'left':
94
- attachX = previous.width / 2;
95
- attachZ = previous.length / 2;
96
- rotationDelta = Math.PI / 2;
97
- break;
98
- case 'right':
99
- attachX = -previous.width / 2;
100
- attachZ = previous.length / 2;
101
- rotationDelta = -Math.PI / 2;
102
- break;
103
- }
104
- const [deltaX, deltaZ] = rotateXZ(attachX, attachZ, currentRot);
105
- currentX += deltaX;
106
- currentY += previous.height;
107
- currentZ += deltaZ;
108
- currentRot += rotationDelta;
109
- transforms.push({
110
- position: [currentX, currentY, currentZ],
111
- rotation: currentRot,
112
- });
113
- }
114
- return transforms;
115
- }
41
+ // (Removing expandPolygonRadially in favor of geometric expansion inside the polygon generators)
42
+ // `rotateXZ` + `computeSegmentTransforms` are shared with the alignment-anchor
43
+ // footprint via `./stair-footprint` so both derive the chain identically.
116
44
  function getLevelNumber(levelId, nodes) {
117
45
  if (!levelId)
118
46
  return undefined;
119
47
  const node = nodes[levelId];
120
48
  return node?.type === 'level' ? node.level : undefined;
121
49
  }
50
+ function getLevelBuildingId(levelId, nodes) {
51
+ if (!levelId)
52
+ return null;
53
+ return resolveBuildingForLevel(levelId, nodes);
54
+ }
55
+ function normalizeLevelId(levelId, nodes) {
56
+ if (!levelId)
57
+ return null;
58
+ return nodes[levelId]?.type === 'level' ? levelId : null;
59
+ }
60
+ function getBuildingLevels(buildingId, nodes) {
61
+ const building = buildingId ? nodes[buildingId] : null;
62
+ if (building?.type !== 'building')
63
+ return [];
64
+ const levels = new Map();
65
+ for (const childId of building.children ?? []) {
66
+ const child = nodes[childId];
67
+ if (child?.type === 'level')
68
+ levels.set(child.id, child);
69
+ }
70
+ for (const candidate of Object.values(nodes)) {
71
+ if (candidate?.type === 'level' && candidate.parentId === building.id) {
72
+ levels.set(candidate.id, candidate);
73
+ }
74
+ }
75
+ return Array.from(levels.values()).sort((left, right) => left.level - right.level);
76
+ }
77
+ function inferSourceLevelForDestination(destinationLevelId, nodes) {
78
+ if (!destinationLevelId)
79
+ return null;
80
+ const destination = nodes[destinationLevelId];
81
+ if (destination?.type !== 'level')
82
+ return null;
83
+ const buildingId = getLevelBuildingId(destinationLevelId, nodes);
84
+ return (getBuildingLevels(buildingId, nodes)
85
+ .filter((level) => level.level < destination.level)
86
+ .at(-1)?.id ?? null);
87
+ }
88
+ function inferDestinationLevelForSource(sourceLevelId, nodes) {
89
+ if (!sourceLevelId)
90
+ return null;
91
+ const source = nodes[sourceLevelId];
92
+ if (source?.type !== 'level')
93
+ return null;
94
+ const buildingId = getLevelBuildingId(sourceLevelId, nodes);
95
+ return (getBuildingLevels(buildingId, nodes).find((level) => level.level > source.level)?.id ?? null);
96
+ }
97
+ function levelsShareBuilding(leftLevelId, rightLevelId, nodes) {
98
+ if (!(leftLevelId && rightLevelId))
99
+ return true;
100
+ const leftBuildingId = getLevelBuildingId(leftLevelId, nodes);
101
+ const rightBuildingId = getLevelBuildingId(rightLevelId, nodes);
102
+ return !(leftBuildingId && rightBuildingId && leftBuildingId !== rightBuildingId);
103
+ }
104
+ function isInStairBuildingScope(stair, surfaceLevelId, nodes) {
105
+ const { fromLevelId, toLevelId } = getResolvedStairLevelIds(stair, nodes);
106
+ const fromBuildingId = getLevelBuildingId(fromLevelId, nodes);
107
+ const toBuildingId = getLevelBuildingId(toLevelId, nodes);
108
+ const surfaceBuildingId = getLevelBuildingId(surfaceLevelId, nodes);
109
+ if (fromBuildingId && toBuildingId && fromBuildingId !== toBuildingId)
110
+ return false;
111
+ if (fromBuildingId && surfaceBuildingId && fromBuildingId !== surfaceBuildingId)
112
+ return false;
113
+ if (toBuildingId && surfaceBuildingId && toBuildingId !== surfaceBuildingId)
114
+ return false;
115
+ return true;
116
+ }
122
117
  function getResolvedStairLevelIds(stair, nodes) {
123
- const parentLevelId = resolveLevelId(stair, nodes);
124
- const fromLevelId = stair.fromLevelId ?? parentLevelId;
125
- const toLevelId = stair.toLevelId ?? fromLevelId;
118
+ const parentLevelId = normalizeLevelId(resolveLevelId(stair, nodes), nodes);
119
+ const explicitToLevelId = normalizeLevelId(stair.toLevelId, nodes);
120
+ const fromLevelId = normalizeLevelId(stair.fromLevelId, nodes) ??
121
+ parentLevelId ??
122
+ inferSourceLevelForDestination(explicitToLevelId, nodes);
123
+ const explicitToLevelIsUsable = explicitToLevelId &&
124
+ explicitToLevelId !== fromLevelId &&
125
+ levelsShareBuilding(fromLevelId, explicitToLevelId, nodes);
126
+ const toLevelId = explicitToLevelIsUsable
127
+ ? explicitToLevelId
128
+ : inferDestinationLevelForSource(fromLevelId, nodes);
126
129
  return { fromLevelId, toLevelId };
127
130
  }
128
131
  function resolveStraightSegments(stair, nodes) {
@@ -179,7 +182,7 @@ function getStraightSegmentSlicePolygon(stair, layout, startAlong, endAlong) {
179
182
  }
180
183
  function getStraightFlightOpeningDepth(stair, segment) {
181
184
  const treadDepth = Math.max(0.2, segment.length / Math.max(segment.stepCount || stair.stepCount || 10, 1));
182
- return Math.min(segment.length, Math.max(treadDepth * 6, segment.length * 0.62, 1.8));
185
+ return Math.min(segment.length, Math.max(treadDepth * 10, segment.length * 0.8, 3.0));
183
186
  }
184
187
  function polygonArea(points) {
185
188
  let area = 0;
@@ -192,34 +195,8 @@ function polygonArea(points) {
192
195
  }
193
196
  return area / 2;
194
197
  }
195
- function pointOnSegment(point, a, b, tolerance = 1e-6) {
196
- const cross = (point[1] - a[1]) * (b[0] - a[0]) - (point[0] - a[0]) * (b[1] - a[1]);
197
- if (Math.abs(cross) > tolerance)
198
- return false;
199
- const dot = (point[0] - a[0]) * (b[0] - a[0]) + (point[1] - a[1]) * (b[1] - a[1]);
200
- if (dot < -tolerance)
201
- return false;
202
- const lenSq = (b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2;
203
- return dot <= lenSq + tolerance;
204
- }
205
- function pointInPolygon(point, polygon) {
206
- if (polygon.length < 3)
207
- return false;
208
- let inside = false;
209
- const [x, z] = point;
210
- for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
211
- const a = polygon[i];
212
- const b = polygon[j];
213
- if (pointOnSegment(point, a, b))
214
- return true;
215
- const intersects = a[1] > z !== b[1] > z && x < ((b[0] - a[0]) * (z - a[1])) / (b[1] - a[1]) + a[0];
216
- if (intersects)
217
- inside = !inside;
218
- }
219
- return inside;
220
- }
221
- function polygonContainsPolygon(outer, inner) {
222
- return inner.every((point) => pointInPolygon(point, outer));
198
+ function isCoveredByExistingHole(existingHoles, autoHole) {
199
+ return existingHoles.some((existingHole) => polygonContainsPolygon(existingHole, autoHole));
223
200
  }
224
201
  function getAxisAlignedRectFromPolygon(polygon) {
225
202
  if (polygon.length < 4)
@@ -318,13 +295,15 @@ function buildUnionPolygonsFromRects(rects) {
318
295
  }
319
296
  return polygons;
320
297
  }
321
- function getCurvedOpeningPolygon(stair) {
298
+ function getCurvedOpeningPolygon(stair, offset = 0) {
322
299
  const width = Math.max(stair.width ?? 1, 0.4);
323
- const innerRadius = Math.max(0.2, stair.innerRadius ?? 0.9);
324
- const outerRadius = innerRadius + width;
300
+ const innerRadius = Math.max(0.01, (stair.innerRadius ?? 0.9) - offset);
301
+ const outerRadius = (stair.innerRadius ?? 0.9) + width + offset;
325
302
  const totalSweep = stair.sweepAngle ?? Math.PI / 2;
326
- const openingSweep = Math.sign(totalSweep || 1) *
327
- Math.max(Math.abs(totalSweep) * CURVED_STAIR_SLAB_OPENING_RATIO, Math.abs(totalSweep) / Math.max(stair.stepCount ?? 1, 1));
303
+ const baseOpeningSweep = Math.abs(totalSweep) *
304
+ Math.max(CURVED_STAIR_SLAB_OPENING_RATIO, 1 / Math.max(stair.stepCount ?? 1, 1));
305
+ const angleOffset = offset / Math.max(innerRadius, 0.1);
306
+ const openingSweep = Math.sign(totalSweep || 1) * Math.min(Math.abs(totalSweep), baseOpeningSweep + angleOffset * 2);
328
307
  const startAngle = totalSweep / 2 - openingSweep;
329
308
  const endAngle = totalSweep / 2;
330
309
  const segmentCount = Math.max(10, Math.min(32, Math.ceil(Math.abs(openingSweep) / (Math.PI / 24) + Math.max(stair.stepCount ?? 1, 1) * 0.5)));
@@ -342,21 +321,21 @@ function getCurvedOpeningPolygon(stair) {
342
321
  }
343
322
  return [...outerPoints, ...innerPoints];
344
323
  }
345
- function getSpiralOpeningPolygon(stair) {
346
- const radius = Math.max(0.05, stair.innerRadius ?? 0.9) + Math.max(stair.width ?? 1, 0.4);
324
+ function getSpiralOpeningPolygon(stair, offset = 0) {
325
+ const radius = Math.max(0.05, stair.innerRadius ?? 0.9) + Math.max(stair.width ?? 1, 0.4) + offset;
347
326
  const segmentCount = 48;
348
327
  return Array.from({ length: segmentCount }).map((_, index) => {
349
328
  const angle = (index / segmentCount) * Math.PI * 2;
350
329
  return toWorldPlanPoint(stair, Math.cos(angle) * radius, Math.sin(angle) * radius);
351
330
  });
352
331
  }
353
- function getStraightOpeningPolygonsForSurface(stair, nodes, targetElevation) {
332
+ function getStraightOpeningPolygonsForSurface(stair, nodes, targetElevation, openingOffsetOverride) {
354
333
  const layouts = getStraightStairLayouts(stair, nodes);
355
334
  if (layouts.length === 0)
356
335
  return [];
357
336
  const riserHeight = (stair.totalRise ?? 2.5) / Math.max(stair.stepCount ?? 10, 1);
358
337
  const targetThreshold = Math.max(riserHeight * 2, STRAIGHT_STAIR_TARGET_THRESHOLD_MIN);
359
- const openingOffset = Math.max(stair.openingOffset ?? 0, 0);
338
+ const openingOffset = Math.max(openingOffsetOverride ?? stair.openingOffset ?? 0, 0);
360
339
  const openingRects = [];
361
340
  for (let index = 0; index < layouts.length; index += 1) {
362
341
  const layout = layouts[index];
@@ -409,20 +388,37 @@ function getStraightOpeningPolygonsForSurface(stair, nodes, targetElevation) {
409
388
  }
410
389
  return fallbackLayout ? [getStraightSegmentFootprintPolygon(stair, fallbackLayout)] : [];
411
390
  }
412
- function getStairOpeningPolygons(stair, nodes, targetElevation) {
391
+ function getStairOpeningPolygons(stair, nodes, targetElevation, openingOffsetOverride) {
413
392
  if ((stair.slabOpeningMode ?? 'none') !== 'destination') {
414
393
  return [];
415
394
  }
395
+ const openingOffset = Math.max(openingOffsetOverride ?? stair.openingOffset ?? 0, 0);
416
396
  if (stair.stairType === 'curved') {
417
- return [getCurvedOpeningPolygon(stair)];
397
+ return [
398
+ getCurvedOpeningPolygon(stair, Math.max(openingOffset - STAIR_SLAB_OPENING_TIGHTENING, 0)),
399
+ ];
418
400
  }
419
401
  if (stair.stairType === 'spiral') {
420
- return [getSpiralOpeningPolygon(stair)];
402
+ const offset = Math.max(openingOffset - STAIR_SLAB_OPENING_TIGHTENING, 0);
403
+ return [getSpiralOpeningPolygon(stair, offset)];
421
404
  }
422
405
  if (typeof targetElevation === 'number') {
423
- return getStraightOpeningPolygonsForSurface(stair, nodes, targetElevation);
406
+ return getStraightOpeningPolygonsForSurface(stair, nodes, targetElevation, openingOffset);
424
407
  }
425
- return getStraightOpeningPolygonsForSurface(stair, nodes, Math.max(...getStraightStairLayouts(stair, nodes).map((layout) => layout.topElevation), 0));
408
+ return getStraightOpeningPolygonsForSurface(stair, nodes, Math.max(...getStraightStairLayouts(stair, nodes).map((layout) => layout.topElevation), 0), openingOffset);
409
+ }
410
+ function getApplicableStairOpeningPolygons(stair, nodes, targetElevation, surfacePolygon) {
411
+ const configuredOffset = Math.max(stair.openingOffset ?? 0, 0);
412
+ const polygons = getStairOpeningPolygons(stair, nodes, targetElevation, configuredOffset);
413
+ const overlappingPolygons = polygons.filter((polygon) => polygonsOverlap(surfacePolygon, polygon));
414
+ if (overlappingPolygons.length === polygons.length || configuredOffset <= 1e-6) {
415
+ return overlappingPolygons;
416
+ }
417
+ const fallbackPolygons = getStairOpeningPolygons(stair, nodes, targetElevation, 0);
418
+ const overlappingFallbackPolygons = fallbackPolygons.filter((polygon) => polygonsOverlap(surfacePolygon, polygon));
419
+ return overlappingFallbackPolygons.length === fallbackPolygons.length
420
+ ? overlappingFallbackPolygons
421
+ : overlappingPolygons;
426
422
  }
427
423
  function getTargetSlabElevationForStair(stair, slab, slabLevelId, nodes) {
428
424
  const { fromLevelId } = getResolvedStairLevelIds(stair, nodes);
@@ -451,6 +447,8 @@ function shouldApplyStairToSlab(stair, slabLevelId, nodes) {
451
447
  const fromLevel = getLevelNumber(fromLevelId, nodes);
452
448
  const toLevel = getLevelNumber(toLevelId, nodes);
453
449
  const slabLevel = getLevelNumber(slabLevelId, nodes);
450
+ if (!isInStairBuildingScope(stair, slabLevelId, nodes))
451
+ return false;
454
452
  if (slabLevel === undefined) {
455
453
  return toLevelId === slabLevelId;
456
454
  }
@@ -466,6 +464,8 @@ function shouldApplyStairToCeiling(stair, ceilingLevelId, nodes) {
466
464
  const fromLevel = getLevelNumber(fromLevelId, nodes);
467
465
  const toLevel = getLevelNumber(toLevelId, nodes);
468
466
  const ceilingLevel = getLevelNumber(ceilingLevelId, nodes);
467
+ if (!isInStairBuildingScope(stair, ceilingLevelId, nodes))
468
+ return false;
469
469
  if (ceilingLevel === undefined) {
470
470
  return fromLevelId === ceilingLevelId;
471
471
  }
@@ -485,24 +485,28 @@ export function syncAutoStairOpenings(nodes) {
485
485
  const slabLevelId = resolveLevelId(slab, nodes);
486
486
  const existingHoles = slab.holes ?? [];
487
487
  const existingMetadata = normalizeExistingMetadata(existingHoles, slab.holeMetadata);
488
- const manualHoles = existingHoles.filter((_hole, index) => existingMetadata[index]?.source !== 'stair');
489
- const manualMetadata = existingMetadata
490
- .filter((entry) => entry.source !== 'stair')
491
- .map((entry) => ({ ...entry }));
488
+ const preservedHoles = existingHoles
489
+ .map((polygon, index) => ({ metadata: existingMetadata[index], polygon }))
490
+ .filter((entry) => entry.metadata.source !== 'stair');
491
+ const preservedHolePolygons = preservedHoles.map((entry) => entry.polygon);
492
492
  const stairHoles = stairs
493
493
  .filter((stair) => shouldApplyStairToSlab(stair, slabLevelId, nodes))
494
- .flatMap((stair) => getStairOpeningPolygons(stair, nodes, getTargetSlabElevationForStair(stair, slab, slabLevelId, nodes)).map((polygon) => ({
495
- polygon: stair.stairType === 'straight'
496
- ? polygon
497
- : expandPolygonFromCentroid(polygon, Math.max((stair.openingOffset ?? 0) - STAIR_SLAB_OPENING_TIGHTENING, 0)),
494
+ .flatMap((stair) => getApplicableStairOpeningPolygons(stair, nodes, getTargetSlabElevationForStair(stair, slab, slabLevelId, nodes), slab.polygon).map((polygon) => ({
495
+ polygon,
498
496
  metadata: {
499
497
  source: 'stair',
500
498
  stairId: stair.id,
501
499
  },
502
500
  })))
503
- .filter((hole) => polygonContainsPolygon(slab.polygon, hole.polygon));
504
- const nextHoles = [...manualHoles, ...stairHoles.map((hole) => hole.polygon)];
505
- const nextMetadata = [...manualMetadata, ...stairHoles.map((hole) => hole.metadata)];
501
+ .filter((hole) => !isCoveredByExistingHole(preservedHolePolygons, hole.polygon));
502
+ const nextHoles = [
503
+ ...preservedHoles.map((hole) => hole.polygon),
504
+ ...stairHoles.map((hole) => hole.polygon),
505
+ ];
506
+ const nextMetadata = [
507
+ ...preservedHoles.map((hole) => ({ ...hole.metadata })),
508
+ ...stairHoles.map((hole) => hole.metadata),
509
+ ];
506
510
  if (!(polygonsEqual(existingHoles, nextHoles) && metadataEqual(existingMetadata, nextMetadata))) {
507
511
  updates.push({
508
512
  id: slab.id,
@@ -517,24 +521,28 @@ export function syncAutoStairOpenings(nodes) {
517
521
  const ceilingLevelId = resolveLevelId(ceiling, nodes);
518
522
  const existingHoles = ceiling.holes ?? [];
519
523
  const existingMetadata = normalizeExistingMetadata(existingHoles, ceiling.holeMetadata);
520
- const manualHoles = existingHoles.filter((_hole, index) => existingMetadata[index]?.source !== 'stair');
521
- const manualMetadata = existingMetadata
522
- .filter((entry) => entry.source !== 'stair')
523
- .map((entry) => ({ ...entry }));
524
+ const preservedHoles = existingHoles
525
+ .map((polygon, index) => ({ metadata: existingMetadata[index], polygon }))
526
+ .filter((entry) => entry.metadata.source !== 'stair');
527
+ const preservedHolePolygons = preservedHoles.map((entry) => entry.polygon);
524
528
  const stairHoles = stairs
525
529
  .filter((stair) => shouldApplyStairToCeiling(stair, ceilingLevelId, nodes))
526
- .flatMap((stair) => getStairOpeningPolygons(stair, nodes, getTargetCeilingElevationForStair(stair, ceiling, ceilingLevelId, nodes)).map((polygon) => ({
527
- polygon: stair.stairType === 'straight'
528
- ? polygon
529
- : expandPolygonFromCentroid(polygon, Math.max((stair.openingOffset ?? 0) - STAIR_SLAB_OPENING_TIGHTENING, 0)),
530
+ .flatMap((stair) => getApplicableStairOpeningPolygons(stair, nodes, getTargetCeilingElevationForStair(stair, ceiling, ceilingLevelId, nodes), ceiling.polygon).map((polygon) => ({
531
+ polygon,
530
532
  metadata: {
531
533
  source: 'stair',
532
534
  stairId: stair.id,
533
535
  },
534
536
  })))
535
- .filter((hole) => polygonContainsPolygon(ceiling.polygon, hole.polygon));
536
- const nextHoles = [...manualHoles, ...stairHoles.map((hole) => hole.polygon)];
537
- const nextMetadata = [...manualMetadata, ...stairHoles.map((hole) => hole.metadata)];
537
+ .filter((hole) => !isCoveredByExistingHole(preservedHolePolygons, hole.polygon));
538
+ const nextHoles = [
539
+ ...preservedHoles.map((hole) => hole.polygon),
540
+ ...stairHoles.map((hole) => hole.polygon),
541
+ ];
542
+ const nextMetadata = [
543
+ ...preservedHoles.map((hole) => ({ ...hole.metadata })),
544
+ ...stairHoles.map((hole) => hole.metadata),
545
+ ];
538
546
  if (!(polygonsEqual(existingHoles, nextHoles) && metadataEqual(existingMetadata, nextMetadata))) {
539
547
  updates.push({
540
548
  id: ceiling.id,