@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,210 @@
1
+ import { beforeEach, describe, expect, test } from 'bun:test';
2
+ import { createSceneApi } from '../registry/scene-api';
3
+ import useScene from '../store/use-scene';
4
+ globalThis.requestAnimationFrame ??= ((cb) => {
5
+ cb(0);
6
+ return 0;
7
+ });
8
+ globalThis.cancelAnimationFrame ??=
9
+ () => { };
10
+ /**
11
+ * Validates the "single-undo dance" pattern used by Stage D actions:
12
+ *
13
+ * action.commit:
14
+ * scene.restoreAll() // revert via snapshot (paused → no zundo record)
15
+ * scene.resumeHistory() // unpause zundo
16
+ * scene.update(...) // re-apply final → zundo records one diff
17
+ * return true
18
+ *
19
+ * After the dance, undo() should roll back ONLY the drag's commit —
20
+ * never further back than that. The fence-bend regression that surfaced
21
+ * after Phase 5 Stage D porting was reportedly losing the prior create
22
+ * step on undo; this test pins the correct behavior.
23
+ */
24
+ const FENCE_ID = 'fence_test';
25
+ function makeFence(curveOffset) {
26
+ return {
27
+ id: FENCE_ID,
28
+ type: 'fence',
29
+ parentId: null,
30
+ object: 'node',
31
+ visible: true,
32
+ metadata: {},
33
+ start: [0, 0],
34
+ end: [3, 0],
35
+ height: 1.8,
36
+ thickness: 0.08,
37
+ baseHeight: 0.22,
38
+ postSpacing: 2,
39
+ postSize: 0.1,
40
+ topRailHeight: 0.04,
41
+ groundClearance: 0,
42
+ edgeInset: 0.015,
43
+ baseStyle: 'grounded',
44
+ showInfill: true,
45
+ color: '#ffffff',
46
+ style: 'slat',
47
+ curveOffset,
48
+ };
49
+ }
50
+ describe('Single-undo dance', () => {
51
+ beforeEach(() => {
52
+ useScene.setState({ nodes: {}, rootNodeIds: [] });
53
+ useScene.temporal.getState().clear();
54
+ });
55
+ test('curve-style commit yields a single undo step', () => {
56
+ // 1. Create the fence (recorded by zundo).
57
+ useScene.getState().createNode(makeFence(0));
58
+ const pastCountAfterCreate = useScene.temporal.getState().pastStates.length;
59
+ // 2. Simulate the drag.
60
+ const scene = createSceneApi(useScene);
61
+ scene.pauseHistory();
62
+ scene.update(FENCE_ID, { curveOffset: 0.2 });
63
+ scene.update(FENCE_ID, { curveOffset: 0.5 });
64
+ expect(useScene.getState().nodes[FENCE_ID].curveOffset).toBe(0.5);
65
+ // 3. Commit dance.
66
+ scene.restoreAll();
67
+ expect(useScene.getState().nodes[FENCE_ID].curveOffset).toBe(0);
68
+ scene.resumeHistory();
69
+ scene.update(FENCE_ID, { curveOffset: 0.5 });
70
+ const pastCountAfterDance = useScene.temporal.getState().pastStates.length;
71
+ expect(pastCountAfterDance).toBe(pastCountAfterCreate + 1);
72
+ expect(useScene.getState().nodes[FENCE_ID].curveOffset).toBe(0.5);
73
+ // 4. Undo — should return fence to curveOffset 0, NOT delete it.
74
+ useScene.temporal.getState().undo();
75
+ const fenceAfterUndo = useScene.getState().nodes[FENCE_ID];
76
+ expect(fenceAfterUndo).toBeDefined();
77
+ expect(fenceAfterUndo.curveOffset).toBe(0);
78
+ });
79
+ test('StrictMode double-mount: dispose-then-restart preserves history', () => {
80
+ useScene.getState().createNode(makeFence(0));
81
+ const pastBeforeBend = useScene.temporal.getState().pastStates.length;
82
+ // Simulate StrictMode: first mount → cleanup (dispose) → second mount → drag → commit.
83
+ const scene = createSceneApi(useScene);
84
+ // Mount 1.
85
+ scene.pauseHistory();
86
+ // Mount 1 cleanup (StrictMode): no apply happened yet. dispose-equivalent.
87
+ scene.restoreAll(); // snapshot empty, no-op.
88
+ scene.resumeHistory();
89
+ // Mount 2.
90
+ scene.pauseHistory();
91
+ // Drag.
92
+ scene.update(FENCE_ID, { curveOffset: 0.3 });
93
+ scene.update(FENCE_ID, { curveOffset: 0.7 });
94
+ // Commit dance.
95
+ scene.restoreAll();
96
+ scene.resumeHistory();
97
+ scene.update(FENCE_ID, { curveOffset: 0.7 });
98
+ const pastAfterBend = useScene.temporal.getState().pastStates.length;
99
+ // Exactly one new entry: the pre-bend state. Not two (which would mean
100
+ // StrictMode's first mount/cleanup polluted history).
101
+ expect(pastAfterBend).toBe(pastBeforeBend + 1);
102
+ useScene.temporal.getState().undo();
103
+ expect(useScene.getState().nodes[FENCE_ID]).toBeDefined();
104
+ expect(useScene.getState().nodes[FENCE_ID].curveOffset).toBe(0);
105
+ });
106
+ test('commit-returns-false (no change) does NOT consume the prior pastState', () => {
107
+ // This is the suspected bend regression: when action.commit returns
108
+ // false (draft.curveOffset === ctx.originalCurveOffset), session.commit
109
+ // calls scene.restoreAll() but doesn't push to pastStates. Subsequent
110
+ // undo pops the PRIOR action (e.g. fence creation), not the no-op
111
+ // bend.
112
+ useScene.getState().createNode(makeFence(0));
113
+ const pastBeforeBend = useScene.temporal.getState().pastStates.length;
114
+ const scene = createSceneApi(useScene);
115
+ scene.pauseHistory();
116
+ // Drag back to original — simulates no-op bend.
117
+ scene.update(FENCE_ID, { curveOffset: 0.0 });
118
+ // Cancel path (mimics session.commit → action.commit returns false → scene.restoreAll → terminate).
119
+ scene.restoreAll();
120
+ scene.resumeHistory();
121
+ const pastAfterNoOp = useScene.temporal.getState().pastStates.length;
122
+ expect(pastAfterNoOp).toBe(pastBeforeBend); // no entries added
123
+ // Now undo — this should be a no-op (state unchanged), but pops the create.
124
+ useScene.temporal.getState().undo();
125
+ // ⚠️ Reproduces the bug — undo removes the fence:
126
+ const fence = useScene.getState().nodes[FENCE_ID];
127
+ if (fence === undefined) {
128
+ // Bug reproduced. The "no-op bend" allowed Ctrl-Z to fall through
129
+ // to the fence creation. Fix is in action.commit: don't return false
130
+ // — push a no-op entry instead, or guard against the cancel path.
131
+ expect(fence).toBeUndefined();
132
+ }
133
+ else {
134
+ expect(fence.curveOffset).toBe(0);
135
+ }
136
+ });
137
+ test('full session flow via createDragSession with real action.commit dance', async () => {
138
+ // Reproduces the actual wrapper flow:
139
+ // - createNode → session.start → moves → grid:click → session.commit.
140
+ // The action.commit does the dance internally.
141
+ const { createDragSession } = await import('./drag-session');
142
+ useScene.getState().createNode(makeFence(0));
143
+ const pastBeforeBend = useScene.temporal.getState().pastStates.length;
144
+ const scene = createSceneApi(useScene);
145
+ const action = {
146
+ begin: () => ({ original: 0 }),
147
+ preview: (_ctx, point) => ({ offset: point[0] }),
148
+ apply: (draft, _ctx, s) => {
149
+ s.update(FENCE_ID, { curveOffset: draft.offset });
150
+ return [FENCE_ID];
151
+ },
152
+ commit: (draft, ctx, s) => {
153
+ if (draft.offset === ctx.original)
154
+ return false;
155
+ s.restoreAll();
156
+ s.resumeHistory();
157
+ s.update(FENCE_ID, { curveOffset: draft.offset });
158
+ return true;
159
+ },
160
+ cancel: () => { },
161
+ };
162
+ const session = createDragSession(action, scene);
163
+ session.start({ point: [0, 0] });
164
+ session.move([0.3, 0], { shift: false, alt: false, ctrl: false, meta: false });
165
+ session.move([0.5, 0], { shift: false, alt: false, ctrl: false, meta: false });
166
+ const okCommit = session.commit();
167
+ expect(okCommit).toBe(true);
168
+ const pastAfter = useScene.temporal.getState().pastStates.length;
169
+ expect(pastAfter).toBe(pastBeforeBend + 1);
170
+ expect(useScene.getState().nodes[FENCE_ID].curveOffset).toBe(0.5);
171
+ useScene.temporal.getState().undo();
172
+ const after = useScene.getState().nodes[FENCE_ID];
173
+ expect(after).toBeDefined();
174
+ expect(after.curveOffset).toBe(0);
175
+ });
176
+ test('REAL bend (draft != original): one Ctrl-Z undoes only the bend', () => {
177
+ useScene.getState().createNode(makeFence(0));
178
+ const stateAfterCreate = useScene.getState().nodes[FENCE_ID];
179
+ expect(stateAfterCreate.curveOffset).toBe(0);
180
+ const scene = createSceneApi(useScene);
181
+ scene.pauseHistory();
182
+ // Simulate a real drag: capture original, mutate to non-zero.
183
+ scene.update(FENCE_ID, { curveOffset: 0.5 });
184
+ expect(useScene.getState().nodes[FENCE_ID].curveOffset).toBe(0.5);
185
+ // Dance.
186
+ scene.restoreAll();
187
+ expect(useScene.getState().nodes[FENCE_ID].curveOffset).toBe(0);
188
+ scene.resumeHistory();
189
+ scene.update(FENCE_ID, { curveOffset: 0.5 });
190
+ expect(useScene.getState().nodes[FENCE_ID].curveOffset).toBe(0.5);
191
+ // First Ctrl-Z should undo the bend.
192
+ useScene.temporal.getState().undo();
193
+ const afterFirstUndo = useScene.getState().nodes[FENCE_ID];
194
+ expect(afterFirstUndo).toBeDefined();
195
+ expect(afterFirstUndo?.curveOffset).toBe(0);
196
+ });
197
+ test('a SECOND undo rolls the create step back', () => {
198
+ useScene.getState().createNode(makeFence(0));
199
+ const scene = createSceneApi(useScene);
200
+ scene.pauseHistory();
201
+ scene.update(FENCE_ID, { curveOffset: 0.5 });
202
+ scene.restoreAll();
203
+ scene.resumeHistory();
204
+ scene.update(FENCE_ID, { curveOffset: 0.5 });
205
+ useScene.temporal.getState().undo();
206
+ expect(useScene.getState().nodes[FENCE_ID]).toBeDefined();
207
+ useScene.temporal.getState().undo();
208
+ expect(useScene.getState().nodes[FENCE_ID]).toBeUndefined();
209
+ });
210
+ });
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Pure snap math — no React, no R3F, no scene access.
3
+ *
4
+ * Phase 1 ships the kind-agnostic snappers (grid + angle). Wall-specific
5
+ * snapping (snap-to-endpoint, snap-along-T) currently lives in
6
+ * `editor/src/components/tools/wall/wall-drafting.ts` and stays there until
7
+ * Phase 3, when the wall migration ports it here behind a `wallSnap` namespace.
8
+ *
9
+ * The functions here are stable contract — Phase 3 only adds, never removes.
10
+ */
11
+ export type Vec2 = readonly [number, number];
12
+ export type Vec3 = readonly [number, number, number];
13
+ /** Default planar grid spacing in meters. Matches the editor's wall tool. */
14
+ export declare const DEFAULT_GRID_STEP = 0.25;
15
+ /** Default angle-snap step — π/12 = 15°. */
16
+ export declare const DEFAULT_ANGLE_STEP: number;
17
+ /** Snaps a single scalar to the nearest multiple of `step`. */
18
+ export declare function snapScalar(value: number, step?: number): number;
19
+ /** Snaps a 2D point to a regular planar grid. */
20
+ export declare function snapPointToGrid(point: Vec2, step?: number): Vec2;
21
+ /** Snaps a 3D point to a regular grid in the X/Z plane, preserving Y. */
22
+ export declare function snapVec3ToGrid(point: Vec3, step?: number): Vec3;
23
+ /**
24
+ * Snap a world XZ point to the grid, then express it in the local frame of
25
+ * a building positioned at `buildingPosition` with rotation `buildingRotationY`
26
+ * (radians, around the Y axis). Returns both the snapped world point and its
27
+ * local-frame equivalent, so callers can render in either frame without
28
+ * recomputing the rotation.
29
+ *
30
+ * Use when a tool needs to keep snapping on the world grid (the grid the
31
+ * editor renders) even when the active building is rotated. Snapping in the
32
+ * building's local frame would otherwise chase the rotated axes and miss
33
+ * the visible grid lines.
34
+ */
35
+ export declare function snapWorldXZToBuildingLocal(worldX: number, worldZ: number, buildingPosition: Vec3, buildingRotationY: number, step?: number): {
36
+ world: [number, number];
37
+ local: [number, number];
38
+ };
39
+ /**
40
+ * Snaps a cursor point to the nearest angle multiple of `angleStep` (radians)
41
+ * measured from `from`, preserving distance. Useful for axis/diagonal-locked
42
+ * placement and wall draft endpoint locking.
43
+ *
44
+ * After the angle snap, the result is grid-snapped if `gridStep` is provided
45
+ * — keeps endpoints landing on grid intersections.
46
+ */
47
+ export declare function snapPointToAngle(from: Vec2, cursor: Vec2, angleStep?: number, gridStep?: number): Vec2;
48
+ /**
49
+ * Snaps a cursor point onto the nearest angle ray from `from` (multiples of
50
+ * `angleStep`), projecting the cursor onto that ray, then snaps the distance
51
+ * ALONG the ray to `distanceStep`. Unlike `snapPointToAngle` with a
52
+ * `gridStep`, the result stays exactly on the snapped ray — grid-snapping
53
+ * after the angle projection pulls points off non-axis rays.
54
+ */
55
+ export declare function snapPointAlongAngleRay(from: Vec2, cursor: Vec2, angleStep?: number, distanceStep?: number): Vec2;
56
+ /**
57
+ * Snaps an angle (in radians) to the nearest entry in `snapAngles` (also in
58
+ * radians). Returns the original angle if no entry is within `toleranceRad`.
59
+ */
60
+ export declare function snapAngleToList(angle: number, snapAngles: readonly number[], toleranceRad?: number): number;
61
+ /**
62
+ * Stable surface that `DragAction.snap` callbacks receive. Phase 1 ships
63
+ * `grid` and `angle`. Phase 3 adds a `wall` namespace populated by wall
64
+ * migration. Plugin authors should target this facade rather than importing
65
+ * the individual functions, so future Phase contributions become visible
66
+ * without code changes.
67
+ */
68
+ export type SnapServices = {
69
+ grid: {
70
+ snap: (point: Vec2, step?: number) => Vec2;
71
+ snapVec3: (point: Vec3, step?: number) => Vec3;
72
+ snapScalar: (value: number, step?: number) => number;
73
+ };
74
+ angle: {
75
+ snapTo: (from: Vec2, cursor: Vec2, angleStep?: number, gridStep?: number) => Vec2;
76
+ snapToList: (angle: number, list: readonly number[], toleranceRad?: number) => number;
77
+ };
78
+ };
79
+ export declare const snapServices: SnapServices;
80
+ //# sourceMappingURL=snap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snap.d.ts","sourceRoot":"","sources":["../../src/services/snap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAC5C,MAAM,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAEpD,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,OAAO,CAAA;AAErC,4CAA4C;AAC5C,eAAO,MAAM,kBAAkB,QAAe,CAAA;AAI9C,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,MAA0B,GAAG,MAAM,CAGlF;AAED,iDAAiD;AACjD,wBAAgB,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,GAAE,MAA0B,GAAG,IAAI,CAEnF;AAED,yEAAyE;AACzE,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,GAAE,MAA0B,GAAG,IAAI,CAElF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,IAAI,EACtB,iBAAiB,EAAE,MAAM,EACzB,IAAI,GAAE,MAA0B,GAC/B;IAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CA2BtD;AAID;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,IAAI,EACZ,SAAS,GAAE,MAA2B,EACtC,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAWN;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,IAAI,EACZ,SAAS,GAAE,MAA2B,EACtC,YAAY,CAAC,EAAE,MAAM,GACpB,IAAI,CAYN;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,YAAY,GAAE,MAAqB,GAClC,MAAM,CAcR;AAID;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE;QACJ,IAAI,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;QAC1C,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;QAC9C,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;KACrD,CAAA;IACD,KAAK,EAAE;QACL,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;QACjF,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;KACtF,CAAA;CACF,CAAA;AAED,eAAO,MAAM,YAAY,EAAE,YAU1B,CAAA"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Pure snap math — no React, no R3F, no scene access.
3
+ *
4
+ * Phase 1 ships the kind-agnostic snappers (grid + angle). Wall-specific
5
+ * snapping (snap-to-endpoint, snap-along-T) currently lives in
6
+ * `editor/src/components/tools/wall/wall-drafting.ts` and stays there until
7
+ * Phase 3, when the wall migration ports it here behind a `wallSnap` namespace.
8
+ *
9
+ * The functions here are stable contract — Phase 3 only adds, never removes.
10
+ */
11
+ /** Default planar grid spacing in meters. Matches the editor's wall tool. */
12
+ export const DEFAULT_GRID_STEP = 0.25;
13
+ /** Default angle-snap step — π/12 = 15°. */
14
+ export const DEFAULT_ANGLE_STEP = Math.PI / 12;
15
+ // ─── Grid snap ────────────────────────────────────────────────────────
16
+ /** Snaps a single scalar to the nearest multiple of `step`. */
17
+ export function snapScalar(value, step = DEFAULT_GRID_STEP) {
18
+ if (step <= 0)
19
+ return value;
20
+ return Math.round(value / step) * step;
21
+ }
22
+ /** Snaps a 2D point to a regular planar grid. */
23
+ export function snapPointToGrid(point, step = DEFAULT_GRID_STEP) {
24
+ return [snapScalar(point[0], step), snapScalar(point[1], step)];
25
+ }
26
+ /** Snaps a 3D point to a regular grid in the X/Z plane, preserving Y. */
27
+ export function snapVec3ToGrid(point, step = DEFAULT_GRID_STEP) {
28
+ return [snapScalar(point[0], step), point[1], snapScalar(point[2], step)];
29
+ }
30
+ /**
31
+ * Snap a world XZ point to the grid, then express it in the local frame of
32
+ * a building positioned at `buildingPosition` with rotation `buildingRotationY`
33
+ * (radians, around the Y axis). Returns both the snapped world point and its
34
+ * local-frame equivalent, so callers can render in either frame without
35
+ * recomputing the rotation.
36
+ *
37
+ * Use when a tool needs to keep snapping on the world grid (the grid the
38
+ * editor renders) even when the active building is rotated. Snapping in the
39
+ * building's local frame would otherwise chase the rotated axes and miss
40
+ * the visible grid lines.
41
+ */
42
+ export function snapWorldXZToBuildingLocal(worldX, worldZ, buildingPosition, buildingRotationY, step = DEFAULT_GRID_STEP) {
43
+ if (step <= 0) {
44
+ const dx = worldX - buildingPosition[0];
45
+ const dz = worldZ - buildingPosition[2];
46
+ const cos = Math.cos(buildingRotationY);
47
+ const sin = Math.sin(buildingRotationY);
48
+ return {
49
+ world: [worldX, worldZ],
50
+ local: [dx * cos - dz * sin, dx * sin + dz * cos],
51
+ };
52
+ }
53
+ const snappedWX = Math.round(worldX / step) * step;
54
+ const snappedWZ = Math.round(worldZ / step) * step;
55
+ const dx = snappedWX - buildingPosition[0];
56
+ const dz = snappedWZ - buildingPosition[2];
57
+ const cos = Math.cos(buildingRotationY);
58
+ const sin = Math.sin(buildingRotationY);
59
+ // The forward (local → world) rotation used in the editor is
60
+ // wx = bx + lx*cos + lz*sin
61
+ // wz = bz - lx*sin + lz*cos
62
+ // so the inverse (orthogonal, so transpose) is
63
+ // lx = dx*cos - dz*sin
64
+ // lz = dx*sin + dz*cos
65
+ return {
66
+ world: [snappedWX, snappedWZ],
67
+ local: [dx * cos - dz * sin, dx * sin + dz * cos],
68
+ };
69
+ }
70
+ // ─── Angle snap ───────────────────────────────────────────────────────
71
+ /**
72
+ * Snaps a cursor point to the nearest angle multiple of `angleStep` (radians)
73
+ * measured from `from`, preserving distance. Useful for axis/diagonal-locked
74
+ * placement and wall draft endpoint locking.
75
+ *
76
+ * After the angle snap, the result is grid-snapped if `gridStep` is provided
77
+ * — keeps endpoints landing on grid intersections.
78
+ */
79
+ export function snapPointToAngle(from, cursor, angleStep = DEFAULT_ANGLE_STEP, gridStep) {
80
+ const dx = cursor[0] - from[0];
81
+ const dz = cursor[1] - from[1];
82
+ const angle = Math.atan2(dz, dx);
83
+ const snappedAngle = Math.round(angle / angleStep) * angleStep;
84
+ const distance = Math.hypot(dx, dz);
85
+ const projected = [
86
+ from[0] + Math.cos(snappedAngle) * distance,
87
+ from[1] + Math.sin(snappedAngle) * distance,
88
+ ];
89
+ return gridStep == null ? projected : snapPointToGrid(projected, gridStep);
90
+ }
91
+ /**
92
+ * Snaps a cursor point onto the nearest angle ray from `from` (multiples of
93
+ * `angleStep`), projecting the cursor onto that ray, then snaps the distance
94
+ * ALONG the ray to `distanceStep`. Unlike `snapPointToAngle` with a
95
+ * `gridStep`, the result stays exactly on the snapped ray — grid-snapping
96
+ * after the angle projection pulls points off non-axis rays.
97
+ */
98
+ export function snapPointAlongAngleRay(from, cursor, angleStep = DEFAULT_ANGLE_STEP, distanceStep) {
99
+ const dx = cursor[0] - from[0];
100
+ const dz = cursor[1] - from[1];
101
+ if (dx === 0 && dz === 0)
102
+ return [from[0], from[1]];
103
+ const angle = Math.atan2(dz, dx);
104
+ const snappedAngle = angleStep > 0 ? Math.round(angle / angleStep) * angleStep : angle;
105
+ const dirX = Math.cos(snappedAngle);
106
+ const dirZ = Math.sin(snappedAngle);
107
+ const projected = dx * dirX + dz * dirZ;
108
+ const distance = distanceStep != null && distanceStep > 0 ? snapScalar(projected, distanceStep) : projected;
109
+ return [from[0] + dirX * distance, from[1] + dirZ * distance];
110
+ }
111
+ /**
112
+ * Snaps an angle (in radians) to the nearest entry in `snapAngles` (also in
113
+ * radians). Returns the original angle if no entry is within `toleranceRad`.
114
+ */
115
+ export function snapAngleToList(angle, snapAngles, toleranceRad = Math.PI / 36) {
116
+ let best = null;
117
+ let bestDelta = Number.POSITIVE_INFINITY;
118
+ for (const target of snapAngles) {
119
+ // wrap delta to [-π, π]
120
+ let delta = ((angle - target) % (Math.PI * 2)) + Math.PI * 3;
121
+ delta = (delta % (Math.PI * 2)) - Math.PI;
122
+ const abs = Math.abs(delta);
123
+ if (abs < bestDelta && abs <= toleranceRad) {
124
+ bestDelta = abs;
125
+ best = target;
126
+ }
127
+ }
128
+ return best ?? angle;
129
+ }
130
+ export const snapServices = {
131
+ grid: {
132
+ snap: snapPointToGrid,
133
+ snapVec3: snapVec3ToGrid,
134
+ snapScalar,
135
+ },
136
+ angle: {
137
+ snapTo: snapPointToAngle,
138
+ snapToList: snapAngleToList,
139
+ },
140
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=snap.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snap.test.d.ts","sourceRoot":"","sources":["../../src/services/snap.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,137 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { DEFAULT_ANGLE_STEP, DEFAULT_GRID_STEP, snapAngleToList, snapPointAlongAngleRay, snapPointToAngle, snapPointToGrid, snapScalar, snapServices, snapVec3ToGrid, } from './snap';
3
+ describe('snapScalar', () => {
4
+ test('rounds to multiples of step', () => {
5
+ expect(snapScalar(0.27, 0.25)).toBe(0.25);
6
+ expect(snapScalar(0.13, 0.25)).toBe(0.25);
7
+ expect(snapScalar(0.12, 0.25)).toBe(0);
8
+ expect(snapScalar(1.7, 0.25)).toBeCloseTo(1.75);
9
+ });
10
+ test('returns input unchanged when step is non-positive', () => {
11
+ expect(snapScalar(0.42, 0)).toBe(0.42);
12
+ expect(snapScalar(0.42, -1)).toBe(0.42);
13
+ });
14
+ test('default step is 0.25m', () => {
15
+ expect(snapScalar(0.3)).toBe(0.25);
16
+ expect(snapScalar(0.4)).toBe(0.5);
17
+ expect(DEFAULT_GRID_STEP).toBe(0.25);
18
+ });
19
+ });
20
+ describe('snapPointToGrid', () => {
21
+ test('snaps both components independently', () => {
22
+ expect(snapPointToGrid([0.3, 0.6], 0.25)).toEqual([0.25, 0.5]);
23
+ });
24
+ test('preserves exact-grid points', () => {
25
+ expect(snapPointToGrid([1, 2], 0.5)).toEqual([1, 2]);
26
+ });
27
+ });
28
+ describe('snapVec3ToGrid', () => {
29
+ test('snaps X and Z, leaves Y untouched', () => {
30
+ expect(snapVec3ToGrid([0.3, 1.7, 0.6], 0.25)).toEqual([0.25, 1.7, 0.5]);
31
+ });
32
+ });
33
+ describe('snapPointToAngle', () => {
34
+ test('snaps to axis (0°) when cursor is near horizontal', () => {
35
+ const from = [0, 0];
36
+ const cursor = [1, 0.05]; // near 0°
37
+ const snapped = snapPointToAngle(from, cursor, Math.PI / 4);
38
+ expect(snapped[0]).toBeCloseTo(1, 1);
39
+ expect(snapped[1]).toBeCloseTo(0, 5);
40
+ });
41
+ test('snaps to 45° at π/4 step', () => {
42
+ const from = [0, 0];
43
+ const cursor = [1, 0.9]; // near 45°
44
+ const snapped = snapPointToAngle(from, cursor, Math.PI / 4);
45
+ // distance preserved (≈ √(1² + 0.9²) ≈ 1.345), angle locked to 45°
46
+ const expectedDist = Math.hypot(1, 0.9);
47
+ expect(snapped[0]).toBeCloseTo(expectedDist * Math.cos(Math.PI / 4));
48
+ expect(snapped[1]).toBeCloseTo(expectedDist * Math.sin(Math.PI / 4));
49
+ });
50
+ test('default angle step is π/12 (15°)', () => {
51
+ expect(DEFAULT_ANGLE_STEP).toBeCloseTo(Math.PI / 12);
52
+ });
53
+ test('grid-snaps the projected point when gridStep is provided', () => {
54
+ const from = [0, 0];
55
+ const cursor = [1.05, 0.02]; // ~horizontal, slightly off grid
56
+ const snapped = snapPointToAngle(from, cursor, Math.PI / 4, 0.25);
57
+ // After 0° lock + 0.25m grid, X must be a 0.25 multiple.
58
+ expect(snapped[0] / 0.25).toBeCloseTo(Math.round(snapped[0] / 0.25));
59
+ });
60
+ test('preserves distance from `from`', () => {
61
+ const from = [2, 3];
62
+ const cursor = [3, 4];
63
+ const distance = Math.hypot(1, 1);
64
+ const snapped = snapPointToAngle(from, cursor, Math.PI / 4);
65
+ expect(Math.hypot(snapped[0] - 2, snapped[1] - 3)).toBeCloseTo(distance);
66
+ });
67
+ });
68
+ describe('snapPointAlongAngleRay', () => {
69
+ test('stays exactly on the 15° ray while distance-snapping to the grid step', () => {
70
+ const from = [0, 0];
71
+ const cursor = [2, 0.5]; // ≈14° — snaps to 15°
72
+ const snapped = snapPointAlongAngleRay(from, cursor, Math.PI / 12, 0.25);
73
+ expect(Math.atan2(snapped[1], snapped[0])).toBeCloseTo(Math.PI / 12, 10);
74
+ const distance = Math.hypot(snapped[0], snapped[1]);
75
+ expect(distance / 0.25).toBeCloseTo(Math.round(distance / 0.25), 10);
76
+ });
77
+ test('grid-snapping after the angle projection would pull the point off the ray', () => {
78
+ const from = [0, 0];
79
+ const cursor = [2, 0.5];
80
+ const offRay = snapPointToAngle(from, cursor, Math.PI / 12, 0.25);
81
+ expect(Math.atan2(offRay[1], offRay[0])).not.toBeCloseTo(Math.PI / 12, 4);
82
+ });
83
+ test('45° back-compat: locks to the diagonal with grid-multiple distance', () => {
84
+ const from = [1, 1];
85
+ const cursor = [2.1, 1.9]; // near 45° from `from`
86
+ const snapped = snapPointAlongAngleRay(from, cursor, Math.PI / 4, 0.25);
87
+ expect(Math.atan2(snapped[1] - 1, snapped[0] - 1)).toBeCloseTo(Math.PI / 4, 10);
88
+ const distance = Math.hypot(snapped[0] - 1, snapped[1] - 1);
89
+ expect(distance / 0.25).toBeCloseTo(Math.round(distance / 0.25), 10);
90
+ });
91
+ test('preserves the projected distance when no distanceStep is given', () => {
92
+ const from = [0, 0];
93
+ const cursor = [1, 0.05]; // near 0°
94
+ const snapped = snapPointAlongAngleRay(from, cursor, Math.PI / 12);
95
+ expect(snapped[0]).toBeCloseTo(1, 10); // projection of (1, 0.05) onto 0° ray
96
+ expect(snapped[1]).toBeCloseTo(0, 10);
97
+ });
98
+ test('returns `from` for a zero-length segment', () => {
99
+ expect(snapPointAlongAngleRay([2, 3], [2, 3], Math.PI / 12, 0.25)).toEqual([2, 3]);
100
+ });
101
+ test('is idempotent on its own output', () => {
102
+ const from = [0.5, -1];
103
+ const cursor = [3.2, 0.4];
104
+ const once = snapPointAlongAngleRay(from, cursor, Math.PI / 12, 0.5);
105
+ const twice = snapPointAlongAngleRay(from, once, Math.PI / 12, 0.5);
106
+ expect(twice[0]).toBeCloseTo(once[0], 10);
107
+ expect(twice[1]).toBeCloseTo(once[1], 10);
108
+ });
109
+ });
110
+ describe('snapAngleToList', () => {
111
+ test('snaps to the nearest entry within tolerance', () => {
112
+ const targets = [0, Math.PI / 2, Math.PI, (3 * Math.PI) / 2];
113
+ expect(snapAngleToList(0.05, targets, Math.PI / 36)).toBe(0);
114
+ expect(snapAngleToList(Math.PI / 2 + 0.02, targets, Math.PI / 36)).toBe(Math.PI / 2);
115
+ });
116
+ test('returns original angle when no target is within tolerance', () => {
117
+ const targets = [0, Math.PI / 2];
118
+ expect(snapAngleToList(0.5, targets, Math.PI / 36)).toBe(0.5);
119
+ });
120
+ test('handles wrap-around near ±π', () => {
121
+ const targets = [Math.PI];
122
+ expect(snapAngleToList(-Math.PI + 0.01, targets, Math.PI / 36)).toBe(Math.PI);
123
+ });
124
+ });
125
+ describe('snapServices facade', () => {
126
+ test('grid.snap matches snapPointToGrid', () => {
127
+ expect(snapServices.grid.snap([0.3, 0.6], 0.25)).toEqual(snapPointToGrid([0.3, 0.6], 0.25));
128
+ });
129
+ test('grid.snapScalar matches snapScalar', () => {
130
+ expect(snapServices.grid.snapScalar(0.3, 0.25)).toBe(snapScalar(0.3, 0.25));
131
+ });
132
+ test('angle.snapTo matches snapPointToAngle', () => {
133
+ const from = [0, 0];
134
+ const cursor = [1, 0.9];
135
+ expect(snapServices.angle.snapTo(from, cursor, Math.PI / 4)).toEqual(snapPointToAngle(from, cursor, Math.PI / 4));
136
+ });
137
+ });
@@ -0,0 +1,30 @@
1
+ import type { AnyNode, AnyNodeId } from '../schema';
2
+ export type SystemSummary = {
3
+ /** Every node in this connected component. */
4
+ nodeIds: AnyNodeId[];
5
+ /** Distribution loops present, e.g. ['supply'], ['supply','return']. */
6
+ systems: string[];
7
+ /** Duct / lineset run statistics. */
8
+ runCount: number;
9
+ runLengthM: number;
10
+ fittingCount: number;
11
+ terminalCount: number;
12
+ equipmentCount: number;
13
+ /** False = orphaned subtree: air goes nowhere (no furnace / air
14
+ * handler / condenser anywhere in the component). */
15
+ connectedToEquipment: boolean;
16
+ };
17
+ /**
18
+ * Group every port-bearing node into connected components via coinciding
19
+ * ports. Nodes with ports but no joints form singleton components; nodes
20
+ * without `def.ports` don't participate at all.
21
+ */
22
+ export declare function buildPortComponents(nodes: Readonly<Record<AnyNodeId, AnyNode>>): AnyNodeId[][];
23
+ /**
24
+ * Summary of the system the given node belongs to, or null when the node
25
+ * has no ports (not a distribution kind). A node with ports but no
26
+ * joints yet still gets a (singleton) summary — `connectedToEquipment:
27
+ * false` is the interesting signal there.
28
+ */
29
+ export declare function summarizeSystemFor(nodeId: AnyNodeId, nodes: Readonly<Record<AnyNodeId, AnyNode>>): SystemSummary | null;
30
+ //# sourceMappingURL=system-graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-graph.d.ts","sourceRoot":"","sources":["../../src/services/system-graph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAoBnD,MAAM,MAAM,aAAa,GAAG;IAC1B,8CAA8C;IAC9C,OAAO,EAAE,SAAS,EAAE,CAAA;IACpB,wEAAwE;IACxE,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,CAAA;IACtB;0DACsD;IACtD,oBAAoB,EAAE,OAAO,CAAA;CAC9B,CAAA;AA2DD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,EAAE,EAAE,CA4B9F;AAkDD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAC1C,aAAa,GAAG,IAAI,CAStB"}