@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
@@ -1,8 +1,8 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
- import { BuildingNode, LevelNode, SlabNode, StairNode, StairSegmentNode } from '../../schema';
2
+ import { BuildingNode, CeilingNode, LevelNode, SlabNode, StairNode, StairSegmentNode, } from '../../schema';
3
3
  import { syncAutoStairOpenings } from './stair-opening-sync';
4
4
  describe('syncAutoStairOpenings', () => {
5
- test('only applies stair holes to destination slabs that contain the opening', () => {
5
+ test('only applies stair holes to destination slabs that overlap the opening', () => {
6
6
  const building = BuildingNode.parse({ name: 'Building' });
7
7
  const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
8
8
  const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
@@ -60,4 +60,426 @@ describe('syncAutoStairOpenings', () => {
60
60
  expect(landingUpdate?.data.holeMetadata).toEqual([{ source: 'stair', stairId: stair.id }]);
61
61
  expect(bedroomUpdate).toBeUndefined();
62
62
  });
63
+ test('applies stair holes to a later destination slab when the configured offset overhangs the slab edge', () => {
64
+ const building = BuildingNode.parse({ name: 'Building' });
65
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
66
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
67
+ const landingSlab = SlabNode.parse({
68
+ name: 'Landing Slab',
69
+ parentId: upper.id,
70
+ polygon: [
71
+ [0, 0],
72
+ [4, 0],
73
+ [4, 3],
74
+ [0, 3],
75
+ ],
76
+ });
77
+ const segment = StairSegmentNode.parse({
78
+ parentId: 'stair_edge',
79
+ width: 1,
80
+ length: 2.6,
81
+ height: 2.5,
82
+ stepCount: 12,
83
+ });
84
+ const stair = StairNode.parse({
85
+ id: 'stair_edge',
86
+ name: 'Edge Stair',
87
+ parentId: ground.id,
88
+ position: [2, 0, 0],
89
+ stairType: 'straight',
90
+ fromLevelId: ground.id,
91
+ toLevelId: upper.id,
92
+ slabOpeningMode: 'destination',
93
+ openingOffset: 0.08,
94
+ children: [segment.id],
95
+ });
96
+ const nodes = Object.fromEntries([building, ground, upper, landingSlab, stair, { ...segment, parentId: stair.id }].map((node) => [node.id, node]));
97
+ const updates = syncAutoStairOpenings(nodes);
98
+ const landingUpdate = updates.find((update) => update.id === landingSlab.id);
99
+ const hole = landingUpdate?.data.holes?.[0];
100
+ expect(hole).toBeDefined();
101
+ expect(Math.min(...hole.map(([, z]) => z))).toBeCloseTo(-0.08);
102
+ expect(landingUpdate?.data.holeMetadata).toEqual([{ source: 'stair', stairId: stair.id }]);
103
+ });
104
+ test('does not apply stair holes to slabs on another building with a matching level number', () => {
105
+ const buildingA = BuildingNode.parse({ name: 'Building A' });
106
+ const groundA = LevelNode.parse({ name: 'Ground A', level: 0, parentId: buildingA.id });
107
+ const upperA = LevelNode.parse({ name: 'Upper A', level: 1, parentId: buildingA.id });
108
+ const buildingB = BuildingNode.parse({ name: 'Building B' });
109
+ const upperB = LevelNode.parse({ name: 'Upper B', level: 1, parentId: buildingB.id });
110
+ const slabA = SlabNode.parse({
111
+ name: 'Upper A Slab',
112
+ parentId: upperA.id,
113
+ polygon: [
114
+ [0, 0],
115
+ [4, 0],
116
+ [4, 3],
117
+ [0, 3],
118
+ ],
119
+ });
120
+ const slabB = SlabNode.parse({
121
+ name: 'Upper B Slab',
122
+ parentId: upperB.id,
123
+ polygon: [
124
+ [0, 0],
125
+ [4, 0],
126
+ [4, 3],
127
+ [0, 3],
128
+ ],
129
+ });
130
+ const segment = StairSegmentNode.parse({
131
+ parentId: 'stair_scoped',
132
+ width: 1,
133
+ length: 2.6,
134
+ height: 2.5,
135
+ stepCount: 12,
136
+ });
137
+ const stair = StairNode.parse({
138
+ id: 'stair_scoped',
139
+ name: 'Scoped Stair',
140
+ parentId: groundA.id,
141
+ position: [2, 0, 0.2],
142
+ stairType: 'straight',
143
+ fromLevelId: groundA.id,
144
+ toLevelId: upperA.id,
145
+ slabOpeningMode: 'destination',
146
+ children: [segment.id],
147
+ });
148
+ const nodes = Object.fromEntries([
149
+ buildingA,
150
+ groundA,
151
+ upperA,
152
+ buildingB,
153
+ upperB,
154
+ slabA,
155
+ slabB,
156
+ stair,
157
+ { ...segment, parentId: stair.id },
158
+ ].map((node) => [node.id, node]));
159
+ const updates = syncAutoStairOpenings(nodes);
160
+ expect(updates.find((update) => update.id === slabA.id)?.data.holes).toHaveLength(1);
161
+ expect(updates.find((update) => update.id === slabB.id)).toBeUndefined();
162
+ });
163
+ test('uses the parent level when a stair has stale from-level data', () => {
164
+ const building = BuildingNode.parse({ name: 'Building' });
165
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
166
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
167
+ const landingSlab = SlabNode.parse({
168
+ name: 'Landing Slab',
169
+ parentId: upper.id,
170
+ polygon: [
171
+ [0, 0],
172
+ [4, 0],
173
+ [4, 8],
174
+ [0, 8],
175
+ ],
176
+ });
177
+ const segment = StairSegmentNode.parse({
178
+ parentId: 'stair_stale_from',
179
+ width: 1,
180
+ length: 6,
181
+ height: 2.5,
182
+ stepCount: 12,
183
+ });
184
+ const stair = StairNode.parse({
185
+ id: 'stair_stale_from',
186
+ name: 'Stale From Stair',
187
+ parentId: ground.id,
188
+ position: [2, 0, 0.2],
189
+ stairType: 'straight',
190
+ fromLevelId: 'default',
191
+ toLevelId: upper.id,
192
+ slabOpeningMode: 'destination',
193
+ children: [segment.id],
194
+ });
195
+ const nodes = Object.fromEntries([building, ground, upper, landingSlab, stair, { ...segment, parentId: stair.id }].map((node) => [node.id, node]));
196
+ const updates = syncAutoStairOpenings(nodes);
197
+ const landingUpdate = updates.find((update) => update.id === landingSlab.id);
198
+ const hole = landingUpdate?.data.holes?.[0];
199
+ expect(hole).toBeDefined();
200
+ expect(Math.min(...hole.map(([, z]) => z))).toBeGreaterThan(0.9);
201
+ expect(landingUpdate?.data.holeMetadata).toEqual([{ source: 'stair', stairId: stair.id }]);
202
+ });
203
+ test('infers the destination level when a destination stair has blank level fields', () => {
204
+ const building = BuildingNode.parse({ name: 'Building' });
205
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
206
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
207
+ const landingSlab = SlabNode.parse({
208
+ name: 'Landing Slab',
209
+ parentId: upper.id,
210
+ polygon: [
211
+ [0, 0],
212
+ [4, 0],
213
+ [4, 8],
214
+ [0, 8],
215
+ ],
216
+ });
217
+ const segment = StairSegmentNode.parse({
218
+ parentId: 'stair_blank_levels',
219
+ width: 1,
220
+ length: 6,
221
+ height: 2.5,
222
+ stepCount: 12,
223
+ });
224
+ const stair = StairNode.parse({
225
+ id: 'stair_blank_levels',
226
+ name: 'Blank Level Stair',
227
+ parentId: ground.id,
228
+ position: [2, 0, 0.2],
229
+ stairType: 'straight',
230
+ fromLevelId: '',
231
+ toLevelId: '',
232
+ slabOpeningMode: 'destination',
233
+ children: [segment.id],
234
+ });
235
+ const nodes = Object.fromEntries([building, ground, upper, landingSlab, stair, { ...segment, parentId: stair.id }].map((node) => [node.id, node]));
236
+ const updates = syncAutoStairOpenings(nodes);
237
+ const landingUpdate = updates.find((update) => update.id === landingSlab.id);
238
+ expect(landingUpdate?.data.holes).toHaveLength(1);
239
+ expect(landingUpdate?.data.holeMetadata).toEqual([{ source: 'stair', stairId: stair.id }]);
240
+ });
241
+ test('infers the destination level when a destination stair targets its source level', () => {
242
+ const building = BuildingNode.parse({ name: 'Building' });
243
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
244
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
245
+ const landingSlab = SlabNode.parse({
246
+ name: 'Landing Slab',
247
+ parentId: upper.id,
248
+ polygon: [
249
+ [0, 0],
250
+ [4, 0],
251
+ [4, 8],
252
+ [0, 8],
253
+ ],
254
+ });
255
+ const segment = StairSegmentNode.parse({
256
+ parentId: 'stair_self_target',
257
+ width: 1,
258
+ length: 6,
259
+ height: 2.5,
260
+ stepCount: 12,
261
+ });
262
+ const stair = StairNode.parse({
263
+ id: 'stair_self_target',
264
+ name: 'Self Target Stair',
265
+ parentId: ground.id,
266
+ position: [2, 0, 0.2],
267
+ stairType: 'straight',
268
+ fromLevelId: ground.id,
269
+ toLevelId: ground.id,
270
+ slabOpeningMode: 'destination',
271
+ children: [segment.id],
272
+ });
273
+ const nodes = Object.fromEntries([building, ground, upper, landingSlab, stair, { ...segment, parentId: stair.id }].map((node) => [node.id, node]));
274
+ const updates = syncAutoStairOpenings(nodes);
275
+ const landingUpdate = updates.find((update) => update.id === landingSlab.id);
276
+ expect(landingUpdate?.data.holes).toHaveLength(1);
277
+ expect(landingUpdate?.data.holeMetadata).toEqual([{ source: 'stair', stairId: stair.id }]);
278
+ });
279
+ test('does not add stair holes when a manual surface hole already covers them', () => {
280
+ const building = BuildingNode.parse({ name: 'Building' });
281
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
282
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
283
+ const manualOpening = [
284
+ [1.0, 0.0],
285
+ [3.0, 0.0],
286
+ [3.0, 3.0],
287
+ [1.0, 3.0],
288
+ ];
289
+ const sourceCeiling = CeilingNode.parse({
290
+ name: 'Source Ceiling',
291
+ parentId: ground.id,
292
+ polygon: [
293
+ [0, 0],
294
+ [4, 0],
295
+ [4, 3],
296
+ [0, 3],
297
+ ],
298
+ holes: [manualOpening],
299
+ holeMetadata: [{ source: 'manual' }],
300
+ });
301
+ const landingSlab = SlabNode.parse({
302
+ name: 'Landing Slab',
303
+ parentId: upper.id,
304
+ polygon: [
305
+ [0, 0],
306
+ [4, 0],
307
+ [4, 3],
308
+ [0, 3],
309
+ ],
310
+ holes: [manualOpening],
311
+ holeMetadata: [{ source: 'manual' }],
312
+ });
313
+ const segment = StairSegmentNode.parse({
314
+ parentId: 'stair_main',
315
+ width: 1,
316
+ length: 2.6,
317
+ height: 2.5,
318
+ stepCount: 12,
319
+ });
320
+ const stair = StairNode.parse({
321
+ id: 'stair_main',
322
+ name: 'Main Stair',
323
+ parentId: ground.id,
324
+ position: [2, 0, 0.2],
325
+ stairType: 'straight',
326
+ fromLevelId: ground.id,
327
+ toLevelId: upper.id,
328
+ slabOpeningMode: 'destination',
329
+ children: [segment.id],
330
+ });
331
+ const nodes = Object.fromEntries([
332
+ building,
333
+ ground,
334
+ upper,
335
+ sourceCeiling,
336
+ landingSlab,
337
+ stair,
338
+ { ...segment, parentId: stair.id },
339
+ ].map((node) => [node.id, node]));
340
+ const updates = syncAutoStairOpenings(nodes);
341
+ expect(updates.find((update) => update.id === landingSlab.id)).toBeUndefined();
342
+ expect(updates.find((update) => update.id === sourceCeiling.id)).toBeUndefined();
343
+ });
344
+ test('adds stair holes when an existing manual hole is too small', () => {
345
+ const building = BuildingNode.parse({ name: 'Building' });
346
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
347
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
348
+ const smallManualOpening = [
349
+ [1.8, 1.6],
350
+ [2.2, 1.6],
351
+ [2.2, 2.1],
352
+ [1.8, 2.1],
353
+ ];
354
+ const landingSlab = SlabNode.parse({
355
+ name: 'Landing Slab',
356
+ parentId: upper.id,
357
+ polygon: [
358
+ [0, 0],
359
+ [4, 0],
360
+ [4, 3],
361
+ [0, 3],
362
+ ],
363
+ holes: [smallManualOpening],
364
+ holeMetadata: [{ source: 'manual' }],
365
+ });
366
+ const segment = StairSegmentNode.parse({
367
+ parentId: 'stair_main',
368
+ width: 1,
369
+ length: 2.6,
370
+ height: 2.5,
371
+ stepCount: 12,
372
+ });
373
+ const stair = StairNode.parse({
374
+ id: 'stair_main',
375
+ name: 'Main Stair',
376
+ parentId: ground.id,
377
+ position: [2, 0, 0.2],
378
+ stairType: 'straight',
379
+ fromLevelId: ground.id,
380
+ toLevelId: upper.id,
381
+ slabOpeningMode: 'destination',
382
+ children: [segment.id],
383
+ });
384
+ const nodes = Object.fromEntries([building, ground, upper, landingSlab, stair, { ...segment, parentId: stair.id }].map((node) => [node.id, node]));
385
+ const updates = syncAutoStairOpenings(nodes);
386
+ const landingUpdate = updates.find((update) => update.id === landingSlab.id);
387
+ expect(landingUpdate?.data.holes).toHaveLength(2);
388
+ expect(landingUpdate?.data.holes?.[0]).toEqual(smallManualOpening);
389
+ expect(landingUpdate?.data.holeMetadata).toEqual([
390
+ { source: 'manual' },
391
+ { source: 'stair', stairId: stair.id },
392
+ ]);
393
+ });
394
+ test('removes stale auto stair holes when a manual hole overlaps the stair opening', () => {
395
+ const building = BuildingNode.parse({ name: 'Building' });
396
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
397
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
398
+ const manualOpening = [
399
+ [1.0, 0.0],
400
+ [3.0, 0.0],
401
+ [3.0, 3.0],
402
+ [1.0, 3.0],
403
+ ];
404
+ const staleAutoOpening = [
405
+ [1.5, 1],
406
+ [2.5, 1],
407
+ [2.5, 2.8],
408
+ [1.5, 2.8],
409
+ ];
410
+ const landingSlab = SlabNode.parse({
411
+ name: 'Landing Slab',
412
+ parentId: upper.id,
413
+ polygon: [
414
+ [0, 0],
415
+ [4, 0],
416
+ [4, 3],
417
+ [0, 3],
418
+ ],
419
+ holes: [manualOpening, staleAutoOpening],
420
+ holeMetadata: [{ source: 'manual' }, { source: 'stair', stairId: 'stair_main' }],
421
+ });
422
+ const segment = StairSegmentNode.parse({
423
+ parentId: 'stair_main',
424
+ width: 1,
425
+ length: 2.6,
426
+ height: 2.5,
427
+ stepCount: 12,
428
+ });
429
+ const stair = StairNode.parse({
430
+ id: 'stair_main',
431
+ name: 'Main Stair',
432
+ parentId: ground.id,
433
+ position: [2, 0, 0.2],
434
+ stairType: 'straight',
435
+ fromLevelId: ground.id,
436
+ toLevelId: upper.id,
437
+ slabOpeningMode: 'destination',
438
+ children: [segment.id],
439
+ });
440
+ const nodes = Object.fromEntries([building, ground, upper, landingSlab, stair, { ...segment, parentId: stair.id }].map((node) => [node.id, node]));
441
+ const updates = syncAutoStairOpenings(nodes);
442
+ const landingUpdate = updates.find((update) => update.id === landingSlab.id);
443
+ expect(landingUpdate?.data.holes).toEqual([manualOpening]);
444
+ expect(landingUpdate?.data.holeMetadata).toEqual([{ source: 'manual' }]);
445
+ });
446
+ test('does not add a separate rectangular hole for an integrated spiral top landing', () => {
447
+ const building = BuildingNode.parse({ name: 'Building' });
448
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
449
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
450
+ const landingSlab = SlabNode.parse({
451
+ name: 'Landing Slab',
452
+ parentId: upper.id,
453
+ polygon: [
454
+ [-4, -4],
455
+ [4, -4],
456
+ [4, 4],
457
+ [-4, 4],
458
+ ],
459
+ });
460
+ const stair = StairNode.parse({
461
+ id: 'stair_spiral_landing',
462
+ name: 'Spiral Landing Stair',
463
+ parentId: ground.id,
464
+ position: [0, 0, 0],
465
+ rotation: Math.PI / 2,
466
+ stairType: 'spiral',
467
+ fromLevelId: ground.id,
468
+ toLevelId: upper.id,
469
+ slabOpeningMode: 'destination',
470
+ innerRadius: 0.35,
471
+ width: 1.2,
472
+ sweepAngle: Math.PI * 1.6,
473
+ topLandingMode: 'integrated',
474
+ topLandingDepth: 1.1,
475
+ });
476
+ const nodes = Object.fromEntries([building, ground, upper, landingSlab, stair].map((node) => [node.id, node]));
477
+ const updates = syncAutoStairOpenings(nodes);
478
+ const landingUpdate = updates.find((update) => update.id === landingSlab.id);
479
+ const holes = landingUpdate?.data.holes ?? [];
480
+ const rectangularHoles = holes.filter((hole) => hole.length === 4);
481
+ expect(holes).toHaveLength(1);
482
+ expect(rectangularHoles).toHaveLength(0);
483
+ expect(landingUpdate?.data.holeMetadata).toEqual([{ source: 'stair', stairId: stair.id }]);
484
+ });
63
485
  });
@@ -0,0 +1,2 @@
1
+ export declare const StairOpeningSystem: () => null;
2
+ //# sourceMappingURL=stair-opening-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stair-opening-system.d.ts","sourceRoot":"","sources":["../../../src/systems/stair/stair-opening-system.tsx"],"names":[],"mappings":"AA2CA,eAAO,MAAM,kBAAkB,YAyF9B,CAAA"}
@@ -0,0 +1,104 @@
1
+ 'use client';
2
+ import { useEffect, useRef } from 'react';
3
+ import { pauseSceneHistory, resumeSceneHistory } from '../../store/history-control';
4
+ import useLiveNodeOverrides from '../../store/use-live-node-overrides';
5
+ import useLiveTransforms from '../../store/use-live-transforms';
6
+ import useScene from '../../store/use-scene';
7
+ import { createSurfaceOpeningPreviewController, getNodesWithLiveStairOpeningInputs, hasLiveStairOpeningInputs, } from './stair-opening-preview';
8
+ import { syncAutoStairOpenings } from './stair-opening-sync';
9
+ function isOpeningRelevantNode(node) {
10
+ return (node?.type === 'building' ||
11
+ node?.type === 'ceiling' ||
12
+ node?.type === 'level' ||
13
+ node?.type === 'slab' ||
14
+ node?.type === 'stair' ||
15
+ node?.type === 'stair-segment');
16
+ }
17
+ function hasOpeningRelevantNodeChange(nextNodes, prevNodes) {
18
+ if (nextNodes === prevNodes)
19
+ return false;
20
+ const ids = new Set([...Object.keys(nextNodes), ...Object.keys(prevNodes)]);
21
+ for (const id of ids) {
22
+ const nextNode = nextNodes[id];
23
+ const prevNode = prevNodes[id];
24
+ if (nextNode === prevNode)
25
+ continue;
26
+ if (isOpeningRelevantNode(nextNode) || isOpeningRelevantNode(prevNode))
27
+ return true;
28
+ }
29
+ return false;
30
+ }
31
+ export const StairOpeningSystem = () => {
32
+ const syncingAutoOpeningsRef = useRef(false);
33
+ const syncingPreviewOpeningsRef = useRef(false);
34
+ const previewControllerRef = useRef(createSurfaceOpeningPreviewController());
35
+ useEffect(() => {
36
+ const applyUpdates = (updates) => {
37
+ if (updates.length === 0)
38
+ return;
39
+ syncingAutoOpeningsRef.current = true;
40
+ pauseSceneHistory(useScene);
41
+ try {
42
+ useScene.getState().updateNodes(updates);
43
+ }
44
+ finally {
45
+ resumeSceneHistory(useScene);
46
+ }
47
+ queueMicrotask(() => {
48
+ syncingAutoOpeningsRef.current = false;
49
+ });
50
+ };
51
+ const applyPreviewUpdates = (updates) => {
52
+ syncingPreviewOpeningsRef.current = true;
53
+ previewControllerRef.current.apply(updates);
54
+ queueMicrotask(() => {
55
+ syncingPreviewOpeningsRef.current = false;
56
+ });
57
+ };
58
+ const clearPreviewUpdates = () => {
59
+ if (previewControllerRef.current.previewSurfaceIds.size === 0)
60
+ return;
61
+ syncingPreviewOpeningsRef.current = true;
62
+ previewControllerRef.current.clear();
63
+ queueMicrotask(() => {
64
+ syncingPreviewOpeningsRef.current = false;
65
+ });
66
+ };
67
+ const refreshLivePreview = () => {
68
+ if (syncingPreviewOpeningsRef.current)
69
+ return;
70
+ const nodes = useScene.getState().nodes;
71
+ const liveTransforms = useLiveTransforms.getState().transforms;
72
+ const liveOverrides = useLiveNodeOverrides.getState().overrides;
73
+ const previewSurfaceIds = previewControllerRef.current.previewSurfaceIds;
74
+ if (!hasLiveStairOpeningInputs(nodes, liveTransforms, liveOverrides, previewSurfaceIds)) {
75
+ clearPreviewUpdates();
76
+ return;
77
+ }
78
+ applyPreviewUpdates(syncAutoStairOpenings(getNodesWithLiveStairOpeningInputs(nodes, liveTransforms, liveOverrides, previewSurfaceIds)));
79
+ };
80
+ applyUpdates(syncAutoStairOpenings(useScene.getState().nodes));
81
+ refreshLivePreview();
82
+ const unsubscribeScene = useScene.subscribe((state, prevState) => {
83
+ if (syncingAutoOpeningsRef.current)
84
+ return;
85
+ if (!hasOpeningRelevantNodeChange(state.nodes, prevState.nodes))
86
+ return;
87
+ applyUpdates(syncAutoStairOpenings(state.nodes));
88
+ refreshLivePreview();
89
+ });
90
+ const unsubscribeLiveTransforms = useLiveTransforms.subscribe(() => {
91
+ refreshLivePreview();
92
+ });
93
+ const unsubscribeLiveOverrides = useLiveNodeOverrides.subscribe(() => {
94
+ refreshLivePreview();
95
+ });
96
+ return () => {
97
+ unsubscribeScene();
98
+ unsubscribeLiveTransforms();
99
+ unsubscribeLiveOverrides();
100
+ previewControllerRef.current.clear();
101
+ };
102
+ }, []);
103
+ return null;
104
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"wall-mitering.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-mitering.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAO5C,MAAM,WAAW,OAAO;IACtB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,OAAO,CAAA;IAClB,UAAU,EAAE,OAAO,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;CAClB;AASD,KAAK,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAGzE,KAAK,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;AAQlD,iBAAS,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,SAAY,GAAG,MAAM,CAG7D;AA8CD,UAAU,QAAQ;IAChB,YAAY,EAAE,OAAO,CAAA;IACrB,cAAc,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,OAAO,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,CAAA;KAAE,CAAC,CAAA;CACpF;AAoND,MAAM,WAAW,aAAa;IAE5B,YAAY,EAAE,YAAY,CAAA;IAE1B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;CACjC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,aAAa,CAWrE;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,QAAQ,EACd,SAAS,EAAE,aAAa,GACvB,uBAAuB,GAAG,IAAI,CA0BhC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CA8C/F;AAGD,OAAO,EAAE,UAAU,EAAE,CAAA"}
1
+ {"version":3,"file":"wall-mitering.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-mitering.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAO5C,MAAM,WAAW,OAAO;IACtB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,OAAO,CAAA;IAClB,UAAU,EAAE,OAAO,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;CAClB;AASD,KAAK,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAGzE,KAAK,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;AAmBlD,iBAAS,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,SAAY,GAAG,MAAM,CAG7D;AA8CD,UAAU,QAAQ;IAChB,YAAY,EAAE,OAAO,CAAA;IACrB,cAAc,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,OAAO,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,CAAA;KAAE,CAAC,CAAA;CACpF;AAkPD,MAAM,WAAW,aAAa;IAE5B,YAAY,EAAE,YAAY,CAAA;IAE1B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;CACjC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,aAAa,CAWrE;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,QAAQ,EACd,SAAS,EAAE,aAAa,GACvB,uBAAuB,GAAG,IAAI,CA0BhC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CA8C/F;AAGD,OAAO,EAAE,UAAU,EAAE,CAAA"}
@@ -3,6 +3,16 @@ import { getWallCurveFrameAt, isCurvedWall } from './wall-curve';
3
3
  // UTILITY FUNCTIONS
4
4
  // ============================================================================
5
5
  const TOLERANCE = 0.001;
6
+ // Miter joints are line-line intersections, so the joint point sits a distance
7
+ // ≈ halfThickness / sin(θ) from the junction, where θ is the angle between the
8
+ // two walls. As θ → 0 (two walls nearly collinear — e.g. a room-preset preview
9
+ // dragged on top of an existing wall, or a freshly-drawn wall almost parallel
10
+ // to its neighbour) that distance runs away to infinity and the wall renders as
11
+ // an infinite spike. Cap the joint at this multiple of the wall half-thickness;
12
+ // beyond it we fall back to a square (butt) joint, exactly like the existing
13
+ // parallel-walls guard. 10× preserves every realistic corner (a 0.1 m wall keeps
14
+ // mitering down to ~11°) while bounding the pathological near-collinear case.
15
+ const MITER_LIMIT = 10;
6
16
  function pointToKey(p, tolerance = TOLERANCE) {
7
17
  const snap = 1 / tolerance;
8
18
  return `${Math.round(p.x * snap)},${Math.round(p.y * snap)}`;
@@ -144,7 +154,14 @@ function calculateJunctionIntersections(junction, getThickness) {
144
154
  const edgeA = createLineFromPointAndVector(pA, v);
145
155
  const edgeB = createLineFromPointAndVector(pB, v);
146
156
  const angle = Math.atan2(v.y, v.x);
147
- processedWalls.push({ wallId: wall.id, angle, edgeA, edgeB, isPassthrough: true });
157
+ processedWalls.push({
158
+ wallId: wall.id,
159
+ angle,
160
+ edgeA,
161
+ edgeB,
162
+ isPassthrough: true,
163
+ halfThickness: halfT,
164
+ });
148
165
  }
149
166
  }
150
167
  else {
@@ -159,7 +176,14 @@ function calculateJunctionIntersections(junction, getThickness) {
159
176
  const edgeA = createLineFromPointAndVector(pA, v);
160
177
  const edgeB = createLineFromPointAndVector(pB, v);
161
178
  const angle = Math.atan2(v.y, v.x);
162
- processedWalls.push({ wallId: wall.id, angle, edgeA, edgeB, isPassthrough: false });
179
+ processedWalls.push({
180
+ wallId: wall.id,
181
+ angle,
182
+ edgeA,
183
+ edgeB,
184
+ isPassthrough: false,
185
+ halfThickness: halfT,
186
+ });
163
187
  }
164
188
  }
165
189
  // Sort by outgoing angle
@@ -182,6 +206,18 @@ function calculateJunctionIntersections(junction, getThickness) {
182
206
  x: (wall1.edgeA.b * wall2.edgeB.c - wall2.edgeB.b * wall1.edgeA.c) / det,
183
207
  y: (wall2.edgeB.a * wall1.edgeA.c - wall1.edgeA.a * wall2.edgeB.c) / det,
184
208
  };
209
+ // Miter limit: `det` only catches walls that are *exactly* parallel. Two
210
+ // walls meeting at a shallow angle have a small-but-nonzero `det`, so `p`
211
+ // lands far from the junction (∝ 1/sin θ) and the wall renders as an
212
+ // infinite spike. Reject any joint farther than MITER_LIMIT half-thicknesses
213
+ // from the meeting point — those walls fall back to a square joint.
214
+ const maxMiter = MITER_LIMIT * Math.max(wall1.halfThickness, wall2.halfThickness);
215
+ const dx = p.x - meetingPoint.x;
216
+ const dy = p.y - meetingPoint.y;
217
+ if (!(Number.isFinite(p.x) && Number.isFinite(p.y)) ||
218
+ dx * dx + dy * dy > maxMiter * maxMiter) {
219
+ continue;
220
+ }
185
221
  // Only assign intersection to non-passthrough walls
186
222
  // Passthrough walls don't receive junction data (their geometry doesn't change)
187
223
  if (!wall1.isPassthrough) {
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=wall-mitering.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wall-mitering.test.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-mitering.test.ts"],"names":[],"mappings":""}