@pascal-app/core 0.9.1 → 1.0.0-beta.1

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 (378) hide show
  1. package/README.md +17 -10
  2. package/dist/events/bus.d.ts +49 -2
  3. package/dist/events/bus.d.ts.map +1 -1
  4. package/dist/hooks/scene-registry/item-clip-registry.d.ts +18 -0
  5. package/dist/hooks/scene-registry/item-clip-registry.d.ts.map +1 -0
  6. package/dist/hooks/scene-registry/item-clip-registry.js +10 -0
  7. package/dist/hooks/scene-registry/scene-registry.d.ts +8 -1
  8. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  9. package/dist/hooks/scene-registry/scene-registry.js +27 -1
  10. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +17 -1
  11. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -1
  12. package/dist/hooks/spatial-grid/floor-placed-elevation.js +61 -8
  13. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +135 -28
  14. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  15. package/dist/hooks/spatial-grid/spatial-grid-manager.js +427 -271
  16. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +46 -1
  17. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  18. package/dist/hooks/spatial-grid/spatial-grid-sync.js +208 -9
  19. package/dist/hooks/spatial-grid/support-host-id.d.ts +3 -0
  20. package/dist/hooks/spatial-grid/support-host-id.d.ts.map +1 -0
  21. package/dist/hooks/spatial-grid/support-host-id.js +2 -0
  22. package/dist/hooks/spatial-grid/support-host-patch.d.ts +51 -0
  23. package/dist/hooks/spatial-grid/support-host-patch.d.ts.map +1 -0
  24. package/dist/hooks/spatial-grid/support-host-patch.js +164 -0
  25. package/dist/index.d.ts +33 -11
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +32 -10
  28. package/dist/lib/level-name.d.ts +4 -0
  29. package/dist/lib/level-name.d.ts.map +1 -0
  30. package/dist/lib/level-name.js +10 -0
  31. package/dist/lib/measurement-geometry.d.ts +23 -0
  32. package/dist/lib/measurement-geometry.d.ts.map +1 -0
  33. package/dist/lib/measurement-geometry.js +249 -0
  34. package/dist/lib/polygon-geometry.d.ts +0 -1
  35. package/dist/lib/polygon-geometry.d.ts.map +1 -1
  36. package/dist/lib/polygon-geometry.js +0 -18
  37. package/dist/lib/selection-proxy.d.ts +4 -0
  38. package/dist/lib/selection-proxy.d.ts.map +1 -0
  39. package/dist/lib/selection-proxy.js +12 -0
  40. package/dist/lib/slab-polygon.d.ts +31 -2
  41. package/dist/lib/slab-polygon.d.ts.map +1 -1
  42. package/dist/lib/slab-polygon.js +557 -31
  43. package/dist/lib/slots.d.ts +14 -0
  44. package/dist/lib/slots.d.ts.map +1 -0
  45. package/dist/lib/slots.js +26 -0
  46. package/dist/lib/space-detection.d.ts +40 -6
  47. package/dist/lib/space-detection.d.ts.map +1 -1
  48. package/dist/lib/space-detection.js +574 -192
  49. package/dist/lib/terrain-brush.d.ts +163 -0
  50. package/dist/lib/terrain-brush.d.ts.map +1 -0
  51. package/dist/lib/terrain-brush.js +359 -0
  52. package/dist/lib/terrain-codec.d.ts +51 -0
  53. package/dist/lib/terrain-codec.d.ts.map +1 -0
  54. package/dist/lib/terrain-codec.js +167 -0
  55. package/dist/lib/terrain-field.d.ts +150 -0
  56. package/dist/lib/terrain-field.d.ts.map +1 -0
  57. package/dist/lib/terrain-field.js +248 -0
  58. package/dist/lib/terrain-raycast.d.ts +44 -0
  59. package/dist/lib/terrain-raycast.d.ts.map +1 -0
  60. package/dist/lib/terrain-raycast.js +188 -0
  61. package/dist/lib/terrain-source.d.ts +60 -0
  62. package/dist/lib/terrain-source.d.ts.map +1 -0
  63. package/dist/lib/terrain-source.js +90 -0
  64. package/dist/lib/terrain-support.d.ts +60 -0
  65. package/dist/lib/terrain-support.d.ts.map +1 -0
  66. package/dist/lib/terrain-support.js +150 -0
  67. package/dist/lib/wall-distance.d.ts +60 -0
  68. package/dist/lib/wall-distance.d.ts.map +1 -0
  69. package/dist/lib/wall-distance.js +85 -0
  70. package/dist/lib/zone-quantities.d.ts +21 -0
  71. package/dist/lib/zone-quantities.d.ts.map +1 -0
  72. package/dist/lib/zone-quantities.js +428 -0
  73. package/dist/material-library.d.ts +28 -1
  74. package/dist/material-library.d.ts.map +1 -1
  75. package/dist/material-library.js +2233 -441
  76. package/dist/registry/handles.d.ts +91 -3
  77. package/dist/registry/handles.d.ts.map +1 -1
  78. package/dist/registry/index.d.ts +3 -3
  79. package/dist/registry/index.d.ts.map +1 -1
  80. package/dist/registry/index.js +1 -1
  81. package/dist/registry/registry.d.ts +46 -3
  82. package/dist/registry/registry.d.ts.map +1 -1
  83. package/dist/registry/registry.js +82 -1
  84. package/dist/registry/subtree.d.ts.map +1 -1
  85. package/dist/registry/subtree.js +8 -1
  86. package/dist/registry/types.d.ts +781 -39
  87. package/dist/registry/types.d.ts.map +1 -1
  88. package/dist/schema/index.d.ts +27 -8
  89. package/dist/schema/index.d.ts.map +1 -1
  90. package/dist/schema/index.js +23 -6
  91. package/dist/schema/material.d.ts +3 -2
  92. package/dist/schema/material.d.ts.map +1 -1
  93. package/dist/schema/material.js +7 -5
  94. package/dist/schema/nodes/box-vent.d.ts +2 -2
  95. package/dist/schema/nodes/cabinet.d.ts +536 -0
  96. package/dist/schema/nodes/cabinet.d.ts.map +1 -0
  97. package/dist/schema/nodes/cabinet.js +139 -0
  98. package/dist/schema/nodes/ceiling.d.ts +4 -3
  99. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  100. package/dist/schema/nodes/ceiling.js +11 -1
  101. package/dist/schema/nodes/chimney.d.ts +4 -4
  102. package/dist/schema/nodes/column.d.ts +4 -2
  103. package/dist/schema/nodes/column.d.ts.map +1 -1
  104. package/dist/schema/nodes/column.js +5 -0
  105. package/dist/schema/nodes/construction-dimension.d.ts +195 -0
  106. package/dist/schema/nodes/construction-dimension.d.ts.map +1 -0
  107. package/dist/schema/nodes/construction-dimension.js +151 -0
  108. package/dist/schema/nodes/cupola.d.ts +2 -2
  109. package/dist/schema/nodes/door.d.ts +42 -5
  110. package/dist/schema/nodes/door.d.ts.map +1 -1
  111. package/dist/schema/nodes/door.js +32 -0
  112. package/dist/schema/nodes/dormer.d.ts +8 -8
  113. package/dist/schema/nodes/dormer.js +1 -1
  114. package/dist/schema/nodes/downspout.d.ts +2 -2
  115. package/dist/schema/nodes/duct-fitting.d.ts +91 -0
  116. package/dist/schema/nodes/duct-fitting.d.ts.map +1 -0
  117. package/dist/schema/nodes/duct-fitting.js +92 -0
  118. package/dist/schema/nodes/duct-segment.d.ts +61 -0
  119. package/dist/schema/nodes/duct-segment.d.ts.map +1 -0
  120. package/dist/schema/nodes/duct-segment.js +73 -0
  121. package/dist/schema/nodes/duct-terminal.d.ts +58 -0
  122. package/dist/schema/nodes/duct-terminal.d.ts.map +1 -0
  123. package/dist/schema/nodes/duct-terminal.js +53 -0
  124. package/dist/schema/nodes/elevator.d.ts +3 -2
  125. package/dist/schema/nodes/elevator.d.ts.map +1 -1
  126. package/dist/schema/nodes/elevator.js +3 -0
  127. package/dist/schema/nodes/eyebrow-vent.d.ts +2 -2
  128. package/dist/schema/nodes/fence.d.ts +20 -2
  129. package/dist/schema/nodes/fence.d.ts.map +1 -1
  130. package/dist/schema/nodes/fence.js +33 -2
  131. package/dist/schema/nodes/gutter.d.ts +2 -2
  132. package/dist/schema/nodes/hvac-equipment.d.ts +64 -0
  133. package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -0
  134. package/dist/schema/nodes/hvac-equipment.js +57 -0
  135. package/dist/schema/nodes/item.d.ts +9 -0
  136. package/dist/schema/nodes/item.d.ts.map +1 -1
  137. package/dist/schema/nodes/item.js +24 -0
  138. package/dist/schema/nodes/level.d.ts +2 -1
  139. package/dist/schema/nodes/level.d.ts.map +1 -1
  140. package/dist/schema/nodes/level.js +12 -31
  141. package/dist/schema/nodes/lineset.d.ts +42 -0
  142. package/dist/schema/nodes/lineset.d.ts.map +1 -0
  143. package/dist/schema/nodes/lineset.js +38 -0
  144. package/dist/schema/nodes/liquid-line.d.ts +34 -0
  145. package/dist/schema/nodes/liquid-line.d.ts.map +1 -0
  146. package/dist/schema/nodes/liquid-line.js +24 -0
  147. package/dist/schema/nodes/measurement.d.ts +306 -0
  148. package/dist/schema/nodes/measurement.d.ts.map +1 -0
  149. package/dist/schema/nodes/measurement.js +87 -0
  150. package/dist/schema/nodes/pipe-fitting.d.ts +56 -0
  151. package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -0
  152. package/dist/schema/nodes/pipe-fitting.js +45 -0
  153. package/dist/schema/nodes/pipe-segment.d.ts +48 -0
  154. package/dist/schema/nodes/pipe-segment.d.ts.map +1 -0
  155. package/dist/schema/nodes/pipe-segment.js +36 -0
  156. package/dist/schema/nodes/pipe-trap.d.ts +43 -0
  157. package/dist/schema/nodes/pipe-trap.d.ts.map +1 -0
  158. package/dist/schema/nodes/pipe-trap.js +36 -0
  159. package/dist/schema/nodes/ridge-vent.d.ts +18 -3
  160. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
  161. package/dist/schema/nodes/ridge-vent.js +221 -3
  162. package/dist/schema/nodes/roof-segment-shape.d.ts +90 -0
  163. package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -0
  164. package/dist/schema/nodes/roof-segment-shape.js +318 -0
  165. package/dist/schema/nodes/roof-segment-walls.d.ts +101 -0
  166. package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -0
  167. package/dist/schema/nodes/roof-segment-walls.js +330 -0
  168. package/dist/schema/nodes/roof-segment.d.ts +84 -8
  169. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  170. package/dist/schema/nodes/roof-segment.js +290 -5
  171. package/dist/schema/nodes/roof.d.ts +8 -8
  172. package/dist/schema/nodes/shelf.d.ts +4 -2
  173. package/dist/schema/nodes/shelf.d.ts.map +1 -1
  174. package/dist/schema/nodes/shelf.js +8 -0
  175. package/dist/schema/nodes/site.d.ts +9 -0
  176. package/dist/schema/nodes/site.d.ts.map +1 -1
  177. package/dist/schema/nodes/site.js +8 -6
  178. package/dist/schema/nodes/skylight.d.ts +10 -10
  179. package/dist/schema/nodes/slab.d.ts +8 -2
  180. package/dist/schema/nodes/slab.d.ts.map +1 -1
  181. package/dist/schema/nodes/slab.js +18 -2
  182. package/dist/schema/nodes/solar-panel.d.ts +4 -4
  183. package/dist/schema/nodes/spawn.d.ts +1 -0
  184. package/dist/schema/nodes/spawn.d.ts.map +1 -1
  185. package/dist/schema/nodes/spawn.js +2 -0
  186. package/dist/schema/nodes/stair-segment.d.ts +4 -4
  187. package/dist/schema/nodes/stair.d.ts +16 -13
  188. package/dist/schema/nodes/stair.d.ts.map +1 -1
  189. package/dist/schema/nodes/stair.js +11 -1
  190. package/dist/schema/nodes/structural-grid.d.ts +27 -0
  191. package/dist/schema/nodes/structural-grid.d.ts.map +1 -0
  192. package/dist/schema/nodes/structural-grid.js +17 -0
  193. package/dist/schema/nodes/turbine-vent.d.ts +2 -2
  194. package/dist/schema/nodes/wall.d.ts +248 -6
  195. package/dist/schema/nodes/wall.d.ts.map +1 -1
  196. package/dist/schema/nodes/wall.js +266 -0
  197. package/dist/schema/nodes/window.d.ts +40 -3
  198. package/dist/schema/nodes/window.d.ts.map +1 -1
  199. package/dist/schema/nodes/window.js +30 -0
  200. package/dist/schema/nodes/zone.d.ts +22 -0
  201. package/dist/schema/nodes/zone.d.ts.map +1 -1
  202. package/dist/schema/nodes/zone.js +21 -0
  203. package/dist/schema/scene-material.d.ts +41 -0
  204. package/dist/schema/scene-material.d.ts.map +1 -0
  205. package/dist/schema/scene-material.js +9 -0
  206. package/dist/schema/terrain.d.ts +28 -0
  207. package/dist/schema/terrain.d.ts.map +1 -0
  208. package/dist/schema/terrain.js +30 -0
  209. package/dist/schema/types.d.ts +1300 -97
  210. package/dist/schema/types.d.ts.map +1 -1
  211. package/dist/schema/types.js +27 -0
  212. package/dist/services/__fixtures__/wall-plane-top-boundary-repro.d.ts +80 -0
  213. package/dist/services/__fixtures__/wall-plane-top-boundary-repro.d.ts.map +1 -0
  214. package/dist/services/__fixtures__/wall-plane-top-boundary-repro.js +89 -0
  215. package/dist/services/alignment-anchors.d.ts.map +1 -1
  216. package/dist/services/alignment-anchors.js +30 -1
  217. package/dist/services/alignment.d.ts +5 -0
  218. package/dist/services/alignment.d.ts.map +1 -1
  219. package/dist/services/alignment.js +2 -0
  220. package/dist/services/hosting.d.ts +8 -0
  221. package/dist/services/hosting.d.ts.map +1 -1
  222. package/dist/services/hosting.js +13 -4
  223. package/dist/services/index.d.ts +9 -2
  224. package/dist/services/index.d.ts.map +1 -1
  225. package/dist/services/index.js +9 -2
  226. package/dist/services/level-height.d.ts +27 -0
  227. package/dist/services/level-height.d.ts.map +1 -0
  228. package/dist/services/level-height.js +94 -0
  229. package/dist/services/opening-guides.d.ts +141 -0
  230. package/dist/services/opening-guides.d.ts.map +1 -0
  231. package/dist/services/opening-guides.js +267 -0
  232. package/dist/services/port-connectivity.d.ts +115 -0
  233. package/dist/services/port-connectivity.d.ts.map +1 -0
  234. package/dist/services/port-connectivity.js +300 -0
  235. package/dist/services/riser-diagram.d.ts +40 -0
  236. package/dist/services/riser-diagram.d.ts.map +1 -0
  237. package/dist/services/riser-diagram.js +110 -0
  238. package/dist/services/snap.d.ts +9 -1
  239. package/dist/services/snap.d.ts.map +1 -1
  240. package/dist/services/snap.js +21 -1
  241. package/dist/services/storey.d.ts +103 -0
  242. package/dist/services/storey.d.ts.map +1 -0
  243. package/dist/services/storey.js +296 -0
  244. package/dist/services/system-graph.d.ts +30 -0
  245. package/dist/services/system-graph.d.ts.map +1 -0
  246. package/dist/services/system-graph.js +164 -0
  247. package/dist/services/validate-dwv.d.ts +32 -0
  248. package/dist/services/validate-dwv.d.ts.map +1 -0
  249. package/dist/services/validate-dwv.js +118 -0
  250. package/dist/store/actions/node-actions.d.ts.map +1 -1
  251. package/dist/store/actions/node-actions.js +141 -4
  252. package/dist/store/history-control.d.ts +31 -0
  253. package/dist/store/history-control.d.ts.map +1 -1
  254. package/dist/store/history-control.js +130 -0
  255. package/dist/store/use-live-terrain.d.ts +65 -0
  256. package/dist/store/use-live-terrain.d.ts.map +1 -0
  257. package/dist/store/use-live-terrain.js +31 -0
  258. package/dist/store/use-live-transforms.d.ts +8 -0
  259. package/dist/store/use-live-transforms.d.ts.map +1 -1
  260. package/dist/store/use-scene.d.ts +47 -3
  261. package/dist/store/use-scene.d.ts.map +1 -1
  262. package/dist/store/use-scene.js +992 -55
  263. package/dist/systems/elevator/elevator-opening-system.d.ts +1 -0
  264. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -1
  265. package/dist/systems/elevator/elevator-opening-system.js +27 -21
  266. package/dist/systems/elevator/elevator-service.d.ts +0 -2
  267. package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
  268. package/dist/systems/elevator/elevator-service.js +2 -24
  269. package/dist/systems/fence/fence-centerline.d.ts +12 -0
  270. package/dist/systems/fence/fence-centerline.d.ts.map +1 -0
  271. package/dist/systems/fence/fence-centerline.js +37 -0
  272. package/dist/systems/fence/fence-spline.d.ts +39 -0
  273. package/dist/systems/fence/fence-spline.d.ts.map +1 -0
  274. package/dist/systems/fence/fence-spline.js +196 -0
  275. package/dist/systems/slab/slab-placement.d.ts +11 -0
  276. package/dist/systems/slab/slab-placement.d.ts.map +1 -0
  277. package/dist/systems/slab/slab-placement.js +14 -0
  278. package/dist/systems/slab/slab-support.d.ts +137 -0
  279. package/dist/systems/slab/slab-support.d.ts.map +1 -0
  280. package/dist/systems/slab/slab-support.js +557 -0
  281. package/dist/systems/stair/stair-footprint.d.ts.map +1 -1
  282. package/dist/systems/stair/stair-footprint.js +21 -11
  283. package/dist/systems/stair/stair-opening-preview.d.ts +1300 -505
  284. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
  285. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  286. package/dist/systems/stair/stair-opening-sync.js +19 -33
  287. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
  288. package/dist/systems/stair/stair-opening-system.js +32 -4
  289. package/dist/systems/stair/stair-rise.d.ts +22 -0
  290. package/dist/systems/stair/stair-rise.d.ts.map +1 -0
  291. package/dist/systems/stair/stair-rise.js +79 -0
  292. package/dist/systems/wall/wall-curve.d.ts +10 -0
  293. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  294. package/dist/systems/wall/wall-curve.js +1 -1
  295. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  296. package/dist/systems/wall/wall-mitering.js +44 -8
  297. package/dist/systems/wall/wall-top.d.ts +40 -0
  298. package/dist/systems/wall/wall-top.d.ts.map +1 -0
  299. package/dist/systems/wall/wall-top.js +46 -0
  300. package/dist/utils/clone-scene-graph.d.ts +1 -0
  301. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  302. package/dist/utils/clone-scene-graph.js +56 -5
  303. package/dist/utils/heal-scene-graph.d.ts +18 -0
  304. package/dist/utils/heal-scene-graph.d.ts.map +1 -0
  305. package/dist/utils/heal-scene-graph.js +106 -0
  306. package/dist/validation/validate-build-json.d.ts +3 -0
  307. package/dist/validation/validate-build-json.d.ts.map +1 -1
  308. package/dist/validation/validate-build-json.js +90 -5
  309. package/package.json +4 -4
  310. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +0 -2
  311. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +0 -1
  312. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +0 -273
  313. package/dist/lib/space-detection-pause.test.d.ts +0 -2
  314. package/dist/lib/space-detection-pause.test.d.ts.map +0 -1
  315. package/dist/lib/space-detection-pause.test.js +0 -42
  316. package/dist/lib/space-detection.test.d.ts +0 -2
  317. package/dist/lib/space-detection.test.d.ts.map +0 -1
  318. package/dist/lib/space-detection.test.js +0 -77
  319. package/dist/registry/registry.test.d.ts +0 -2
  320. package/dist/registry/registry.test.d.ts.map +0 -1
  321. package/dist/registry/registry.test.js +0 -183
  322. package/dist/registry/relations-resolver.test.d.ts +0 -2
  323. package/dist/registry/relations-resolver.test.d.ts.map +0 -1
  324. package/dist/registry/relations-resolver.test.js +0 -183
  325. package/dist/registry/scene-api.test.d.ts +0 -2
  326. package/dist/registry/scene-api.test.d.ts.map +0 -1
  327. package/dist/registry/scene-api.test.js +0 -183
  328. package/dist/registry/subtree.test.d.ts +0 -2
  329. package/dist/registry/subtree.test.d.ts.map +0 -1
  330. package/dist/registry/subtree.test.js +0 -133
  331. package/dist/schema/asset-url.test.d.ts +0 -2
  332. package/dist/schema/asset-url.test.d.ts.map +0 -1
  333. package/dist/schema/asset-url.test.js +0 -134
  334. package/dist/services/alignment-anchors.test.d.ts +0 -2
  335. package/dist/services/alignment-anchors.test.d.ts.map +0 -1
  336. package/dist/services/alignment-anchors.test.js +0 -383
  337. package/dist/services/alignment.test.d.ts +0 -2
  338. package/dist/services/alignment.test.d.ts.map +0 -1
  339. package/dist/services/alignment.test.js +0 -92
  340. package/dist/services/drag-session.test.d.ts +0 -2
  341. package/dist/services/drag-session.test.d.ts.map +0 -1
  342. package/dist/services/drag-session.test.js +0 -219
  343. package/dist/services/hosting.test.d.ts +0 -2
  344. package/dist/services/hosting.test.d.ts.map +0 -1
  345. package/dist/services/hosting.test.js +0 -199
  346. package/dist/services/movement.test.d.ts +0 -2
  347. package/dist/services/movement.test.d.ts.map +0 -1
  348. package/dist/services/movement.test.js +0 -133
  349. package/dist/services/single-undo-dance.test.d.ts +0 -2
  350. package/dist/services/single-undo-dance.test.d.ts.map +0 -1
  351. package/dist/services/single-undo-dance.test.js +0 -210
  352. package/dist/services/snap.test.d.ts +0 -2
  353. package/dist/services/snap.test.d.ts.map +0 -1
  354. package/dist/services/snap.test.js +0 -95
  355. package/dist/store/actions/node-mutation-sanitize.test.d.ts +0 -2
  356. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +0 -1
  357. package/dist/store/actions/node-mutation-sanitize.test.js +0 -147
  358. package/dist/store/actions/reparent.test.d.ts +0 -2
  359. package/dist/store/actions/reparent.test.d.ts.map +0 -1
  360. package/dist/store/actions/reparent.test.js +0 -212
  361. package/dist/store/use-scene-elevator-migration.test.d.ts +0 -2
  362. package/dist/store/use-scene-elevator-migration.test.d.ts.map +0 -1
  363. package/dist/store/use-scene-elevator-migration.test.js +0 -109
  364. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +0 -2
  365. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +0 -1
  366. package/dist/systems/elevator/elevator-opening-sync.test.js +0 -146
  367. package/dist/systems/elevator/elevator-runtime.test.d.ts +0 -2
  368. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +0 -1
  369. package/dist/systems/elevator/elevator-runtime.test.js +0 -64
  370. package/dist/systems/stair/stair-opening-preview.test.d.ts +0 -2
  371. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +0 -1
  372. package/dist/systems/stair/stair-opening-preview.test.js +0 -80
  373. package/dist/systems/stair/stair-opening-sync.test.d.ts +0 -2
  374. package/dist/systems/stair/stair-opening-sync.test.d.ts.map +0 -1
  375. package/dist/systems/stair/stair-opening-sync.test.js +0 -446
  376. package/dist/utils/clone-scene-graph.test.d.ts +0 -2
  377. package/dist/utils/clone-scene-graph.test.d.ts.map +0 -1
  378. package/dist/utils/clone-scene-graph.test.js +0 -59
