@pascal-app/nodes 1.0.0-beta.5 → 1.0.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 (929) hide show
  1. package/LICENSE +21 -0
  2. package/dist/block/commands.d.ts +68 -0
  3. package/dist/block/commands.d.ts.map +1 -0
  4. package/dist/block/commands.js +1144 -0
  5. package/dist/block/contextual-help.d.ts +3 -0
  6. package/dist/block/contextual-help.d.ts.map +1 -0
  7. package/dist/block/contextual-help.js +38 -0
  8. package/dist/block/definition.d.ts +8 -0
  9. package/dist/block/definition.d.ts.map +1 -0
  10. package/dist/block/definition.js +126 -0
  11. package/dist/block/edit-session.d.ts +16 -0
  12. package/dist/block/edit-session.d.ts.map +1 -0
  13. package/dist/block/edit-session.js +32 -0
  14. package/dist/block/face-host.d.ts +3 -0
  15. package/dist/block/face-host.d.ts.map +1 -0
  16. package/dist/block/face-host.js +178 -0
  17. package/dist/block/floorplan.d.ts +3 -0
  18. package/dist/block/floorplan.d.ts.map +1 -0
  19. package/dist/block/floorplan.js +43 -0
  20. package/dist/block/geometry-snap.d.ts +18 -0
  21. package/dist/block/geometry-snap.d.ts.map +1 -0
  22. package/dist/block/geometry-snap.js +137 -0
  23. package/dist/block/geometry.d.ts +11 -0
  24. package/dist/block/geometry.d.ts.map +1 -0
  25. package/dist/block/geometry.js +146 -0
  26. package/dist/block/gesture-wheel.d.ts +8 -0
  27. package/dist/block/gesture-wheel.d.ts.map +1 -0
  28. package/dist/block/gesture-wheel.js +7 -0
  29. package/dist/block/interaction-sfx.d.ts +3 -0
  30. package/dist/block/interaction-sfx.d.ts.map +1 -0
  31. package/dist/block/interaction-sfx.js +16 -0
  32. package/dist/block/last-operation.d.ts +48 -0
  33. package/dist/block/last-operation.d.ts.map +1 -0
  34. package/dist/block/last-operation.js +120 -0
  35. package/dist/block/loop-cut-interaction.d.ts +5 -0
  36. package/dist/block/loop-cut-interaction.d.ts.map +1 -0
  37. package/dist/block/loop-cut-interaction.js +13 -0
  38. package/dist/block/material-slots.d.ts +61 -0
  39. package/dist/block/material-slots.d.ts.map +1 -0
  40. package/dist/block/material-slots.js +131 -0
  41. package/dist/block/modal-face-operation.d.ts +13 -0
  42. package/dist/block/modal-face-operation.d.ts.map +1 -0
  43. package/dist/block/modal-face-operation.js +40 -0
  44. package/dist/block/modal-session.d.ts +15 -0
  45. package/dist/block/modal-session.d.ts.map +1 -0
  46. package/dist/block/modal-session.js +40 -0
  47. package/dist/block/modal-transform.d.ts +30 -0
  48. package/dist/block/modal-transform.d.ts.map +1 -0
  49. package/dist/block/modal-transform.js +144 -0
  50. package/dist/block/paint.d.ts +3 -0
  51. package/dist/block/paint.d.ts.map +1 -0
  52. package/dist/block/paint.js +136 -0
  53. package/dist/block/panel.d.ts +2 -0
  54. package/dist/block/panel.d.ts.map +1 -0
  55. package/dist/block/panel.js +234 -0
  56. package/dist/block/parametrics.d.ts +4 -0
  57. package/dist/block/parametrics.d.ts.map +1 -0
  58. package/dist/block/parametrics.js +9 -0
  59. package/dist/block/preview.d.ts +6 -0
  60. package/dist/block/preview.d.ts.map +1 -0
  61. package/dist/block/preview.js +46 -0
  62. package/dist/block/rotation-drag.d.ts +5 -0
  63. package/dist/block/rotation-drag.d.ts.map +1 -0
  64. package/dist/block/rotation-drag.js +18 -0
  65. package/dist/block/schema.d.ts +2 -0
  66. package/dist/block/schema.d.ts.map +1 -0
  67. package/dist/block/schema.js +1 -0
  68. package/dist/block/selection-geometry.d.ts +9 -0
  69. package/dist/block/selection-geometry.d.ts.map +1 -0
  70. package/dist/block/selection-geometry.js +31 -0
  71. package/dist/block/selection-model.d.ts +17 -0
  72. package/dist/block/selection-model.d.ts.map +1 -0
  73. package/dist/block/selection-model.js +84 -0
  74. package/dist/block/selection.d.ts +4 -0
  75. package/dist/block/selection.d.ts.map +1 -0
  76. package/dist/block/selection.js +2570 -0
  77. package/dist/block/slots.d.ts +4 -0
  78. package/dist/block/slots.d.ts.map +1 -0
  79. package/dist/block/slots.js +17 -0
  80. package/dist/block/tool.d.ts +3 -0
  81. package/dist/block/tool.d.ts.map +1 -0
  82. package/dist/block/tool.js +174 -0
  83. package/dist/block/toolbar-state.d.ts +46 -0
  84. package/dist/block/toolbar-state.d.ts.map +1 -0
  85. package/dist/block/toolbar-state.js +67 -0
  86. package/dist/block/use-block-face-operation.d.ts +39 -0
  87. package/dist/block/use-block-face-operation.d.ts.map +1 -0
  88. package/dist/block/use-block-face-operation.js +218 -0
  89. package/dist/box-vent/definition.d.ts.map +1 -1
  90. package/dist/box-vent/definition.js +8 -5
  91. package/dist/box-vent/geometry.d.ts +4 -0
  92. package/dist/box-vent/geometry.d.ts.map +1 -1
  93. package/dist/box-vent/geometry.js +76 -44
  94. package/dist/box-vent/paint.d.ts +4 -0
  95. package/dist/box-vent/paint.d.ts.map +1 -0
  96. package/dist/box-vent/paint.js +40 -0
  97. package/dist/box-vent/panel.js +4 -4
  98. package/dist/box-vent/parametrics.js +3 -3
  99. package/dist/box-vent/renderer.d.ts.map +1 -1
  100. package/dist/box-vent/renderer.js +29 -10
  101. package/dist/cabinet/appliance-layout.d.ts +26 -0
  102. package/dist/cabinet/appliance-layout.d.ts.map +1 -0
  103. package/dist/cabinet/appliance-layout.js +52 -0
  104. package/dist/cabinet/compartment-card.d.ts.map +1 -1
  105. package/dist/cabinet/compartment-card.js +11 -3
  106. package/dist/cabinet/continuous-placement.d.ts +3 -0
  107. package/dist/cabinet/continuous-placement.d.ts.map +1 -1
  108. package/dist/cabinet/continuous-placement.js +6 -0
  109. package/dist/cabinet/countertop-layout.d.ts +173 -0
  110. package/dist/cabinet/countertop-layout.d.ts.map +1 -0
  111. package/dist/cabinet/countertop-layout.js +80 -0
  112. package/dist/cabinet/definition.d.ts.map +1 -1
  113. package/dist/cabinet/definition.js +414 -61
  114. package/dist/cabinet/floorplan-move.d.ts.map +1 -1
  115. package/dist/cabinet/floorplan-move.js +91 -22
  116. package/dist/cabinet/geometry/cooktop.d.ts.map +1 -1
  117. package/dist/cabinet/geometry/cooktop.js +2 -2
  118. package/dist/cabinet/geometry/fridge.d.ts.map +1 -1
  119. package/dist/cabinet/geometry/fridge.js +25 -1
  120. package/dist/cabinet/geometry/fronts.d.ts +4 -0
  121. package/dist/cabinet/geometry/fronts.d.ts.map +1 -1
  122. package/dist/cabinet/geometry/fronts.js +1 -1
  123. package/dist/cabinet/geometry/run.d.ts.map +1 -1
  124. package/dist/cabinet/geometry/run.js +28 -71
  125. package/dist/cabinet/geometry/sink.d.ts +2 -12
  126. package/dist/cabinet/geometry/sink.d.ts.map +1 -1
  127. package/dist/cabinet/geometry/sink.js +8 -39
  128. package/dist/cabinet/geometry.d.ts.map +1 -1
  129. package/dist/cabinet/geometry.js +63 -1
  130. package/dist/cabinet/hosted-resize.d.ts +8 -0
  131. package/dist/cabinet/hosted-resize.d.ts.map +1 -0
  132. package/dist/cabinet/hosted-resize.js +158 -0
  133. package/dist/cabinet/index.d.ts +1 -0
  134. package/dist/cabinet/index.d.ts.map +1 -1
  135. package/dist/cabinet/index.js +1 -0
  136. package/dist/cabinet/insertion.d.ts +19 -0
  137. package/dist/cabinet/insertion.d.ts.map +1 -0
  138. package/dist/cabinet/insertion.js +49 -0
  139. package/dist/cabinet/move-frame.d.ts.map +1 -1
  140. package/dist/cabinet/move-frame.js +68 -2
  141. package/dist/cabinet/panel-context.d.ts +7 -0
  142. package/dist/cabinet/panel-context.d.ts.map +1 -0
  143. package/dist/cabinet/panel-context.js +16 -0
  144. package/dist/cabinet/panel-visibility.d.ts +9 -0
  145. package/dist/cabinet/panel-visibility.d.ts.map +1 -0
  146. package/dist/cabinet/panel-visibility.js +26 -0
  147. package/dist/cabinet/panel.d.ts.map +1 -1
  148. package/dist/cabinet/panel.js +221 -37
  149. package/dist/cabinet/placement-dimensions.d.ts +42 -0
  150. package/dist/cabinet/placement-dimensions.d.ts.map +1 -0
  151. package/dist/cabinet/placement-dimensions.js +227 -0
  152. package/dist/cabinet/placement-snap.d.ts +21 -1
  153. package/dist/cabinet/placement-snap.d.ts.map +1 -1
  154. package/dist/cabinet/placement-snap.js +48 -6
  155. package/dist/cabinet/preset-width-debt.d.ts +7 -0
  156. package/dist/cabinet/preset-width-debt.d.ts.map +1 -0
  157. package/dist/cabinet/preset-width-debt.js +45 -0
  158. package/dist/cabinet/presets.d.ts.map +1 -1
  159. package/dist/cabinet/presets.js +14 -13
  160. package/dist/cabinet/profiles.d.ts +14 -0
  161. package/dist/cabinet/profiles.d.ts.map +1 -0
  162. package/dist/cabinet/profiles.js +30 -0
  163. package/dist/cabinet/quick-actions.d.ts.map +1 -1
  164. package/dist/cabinet/quick-actions.js +8 -24
  165. package/dist/cabinet/reveals.d.ts +37 -0
  166. package/dist/cabinet/reveals.d.ts.map +1 -0
  167. package/dist/cabinet/reveals.js +13 -0
  168. package/dist/cabinet/run-layout.d.ts +98 -5
  169. package/dist/cabinet/run-layout.d.ts.map +1 -1
  170. package/dist/cabinet/run-layout.js +430 -37
  171. package/dist/cabinet/run-ops.d.ts +204 -12
  172. package/dist/cabinet/run-ops.d.ts.map +1 -1
  173. package/dist/cabinet/run-ops.js +523 -36
  174. package/dist/cabinet/run-panel.d.ts +6 -2
  175. package/dist/cabinet/run-panel.d.ts.map +1 -1
  176. package/dist/cabinet/run-panel.js +294 -108
  177. package/dist/cabinet/stack-transitions.d.ts.map +1 -1
  178. package/dist/cabinet/stack-transitions.js +35 -24
  179. package/dist/cabinet/stack.d.ts +169 -1
  180. package/dist/cabinet/stack.d.ts.map +1 -1
  181. package/dist/cabinet/stack.js +57 -18
  182. package/dist/cabinet/surfaces.d.ts +4 -0
  183. package/dist/cabinet/surfaces.d.ts.map +1 -0
  184. package/dist/cabinet/surfaces.js +101 -0
  185. package/dist/cabinet/tool.d.ts.map +1 -1
  186. package/dist/cabinet/tool.js +615 -91
  187. package/dist/cabinet/tree-structure.d.ts.map +1 -1
  188. package/dist/cabinet/tree-structure.js +10 -9
  189. package/dist/cabinet/validation.d.ts +27 -0
  190. package/dist/cabinet/validation.d.ts.map +1 -0
  191. package/dist/cabinet/validation.js +56 -0
  192. package/dist/cabinet/wall-height-presets.d.ts +12 -0
  193. package/dist/cabinet/wall-height-presets.d.ts.map +1 -0
  194. package/dist/cabinet/wall-height-presets.js +15 -0
  195. package/dist/cabinet/wall-snap.d.ts +27 -3
  196. package/dist/cabinet/wall-snap.d.ts.map +1 -1
  197. package/dist/cabinet/wall-snap.js +278 -62
  198. package/dist/cabinet/widths.d.ts +10 -0
  199. package/dist/cabinet/widths.d.ts.map +1 -0
  200. package/dist/cabinet/widths.js +13 -0
  201. package/dist/ceiling/definition.d.ts.map +1 -1
  202. package/dist/ceiling/definition.js +8 -2
  203. package/dist/ceiling/floorplan-affordances.d.ts +12 -12
  204. package/dist/ceiling/materials.d.ts.map +1 -1
  205. package/dist/ceiling/materials.js +0 -1
  206. package/dist/ceiling/panel.js +1 -1
  207. package/dist/ceiling/parametrics.js +1 -1
  208. package/dist/chimney/definition.js +1 -1
  209. package/dist/chimney/panel.js +9 -9
  210. package/dist/chimney/parametrics.js +3 -3
  211. package/dist/column/definition.d.ts.map +1 -1
  212. package/dist/column/definition.js +22 -4
  213. package/dist/column/panel.d.ts.map +1 -1
  214. package/dist/column/panel.js +37 -7
  215. package/dist/column/parametrics.d.ts.map +1 -1
  216. package/dist/column/parametrics.js +5 -3
  217. package/dist/column/tool.d.ts.map +1 -1
  218. package/dist/column/tool.js +50 -14
  219. package/dist/cupola/definition.d.ts.map +1 -1
  220. package/dist/cupola/definition.js +15 -6
  221. package/dist/cupola/geometry.d.ts +6 -0
  222. package/dist/cupola/geometry.d.ts.map +1 -1
  223. package/dist/cupola/geometry.js +60 -13
  224. package/dist/cupola/paint.d.ts +4 -0
  225. package/dist/cupola/paint.d.ts.map +1 -0
  226. package/dist/cupola/paint.js +39 -0
  227. package/dist/cupola/panel.js +1 -1
  228. package/dist/cupola/parametrics.js +3 -3
  229. package/dist/cupola/renderer.d.ts.map +1 -1
  230. package/dist/cupola/renderer.js +27 -8
  231. package/dist/door/floorplan-move.d.ts.map +1 -1
  232. package/dist/door/floorplan-move.js +1 -1
  233. package/dist/door/move-tool.d.ts.map +1 -1
  234. package/dist/door/move-tool.js +86 -32
  235. package/dist/door/panel.d.ts.map +1 -1
  236. package/dist/door/panel.js +12 -12
  237. package/dist/door/parametrics.js +2 -2
  238. package/dist/door/tool.d.ts.map +1 -1
  239. package/dist/door/tool.js +16 -4
  240. package/dist/dormer/csg-geometry.d.ts +6 -42
  241. package/dist/dormer/csg-geometry.d.ts.map +1 -1
  242. package/dist/dormer/csg-geometry.js +38 -209
  243. package/dist/dormer/definition.d.ts +4 -12
  244. package/dist/dormer/definition.d.ts.map +1 -1
  245. package/dist/dormer/definition.js +18 -193
  246. package/dist/dormer/floorplan.d.ts +3 -3
  247. package/dist/dormer/floorplan.d.ts.map +1 -1
  248. package/dist/dormer/floorplan.js +6 -17
  249. package/dist/dormer/geometry.d.ts +5 -24
  250. package/dist/dormer/geometry.d.ts.map +1 -1
  251. package/dist/dormer/geometry.js +64 -41
  252. package/dist/dormer/index.d.ts +1 -1
  253. package/dist/dormer/index.d.ts.map +1 -1
  254. package/dist/dormer/index.js +1 -1
  255. package/dist/dormer/move-tool.d.ts.map +1 -1
  256. package/dist/dormer/move-tool.js +31 -3
  257. package/dist/dormer/panel-position-section.js +2 -2
  258. package/dist/dormer/panel-windows-section.d.ts +9 -0
  259. package/dist/dormer/panel-windows-section.d.ts.map +1 -0
  260. package/dist/dormer/panel-windows-section.js +7 -0
  261. package/dist/dormer/panel.d.ts.map +1 -1
  262. package/dist/dormer/panel.js +100 -18
  263. package/dist/dormer/parametrics.d.ts.map +1 -1
  264. package/dist/dormer/parametrics.js +8 -77
  265. package/dist/dormer/preview.d.ts.map +1 -1
  266. package/dist/dormer/preview.js +9 -1
  267. package/dist/dormer/renderer.d.ts.map +1 -1
  268. package/dist/dormer/renderer.js +24 -26
  269. package/dist/dormer/tool.d.ts.map +1 -1
  270. package/dist/dormer/tool.js +3 -1
  271. package/dist/dormer/window-layout.d.ts +15 -0
  272. package/dist/dormer/window-layout.d.ts.map +1 -0
  273. package/dist/dormer/window-layout.js +59 -0
  274. package/dist/downspout/definition.d.ts.map +1 -1
  275. package/dist/downspout/definition.js +17 -4
  276. package/dist/downspout/geometry.d.ts.map +1 -1
  277. package/dist/downspout/geometry.js +10 -0
  278. package/dist/downspout/inspector-editors.d.ts.map +1 -1
  279. package/dist/downspout/inspector-editors.js +9 -1
  280. package/dist/downspout/parametrics.d.ts.map +1 -1
  281. package/dist/downspout/parametrics.js +2 -1
  282. package/dist/downspout/renderer.d.ts.map +1 -1
  283. package/dist/downspout/renderer.js +19 -3
  284. package/dist/downspout/system.d.ts +7 -0
  285. package/dist/downspout/system.d.ts.map +1 -0
  286. package/dist/downspout/system.js +114 -0
  287. package/dist/duct-fitting/accessory-geometry.d.ts +4 -0
  288. package/dist/duct-fitting/accessory-geometry.d.ts.map +1 -0
  289. package/dist/duct-fitting/accessory-geometry.js +47 -0
  290. package/dist/duct-fitting/definition.d.ts.map +1 -1
  291. package/dist/duct-fitting/definition.js +11 -1
  292. package/dist/duct-fitting/floorplan.d.ts.map +1 -1
  293. package/dist/duct-fitting/floorplan.js +8 -0
  294. package/dist/duct-fitting/geometry.d.ts.map +1 -1
  295. package/dist/duct-fitting/geometry.js +52 -38
  296. package/dist/duct-fitting/parametrics.d.ts.map +1 -1
  297. package/dist/duct-fitting/parametrics.js +78 -15
  298. package/dist/duct-fitting/ports.d.ts +1 -0
  299. package/dist/duct-fitting/ports.d.ts.map +1 -1
  300. package/dist/duct-fitting/ports.js +46 -0
  301. package/dist/duct-fitting/tool.d.ts +19 -0
  302. package/dist/duct-fitting/tool.d.ts.map +1 -1
  303. package/dist/duct-fitting/tool.js +126 -34
  304. package/dist/duct-segment/continuation.d.ts +21 -0
  305. package/dist/duct-segment/continuation.d.ts.map +1 -0
  306. package/dist/duct-segment/continuation.js +167 -0
  307. package/dist/duct-segment/definition.d.ts.map +1 -1
  308. package/dist/duct-segment/definition.js +19 -18
  309. package/dist/duct-segment/floorplan.d.ts.map +1 -1
  310. package/dist/duct-segment/floorplan.js +43 -0
  311. package/dist/duct-segment/geometry.d.ts.map +1 -1
  312. package/dist/duct-segment/geometry.js +3 -0
  313. package/dist/duct-segment/move-tool.d.ts.map +1 -1
  314. package/dist/duct-segment/move-tool.js +33 -6
  315. package/dist/duct-segment/parametrics.d.ts.map +1 -1
  316. package/dist/duct-segment/parametrics.js +49 -1
  317. package/dist/duct-segment/selection.d.ts +3 -4
  318. package/dist/duct-segment/selection.d.ts.map +1 -1
  319. package/dist/duct-segment/selection.js +186 -158
  320. package/dist/duct-segment/tool.d.ts +38 -0
  321. package/dist/duct-segment/tool.d.ts.map +1 -1
  322. package/dist/duct-segment/tool.js +306 -677
  323. package/dist/duct-terminal/parametrics.js +2 -2
  324. package/dist/duct-terminal/tool.d.ts.map +1 -1
  325. package/dist/duct-terminal/tool.js +37 -7
  326. package/dist/elevator/panel.d.ts.map +1 -1
  327. package/dist/elevator/panel.js +7 -7
  328. package/dist/elevator/system.d.ts.map +1 -1
  329. package/dist/elevator/system.js +10 -1
  330. package/dist/eyebrow-vent/definition.d.ts.map +1 -1
  331. package/dist/eyebrow-vent/definition.js +8 -5
  332. package/dist/eyebrow-vent/geometry.d.ts +4 -0
  333. package/dist/eyebrow-vent/geometry.d.ts.map +1 -1
  334. package/dist/eyebrow-vent/geometry.js +60 -15
  335. package/dist/eyebrow-vent/paint.d.ts +4 -0
  336. package/dist/eyebrow-vent/paint.d.ts.map +1 -0
  337. package/dist/eyebrow-vent/paint.js +33 -0
  338. package/dist/eyebrow-vent/panel.js +1 -1
  339. package/dist/eyebrow-vent/parametrics.js +3 -3
  340. package/dist/eyebrow-vent/renderer.d.ts.map +1 -1
  341. package/dist/eyebrow-vent/renderer.js +27 -8
  342. package/dist/fence/definition.d.ts.map +1 -1
  343. package/dist/fence/definition.js +2 -1
  344. package/dist/fence/floorplan-affordances.d.ts +1 -0
  345. package/dist/fence/floorplan-affordances.d.ts.map +1 -1
  346. package/dist/fence/floorplan-affordances.js +30 -1
  347. package/dist/fence/floorplan.d.ts.map +1 -1
  348. package/dist/fence/floorplan.js +14 -0
  349. package/dist/fence/inspector-editors.js +1 -1
  350. package/dist/fence/parametrics.js +3 -3
  351. package/dist/fence/tool.d.ts.map +1 -1
  352. package/dist/fence/tool.js +47 -20
  353. package/dist/gutter/corner-mitre.d.ts.map +1 -1
  354. package/dist/gutter/corner-mitre.js +47 -7
  355. package/dist/gutter/definition.d.ts.map +1 -1
  356. package/dist/gutter/definition.js +23 -13
  357. package/dist/gutter/eave-align.d.ts.map +1 -1
  358. package/dist/gutter/eave-align.js +10 -0
  359. package/dist/gutter/eave-snap.d.ts +1 -1
  360. package/dist/gutter/eave-snap.d.ts.map +1 -1
  361. package/dist/gutter/eave-snap.js +10 -23
  362. package/dist/gutter/geometry.d.ts.map +1 -1
  363. package/dist/gutter/geometry.js +126 -8
  364. package/dist/gutter/outlet-lookup.d.ts.map +1 -1
  365. package/dist/gutter/outlet-lookup.js +22 -1
  366. package/dist/gutter/paint.d.ts +3 -0
  367. package/dist/gutter/paint.d.ts.map +1 -0
  368. package/dist/gutter/paint.js +6 -0
  369. package/dist/gutter/parametrics.d.ts.map +1 -1
  370. package/dist/gutter/parametrics.js +4 -1
  371. package/dist/gutter/renderer.d.ts.map +1 -1
  372. package/dist/gutter/renderer.js +63 -7
  373. package/dist/gutter/trim-clip.d.ts +3 -0
  374. package/dist/gutter/trim-clip.d.ts.map +1 -0
  375. package/dist/gutter/trim-clip.js +5 -0
  376. package/dist/hvac-equipment/parametrics.js +3 -3
  377. package/dist/hvac-equipment/tool.d.ts.map +1 -1
  378. package/dist/hvac-equipment/tool.js +12 -2
  379. package/dist/imported-mesh/definition.d.ts +5 -0
  380. package/dist/imported-mesh/definition.d.ts.map +1 -0
  381. package/dist/imported-mesh/definition.js +38 -0
  382. package/dist/imported-mesh/floorplan.d.ts +4 -0
  383. package/dist/imported-mesh/floorplan.d.ts.map +1 -0
  384. package/dist/imported-mesh/floorplan.js +34 -0
  385. package/dist/imported-mesh/geometry.d.ts +5 -0
  386. package/dist/imported-mesh/geometry.d.ts.map +1 -0
  387. package/dist/imported-mesh/geometry.js +34 -0
  388. package/dist/imported-mesh/index.d.ts +4 -0
  389. package/dist/imported-mesh/index.d.ts.map +1 -0
  390. package/dist/imported-mesh/index.js +3 -0
  391. package/dist/imported-mesh/schema.d.ts +2 -0
  392. package/dist/imported-mesh/schema.d.ts.map +1 -0
  393. package/dist/imported-mesh/schema.js +1 -0
  394. package/dist/index.d.ts +7 -2
  395. package/dist/index.d.ts.map +1 -1
  396. package/dist/index.js +17 -2
  397. package/dist/item/definition.d.ts.map +1 -1
  398. package/dist/item/definition.js +10 -2
  399. package/dist/item/floorplan-move.d.ts +9 -1
  400. package/dist/item/floorplan-move.d.ts.map +1 -1
  401. package/dist/item/floorplan-move.js +74 -2
  402. package/dist/item/floorplan.d.ts.map +1 -1
  403. package/dist/item/floorplan.js +33 -1
  404. package/dist/item/move-tool.d.ts +28 -1
  405. package/dist/item/move-tool.d.ts.map +1 -1
  406. package/dist/item/move-tool.js +15 -3
  407. package/dist/item/panel.js +1 -1
  408. package/dist/item/renderer.d.ts.map +1 -1
  409. package/dist/item/renderer.js +16 -6
  410. package/dist/item/system.d.ts +5 -0
  411. package/dist/item/system.d.ts.map +1 -1
  412. package/dist/item/system.js +7 -1
  413. package/dist/lean-to-extension/arc.d.ts +14 -0
  414. package/dist/lean-to-extension/arc.d.ts.map +1 -0
  415. package/dist/lean-to-extension/arc.js +56 -0
  416. package/dist/lean-to-extension/assembly.d.ts +61 -0
  417. package/dist/lean-to-extension/assembly.d.ts.map +1 -0
  418. package/dist/lean-to-extension/assembly.js +761 -0
  419. package/dist/lean-to-extension/canopy-joint.d.ts +35 -0
  420. package/dist/lean-to-extension/canopy-joint.d.ts.map +1 -0
  421. package/dist/lean-to-extension/canopy-joint.js +234 -0
  422. package/dist/lean-to-extension/conical-host.d.ts +15 -0
  423. package/dist/lean-to-extension/conical-host.d.ts.map +1 -0
  424. package/dist/lean-to-extension/conical-host.js +113 -0
  425. package/dist/lean-to-extension/corner-joint.d.ts +29 -0
  426. package/dist/lean-to-extension/corner-joint.d.ts.map +1 -0
  427. package/dist/lean-to-extension/corner-joint.js +1202 -0
  428. package/dist/lean-to-extension/definition.d.ts +4 -0
  429. package/dist/lean-to-extension/definition.d.ts.map +1 -0
  430. package/dist/lean-to-extension/definition.js +433 -0
  431. package/dist/lean-to-extension/floorplan-affordances.d.ts +4 -0
  432. package/dist/lean-to-extension/floorplan-affordances.d.ts.map +1 -0
  433. package/dist/lean-to-extension/floorplan-affordances.js +144 -0
  434. package/dist/lean-to-extension/floorplan-move.d.ts +3 -0
  435. package/dist/lean-to-extension/floorplan-move.d.ts.map +1 -0
  436. package/dist/lean-to-extension/floorplan-move.js +157 -0
  437. package/dist/lean-to-extension/floorplan-tool.d.ts +4 -0
  438. package/dist/lean-to-extension/floorplan-tool.d.ts.map +1 -0
  439. package/dist/lean-to-extension/floorplan-tool.js +385 -0
  440. package/dist/lean-to-extension/floorplan.d.ts +3 -0
  441. package/dist/lean-to-extension/floorplan.d.ts.map +1 -0
  442. package/dist/lean-to-extension/floorplan.js +373 -0
  443. package/dist/lean-to-extension/geometry.d.ts +7 -0
  444. package/dist/lean-to-extension/geometry.d.ts.map +1 -0
  445. package/dist/lean-to-extension/geometry.js +662 -0
  446. package/dist/lean-to-extension/index.d.ts +8 -0
  447. package/dist/lean-to-extension/index.d.ts.map +1 -0
  448. package/dist/lean-to-extension/index.js +7 -0
  449. package/dist/lean-to-extension/layout.d.ts +108 -0
  450. package/dist/lean-to-extension/layout.d.ts.map +1 -0
  451. package/dist/lean-to-extension/layout.js +477 -0
  452. package/dist/lean-to-extension/managed-preview.d.ts +3 -0
  453. package/dist/lean-to-extension/managed-preview.d.ts.map +1 -0
  454. package/dist/lean-to-extension/managed-preview.js +93 -0
  455. package/dist/lean-to-extension/move-tool.d.ts +8 -0
  456. package/dist/lean-to-extension/move-tool.d.ts.map +1 -0
  457. package/dist/lean-to-extension/move-tool.js +202 -0
  458. package/dist/lean-to-extension/paint.d.ts +2 -0
  459. package/dist/lean-to-extension/paint.d.ts.map +1 -0
  460. package/dist/lean-to-extension/paint.js +16 -0
  461. package/dist/lean-to-extension/parametrics.d.ts +4 -0
  462. package/dist/lean-to-extension/parametrics.d.ts.map +1 -0
  463. package/dist/lean-to-extension/parametrics.js +570 -0
  464. package/dist/lean-to-extension/placement-scope.d.ts +3 -0
  465. package/dist/lean-to-extension/placement-scope.d.ts.map +1 -0
  466. package/dist/lean-to-extension/placement-scope.js +4 -0
  467. package/dist/lean-to-extension/placement-validation.d.ts +4 -0
  468. package/dist/lean-to-extension/placement-validation.d.ts.map +1 -0
  469. package/dist/lean-to-extension/placement-validation.js +324 -0
  470. package/dist/lean-to-extension/placement.d.ts +59 -0
  471. package/dist/lean-to-extension/placement.d.ts.map +1 -0
  472. package/dist/lean-to-extension/placement.js +331 -0
  473. package/dist/lean-to-extension/plan-footprint.d.ts +10 -0
  474. package/dist/lean-to-extension/plan-footprint.d.ts.map +1 -0
  475. package/dist/lean-to-extension/plan-footprint.js +27 -0
  476. package/dist/lean-to-extension/preview-geometry.d.ts +7 -0
  477. package/dist/lean-to-extension/preview-geometry.d.ts.map +1 -0
  478. package/dist/lean-to-extension/preview-geometry.js +40 -0
  479. package/dist/lean-to-extension/preview.d.ts +7 -0
  480. package/dist/lean-to-extension/preview.d.ts.map +1 -0
  481. package/dist/lean-to-extension/preview.js +20 -0
  482. package/dist/lean-to-extension/renderer.d.ts +6 -0
  483. package/dist/lean-to-extension/renderer.d.ts.map +1 -0
  484. package/dist/lean-to-extension/renderer.js +34 -0
  485. package/dist/lean-to-extension/roof-attachment.d.ts +28 -0
  486. package/dist/lean-to-extension/roof-attachment.d.ts.map +1 -0
  487. package/dist/lean-to-extension/roof-attachment.js +376 -0
  488. package/dist/lean-to-extension/schema.d.ts +2 -0
  489. package/dist/lean-to-extension/schema.d.ts.map +1 -0
  490. package/dist/lean-to-extension/schema.js +1 -0
  491. package/dist/lean-to-extension/slots.d.ts +5 -0
  492. package/dist/lean-to-extension/slots.d.ts.map +1 -0
  493. package/dist/lean-to-extension/slots.js +15 -0
  494. package/dist/lean-to-extension/system.d.ts +7 -0
  495. package/dist/lean-to-extension/system.d.ts.map +1 -0
  496. package/dist/lean-to-extension/system.js +726 -0
  497. package/dist/lean-to-extension/tool.d.ts +3 -0
  498. package/dist/lean-to-extension/tool.d.ts.map +1 -0
  499. package/dist/lean-to-extension/tool.js +580 -0
  500. package/dist/lean-to-extension/wall-target.d.ts +10 -0
  501. package/dist/lean-to-extension/wall-target.d.ts.map +1 -0
  502. package/dist/lean-to-extension/wall-target.js +29 -0
  503. package/dist/lineset/definition.d.ts.map +1 -1
  504. package/dist/lineset/definition.js +1 -0
  505. package/dist/liquid-line/definition.d.ts.map +1 -1
  506. package/dist/liquid-line/definition.js +1 -0
  507. package/dist/pipe-fitting/definition.d.ts.map +1 -1
  508. package/dist/pipe-fitting/definition.js +16 -2
  509. package/dist/pipe-fitting/floorplan.d.ts.map +1 -1
  510. package/dist/pipe-fitting/floorplan.js +4 -0
  511. package/dist/pipe-fitting/geometry.d.ts +3 -5
  512. package/dist/pipe-fitting/geometry.d.ts.map +1 -1
  513. package/dist/pipe-fitting/geometry.js +154 -21
  514. package/dist/pipe-fitting/inline-insertion.d.ts +13 -0
  515. package/dist/pipe-fitting/inline-insertion.d.ts.map +1 -0
  516. package/dist/pipe-fitting/inline-insertion.js +96 -0
  517. package/dist/pipe-fitting/parametrics.d.ts.map +1 -1
  518. package/dist/pipe-fitting/parametrics.js +17 -3
  519. package/dist/pipe-fitting/ports.d.ts.map +1 -1
  520. package/dist/pipe-fitting/ports.js +8 -1
  521. package/dist/pipe-fitting/tool.d.ts +26 -0
  522. package/dist/pipe-fitting/tool.d.ts.map +1 -1
  523. package/dist/pipe-fitting/tool.js +182 -37
  524. package/dist/pipe-segment/continuation.d.ts +22 -0
  525. package/dist/pipe-segment/continuation.d.ts.map +1 -0
  526. package/dist/pipe-segment/continuation.js +146 -0
  527. package/dist/pipe-segment/definition.d.ts.map +1 -1
  528. package/dist/pipe-segment/definition.js +23 -7
  529. package/dist/pipe-segment/floorplan.d.ts.map +1 -1
  530. package/dist/pipe-segment/floorplan.js +42 -0
  531. package/dist/pipe-segment/geometry.d.ts +2 -1
  532. package/dist/pipe-segment/geometry.d.ts.map +1 -1
  533. package/dist/pipe-segment/geometry.js +4 -1
  534. package/dist/pipe-segment/move-tool.d.ts.map +1 -1
  535. package/dist/pipe-segment/move-tool.js +25 -5
  536. package/dist/pipe-segment/parametrics.d.ts.map +1 -1
  537. package/dist/pipe-segment/parametrics.js +49 -0
  538. package/dist/pipe-segment/selection.d.ts +3 -4
  539. package/dist/pipe-segment/selection.d.ts.map +1 -1
  540. package/dist/pipe-segment/selection.js +169 -105
  541. package/dist/pipe-segment/slope.d.ts +5 -0
  542. package/dist/pipe-segment/slope.d.ts.map +1 -0
  543. package/dist/pipe-segment/slope.js +10 -0
  544. package/dist/pipe-segment/tool.d.ts.map +1 -1
  545. package/dist/pipe-segment/tool.js +325 -545
  546. package/dist/pipe-trap/tool.d.ts.map +1 -1
  547. package/dist/pipe-trap/tool.js +15 -2
  548. package/dist/procedural-item/definition.d.ts +4 -0
  549. package/dist/procedural-item/definition.d.ts.map +1 -0
  550. package/dist/procedural-item/definition.js +254 -0
  551. package/dist/procedural-item/geometry.d.ts +31 -0
  552. package/dist/procedural-item/geometry.d.ts.map +1 -0
  553. package/dist/procedural-item/geometry.js +88 -0
  554. package/dist/procedural-item/index.d.ts +3 -0
  555. package/dist/procedural-item/index.d.ts.map +1 -0
  556. package/dist/procedural-item/index.js +2 -0
  557. package/dist/procedural-item/move-session.d.ts +15 -0
  558. package/dist/procedural-item/move-session.d.ts.map +1 -0
  559. package/dist/procedural-item/move-session.js +197 -0
  560. package/dist/procedural-item/move-tool.d.ts +5 -0
  561. package/dist/procedural-item/move-tool.d.ts.map +1 -0
  562. package/dist/procedural-item/move-tool.js +193 -0
  563. package/dist/procedural-item/renderer.d.ts +5 -0
  564. package/dist/procedural-item/renderer.d.ts.map +1 -0
  565. package/dist/procedural-item/renderer.js +66 -0
  566. package/dist/ridge-vent/definition.js +1 -1
  567. package/dist/ridge-vent/panel.d.ts.map +1 -1
  568. package/dist/ridge-vent/panel.js +4 -4
  569. package/dist/ridge-vent/parametrics.js +1 -1
  570. package/dist/roof/conical-roof-placement.d.ts +36 -0
  571. package/dist/roof/conical-roof-placement.d.ts.map +1 -0
  572. package/dist/roof/conical-roof-placement.js +134 -0
  573. package/dist/roof/conical-roof.d.ts +3 -0
  574. package/dist/roof/conical-roof.d.ts.map +1 -0
  575. package/dist/roof/conical-roof.js +53 -0
  576. package/dist/roof/definition.d.ts +3 -10
  577. package/dist/roof/definition.d.ts.map +1 -1
  578. package/dist/roof/definition.js +83 -13
  579. package/dist/roof/floorplan.d.ts +3 -10
  580. package/dist/roof/floorplan.d.ts.map +1 -1
  581. package/dist/roof/floorplan.js +139 -165
  582. package/dist/roof/index.d.ts +1 -0
  583. package/dist/roof/index.d.ts.map +1 -1
  584. package/dist/roof/index.js +1 -0
  585. package/dist/roof/panel.d.ts.map +1 -1
  586. package/dist/roof/panel.js +26 -25
  587. package/dist/roof/renderer.d.ts.map +1 -1
  588. package/dist/roof/roof-footprint-source.d.ts +16 -0
  589. package/dist/roof/roof-footprint-source.d.ts.map +1 -0
  590. package/dist/roof/roof-footprint-source.js +14 -0
  591. package/dist/roof/roof-footprint.d.ts +25 -0
  592. package/dist/roof/roof-footprint.d.ts.map +1 -0
  593. package/dist/roof/roof-footprint.js +94 -0
  594. package/dist/roof/roof-placement-mode.d.ts +18 -0
  595. package/dist/roof/roof-placement-mode.d.ts.map +1 -0
  596. package/dist/roof/roof-placement-mode.js +24 -0
  597. package/dist/roof/tool.d.ts +10 -0
  598. package/dist/roof/tool.d.ts.map +1 -0
  599. package/dist/roof/tool.js +922 -0
  600. package/dist/roof-segment/definition.d.ts.map +1 -1
  601. package/dist/roof-segment/definition.js +52 -8
  602. package/dist/roof-segment/floorplan-affordances.d.ts +5 -6
  603. package/dist/roof-segment/floorplan-affordances.d.ts.map +1 -1
  604. package/dist/roof-segment/floorplan-affordances.js +64 -18
  605. package/dist/roof-segment/floorplan.d.ts +3 -2
  606. package/dist/roof-segment/floorplan.d.ts.map +1 -1
  607. package/dist/roof-segment/floorplan.js +95 -46
  608. package/dist/roof-segment/panel.d.ts.map +1 -1
  609. package/dist/roof-segment/panel.js +122 -27
  610. package/dist/scan/definition.d.ts +2 -3
  611. package/dist/scan/definition.d.ts.map +1 -1
  612. package/dist/scan/definition.js +9 -7
  613. package/dist/scan/parametrics.d.ts.map +1 -1
  614. package/dist/scan/parametrics.js +13 -1
  615. package/dist/scan/renderer.d.ts.map +1 -1
  616. package/dist/scan/renderer.js +8 -2
  617. package/dist/shared/accessory-cursor.d.ts +7 -0
  618. package/dist/shared/accessory-cursor.d.ts.map +1 -0
  619. package/dist/shared/accessory-cursor.js +83 -0
  620. package/dist/shared/accessory-floorplan.d.ts +4 -0
  621. package/dist/shared/accessory-floorplan.d.ts.map +1 -0
  622. package/dist/shared/accessory-floorplan.js +51 -0
  623. package/dist/shared/accessory-geometry.d.ts +7 -0
  624. package/dist/shared/accessory-geometry.d.ts.map +1 -0
  625. package/dist/shared/accessory-geometry.js +79 -0
  626. package/dist/shared/accessory-placement.d.ts +10 -0
  627. package/dist/shared/accessory-placement.d.ts.map +1 -0
  628. package/dist/shared/accessory-placement.js +126 -0
  629. package/dist/shared/accessory-snapping.d.ts +5 -0
  630. package/dist/shared/accessory-snapping.d.ts.map +1 -0
  631. package/dist/shared/accessory-snapping.js +40 -0
  632. package/dist/shared/automatic-run-end-cap.d.ts +12 -0
  633. package/dist/shared/automatic-run-end-cap.d.ts.map +1 -0
  634. package/dist/shared/automatic-run-end-cap.js +169 -0
  635. package/dist/shared/block-face-host.d.ts +15 -0
  636. package/dist/shared/block-face-host.d.ts.map +1 -0
  637. package/dist/shared/block-face-host.js +36 -0
  638. package/dist/shared/connection-compatibility.d.ts +8 -0
  639. package/dist/shared/connection-compatibility.d.ts.map +1 -0
  640. package/dist/shared/connection-compatibility.js +28 -0
  641. package/dist/shared/connection-feedback.d.ts +10 -0
  642. package/dist/shared/connection-feedback.d.ts.map +1 -0
  643. package/dist/shared/connection-feedback.js +17 -0
  644. package/dist/shared/distribution-run-contract.d.ts +49 -0
  645. package/dist/shared/distribution-run-contract.d.ts.map +1 -0
  646. package/dist/shared/distribution-run-contract.js +30 -0
  647. package/dist/shared/distribution-run-tool.d.ts +117 -0
  648. package/dist/shared/distribution-run-tool.d.ts.map +1 -0
  649. package/dist/shared/distribution-run-tool.js +986 -0
  650. package/dist/shared/dormer-wall-opening-placement.d.ts +21 -0
  651. package/dist/shared/dormer-wall-opening-placement.d.ts.map +1 -0
  652. package/dist/shared/dormer-wall-opening-placement.js +101 -0
  653. package/dist/shared/duct-adapter.d.ts +10 -0
  654. package/dist/shared/duct-adapter.d.ts.map +1 -0
  655. package/dist/shared/duct-adapter.js +43 -0
  656. package/dist/shared/elbow-branch-continuation.d.ts +20 -0
  657. package/dist/shared/elbow-branch-continuation.d.ts.map +1 -0
  658. package/dist/shared/elbow-branch-continuation.js +200 -0
  659. package/dist/shared/fitting-clearance.d.ts +5 -0
  660. package/dist/shared/fitting-clearance.d.ts.map +1 -0
  661. package/dist/shared/fitting-clearance.js +11 -0
  662. package/dist/shared/fitting-deletion-cleanup.d.ts +14 -0
  663. package/dist/shared/fitting-deletion-cleanup.d.ts.map +1 -0
  664. package/dist/shared/fitting-deletion-cleanup.js +297 -0
  665. package/dist/shared/fitting-surface-support.d.ts +5 -0
  666. package/dist/shared/fitting-surface-support.d.ts.map +1 -0
  667. package/dist/shared/fitting-surface-support.js +42 -0
  668. package/dist/shared/fitting-tool-options.d.ts +4 -0
  669. package/dist/shared/fitting-tool-options.d.ts.map +1 -0
  670. package/dist/shared/fitting-tool-options.js +175 -0
  671. package/dist/shared/floor-placement.d.ts +1 -1
  672. package/dist/shared/floor-placement.d.ts.map +1 -1
  673. package/dist/shared/floor-placement.js +13 -33
  674. package/dist/shared/ghost-materials.d.ts.map +1 -1
  675. package/dist/shared/ghost-materials.js +2 -1
  676. package/dist/shared/lean-to-post-omissions.d.ts +8 -0
  677. package/dist/shared/lean-to-post-omissions.d.ts.map +1 -0
  678. package/dist/shared/lean-to-post-omissions.js +51 -0
  679. package/dist/shared/mep-fitting-actions.d.ts +5 -0
  680. package/dist/shared/mep-fitting-actions.d.ts.map +1 -0
  681. package/dist/shared/mep-fitting-actions.js +16 -0
  682. package/dist/shared/mep-presets.d.ts +11 -0
  683. package/dist/shared/mep-presets.d.ts.map +1 -0
  684. package/dist/shared/mep-presets.js +34 -0
  685. package/dist/shared/node-batch/candidates.d.ts +29 -0
  686. package/dist/shared/node-batch/candidates.d.ts.map +1 -0
  687. package/dist/shared/node-batch/candidates.js +260 -0
  688. package/dist/shared/node-batch/store.d.ts +26 -0
  689. package/dist/shared/node-batch/store.d.ts.map +1 -0
  690. package/dist/shared/node-batch/store.js +319 -0
  691. package/dist/shared/node-batch/system.d.ts +8 -0
  692. package/dist/shared/node-batch/system.d.ts.map +1 -0
  693. package/dist/shared/node-batch/system.js +569 -0
  694. package/dist/shared/node-batch/types.d.ts +99 -0
  695. package/dist/shared/node-batch/types.d.ts.map +1 -0
  696. package/dist/shared/node-batch/types.js +4 -0
  697. package/dist/shared/opening-move-history.d.ts +47 -0
  698. package/dist/shared/opening-move-history.d.ts.map +1 -0
  699. package/dist/shared/opening-move-history.js +18 -0
  700. package/dist/shared/opening-move-wall-gate.d.ts +55 -0
  701. package/dist/shared/opening-move-wall-gate.d.ts.map +1 -0
  702. package/dist/shared/opening-move-wall-gate.js +44 -0
  703. package/dist/shared/panel-fields.d.ts +15 -0
  704. package/dist/shared/panel-fields.d.ts.map +1 -0
  705. package/dist/shared/panel-fields.js +31 -0
  706. package/dist/shared/path-point-affordance.d.ts +2 -2
  707. package/dist/shared/path-point-affordance.d.ts.map +1 -1
  708. package/dist/shared/path-point-affordance.js +30 -6
  709. package/dist/shared/placeholder-geometry.d.ts.map +1 -1
  710. package/dist/shared/placeholder-geometry.js +4 -0
  711. package/dist/shared/ports.d.ts +20 -3
  712. package/dist/shared/ports.d.ts.map +1 -1
  713. package/dist/shared/ports.js +148 -7
  714. package/dist/shared/primitive-uv.d.ts +17 -0
  715. package/dist/shared/primitive-uv.d.ts.map +1 -0
  716. package/dist/shared/primitive-uv.js +107 -0
  717. package/dist/shared/reducer-size.d.ts +2 -0
  718. package/dist/shared/reducer-size.d.ts.map +1 -0
  719. package/dist/shared/reducer-size.js +12 -0
  720. package/dist/shared/resting-surface-plan.d.ts +3 -0
  721. package/dist/shared/resting-surface-plan.d.ts.map +1 -0
  722. package/dist/shared/resting-surface-plan.js +28 -0
  723. package/dist/shared/ridge-snap.js +1 -1
  724. package/dist/shared/roof-surface.d.ts.map +1 -1
  725. package/dist/shared/roof-surface.js +17 -2
  726. package/dist/shared/run-cursor.d.ts +22 -0
  727. package/dist/shared/run-cursor.d.ts.map +1 -0
  728. package/dist/shared/run-cursor.js +19 -0
  729. package/dist/shared/run-direction-feedback.d.ts +20 -0
  730. package/dist/shared/run-direction-feedback.d.ts.map +1 -0
  731. package/dist/shared/run-direction-feedback.js +139 -0
  732. package/dist/shared/run-hanger-controls.d.ts +7 -0
  733. package/dist/shared/run-hanger-controls.d.ts.map +1 -0
  734. package/dist/shared/run-hanger-controls.js +44 -0
  735. package/dist/shared/run-hanger-inspector.d.ts +5 -0
  736. package/dist/shared/run-hanger-inspector.d.ts.map +1 -0
  737. package/dist/shared/run-hanger-inspector.js +43 -0
  738. package/dist/shared/run-hanger-mode.d.ts +11 -0
  739. package/dist/shared/run-hanger-mode.d.ts.map +1 -0
  740. package/dist/shared/run-hanger-mode.js +32 -0
  741. package/dist/shared/run-hanger-system.d.ts +3 -0
  742. package/dist/shared/run-hanger-system.d.ts.map +1 -0
  743. package/dist/shared/run-hanger-system.js +39 -0
  744. package/dist/shared/run-hangers.d.ts +25 -0
  745. package/dist/shared/run-hangers.d.ts.map +1 -0
  746. package/dist/shared/run-hangers.js +261 -0
  747. package/dist/shared/run-port-snap.d.ts +11 -0
  748. package/dist/shared/run-port-snap.d.ts.map +1 -0
  749. package/dist/shared/run-port-snap.js +19 -0
  750. package/dist/shared/run-screen-direction.d.ts +5 -0
  751. package/dist/shared/run-screen-direction.d.ts.map +1 -0
  752. package/dist/shared/run-screen-direction.js +24 -0
  753. package/dist/shared/selection-handles.d.ts +4 -0
  754. package/dist/shared/selection-handles.d.ts.map +1 -1
  755. package/dist/shared/selection-handles.js +10 -0
  756. package/dist/shared/slot-paint.d.ts +8 -1
  757. package/dist/shared/slot-paint.d.ts.map +1 -1
  758. package/dist/shared/slot-paint.js +120 -27
  759. package/dist/shared/surface-paint.d.ts +1 -2
  760. package/dist/shared/surface-paint.d.ts.map +1 -1
  761. package/dist/shared/surface-paint.js +9 -20
  762. package/dist/shared/system-check-panel.d.ts +6 -0
  763. package/dist/shared/system-check-panel.d.ts.map +1 -0
  764. package/dist/shared/system-check-panel.js +62 -0
  765. package/dist/shared/system-checks.d.ts +9 -0
  766. package/dist/shared/system-checks.d.ts.map +1 -0
  767. package/dist/shared/system-checks.js +131 -0
  768. package/dist/shared/wall-attach-target.d.ts +14 -2
  769. package/dist/shared/wall-attach-target.d.ts.map +1 -1
  770. package/dist/shared/wall-attach-target.js +108 -4
  771. package/dist/shared/wall-opening-clearance.d.ts +19 -0
  772. package/dist/shared/wall-opening-clearance.d.ts.map +1 -0
  773. package/dist/shared/wall-opening-clearance.js +28 -0
  774. package/dist/shared/wall-run-move.d.ts +17 -0
  775. package/dist/shared/wall-run-move.d.ts.map +1 -0
  776. package/dist/shared/wall-run-move.js +56 -0
  777. package/dist/shelf/definition.d.ts.map +1 -1
  778. package/dist/shelf/definition.js +2 -1
  779. package/dist/shelf/floorplan-affordances.d.ts.map +1 -1
  780. package/dist/shelf/floorplan-affordances.js +5 -3
  781. package/dist/shelf/floorplan-move.d.ts.map +1 -1
  782. package/dist/shelf/floorplan-move.js +3 -2
  783. package/dist/shelf/geometry.d.ts.map +1 -1
  784. package/dist/shelf/geometry.js +2 -10
  785. package/dist/shelf/parametrics.js +4 -4
  786. package/dist/site/renderer.d.ts.map +1 -1
  787. package/dist/site/renderer.js +40 -28
  788. package/dist/site/terrain-geometry.d.ts +1 -1
  789. package/dist/site/terrain-geometry.d.ts.map +1 -1
  790. package/dist/site/terrain-geometry.js +35 -9
  791. package/dist/site/terrain-mesh.d.ts +5 -0
  792. package/dist/site/terrain-mesh.d.ts.map +1 -1
  793. package/dist/site/terrain-mesh.js +63 -34
  794. package/dist/skylight/definition.js +1 -1
  795. package/dist/skylight/panel.js +6 -6
  796. package/dist/skylight/parametrics.js +2 -2
  797. package/dist/slab/definition.d.ts.map +1 -1
  798. package/dist/slab/definition.js +6 -1
  799. package/dist/slab/dependency-tracker.d.ts +3 -0
  800. package/dist/slab/dependency-tracker.d.ts.map +1 -0
  801. package/dist/slab/dependency-tracker.js +104 -0
  802. package/dist/slab/floorplan-affordances.d.ts +8 -8
  803. package/dist/slab/geometry.d.ts.map +1 -1
  804. package/dist/slab/geometry.js +16 -2
  805. package/dist/slab/panel.js +1 -1
  806. package/dist/slab/parametrics.js +1 -1
  807. package/dist/slab/system.d.ts.map +1 -1
  808. package/dist/slab/system.js +8 -64
  809. package/dist/solar-panel/definition.js +1 -1
  810. package/dist/solar-panel/panel.js +2 -2
  811. package/dist/solar-panel/parametrics.js +2 -2
  812. package/dist/spawn/panel.js +1 -1
  813. package/dist/stair/panel.d.ts.map +1 -1
  814. package/dist/stair/panel.js +23 -16
  815. package/dist/stair/stair-type.d.ts +18 -0
  816. package/dist/stair/stair-type.d.ts.map +1 -0
  817. package/dist/stair/stair-type.js +25 -0
  818. package/dist/stair-segment/panel.d.ts.map +1 -1
  819. package/dist/stair-segment/panel.js +45 -6
  820. package/dist/turbine-vent/definition.d.ts.map +1 -1
  821. package/dist/turbine-vent/definition.js +8 -5
  822. package/dist/turbine-vent/geometry.d.ts.map +1 -1
  823. package/dist/turbine-vent/geometry.js +48 -18
  824. package/dist/turbine-vent/paint.d.ts +4 -0
  825. package/dist/turbine-vent/paint.d.ts.map +1 -0
  826. package/dist/turbine-vent/paint.js +35 -0
  827. package/dist/turbine-vent/panel.js +1 -1
  828. package/dist/turbine-vent/parametrics.js +2 -2
  829. package/dist/turbine-vent/renderer.d.ts.map +1 -1
  830. package/dist/turbine-vent/renderer.js +31 -9
  831. package/dist/unit/definition.d.ts +10 -0
  832. package/dist/unit/definition.d.ts.map +1 -0
  833. package/dist/unit/definition.js +38 -0
  834. package/dist/unit/index.d.ts +2 -0
  835. package/dist/unit/index.d.ts.map +1 -0
  836. package/dist/unit/index.js +1 -0
  837. package/dist/unit/parametrics.d.ts +3 -0
  838. package/dist/unit/parametrics.d.ts.map +1 -0
  839. package/dist/unit/parametrics.js +4 -0
  840. package/dist/unit/schema.d.ts +2 -0
  841. package/dist/unit/schema.d.ts.map +1 -0
  842. package/dist/unit/schema.js +1 -0
  843. package/dist/unit/unit-panel.d.ts +5 -0
  844. package/dist/unit/unit-panel.d.ts.map +1 -0
  845. package/dist/unit/unit-panel.js +61 -0
  846. package/dist/wall/curve-eligibility.d.ts +3 -0
  847. package/dist/wall/curve-eligibility.d.ts.map +1 -0
  848. package/dist/wall/curve-eligibility.js +10 -0
  849. package/dist/wall/curve-tool.d.ts.map +1 -1
  850. package/dist/wall/curve-tool.js +8 -11
  851. package/dist/wall/definition.d.ts +1 -1
  852. package/dist/wall/definition.d.ts.map +1 -1
  853. package/dist/wall/definition.js +36 -13
  854. package/dist/wall/floorplan-affordances.d.ts +1 -0
  855. package/dist/wall/floorplan-affordances.d.ts.map +1 -1
  856. package/dist/wall/floorplan-affordances.js +46 -7
  857. package/dist/wall/floorplan-move.d.ts +3 -4
  858. package/dist/wall/floorplan-move.d.ts.map +1 -1
  859. package/dist/wall/floorplan-move.js +122 -9
  860. package/dist/wall/floorplan.d.ts.map +1 -1
  861. package/dist/wall/floorplan.js +19 -23
  862. package/dist/wall/move-shared.d.ts.map +1 -1
  863. package/dist/wall/move-shared.js +10 -7
  864. package/dist/wall/paint.d.ts.map +1 -1
  865. package/dist/wall/paint.js +2 -0
  866. package/dist/wall/panel.d.ts.map +1 -1
  867. package/dist/wall/panel.js +10 -17
  868. package/dist/wall/parametrics.js +2 -2
  869. package/dist/wall/pointer-transparency.d.ts +128 -0
  870. package/dist/wall/pointer-transparency.d.ts.map +1 -0
  871. package/dist/wall/pointer-transparency.js +147 -0
  872. package/dist/wall/renderer.d.ts.map +1 -1
  873. package/dist/wall/renderer.js +65 -8
  874. package/dist/wall/selection-hit-owner.d.ts +53 -0
  875. package/dist/wall/selection-hit-owner.d.ts.map +1 -0
  876. package/dist/wall/selection-hit-owner.js +79 -0
  877. package/dist/wall/system.d.ts +2 -0
  878. package/dist/wall/system.d.ts.map +1 -1
  879. package/dist/wall/system.js +66 -25
  880. package/dist/wall/tool.d.ts.map +1 -1
  881. package/dist/wall/tool.js +5 -0
  882. package/dist/wall/treatment-level-data.d.ts +6 -1
  883. package/dist/wall/treatment-level-data.d.ts.map +1 -1
  884. package/dist/wall/treatment-level-data.js +82 -5
  885. package/dist/wall/treatments.d.ts +1 -0
  886. package/dist/wall/treatments.d.ts.map +1 -1
  887. package/dist/wall/treatments.js +5 -0
  888. package/dist/wall/wall-batch-system.d.ts +38 -6
  889. package/dist/wall/wall-batch-system.d.ts.map +1 -1
  890. package/dist/wall/wall-batch-system.js +122 -25
  891. package/dist/wall/wall-batch.js +2 -2
  892. package/dist/window/definition.d.ts +2 -1
  893. package/dist/window/definition.d.ts.map +1 -1
  894. package/dist/window/definition.js +51 -24
  895. package/dist/window/floorplan-move.d.ts.map +1 -1
  896. package/dist/window/floorplan-move.js +1 -1
  897. package/dist/window/move-tool.d.ts +7 -5
  898. package/dist/window/move-tool.d.ts.map +1 -1
  899. package/dist/window/move-tool.js +344 -58
  900. package/dist/window/panel.d.ts.map +1 -1
  901. package/dist/window/panel.js +8 -6
  902. package/dist/window/parametrics.js +2 -2
  903. package/dist/window/renderer.d.ts.map +1 -1
  904. package/dist/window/renderer.js +5 -7
  905. package/dist/window/tool.d.ts.map +1 -1
  906. package/dist/window/tool.js +227 -13
  907. package/dist/zone/definition.d.ts.map +1 -1
  908. package/dist/zone/definition.js +7 -1
  909. package/dist/zone/floorplan-affordances.d.ts +12 -12
  910. package/dist/zone/floorplan.d.ts +1 -3
  911. package/dist/zone/floorplan.d.ts.map +1 -1
  912. package/dist/zone/floorplan.js +43 -28
  913. package/dist/zone/quantities-panel.d.ts.map +1 -1
  914. package/dist/zone/quantities-panel.js +33 -29
  915. package/dist/zone/renderer.d.ts.map +1 -1
  916. package/dist/zone/renderer.js +20 -6
  917. package/dist/zone/unit-membership.d.ts +12 -0
  918. package/dist/zone/unit-membership.d.ts.map +1 -0
  919. package/dist/zone/unit-membership.js +22 -0
  920. package/package.json +16 -8
  921. package/dist/dormer/panel-window-section.d.ts +0 -14
  922. package/dist/dormer/panel-window-section.d.ts.map +0 -1
  923. package/dist/dormer/panel-window-section.js +0 -59
  924. package/dist/dormer/window-assembly.d.ts +0 -22
  925. package/dist/dormer/window-assembly.d.ts.map +0 -1
  926. package/dist/dormer/window-assembly.js +0 -111
  927. package/dist/dormer/window-frame.d.ts +0 -24
  928. package/dist/dormer/window-frame.d.ts.map +0 -1
  929. package/dist/dormer/window-frame.js +0 -100
