@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,65 @@
1
+ import { nodeRegistry } from '../registry/registry';
2
+ import { snapVec3ToGrid } from './snap';
3
+ /**
4
+ * Returns the MovableConfig effective for `node` after running its `override`
5
+ * callback if declared. Returns `null` if the node's def doesn't declare
6
+ * `movable` (i.e. the node is not movable).
7
+ */
8
+ export function resolveMovable(node) {
9
+ const def = nodeRegistry.get(node.type);
10
+ const base = def?.capabilities.movable;
11
+ if (!base)
12
+ return null;
13
+ if (base.override) {
14
+ const overridden = base.override({ node });
15
+ return overridden ?? base;
16
+ }
17
+ return base;
18
+ }
19
+ /**
20
+ * Projects a target X/Y/Z onto the axes a node is allowed to move on. Components
21
+ * outside the lock fall back to the node's current values, so caller-supplied
22
+ * positions can come from any 3D source without breaking axis-locked motion.
23
+ */
24
+ export function applyAxisLock(current, target, axes) {
25
+ return [
26
+ axes.includes('x') ? target[0] : current[0],
27
+ axes.includes('y') ? target[1] : current[1],
28
+ axes.includes('z') ? target[2] : current[2],
29
+ ];
30
+ }
31
+ /**
32
+ * Top-level helper: takes a node and a desired position, returns the position
33
+ * filtered through the node's movable capability (axis lock + optional grid
34
+ * snap). Returns `null` when the node is not movable.
35
+ */
36
+ export function moveToward(node, current, target, options = {}) {
37
+ const config = resolveMovable(node);
38
+ if (!config)
39
+ return null;
40
+ let next = applyAxisLock(current, target, config.axes);
41
+ const wantsGridSnap = options.gridSnap ?? config.gridSnap;
42
+ if (wantsGridSnap) {
43
+ next = snapVec3ToGrid(next, options.gridStep);
44
+ }
45
+ return next;
46
+ }
47
+ /**
48
+ * 2D convenience: same as moveToward but for plan-view (X/Z) operations like
49
+ * floor placement. Returns a tuple in the X/Z plane so callers don't have to
50
+ * pack/unpack the dropped Y.
51
+ */
52
+ export function movePlanToward(node, currentY, current, target, options = {}) {
53
+ const result = moveToward(node, [current[0], currentY, current[1]], [target[0], currentY, target[1]], options);
54
+ if (!result)
55
+ return null;
56
+ return [result[0], result[2]];
57
+ }
58
+ /**
59
+ * Returns true when a node's def declares it as movable on any axis.
60
+ * Quick predicate for tools/UI that gate on movability.
61
+ */
62
+ export function isMovable(node) {
63
+ const config = resolveMovable(node);
64
+ return config != null && config.axes.length > 0;
65
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=movement.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"movement.test.d.ts","sourceRoot":"","sources":["../../src/services/movement.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,133 @@
1
+ import { beforeEach, describe, expect, test } from 'bun:test';
2
+ import { z } from 'zod';
3
+ import { nodeRegistry, registerNode } from '../registry/registry';
4
+ import { applyAxisLock, isMovable, movePlanToward, moveToward, resolveMovable } from './movement';
5
+ const id = (s) => s;
6
+ function makeDef(kind, capabilities = {}) {
7
+ return {
8
+ kind,
9
+ schemaVersion: 1,
10
+ schema: z.object({ type: z.literal(kind) }),
11
+ category: 'utility',
12
+ defaults: () => ({}),
13
+ capabilities,
14
+ renderer: { kind: 'parametric', module: async () => ({ default: () => null }) },
15
+ };
16
+ }
17
+ function makeNode(kind, idStr) {
18
+ return {
19
+ id: id(idStr),
20
+ type: kind,
21
+ parentId: null,
22
+ visible: true,
23
+ };
24
+ }
25
+ describe('resolveMovable', () => {
26
+ beforeEach(() => {
27
+ nodeRegistry._reset();
28
+ });
29
+ test('returns null when no def is registered', () => {
30
+ expect(resolveMovable(makeNode('mystery', 'm'))).toBeNull();
31
+ });
32
+ test('returns null when def declares no movable capability', () => {
33
+ registerNode(makeDef('static'));
34
+ expect(resolveMovable(makeNode('static', 's'))).toBeNull();
35
+ });
36
+ test('returns the declared config', () => {
37
+ registerNode(makeDef('column', { movable: { axes: ['x', 'z'], gridSnap: true } }));
38
+ const config = resolveMovable(makeNode('column', 'c'));
39
+ expect(config?.axes).toEqual(['x', 'z']);
40
+ expect(config?.gridSnap).toBe(true);
41
+ });
42
+ test('runs override callback when present', () => {
43
+ let overrideRan = false;
44
+ const config = {
45
+ axes: ['x'],
46
+ override: () => {
47
+ overrideRan = true;
48
+ return { axes: ['y'] };
49
+ },
50
+ };
51
+ registerNode(makeDef('weird', { movable: config }));
52
+ const resolved = resolveMovable(makeNode('weird', 'w'));
53
+ expect(overrideRan).toBe(true);
54
+ expect(resolved?.axes).toEqual(['y']);
55
+ });
56
+ test('override returning null falls back to base config', () => {
57
+ registerNode(makeDef('column', {
58
+ movable: { axes: ['x', 'z'], override: () => null },
59
+ }));
60
+ const resolved = resolveMovable(makeNode('column', 'c'));
61
+ expect(resolved?.axes).toEqual(['x', 'z']);
62
+ });
63
+ });
64
+ describe('applyAxisLock', () => {
65
+ test('passes through unlocked axes only', () => {
66
+ expect(applyAxisLock([1, 2, 3], [10, 20, 30], ['x'])).toEqual([10, 2, 3]);
67
+ expect(applyAxisLock([1, 2, 3], [10, 20, 30], ['x', 'z'])).toEqual([10, 2, 30]);
68
+ expect(applyAxisLock([1, 2, 3], [10, 20, 30], ['x', 'y', 'z'])).toEqual([10, 20, 30]);
69
+ });
70
+ test('empty lock returns current unchanged', () => {
71
+ expect(applyAxisLock([1, 2, 3], [10, 20, 30], [])).toEqual([1, 2, 3]);
72
+ });
73
+ });
74
+ describe('moveToward', () => {
75
+ beforeEach(() => {
76
+ nodeRegistry._reset();
77
+ });
78
+ test('returns null when node is not movable', () => {
79
+ registerNode(makeDef('static'));
80
+ expect(moveToward(makeNode('static', 's'), [0, 0, 0], [1, 1, 1])).toBeNull();
81
+ });
82
+ test('axis-locks and returns the constrained target', () => {
83
+ registerNode(makeDef('column', { movable: { axes: ['x', 'z'] } }));
84
+ expect(moveToward(makeNode('column', 'c'), [0, 0.5, 0], [1, 99, 2])).toEqual([1, 0.5, 2]);
85
+ });
86
+ test('applies grid snap when capability declares gridSnap: true', () => {
87
+ registerNode(makeDef('column', { movable: { axes: ['x', 'z'], gridSnap: true } }));
88
+ const result = moveToward(makeNode('column', 'c'), [0, 0, 0], [0.3, 0, 0.6], {
89
+ gridStep: 0.25,
90
+ });
91
+ expect(result).toEqual([0.25, 0, 0.5]);
92
+ });
93
+ test('grid snap can be overridden at call site', () => {
94
+ registerNode(makeDef('column', { movable: { axes: ['x', 'z'], gridSnap: true } }));
95
+ // Caller explicitly disables grid snap for this call
96
+ const result = moveToward(makeNode('column', 'c'), [0, 0, 0], [0.3, 0, 0.6], {
97
+ gridSnap: false,
98
+ });
99
+ expect(result).toEqual([0.3, 0, 0.6]);
100
+ });
101
+ });
102
+ describe('movePlanToward', () => {
103
+ beforeEach(() => {
104
+ nodeRegistry._reset();
105
+ });
106
+ test('returns 2D point with X/Z constrained, Y dropped', () => {
107
+ registerNode(makeDef('column', { movable: { axes: ['x', 'z'], gridSnap: true } }));
108
+ const result = movePlanToward(makeNode('column', 'c'), 0.5, // currentY
109
+ [0, 0], [0.3, 0.6], { gridStep: 0.25 });
110
+ expect(result).toEqual([0.25, 0.5]);
111
+ });
112
+ test('returns null when node is not movable', () => {
113
+ registerNode(makeDef('static'));
114
+ expect(movePlanToward(makeNode('static', 's'), 0, [0, 0], [1, 1])).toBeNull();
115
+ });
116
+ });
117
+ describe('isMovable', () => {
118
+ beforeEach(() => {
119
+ nodeRegistry._reset();
120
+ });
121
+ test('true when movable.axes has entries', () => {
122
+ registerNode(makeDef('column', { movable: { axes: ['x'] } }));
123
+ expect(isMovable(makeNode('column', 'c'))).toBe(true);
124
+ });
125
+ test('false when no movable capability', () => {
126
+ registerNode(makeDef('static'));
127
+ expect(isMovable(makeNode('static', 's'))).toBe(false);
128
+ });
129
+ test('false when movable.axes is empty', () => {
130
+ registerNode(makeDef('locked', { movable: { axes: [] } }));
131
+ expect(isMovable(makeNode('locked', 'l'))).toBe(false);
132
+ });
133
+ });
@@ -0,0 +1,141 @@
1
+ /** An opening's footprint in its host wall's local frame. */
2
+ export type OpeningSpan = {
3
+ id: string;
4
+ /** Centre along the wall, measured from `wall.start` (m). */
5
+ centerS: number;
6
+ /** Along-wall extent (m). */
7
+ width: number;
8
+ /** Vertical centre above the wall base (floor at y=0) (m). */
9
+ centerY: number;
10
+ /** Vertical extent (m). */
11
+ height: number;
12
+ };
13
+ export type WallExtent = {
14
+ /** Wall length (m). */
15
+ length: number;
16
+ /** Wall height (m). */
17
+ height: number;
18
+ };
19
+ export type OpeningGuideTolerances = {
20
+ /** Max distance for an edge/centre to count as aligned with a neighbour (m). */
21
+ align: number;
22
+ /** Max difference between two gaps for them to count as equal (m). */
23
+ equalSpacing: number;
24
+ /** Gaps below this are treated as touching/overlap noise and ignored (m). */
25
+ minGap: number;
26
+ };
27
+ export declare const DEFAULT_OPENING_GUIDE_TOLERANCES: OpeningGuideTolerances;
28
+ /** Which along-wall feature of an opening a guide references. */
29
+ export type AlongWallFeature = 'left' | 'center' | 'right';
30
+ /** Which vertical feature of an opening a guide references. */
31
+ export type VerticalFeature = 'sill' | 'center' | 'top';
32
+ export type SillHeadGuide = {
33
+ /** Floor (y=0) → the opening's bottom edge (m). */
34
+ sill: number;
35
+ /** Wall-local y of the bottom edge. */
36
+ bottomY: number;
37
+ /** The opening's top edge → the wall top (m). */
38
+ head: number;
39
+ /** Wall-local y of the top edge. */
40
+ topY: number;
41
+ };
42
+ export type EdgeGap = {
43
+ side: 'left' | 'right';
44
+ /** Clearance along the wall (m). */
45
+ distance: number;
46
+ /** Wall-local s of the moving opening's edge. */
47
+ fromS: number;
48
+ /** Wall-local s of the neighbour edge / wall end. */
49
+ toS: number;
50
+ target: 'opening' | 'wall-start' | 'wall-end';
51
+ /** Set when `target === 'opening'`. */
52
+ targetId?: string;
53
+ };
54
+ export type AlongWallAlignment = {
55
+ /** Wall-local s the two features share. */
56
+ s: number;
57
+ movingFeature: AlongWallFeature;
58
+ targetId: string;
59
+ targetFeature: AlongWallFeature;
60
+ /** Delta to add to the moving opening's `centerS` to make them coincide. */
61
+ snap: number;
62
+ };
63
+ export type VerticalAlignment = {
64
+ /** Wall-local y the two features share. */
65
+ y: number;
66
+ movingFeature: VerticalFeature;
67
+ targetId: string;
68
+ targetFeature: VerticalFeature;
69
+ /** Delta to add to the moving opening's `centerY` to make them coincide. */
70
+ snap: number;
71
+ };
72
+ export type EqualSpacingRun = {
73
+ /** The repeated gap value (average of the run's gaps) (m). */
74
+ gap: number;
75
+ /** The equal-gap segments along the wall, in order (left → right). */
76
+ segments: {
77
+ fromS: number;
78
+ toS: number;
79
+ }[];
80
+ /** Participating opening ids, ordered along the wall, including the moving one. */
81
+ openingIds: string[];
82
+ };
83
+ export type OpeningGuides = {
84
+ sillHead: SillHeadGuide | null;
85
+ gaps: EdgeGap[];
86
+ alongWall: AlongWallAlignment | null;
87
+ vertical: VerticalAlignment | null;
88
+ equalSpacing: EqualSpacingRun | null;
89
+ };
90
+ export type OpeningGuideInput = {
91
+ moving: OpeningSpan;
92
+ /** Other openings on the SAME wall (the moving opening excluded). */
93
+ siblings: readonly OpeningSpan[];
94
+ wall: WallExtent;
95
+ /**
96
+ * Whether to compute vertical (sill/head/vertical-alignment) guides. True for
97
+ * windows; false for doors, which sit on the floor so their sill is always 0.
98
+ */
99
+ includeVertical: boolean;
100
+ tolerances?: Partial<OpeningGuideTolerances>;
101
+ };
102
+ /**
103
+ * Edge-to-edge clearance from the moving opening to the nearest neighbour on
104
+ * each side, falling back to the wall ends when there is no neighbour — the
105
+ * "how much wall is left here" reading. Returns 0–2 gaps (one per side); a side
106
+ * is omitted when its clearance is below `minGap` (the opening is flush against
107
+ * or overlapping that neighbour).
108
+ */
109
+ export declare function computeEdgeGaps(moving: OpeningSpan, siblings: readonly OpeningSpan[], wall: WallExtent, minGap: number): EdgeGap[];
110
+ /**
111
+ * The closest coincidence between any of the moving opening's edges/centre and
112
+ * any sibling's edges/centre along the wall, within `tolerance`. Edge-to-edge
113
+ * and centre-to-centre are weighed equally; the single closest pair wins
114
+ * (matching the one-guide-per-axis behaviour of the floor-plane resolver).
115
+ */
116
+ export declare function detectAlongWallAlignment(moving: OpeningSpan, siblings: readonly OpeningSpan[], tolerance: number): AlongWallAlignment | null;
117
+ /**
118
+ * The closest coincidence between the moving opening's sill/centre/top and any
119
+ * sibling's sill/centre/top, within `tolerance` — the "these two windows share
120
+ * a sill height" detector. Same single-best-match policy as the along-wall
121
+ * variant.
122
+ */
123
+ export declare function detectVerticalAlignment(moving: OpeningSpan, siblings: readonly OpeningSpan[], tolerance: number): VerticalAlignment | null;
124
+ /**
125
+ * Figma-style equal-spacing detection: order all openings along the wall, look
126
+ * at the clearances BETWEEN consecutive openings, and return the longest run of
127
+ * ≥2 consecutive gaps that are equal within `tolerance` and that the moving
128
+ * opening participates in (so the badges only appear while the drag is actually
129
+ * forming or extending a series). Returns null when no such run exists.
130
+ *
131
+ * Gaps below `minGap` (touching/overlapping openings) break a run — a row of
132
+ * flush openings is not "equally spaced".
133
+ */
134
+ export declare function detectEqualSpacing(allOpenings: readonly OpeningSpan[], movingId: string, tolerance: number, minGap: number): EqualSpacingRun | null;
135
+ /**
136
+ * Compute every proximity/alignment guide for the moving opening in one pass.
137
+ * Pure: feed it the moving opening's wall-local span, its same-wall siblings,
138
+ * and the wall extent; render the result in whichever view.
139
+ */
140
+ export declare function computeOpeningGuides(input: OpeningGuideInput): OpeningGuides;
141
+ //# sourceMappingURL=opening-guides.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opening-guides.d.ts","sourceRoot":"","sources":["../../src/services/opening-guides.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,267 @@
1
+ // Proximity / alignment guides for wall-hosted openings (doors, windows).
2
+ //
3
+ // Pure geometry over a single host wall's LOCAL frame — no Three.js, no scene
4
+ // store, no React — so it runs identically for the 3D viewport and the 2D
5
+ // floor plan and is unit-testable in isolation. Callers extract the spans from
6
+ // the scene graph (an opening's `position[0]` is its along-wall centre, its
7
+ // `position[1]` its vertical centre with the wall base at y=0) and feed them in;
8
+ // the renderers transform the returned wall-local coordinates back to world
9
+ // (3D) or plan (2D).
10
+ //
11
+ // What it produces, mirroring the affordances architects expect (and Figma's
12
+ // smart guides):
13
+ // - sill/head : a window's bottom edge → floor and top edge → wall top.
14
+ // - edge gaps : along-wall clearance to the nearest neighbour opening (or
15
+ // the wall end) on each side.
16
+ // - alongWall : the moving opening's edge/centre lining up with a
17
+ // neighbour's edge/centre along the wall.
18
+ // - vertical : two openings sharing a sill / head / vertical centre.
19
+ // - equalSpacing : a run of 3+ openings with (near-)equal gaps between them.
20
+ //
21
+ // Detection is passive — it reports what currently coincides within tolerance
22
+ // and the snap delta that would make it exact, leaving the snap decision to the
23
+ // caller's manipulation policy (grid vs. alignment vs. Shift bypass).
24
+ export const DEFAULT_OPENING_GUIDE_TOLERANCES = {
25
+ // Parity with the along-wall snap threshold (`ALONG_WALL_ALIGN_THRESHOLD_M`).
26
+ align: 0.08,
27
+ equalSpacing: 0.03,
28
+ minGap: 0.02,
29
+ };
30
+ const leftEdge = (s) => s.centerS - s.width / 2;
31
+ const rightEdge = (s) => s.centerS + s.width / 2;
32
+ const bottomEdge = (s) => s.centerY - s.height / 2;
33
+ const topEdge = (s) => s.centerY + s.height / 2;
34
+ function alongWallFeatureCoord(s, feature) {
35
+ if (feature === 'left')
36
+ return leftEdge(s);
37
+ if (feature === 'right')
38
+ return rightEdge(s);
39
+ return s.centerS;
40
+ }
41
+ function verticalFeatureCoord(s, feature) {
42
+ if (feature === 'sill')
43
+ return bottomEdge(s);
44
+ if (feature === 'top')
45
+ return topEdge(s);
46
+ return s.centerY;
47
+ }
48
+ const ALONG_WALL_FEATURES = ['left', 'center', 'right'];
49
+ const VERTICAL_FEATURES = ['sill', 'center', 'top'];
50
+ /**
51
+ * Edge-to-edge clearance from the moving opening to the nearest neighbour on
52
+ * each side, falling back to the wall ends when there is no neighbour — the
53
+ * "how much wall is left here" reading. Returns 0–2 gaps (one per side); a side
54
+ * is omitted when its clearance is below `minGap` (the opening is flush against
55
+ * or overlapping that neighbour).
56
+ */
57
+ export function computeEdgeGaps(moving, siblings, wall, minGap) {
58
+ const movingLeft = leftEdge(moving);
59
+ const movingRight = rightEdge(moving);
60
+ // A sibling that straddles one of the moving opening's edges is an OVERLAP,
61
+ // not a neighbour: there is no clearance on that side, and we must not fall
62
+ // back to the wall end (which would report a misleading distance measured
63
+ // "through" the overlapping opening).
64
+ const leftCrossed = siblings.some((s) => leftEdge(s) < movingLeft && rightEdge(s) > movingLeft);
65
+ const rightCrossed = siblings.some((s) => leftEdge(s) < movingRight && rightEdge(s) > movingRight);
66
+ let leftNeighbour = null;
67
+ let rightNeighbour = null;
68
+ for (const sib of siblings) {
69
+ const sibRight = rightEdge(sib);
70
+ const sibLeft = leftEdge(sib);
71
+ // Entirely to the left of the moving opening → candidate left neighbour.
72
+ if (sibRight <= movingLeft && (leftNeighbour === null || sibRight > leftNeighbour.s)) {
73
+ leftNeighbour = { s: sibRight, id: sib.id };
74
+ }
75
+ // Entirely to the right → candidate right neighbour.
76
+ if (sibLeft >= movingRight && (rightNeighbour === null || sibLeft < rightNeighbour.s)) {
77
+ rightNeighbour = { s: sibLeft, id: sib.id };
78
+ }
79
+ }
80
+ const gaps = [];
81
+ if (!leftCrossed) {
82
+ const leftToS = leftNeighbour ? leftNeighbour.s : 0;
83
+ const leftDistance = movingLeft - leftToS;
84
+ if (leftDistance >= minGap) {
85
+ gaps.push({
86
+ side: 'left',
87
+ distance: leftDistance,
88
+ fromS: movingLeft,
89
+ toS: leftToS,
90
+ target: leftNeighbour ? 'opening' : 'wall-start',
91
+ targetId: leftNeighbour?.id,
92
+ });
93
+ }
94
+ }
95
+ if (!rightCrossed) {
96
+ const rightToS = rightNeighbour ? rightNeighbour.s : wall.length;
97
+ const rightDistance = rightToS - movingRight;
98
+ if (rightDistance >= minGap) {
99
+ gaps.push({
100
+ side: 'right',
101
+ distance: rightDistance,
102
+ fromS: movingRight,
103
+ toS: rightToS,
104
+ target: rightNeighbour ? 'opening' : 'wall-end',
105
+ targetId: rightNeighbour?.id,
106
+ });
107
+ }
108
+ }
109
+ return gaps;
110
+ }
111
+ /**
112
+ * The closest coincidence between any of the moving opening's edges/centre and
113
+ * any sibling's edges/centre along the wall, within `tolerance`. Edge-to-edge
114
+ * and centre-to-centre are weighed equally; the single closest pair wins
115
+ * (matching the one-guide-per-axis behaviour of the floor-plane resolver).
116
+ */
117
+ export function detectAlongWallAlignment(moving, siblings, tolerance) {
118
+ let best = null;
119
+ let bestAbs = tolerance;
120
+ for (const movingFeature of ALONG_WALL_FEATURES) {
121
+ const movingCoord = alongWallFeatureCoord(moving, movingFeature);
122
+ for (const sib of siblings) {
123
+ if (sib.id === moving.id)
124
+ continue;
125
+ for (const targetFeature of ALONG_WALL_FEATURES) {
126
+ const targetCoord = alongWallFeatureCoord(sib, targetFeature);
127
+ const diff = targetCoord - movingCoord;
128
+ const abs = Math.abs(diff);
129
+ if (abs <= bestAbs && (best === null || abs < bestAbs)) {
130
+ bestAbs = abs;
131
+ best = {
132
+ s: targetCoord,
133
+ movingFeature,
134
+ targetId: sib.id,
135
+ targetFeature,
136
+ snap: diff,
137
+ };
138
+ }
139
+ }
140
+ }
141
+ }
142
+ return best;
143
+ }
144
+ /**
145
+ * The closest coincidence between the moving opening's sill/centre/top and any
146
+ * sibling's sill/centre/top, within `tolerance` — the "these two windows share
147
+ * a sill height" detector. Same single-best-match policy as the along-wall
148
+ * variant.
149
+ */
150
+ export function detectVerticalAlignment(moving, siblings, tolerance) {
151
+ let best = null;
152
+ let bestAbs = tolerance;
153
+ for (const movingFeature of VERTICAL_FEATURES) {
154
+ const movingCoord = verticalFeatureCoord(moving, movingFeature);
155
+ for (const sib of siblings) {
156
+ if (sib.id === moving.id)
157
+ continue;
158
+ for (const targetFeature of VERTICAL_FEATURES) {
159
+ const targetCoord = verticalFeatureCoord(sib, targetFeature);
160
+ const diff = targetCoord - movingCoord;
161
+ const abs = Math.abs(diff);
162
+ if (abs <= bestAbs && (best === null || abs < bestAbs)) {
163
+ bestAbs = abs;
164
+ best = {
165
+ y: targetCoord,
166
+ movingFeature,
167
+ targetId: sib.id,
168
+ targetFeature,
169
+ snap: diff,
170
+ };
171
+ }
172
+ }
173
+ }
174
+ }
175
+ return best;
176
+ }
177
+ /**
178
+ * Figma-style equal-spacing detection: order all openings along the wall, look
179
+ * at the clearances BETWEEN consecutive openings, and return the longest run of
180
+ * ≥2 consecutive gaps that are equal within `tolerance` and that the moving
181
+ * opening participates in (so the badges only appear while the drag is actually
182
+ * forming or extending a series). Returns null when no such run exists.
183
+ *
184
+ * Gaps below `minGap` (touching/overlapping openings) break a run — a row of
185
+ * flush openings is not "equally spaced".
186
+ */
187
+ export function detectEqualSpacing(allOpenings, movingId, tolerance, minGap) {
188
+ if (allOpenings.length < 3)
189
+ return null;
190
+ const sorted = [...allOpenings].sort((a, b) => a.centerS - b.centerS);
191
+ const movingIndex = sorted.findIndex((s) => s.id === movingId);
192
+ if (movingIndex < 0)
193
+ return null;
194
+ // Clearance between opening i and i+1.
195
+ const gaps = [];
196
+ for (let i = 0; i < sorted.length - 1; i++) {
197
+ const a = sorted[i];
198
+ const b = sorted[i + 1];
199
+ if (!a || !b)
200
+ continue;
201
+ const fromS = rightEdge(a);
202
+ const toS = leftEdge(b);
203
+ gaps.push({ value: toS - fromS, fromS, toS });
204
+ }
205
+ // Longest contiguous window of gaps that are (a) each ≥ minGap and (b)
206
+ // mutually equal within tolerance (window max − min ≤ tolerance), spanning at
207
+ // least 2 gaps and including the moving opening. Brute force over windows
208
+ // (openings per wall are few). A first-gap-anchored greedy scan is NOT
209
+ // equivalent: it drops a valid run that begins partway through a drifting
210
+ // sequence — e.g. gaps 1.00, 1.02, 1.04 with the moving opening at the end,
211
+ // where [1.02, 1.04] is a real run. On a length tie the leftmost window wins,
212
+ // for determinism.
213
+ let best = null;
214
+ for (let lo = 0; lo < gaps.length; lo++) {
215
+ let min = Number.POSITIVE_INFINITY;
216
+ let max = Number.NEGATIVE_INFINITY;
217
+ for (let hi = lo; hi < gaps.length; hi++) {
218
+ const gap = gaps[hi];
219
+ if (!gap || gap.value < minGap)
220
+ break; // a sub-minGap gap can't join a run
221
+ min = Math.min(min, gap.value);
222
+ max = Math.max(max, gap.value);
223
+ if (max - min > tolerance)
224
+ break; // extending only widens the spread
225
+ const gapCount = hi - lo + 1;
226
+ if (gapCount < 2)
227
+ continue;
228
+ const firstOpening = lo; // gap i sits between openings i and i+1
229
+ const lastOpening = hi + 1;
230
+ if (movingIndex < firstOpening || movingIndex > lastOpening)
231
+ continue;
232
+ if (best !== null && gapCount <= best.segments.length)
233
+ continue;
234
+ const windowGaps = gaps.slice(lo, hi + 1);
235
+ best = {
236
+ gap: windowGaps.reduce((sum, g) => sum + g.value, 0) / windowGaps.length,
237
+ segments: windowGaps.map((g) => ({ fromS: g.fromS, toS: g.toS })),
238
+ openingIds: sorted.slice(firstOpening, lastOpening + 1).map((s) => s.id),
239
+ };
240
+ }
241
+ }
242
+ return best;
243
+ }
244
+ /**
245
+ * Compute every proximity/alignment guide for the moving opening in one pass.
246
+ * Pure: feed it the moving opening's wall-local span, its same-wall siblings,
247
+ * and the wall extent; render the result in whichever view.
248
+ */
249
+ export function computeOpeningGuides(input) {
250
+ const tol = { ...DEFAULT_OPENING_GUIDE_TOLERANCES, ...input.tolerances };
251
+ const { moving, siblings, wall, includeVertical } = input;
252
+ const sillHead = includeVertical
253
+ ? {
254
+ sill: bottomEdge(moving),
255
+ bottomY: bottomEdge(moving),
256
+ head: wall.height - topEdge(moving),
257
+ topY: topEdge(moving),
258
+ }
259
+ : null;
260
+ return {
261
+ sillHead,
262
+ gaps: computeEdgeGaps(moving, siblings, wall, tol.minGap),
263
+ alongWall: detectAlongWallAlignment(moving, siblings, tol.align),
264
+ vertical: includeVertical ? detectVerticalAlignment(moving, siblings, tol.align) : null,
265
+ equalSpacing: detectEqualSpacing([moving, ...siblings], moving.id, tol.equalSpacing, tol.minGap),
266
+ };
267
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=opening-guides.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opening-guides.test.d.ts","sourceRoot":"","sources":["../../src/services/opening-guides.test.ts"],"names":[],"mappings":""}