@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,141 @@
1
+ /** An opening's footprint in its host wall's local frame. */
2
+ export type OpeningSpan = {
3
+ id: string;
4
+ /** Centre along the wall, measured from `wall.start` (m). */
5
+ centerS: number;
6
+ /** Along-wall extent (m). */
7
+ width: number;
8
+ /** Vertical centre above the wall base (floor at y=0) (m). */
9
+ centerY: number;
10
+ /** Vertical extent (m). */
11
+ height: number;
12
+ };
13
+ export type WallExtent = {
14
+ /** Wall length (m). */
15
+ length: number;
16
+ /** Wall height (m). */
17
+ height: number;
18
+ };
19
+ export type OpeningGuideTolerances = {
20
+ /** Max distance for an edge/centre to count as aligned with a neighbour (m). */
21
+ align: number;
22
+ /** Max difference between two gaps for them to count as equal (m). */
23
+ equalSpacing: number;
24
+ /** Gaps below this are treated as touching/overlap noise and ignored (m). */
25
+ minGap: number;
26
+ };
27
+ export declare const DEFAULT_OPENING_GUIDE_TOLERANCES: OpeningGuideTolerances;
28
+ /** Which along-wall feature of an opening a guide references. */
29
+ export type AlongWallFeature = 'left' | 'center' | 'right';
30
+ /** Which vertical feature of an opening a guide references. */
31
+ export type VerticalFeature = 'sill' | 'center' | 'top';
32
+ export type SillHeadGuide = {
33
+ /** Floor (y=0) → the opening's bottom edge (m). */
34
+ sill: number;
35
+ /** Wall-local y of the bottom edge. */
36
+ bottomY: number;
37
+ /** The opening's top edge → the wall top (m). */
38
+ head: number;
39
+ /** Wall-local y of the top edge. */
40
+ topY: number;
41
+ };
42
+ export type EdgeGap = {
43
+ side: 'left' | 'right';
44
+ /** Clearance along the wall (m). */
45
+ distance: number;
46
+ /** Wall-local s of the moving opening's edge. */
47
+ fromS: number;
48
+ /** Wall-local s of the neighbour edge / wall end. */
49
+ toS: number;
50
+ target: 'opening' | 'wall-start' | 'wall-end';
51
+ /** Set when `target === 'opening'`. */
52
+ targetId?: string;
53
+ };
54
+ export type AlongWallAlignment = {
55
+ /** Wall-local s the two features share. */
56
+ s: number;
57
+ movingFeature: AlongWallFeature;
58
+ targetId: string;
59
+ targetFeature: AlongWallFeature;
60
+ /** Delta to add to the moving opening's `centerS` to make them coincide. */
61
+ snap: number;
62
+ };
63
+ export type VerticalAlignment = {
64
+ /** Wall-local y the two features share. */
65
+ y: number;
66
+ movingFeature: VerticalFeature;
67
+ targetId: string;
68
+ targetFeature: VerticalFeature;
69
+ /** Delta to add to the moving opening's `centerY` to make them coincide. */
70
+ snap: number;
71
+ };
72
+ export type EqualSpacingRun = {
73
+ /** The repeated gap value (average of the run's gaps) (m). */
74
+ gap: number;
75
+ /** The equal-gap segments along the wall, in order (left → right). */
76
+ segments: {
77
+ fromS: number;
78
+ toS: number;
79
+ }[];
80
+ /** Participating opening ids, ordered along the wall, including the moving one. */
81
+ openingIds: string[];
82
+ };
83
+ export type OpeningGuides = {
84
+ sillHead: SillHeadGuide | null;
85
+ gaps: EdgeGap[];
86
+ alongWall: AlongWallAlignment | null;
87
+ vertical: VerticalAlignment | null;
88
+ equalSpacing: EqualSpacingRun | null;
89
+ };
90
+ export type OpeningGuideInput = {
91
+ moving: OpeningSpan;
92
+ /** Other openings on the SAME wall (the moving opening excluded). */
93
+ siblings: readonly OpeningSpan[];
94
+ wall: WallExtent;
95
+ /**
96
+ * Whether to compute vertical (sill/head/vertical-alignment) guides. True for
97
+ * windows; false for doors, which sit on the floor so their sill is always 0.
98
+ */
99
+ includeVertical: boolean;
100
+ tolerances?: Partial<OpeningGuideTolerances>;
101
+ };
102
+ /**
103
+ * Edge-to-edge clearance from the moving opening to the nearest neighbour on
104
+ * each side, falling back to the wall ends when there is no neighbour — the
105
+ * "how much wall is left here" reading. Returns 0–2 gaps (one per side); a side
106
+ * is omitted when its clearance is below `minGap` (the opening is flush against
107
+ * or overlapping that neighbour).
108
+ */
109
+ export declare function computeEdgeGaps(moving: OpeningSpan, siblings: readonly OpeningSpan[], wall: WallExtent, minGap: number): EdgeGap[];
110
+ /**
111
+ * The closest coincidence between any of the moving opening's edges/centre and
112
+ * any sibling's edges/centre along the wall, within `tolerance`. Edge-to-edge
113
+ * and centre-to-centre are weighed equally; the single closest pair wins
114
+ * (matching the one-guide-per-axis behaviour of the floor-plane resolver).
115
+ */
116
+ export declare function detectAlongWallAlignment(moving: OpeningSpan, siblings: readonly OpeningSpan[], tolerance: number): AlongWallAlignment | null;
117
+ /**
118
+ * The closest coincidence between the moving opening's sill/centre/top and any
119
+ * sibling's sill/centre/top, within `tolerance` — the "these two windows share
120
+ * a sill height" detector. Same single-best-match policy as the along-wall
121
+ * variant.
122
+ */
123
+ export declare function detectVerticalAlignment(moving: OpeningSpan, siblings: readonly OpeningSpan[], tolerance: number): VerticalAlignment | null;
124
+ /**
125
+ * Figma-style equal-spacing detection: order all openings along the wall, look
126
+ * at the clearances BETWEEN consecutive openings, and return the longest run of
127
+ * ≥2 consecutive gaps that are equal within `tolerance` and that the moving
128
+ * opening participates in (so the badges only appear while the drag is actually
129
+ * forming or extending a series). Returns null when no such run exists.
130
+ *
131
+ * Gaps below `minGap` (touching/overlapping openings) break a run — a row of
132
+ * flush openings is not "equally spaced".
133
+ */
134
+ export declare function detectEqualSpacing(allOpenings: readonly OpeningSpan[], movingId: string, tolerance: number, minGap: number): EqualSpacingRun | null;
135
+ /**
136
+ * Compute every proximity/alignment guide for the moving opening in one pass.
137
+ * Pure: feed it the moving opening's wall-local span, its same-wall siblings,
138
+ * and the wall extent; render the result in whichever view.
139
+ */
140
+ export declare function computeOpeningGuides(input: OpeningGuideInput): OpeningGuides;
141
+ //# sourceMappingURL=opening-guides.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opening-guides.d.ts","sourceRoot":"","sources":["../../src/services/opening-guides.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,267 @@
1
+ // Proximity / alignment guides for wall-hosted openings (doors, windows).
2
+ //
3
+ // Pure geometry over a single host wall's LOCAL frame — no Three.js, no scene
4
+ // store, no React — so it runs identically for the 3D viewport and the 2D
5
+ // floor plan and is unit-testable in isolation. Callers extract the spans from
6
+ // the scene graph (an opening's `position[0]` is its along-wall centre, its
7
+ // `position[1]` its vertical centre with the wall base at y=0) and feed them in;
8
+ // the renderers transform the returned wall-local coordinates back to world
9
+ // (3D) or plan (2D).
10
+ //
11
+ // What it produces, mirroring the affordances architects expect (and Figma's
12
+ // smart guides):
13
+ // - sill/head : a window's bottom edge → floor and top edge → wall top.
14
+ // - edge gaps : along-wall clearance to the nearest neighbour opening (or
15
+ // the wall end) on each side.
16
+ // - alongWall : the moving opening's edge/centre lining up with a
17
+ // neighbour's edge/centre along the wall.
18
+ // - vertical : two openings sharing a sill / head / vertical centre.
19
+ // - equalSpacing : a run of 3+ openings with (near-)equal gaps between them.
20
+ //
21
+ // Detection is passive — it reports what currently coincides within tolerance
22
+ // and the snap delta that would make it exact, leaving the snap decision to the
23
+ // caller's manipulation policy (grid vs. alignment vs. Shift bypass).
24
+ export const DEFAULT_OPENING_GUIDE_TOLERANCES = {
25
+ // Parity with the along-wall snap threshold (`ALONG_WALL_ALIGN_THRESHOLD_M`).
26
+ align: 0.08,
27
+ equalSpacing: 0.03,
28
+ minGap: 0.02,
29
+ };
30
+ const leftEdge = (s) => s.centerS - s.width / 2;
31
+ const rightEdge = (s) => s.centerS + s.width / 2;
32
+ const bottomEdge = (s) => s.centerY - s.height / 2;
33
+ const topEdge = (s) => s.centerY + s.height / 2;
34
+ function alongWallFeatureCoord(s, feature) {
35
+ if (feature === 'left')
36
+ return leftEdge(s);
37
+ if (feature === 'right')
38
+ return rightEdge(s);
39
+ return s.centerS;
40
+ }
41
+ function verticalFeatureCoord(s, feature) {
42
+ if (feature === 'sill')
43
+ return bottomEdge(s);
44
+ if (feature === 'top')
45
+ return topEdge(s);
46
+ return s.centerY;
47
+ }
48
+ const ALONG_WALL_FEATURES = ['left', 'center', 'right'];
49
+ const VERTICAL_FEATURES = ['sill', 'center', 'top'];
50
+ /**
51
+ * Edge-to-edge clearance from the moving opening to the nearest neighbour on
52
+ * each side, falling back to the wall ends when there is no neighbour — the
53
+ * "how much wall is left here" reading. Returns 0–2 gaps (one per side); a side
54
+ * is omitted when its clearance is below `minGap` (the opening is flush against
55
+ * or overlapping that neighbour).
56
+ */
57
+ export function computeEdgeGaps(moving, siblings, wall, minGap) {
58
+ const movingLeft = leftEdge(moving);
59
+ const movingRight = rightEdge(moving);
60
+ // A sibling that straddles one of the moving opening's edges is an OVERLAP,
61
+ // not a neighbour: there is no clearance on that side, and we must not fall
62
+ // back to the wall end (which would report a misleading distance measured
63
+ // "through" the overlapping opening).
64
+ const leftCrossed = siblings.some((s) => leftEdge(s) < movingLeft && rightEdge(s) > movingLeft);
65
+ const rightCrossed = siblings.some((s) => leftEdge(s) < movingRight && rightEdge(s) > movingRight);
66
+ let leftNeighbour = null;
67
+ let rightNeighbour = null;
68
+ for (const sib of siblings) {
69
+ const sibRight = rightEdge(sib);
70
+ const sibLeft = leftEdge(sib);
71
+ // Entirely to the left of the moving opening → candidate left neighbour.
72
+ if (sibRight <= movingLeft && (leftNeighbour === null || sibRight > leftNeighbour.s)) {
73
+ leftNeighbour = { s: sibRight, id: sib.id };
74
+ }
75
+ // Entirely to the right → candidate right neighbour.
76
+ if (sibLeft >= movingRight && (rightNeighbour === null || sibLeft < rightNeighbour.s)) {
77
+ rightNeighbour = { s: sibLeft, id: sib.id };
78
+ }
79
+ }
80
+ const gaps = [];
81
+ if (!leftCrossed) {
82
+ const leftToS = leftNeighbour ? leftNeighbour.s : 0;
83
+ const leftDistance = movingLeft - leftToS;
84
+ if (leftDistance >= minGap) {
85
+ gaps.push({
86
+ side: 'left',
87
+ distance: leftDistance,
88
+ fromS: movingLeft,
89
+ toS: leftToS,
90
+ target: leftNeighbour ? 'opening' : 'wall-start',
91
+ targetId: leftNeighbour?.id,
92
+ });
93
+ }
94
+ }
95
+ if (!rightCrossed) {
96
+ const rightToS = rightNeighbour ? rightNeighbour.s : wall.length;
97
+ const rightDistance = rightToS - movingRight;
98
+ if (rightDistance >= minGap) {
99
+ gaps.push({
100
+ side: 'right',
101
+ distance: rightDistance,
102
+ fromS: movingRight,
103
+ toS: rightToS,
104
+ target: rightNeighbour ? 'opening' : 'wall-end',
105
+ targetId: rightNeighbour?.id,
106
+ });
107
+ }
108
+ }
109
+ return gaps;
110
+ }
111
+ /**
112
+ * The closest coincidence between any of the moving opening's edges/centre and
113
+ * any sibling's edges/centre along the wall, within `tolerance`. Edge-to-edge
114
+ * and centre-to-centre are weighed equally; the single closest pair wins
115
+ * (matching the one-guide-per-axis behaviour of the floor-plane resolver).
116
+ */
117
+ export function detectAlongWallAlignment(moving, siblings, tolerance) {
118
+ let best = null;
119
+ let bestAbs = tolerance;
120
+ for (const movingFeature of ALONG_WALL_FEATURES) {
121
+ const movingCoord = alongWallFeatureCoord(moving, movingFeature);
122
+ for (const sib of siblings) {
123
+ if (sib.id === moving.id)
124
+ continue;
125
+ for (const targetFeature of ALONG_WALL_FEATURES) {
126
+ const targetCoord = alongWallFeatureCoord(sib, targetFeature);
127
+ const diff = targetCoord - movingCoord;
128
+ const abs = Math.abs(diff);
129
+ if (abs <= bestAbs && (best === null || abs < bestAbs)) {
130
+ bestAbs = abs;
131
+ best = {
132
+ s: targetCoord,
133
+ movingFeature,
134
+ targetId: sib.id,
135
+ targetFeature,
136
+ snap: diff,
137
+ };
138
+ }
139
+ }
140
+ }
141
+ }
142
+ return best;
143
+ }
144
+ /**
145
+ * The closest coincidence between the moving opening's sill/centre/top and any
146
+ * sibling's sill/centre/top, within `tolerance` — the "these two windows share
147
+ * a sill height" detector. Same single-best-match policy as the along-wall
148
+ * variant.
149
+ */
150
+ export function detectVerticalAlignment(moving, siblings, tolerance) {
151
+ let best = null;
152
+ let bestAbs = tolerance;
153
+ for (const movingFeature of VERTICAL_FEATURES) {
154
+ const movingCoord = verticalFeatureCoord(moving, movingFeature);
155
+ for (const sib of siblings) {
156
+ if (sib.id === moving.id)
157
+ continue;
158
+ for (const targetFeature of VERTICAL_FEATURES) {
159
+ const targetCoord = verticalFeatureCoord(sib, targetFeature);
160
+ const diff = targetCoord - movingCoord;
161
+ const abs = Math.abs(diff);
162
+ if (abs <= bestAbs && (best === null || abs < bestAbs)) {
163
+ bestAbs = abs;
164
+ best = {
165
+ y: targetCoord,
166
+ movingFeature,
167
+ targetId: sib.id,
168
+ targetFeature,
169
+ snap: diff,
170
+ };
171
+ }
172
+ }
173
+ }
174
+ }
175
+ return best;
176
+ }
177
+ /**
178
+ * Figma-style equal-spacing detection: order all openings along the wall, look
179
+ * at the clearances BETWEEN consecutive openings, and return the longest run of
180
+ * ≥2 consecutive gaps that are equal within `tolerance` and that the moving
181
+ * opening participates in (so the badges only appear while the drag is actually
182
+ * forming or extending a series). Returns null when no such run exists.
183
+ *
184
+ * Gaps below `minGap` (touching/overlapping openings) break a run — a row of
185
+ * flush openings is not "equally spaced".
186
+ */
187
+ export function detectEqualSpacing(allOpenings, movingId, tolerance, minGap) {
188
+ if (allOpenings.length < 3)
189
+ return null;
190
+ const sorted = [...allOpenings].sort((a, b) => a.centerS - b.centerS);
191
+ const movingIndex = sorted.findIndex((s) => s.id === movingId);
192
+ if (movingIndex < 0)
193
+ return null;
194
+ // Clearance between opening i and i+1.
195
+ const gaps = [];
196
+ for (let i = 0; i < sorted.length - 1; i++) {
197
+ const a = sorted[i];
198
+ const b = sorted[i + 1];
199
+ if (!a || !b)
200
+ continue;
201
+ const fromS = rightEdge(a);
202
+ const toS = leftEdge(b);
203
+ gaps.push({ value: toS - fromS, fromS, toS });
204
+ }
205
+ // Longest contiguous window of gaps that are (a) each ≥ minGap and (b)
206
+ // mutually equal within tolerance (window max − min ≤ tolerance), spanning at
207
+ // least 2 gaps and including the moving opening. Brute force over windows
208
+ // (openings per wall are few). A first-gap-anchored greedy scan is NOT
209
+ // equivalent: it drops a valid run that begins partway through a drifting
210
+ // sequence — e.g. gaps 1.00, 1.02, 1.04 with the moving opening at the end,
211
+ // where [1.02, 1.04] is a real run. On a length tie the leftmost window wins,
212
+ // for determinism.
213
+ let best = null;
214
+ for (let lo = 0; lo < gaps.length; lo++) {
215
+ let min = Number.POSITIVE_INFINITY;
216
+ let max = Number.NEGATIVE_INFINITY;
217
+ for (let hi = lo; hi < gaps.length; hi++) {
218
+ const gap = gaps[hi];
219
+ if (!gap || gap.value < minGap)
220
+ break; // a sub-minGap gap can't join a run
221
+ min = Math.min(min, gap.value);
222
+ max = Math.max(max, gap.value);
223
+ if (max - min > tolerance)
224
+ break; // extending only widens the spread
225
+ const gapCount = hi - lo + 1;
226
+ if (gapCount < 2)
227
+ continue;
228
+ const firstOpening = lo; // gap i sits between openings i and i+1
229
+ const lastOpening = hi + 1;
230
+ if (movingIndex < firstOpening || movingIndex > lastOpening)
231
+ continue;
232
+ if (best !== null && gapCount <= best.segments.length)
233
+ continue;
234
+ const windowGaps = gaps.slice(lo, hi + 1);
235
+ best = {
236
+ gap: windowGaps.reduce((sum, g) => sum + g.value, 0) / windowGaps.length,
237
+ segments: windowGaps.map((g) => ({ fromS: g.fromS, toS: g.toS })),
238
+ openingIds: sorted.slice(firstOpening, lastOpening + 1).map((s) => s.id),
239
+ };
240
+ }
241
+ }
242
+ return best;
243
+ }
244
+ /**
245
+ * Compute every proximity/alignment guide for the moving opening in one pass.
246
+ * Pure: feed it the moving opening's wall-local span, its same-wall siblings,
247
+ * and the wall extent; render the result in whichever view.
248
+ */
249
+ export function computeOpeningGuides(input) {
250
+ const tol = { ...DEFAULT_OPENING_GUIDE_TOLERANCES, ...input.tolerances };
251
+ const { moving, siblings, wall, includeVertical } = input;
252
+ const sillHead = includeVertical
253
+ ? {
254
+ sill: bottomEdge(moving),
255
+ bottomY: bottomEdge(moving),
256
+ head: wall.height - topEdge(moving),
257
+ topY: topEdge(moving),
258
+ }
259
+ : null;
260
+ return {
261
+ sillHead,
262
+ gaps: computeEdgeGaps(moving, siblings, wall, tol.minGap),
263
+ alongWall: detectAlongWallAlignment(moving, siblings, tol.align),
264
+ vertical: includeVertical ? detectVerticalAlignment(moving, siblings, tol.align) : null,
265
+ equalSpacing: detectEqualSpacing([moving, ...siblings], moving.id, tol.equalSpacing, tol.minGap),
266
+ };
267
+ }
@@ -0,0 +1,115 @@
1
+ import type { AnyNode, AnyNodeId } from '../schema';
2
+ /**
3
+ * Connectivity-aware editing for port-bearing distribution kinds
4
+ * (HVAC ductwork AND DWV plumbing).
5
+ *
6
+ * Two nodes are "connected" when a port of one coincides in space with a
7
+ * port of the other — exactly how the placement tools mate a fitting onto
8
+ * a duct end (they snap the fitting's collar onto the run's open port).
9
+ * This service reads that relationship back out so an edit to one node can
10
+ * carry its neighbours along.
11
+ *
12
+ * Pure logic: it asks each node for its ports via `def.ports` (level-local
13
+ * meters) and does arithmetic. No Three.js, no rendering — it lives in
14
+ * core and is consumed by the editor's move tool and the duct/pipe
15
+ * selection affordances alike.
16
+ *
17
+ * ## Propagation model
18
+ *
19
+ * The joint graph is snapshotted once at drag start (`analyzePortConnectivity`)
20
+ * and walked every frame (`resolveConnectivityUpdates`) given the moved node's
21
+ * live transform. Deltas flow outward from the moved node through coincident
22
+ * ports:
23
+ *
24
+ * - **Fitting** (rigid): a collar pushed by delta `d` translates the whole
25
+ * fitting by `d`; every other collar carries that same `d` onward.
26
+ * - **Run** (stretch + slide, never skew): an endpoint pushed by delta `d` is
27
+ * split against the run's own axis. The *parallel* part slides only that
28
+ * endpoint (the run lengthens / shortens); the *perpendicular* part
29
+ * translates the entire run (so its direction is preserved). The far
30
+ * endpoint therefore moves by just the perpendicular part, and that part
31
+ * propagates onward to whatever is mated to the far endpoint.
32
+ *
33
+ * Propagation walks the whole connected component so a joint stays welded all
34
+ * the way down the chain, with a visited guard so cycles (looped runs) and
35
+ * shared joints terminate. First-reached (shortest path) wins on a node
36
+ * reachable two ways.
37
+ */
38
+ type Point = readonly [number, number, number];
39
+ /** A node carried by the edit, plus the snapshot needed to revert it. Kept
40
+ * deliberately small: the move tools read only `kind` + `nodeId` and the
41
+ * matching start snapshot to revert before the single tracked commit. */
42
+ export type PortConnection = {
43
+ /** A fitting mated collar-to-collar: it translates rigidly. */
44
+ kind: 'rigid-node';
45
+ nodeId: AnyNodeId;
46
+ /** Node's `position` at edit-start. */
47
+ startPosition: Point;
48
+ } | {
49
+ /** A run whose endpoint(s) ride the edit: it stretches and/or
50
+ * translates, never skews. */
51
+ kind: 'run';
52
+ nodeId: AnyNodeId;
53
+ /** The run's full `path` at edit-start. */
54
+ startPath: Point[];
55
+ };
56
+ /** One node in the snapshotted joint graph (everything reachable from the
57
+ * moved node, excluding the moved node itself). */
58
+ type GraphNode = {
59
+ id: AnyNodeId;
60
+ role: 'run' | 'fitting';
61
+ ports: ReadonlyArray<{
62
+ id: string;
63
+ position: Point;
64
+ system?: string;
65
+ }>;
66
+ startPath?: Point[];
67
+ startPosition?: Point;
68
+ };
69
+ /** Who else sits on a given node's port, keyed `nodeId` → `portId` → mates. */
70
+ type Adjacency = Record<string, Record<string, Array<{
71
+ nodeId: AnyNodeId;
72
+ portId: string;
73
+ }>>>;
74
+ export type PortConnectivity = {
75
+ movedNodeId: AnyNodeId;
76
+ /** The moved node's port world positions at edit-start, keyed by port id —
77
+ * the reference each frame's delta is measured from. */
78
+ startMovedPorts: Record<string, Point>;
79
+ /** Reachable run/fitting nodes (excludes the moved node), keyed by id. */
80
+ graph: Record<string, GraphNode>;
81
+ /** Port coincidence edges across the moved node + every graph node. */
82
+ adjacency: Adjacency;
83
+ /** Flat list of carried nodes for the move tools' revert + "anything to
84
+ * follow?" check. Derived from `graph`. */
85
+ connections: PortConnection[];
86
+ };
87
+ /**
88
+ * Snapshot the joint graph reachable from `movedNode`'s ports, taken at the
89
+ * start of a move/resize. Call once before the drag; feed the result to
90
+ * `resolveConnectivityUpdates` on every frame.
91
+ *
92
+ * Only `run`-role partners (segments) and `fitting`-role partners are walked —
93
+ * terminals and equipment usually mount to a surface and shouldn't be yanked
94
+ * off it when an adjacent fitting nudges. Fittings that declare
95
+ * `portConnectivityFollow: false` are anchored fixtures (e.g. pipe-trap) and
96
+ * are skipped, so a connected run stretches against them instead.
97
+ */
98
+ export declare function analyzePortConnectivity(movedNode: AnyNode, nodes: Record<string, AnyNode>): PortConnectivity;
99
+ /**
100
+ * Given the moved node in its live (in-drag) transform, produce the patches
101
+ * that keep every connected node attached. `previewNode` is the moved node
102
+ * with its current drag position/rotation applied so its ports recompute.
103
+ *
104
+ * Walks the snapshotted graph, propagating each port delta outward: fittings
105
+ * translate rigidly, runs stretch along their axis and translate across it
106
+ * (never skew when driven from one end), and effective port movement carries on
107
+ * to neighbouring joints. Port-level output guards bound cycles while still
108
+ * allowing a looped/shared run to accept constraints at both endpoints.
109
+ */
110
+ export declare function resolveConnectivityUpdates(connectivity: PortConnectivity, previewNode: AnyNode): {
111
+ id: AnyNodeId;
112
+ data: Partial<AnyNode>;
113
+ }[];
114
+ export {};
115
+ //# sourceMappingURL=port-connectivity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-connectivity.d.ts","sourceRoot":"","sources":["../../src/services/port-connectivity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,KAAK,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAY9C;;0EAE0E;AAC1E,MAAM,MAAM,cAAc,GACtB;IACE,+DAA+D;IAC/D,IAAI,EAAE,YAAY,CAAA;IAClB,MAAM,EAAE,SAAS,CAAA;IACjB,uCAAuC;IACvC,aAAa,EAAE,KAAK,CAAA;CACrB,GACD;IACE;mCAC+B;IAC/B,IAAI,EAAE,KAAK,CAAA;IACX,MAAM,EAAE,SAAS,CAAA;IACjB,2CAA2C;IAC3C,SAAS,EAAE,KAAK,EAAE,CAAA;CACnB,CAAA;AAEL;oDACoD;AACpD,KAAK,SAAS,GAAG;IACf,EAAE,EAAE,SAAS,CAAA;IACb,IAAI,EAAE,KAAK,GAAG,SAAS,CAAA;IACvB,KAAK,EAAE,aAAa,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,KAAK,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtE,SAAS,CAAC,EAAE,KAAK,EAAE,CAAA;IACnB,aAAa,CAAC,EAAE,KAAK,CAAA;CACtB,CAAA;AAED,+EAA+E;AAC/E,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAAC,CAAA;AAE7F,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,SAAS,CAAA;IACtB;6DACyD;IACzD,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IACtC,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAChC,uEAAuE;IACvE,SAAS,EAAE,SAAS,CAAA;IACpB;gDAC4C;IAC5C,WAAW,EAAE,cAAc,EAAE,CAAA;CAC9B,CAAA;AAkCD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,OAAO,EAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,gBAAgB,CAuFlB;AA4GD;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,gBAAgB,EAC9B,WAAW,EAAE,OAAO,GACnB;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAAE,CA0F7C"}