@@ -1,19 +1,25 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { DuctSegmentNode, emitter, getCeilingAt, getCeilingHeightAt, resolveCeilingHeight, useScene, } from '@pascal-app/core';
4
- import { CursorSphere, DimensionPill, EDITOR_LAYER, isAngleSnapActive, isGridSnapActive, isMagneticSnapActive, markToolCancelConsumed, triggerSFX, useEditor, usePathDraftPreview, } from '@pascal-app/editor';
5
- import { useViewer } from '@pascal-app/viewer';
6
- import { Html } from '@react-three/drei';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { DuctSegmentNode } from '@pascal-app/core';
4
+ import { EDITOR_LAYER, triggerSFX, useEditor, usePathDraftPreview, useRegistryToolContext, } from '@pascal-app/editor';
7
5
  import { useEffect, useMemo, useRef, useState } from 'react';
8
- import { DoubleSide, Matrix4, Path, Shape, ShapeGeometry, Vector3, } from 'three';
6
+ import { Euler, Vector3 } from 'three';
9
7
  import { getDuctFittingPorts } from '../duct-fitting/ports';
10
8
  import { planCrossAtRunBody, planElbowAtPort, planElbowRealign, planTeeAtRunBody, } from '../shared/auto-fitting';
11
- import { alignDrawPoint, clearDrawAlignment } from '../shared/draw-alignment';
9
+ import { createDuctRunEndCap, findMatedRunEndCapIds, isRunEndCapPort, } from '../shared/automatic-run-end-cap';
10
+ import { ConnectionFeedback } from '../shared/connection-feedback';
11
+ import { createRunWallAttachment } from '../shared/distribution-run-contract';
12
+ import { DistributionRunCursor, runDistanceSquared as dist2, runSectionHalfSizeM, stepNominalRunSize, useDistributionRunTool, } from '../shared/distribution-run-tool';
13
+ import { ductProfilesMatch, planDuctAdapter } from '../shared/duct-adapter';
14
+ import { FITTING_CLEARANCE_MESSAGE, hasFittingClearance } from '../shared/fitting-clearance';
12
15
  import { LevelOffsetGroup } from '../shared/level-offset-group';
