@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,296 @@
1
+ import { pointInPolygon, pointOnPolygonBoundary, wallOverlapsSlabFootprint, } from '../systems/slab/slab-support';
2
+ import { DEFAULT_LEVEL_HEIGHT } from './level-height';
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 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 function getStoredLevelHeight(level) {
15
+ return level.height ?? DEFAULT_LEVEL_HEIGHT;
16
+ }
17
+ /**
18
+ * Resolves the owning building: explicit `parentId` pointing at a building
19
+ * wins; legacy levels that only appear in a building's `children` array
20
+ * resolve through that membership.
21
+ */
22
+ function resolveLevelBuildingId(levelId, parentId, buildings) {
23
+ const directParent = parentId ? buildings.find((building) => building.id === parentId) : undefined;
24
+ if (directParent)
25
+ return directParent.id;
26
+ return buildings.find((building) => building.children.includes(levelId))?.id ?? null;
27
+ }
28
+ /**
29
+ * Per-building stacked elevations from stored storey heights: levels are
30
+ * sorted by ordinal ascending within each building, the lowest level's floor
31
+ * sits at 0, and each next floor sits on top of the previous storey height.
32
+ * Levels with no resolvable building share one legacy stack from 0.
33
+ *
34
+ * Pure — operates on the serialized nodes record only.
35
+ */
36
+ export function getLevelElevations(nodes) {
37
+ const buildings = Object.values(nodes).filter((node) => node?.type === 'building');
38
+ const entries = [];
39
+ for (const node of Object.values(nodes)) {
40
+ if (node?.type !== 'level')
41
+ continue;
42
+ const level = node;
43
+ entries.push({
44
+ levelId: level.id,
45
+ baseY: 0,
46
+ height: getStoredLevelHeight(level),
47
+ buildingId: resolveLevelBuildingId(level.id, level.parentId, buildings),
48
+ ordinal: level.level,
49
+ });
50
+ }
51
+ const elevations = new Map();
52
+ const cumulativeYByBuilding = new Map();
53
+ for (const entry of entries.sort((a, b) => a.ordinal - b.ordinal)) {
54
+ const baseY = cumulativeYByBuilding.get(entry.buildingId) ?? 0;
55
+ elevations.set(entry.levelId, {
56
+ baseY,
57
+ height: entry.height,
58
+ buildingId: entry.buildingId,
59
+ ordinal: entry.ordinal,
60
+ });
61
+ cumulativeYByBuilding.set(entry.buildingId, baseY + entry.height);
62
+ }
63
+ return elevations;
64
+ }
65
+ /**
66
+ * The id of the level directly above `levelId` in its own stack (same
67
+ * resolved building, or the shared legacy stack for building-less levels):
68
+ * the level with the lowest ordinal strictly greater than the queried
69
+ * level's. `null` when the level is topmost or unresolvable.
70
+ */
71
+ export function findLevelAboveId(levelId, elevations) {
72
+ const entry = elevations.get(levelId);
73
+ if (!entry)
74
+ return null;
75
+ let aboveId = null;
76
+ let aboveOrdinal = Number.POSITIVE_INFINITY;
77
+ for (const [candidateId, candidate] of elevations) {
78
+ if (candidateId === levelId)
79
+ continue;
80
+ if (candidate.buildingId !== entry.buildingId)
81
+ continue;
82
+ if (candidate.ordinal > entry.ordinal && candidate.ordinal < aboveOrdinal) {
83
+ aboveOrdinal = candidate.ordinal;
84
+ aboveId = candidateId;
85
+ }
86
+ }
87
+ return aboveId;
88
+ }
89
+ /**
90
+ * The level directly above `levelId` — see {@link findLevelAboveId}.
91
+ * `null` when topmost or unresolvable. Pure.
92
+ */
93
+ export function getLevelAbove(levelId, nodes) {
94
+ const aboveId = findLevelAboveId(levelId, getLevelElevations(nodes));
95
+ if (!aboveId)
96
+ return null;
97
+ const above = nodes[aboveId];
98
+ return above?.type === 'level' ? above : null;
99
+ }
100
+ /**
101
+ * The id of the level directly below `levelId` in its own stack — mirror of
102
+ * {@link findLevelAboveId}: the level with the highest ordinal strictly less
103
+ * than the queried level's. `null` when the level is lowest or unresolvable.
104
+ */
105
+ export function findLevelBelowId(levelId, elevations) {
106
+ const entry = elevations.get(levelId);
107
+ if (!entry)
108
+ return null;
109
+ let belowId = null;
110
+ let belowOrdinal = Number.NEGATIVE_INFINITY;
111
+ for (const [candidateId, candidate] of elevations) {
112
+ if (candidateId === levelId)
113
+ continue;
114
+ if (candidate.buildingId !== entry.buildingId)
115
+ continue;
116
+ if (candidate.ordinal < entry.ordinal && candidate.ordinal > belowOrdinal) {
117
+ belowOrdinal = candidate.ordinal;
118
+ belowId = candidateId;
119
+ }
120
+ }
121
+ return belowId;
122
+ }
123
+ /**
124
+ * The level directly below `levelId` — see {@link findLevelBelowId}.
125
+ * `null` when lowest or unresolvable. Pure.
126
+ */
127
+ export function getLevelBelow(levelId, nodes) {
128
+ const belowId = findLevelBelowId(levelId, getLevelElevations(nodes));
129
+ if (!belowId)
130
+ return null;
131
+ const below = nodes[belowId];
132
+ return below?.type === 'level' ? below : null;
133
+ }
134
+ /**
135
+ * Storey height of the queried level plus the level-above's covering
136
+ * (non-recessed) slabs. `null` when `levelId` doesn't resolve to a level.
137
+ * A missing level above yields an empty slab list, not `null` — the
138
+ * storey height is still meaningful for the clamp bound.
139
+ */
140
+ function resolveCoveringSlabContext(levelId, nodes) {
141
+ const level = nodes[levelId];
142
+ if (level?.type !== 'level')
143
+ return null;
144
+ const above = getLevelAbove(levelId, nodes);
145
+ const slabs = [];
146
+ for (const childId of above?.children ?? []) {
147
+ const child = nodes[childId];
148
+ if (child?.type !== 'slab')
149
+ continue;
150
+ const slab = child;
151
+ // Recessed slabs (pools) are open shells, not covering solids.
152
+ if (slab.recessed === true)
153
+ continue;
154
+ if (slab.polygon.length < 3)
155
+ continue;
156
+ slabs.push(slab);
157
+ }
158
+ return { storeyHeight: getStoredLevelHeight(level), slabs };
159
+ }
160
+ /**
161
+ * Underside of `slab`'s solid in the QUERIED level's local Y. The solid
162
+ * occupies `[elevation - thickness, elevation]` in ITS level's local Y,
163
+ * which sits `storeyHeight` above the queried level's floor.
164
+ */
165
+ function coveringUndersideY(storeyHeight, slab) {
166
+ return storeyHeight + ((slab.elevation ?? 0.05) - (slab.thickness ?? 0.05));
167
+ }
168
+ /**
169
+ * Whether `slab`'s stored footprint (polygon minus holes) covers `[x, z]`.
170
+ * Ray-cast pointInPolygon flips arbitrarily for points exactly ON the
171
+ * boundary (min-side edges read inside, max-side edges outside), so
172
+ * boundary contact counts as covered explicitly — the same convention as
173
+ * the slab-support interval classification. A point on a hole's rim keeps
174
+ * coverage (mirrors the support election's hole handling).
175
+ *
176
+ * Raw stored polygon + holes on purpose (mirrors getCeilingAt): the
177
+ * clamp bound doesn't need the rendered footprint's junction trims,
178
+ * and staying off the render path keeps this query cheap and pure.
179
+ */
180
+ function slabCoversPoint(slab, x, z) {
181
+ if (!pointInPolygon(x, z, slab.polygon) && !pointOnPolygonBoundary(x, z, slab.polygon)) {
182
+ return false;
183
+ }
184
+ for (const hole of slab.holes ?? []) {
185
+ if (hole.length < 3)
186
+ continue;
187
+ if (pointInPolygon(x, z, hole) && !pointOnPolygonBoundary(x, z, hole))
188
+ return false;
189
+ }
190
+ return true;
191
+ }
192
+ /**
193
+ * Lowest underside among `slabs` covering `[x, z]`, in the queried
194
+ * level's local Y, or `null` when none covers the point.
195
+ */
196
+ function lowestCoveringUndersideAt(context, x, z) {
197
+ let lowest = null;
198
+ for (const slab of context.slabs) {
199
+ if (!slabCoversPoint(slab, x, z))
200
+ continue;
201
+ const underside = coveringUndersideY(context.storeyHeight, slab);
202
+ if (lowest === null || underside < lowest)
203
+ lowest = underside;
204
+ }
205
+ return lowest;
206
+ }
207
+ /**
208
+ * Underside of the LOWEST slab from the level above that covers
209
+ * level-local point `[x, z]`, expressed in the queried level's local Y:
210
+ * `storeyHeight + (slab.elevation - slab.thickness)`. `recessed` slabs
211
+ * (pools) never cover. `null` when no covering slab (or no level above).
212
+ *
213
+ * Coordinate spaces: levels stack in Y only (`LevelNode` carries no XZ
214
+ * transform and the viewer's LevelSystem writes only `position.y`), so a
215
+ * level-local `[x, z]` is valid in every level of the stack unchanged.
216
+ */
217
+ export function getCoveringSlabUndersideAt(levelId, nodes, x, z) {
218
+ const context = resolveCoveringSlabContext(levelId, nodes);
219
+ if (!context)
220
+ return null;
221
+ return lowestCoveringUndersideAt(context, x, z);
222
+ }
223
+ /**
224
+ * Top plane for a plane-bound wall on `levelId`, in level-local Y:
225
+ * `min(stored storey height, lowest covering-slab underside over the wall's
226
+ * span)` — a thick or flush slab on the level above SHORTENS the walls below
227
+ * instead of colliding with them (Revit-style automatic attach).
228
+ *
229
+ * Coverage: the wall's thickness band (centerline + face lines, arc-aware)
230
+ * is clipped against each covering slab's stored polygon minus holes via
231
+ * {@link wallOverlapsSlabFootprint} — the same overlap machinery as the
232
+ * support election. Point sampling is deliberately avoided: auto-slab
233
+ * polygons derive from wall CENTERLINES, so perimeter walls sit exactly ON
234
+ * the polygon boundary, where ray-cast point-in-polygon flips with the
235
+ * edge's orientation (one wall clamped, its neighbor didn't). Boundary
236
+ * contact counts as covered on every side of the slab.
237
+ *
238
+ * This is THE plane for a plane-bound wall (`height` absent). Explicit-height
239
+ * walls ignore the value (`resolveWallTop` returns their stored height), so
240
+ * passing it wherever a raw storey height feeds `resolveWallTop` /
241
+ * `resolveWallEffectiveHeight` is always safe. Falls back to
242
+ * {@link DEFAULT_LEVEL_HEIGHT} when `levelId` doesn't resolve to a level.
243
+ */
244
+ export function getWallPlaneTop(wall, levelId, nodes) {
245
+ const context = resolveCoveringSlabContext(levelId, nodes);
246
+ if (!context)
247
+ return DEFAULT_LEVEL_HEIGHT;
248
+ let plane = context.storeyHeight;
249
+ for (const slab of context.slabs) {
250
+ const underside = coveringUndersideY(context.storeyHeight, slab);
251
+ if (underside >= plane)
252
+ continue;
253
+ if (!wallOverlapsSlabFootprint(wall, slab.polygon, slab.holes))
254
+ continue;
255
+ plane = underside;
256
+ }
257
+ return plane;
258
+ }
259
+ /**
260
+ * Upper bound for a ceiling's stored height over `polygon` on `levelId`:
261
+ * `min(storey plane, lowest covering-slab underside) - CEILING_CLAMP_MARGIN`.
262
+ * The covering underside is sampled at every polygon vertex plus the
263
+ * centroid — cheap, and a slab overlapping a convex-ish ceiling almost
264
+ * always covers one of those points; exact polygon-vs-polygon overlap is
265
+ * not worth its cost for a clamp bound. Ceiling outlines share footprint
266
+ * edges with the slabs above them the same way walls do, so vertices
267
+ * sitting exactly on a slab's boundary count as covered on every side
268
+ * (see `slabCoversPoint`) instead of flipping with the edge orientation.
269
+ *
270
+ * Returns `Infinity` when `levelId` doesn't resolve, so callers clamp
271
+ * against nothing rather than a garbage plane.
272
+ */
273
+ export function getCeilingClampBound(levelId, nodes, polygon) {
274
+ const context = resolveCoveringSlabContext(levelId, nodes);
275
+ if (!context)
276
+ return Number.POSITIVE_INFINITY;
277
+ let bound = context.storeyHeight;
278
+ if (polygon.length > 0) {
279
+ let cx = 0;
280
+ let cz = 0;
281
+ for (const [x, z] of polygon) {
282
+ cx += x;
283
+ cz += z;
284
+ }
285
+ const samples = [
286
+ ...polygon,
287
+ [cx / polygon.length, cz / polygon.length],
288
+ ];
289
+ for (const [x, z] of samples) {
290
+ const underside = lowestCoveringUndersideAt(context, x, z);
291
+ if (underside !== null && underside < bound)
292
+ bound = underside;
293
+ }
294
+ }
295
+ return bound - CEILING_CLAMP_MARGIN;
296
+ }
@@ -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"}
@@ -0,0 +1,164 @@
1
+ import { nodeRegistry } from '../registry';
2
+ /**
3
+ * The "System" primitive: connected components over the port graph.
4
+ *
5
+ * Two nodes are joined when a port of one coincides in space with a port
6
+ * of the other — the same mated-joint relationship `port-connectivity`
7
+ * uses for drag propagation, read here at whole-scene scope. A component
8
+ * is one distribution system: a furnace, its trunk, the tees, branches,
9
+ * and registers hanging off it.
10
+ *
11
+ * Pure logic (def.ports + arithmetic), no rendering — lives in core so
12
+ * the editor (badges, schedules) and analyses (sizing, code checks) can
13
+ * share it.
14
+ */
15
+ /** Distance (meters) under which two ports count as the same joint —
16
+ * matches port-connectivity's tolerance for hand-placed joints. */
17
+ const COINCIDENT_EPS_M = 0.05;
18
+ function collectPorts(nodes) {
19
+ const result = [];
20
+ for (const node of Object.values(nodes)) {
21
+ if (!node)
22
+ continue;
23
+ const ports = nodeRegistry.get(node.type)?.ports?.(node);
24
+ if (!ports)
25
+ continue;
26
+ for (const port of ports) {
27
+ result.push({
28
+ nodeId: node.id,
29
+ x: port.position[0],
30
+ y: port.position[1],
31
+ z: port.position[2],
32
+ system: port.system,
33
+ });
34
+ }
35
+ }
36
+ return result;
37
+ }
38
+ /** Union-find over node ids. */
39
+ class Components {
40
+ parent = new Map();
41
+ find(id) {
42
+ let root = this.parent.get(id) ?? id;
43
+ if (root !== id) {
44
+ root = this.find(root);
45
+ this.parent.set(id, root);
46
+ }
47
+ return root;
48
+ }
49
+ union(a, b) {
50
+ const ra = this.find(a);
51
+ const rb = this.find(b);
52
+ if (ra !== rb)
53
+ this.parent.set(rb, ra);
54
+ }
55
+ }
56
+ function pathLength(path) {
57
+ let total = 0;
58
+ for (let i = 0; i < path.length - 1; i++) {
59
+ const a = path[i];
60
+ const b = path[i + 1];
61
+ total += Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
62
+ }
63
+ return total;
64
+ }
65
+ /**
66
+ * Group every port-bearing node into connected components via coinciding
67
+ * ports. Nodes with ports but no joints form singleton components; nodes
68
+ * without `def.ports` don't participate at all.
69
+ */
70
+ export function buildPortComponents(nodes) {
71
+ const ports = collectPorts(nodes);
72
+ const components = new Components();
73
+ const epsSq = COINCIDENT_EPS_M * COINCIDENT_EPS_M;
74
+ for (let i = 0; i < ports.length; i++) {
75
+ const a = ports[i];
76
+ for (let j = i + 1; j < ports.length; j++) {
77
+ const b = ports[j];
78
+ if (a.nodeId === b.nodeId)
79
+ continue;
80
+ const dx = a.x - b.x;
81
+ const dy = a.y - b.y;
82
+ const dz = a.z - b.z;
83
+ if (dx * dx + dy * dy + dz * dz <= epsSq)
84
+ components.union(a.nodeId, b.nodeId);
85
+ }
86
+ }
87
+ const grouped = new Map();
88
+ const seen = new Set();
89
+ for (const port of ports) {
90
+ if (seen.has(port.nodeId))
91
+ continue;
92
+ seen.add(port.nodeId);
93
+ const root = components.find(port.nodeId);
94
+ const group = grouped.get(root);
95
+ if (group)
96
+ group.push(port.nodeId);
97
+ else
98
+ grouped.set(root, [port.nodeId]);
99
+ }
100
+ return [...grouped.values()];
101
+ }
102
+ function summarize(nodeIds, nodes) {
103
+ const systems = new Set();
104
+ let runCount = 0;
105
+ let runLengthM = 0;
106
+ let fittingCount = 0;
107
+ let terminalCount = 0;
108
+ let equipmentCount = 0;
109
+ for (const id of nodeIds) {
110
+ const node = nodes[id];
111
+ if (!node)
112
+ continue;
113
+ const role = nodeRegistry.get(node.type)?.distributionRole;
114
+ const fields = node;
115
+ if (role === 'run') {
116
+ runCount += 1;
117
+ if (fields.path)
118
+ runLengthM += pathLength(fields.path);
119
+ // Linesets carry refrigerant; duct / pipe runs name their own loop.
120
+ systems.add(fields.system ?? 'refrigerant');
121
+ }
122
+ else if (role === 'fitting') {
123
+ fittingCount += 1;
124
+ if (fields.system)
125
+ systems.add(fields.system);
126
+ }
127
+ else if (role === 'terminal') {
128
+ terminalCount += 1;
129
+ systems.add(fields.terminalType === 'return-grille' ? 'return' : 'supply');
130
+ }
131
+ else if (role === 'equipment') {
132
+ equipmentCount += 1;
133
+ }
134
+ }
135
+ return {
136
+ nodeIds,
137
+ systems: [...systems].sort(),
138
+ runCount,
139
+ runLengthM,
140
+ fittingCount,
141
+ terminalCount,
142
+ equipmentCount,
143
+ connectedToEquipment: equipmentCount > 0,
144
+ };
145
+ }
146
+ /**
147
+ * Summary of the system the given node belongs to, or null when the node
148
+ * has no ports (not a distribution kind). A node with ports but no
149
+ * joints yet still gets a (singleton) summary — `connectedToEquipment:
150
+ * false` is the interesting signal there.
151
+ */
152
+ export function summarizeSystemFor(nodeId, nodes) {
153
+ const node = nodes[nodeId];
154
+ if (!node)
155
+ return null;
156
+ const ports = nodeRegistry.get(node.type)?.ports?.(node);
157
+ if (!ports || ports.length === 0)
158
+ return null;
159
+ for (const component of buildPortComponents(nodes)) {
160
+ if (component.includes(nodeId))
161
+ return summarize(component, nodes);
162
+ }
163
+ return summarize([nodeId], nodes);
164
+ }
@@ -0,0 +1,32 @@
1
+ import type { AnyNode, AnyNodeId } from '../schema';
2
+ /**
3
+ * IPC validators for the DWV (drain-waste-vent) system — the "CodeRule"
4
+ * primitive from the domain brief. The slope, minimum-size, and
5
+ * trap-arm rules are all geometric and read straight off the node
6
+ * fields, so they live here in core (pure logic) where the editor can
7
+ * surface them and analyses can reuse them.
8
+ *
9
+ * Scope is residential IPC, simplified:
10
+ * - 704.1 drainage slope by pipe size.
11
+ * - 909 trap-arm maximum developed length by trap size.
12
+ *
13
+ * These are intentionally conservative approximations, not a certified
14
+ * plan-check — enough to flag the mistakes a drawing tool invites.
15
+ */
16
+ /** Drainage findings, worst-first per consumer's sort. */
17
+ export type DwvSeverity = 'error' | 'warning';
18
+ export type DwvFinding = {
19
+ severity: DwvSeverity;
20
+ /** Stable rule id, e.g. 'slope-too-flat'. */
21
+ code: string;
22
+ /** Human-readable, already-formatted message. */
23
+ message: string;
24
+ /** Nodes the finding implicates (usually one). */
25
+ nodeIds: AnyNodeId[];
26
+ };
27
+ /**
28
+ * Run every DWV rule over the scene and return the findings. Empty
29
+ * array = nothing to flag. Pure: no scene/store access, no rendering.
30
+ */
31
+ export declare function validateDwv(nodes: Readonly<Record<AnyNodeId, AnyNode>>): DwvFinding[];
32
+ //# sourceMappingURL=validate-dwv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-dwv.d.ts","sourceRoot":"","sources":["../../src/services/validate-dwv.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAGnD;;;;;;;;;;;;;GAaG;AAEH,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,SAAS,CAAA;AAE7C,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,WAAW,CAAA;IACrB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAA;IACZ,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,kDAAkD;IAClD,OAAO,EAAE,SAAS,EAAE,CAAA;CACrB,CAAA;AAuDD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAAG,UAAU,EAAE,CAwErF"}
@@ -0,0 +1,118 @@
1
+ import { buildPortComponents } from './system-graph';
2
+ /** IPC 704.1 minimum drainage slope (rise/run, dimensionless) by
3
+ * nominal pipe size: ¼"/ft (1:48) under 3", ⅛"/ft (1:96) for 3–6",
4
+ * 1/16"/ft (1:192) at 8"+. */
5
+ function minSlopeFor(diameterIn) {
6
+ if (diameterIn < 3)
7
+ return 1 / 48;
8
+ if (diameterIn < 8)
9
+ return 1 / 96;
10
+ return 1 / 192;
11
+ }
12
+ /** IPC Table 909.1 maximum trap-arm developed length (meters) by trap
13
+ * size: 30" @ 1¼", 42" @ 1½", 60" @ 2", 72" @ 3", 120" @ 4". */
14
+ const TRAP_ARM_MAX_M = [
15
+ [1.25, 30 * 0.0254],
16
+ [1.5, 42 * 0.0254],
17
+ [2, 60 * 0.0254],
18
+ [3, 72 * 0.0254],
19
+ [4, 120 * 0.0254],
20
+ ];
21
+ function trapArmMaxFor(diameterIn) {
22
+ let max = Infinity;
23
+ for (const [size, lengthM] of TRAP_ARM_MAX_M) {
24
+ if (diameterIn <= size)
25
+ return lengthM;
26
+ max = lengthM;
27
+ }
28
+ return max;
29
+ }
30
+ /** Slopes shallower than this fraction of the minimum are flagged
31
+ * "too flat" — a small tolerance keeps round-off off the list. */
32
+ const SLOPE_TOLERANCE = 0.9;
33
+ /** Horizontal legs shorter than this (meters) are treated as vertical
34
+ * stacks and skipped from the slope check. */
35
+ const VERTICAL_LEG_EPS_M = 0.02;
36
+ function legSlope(a, b) {
37
+ const horizontalM = Math.hypot(b[0] - a[0], b[2] - a[2]);
38
+ if (horizontalM < VERTICAL_LEG_EPS_M)
39
+ return { horizontalM, slope: Infinity };
40
+ return { horizontalM, slope: Math.abs(a[1] - b[1]) / horizontalM };
41
+ }
42
+ function inchLabel(value) {
43
+ return `${value}"`;
44
+ }
45
+ /** Per-foot slope as a readable fraction, e.g. 0.0208 → '¼"/ft'. */
46
+ function slopePerFootLabel(slope) {
47
+ const inchesPerFoot = slope * 12;
48
+ return `${inchesPerFoot.toFixed(2)}"/ft`;
49
+ }
50
+ /**
51
+ * Run every DWV rule over the scene and return the findings. Empty
52
+ * array = nothing to flag. Pure: no scene/store access, no rendering.
53
+ */
54
+ export function validateDwv(nodes) {
55
+ const findings = [];
56
+ // ── Per-segment slope (waste only) ──────────────────────────────
57
+ for (const node of Object.values(nodes)) {
58
+ if (node?.type !== 'pipe-segment' || node.system !== 'waste')
59
+ continue;
60
+ const path = node.path;
61
+ const minSlope = minSlopeFor(node.diameter);
62
+ const maxSlope = node.diameter / 12; // 1 pipe-diameter per foot → siphoning
63
+ let flaggedFlat = false;
64
+ let flaggedSteep = false;
65
+ for (let i = 0; i < path.length - 1; i++) {
66
+ const { slope } = legSlope(path[i], path[i + 1]);
67
+ if (slope === Infinity)
68
+ continue; // vertical stack leg
69
+ if (!flaggedFlat && slope < minSlope * SLOPE_TOLERANCE) {
70
+ findings.push({
71
+ severity: 'error',
72
+ code: 'slope-too-flat',
73
+ message: `${inchLabel(node.diameter)} drain slopes ${slopePerFootLabel(slope)} — IPC 704.1 requires at least ${slopePerFootLabel(minSlope)}.`,
74
+ nodeIds: [node.id],
75
+ });
76
+ flaggedFlat = true;
77
+ }
78
+ if (!flaggedSteep && slope > maxSlope) {
79
+ findings.push({
80
+ severity: 'warning',
81
+ code: 'slope-too-steep',
82
+ message: `${inchLabel(node.diameter)} drain slopes ${slopePerFootLabel(slope)} — over one pipe-diameter per foot risks siphoning the traps.`,
83
+ nodeIds: [node.id],
84
+ });
85
+ flaggedSteep = true;
86
+ }
87
+ }
88
+ }
89
+ // ── Component-scoped trap rules ──────────────────────────────────
90
+ for (const component of buildPortComponents(nodes)) {
91
+ const traps = [];
92
+ for (const id of component) {
93
+ const node = nodes[id];
94
+ if (!node)
95
+ continue;
96
+ if (node.type === 'pipe-trap') {
97
+ traps.push(node);
98
+ }
99
+ }
100
+ // Trap-arm developed length: trap outlet → its vent, capped by size.
101
+ // Independent of waste segments — a trap on its own can already be
102
+ // over-armed.
103
+ for (const trap of traps) {
104
+ const t = trap;
105
+ const armLengthM = t.armLengthM ?? 0;
106
+ const maxArm = trapArmMaxFor(t.diameter);
107
+ if (armLengthM > maxArm + 1e-6) {
108
+ findings.push({
109
+ severity: 'error',
110
+ code: 'trap-arm-too-long',
111
+ message: `${inchLabel(t.diameter)} trap arm runs ${(armLengthM / 0.0254).toFixed(0)}" to its vent — IPC 909.1 caps it at ${(maxArm / 0.0254).toFixed(0)}".`,
112
+ nodeIds: [t.id],
113
+ });
114
+ }
115
+ }
116
+ }
117
+ return findings;
118
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"node-actions.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-actions.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EAKf,MAAM,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAG9C,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,CAAA;AAC3D,KAAK,YAAY,GAAG;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAA;AAC7D,KAAK,YAAY,GAAG,SAAS,CAAA;AAqrB7B,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,YAAY,EAAE,SAkDpB,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAA;CAAE,SAoIvF,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAAE,SA2ErD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,SAAS,EAAE,SAsGjB,CAAA"}
1
+ {"version":3,"file":"node-actions.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-actions.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EASf,MAAM,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAG9C,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,CAAA;AAC3D,KAAK,YAAY,GAAG;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAA;AAC7D,KAAK,YAAY,GAAG,SAAS,CAAA;AA0vB7B,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,YAAY,EAAE,SAkDpB,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAA;CAAE,SAyIvF,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAAE,SAoFrD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,SAAS,EAAE,SA8JjB,CAAA"}