@pascal-app/core 0.8.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. package/dist/events/bus.d.ts +67 -13
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/hooks/scene-registry/item-clip-registry.d.ts +18 -0
  4. package/dist/hooks/scene-registry/item-clip-registry.d.ts.map +1 -0
  5. package/dist/hooks/scene-registry/item-clip-registry.js +10 -0
  6. package/dist/hooks/scene-registry/scene-registry.d.ts +14 -23
  7. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  8. package/dist/hooks/scene-registry/scene-registry.js +59 -26
  9. package/dist/hooks/scene-registry/scene-registry.test.d.ts +2 -0
  10. package/dist/hooks/scene-registry/scene-registry.test.d.ts.map +1 -0
  11. package/dist/hooks/scene-registry/scene-registry.test.js +26 -0
  12. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
  13. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
  14. package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
  15. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
  16. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
  17. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +366 -0
  18. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +79 -11
  19. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  20. package/dist/hooks/spatial-grid/spatial-grid-manager.js +508 -141
  21. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
  22. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  23. package/dist/hooks/spatial-grid/spatial-grid-sync.js +109 -18
  24. package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts +2 -0
  25. package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts.map +1 -0
  26. package/dist/hooks/spatial-grid/wall-slab-overlap.test.js +363 -0
  27. package/dist/index.d.ts +36 -8
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +33 -6
  30. package/dist/lib/level-name.d.ts +4 -0
  31. package/dist/lib/level-name.d.ts.map +1 -0
  32. package/dist/lib/level-name.js +10 -0
  33. package/dist/lib/measurement-geometry.d.ts +19 -0
  34. package/dist/lib/measurement-geometry.d.ts.map +1 -0
  35. package/dist/lib/measurement-geometry.js +232 -0
  36. package/dist/lib/measurement-geometry.test.d.ts +2 -0
  37. package/dist/lib/measurement-geometry.test.d.ts.map +1 -0
  38. package/dist/lib/measurement-geometry.test.js +102 -0
  39. package/dist/lib/polygon-geometry.d.ts +0 -1
  40. package/dist/lib/polygon-geometry.d.ts.map +1 -1
  41. package/dist/lib/polygon-geometry.js +0 -18
  42. package/dist/lib/polygon-relations.d.ts +10 -0
  43. package/dist/lib/polygon-relations.d.ts.map +1 -0
  44. package/dist/lib/polygon-relations.js +73 -0
  45. package/dist/lib/selection-proxy.d.ts +4 -0
  46. package/dist/lib/selection-proxy.d.ts.map +1 -0
  47. package/dist/lib/selection-proxy.js +12 -0
  48. package/dist/lib/slab-polygon.d.ts +31 -2
  49. package/dist/lib/slab-polygon.d.ts.map +1 -1
  50. package/dist/lib/slab-polygon.js +525 -31
  51. package/dist/lib/slab-polygon.test.d.ts +2 -0
  52. package/dist/lib/slab-polygon.test.d.ts.map +1 -0
  53. package/dist/lib/slab-polygon.test.js +635 -0
  54. package/dist/lib/slots.d.ts +14 -0
  55. package/dist/lib/slots.d.ts.map +1 -0
  56. package/dist/lib/slots.js +26 -0
  57. package/dist/lib/space-detection-pause.test.d.ts +2 -0
  58. package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
  59. package/dist/lib/space-detection-pause.test.js +42 -0
  60. package/dist/lib/space-detection.d.ts +54 -2
  61. package/dist/lib/space-detection.d.ts.map +1 -1
  62. package/dist/lib/space-detection.js +599 -124
  63. package/dist/lib/space-detection.test.d.ts +2 -0
  64. package/dist/lib/space-detection.test.d.ts.map +1 -0
  65. package/dist/lib/space-detection.test.js +376 -0
  66. package/dist/lib/wall-distance.d.ts +60 -0
  67. package/dist/lib/wall-distance.d.ts.map +1 -0
  68. package/dist/lib/wall-distance.js +85 -0
  69. package/dist/lib/zone-quantities.d.ts +21 -0
  70. package/dist/lib/zone-quantities.d.ts.map +1 -0
  71. package/dist/lib/zone-quantities.js +183 -0
  72. package/dist/lib/zone-quantities.test.d.ts +2 -0
  73. package/dist/lib/zone-quantities.test.d.ts.map +1 -0
  74. package/dist/lib/zone-quantities.test.js +82 -0
  75. package/dist/material-library.d.ts +20 -1
  76. package/dist/material-library.d.ts.map +1 -1
  77. package/dist/material-library.js +3737 -200
  78. package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
  79. package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
  80. package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
  81. package/dist/registry/handles.d.ts +394 -0
  82. package/dist/registry/handles.d.ts.map +1 -0
  83. package/dist/registry/handles.js +24 -0
  84. package/dist/registry/index.d.ts +7 -0
  85. package/dist/registry/index.d.ts.map +1 -0
  86. package/dist/registry/index.js +4 -0
  87. package/dist/registry/registry.d.ts +136 -0
  88. package/dist/registry/registry.d.ts.map +1 -0
  89. package/dist/registry/registry.js +282 -0
  90. package/dist/registry/registry.test.d.ts +2 -0
  91. package/dist/registry/registry.test.d.ts.map +1 -0
  92. package/dist/registry/registry.test.js +196 -0
  93. package/dist/registry/relations-resolver.d.ts +42 -0
  94. package/dist/registry/relations-resolver.d.ts.map +1 -0
  95. package/dist/registry/relations-resolver.js +77 -0
  96. package/dist/registry/relations-resolver.test.d.ts +2 -0
  97. package/dist/registry/relations-resolver.test.d.ts.map +1 -0
  98. package/dist/registry/relations-resolver.test.js +183 -0
  99. package/dist/registry/scene-api.d.ts +44 -0
  100. package/dist/registry/scene-api.d.ts.map +1 -0
  101. package/dist/registry/scene-api.js +111 -0
  102. package/dist/registry/scene-api.test.d.ts +2 -0
  103. package/dist/registry/scene-api.test.d.ts.map +1 -0
  104. package/dist/registry/scene-api.test.js +183 -0
  105. package/dist/registry/subtree.d.ts +67 -0
  106. package/dist/registry/subtree.d.ts.map +1 -0
  107. package/dist/registry/subtree.js +123 -0
  108. package/dist/registry/subtree.test.d.ts +2 -0
  109. package/dist/registry/subtree.test.d.ts.map +1 -0
  110. package/dist/registry/subtree.test.js +163 -0
  111. package/dist/registry/types.d.ts +2099 -0
  112. package/dist/registry/types.d.ts.map +1 -0
  113. package/dist/registry/types.js +1 -0
  114. package/dist/schema/index.d.ts +35 -4
  115. package/dist/schema/index.d.ts.map +1 -1
  116. package/dist/schema/index.js +32 -4
  117. package/dist/schema/material.d.ts +13 -2
  118. package/dist/schema/material.d.ts.map +1 -1
  119. package/dist/schema/material.js +17 -5
  120. package/dist/schema/material.test.d.ts +2 -0
  121. package/dist/schema/material.test.d.ts.map +1 -0
  122. package/dist/schema/material.test.js +43 -0
  123. package/dist/schema/nodes/box-vent.d.ts +74 -0
  124. package/dist/schema/nodes/box-vent.d.ts.map +1 -0
  125. package/dist/schema/nodes/box-vent.js +63 -0
  126. package/dist/schema/nodes/building.d.ts +1 -1
  127. package/dist/schema/nodes/building.d.ts.map +1 -1
  128. package/dist/schema/nodes/building.js +3 -2
  129. package/dist/schema/nodes/cabinet.d.ts +534 -0
  130. package/dist/schema/nodes/cabinet.d.ts.map +1 -0
  131. package/dist/schema/nodes/cabinet.js +137 -0
  132. package/dist/schema/nodes/ceiling.d.ts +5 -2
  133. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  134. package/dist/schema/nodes/ceiling.js +5 -1
  135. package/dist/schema/nodes/chimney.d.ts +158 -0
  136. package/dist/schema/nodes/chimney.d.ts.map +1 -0
  137. package/dist/schema/nodes/chimney.js +67 -0
  138. package/dist/schema/nodes/column.d.ts +600 -7
  139. package/dist/schema/nodes/column.d.ts.map +1 -1
  140. package/dist/schema/nodes/column.js +589 -6
  141. package/dist/schema/nodes/cupola.d.ts +66 -0
  142. package/dist/schema/nodes/cupola.d.ts.map +1 -0
  143. package/dist/schema/nodes/cupola.js +28 -0
  144. package/dist/schema/nodes/door.d.ts +13 -5
  145. package/dist/schema/nodes/door.d.ts.map +1 -1
  146. package/dist/schema/nodes/door.js +12 -0
  147. package/dist/schema/nodes/dormer.d.ts +228 -0
  148. package/dist/schema/nodes/dormer.d.ts.map +1 -0
  149. package/dist/schema/nodes/dormer.js +119 -0
  150. package/dist/schema/nodes/downspout.d.ts +75 -0
  151. package/dist/schema/nodes/downspout.d.ts.map +1 -0
  152. package/dist/schema/nodes/downspout.js +63 -0
  153. package/dist/schema/nodes/duct-fitting.d.ts +91 -0
  154. package/dist/schema/nodes/duct-fitting.d.ts.map +1 -0
  155. package/dist/schema/nodes/duct-fitting.js +92 -0
  156. package/dist/schema/nodes/duct-segment.d.ts +61 -0
  157. package/dist/schema/nodes/duct-segment.d.ts.map +1 -0
  158. package/dist/schema/nodes/duct-segment.js +73 -0
  159. package/dist/schema/nodes/duct-terminal.d.ts +57 -0
  160. package/dist/schema/nodes/duct-terminal.d.ts.map +1 -0
  161. package/dist/schema/nodes/duct-terminal.js +51 -0
  162. package/dist/schema/nodes/elevator.d.ts +106 -0
  163. package/dist/schema/nodes/elevator.d.ts.map +1 -0
  164. package/dist/schema/nodes/elevator.js +57 -0
  165. package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
  166. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
  167. package/dist/schema/nodes/eyebrow-vent.js +40 -0
  168. package/dist/schema/nodes/fence.d.ts +19 -2
  169. package/dist/schema/nodes/fence.d.ts.map +1 -1
  170. package/dist/schema/nodes/fence.js +27 -2
  171. package/dist/schema/nodes/gutter.d.ts +84 -0
  172. package/dist/schema/nodes/gutter.d.ts.map +1 -0
  173. package/dist/schema/nodes/gutter.js +76 -0
  174. package/dist/schema/nodes/hvac-equipment.d.ts +63 -0
  175. package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -0
  176. package/dist/schema/nodes/hvac-equipment.js +55 -0
  177. package/dist/schema/nodes/item.d.ts +12 -0
  178. package/dist/schema/nodes/item.d.ts.map +1 -1
  179. package/dist/schema/nodes/item.js +20 -0
  180. package/dist/schema/nodes/level.d.ts +1 -1
  181. package/dist/schema/nodes/level.d.ts.map +1 -1
  182. package/dist/schema/nodes/level.js +4 -0
  183. package/dist/schema/nodes/lineset.d.ts +42 -0
  184. package/dist/schema/nodes/lineset.d.ts.map +1 -0
  185. package/dist/schema/nodes/lineset.js +38 -0
  186. package/dist/schema/nodes/liquid-line.d.ts +34 -0
  187. package/dist/schema/nodes/liquid-line.d.ts.map +1 -0
  188. package/dist/schema/nodes/liquid-line.js +24 -0
  189. package/dist/schema/nodes/measurement.d.ts +306 -0
  190. package/dist/schema/nodes/measurement.d.ts.map +1 -0
  191. package/dist/schema/nodes/measurement.js +87 -0
  192. package/dist/schema/nodes/measurement.test.d.ts +2 -0
  193. package/dist/schema/nodes/measurement.test.d.ts.map +1 -0
  194. package/dist/schema/nodes/measurement.test.js +139 -0
  195. package/dist/schema/nodes/pipe-fitting.d.ts +56 -0
  196. package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -0
  197. package/dist/schema/nodes/pipe-fitting.js +45 -0
  198. package/dist/schema/nodes/pipe-segment.d.ts +48 -0
  199. package/dist/schema/nodes/pipe-segment.d.ts.map +1 -0
  200. package/dist/schema/nodes/pipe-segment.js +36 -0
  201. package/dist/schema/nodes/pipe-trap.d.ts +43 -0
  202. package/dist/schema/nodes/pipe-trap.d.ts.map +1 -0
  203. package/dist/schema/nodes/pipe-trap.js +36 -0
  204. package/dist/schema/nodes/ridge-vent-defaults.test.d.ts +2 -0
  205. package/dist/schema/nodes/ridge-vent-defaults.test.d.ts.map +1 -0
  206. package/dist/schema/nodes/ridge-vent-defaults.test.js +205 -0
  207. package/dist/schema/nodes/ridge-vent.d.ts +82 -0
  208. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
  209. package/dist/schema/nodes/ridge-vent.js +248 -0
  210. package/dist/schema/nodes/roof-segment-shape.d.ts +90 -0
  211. package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -0
  212. package/dist/schema/nodes/roof-segment-shape.js +318 -0
  213. package/dist/schema/nodes/roof-segment-shape.test.d.ts +2 -0
  214. package/dist/schema/nodes/roof-segment-shape.test.d.ts.map +1 -0
  215. package/dist/schema/nodes/roof-segment-shape.test.js +175 -0
  216. package/dist/schema/nodes/roof-segment-surface.test.d.ts +2 -0
  217. package/dist/schema/nodes/roof-segment-surface.test.d.ts.map +1 -0
  218. package/dist/schema/nodes/roof-segment-surface.test.js +48 -0
  219. package/dist/schema/nodes/roof-segment-trim.test.d.ts +2 -0
  220. package/dist/schema/nodes/roof-segment-trim.test.d.ts.map +1 -0
  221. package/dist/schema/nodes/roof-segment-trim.test.js +107 -0
  222. package/dist/schema/nodes/roof-segment-walls.d.ts +101 -0
  223. package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -0
  224. package/dist/schema/nodes/roof-segment-walls.js +330 -0
  225. package/dist/schema/nodes/roof-segment-walls.test.d.ts +2 -0
  226. package/dist/schema/nodes/roof-segment-walls.test.d.ts.map +1 -0
  227. package/dist/schema/nodes/roof-segment-walls.test.js +92 -0
  228. package/dist/schema/nodes/roof-segment.d.ts +273 -3
  229. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  230. package/dist/schema/nodes/roof-segment.js +539 -3
  231. package/dist/schema/nodes/roof.d.ts +8 -8
  232. package/dist/schema/nodes/roof.d.ts.map +1 -1
  233. package/dist/schema/nodes/roof.js +4 -16
  234. package/dist/schema/nodes/shelf.d.ts +106 -0
  235. package/dist/schema/nodes/shelf.d.ts.map +1 -0
  236. package/dist/schema/nodes/shelf.js +90 -0
  237. package/dist/schema/nodes/site.d.ts +1 -122
  238. package/dist/schema/nodes/site.d.ts.map +1 -1
  239. package/dist/schema/nodes/site.js +2 -6
  240. package/dist/schema/nodes/skylight.d.ts +279 -0
  241. package/dist/schema/nodes/skylight.d.ts.map +1 -0
  242. package/dist/schema/nodes/skylight.js +152 -0
  243. package/dist/schema/nodes/slab.d.ts +5 -2
  244. package/dist/schema/nodes/slab.d.ts.map +1 -1
  245. package/dist/schema/nodes/slab.js +5 -1
  246. package/dist/schema/nodes/solar-panel.d.ts +117 -0
  247. package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
  248. package/dist/schema/nodes/solar-panel.js +47 -0
  249. package/dist/schema/nodes/stair-segment.d.ts +4 -4
  250. package/dist/schema/nodes/stair.d.ts +13 -12
  251. package/dist/schema/nodes/stair.d.ts.map +1 -1
  252. package/dist/schema/nodes/stair.js +3 -0
  253. package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
  254. package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
  255. package/dist/schema/nodes/surface-hole-metadata.js +3 -1
  256. package/dist/schema/nodes/turbine-vent.d.ts +68 -0
  257. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
  258. package/dist/schema/nodes/turbine-vent.js +41 -0
  259. package/dist/schema/nodes/wall.d.ts +245 -6
  260. package/dist/schema/nodes/wall.d.ts.map +1 -1
  261. package/dist/schema/nodes/wall.js +257 -0
  262. package/dist/schema/nodes/wall.test.d.ts +2 -0
  263. package/dist/schema/nodes/wall.test.d.ts.map +1 -0
  264. package/dist/schema/nodes/wall.test.js +208 -0
  265. package/dist/schema/nodes/window.d.ts +11 -3
  266. package/dist/schema/nodes/window.d.ts.map +1 -1
  267. package/dist/schema/nodes/window.js +12 -0
  268. package/dist/schema/nodes/zone.d.ts +2 -0
  269. package/dist/schema/nodes/zone.d.ts.map +1 -1
  270. package/dist/schema/nodes/zone.js +6 -0
  271. package/dist/schema/scene-material.d.ts +41 -0
  272. package/dist/schema/scene-material.d.ts.map +1 -0
  273. package/dist/schema/scene-material.js +9 -0
  274. package/dist/schema/types.d.ts +2850 -655
  275. package/dist/schema/types.d.ts.map +1 -1
  276. package/dist/schema/types.js +49 -0
  277. package/dist/services/alignment-anchors.d.ts +100 -0
  278. package/dist/services/alignment-anchors.d.ts.map +1 -0
  279. package/dist/services/alignment-anchors.js +312 -0
  280. package/dist/services/alignment-anchors.test.d.ts +2 -0
  281. package/dist/services/alignment-anchors.test.d.ts.map +1 -0
  282. package/dist/services/alignment-anchors.test.js +383 -0
  283. package/dist/services/alignment.d.ts +131 -0
  284. package/dist/services/alignment.d.ts.map +1 -0
  285. package/dist/services/alignment.js +171 -0
  286. package/dist/services/alignment.test.d.ts +2 -0
  287. package/dist/services/alignment.test.d.ts.map +1 -0
  288. package/dist/services/alignment.test.js +92 -0
  289. package/dist/services/drag-session.d.ts +47 -0
  290. package/dist/services/drag-session.d.ts.map +1 -0
  291. package/dist/services/drag-session.js +100 -0
  292. package/dist/services/drag-session.test.d.ts +2 -0
  293. package/dist/services/drag-session.test.d.ts.map +1 -0
  294. package/dist/services/drag-session.test.js +219 -0
  295. package/dist/services/hosting.d.ts +88 -0
  296. package/dist/services/hosting.d.ts.map +1 -0
  297. package/dist/services/hosting.js +131 -0
  298. package/dist/services/hosting.test.d.ts +2 -0
  299. package/dist/services/hosting.test.d.ts.map +1 -0
  300. package/dist/services/hosting.test.js +230 -0
  301. package/dist/services/index.d.ts +13 -0
  302. package/dist/services/index.d.ts.map +1 -0
  303. package/dist/services/index.js +12 -0
  304. package/dist/services/level-height.d.ts +28 -0
  305. package/dist/services/level-height.d.ts.map +1 -0
  306. package/dist/services/level-height.js +62 -0
  307. package/dist/services/movement.d.ts +48 -0
  308. package/dist/services/movement.d.ts.map +1 -0
  309. package/dist/services/movement.js +65 -0
  310. package/dist/services/movement.test.d.ts +2 -0
  311. package/dist/services/movement.test.d.ts.map +1 -0
  312. package/dist/services/movement.test.js +133 -0
  313. package/dist/services/opening-guides.d.ts +141 -0
  314. package/dist/services/opening-guides.d.ts.map +1 -0
  315. package/dist/services/opening-guides.js +267 -0
  316. package/dist/services/opening-guides.test.d.ts +2 -0
  317. package/dist/services/opening-guides.test.d.ts.map +1 -0
  318. package/dist/services/opening-guides.test.js +208 -0
  319. package/dist/services/port-connectivity.d.ts +115 -0
  320. package/dist/services/port-connectivity.d.ts.map +1 -0
  321. package/dist/services/port-connectivity.js +300 -0
  322. package/dist/services/port-connectivity.test.d.ts +2 -0
  323. package/dist/services/port-connectivity.test.d.ts.map +1 -0
  324. package/dist/services/port-connectivity.test.js +297 -0
  325. package/dist/services/riser-diagram.d.ts +40 -0
  326. package/dist/services/riser-diagram.d.ts.map +1 -0
  327. package/dist/services/riser-diagram.js +110 -0
  328. package/dist/services/riser-diagram.test.d.ts +2 -0
  329. package/dist/services/riser-diagram.test.d.ts.map +1 -0
  330. package/dist/services/riser-diagram.test.js +66 -0
  331. package/dist/services/single-undo-dance.test.d.ts +2 -0
  332. package/dist/services/single-undo-dance.test.d.ts.map +1 -0
  333. package/dist/services/single-undo-dance.test.js +210 -0
  334. package/dist/services/snap.d.ts +80 -0
  335. package/dist/services/snap.d.ts.map +1 -0
  336. package/dist/services/snap.js +140 -0
  337. package/dist/services/snap.test.d.ts +2 -0
  338. package/dist/services/snap.test.d.ts.map +1 -0
  339. package/dist/services/snap.test.js +137 -0
  340. package/dist/services/system-graph.d.ts +30 -0
  341. package/dist/services/system-graph.d.ts.map +1 -0
  342. package/dist/services/system-graph.js +164 -0
  343. package/dist/services/system-graph.test.d.ts +2 -0
  344. package/dist/services/system-graph.test.d.ts.map +1 -0
  345. package/dist/services/system-graph.test.js +138 -0
  346. package/dist/services/validate-dwv.d.ts +32 -0
  347. package/dist/services/validate-dwv.d.ts.map +1 -0
  348. package/dist/services/validate-dwv.js +118 -0
  349. package/dist/services/validate-dwv.test.d.ts +2 -0
  350. package/dist/services/validate-dwv.test.d.ts.map +1 -0
  351. package/dist/services/validate-dwv.test.js +109 -0
  352. package/dist/solar-panel-presets.d.ts +17 -0
  353. package/dist/solar-panel-presets.d.ts.map +1 -0
  354. package/dist/solar-panel-presets.js +43 -0
  355. package/dist/store/actions/node-actions.d.ts +14 -2
  356. package/dist/store/actions/node-actions.d.ts.map +1 -1
  357. package/dist/store/actions/node-actions.js +585 -15
  358. package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
  359. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
  360. package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
  361. package/dist/store/actions/reparent.test.d.ts +2 -0
  362. package/dist/store/actions/reparent.test.d.ts.map +1 -0
  363. package/dist/store/actions/reparent.test.js +212 -0
  364. package/dist/store/actions/ridge-vent-update.test.d.ts +2 -0
  365. package/dist/store/actions/ridge-vent-update.test.d.ts.map +1 -0
  366. package/dist/store/actions/ridge-vent-update.test.js +264 -0
  367. package/dist/store/history-control.d.ts +11 -0
  368. package/dist/store/history-control.d.ts.map +1 -1
  369. package/dist/store/history-control.js +33 -0
  370. package/dist/store/use-interactive.d.ts +39 -0
  371. package/dist/store/use-interactive.d.ts.map +1 -1
  372. package/dist/store/use-interactive.js +75 -0
  373. package/dist/store/use-live-node-overrides.d.ts +22 -0
  374. package/dist/store/use-live-node-overrides.d.ts.map +1 -0
  375. package/dist/store/use-live-node-overrides.js +59 -0
  376. package/dist/store/use-scene-dirty-tracking.test.d.ts +2 -0
  377. package/dist/store/use-scene-dirty-tracking.test.d.ts.map +1 -0
  378. package/dist/store/use-scene-dirty-tracking.test.js +68 -0
  379. package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
  380. package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
  381. package/dist/store/use-scene-elevator-migration.test.js +109 -0
  382. package/dist/store/use-scene-measurement-roundtrip.test.d.ts +2 -0
  383. package/dist/store/use-scene-measurement-roundtrip.test.d.ts.map +1 -0
  384. package/dist/store/use-scene-measurement-roundtrip.test.js +97 -0
  385. package/dist/store/use-scene-plugins.test.d.ts +2 -0
  386. package/dist/store/use-scene-plugins.test.d.ts.map +1 -0
  387. package/dist/store/use-scene-plugins.test.js +51 -0
  388. package/dist/store/use-scene-wall-slot-migration.test.d.ts +2 -0
  389. package/dist/store/use-scene-wall-slot-migration.test.d.ts.map +1 -0
  390. package/dist/store/use-scene-wall-slot-migration.test.js +210 -0
  391. package/dist/store/use-scene-window-migration.test.d.ts +2 -0
  392. package/dist/store/use-scene-window-migration.test.d.ts.map +1 -0
  393. package/dist/store/use-scene-window-migration.test.js +85 -0
  394. package/dist/store/use-scene.d.ts +28 -2
  395. package/dist/store/use-scene.d.ts.map +1 -1
  396. package/dist/store/use-scene.js +546 -49
  397. package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
  398. package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
  399. package/dist/systems/elevator/elevator-dispatch.js +57 -0
  400. package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
  401. package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
  402. package/dist/systems/elevator/elevator-geometry.js +50 -0
  403. package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
  404. package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
  405. package/dist/systems/elevator/elevator-opening-sync.js +218 -0
  406. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
  407. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
  408. package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
  409. package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
  410. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
  411. package/dist/systems/elevator/elevator-opening-system.js +48 -0
  412. package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
  413. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
  414. package/dist/systems/elevator/elevator-runtime-system.js +8 -0
  415. package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
  416. package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
  417. package/dist/systems/elevator/elevator-runtime.js +225 -0
  418. package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
  419. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
  420. package/dist/systems/elevator/elevator-runtime.test.js +64 -0
  421. package/dist/systems/elevator/elevator-service.d.ts +19 -0
  422. package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
  423. package/dist/systems/elevator/elevator-service.js +111 -0
  424. package/dist/systems/fence/fence-centerline.d.ts +12 -0
  425. package/dist/systems/fence/fence-centerline.d.ts.map +1 -0
  426. package/dist/systems/fence/fence-centerline.js +37 -0
  427. package/dist/systems/fence/fence-spline.d.ts +39 -0
  428. package/dist/systems/fence/fence-spline.d.ts.map +1 -0
  429. package/dist/systems/fence/fence-spline.js +196 -0
  430. package/dist/systems/fence/fence-spline.test.d.ts +2 -0
  431. package/dist/systems/fence/fence-spline.test.d.ts.map +1 -0
  432. package/dist/systems/fence/fence-spline.test.js +136 -0
  433. package/dist/systems/stair/stair-footprint.d.ts +44 -0
  434. package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
  435. package/dist/systems/stair/stair-footprint.js +178 -0
  436. package/dist/systems/stair/stair-opening-preview.d.ts +2447 -0
  437. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
  438. package/dist/systems/stair/stair-opening-preview.js +93 -0
  439. package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
  440. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
  441. package/dist/systems/stair/stair-opening-preview.test.js +80 -0
  442. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  443. package/dist/systems/stair/stair-opening-sync.js +155 -147
  444. package/dist/systems/stair/stair-opening-sync.test.js +424 -2
  445. package/dist/systems/stair/stair-opening-system.d.ts +2 -0
  446. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
  447. package/dist/systems/stair/stair-opening-system.js +104 -0
  448. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  449. package/dist/systems/wall/wall-mitering.js +38 -2
  450. package/dist/systems/wall/wall-mitering.test.d.ts +2 -0
  451. package/dist/systems/wall/wall-mitering.test.d.ts.map +1 -0
  452. package/dist/systems/wall/wall-mitering.test.js +63 -0
  453. package/dist/systems/wall/wall-move.d.ts +48 -0
  454. package/dist/systems/wall/wall-move.d.ts.map +1 -0
  455. package/dist/systems/wall/wall-move.js +193 -0
  456. package/dist/utils/clone-scene-graph.d.ts +7 -4
  457. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  458. package/dist/utils/clone-scene-graph.js +29 -7
  459. package/dist/utils/clone-scene-graph.test.d.ts +2 -0
  460. package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
  461. package/dist/utils/clone-scene-graph.test.js +62 -0
  462. package/dist/utils/heal-scene-graph.d.ts +18 -0
  463. package/dist/utils/heal-scene-graph.d.ts.map +1 -0
  464. package/dist/utils/heal-scene-graph.js +90 -0
  465. package/dist/utils/heal-scene-graph.test.d.ts +2 -0
  466. package/dist/utils/heal-scene-graph.test.d.ts.map +1 -0
  467. package/dist/utils/heal-scene-graph.test.js +88 -0
  468. package/dist/validation/validate-build-json.d.ts +44 -0
  469. package/dist/validation/validate-build-json.d.ts.map +1 -0
  470. package/dist/validation/validate-build-json.js +256 -0
  471. package/package.json +11 -4