13
- import { FittingGhost } from '../shared/mep-ghost';
14
- import { collectScenePorts, DUCT_PORT_SYSTEMS, findNearestPortXZ, findNearestRunBodyXZ, findRunBodyCrossingXZ, } from '../shared/ports';
16
+ import { DuctSegmentGhost, FittingGhost } from '../shared/mep-ghost';
17
+ import { collectScenePorts, DUCT_PORT_SYSTEMS, findNearestRunBody3D, findRunBodyCrossingSurface, } from '../shared/ports';
18
+ import { RunHangerPreview } from '../shared/run-hanger-controls';
19
+ import { useRunHangerMode } from '../shared/run-hanger-mode';
20
+ import { currentDuctContinuationSeed, ductEndpointPort } from './continuation';
15
21
  import { ductSegmentDefinition } from './definition';
16
- import { ductPortDiameterIn, rectSectionAxes, rollToContinueAcrossElbow } from './geometry';
22
+ import { rectSectionAxes, rollToContinueAcrossElbow } from './geometry';
17
23
  /**
18
24
  * Continuous placement tool for duct segments.
19
25
  *
@@ -42,46 +48,16 @@ import { ductPortDiameterIn, rectSectionAxes, rollToContinueAcrossElbow } from '
42
48
  * vertical mouse motion drives Y. Click commits the riser segment.
43
49
  * - **[ / ]** step the duct diameter through nominal US sizes; the
44
50
  * ghost preview and the committed node both use it.
45
- * - **C** toggles ceiling-level placement: each point lands just below
46
- * the ceiling actually covering it (duct top hugging that ceiling)
47
- * instead of the floor, so a run tracks per-room ceiling heights.
48
- * Points not under any ceiling fall back to the floor.
49
- * - Esc clears an anchored start point.
51
+ * - Esc exits drawing.
50
52
  */