@@ -0,0 +1,300 @@
1
+ import { nodeRegistry } from '../registry';
2
+ /** Distance (meters) under which two ports count as the same joint. Joints
3
+ * formed by placement snapping coincide to sub-millimeter; 5 cm leaves
4
+ * generous slack for grid-snapped hand placement without false matches. */
5
+ const COINCIDENT_EPS_M = 0.05;
6
+ /** Below this (meters) a propagated delta is treated as zero — stops the
7
+ * walk from chasing sub-millimeter perpendicular residue. */
8
+ const DELTA_EPS_M = 1e-4;
9
+ const PROPAGATION_EPS_M = 1e-9;
10
+ function portsOf(node) {
11
+ return nodeRegistry.get(node.type)?.ports?.(node);
12
+ }
13
+ /** A node's distribution role from the registry (run / fitting / …). */
14
+ function roleOf(node) {
15
+ return nodeRegistry.get(node.type)?.distributionRole;
16
+ }
17
+ function distSq(a, b) {
18
+ const dx = a[0] - b[0];
19
+ const dy = a[1] - b[1];
20
+ const dz = a[2] - b[2];
21
+ return dx * dx + dy * dy + dz * dz;
22
+ }
23
+ /** Two ports mate when they coincide AND don't cross incompatible systems
24
+ * (a supply duct and a waste pipe that merely touch must not fuse). */
25
+ function portsMate(a, b, epsSq) {
26
+ if (distSq(a.position, b.position) > epsSq)
27
+ return false;
28
+ if (a.system && b.system && a.system !== b.system)
29
+ return false;
30
+ return true;
31
+ }
32
+ /**
33
+ * Snapshot the joint graph reachable from `movedNode`'s ports, taken at the
34
+ * start of a move/resize. Call once before the drag; feed the result to
35
+ * `resolveConnectivityUpdates` on every frame.
36
+ *
37
+ * Only `run`-role partners (segments) and `fitting`-role partners are walked —
38
+ * terminals and equipment usually mount to a surface and shouldn't be yanked
39
+ * off it when an adjacent fitting nudges. Fittings that declare
40
+ * `portConnectivityFollow: false` are anchored fixtures (e.g. pipe-trap) and
41
+ * are skipped, so a connected run stretches against them instead.
42
+ */
43
+ export function analyzePortConnectivity(movedNode, nodes) {
44
+ const epsSq = COINCIDENT_EPS_M * COINCIDENT_EPS_M;
45
+ const movedPorts = portsOf(movedNode) ?? [];
46
+ const startMovedPorts = {};
47
+ for (const p of movedPorts)
48
+ startMovedPorts[p.id] = p.position;
49
+ // Candidate partners: every run + every following fitting in the scene.
50
+ const candidates = [];
51
+ for (const other of Object.values(nodes)) {
52
+ if (!other || other.id === movedNode.id)
53
+ continue;
54
+ const role = roleOf(other);
55
+ if (role !== 'run' && role !== 'fitting')
56
+ continue;
57
+ if (role === 'fitting' && nodeRegistry.get(other.type)?.portConnectivityFollow === false) {
58
+ continue;
59
+ }
60
+ const ports = portsOf(other);
61
+ if (!ports)
62
+ continue;
63
+ const startPath = role === 'run'
64
+ ? other.path?.map((p) => [...p])
65
+ : undefined;
66
+ if (role === 'run' && (!startPath || startPath.length < 2))
67
+ continue;
68
+ const startPosition = role === 'fitting'
69
+ ? (() => {
70
+ const pos = other.position;
71
+ return pos ? [pos[0], pos[1], pos[2]] : undefined;
72
+ })()
73
+ : undefined;
74
+ if (role === 'fitting' && !startPosition)
75
+ continue;
76
+ candidates.push({ id: other.id, role, ports, startPath, startPosition });
77
+ }
78
+ // Walk outward from the moved node, collecting every node reachable through
79
+ // coincident ports. The adjacency records each port's mates so the resolver
80
+ // can replay the same edges with live deltas.
81
+ const adjacency = {};
82
+ const addEdge = (nodeId, portId, mate) => {
83
+ const byPort = adjacency[nodeId] ?? {};
84
+ adjacency[nodeId] = byPort;
85
+ const mates = byPort[portId] ?? [];
86
+ byPort[portId] = mates;
87
+ mates.push(mate);
88
+ };
89
+ const graph = {};
90
+ const visited = new Set([movedNode.id]);
91
+ // Seed: the moved node's own ports.
92
+ const queue = [{ id: movedNode.id, ports: movedPorts }];
93
+ while (queue.length > 0) {
94
+ const { id, ports } = queue.shift();
95
+ for (const port of ports) {
96
+ for (const cand of candidates) {
97
+ if (cand.id === id)
98
+ continue;
99
+ for (const cp of cand.ports) {
100
+ if (!portsMate(port, cp, epsSq))
101
+ continue;
102
+ addEdge(id, port.id, { nodeId: cand.id, portId: cp.id });
103
+ addEdge(cand.id, cp.id, { nodeId: id, portId: port.id });
104
+ if (!visited.has(cand.id)) {
105
+ visited.add(cand.id);
106
+ graph[cand.id] = cand;
107
+ queue.push({ id: cand.id, ports: cand.ports });
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ const connections = Object.values(graph).map((g) => g.role === 'fitting'
114
+ ? { kind: 'rigid-node', nodeId: g.id, startPosition: g.startPosition }
115
+ : { kind: 'run', nodeId: g.id, startPath: g.startPath });
116
+ return {
117
+ movedNodeId: movedNode.id,
118
+ startMovedPorts,
119
+ graph,
120
+ adjacency,
121
+ connections,
122
+ };
123
+ }
124
+ function add(a, b) {
125
+ return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
126
+ }
127
+ function sub(a, b) {
128
+ return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
129
+ }
130
+ function lenSq(v) {
131
+ return v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
132
+ }
133
+ /** Split `delta` into the component along unit `axis` and the remainder. */
134
+ function decompose(delta, axis) {
135
+ const dot = delta[0] * axis[0] + delta[1] * axis[1] + delta[2] * axis[2];
136
+ const parallel = [axis[0] * dot, axis[1] * dot, axis[2] * dot];
137
+ return { parallel, perp: sub(delta, parallel) };
138
+ }
139
+ function scale(v, scalar) {
140
+ return [v[0] * scalar, v[1] * scalar, v[2] * scalar];
141
+ }
142
+ function average(deltas) {
143
+ const sum = deltas.reduce((acc, delta) => add(acc, delta), [0, 0, 0]);
144
+ return scale(sum, 1 / deltas.length);
145
+ }
146
+ function nearlyEqual(a, b) {
147
+ return lenSq(sub(a, b)) <= DELTA_EPS_M * DELTA_EPS_M;
148
+ }
149
+ function propagationEqual(a, b) {
150
+ return lenSq(sub(a, b)) <= PROPAGATION_EPS_M * PROPAGATION_EPS_M;
151
+ }
152
+ function effectivePortDeltas(constraints) {
153
+ return Object.fromEntries(Object.entries(constraints).map(([portId, bySource]) => [
154
+ portId,
155
+ average(Object.values(bySource)),
156
+ ]));
157
+ }
158
+ /** Unit direction of the run's segment adjacent to its `start` / `end` tip. */
159
+ function endpointAxis(path, portId) {
160
+ const n = path.length;
161
+ const [a, b] = portId === 'start' ? [path[1], path[0]] : [path[n - 2], path[n - 1]];
162
+ const dir = sub(b, a);
163
+ const l2 = lenSq(dir);
164
+ if (l2 < 1e-12)
165
+ return [0, 0, 0];
166
+ const l = Math.sqrt(l2);
167
+ return [dir[0] / l, dir[1] / l, dir[2] / l];
168
+ }
169
+ function runPathFromSinglePortDelta(startPath, portId, delta) {
170
+ const nearIdx = portId === 'start' ? 0 : startPath.length - 1;
171
+ const axis = endpointAxis(startPath, portId);
172
+ const { parallel, perp } = decompose(delta, axis);
173
+ const path = startPath.map((p) => add(p, perp));
174
+ path[nearIdx] = add(path[nearIdx], parallel);
175
+ return path;
176
+ }
177
+ function runEndpointDeltas(startPath, path) {
178
+ return {
179
+ start: sub(path[0], startPath[0]),
180
+ end: sub(path[path.length - 1], startPath[startPath.length - 1]),
181
+ };
182
+ }
183
+ function runPathFromPortDeltas(startPath, portDeltas) {
184
+ const startDelta = portDeltas.start;
185
+ const endDelta = portDeltas.end;
186
+ if (startDelta && endDelta) {
187
+ if (startPath.length === 2) {
188
+ return [add(startPath[0], startDelta), add(startPath[1], endDelta)];
189
+ }
190
+ if (nearlyEqual(startDelta, endDelta)) {
191
+ return startPath.map((p) => add(p, startDelta));
192
+ }
193
+ const startParts = decompose(startDelta, endpointAxis(startPath, 'start'));
194
+ const endParts = decompose(endDelta, endpointAxis(startPath, 'end'));
195
+ const commonPerp = average([startParts.perp, endParts.perp]);
196
+ const path = startPath.map((p) => add(p, commonPerp));
197
+ path[0] = add(path[0], startParts.parallel);
198
+ path[path.length - 1] = add(path[path.length - 1], endParts.parallel);
199
+ return path;
200
+ }
201
+ return runPathFromSinglePortDelta(startPath, startDelta ? 'start' : 'end', (startDelta ?? endDelta));
202
+ }
203
+ /**
204
+ * Given the moved node in its live (in-drag) transform, produce the patches
205
+ * that keep every connected node attached. `previewNode` is the moved node
206
+ * with its current drag position/rotation applied so its ports recompute.
207
+ *
208
+ * Walks the snapshotted graph, propagating each port delta outward: fittings
209
+ * translate rigidly, runs stretch along their axis and translate across it
210
+ * (never skew when driven from one end), and effective port movement carries on
211
+ * to neighbouring joints. Port-level output guards bound cycles while still
212
+ * allowing a looped/shared run to accept constraints at both endpoints.
213
+ */
214
+ export function resolveConnectivityUpdates(connectivity, previewNode) {
215
+ const { graph, adjacency, startMovedPorts, movedNodeId } = connectivity;
216
+ if (Object.keys(graph).length === 0)
217
+ return [];
218
+ const newPorts = portsOf(previewNode) ?? [];
219
+ const newMovedPos = {};
220
+ for (const p of newPorts)
221
+ newMovedPos[p.id] = p.position;
222
+ // Each queue item drives a node's port by a delta ("this collar / endpoint
223
+ // must move by this much").
224
+ const queue = [];
225
+ const results = {};
226
+ const constrainedPorts = {};
227
+ const propagatedPorts = {};
228
+ const enqueueMates = (nodeId, portId, delta) => {
229
+ const byPort = propagatedPorts[nodeId] ?? {};
230
+ propagatedPorts[nodeId] = byPort;
231
+ const previous = byPort[portId];
232
+ if (previous && propagationEqual(previous, delta))
233
+ return;
234
+ byPort[portId] = delta;
235
+ for (const mate of adjacency[nodeId]?.[portId] ?? []) {
236
+ if (mate.nodeId === movedNodeId)
237
+ continue;
238
+ queue.push({
239
+ nodeId: mate.nodeId,
240
+ portId: mate.portId,
241
+ delta,
242
+ sourceKey: `${nodeId}:${portId}`,
243
+ });
244
+ }
245
+ };
246
+ const acceptPortDelta = (nodeId, portId, sourceKey, delta) => {
247
+ const byPort = constrainedPorts[nodeId] ?? {};
248
+ constrainedPorts[nodeId] = byPort;
249
+ const bySource = byPort[portId] ?? {};
250
+ byPort[portId] = bySource;
251
+ const existing = bySource[sourceKey];
252
+ if (existing && propagationEqual(existing, delta)) {
253
+ return false;
254
+ }
255
+ bySource[sourceKey] = delta;
256
+ return true;
257
+ };
258
+ // Seed from the moved node's live port deltas.
259
+ for (const [portId, start] of Object.entries(startMovedPorts)) {
260
+ const now = newMovedPos[portId];
261
+ if (!now)
262
+ continue;
263
+ const delta = sub(now, start);
264
+ if (lenSq(delta) <= DELTA_EPS_M * DELTA_EPS_M)
265
+ continue;
266
+ enqueueMates(movedNodeId, portId, delta);
267
+ }
268
+ while (queue.length > 0) {
269
+ const { nodeId, portId, delta, sourceKey } = queue.shift();
270
+ const node = graph[nodeId];
271
+ if (!node)
272
+ continue;
273
+ if (!acceptPortDelta(nodeId, portId, sourceKey, delta))
274
+ continue;
275
+ const portDeltas = effectivePortDeltas(constrainedPorts[nodeId]);
276
+ if (node.role === 'fitting') {
277
+ const start = node.startPosition;
278
+ const effectiveDelta = average(Object.values(portDeltas));
279
+ results[nodeId] = {
280
+ id: nodeId,
281
+ data: { position: add(start, effectiveDelta) },
282
+ };
283
+ // Rigid: every collar carries the effective body translation onward.
284
+ for (const p of node.ports) {
285
+ enqueueMates(nodeId, p.id, effectiveDelta);
286
+ }
287
+ }
288
+ else {
289
+ const startPath = node.startPath;
290
+ const path = runPathFromPortDeltas(startPath, portDeltas);
291
+ results[nodeId] = { id: nodeId, data: { path } };
292
+ for (const [nextPortId, nextDelta] of Object.entries(runEndpointDeltas(startPath, path))) {
293
+ if (lenSq(nextDelta) <= DELTA_EPS_M * DELTA_EPS_M)
294
+ continue;
295
+ enqueueMates(nodeId, nextPortId, nextDelta);
296
+ }
297
+ }
298
+ }
299
+ return Object.values(results);
300
+ }
@@ -0,0 +1,40 @@
1
+ import type { AnyNode, AnyNodeId } from '../schema';
2
+ /** A 3D level-local point (meters) projected to 2D iso screen space.
3
+ * Screen Y grows DOWNWARD (SVG convention), so higher elevation → lower
4
+ * screen Y. */
5
+ export declare function projectIso(x: number, y: number, z: number): [number, number];
6
+ export type RiserLine = {
7
+ /** Projected endpoints in iso screen space. */
8
+ from: [number, number];
9
+ to: [number, number];
10
+ system: 'waste' | 'vent';
11
+ /** Nominal size in inches. */
12
+ diameter: number;
13
+ /** True for a (near-)vertical run — drawn solid/bold as a stack. */
14
+ vertical: boolean;
15
+ /** Source node, so the editor can link selection. */
16
+ nodeId: AnyNodeId;
17
+ };
18
+ export type RiserMarker = {
19
+ point: [number, number];
20
+ kind: 'trap' | 'vent-termination' | 'fitting';
21
+ label: string;
22
+ nodeId: AnyNodeId;
23
+ };
24
+ export type RiserDiagram = {
25
+ lines: RiserLine[];
26
+ markers: RiserMarker[];
27
+ /** Bounding box of all projected geometry, screen space. */
28
+ bounds: {
29
+ minX: number;
30
+ minY: number;
31
+ maxX: number;
32
+ maxY: number;
33
+ };
34
+ };
35
+ /**
36
+ * Build the riser diagram for the whole scene. Returns null when there's
37
+ * no DWV geometry to draw.
38
+ */
39
+ export declare function buildRiserDiagram(nodes: Readonly<Record<AnyNodeId, AnyNode>>): RiserDiagram | null;
40
+ //# sourceMappingURL=riser-diagram.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"riser-diagram.d.ts","sourceRoot":"","sources":["../../src/services/riser-diagram.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAiBnD;;gBAEgB;AAChB,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAI5E;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,+CAA+C;IAC/C,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACtB,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpB,MAAM,EAAE,OAAO,GAAG,MAAM,CAAA;IACxB,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,QAAQ,EAAE,OAAO,CAAA;IACjB,qDAAqD;IACrD,MAAM,EAAE,SAAS,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvB,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAAA;IAC7C,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,SAAS,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,OAAO,EAAE,WAAW,EAAE,CAAA;IACtB,4DAA4D;IAC5D,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACnE,CAAA;AAcD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAC1C,YAAY,GAAG,IAAI,CAuErB"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Riser diagram (plumbing isometric) — the conventional way DWV systems
3
+ * are drawn for permit: the drain/vent tree projected to a 30° iso so
4
+ * vertical stacks read as vertical and horizontal runs lean off at 30°,
5
+ * annotated with size + slope and vent terminations.
6
+ *
7
+ * This is a pure projector: it turns the scene's DWV nodes into 2D
8
+ * drawables (level-independent, no rendering). The editor draws the
9
+ * result as SVG. Air/refrigerant nodes are ignored — riser diagrams are
10
+ * a plumbing convention.
11
+ */
12
+ const COS30 = Math.cos(Math.PI / 6);
13
+ const SIN30 = Math.sin(Math.PI / 6);
14
+ /** A 3D level-local point (meters) projected to 2D iso screen space.
15
+ * Screen Y grows DOWNWARD (SVG convention), so higher elevation → lower
16
+ * screen Y. */
17
+ export function projectIso(x, y, z) {
18
+ const sx = (x - z) * COS30;
19
+ const sy = (x + z) * SIN30 - y;
20
+ return [sx, sy];
21
+ }
22
+ /** Elevation gain per horizontal meter under which a leg is "vertical". */
23
+ const VERTICAL_EPS = 4; // dy/dxz ratio: steeper than this reads as a stack
24
+ function legIsVertical(a, b) {
25
+ const horizontal = Math.hypot(b[0] - a[0], b[2] - a[2]);
26
+ const vertical = Math.abs(b[1] - a[1]);
27
+ if (horizontal < 1e-4)
28
+ return true;
29
+ return vertical / horizontal > VERTICAL_EPS;
30
+ }
31
+ /**
32
+ * Build the riser diagram for the whole scene. Returns null when there's
33
+ * no DWV geometry to draw.
34
+ */
35
+ export function buildRiserDiagram(nodes) {
36
+ const lines = [];
37
+ const markers = [];
38
+ let minX = Infinity;
39
+ let minY = Infinity;
40
+ let maxX = -Infinity;
41
+ let maxY = -Infinity;
42
+ const grow = (p) => {
43
+ if (p[0] < minX)
44
+ minX = p[0];
45
+ if (p[1] < minY)
46
+ minY = p[1];
47
+ if (p[0] > maxX)
48
+ maxX = p[0];
49
+ if (p[1] > maxY)
50
+ maxY = p[1];
51
+ };
52
+ for (const node of Object.values(nodes)) {
53
+ if (!node)
54
+ continue;
55
+ if (node.type === 'pipe-segment') {
56
+ const path = node.path;
57
+ for (let i = 0; i < path.length - 1; i++) {
58
+ const a = path[i];
59
+ const b = path[i + 1];
60
+ const from = projectIso(a[0], a[1], a[2]);
61
+ const to = projectIso(b[0], b[1], b[2]);
62
+ grow(from);
63
+ grow(to);
64
+ lines.push({
65
+ from,
66
+ to,
67
+ system: node.system,
68
+ diameter: node.diameter,
69
+ vertical: legIsVertical(a, b),
70
+ nodeId: node.id,
71
+ });
72
+ }
73
+ // Vent runs that end above everything are vent terminations
74
+ // (through-roof). Tag the highest endpoint of a vent run.
75
+ if (node.system === 'vent') {
76
+ const top = path.reduce((hi, p) => (p[1] > hi[1] ? p : hi), path[0]);
77
+ const pt = projectIso(top[0], top[1], top[2]);
78
+ markers.push({
79
+ point: pt,
80
+ kind: 'vent-termination',
81
+ label: `${node.diameter}" VTR`,
82
+ nodeId: node.id,
83
+ });
84
+ }
85
+ }
86
+ else if (node.type === 'pipe-trap') {
87
+ const pt = projectIso(node.position[0], node.position[1], node.position[2]);
88
+ grow(pt);
89
+ markers.push({
90
+ point: pt,
91
+ kind: 'trap',
92
+ label: `${node.diameter}" P-trap`,
93
+ nodeId: node.id,
94
+ });
95
+ }
96
+ else if (node.type === 'pipe-fitting') {
97
+ const pt = projectIso(node.position[0], node.position[1], node.position[2]);
98
+ grow(pt);
99
+ markers.push({
100
+ point: pt,
101
+ kind: 'fitting',
102
+ label: node.fittingType,
103
+ nodeId: node.id,
104
+ });
105
+ }
106
+ }
107
+ if (lines.length === 0 && markers.length === 0)
108
+ return null;
109
+ return { lines, markers, bounds: { minX, minY, maxX, maxY } };
110
+ }
@@ -12,7 +12,7 @@ export type Vec2 = readonly [number, number];
12
12
  export type Vec3 = readonly [number, number, number];
13
13
  /** Default planar grid spacing in meters. Matches the editor's wall tool. */
14
14
  export declare const DEFAULT_GRID_STEP = 0.25;
15
- /** Default angle-snap step — π/12 = 15°. Wall tools also use π/4 (45°). */
15
+ /** Default angle-snap step — π/12 = 15°. */
16
16
  export declare const DEFAULT_ANGLE_STEP: number;
17
17
  /** Snaps a single scalar to the nearest multiple of `step`. */
18
18
  export declare function snapScalar(value: number, step?: number): number;
@@ -45,6 +45,14 @@ export declare function snapWorldXZToBuildingLocal(worldX: number, worldZ: numbe
45
45
  * — keeps endpoints landing on grid intersections.
46
46
  */
47
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;
48
56
  /**
49
57
  * Snaps an angle (in radians) to the nearest entry in `snapAngles` (also in
50
58
  * radians). Returns the original angle if no entry is within `toleranceRad`.
@@ -1 +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,2EAA2E;AAC3E,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;;;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"}
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"}
@@ -10,7 +10,7 @@
10
10
  */
11
11
  /** Default planar grid spacing in meters. Matches the editor's wall tool. */
12
12
  export const DEFAULT_GRID_STEP = 0.25;
13
- /** Default angle-snap step — π/12 = 15°. Wall tools also use π/4 (45°). */
13
+ /** Default angle-snap step — π/12 = 15°. */
14
14
  export const DEFAULT_ANGLE_STEP = Math.PI / 12;
15
15
  // ─── Grid snap ────────────────────────────────────────────────────────
16
16
  /** Snaps a single scalar to the nearest multiple of `step`. */
@@ -88,6 +88,26 @@ export function snapPointToAngle(from, cursor, angleStep = DEFAULT_ANGLE_STEP, g
88
88
  ];
89
89
  return gridStep == null ? projected : snapPointToGrid(projected, gridStep);
90
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
+ }
91
111
  /**
92
112
  * Snaps an angle (in radians) to the nearest entry in `snapAngles` (also in
93
113
  * radians). Returns the original angle if no entry is within `toleranceRad`.
@@ -0,0 +1,103 @@
1
+ import type { LevelNode, WallNode } from '../schema';
2
+ import type { AnyNode, AnyNodeId } from '../schema/types';
3
+ /**
4
+ * Gap kept between a ceiling's stored height and its clamp bound (storey
5
+ * plane or covering-slab underside), so the ceiling surface never
6
+ * coincides with the solid above it.
7
+ */
8
+ export declare const CEILING_CLAMP_MARGIN = 0.01;
9
+ /**
10
+ * Stored storey height in meters (floor-to-floor). Falls back to
11
+ * {@link DEFAULT_LEVEL_HEIGHT} for unmigrated legacy levels whose `height`
12
+ * field is absent.
13
+ */
14
+ export declare function getStoredLevelHeight(level: Pick<LevelNode, 'height'>): number;
15
+ export type LevelElevation = {
16
+ /** World Y of the level's floor: prefix sum of the storey heights below it. */
17
+ baseY: number;
18
+ /** Stored storey height of this level (fallback applied). */
19
+ height: number;
20
+ buildingId: string | null;
21
+ ordinal: number;
22
+ };
23
+ /**
24
+ * Per-building stacked elevations from stored storey heights: levels are
25
+ * sorted by ordinal ascending within each building, the lowest level's floor
26
+ * sits at 0, and each next floor sits on top of the previous storey height.
27
+ * Levels with no resolvable building share one legacy stack from 0.
28
+ *
29
+ * Pure — operates on the serialized nodes record only.
30
+ */
31
+ export declare function getLevelElevations(nodes: Record<AnyNodeId, AnyNode>): Map<string, LevelElevation>;
32
+ /**
33
+ * The id of the level directly above `levelId` in its own stack (same
34
+ * resolved building, or the shared legacy stack for building-less levels):
35
+ * the level with the lowest ordinal strictly greater than the queried
36
+ * level's. `null` when the level is topmost or unresolvable.
37
+ */
38
+ export declare function findLevelAboveId(levelId: string, elevations: Map<string, LevelElevation>): string | null;
39
+ /**
40
+ * The level directly above `levelId` — see {@link findLevelAboveId}.
41
+ * `null` when topmost or unresolvable. Pure.
42
+ */
43
+ export declare function getLevelAbove(levelId: string, nodes: Record<AnyNodeId, AnyNode>): LevelNode | null;
44
+ /**
45
+ * The id of the level directly below `levelId` in its own stack — mirror of
46
+ * {@link findLevelAboveId}: the level with the highest ordinal strictly less
47
+ * than the queried level's. `null` when the level is lowest or unresolvable.
48
+ */
49
+ export declare function findLevelBelowId(levelId: string, elevations: Map<string, LevelElevation>): string | null;
50
+ /**
51
+ * The level directly below `levelId` — see {@link findLevelBelowId}.
52
+ * `null` when lowest or unresolvable. Pure.
53
+ */
54
+ export declare function getLevelBelow(levelId: string, nodes: Record<AnyNodeId, AnyNode>): LevelNode | null;
55
+ /**
56
+ * Underside of the LOWEST slab from the level above that covers
57
+ * level-local point `[x, z]`, expressed in the queried level's local Y:
58
+ * `storeyHeight + (slab.elevation - slab.thickness)`. `recessed` slabs
59
+ * (pools) never cover. `null` when no covering slab (or no level above).
60
+ *
61
+ * Coordinate spaces: levels stack in Y only (`LevelNode` carries no XZ
62
+ * transform and the viewer's LevelSystem writes only `position.y`), so a
63
+ * level-local `[x, z]` is valid in every level of the stack unchanged.
64
+ */
65
+ export declare function getCoveringSlabUndersideAt(levelId: string, nodes: Record<AnyNodeId, AnyNode>, x: number, z: number): number | null;
66
+ /**
67
+ * Top plane for a plane-bound wall on `levelId`, in level-local Y:
68
+ * `min(stored storey height, lowest covering-slab underside over the wall's
69
+ * span)` — a thick or flush slab on the level above SHORTENS the walls below
70
+ * instead of colliding with them (Revit-style automatic attach).
71
+ *
72
+ * Coverage: the wall's thickness band (centerline + face lines, arc-aware)
73
+ * is clipped against each covering slab's stored polygon minus holes via
74
+ * {@link wallOverlapsSlabFootprint} — the same overlap machinery as the
75
+ * support election. Point sampling is deliberately avoided: auto-slab
76
+ * polygons derive from wall CENTERLINES, so perimeter walls sit exactly ON
77
+ * the polygon boundary, where ray-cast point-in-polygon flips with the
78
+ * edge's orientation (one wall clamped, its neighbor didn't). Boundary
79
+ * contact counts as covered on every side of the slab.
80
+ *
81
+ * This is THE plane for a plane-bound wall (`height` absent). Explicit-height
82
+ * walls ignore the value (`resolveWallTop` returns their stored height), so
83
+ * passing it wherever a raw storey height feeds `resolveWallTop` /
84
+ * `resolveWallEffectiveHeight` is always safe. Falls back to
85
+ * {@link DEFAULT_LEVEL_HEIGHT} when `levelId` doesn't resolve to a level.
86
+ */
87
+ export declare function getWallPlaneTop(wall: Pick<WallNode, 'start' | 'end'> & Partial<Pick<WallNode, 'thickness' | 'curveOffset'>>, levelId: string, nodes: Record<AnyNodeId, AnyNode>): number;
88
+ /**
89
+ * Upper bound for a ceiling's stored height over `polygon` on `levelId`:
90
+ * `min(storey plane, lowest covering-slab underside) - CEILING_CLAMP_MARGIN`.
91
+ * The covering underside is sampled at every polygon vertex plus the
92
+ * centroid — cheap, and a slab overlapping a convex-ish ceiling almost
93
+ * always covers one of those points; exact polygon-vs-polygon overlap is
94
+ * not worth its cost for a clamp bound. Ceiling outlines share footprint
95
+ * edges with the slabs above them the same way walls do, so vertices
96
+ * sitting exactly on a slab's boundary count as covered on every side
97
+ * (see `slabCoversPoint`) instead of flipping with the edge orientation.
98
+ *
99
+ * Returns `Infinity` when `levelId` doesn't resolve, so callers clamp
100
+ * against nothing rather than a garbage plane.
101
+ */
102
+ export declare function getCeilingClampBound(levelId: string, nodes: Record<AnyNodeId, AnyNode>, polygon: ReadonlyArray<[number, number]>): number;
103
+ //# sourceMappingURL=storey.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storey.d.ts","sourceRoot":"","sources":["../../src/services/storey.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,SAAS,EAAY,QAAQ,EAAE,MAAM,WAAW,CAAA;AAC5E,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAQzD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,OAAO,CAAA;AAExC;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,GAAG,MAAM,CAE7E;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,+EAA+E;IAC/E,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAkBD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAgCjG;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GACtC,MAAM,GAAG,IAAI,CAef;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,GAChC,SAAS,GAAG,IAAI,CAKlB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GACtC,MAAM,GAAG,IAAI,CAef;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,GAChC,SAAS,GAAG,IAAI,CAKlB;AAuFD;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EACjC,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,GACR,MAAM,GAAG,IAAI,CAIf;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,GAAG,aAAa,CAAC,CAAC,EAC5F,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,GAChC,MAAM,CAYR;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EACjC,OAAO,EAAE,aAAa,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACvC,MAAM,CAuBR"}