@@ -0,0 +1,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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=system-graph.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-graph.test.d.ts","sourceRoot":"","sources":["../../src/services/system-graph.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,138 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { registerNode } from '../registry';
3
+ import { buildPortComponents, summarizeSystemFor } from './system-graph';
4
+ // Stub registrations: the graph consults `def.ports` for the connectivity
5
+ // graph and `def.distributionRole` to classify each node. Mirrors the real
6
+ // kinds' port + role conventions (duct runs expose start/end, equipment a
7
+ // supply collar, terminals one collar) without importing the nodes package.
8
+ function stubDef(kind, distributionRole, ports) {
9
+ registerNode({
10
+ kind,
11
+ schemaVersion: 1,
12
+ schema: {},
13
+ category: 'utility',
14
+ distributionRole,
15
+ defaults: () => ({}),
16
+ capabilities: {},
17
+ ports,
18
+ });
19
+ }
20
+ stubDef('duct-segment', 'run', (node) => {
21
+ const path = node.path;
22
+ const system = node.system;
23
+ return [
24
+ { id: 'start', position: path[0], direction: [-1, 0, 0], diameter: 6, system },
25
+ {
26
+ id: 'end',
27
+ position: path[path.length - 1],
28
+ direction: [1, 0, 0],
29
+ diameter: 6,
30
+ system,
31
+ },
32
+ ];
33
+ });
34
+ stubDef('hvac-equipment', 'equipment', (node) => {
35
+ const position = node.position;
36
+ return [{ id: 'supply', position, direction: [0, 1, 0], diameter: 12, system: 'supply' }];
37
+ });
38
+ stubDef('duct-terminal', 'terminal', (node) => {
39
+ const position = node.position;
40
+ return [{ id: 'collar', position, direction: [0, -1, 0], diameter: 6, system: 'supply' }];
41
+ });
42
+ let nextId = 0;
43
+ function makeNode(type, fields) {
44
+ nextId += 1;
45
+ return { id: `${type}_${nextId}`, type, object: 'node', parentId: null, ...fields };
46
+ }
47
+ function sceneOf(...nodes) {
48
+ return Object.fromEntries(nodes.map((n) => [n.id, n]));
49
+ }
50
+ function run(path, system = 'supply') {
51
+ return makeNode('duct-segment', { path, system, diameter: 6 });
52
+ }
53
+ describe('buildPortComponents', () => {
54
+ test('chained runs land in one component; a distant run is separate', () => {
55
+ const a = run([
56
+ [0, 0, 0],
57
+ [3, 0, 0],
58
+ ]);
59
+ const b = run([
60
+ [3, 0, 0],
61
+ [3, 0, 4],
62
+ ]); // shares a's end
63
+ const c = run([
64
+ [20, 0, 0],
65
+ [24, 0, 0],
66
+ ]); // far away
67
+ const components = buildPortComponents(sceneOf(a, b, c));
68
+ expect(components.length).toBe(2);
69
+ const joined = components.find((g) => g.length === 2);
70
+ expect(new Set(joined)).toEqual(new Set([a.id, b.id]));
71
+ });
72
+ test('joints within tolerance still join; outside do not', () => {
73
+ const a = run([
74
+ [0, 0, 0],
75
+ [3, 0, 0],
76
+ ]);
77
+ const near = run([
78
+ [3.03, 0, 0],
79
+ [6, 0, 0],
80
+ ]); // 3 cm — joined
81
+ const far = run([
82
+ [3.2, 0, 4],
83
+ [6, 0, 4],
84
+ ]); // 20 cm in another row — separate
85
+ const components = buildPortComponents(sceneOf(a, near, far));
86
+ expect(components.length).toBe(2);
87
+ });
88
+ test('nodes without ports do not participate', () => {
89
+ const wall = makeNode('wall', {});
90
+ const a = run([
91
+ [0, 0, 0],
92
+ [3, 0, 0],
93
+ ]);
94
+ const components = buildPortComponents(sceneOf(wall, a));
95
+ expect(components.length).toBe(1);
96
+ expect(components[0]).toEqual([a.id]);
97
+ });
98
+ });
99
+ describe('summarizeSystemFor', () => {
100
+ test('full tree: equipment → run → terminal, stats add up', () => {
101
+ const furnace = makeNode('hvac-equipment', { position: [0, 0, 0] });
102
+ const trunk = run([
103
+ [0, 0, 0],
104
+ [4, 0, 0],
105
+ ]);
106
+ const branch = run([
107
+ [4, 0, 0],
108
+ [4, 0, 3],
109
+ ]);
110
+ const register = makeNode('duct-terminal', {
111
+ position: [4, 0, 3],
112
+ terminalType: 'supply-register',
113
+ });
114
+ const scene = sceneOf(furnace, trunk, branch, register);
115
+ const summary = summarizeSystemFor(register.id, scene);
116
+ expect(summary.nodeIds.length).toBe(4);
117
+ expect(summary.connectedToEquipment).toBe(true);
118
+ expect(summary.runCount).toBe(2);
119
+ expect(summary.runLengthM).toBeCloseTo(7, 6);
120
+ expect(summary.terminalCount).toBe(1);
121
+ expect(summary.equipmentCount).toBe(1);
122
+ expect(summary.systems).toEqual(['supply']);
123
+ });
124
+ test('orphaned run reports no equipment', () => {
125
+ const lonely = run([
126
+ [10, 0, 10],
127
+ [14, 0, 10],
128
+ ]);
129
+ const summary = summarizeSystemFor(lonely.id, sceneOf(lonely));
130
+ expect(summary.connectedToEquipment).toBe(false);
131
+ expect(summary.runCount).toBe(1);
132
+ expect(summary.runLengthM).toBeCloseTo(4, 6);
133
+ });
134
+ test('port-less node → null', () => {
135
+ const wall = makeNode('wall', {});
136
+ expect(summarizeSystemFor(wall.id, sceneOf(wall))).toBeNull();
137
+ });
138
+ });
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=validate-dwv.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-dwv.test.d.ts","sourceRoot":"","sources":["../../src/services/validate-dwv.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,109 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { registerNode } from '../registry';
3
+ import { validateDwv } from './validate-dwv';
4
+ // The validator reads node fields directly + buildPortComponents (which
5
+ // consults def.ports), so register stub port-providers for the DWV kinds
6
+ // it groups by. Mirrors the system-graph test's approach.
7
+ function stubDef(kind, ports) {
8
+ registerNode({
9
+ kind,
10
+ schemaVersion: 1,
11
+ schema: {},
12
+ category: 'utility',
13
+ defaults: () => ({}),
14
+ capabilities: {},
15
+ ports,
16
+ });
17
+ }
18
+ stubDef('pipe-segment', (node) => {
19
+ const path = node.path;
20
+ const diameter = node.diameter;
21
+ const system = node.system;
22
+ return [
23
+ { id: 'start', position: path[0], direction: [-1, 0, 0], diameter, system },
24
+ {
25
+ id: 'end',
26
+ position: path[path.length - 1],
27
+ direction: [1, 0, 0],
28
+ diameter,
29
+ system,
30
+ },
31
+ ];
32
+ });
33
+ stubDef('pipe-trap', (node) => {
34
+ const position = node.position;
35
+ return [{ id: 'inlet', position, direction: [0, 1, 0], diameter: 1.5, system: 'waste' }];
36
+ });
37
+ let nextId = 0;
38
+ function makeNode(type, fields) {
39
+ nextId += 1;
40
+ return { id: `${type}_${nextId}`, type, object: 'node', parentId: null, ...fields };
41
+ }
42
+ function sceneOf(...nodes) {
43
+ return Object.fromEntries(nodes.map((n) => [n.id, n]));
44
+ }
45
+ /** A waste run from a→b. Drop the end Y to slope it. */
46
+ function waste(path, diameter = 2) {
47
+ return makeNode('pipe-segment', { path, diameter, system: 'waste' });
48
+ }
49
+ const QUARTER_PER_FOOT = 1 / 48;
50
+ describe('validateDwv — slope', () => {
51
+ test('flags a flat waste run', () => {
52
+ const run = waste([
53
+ [0, 0, 0],
54
+ [3, 0, 0], // dead level
55
+ ]);
56
+ const findings = validateDwv(sceneOf(run));
57
+ expect(findings.some((f) => f.code === 'slope-too-flat')).toBe(true);
58
+ });
59
+ test('passes a run sloped at quarter-inch per foot', () => {
60
+ const drop = 3 * QUARTER_PER_FOOT;
61
+ const run = waste([
62
+ [0, 0, 0],
63
+ [3, -drop, 0],
64
+ ]);
65
+ const findings = validateDwv(sceneOf(run));
66
+ expect(findings.some((f) => f.code === 'slope-too-flat')).toBe(false);
67
+ });
68
+ test('flags an over-steep run (siphoning risk)', () => {
69
+ // 2" pipe, max slope = 2/12 ≈ 0.167; drop 2m over 1m horizontal.
70
+ const run = waste([
71
+ [0, 0, 0],
72
+ [1, -2, 0],
73
+ ]);
74
+ const findings = validateDwv(sceneOf(run));
75
+ expect(findings.some((f) => f.code === 'slope-too-steep')).toBe(true);
76
+ });
77
+ test('ignores vents (level is fine)', () => {
78
+ const vent = makeNode('pipe-segment', {
79
+ path: [
80
+ [0, 0, 0],
81
+ [0, 3, 0],
82
+ ],
83
+ diameter: 2,
84
+ system: 'vent',
85
+ });
86
+ const findings = validateDwv(sceneOf(vent));
87
+ expect(findings.length).toBe(0);
88
+ });
89
+ });
90
+ describe('validateDwv — trap arm', () => {
91
+ test('flags an over-long trap arm', () => {
92
+ const trap = makeNode('pipe-trap', {
93
+ position: [0, 0, 0],
94
+ diameter: 1.5, // max arm 42in = 1.067m
95
+ armLengthM: 2, // way over
96
+ });
97
+ const findings = validateDwv(sceneOf(trap));
98
+ expect(findings.some((f) => f.code === 'trap-arm-too-long')).toBe(true);
99
+ });
100
+ test('passes a trap arm within the limit', () => {
101
+ const trap = makeNode('pipe-trap', {
102
+ position: [0, 0, 0],
103
+ diameter: 2, // max arm 60in = 1.524m
104
+ armLengthM: 1,
105
+ });
106
+ const findings = validateDwv(sceneOf(trap));
107
+ expect(findings.some((f) => f.code === 'trap-arm-too-long')).toBe(false);
108
+ });
109
+ });
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ export declare const SolarPanelPresetKey: z.ZodEnum<{
3
+ residential: "residential";
4
+ "residential-large": "residential-large";
5
+ compact: "compact";
6
+ frameless: "frameless";
7
+ }>;
8
+ export type SolarPanelPresetKey = z.infer<typeof SolarPanelPresetKey>;
9
+ export type SolarPanelPresetDims = {
10
+ panelWidth: number;
11
+ panelHeight: number;
12
+ frameThickness: number;
13
+ frameDepth: number;
14
+ };
15
+ export declare const SOLAR_PANEL_PRESETS: Record<SolarPanelPresetKey, SolarPanelPresetDims>;
16
+ export declare const SOLAR_PANEL_PRESET_LABELS: Record<SolarPanelPresetKey, string>;
17
+ //# sourceMappingURL=solar-panel-presets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solar-panel-presets.d.ts","sourceRoot":"","sources":["../src/solar-panel-presets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAOvB,eAAO,MAAM,mBAAmB;;;;;EAK9B,CAAA;AACF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAErE,MAAM,MAAM,oBAAoB,GAAG;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,mBAAmB,EAAE,oBAAoB,CAyBjF,CAAA;AAED,eAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAKzE,CAAA"}
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod';
2
+ // Vendor-neutral visual presets. Picking one writes the four `panelWidth`,
3
+ // `panelHeight`, `frameThickness`, `frameDepth` fields onto a SolarPanelNode.
4
+ // Manually editing any of those four fields clears the preset back to undefined
5
+ // ("Custom") so the schema's `panelTypePreset` always matches the dims.
6
+ export const SolarPanelPresetKey = z.enum([
7
+ 'residential',
8
+ 'residential-large',
9
+ 'compact',
10
+ 'frameless',
11
+ ]);
12
+ export const SOLAR_PANEL_PRESETS = {
13
+ residential: {
14
+ panelWidth: 1.0,
15
+ panelHeight: 1.65,
16
+ frameThickness: 0.04,
17
+ frameDepth: 0.04,
18
+ },
19
+ 'residential-large': {
20
+ panelWidth: 1.0,
21
+ panelHeight: 2.0,
22
+ frameThickness: 0.04,
23
+ frameDepth: 0.04,
24
+ },
25
+ compact: {
26
+ panelWidth: 0.83,
27
+ panelHeight: 1.0,
28
+ frameThickness: 0.03,
29
+ frameDepth: 0.035,
30
+ },
31
+ frameless: {
32
+ panelWidth: 1.0,
33
+ panelHeight: 1.65,
34
+ frameThickness: 0.005,
35
+ frameDepth: 0.025,
36
+ },
37
+ };
38
+ export const SOLAR_PANEL_PRESET_LABELS = {
39
+ residential: 'Residential',
40
+ 'residential-large': 'Residential Large',
41
+ compact: 'Compact',
42
+ frameless: 'Frameless',
43
+ };
@@ -1,12 +1,24 @@
1
1
  import { type AnyNode, type AnyNodeId } from '../../schema';