51
- const PREVIEW_OPACITY = 0.55;
52
53
  /**
53
54
  * Nominal US round-duct sizes (inches): 4"–10" in 1" steps, 12"+ in 2"
54
55
  * steps — matches what flex and rigid round actually ship in.
55
56
  */
56
57
  const DUCT_DIAMETERS_IN = [4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20];
57
- /** Snap radius (meters) for joining onto an existing duct's start/end. */
58
- const ENDPOINT_SNAP_RADIUS_M = 0.5;
59
- /** Snap radius (meters) for tapping the SIDE of an existing run — a tee
60
- * is minted there. Tighter than the port radius so run ends keep
61
- * priority near their last stretch. */
62
58
  const BODY_SNAP_RADIUS_M = 0.35;
59
+ const DUCT_WALL_STANDOFF_M = 0.01;
63
60
  /** Angle step (radians) for the XZ angle lock — 45°. */
64
- const ANGLE_STEP_RAD = Math.PI / 4;
65
- /** Mouse pixels → meters mapping for Alt-vertical drag. 100 px ≈ 1 m. */
66
- const ALT_PIXELS_PER_METER = 100;
67
- /** Bounds on Alt-driven Y so a wild fling doesn't fly off. */
68
- const ALT_Y_MIN_M = -3;
69
- const ALT_Y_MAX_M = 10;
70
- /** green-500 — the project's bounding-box / placeable accent. The cursor
71
- * ring + vertical line recolour to this while the point is snapped onto an
72
- * existing run, so the coincidence reads with the familiar snap green. */
73
- const SNAP_CURSOR_COLOR = '#22c55e';
74
- function snap(value, step) {
75
- if (step <= 0)
76
- return value;
77
- return Math.round(value / step) * step;
78
- }
79
- function dist2(a, b) {
80
- const dx = a[0] - b[0];
81
- const dy = a[1] - b[1];
82
- const dz = a[2] - b[2];
83
- return dx * dx + dy * dy + dz * dz;
84
- }
85
61
  /**
86
62
  * Cross-section roll for a new rect run leaving `port` along `newDir`,
87
63
  * so its profile stays continuous with whatever it joined: a turn
@@ -92,11 +68,15 @@ function dist2(a, b) {
92
68
  * there). Null when the port doesn't carry a rect orientation. Shared
93
69
  * by the ghost preview and the commit so what you see is what lands.
94
70
  */