2
2
  import type { SceneState } from '../use-scene';
3
- export declare const createNodesAction: (set: (fn: (state: SceneState) => Partial<SceneState>) => void, get: () => SceneState, ops: {
3
+ type NodeCreateOp = {
4
4
  node: AnyNode;
5
5
  parentId?: AnyNodeId;
6
- }[]) => void;
6
+ };
7
+ type NodeUpdateOp = {
8
+ id: AnyNodeId;
9
+ data: Partial<AnyNode>;
10
+ };
11
+ type NodeDeleteOp = AnyNodeId;
12
+ export declare const createNodesAction: (set: (fn: (state: SceneState) => Partial<SceneState>) => void, get: () => SceneState, ops: NodeCreateOp[]) => void;
13
+ export declare const applyNodeChangesAction: (set: (fn: (state: SceneState) => Partial<SceneState>) => void, get: () => SceneState, changes: {
14
+ create?: NodeCreateOp[];
15
+ update?: NodeUpdateOp[];
16
+ delete?: NodeDeleteOp[];
17
+ }) => void;
7
18
  export declare const updateNodesAction: (set: (fn: (state: SceneState) => Partial<SceneState>) => void, get: () => SceneState, updates: {
8
19
  id: AnyNodeId;
9
20
  data: Partial<AnyNode>;
10
21
  }[]) => void;
11
22
  export declare const deleteNodesAction: (set: (fn: (state: SceneState) => Partial<SceneState>) => void, get: () => SceneState, ids: AnyNodeId[]) => void;
23
+ export {};
12
24
  //# sourceMappingURL=node-actions.d.ts.map
@@ -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,EAIf,MAAM,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AA+N9C,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;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,EAAE,SA+C/C,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,SA+DrD,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;AA0uB7B,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,SAqIjB,CAAA"}