95
- function continuityRollFrom(port, newDir) {
71
+ function continuityRollFrom(port, newDir, nodes) {
96
72
  if (!port)
97
73
  return null;
98
- const nodes = useScene.getState().nodes;
99
74
  const owner = nodes[port.nodeId];
75
+ if (owner?.type === 'duct-fitting' && ['reducer', 'transition'].includes(owner.fittingType)) {
76
+ const width = new Vector3(0, 0, 1).applyEuler(new Euler(...owner.rotation));
77
+ const basis = rectSectionAxes(newDir);
78
+ return Math.atan2(width.dot(basis.height), width.dot(basis.width));
79
+ }
100
80
  let srcDir = null;
101
81
  let srcRoll = 0;
102
82
  if ((owner?.type === 'hvac-equipment' || owner?.type === 'duct-terminal') &&
@@ -134,26 +114,33 @@ function continuityRollFrom(port, newDir) {
134
114
  if (!srcDir)
135
115
  return null;
136
116
  const cross = new Vector3().crossVectors(srcDir, newDir);
137
- if (cross.lengthSq() < 1e-8)
117
+ if (cross.lengthSq() < 1e-8) {
118
+ if (owner?.type === 'duct-segment') {
119
+ const i = port.id === 'start' ? 0 : owner.path.length - 2;
120
+ const direction = new Vector3(...owner.path[i + 1]).sub(new Vector3(...owner.path[i]));
121
+ const width = rectSectionAxes(direction, owner.roll).width;
122
+ const basis = rectSectionAxes(newDir);
123
+ return Math.atan2(width.dot(basis.height), width.dot(basis.width));
124
+ }
138
125
  return srcRoll;
126
+ }
139
127
  return rollToContinueAcrossElbow(srcDir, srcRoll, srcDir, newDir);
140
128
  }
141
- function continuityRollForRun(startPort, endPort, dir) {
142
- return continuityRollFrom(startPort, dir) ?? continuityRollFrom(endPort, dir) ?? 0;
129
+ function continuityRollForRun(startPort, endPort, dir, nodes) {
130
+ return continuityRollFrom(startPort, dir, nodes) ?? continuityRollFrom(endPort, dir, nodes) ?? 0;
143
131
  }
144
- /**
145
- * Nearest typed port — duct run ends, fitting collars, anything whose
146
- * kind registers `def.ports` — within snap range of `point` on the XZ
147
- * plane. Y is ignored for the distance check (grid events ride the floor
148
- * while ports hang at duct height); the snap adopts the port's full 3D
149
- * position. The full port is returned so the commit knows what it joined
150
- * (auto-elbow insertion needs the port's direction and owner).
151
- */
152
- function findNearbyPort(point) {
153
- return findNearestPortXZ(point, collectScenePorts({ systems: DUCT_PORT_SYSTEMS }), ENDPOINT_SNAP_RADIUS_M);
132
+ function getConnectionPorts(levelId, nodes) {
133
+ return collectScenePorts({
134
+ systems: DUCT_PORT_SYSTEMS,
135
+ levelId: levelId ?? undefined,
136
+ }).filter((port) => !isRunEndCapPort(port, nodes));
154
137
  }
155
- function portPoint(port) {
156
- return [port.position[0], port.position[1], port.position[2]];
138
+ export function ductSurfaceClearanceM(profile, wall = false) {
139
+ return (runSectionHalfSizeM(profile.shape === 'round'
140
+ ? profile.diameter
141
+ : wall
142
+ ? Math.max(profile.width, profile.height)
143
+ : profile.height) + (wall && profile.shape !== 'round' ? DUCT_WALL_STANDOFF_M : 0));
157
144
  }
158
145
  /**
159
146
  * Profile to inherit when the segment start snaps onto `port` — joining
@@ -161,16 +148,16 @@ function portPoint(port) {
161
148
  * run / fitting collar keeps its diameter. Equipment and terminal
162
149
  * collars are round at the port's advertised size.
163
150
  */
164
- function inheritProfile(port) {
165
- const owner = useScene.getState().nodes[port.nodeId];
151
+ function inheritProfile(port, nodes) {
152
+ const owner = nodes[port.nodeId];
166
153
  if (!owner)
167
154
  return null;
168
155
  if (owner.type === 'duct-segment' || owner.type === 'duct-fitting') {
169
156
  return {
170
- shape: owner.shape,
157
+ shape: port.shape ?? owner.shape,
171
158
  diameter: Math.min(48, Math.max(2, owner.type === 'duct-segment' ? owner.diameter : port.diameter)),
172
- width: owner.width,
173
- height: owner.height,
159
+ width: port.width ?? owner.width,
160
+ height: port.height ?? owner.height,
174
161
  };
175
162
  }
176
163
  if (owner.type === 'hvac-equipment' || owner.type === 'duct-terminal') {
@@ -195,52 +182,38 @@ function inheritProfile(port) {
195
182
  }
196
183
  return null;
197
184
  }
198
- /**
199
- * Project `raw` onto the nearest of the eight 45° rays emanating from
200
- * `from` in the XZ plane. Y is preserved from `from`. The projection
201
- * keeps the cursor's *distance* along the chosen ray so the user feels
202
- * the segment grow with their mouse motion rather than snap to a fixed
203
- * length.
204
- */
205
- function projectToAngleLock(from, raw) {
206
- const dx = raw[0] - from[0];
207
- const dz = raw[2] - from[2];
208
- const len = Math.hypot(dx, dz);
209
- if (len < 1e-4)
210
- return [from[0], from[1], from[2]];
211
- const theta = Math.atan2(dz, dx);
212
- const snapped = Math.round(theta / ANGLE_STEP_RAD) * ANGLE_STEP_RAD;
213
- // Distance along the chosen ray = projection of raw onto that direction.
214
- const proj = dx * Math.cos(snapped) + dz * Math.sin(snapped);
215
- const d = Math.max(0, proj);
216
- return [from[0] + Math.cos(snapped) * d, from[1], from[2] + Math.sin(snapped) * d];
217
- }
218
- const elbowPlanFor = (port, awayDir, profile) => {
185
+ const elbowPlanFor = (port, awayDir, profile, nodes) => {
219
186
  if (!port)
220
187
  return null;
221
- const owner = useScene.getState().nodes[port.nodeId];
188
+ const owner = nodes[port.nodeId];
222
189
  if (owner?.type !== 'duct-segment')
223
190
  return null;
224
- const plan = planElbowAtPort(port, awayDir, profile);
191
+ const source = inheritProfile(port, nodes) ?? profile;
192
+ const plan = planElbowAtPort(port, awayDir, source);
225
193
  if (!plan)
226
194
  return null;
227
195
  // Trim the run's snapped endpoint back to the elbow's inlet collar.
228
196
  const path = owner.path.map((p) => [...p]);
229
197
  const index = port.id === 'start' ? 0 : path.length - 1;
230
198
  const neighbor = path[index === 0 ? 1 : index - 1];
231
- const remaining = Math.hypot(plan.trimmedPortPoint[0] - neighbor[0], plan.trimmedPortPoint[1] - neighbor[1], plan.trimmedPortPoint[2] - neighbor[2]);
232
- // The trim must leave a real piece of the existing run AND not flip it.
233
199
  const original = path[index];
234
- const originalLen = Math.hypot(original[0] - neighbor[0], original[1] - neighbor[1], original[2] - neighbor[2]);
235
- if (remaining < 0.08 || remaining >= originalLen)
236
- return null;
200
+ const direction = [
201
+ original[0] - neighbor[0],
202
+ original[1] - neighbor[1],
203
+ original[2] - neighbor[2],
204
+ ];
205
+ const hasClearance = hasFittingClearance(neighbor, plan.trimmedPortPoint, direction, 0.08);
237
206
  path[index] = plan.trimmedPortPoint;
238
- return { ...plan, trim: { id: port.nodeId, data: { path } } };
207
+ return {
208
+ ...plan,
209
+ hasClearance,
210
+ trim: { id: port.nodeId, data: { path } },
211
+ };
239
212
  };
240
- const realignPlanFor = (port, awayDir) => {
213
+ const realignPlanFor = (port, awayDir, nodes) => {
241
214
  if (!port)
242
215
  return null;
243
- const owner = useScene.getState().nodes[port.nodeId];
216
+ const owner = nodes[port.nodeId];
244
217
  if (owner?.type !== 'duct-fitting')
245
218
  return null;
246
219
  return planElbowRealign(owner, port.id, awayDir);
@@ -251,10 +224,10 @@ const realignPlanFor = (port, awayDir) => {
251
224
  * tap), decide every node the commit creates / updates — auto-inserted
252
225
  * elbows / tees / crosses, the drawn run (split in two when it crosses a
253
226
  * trunk), trunk tails, and trim / realign updates. Reads the live scene
254
- * graph but mutates nothing, so the live preview can call it each frame
255
- * to ghost the fittings before the commit applies the identical plan.
227
+ * graph snapshot but mutates nothing, so the live preview can call it each
228
+ * frame to ghost the fittings before the commit applies the identical plan.
256
229
  */
257
- function planDuctDraw(start, end, startPort, startBody, endPort, endBody, profile) {
230
+ export function planDuctDraw(start, end, startPort, startBody, endPort, endBody, profile, nodes, surface, autoHangers = false, toolDefaults = {}, hangerStyle = 'single') {
258
231
  const length = Math.hypot(end[0] - start[0], end[1] - start[1], end[2] - start[2]);
259
232
  if (length < 1e-4)
260
233
  return null;
@@ -263,56 +236,107 @@ function planDuctDraw(start, end, startPort, startBody, endPort, endBody, profil
263
236
  (end[1] - start[1]) / length,
264
237
  (end[2] - start[2]) / length,
265
238
  ];
266
- const startPlan = elbowPlanFor(startPort, dir, profile);
267
- const endPlan = elbowPlanFor(endPort, [-dir[0], -dir[1], -dir[2]], profile);
268
- const startRealign = startPlan ? null : realignPlanFor(startPort, dir);
269
- const endRealign = endPlan ? null : realignPlanFor(endPort, [-dir[0], -dir[1], -dir[2]]);
239
+ const startPlan = elbowPlanFor(startPort, dir, profile, nodes);
240
+ const endPlan = elbowPlanFor(endPort, [-dir[0], -dir[1], -dir[2]], profile, nodes);
241
+ const invalidPlan = () => ({
242
+ validationMessage: FITTING_CLEARANCE_MESSAGE,
243
+ fittings: [],
244
+ ducts: [],
245
+ tails: [],
246
+ updates: [],
247
+ delete: [],
248
+ });
249
+ if (startPlan?.hasClearance === false || endPlan?.hasClearance === false)
250
+ return invalidPlan();
251
+ const startRealign = startPlan ? null : realignPlanFor(startPort, dir, nodes);
252
+ const endRealign = endPlan ? null : realignPlanFor(endPort, [-dir[0], -dir[1], -dir[2]], nodes);
270
253
  const trunkBody = startPlan ? null : startBody;
271
- const trunkOwner = trunkBody ? useScene.getState().nodes[trunkBody.nodeId] : null;
254
+ const trunkOwner = trunkBody ? nodes[trunkBody.nodeId] : null;
272
255
  const teePlan = trunkBody && trunkOwner?.type === 'duct-segment'
273
256
  ? planTeeAtRunBody(trunkOwner, trunkBody, dir, profile)
274
257
  : null;
275
258
  const endTrunkBody = endPlan || endRealign ? null : endBody;
276
- const endTrunkOwner = endTrunkBody ? useScene.getState().nodes[endTrunkBody.nodeId] : null;
259
+ const endTrunkOwner = endTrunkBody ? nodes[endTrunkBody.nodeId] : null;
277
260
  const endTeePlan = endTrunkBody && endTrunkOwner?.type === 'duct-segment'
278
261
  ? planTeeAtRunBody(endTrunkOwner, endTrunkBody, [-dir[0], -dir[1], -dir[2]], profile)
279
262
  : null;
280
- let ductStart = startPlan?.collarPoint ?? teePlan?.branchCollar ?? startRealign?.collarPoint ?? start;
281
- let ductEnd = endPlan?.collarPoint ?? endTeePlan?.branchCollar ?? endRealign?.collarPoint ?? end;
282
- const remaining = Math.hypot(ductEnd[0] - ductStart[0], ductEnd[1] - ductStart[1], ductEnd[2] - ductStart[2]);
283
- let plans = [startPlan, endPlan].filter((p) => p !== null);
284
- let tee = teePlan;
263
+ const adapterFor = (port, corner, away) => {
264
+ if (!port)
265
+ return null;
266
+ const source = inheritProfile(port, nodes);
267
+ if (!source)
268
+ return null;
269
+ const axis = new Vector3(...away);
270
+ if (!corner && axis.dot(new Vector3(...port.direction).normalize()) < 0.9999)
271
+ return null;
272
+ const owner = nodes[port.nodeId];
273
+ const roll = continuityRollFrom(port, axis, nodes) ?? 0;
274
+ const width = rectSectionAxes(axis, roll).width;
275
+ if (owner?.type === 'duct-fitting' && !corner) {
276
+ width.set(0, 0, 1).applyEuler(new Euler(...owner.rotation));
277
+ }
278
+ return planDuctAdapter({ ...port, position: corner?.collarPoint ?? port.position, direction: away }, source, profile, width);
279
+ };
280
+ const startAdapter = adapterFor(startPort, startPlan, dir);
281
+ const endAdapter = adapterFor(endPort, endPlan, [-dir[0], -dir[1], -dir[2]]);
282
+ for (const [port, adapter] of [
283
+ [startPort, startAdapter],
284
+ [endPort, endAdapter],
285
+ ]) {
286
+ const source = port ? inheritProfile(port, nodes) : null;
287
+ if (source && !ductProfilesMatch(source, profile) && !adapter) {
288
+ return {
289
+ ...invalidPlan(),
290
+ validationMessage: 'Align the connection or draw a 15–90° bend to fit the profile change.',
291
+ };
292
+ }
293
+ }
294
+ const ductStart = startAdapter?.collarPoint ??
295
+ startPlan?.collarPoint ??
296
+ teePlan?.branchCollar ??
297
+ startRealign?.collarPoint ??
298
+ start;
299
+ let ductEnd = endAdapter?.collarPoint ??
300
+ endPlan?.collarPoint ??
301
+ endTeePlan?.branchCollar ??
302
+ endRealign?.collarPoint ??
303
+ end;
304
+ const plans = [startPlan, endPlan].filter((p) => p !== null);
305
+ const tee = teePlan;
285
306
  let endTee = endTeePlan && endTrunkBody?.nodeId === trunkBody?.nodeId ? null : endTeePlan;
286
307
  if (!endTee && endTeePlan)
287
308
  ductEnd = endRealign?.collarPoint ?? end;
288
- let realigns = [startRealign, endRealign].filter((p) => p !== null);
289
- const crossHit = findRunBodyCrossingXZ(start, end, BODY_SNAP_RADIUS_M);
290
- const crossOwner = crossHit ? useScene.getState().nodes[crossHit.nodeId] : null;
309
+ const realigns = [startRealign, endRealign].filter((p) => p !== null);
310
+ const crossHit = surface
311
+ ? findRunBodyCrossingSurface(start, end, BODY_SNAP_RADIUS_M, surface)
312
+ : null;
313
+ const crossOwner = crossHit ? nodes[crossHit.nodeId] : null;
291
314
  const crossTappedElsewhere = crossHit?.nodeId === trunkBody?.nodeId || crossHit?.nodeId === endTrunkBody?.nodeId;
292
- let cross = crossHit && !crossTappedElsewhere && crossOwner?.type === 'duct-segment'
315
+ const cross = crossHit && !crossTappedElsewhere && crossOwner?.type === 'duct-segment'
293
316
  ? planCrossAtRunBody(crossOwner, crossHit, dir, profile)
294
317
  : null;
295
- if (remaining <= 0.08) {
296
- plans = [];
297
- tee = null;
298
- endTee = null;
299
- realigns = [];
300
- cross = null;
301
- ductStart = start;
302
- ductEnd = end;
303
- }
318
+ if (!hasFittingClearance(ductStart, ductEnd, dir, 0.08) ||
319
+ (trunkBody && !teePlan) ||
320
+ (endTrunkBody && !endTeePlan) ||
321
+ (crossHit && !crossTappedElsewhere && !cross))
322
+ return invalidPlan();
323
+ if (cross &&
324
+ (!hasFittingClearance(ductStart, cross.branchCollarNear, dir, 0.08) ||
325
+ !hasFittingClearance(cross.branchCollarFar, ductEnd, dir, 0.08)))
326
+ return invalidPlan();
304
327
  // Rect / oval continuity: roll the new run's cross-section so its
305
328
  // profile stays continuous with whatever either end joined.
306
329
  let roll = 0;
307
330
  if (profile.shape !== 'round') {
308
331
  const newDir = new Vector3(...dir);
309
- roll = continuityRollForRun(startPort, endPort, newDir);
332
+ roll = continuityRollForRun(startPort, endPort, newDir, nodes);
310
333
  }
311
334
  const defaults = ductSegmentDefinition.defaults();
312
- const toolDefaults = useEditor.getState().toolDefaults['duct-segment'] ?? {};
313
335
  const makeDuct = (from, to) => DuctSegmentNode.parse({
314
336
  ...defaults,
315
337
  ...toolDefaults,
338
+ autoHangers,
339
+ hangerStyle,
316
340
  name: profile.shape === 'rect' ? 'Trunk' : 'Duct run',
317
341
  path: [from, to],
318
342
  shape: profile.shape,
@@ -333,6 +357,7 @@ function planDuctDraw(start, end, startPort, startBody, endPort, endBody, profil
333
357
  : [makeDuct(ductStart, ductEnd)];
334
358
  const fittings = [
335
359
  ...plans.map((p) => p.fitting),
360
+ ...[startAdapter, endAdapter].flatMap((p) => (p ? [p.fitting] : [])),
336
361
  ...(tee ? [tee.fitting] : []),
337
362
  ...(endTee ? [endTee.fitting] : []),
338
363
  ...(cross ? [cross.fitting] : []),
@@ -349,587 +374,191 @@ function planDuctDraw(start, end, startPort, startBody, endPort, endBody, profil
349
374
  ...(cross ? [cross.trunkUpdate] : []),
350
375
  ...realigns.map((p) => p.update),
351
376
  ];
352
- return { fittings, ducts, tails, updates };
353
- }
354
- function ductEndPort(duct, id) {
355
- if (duct.path.length < 2)
356
- return null;
357
- const index = id === 'start' ? 0 : duct.path.length - 1;
358
- const neighborIndex = id === 'start' ? 1 : duct.path.length - 2;
359
- const position = duct.path[index];
360
- const neighbor = duct.path[neighborIndex];
361
- const dx = position[0] - neighbor[0];
362
- const dy = position[1] - neighbor[1];
363
- const dz = position[2] - neighbor[2];
364
- const len = Math.hypot(dx, dy, dz);
365
- const direction = len < 1e-9 ? [1, 0, 0] : [dx / len, dy / len, dz / len];
366
- return {
367
- id,
368
- nodeId: duct.id,
369
- position,
370
- direction,
371
- diameter: ductPortDiameterIn(duct),
372
- system: duct.system,
373
- };
377
+ const deleteIds = Array.from(new Set([
378
+ ...findMatedRunEndCapIds(startPort, nodes, 'duct-fitting'),
379
+ ...findMatedRunEndCapIds(endPort, nodes, 'duct-fitting'),
380
+ ]));
381
+ const firstDuct = ducts[0];
382
+ const nextDuct = ducts.at(-1);
383
+ const startEndCap = !startPort && !startBody && firstDuct ? createDuctRunEndCap(firstDuct, 'start') : null;
384
+ const nextEndCap = !endPort && !endBody && nextDuct ? createDuctRunEndCap(nextDuct) : null;
385
+ if (startEndCap)
386
+ fittings.push(startEndCap);
387
+ if (nextEndCap)
388
+ fittings.push(nextEndCap);
389
+ return { validationMessage: null, fittings, ducts, tails, updates, delete: deleteIds };
374
390
  }
375
391
  const DuctSegmentTool = () => {
376
- const activeLevelId = useViewer((s) => s.selection.levelId);
377
- const unit = useViewer((s) => s.unit);
392
+ const { activeLevelId, sceneApi, unit } = useRegistryToolContext();
378
393
  const cursorRef = useRef(null);
379
- // Cross-section profile for the next committed segment. Q toggles
380
- // round/rect, [ / ] steps the round diameter, and snapping the start
381
- // onto an existing run / fitting INHERITS that node's profile — so
382
- // continuing a 14×8 trunk keeps drawing 14×8, and branching off a
383
- // round collar keeps its diameter. Seeded from `toolDefaults`.
394
+ const continuationSeedRef = useRef(currentDuctContinuationSeed());
395
+ const continuationSeed = continuationSeedRef.current;
396
+ const hangerDefaults = useEditor((state) => state.toolDefaults['duct-segment']);
397
+ const initialAutoHangersRef = useRef(Boolean(hangerDefaults?.autoHangers ?? continuationSeed?.duct.autoHangers ?? false));
398
+ const autoHangers = useRunHangerMode((state) => state.enabled['duct-segment']);
399
+ const hangerStyle = (hangerDefaults?.hangerStyle ?? continuationSeed?.duct.hangerStyle) === 'double'
400
+ ? 'double'
401
+ : 'single';
402
+ const hangerStyleRef = useRef(hangerStyle);
403
+ hangerStyleRef.current = hangerStyle;
404
+ const autoHangersRef = useRef(autoHangers);
405
+ autoHangersRef.current = autoHangers;
406
+ const pendingPromotionRef = useRef(continuationSeed?.promotedFitting ?? null);
384
407
  const [profile, setProfile] = useState(() => {
385
408
  const defaults = ductSegmentDefinition.defaults();
386
409
  const seeded = useEditor.getState().toolDefaults['duct-segment'];
387
410
  return {
388
- shape: seeded?.shape ?? defaults.shape,
389
- diameter: seeded?.diameter ?? defaults.diameter,
390
- width: seeded?.width ?? defaults.width,
391
- height: seeded?.height ?? defaults.height,
411
+ shape: continuationSeed?.duct.shape ?? seeded?.shape ?? defaults.shape,
412
+ diameter: continuationSeed?.duct.diameter ?? seeded?.diameter ?? defaults.diameter,
413
+ width: continuationSeed?.duct.width ?? seeded?.width ?? defaults.width,
414
+ height: continuationSeed?.duct.height ?? seeded?.height ?? defaults.height,
392
415
  };
393
416
  });
394
- const [draftPoints, setDraftPoints] = useState([]);
395
- const [cursorPos, setCursorPos] = useState(null);
396
- // Ceiling mode (toggle with C): the first point lands at the level's
397
- // ceiling height (duct top hugging the ceiling) instead of the floor.
398
- const [ceilingMode, setCeilingMode] = useState(false);
399
- // The shared coordinate when the cursor is within snap range of an existing
400
- // duct (null = free placement). Drives the green cursor highlight so the
401
- // user sees the next click will join an existing run, not freeform-place.
402
- const [snapTarget, setSnapTarget] = useState(null);
403
- // In ceiling mode, the ceiling the cursor is currently under — rendered as
404
- // a translucent overlay so the duct reads as hung against a real surface
405
- // rather than a dot floating in space. Null when off-ceiling.
406
- const [hoverCeiling, setHoverCeiling] = useState(null);
407
- // True while Alt is held with a last point on the draft — drives the
408
- // vertical-cylinder ghost and the cursor HUD label.
409
- const [altActive, setAltActive] = useState(false);
410
- // What the in-flight cursor end currently snaps onto (port end, or a
411
- // run body for a tee / cross tap). Drives the auto-fitting GHOST so the
412
- // user sees the elbow / tee / cross the next click will mint.
413
- const [endSnap, setEndSnap] = useState({
414
- port: null,
415
- body: null,
416
- });
417
- // Mirror into refs so emitter callbacks (closing over the first render's
418
- // setState) read the latest values without re-subscribing.
419
- const draftRef = useRef(draftPoints);
420
- draftRef.current = draftPoints;
421
- const cursorPosRef = useRef(cursorPos);
422
- cursorPosRef.current = cursorPos;
423
417
  const profileRef = useRef(profile);
424
418
  profileRef.current = profile;
425
- const ceilingModeRef = useRef(ceilingMode);
426
- ceilingModeRef.current = ceilingMode;
427
- // Port the anchored START point snapped onto (null = free placement).
428
- // Read at commit so a turn off an existing run mints an elbow there.
429
- const startPortRef = useRef(null);
430
- // Centerline hit the anchored START point snapped onto (null = none).
431
- // Read at commit so a branch off a trunk's side mints a tee there.
432
- const startBodyRef = useRef(null);
433
- // Anchor captured when Alt is pressed: screen Y at that moment and the
434
- // base elevation (= last point's Y). Cleared on Alt release.
435
- const altAnchorRef = useRef(null);
436
- // Latest mouse clientY from grid:move; used so the Alt anchor knows where
437
- // the cursor was at key-press time.
438
- const lastClientYRef = useRef(null);
439
- const ghostFittings = useMemo(() => {
440
- const last = draftPoints.at(-1);
441
- if (!(activeLevelId && last && cursorPos) || altActive)
442
- return [];
443
- const fittings = planDuctDraw(last, cursorPos, startPortRef.current, startBodyRef.current, endSnap.port, endSnap.body, profile)?.fittings ?? [];
444
- return fittings.map((fitting, index) => ({
445
- ...fitting,
446
- id: `duct-fitting_live-draft-${index}`,
447
- parentId: activeLevelId,
448
- }));
449
- }, [activeLevelId, altActive, cursorPos, draftPoints, endSnap, profile]);
450
- useEffect(() => {
451
- usePathDraftPreview
452
- .getState()
453
- .setDraft('duct-segment', draftPoints, cursorPos, profile, ghostFittings);
454
- }, [cursorPos, draftPoints, ghostFittings, profile]);
455
- useEffect(() => () => usePathDraftPreview.getState().clear('duct-segment'), []);
456
419
  useEffect(() => {
457
- if (!activeLevelId)
458
- return;
459
- // Continuous chain: first click anchors the start, each following
460
- // click commits one two-point duct and uses that duct's far end as
461
- // the next anchor. No selection switch or finish gesture.
462
- //
463
- // All the auto-fitting decisions (elbow / tee / cross) live in the
464
- // shared `planDuctDraw` so the live ghost previews exactly what this
465
- // commit applies.
466
- const commitSegment = (start, end, endPort = null, endBody = null) => {
467
- const plan = planDuctDraw(start, end, startPortRef.current, startBodyRef.current, endPort, endBody, profileRef.current);
468
- if (!plan)
420
+ const mode = useRunHangerMode.getState();
421
+ mode.setEnabled('duct-segment', initialAutoHangersRef.current);
422
+ return () => mode.setEnabled('duct-segment', false);
423
+ }, []);
424
+ const run = useDistributionRunTool({
425
+ active: !!activeLevelId,
426
+ levelId: activeLevelId,
427
+ toolName: 'duct-segment',
428
+ initialStart: continuationSeed
429
+ ? [...(continuationSeed.port?.position ?? continuationSeed.body?.point ?? [0, 0, 0])]
430
+ : null,
431
+ initialConnection: continuationSeed
432
+ ? { port: continuationSeed.port, body: continuationSeed.body }
433
+ : null,
434
+ getPorts: () => getConnectionPorts(activeLevelId, sceneApi.nodes()),
435
+ findBody: (point) => findNearestRunBody3D(point, BODY_SNAP_RADIUS_M, { levelId: activeLevelId ?? undefined }),
436
+ surfaceClearance: (surface) => surface ? ductSurfaceClearanceM(profileRef.current, surface.kind === 'wall') : 0,
437
+ minimumSegmentLength: 0.08,
438
+ inheritFromConnection: ({ port }) => {
439
+ if (!port)
469
440
  return;
470
- // One atomic change: trim / split the joined runs, create the
471
- // fittings + the new duct. Single undo step.
472
- useScene.getState().applyNodeChanges({
441
+ const inherited = inheritProfile(port, sceneApi.nodes());
442
+ if (inherited)
443
+ setProfile(inherited);
444
+ },
445
+ commit: ({ start, end, startConnection, endConnection, surfaceTarget }) => {
446
+ if (!activeLevelId)
447
+ return null;
448
+ const promotedFitting = pendingPromotionRef.current;
449
+ const plan = planDuctDraw(start, end, promotedFitting ? null : startConnection.port, startConnection.body, endConnection.port, endConnection.body, profileRef.current, sceneApi.nodes(), surfaceTarget, autoHangersRef.current, hangerDefaults, hangerStyleRef.current);
450
+ if (!plan || plan.validationMessage)
451
+ return null;
452
+ const attachDuct = (node) => {
453
+ const wallAttachment = surfaceTarget?.kind === 'wall'
454
+ ? createRunWallAttachment(surfaceTarget.hostId, surfaceTarget.side, node.path[0], node.path.at(-1), surfaceTarget, ductSurfaceClearanceM(profileRef.current, true))
455
+ : undefined;
456
+ return { ...node, wallAttachment };
457
+ };
458
+ const ducts = plan.ducts.map(attachDuct);
459
+ const tails = plan.tails;
460
+ if (!sceneApi.applyChanges)
461
+ throw new Error('Registry SceneApi must support atomic changes');
462
+ sceneApi.applyChanges({
473
463
  create: [
474
464
  ...plan.fittings.map((node) => ({ node, parentId: activeLevelId })),
475
- ...plan.tails.map((node) => ({ node, parentId: activeLevelId })),
476
- ...plan.ducts.map((node) => ({ node, parentId: activeLevelId })),
465
+ ...tails.map((node) => ({ node, parentId: activeLevelId })),
466
+ ...ducts.map((node) => ({ node, parentId: activeLevelId })),
467
+ ],
468
+ update: [
469
+ ...(promotedFitting
470
+ ? [
471
+ {
472
+ id: promotedFitting.id,
473
+ data: {
474
+ name: promotedFitting.name,
475
+ fittingType: promotedFitting.fittingType,
476
+ rotation: promotedFitting.rotation,
477
+ branchAngle: promotedFitting.branchAngle,
478
+ shape2: promotedFitting.shape2,
479
+ width2: promotedFitting.width2,
480
+ height2: promotedFitting.height2,
481
+ diameter2: promotedFitting.diameter2,
482
+ },
483
+ },
484
+ ]
485
+ : []),
486
+ ...plan.updates,
477
487
  ],
478
- update: plan.updates,
488
+ delete: plan.delete,
479
489
  });
490
+ pendingPromotionRef.current = null;
480
491
  const nextDuct = plan.ducts.at(-1);
481
492
  const nextStart = nextDuct ? nextDuct.path[nextDuct.path.length - 1] : end;
482
- const nextPort = nextDuct ? ductEndPort(nextDuct, 'end') : endPort;
483
- triggerSFX('sfx:item-place');
484
- setDraftPoints([nextStart]);
485
- setSnapTarget(null);
486
- setEndSnap({ port: null, body: null });
487
- startPortRef.current = nextPort;
488
- startBodyRef.current = nextPort ? null : endBody;
489
- altAnchorRef.current = null;
490
- setAltActive(false);
491
- };
492
- // Y for a point at level-local `[x, z]`. Floor (0) when ceiling mode is
493
- // off. In ceiling mode, query the ceiling actually covering that point
494
- // and hang the duct just below it (centerline = ceiling underside −
495
- // half the duct's vertical dimension) so its top hugs the ceiling. Each
496
- // point follows its own ceiling, so a run stepping into a room with a
497
- // different ceiling height tracks that change. Points not under any
498
- // ceiling fall back to the floor.
499
- const resolveCeilingY = (x, z) => {
500
- if (!ceilingModeRef.current)
501
- return 0;
502
- const ceiling = getCeilingHeightAt(activeLevelId, useScene.getState().nodes, x, z);
503
- if (ceiling === null)
504
- return 0;
505
- const p = profileRef.current;
506
- const verticalIn = p.shape === 'round' ? p.diameter : p.height;
507
- return Math.max(0, ceiling - (verticalIn * 0.0254) / 2);
508
- };
509
- const resolveSnappedPoint = (event) => {
510
- // Port / body mating is the run's primary affordance; it stays on in
511
- // every snapping mode except `off` (the raw-cursor bypass).
512
- const snapEnabled = isGridSnapActive() || isMagneticSnapActive() || isAngleSnapActive();
513
- const last = draftRef.current.at(-1);
514
- // First point of the run: grid-snapped placement. Y follows the
515
- // ceiling under the cursor in ceiling mode (floor otherwise).
516
- // Endpoint snap can still join an existing run.
517
- if (!last) {
518
- const baseY = resolveCeilingY(event.localPosition[0], event.localPosition[2]);
519
- const raw = [
520
- event.localPosition[0],
521
- baseY,
522
- event.localPosition[2],
523
- ];
524
- const step = isGridSnapActive() ? useEditor.getState().gridSnapStep : 0;
525
- if (event.nativeEvent?.altKey !== true && snapEnabled) {
526
- const target = findNearbyPort(raw);
527
- if (target)
528
- return {
529
- point: portPoint(target),
530
- snapped: portPoint(target),
531
- port: target,
532
- body: null,
533
- };
534
- // No open end nearby — try the side of a run (tee tap). Probe
535
- // with a grid-snapped cursor so the tap steps along the duct
536
- // like every other placement; `off` mode (step 0) rides smoothly.
537
- const probe = [snap(raw[0], step), baseY, snap(raw[2], step)];
538
- const body = findNearestRunBodyXZ(probe, BODY_SNAP_RADIUS_M);
539
- if (body)
540
- return { point: body.point, snapped: body.point, port: null, body };
541
- }
542
- const sx = snap(raw[0], step);
543
- const sz = snap(raw[2], step);
544
- return {
545
- point: [sx, resolveCeilingY(sx, sz), sz],
546
- snapped: null,
547
- port: null,
548
- body: null,
549
- };
550
- }
551
- // Subsequent points: angle-locked to 45° from `last` in `angles` mode.
552
- // Y inherits `last[1]` for the angle/probe math; the free placement below
553
- // re-resolves it from the ceiling under the point in ceiling mode, so a run
554
- // stepping into a room with a different ceiling height tracks that change.
555
- // Depth changes otherwise come from Alt-vertical risers.
556
- const rawXZ = [
557
- event.localPosition[0],
558
- last[1],
559
- event.localPosition[2],
560
- ];
561
- // The 45° lock is now the `angles` snapping mode (Shift cycles to it),
562
- // not a held key.
563
- const angled = isAngleSnapActive() ? projectToAngleLock(last, rawXZ) : rawXZ;
564
- const step = isGridSnapActive() ? useEditor.getState().gridSnapStep : 0;
565
- // Port snap (Alt bypass) — checked against the RAW cursor, not the
566
- // angle-locked projection, so a port slightly off the 45° ray can
567
- // still capture the cursor. Joining beats the lock.
568
- if (event.nativeEvent?.altKey !== true && snapEnabled) {
569
- const target = findNearbyPort(rawXZ);
570
- if (target)
571
- return { point: portPoint(target), snapped: portPoint(target), port: target, body: null };
572
- // No open end nearby — landing on the side of a run taps a tee
573
- // there (mirror of the first-point tee tap). Probe with a
574
- // grid-snapped cursor so the tap steps along the duct instead of
575
- // sliding smoothly (Shift above frees it). Checked against the
576
- // cursor, not the 45° projection, so a slightly-off trunk captures.
577
- const probe = [
578
- snap(rawXZ[0], step),
579
- rawXZ[1],
580
- snap(rawXZ[2], step),
581
- ];
582
- const body = findNearestRunBodyXZ(probe, BODY_SNAP_RADIUS_M);
583
- if (body)
584
- return { point: body.point, snapped: body.point, port: null, body };
585
- }
586
- const fx = snap(angled[0], step);
587
- const fz = snap(angled[2], step);
588
- const fy = ceilingModeRef.current ? resolveCeilingY(fx, fz) : angled[1];
493
+ const nextPort = nextDuct ? ductEndpointPort(nextDuct, 'end') : endConnection.port;
589
494
  return {
590
- point: [fx, fy, fz],
591
- snapped: null,
592
- port: null,
593
- body: null,
495
+ nextStart,
496
+ nextConnection: {
497
+ port: nextPort,
498
+ body: nextPort ? null : endConnection.body,
499
+ },
594
500
  };
595
- };
596
- /**
597
- * Compute the Alt-mode cursor position: XZ locked to the last point,
598
- * Y driven by how far the mouse has moved vertically on screen since
599
- * Alt was pressed. Returns null if there's no anchor (Alt not active).
600
- */
601
- const resolveAltVerticalPoint = (clientY) => {
602
- const anchor = altAnchorRef.current;
603
- const last = draftRef.current.at(-1);
604
- if (!anchor || !last)
605
- return null;
606
- const step = isGridSnapActive() ? useEditor.getState().gridSnapStep : 0;
607
- // Screen +Y points down, so subtract to map "drag up = raise Y".
608
- const dy = (anchor.clientY - clientY) / ALT_PIXELS_PER_METER;
609
- const snappedDy = snap(dy, step);
610
- const y = Math.min(ALT_Y_MAX_M, Math.max(ALT_Y_MIN_M, anchor.baseY + snappedDy));
611
- return [last[0], y, last[2]];
612
- };
613
- // Resolve the cursor point (port / body / grid / angle snap) and then
614
- // layer Figma-style alignment on top so a run lines up with other runs,
615
- // fittings, and items as it's drawn. A free point (first vertex, or no
616
- // angle lock) snaps; an angle-locked continuation shows the guide passively
617
- // without leaving its 45° ray. Alignment follows the `lines` mode; a
618
- // port / body snap or Alt-vertical bypasses it.
619
- const resolveAlignedPoint = (event) => {
620
- const r = resolveSnappedPoint(event);
621
- const hasStart = draftRef.current.length > 0;
622
- const alt = event.nativeEvent?.altKey === true;
623
- const point = alignDrawPoint(r.point, {
624
- applySnap: isMagneticSnapActive() && (!hasStart || !isAngleSnapActive()),
625
- bypass: alt || r.snapped !== null,
626
- });
627
- return { ...r, point };
628
- };
629
- // The ceiling the cursor is under (ceiling mode only) — drives the
630
- // translucent surface overlay so the in-flight point reads as hung
631
- // against a real ceiling. Cleared when off-ceiling or out of mode.
632
- const updateHoverCeiling = (x, z) => {
633
- if (!ceilingModeRef.current) {
634
- setHoverCeiling(null);
635
- return;
636
- }
637
- setHoverCeiling(getCeilingAt(activeLevelId, useScene.getState().nodes, x, z));
638
- };
639
- const onMove = (event) => {
640
- const clientY = event.nativeEvent?.clientY;
641
- if (typeof clientY === 'number')
642
- lastClientYRef.current = clientY;
643
- // Alt vertical mode wins over the XZ logic.
644
- if (altAnchorRef.current && typeof clientY === 'number') {
645
- const point = resolveAltVerticalPoint(clientY);
646
- if (point) {
647
- clearDrawAlignment();
648
- setCursorPos(point);
649
- setSnapTarget(null);
650
- setEndSnap({ port: null, body: null });
651
- updateHoverCeiling(point[0], point[2]);
652
- return;
501
+ },
502
+ onShortcut: (event) => {
503
+ if (event.key === '[' || event.key === ']') {
504
+ event.preventDefault();
505
+ const next = stepNominalRunSize(DUCT_DIAMETERS_IN, profileRef.current.diameter, event.key === ']' ? 1 : -1);
506
+ if (next !== profileRef.current.diameter) {
507
+ setProfile((current) => ({ ...current, diameter: next }));
508
+ triggerSFX('sfx:grid-snap');
653
509
  }
654
510
  }
655
- const { point, snapped, port, body } = resolveAlignedPoint(event);
656
- setCursorPos(point);
657
- setSnapTarget(snapped);
658
- setEndSnap({ port, body: port ? null : body });
659
- updateHoverCeiling(point[0], point[2]);
660
- };
661
- const onClick = (event) => {
662
- const start = draftRef.current.at(-1);
663
- // Vertical mode with a start anchored: the click commits the riser
664
- // segment right there. Never falls through to the XZ logic — a
665
- // no-op Alt click (height unchanged) must not place anything.
666
- if (altAnchorRef.current && start) {
667
- const clientY = event.nativeEvent?.clientY ?? lastClientYRef.current;
668
- if (typeof clientY === 'number') {
669
- const point = resolveAltVerticalPoint(clientY);
670
- if (point && Math.abs(point[1] - start[1]) >= 1e-4) {
671
- commitSegment(start, point);
672
- }
673
- }
674
- return;
675
- }
676
- const { point, port, body } = resolveAlignedPoint(event);
677
- if (!start) {
678
- // First click: anchor the segment start, remembering the port or
679
- // run body it snapped to so the commit can mint an elbow / tee.
680
- // Joining a port INHERITS the source's cross-section — continuing
681
- // a rect trunk keeps drawing rect at its W×H, a round collar its
682
- // diameter. Body taps (tee branches) keep the tool's own profile.
683
- triggerSFX('sfx:grid-snap');
684
- startPortRef.current = port;
685
- startBodyRef.current = port ? null : body;
686
- if (port) {
687
- const inherited = inheritProfile(port);
688
- if (inherited)
689
- setProfile(inherited);
690
- }
691
- setDraftPoints([point]);
692
- return;
693
- }
694
- // Second click: commit the segment and re-arm. A body hit on the end
695
- // (no end port) taps a tee into that run's side.
696
- commitSegment(start, point, port, port ? null : body);
697
- };
698
- const enterAltMode = () => {
699
- const last = draftRef.current.at(-1);
700
- if (!last || lastClientYRef.current === null)
701
- return;
702
- if (altAnchorRef.current)
703
- return;
704
- altAnchorRef.current = { clientY: lastClientYRef.current, baseY: last[1] };
705
- setAltActive(true);
706
- };
707
- const exitAltMode = () => {
708
- if (!altAnchorRef.current)
709
- return;
710
- altAnchorRef.current = null;
711
- setAltActive(false);
712
- };
713
- const stepDiameter = (step) => {
714
- const sizes = DUCT_DIAMETERS_IN;
715
- const current = profileRef.current.diameter;
716
- // Nearest catalogue index, then step — handles seeded off-catalogue
717
- // values (e.g. a preset's 7.5") gracefully.
718
- let nearest = 0;
719
- for (let i = 1; i < sizes.length; i++) {
720
- if (Math.abs(sizes[i] - current) < Math.abs(sizes[nearest] - current))
721
- nearest = i;
722
- }
723
- const next = sizes[Math.min(sizes.length - 1, Math.max(0, nearest + step))];
724
- if (next === current)
725
- return;
726
- setProfile((p) => ({ ...p, diameter: next }));
727
- triggerSFX('sfx:grid-snap');
728
- };
729
- const onKeyDown = (e) => {
730
- const tag = e.target?.tagName;
731
- if (tag === 'INPUT' || tag === 'TEXTAREA')
732
- return;
733
- if (e.key === 'Alt') {
734
- e.preventDefault();
735
- enterAltMode();
736
- }
737
- else if (e.key === '[') {
738
- e.preventDefault();
739
- stepDiameter(-1);
740
- }
741
- else if (e.key === ']') {
742
- e.preventDefault();
743
- stepDiameter(1);
744
- }
745
- else if (e.key === 'q' || e.key === 'Q') {
746
- e.preventDefault();
747
- setProfile((p) => ({ ...p, shape: p.shape === 'round' ? 'rect' : 'round' }));
511
+ else if (event.key === 'q' || event.key === 'Q') {
512
+ event.preventDefault();
513
+ setProfile((current) => ({
514
+ ...current,
515
+ shape: current.shape === 'round' ? 'rect' : current.shape === 'rect' ? 'oval' : 'round',
516
+ }));
748
517
  triggerSFX('sfx:grid-snap');
749
518
  }
750
- else if (e.key === 'c' || e.key === 'C') {
751
- // Toggle ceiling mode: points hang from the ceiling above them
752
- // (duct top hugging the ceiling) instead of sitting on the floor.
753
- // Only flip while unanchored — already-placed points keep their Y,
754
- // so a mid-run toggle would split a run across two height regimes.
755
- if (draftRef.current.length > 0)
756
- return;
757
- e.preventDefault();
758
- setCeilingMode((m) => !m);
759
- setHoverCeiling(null);
519
+ else if (event.key === 'h' || event.key === 'H') {
520
+ event.preventDefault();
521
+ useRunHangerMode.getState().toggle('duct-segment');
760
522
  triggerSFX('sfx:grid-snap');
761
523
  }
762
- };
763
- const onKeyUp = (e) => {
764
- if (e.key === 'Alt') {
765
- e.preventDefault();
766
- exitAltMode();
767
- }
768
- };
769
- const onCancel = () => {
770
- clearDrawAlignment();
771
- if (draftRef.current.length === 0)
772
- return;
773
- markToolCancelConsumed();
774
- setDraftPoints([]);
775
- setCursorPos(null);
776
- setSnapTarget(null);
777
- setEndSnap({ port: null, body: null });
778
- setHoverCeiling(null);
779
- startPortRef.current = null;
780
- startBodyRef.current = null;
781
- };
782
- emitter.on('grid:move', onMove);
783
- emitter.on('grid:click', onClick);
784
- emitter.on('tool:cancel', onCancel);
785
- window.addEventListener('keydown', onKeyDown);
786
- window.addEventListener('keyup', onKeyUp);
787
- return () => {
788
- emitter.off('grid:move', onMove);
789
- emitter.off('grid:click', onClick);
790
- emitter.off('tool:cancel', onCancel);
791
- window.removeEventListener('keydown', onKeyDown);
792
- window.removeEventListener('keyup', onKeyUp);
793
- altAnchorRef.current = null;
794
- clearDrawAlignment();
795
- };
796
- }, [activeLevelId]);
524
+ },
525
+ });
526
+ const previewPlan = useMemo(() => {
527
+ if (!(activeLevelId && run.start && run.cursor))
528
+ return null;
529
+ return planDuctDraw(run.start, run.cursor, pendingPromotionRef.current ? null : run.startConnection.port, run.startConnection.body, run.endConnection.port, run.endConnection.body, profile, sceneApi.nodes(), run.surfaceTarget, autoHangers, hangerDefaults, hangerStyle);
530
+ }, [
531
+ activeLevelId,
532
+ autoHangers,
533
+ hangerDefaults,
534
+ hangerStyle,
535
+ profile,
536
+ run.start,
537
+ run.cursor,
538
+ run.startConnection,
539
+ run.endConnection,
540
+ run.surfaceTarget,
541
+ sceneApi,
542
+ ]);
543
+ const ghostFittings = useMemo(() => previewPlan?.fittings ?? [], [previewPlan]);
544
+ useEffect(() => {
545
+ usePathDraftPreview
546
+ .getState()
547
+ .setDraft('duct-segment', run.start ? [run.start] : [], run.cursor, { ...profile, autoHangers, hangerStyle }, ghostFittings);
548
+ }, [autoHangers, hangerStyle, ghostFittings, profile, run.cursor, run.start]);
549
+ useEffect(() => () => usePathDraftPreview.getState().clear('duct-segment'), []);
550
+ useEffect(() => () => useEditor.getState().setToolDefaults('duct-segment', null), []);
797
551
  if (!activeLevelId)
798
552
  return null;
799
- const previewSegments = [];
800
- for (let i = 0; i < draftPoints.length - 1; i++) {
801
- previewSegments.push({ a: draftPoints[i], b: draftPoints[i + 1] });
802
- }
803
- const last = draftPoints.at(-1);
804
- if (last && cursorPos) {
805
- previewSegments.push({ a: last, b: cursorPos });
806
- }
807
- // Wall-style dimension pill above the cursor: absolute world coords before
808
- // the first point, signed per-axis deltas from the last placed point while
809
- // a segment is in flight. The actively-driven axis is emphasised — Y in
810
- // Alt-vertical mode, otherwise whichever horizontal axis dominates. A
811
- // trailing Ø readout shows the diameter the next click commits ([ / ]).
812
- const pillParts = cursorPos
813
- ? [
814
- ...['x', 'y', 'z'].map((axis, i) => ({
815
- key: axis,
816
- prefix: axis.toUpperCase(),
817
- value: last ? cursorPos[i] - last[i] : cursorPos[i],
818
- signed: !!last,
819
- })),
820
- ...(profile.shape === 'round'
821
- ? [{ key: 'diameter', prefix: 'Ø', value: profile.diameter * 0.0254, signed: false }]
822
- : [
823
- { key: 'trunk-w', prefix: 'W', value: profile.width * 0.0254, signed: false },
824
- { key: 'trunk-h', prefix: 'H', value: profile.height * 0.0254, signed: false },
825
- ]),
826
- ]
827
- : null;
828
- const pillPrimary = last && cursorPos
829
- ? altActive
830
- ? 'y'
831
- : Math.abs(cursorPos[0] - last[0]) >= Math.abs(cursorPos[2] - last[2])
832
- ? 'x'
833
- : 'z'
834
- : undefined;
835
- // When the in-flight point hangs above the floor (ceiling mode, or an
836
- // Alt riser), the cursor marker itself rides AT the point (where the
837
- // mouse is aiming and the next click commits), and a plumb line drops
838
- // straight down to a faint ground ring on the floor below — so the plan
839
- // position stays legible from any angle. A floor-level point keeps the
840
- // standard fixed-height cursor look.
841
- const cursorElevation = cursorPos ? cursorPos[1] : 0;
842
- const isElevated = cursorElevation > 0.001;
843
- const cursorGround = cursorPos
844
- ? [cursorPos[0], 0, cursorPos[2]]
845
- : null;
846
- return (_jsxs(LevelOffsetGroup, { children: [ceilingMode && hoverCeiling && _jsx(CeilingHighlight, { ceiling: hoverCeiling }), cursorPos && cursorGround && (_jsxs(_Fragment, { children: [isElevated ? (_jsx(CursorSphere, { color: snapTarget ? SNAP_CURSOR_COLOR : undefined, dotAtTip: true, height: cursorElevation, position: cursorGround, ref: cursorRef })) : (_jsx(CursorSphere, { color: snapTarget ? SNAP_CURSOR_COLOR : undefined, position: cursorPos, ref: cursorRef })), pillParts && (_jsx("group", { position: cursorPos, children: _jsx(Html, { center: true, position: [0, 1.45, 0], style: { pointerEvents: 'none', userSelect: 'none' }, zIndexRange: [100, 0], children: _jsxs("div", { className: "flex flex-col items-center gap-2", children: [ceilingMode && !last && (_jsx("div", { className: "whitespace-nowrap rounded-full border border-border/60 bg-background/90 px-3 py-0.5 text-[10px] text-muted-foreground shadow-sm backdrop-blur", children: "Ceiling \u00B7 C to toggle" })), _jsx(DimensionPill, { parts: pillParts, primary: pillPrimary, unit: unit })] }) }) }))] })), draftPoints.map((p, i) => (_jsxs("mesh", { layers: EDITOR_LAYER, position: p, children: [_jsx("sphereGeometry", { args: [0.07, 16, 12] }), _jsx("meshBasicMaterial", { color: "#818cf8", depthTest: false })] }, `pt-${i}`))), previewSegments.map((seg, i) => (_jsx(PreviewSegment, { a: seg.a, b: seg.b, endPort: endSnap.port, profile: profile, startPort: startPortRef.current }, `seg-${i}`))), ghostFittings.map((fitting) => (_jsx(FittingGhost, { fitting: fitting }, fitting.id)))] }));
553
+ const extraParts = profile.shape === 'round'
554
+ ? [{ key: 'diameter', prefix: 'Ø', value: profile.diameter * 0.0254 }]
555
+ : [
556
+ { key: 'trunk-w', prefix: 'W', value: profile.width * 0.0254 },
557
+ { key: 'trunk-h', prefix: 'H', value: profile.height * 0.0254 },
558
+ ];
559
+ return (_jsxs(LevelOffsetGroup, { children: [_jsx(ConnectionFeedback, { point: run.cursor, target: run.endConnection.port, levelId: activeLevelId, profile: {
560
+ ...profile,
561
+ system: String(hangerDefaults?.system ?? ductSegmentDefinition.defaults().system),
562
+ } }), _jsx(DistributionRunCursor, { altActive: run.altActive, cursor: run.cursor, cursorRef: cursorRef, directionMode: run.directionMode, extraParts: extraParts, lengthInput: run.lengthInput, onLengthInputChange: run.onLengthInputChange, onDirectionSelect: run.onDirectionSelect, validationMessage: previewPlan?.validationMessage ?? run.validationMessage, snapTarget: run.snapTarget, snapScreen: run.snapScreen, start: run.start, startDirection: run.startConnection.port?.direction ?? null, unit: unit }), run.start && (_jsxs("mesh", { layers: EDITOR_LAYER, position: run.start, children: [_jsx("sphereGeometry", { args: [0.07, 16, 12] }), _jsx("meshBasicMaterial", { color: "#818cf8", depthTest: false })] })), previewPlan?.ducts.map((duct, index) => (_jsx(DuctSegmentGhost, { duct: duct }, index))), previewPlan?.ducts.map((duct, index) => (_jsx(RunHangerPreview, { run: duct, levelId: activeLevelId }, `hanger-${index}`))), ghostFittings.map((fitting) => (_jsx(FittingGhost, { fitting: fitting }, fitting.id)))] }));
847
563
  };
848
- /**
849
- * Build a horizontal `ShapeGeometry` for a ceiling polygon (with holes) in
850
- * level-local XZ, laid flat in the XZ plane. Mirrors the ceiling renderer /
851
- * move-tool convention (Z negated, then rotated onto the floor plane).
852
- */
853
- function buildCeilingShape(polygon, holes) {
854
- if (polygon.length < 3)
855
- return null;
856
- const shape = new Shape();
857
- const first = polygon[0];
858
- shape.moveTo(first[0], -first[1]);
859
- for (let i = 1; i < polygon.length; i++) {
860
- const pt = polygon[i];
861
- shape.lineTo(pt[0], -pt[1]);
862
- }
863
- shape.closePath();
864
- for (const holePolygon of holes) {
865
- if (holePolygon.length < 3)
866
- continue;
867
- const hole = new Path();
868
- const hf = holePolygon[0];
869
- hole.moveTo(hf[0], -hf[1]);
870
- for (let i = 1; i < holePolygon.length; i++) {
871
- const pt = holePolygon[i];
872
- hole.lineTo(pt[0], -pt[1]);
873
- }
874
- hole.closePath();
875
- shape.holes.push(hole);
876
- }
877
- const geometry = new ShapeGeometry(shape);
878
- geometry.rotateX(-Math.PI / 2);
879
- return geometry;
880
- }
881
- /**
882
- * Translucent overlay of the ceiling the cursor is under, drawn at the
883
- * ceiling's own height. Gives the in-flight duct point a real surface to
884
- * read against, so "hung against the ceiling" is visible from any angle
885
- * instead of being a dot floating in space.
886
- */
887
- function CeilingHighlight({ ceiling }) {
888
- const geometry = useMemo(() => buildCeilingShape(ceiling.polygon, ceiling.holes), [ceiling.polygon, ceiling.holes]);
889
- const outline = useMemo(() => {
890
- if (ceiling.polygon.length < 2)
891
- return null;
892
- const pts = ceiling.polygon.map(([x, z]) => new Vector3(x, 0, z));
893
- const f = ceiling.polygon[0];
894
- pts.push(new Vector3(f[0], 0, f[1]));
895
- return pts;
896
- }, [ceiling.polygon]);
897
- if (!geometry)
898
- return null;
899
- const y = resolveCeilingHeight(ceiling, useScene.getState().nodes);
900
- return (_jsxs("group", { position: [0, y, 0], children: [_jsx("mesh", { geometry: geometry, layers: EDITOR_LAYER, renderOrder: 1, children: _jsx("meshBasicMaterial", { color: "#818cf8", depthWrite: false, opacity: 0.15, side: DoubleSide, transparent: true }) }), outline && (_jsxs("line", { children: [_jsx("bufferGeometry", { ref: (g) => {
901
- if (g)
902
- g.setFromPoints(outline);
903
- } }), _jsx("lineBasicMaterial", { color: "#818cf8", opacity: 0.6, transparent: true })] }))] }));
904
- }
905
- function PreviewSegment({ a, b, profile, startPort, endPort, }) {
906
- const start = new Vector3(...a);
907
- const end = new Vector3(...b);
908
- const dir = new Vector3().subVectors(end, start);
909
- const length = dir.length();
910
- if (length < 1e-4)
911
- return null;
912
- dir.normalize();
913
- const mid = new Vector3().addVectors(start, end).multiplyScalar(0.5);
914
- // Rect AND oval ghost as a box — close enough for a translucent guide.
915
- if (profile.shape !== 'round') {
916
- const w = profile.width * 0.0254;
917
- const h = profile.height * 0.0254;
918
- return (_jsxs("mesh", { layers: EDITOR_LAYER, position: mid.toArray(), ref: (m) => {
919
- if (!m)
920
- return;
921
- // Same basis AND roll as the commit will use, so the ghost
922
- // shows the orientation that actually lands.
923
- const roll = continuityRollForRun(startPort, endPort, dir);
924
- const { width: x, height: z } = rectSectionAxes(dir, roll);
925
- m.quaternion.setFromRotationMatrix(new Matrix4().makeBasis(x, dir, z));
926
- }, children: [_jsx("boxGeometry", { args: [w, length, h] }), _jsx("meshBasicMaterial", { color: "#818cf8", depthTest: false, opacity: PREVIEW_OPACITY, transparent: true })] }));
927
- }
928
- const radius = (profile.diameter * 0.0254) / 2;
929
- return (_jsxs("mesh", { layers: EDITOR_LAYER, position: mid.toArray(), ref: (m) => {
930
- if (!m)
931
- return;
932
- m.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir);
933
- }, children: [_jsx("cylinderGeometry", { args: [radius, radius, length, 24, 1, false] }), _jsx("meshBasicMaterial", { color: "#818cf8", depthTest: false, opacity: PREVIEW_OPACITY, transparent: true })] }));
934
- }
935
564
  export default DuctSegmentTool;