@pascal-app/nodes 0.1.1 → 1.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (408) hide show
  1. package/README.md +34 -0
  2. package/dist/building/definition.js +1 -1
  3. package/dist/cabinet/definition.d.ts.map +1 -1
  4. package/dist/cabinet/definition.js +809 -72
  5. package/dist/cabinet/geometry.d.ts.map +1 -1
  6. package/dist/cabinet/geometry.js +4 -0
  7. package/dist/cabinet/move-frame.js +3 -3
  8. package/dist/cabinet/panel.d.ts.map +1 -1
  9. package/dist/cabinet/panel.js +1 -0
  10. package/dist/cabinet/placement-snap.d.ts +8 -0
  11. package/dist/cabinet/placement-snap.d.ts.map +1 -0
  12. package/dist/cabinet/placement-snap.js +19 -0
  13. package/dist/cabinet/presets.js +8 -8
  14. package/dist/cabinet/quick-actions.d.ts.map +1 -1
  15. package/dist/cabinet/quick-actions.js +9 -3
  16. package/dist/cabinet/resize-limits.d.ts +11 -0
  17. package/dist/cabinet/resize-limits.d.ts.map +1 -0
  18. package/dist/cabinet/resize-limits.js +22 -0
  19. package/dist/cabinet/run-layout.d.ts +8 -2
  20. package/dist/cabinet/run-layout.d.ts.map +1 -1
  21. package/dist/cabinet/run-layout.js +59 -7
  22. package/dist/cabinet/run-ops.d.ts +44 -3
  23. package/dist/cabinet/run-ops.d.ts.map +1 -1
  24. package/dist/cabinet/run-ops.js +646 -110
  25. package/dist/cabinet/run-panel.d.ts +2 -1
  26. package/dist/cabinet/run-panel.d.ts.map +1 -1
  27. package/dist/cabinet/run-panel.js +38 -4
  28. package/dist/cabinet/stack-transitions.js +4 -4
  29. package/dist/cabinet/tool.d.ts.map +1 -1
  30. package/dist/cabinet/tool.js +95 -17
  31. package/dist/cabinet/wall-snap.d.ts.map +1 -1
  32. package/dist/cabinet/wall-snap.js +2 -6
  33. package/dist/ceiling/boundary-editor.d.ts.map +1 -1
  34. package/dist/ceiling/boundary-editor.js +2 -2
  35. package/dist/ceiling/definition.d.ts +1 -1
  36. package/dist/ceiling/definition.d.ts.map +1 -1
  37. package/dist/ceiling/definition.js +36 -5
  38. package/dist/ceiling/floorplan-affordances.d.ts +4 -4
  39. package/dist/ceiling/floorplan-move.d.ts +4 -4
  40. package/dist/ceiling/floorplan-move.d.ts.map +1 -1
  41. package/dist/ceiling/floorplan-move.js +5 -4
  42. package/dist/ceiling/hole-editor.d.ts.map +1 -1
  43. package/dist/ceiling/hole-editor.js +2 -2
  44. package/dist/ceiling/move-tool.d.ts.map +1 -1
  45. package/dist/ceiling/move-tool.js +4 -3
  46. package/dist/ceiling/panel.d.ts.map +1 -1
  47. package/dist/ceiling/panel.js +65 -3
  48. package/dist/ceiling/renderer.d.ts.map +1 -1
  49. package/dist/ceiling/renderer.js +6 -2
  50. package/dist/ceiling/tool.d.ts.map +1 -1
  51. package/dist/ceiling/tool.js +12 -1
  52. package/dist/chimney/definition.d.ts.map +1 -1
  53. package/dist/chimney/definition.js +1 -0
  54. package/dist/chimney/tool.d.ts.map +1 -1
  55. package/dist/chimney/tool.js +16 -1
  56. package/dist/column/definition.d.ts.map +1 -1
  57. package/dist/column/definition.js +14 -2
  58. package/dist/column/floorplan-affordances.d.ts.map +1 -1
  59. package/dist/column/floorplan-affordances.js +19 -14
  60. package/dist/column/floorplan-move.d.ts.map +1 -1
  61. package/dist/column/floorplan-move.js +7 -2
  62. package/dist/column/floorplan.d.ts +9 -1
  63. package/dist/column/floorplan.d.ts.map +1 -1
  64. package/dist/column/floorplan.js +59 -4
  65. package/dist/column/tool.d.ts.map +1 -1
  66. package/dist/column/tool.js +33 -7
  67. package/dist/construction-dimension/definition.d.ts +4 -0
  68. package/dist/construction-dimension/definition.d.ts.map +1 -0
  69. package/dist/construction-dimension/definition.js +83 -0
  70. package/dist/construction-dimension/drawing-coordination.d.ts +7 -0
  71. package/dist/construction-dimension/drawing-coordination.d.ts.map +1 -0
  72. package/dist/construction-dimension/drawing-coordination.js +54 -0
  73. package/dist/construction-dimension/floorplan-affordances.d.ts +4 -0
  74. package/dist/construction-dimension/floorplan-affordances.d.ts.map +1 -0
  75. package/dist/construction-dimension/floorplan-affordances.js +114 -0
  76. package/dist/construction-dimension/floorplan-tool.d.ts +21 -0
  77. package/dist/construction-dimension/floorplan-tool.d.ts.map +1 -0
  78. package/dist/construction-dimension/floorplan-tool.js +557 -0
  79. package/dist/construction-dimension/floorplan.d.ts +3 -0
  80. package/dist/construction-dimension/floorplan.d.ts.map +1 -0
  81. package/dist/construction-dimension/floorplan.js +457 -0
  82. package/dist/construction-dimension/geometry.d.ts +31 -0
  83. package/dist/construction-dimension/geometry.d.ts.map +1 -0
  84. package/dist/construction-dimension/geometry.js +144 -0
  85. package/dist/construction-dimension/index.d.ts +4 -0
  86. package/dist/construction-dimension/index.d.ts.map +1 -0
  87. package/dist/construction-dimension/index.js +3 -0
  88. package/dist/construction-dimension/panel.d.ts +2 -0
  89. package/dist/construction-dimension/panel.d.ts.map +1 -0
  90. package/dist/construction-dimension/panel.js +128 -0
  91. package/dist/construction-dimension/parametrics.d.ts +3 -0
  92. package/dist/construction-dimension/parametrics.d.ts.map +1 -0
  93. package/dist/construction-dimension/parametrics.js +4 -0
  94. package/dist/construction-dimension/schema.d.ts +2 -0
  95. package/dist/construction-dimension/schema.d.ts.map +1 -0
  96. package/dist/construction-dimension/schema.js +1 -0
  97. package/dist/door/contextual-dimensions.d.ts +3 -0
  98. package/dist/door/contextual-dimensions.d.ts.map +1 -0
  99. package/dist/door/contextual-dimensions.js +7 -0
  100. package/dist/door/definition.d.ts.map +1 -1
  101. package/dist/door/definition.js +13 -8
  102. package/dist/door/floorplan-affordances.d.ts +2 -5
  103. package/dist/door/floorplan-affordances.d.ts.map +1 -1
  104. package/dist/door/floorplan-affordances.js +9 -18
  105. package/dist/door/floorplan.d.ts +3 -4
  106. package/dist/door/floorplan.d.ts.map +1 -1
  107. package/dist/door/floorplan.js +88 -6
  108. package/dist/door/move-tool.d.ts.map +1 -1
  109. package/dist/door/move-tool.js +2 -2
  110. package/dist/door/panel.d.ts.map +1 -1
  111. package/dist/door/panel.js +3 -1
  112. package/dist/door/schema.d.ts +1 -1
  113. package/dist/door/schema.d.ts.map +1 -1
  114. package/dist/door/schema.js +1 -1
  115. package/dist/door/tool.d.ts.map +1 -1
  116. package/dist/door/tool.js +63 -6
  117. package/dist/dormer/definition.d.ts.map +1 -1
  118. package/dist/dormer/definition.js +1 -0
  119. package/dist/dormer/tool.d.ts.map +1 -1
  120. package/dist/dormer/tool.js +23 -1
  121. package/dist/duct-segment/tool.d.ts.map +1 -1
  122. package/dist/duct-segment/tool.js +20 -9
  123. package/dist/duct-terminal/tool.d.ts.map +1 -1
  124. package/dist/duct-terminal/tool.js +9 -6
  125. package/dist/elevator/floorplan-affordances.d.ts +2 -6
  126. package/dist/elevator/floorplan-affordances.d.ts.map +1 -1
  127. package/dist/elevator/floorplan-affordances.js +13 -12
  128. package/dist/fence/actions/move-endpoint.d.ts.map +1 -1
  129. package/dist/fence/actions/move-endpoint.js +29 -1
  130. package/dist/fence/definition.d.ts.map +1 -1
  131. package/dist/fence/definition.js +74 -14
  132. package/dist/fence/floorplan-affordances.d.ts.map +1 -1
  133. package/dist/fence/floorplan-affordances.js +48 -11
  134. package/dist/fence/floorplan-move.d.ts.map +1 -1
  135. package/dist/fence/floorplan-move.js +38 -3
  136. package/dist/fence/geometry.d.ts.map +1 -1
  137. package/dist/fence/geometry.js +15 -1
  138. package/dist/fence/lift.d.ts +38 -0
  139. package/dist/fence/lift.d.ts.map +1 -0
  140. package/dist/fence/lift.js +51 -0
  141. package/dist/fence/move-tool.d.ts.map +1 -1
  142. package/dist/fence/move-tool.js +25 -5
  143. package/dist/fence/system.d.ts +3 -0
  144. package/dist/fence/system.d.ts.map +1 -0
  145. package/dist/fence/system.js +46 -0
  146. package/dist/fence/tool.d.ts.map +1 -1
  147. package/dist/fence/tool.js +103 -32
  148. package/dist/hvac-equipment/tool.d.ts.map +1 -1
  149. package/dist/hvac-equipment/tool.js +8 -3
  150. package/dist/index.d.ts +2 -0
  151. package/dist/index.d.ts.map +1 -1
  152. package/dist/index.js +6 -0
  153. package/dist/item/definition.d.ts.map +1 -1
  154. package/dist/item/definition.js +7 -1
  155. package/dist/item/floorplan-move.d.ts.map +1 -1
  156. package/dist/item/floorplan-move.js +50 -43
  157. package/dist/item/floorplan.d.ts +1 -0
  158. package/dist/item/floorplan.d.ts.map +1 -1
  159. package/dist/item/floorplan.js +49 -0
  160. package/dist/item/renderer.d.ts +3 -0
  161. package/dist/item/renderer.d.ts.map +1 -1
  162. package/dist/item/renderer.js +96 -8
  163. package/dist/level/definition.d.ts.map +1 -1
  164. package/dist/level/definition.js +7 -3
  165. package/dist/lineset/tool.d.ts.map +1 -1
  166. package/dist/lineset/tool.js +5 -1
  167. package/dist/liquid-line/tool.d.ts.map +1 -1
  168. package/dist/liquid-line/tool.js +7 -1
  169. package/dist/measurement/definition.d.ts.map +1 -1
  170. package/dist/measurement/definition.js +5 -0
  171. package/dist/measurement/floorplan.d.ts.map +1 -1
  172. package/dist/measurement/floorplan.js +11 -10
  173. package/dist/measurement/index.d.ts +1 -1
  174. package/dist/measurement/index.d.ts.map +1 -1
  175. package/dist/measurement/index.js +1 -1
  176. package/dist/measurement/resolve.d.ts +5 -0
  177. package/dist/measurement/resolve.d.ts.map +1 -1
  178. package/dist/measurement/resolve.js +2 -2
  179. package/dist/measurement/surface-query.js +5 -5
  180. package/dist/measurement/tool.d.ts +6 -1
  181. package/dist/measurement/tool.d.ts.map +1 -1
  182. package/dist/measurement/tool.js +7 -11
  183. package/dist/pipe-segment/tool.d.ts.map +1 -1
  184. package/dist/pipe-segment/tool.js +22 -19
  185. package/dist/roof-segment/floorplan-affordances.d.ts.map +1 -1
  186. package/dist/roof-segment/floorplan-affordances.js +14 -8
  187. package/dist/shared/construction-dimension-standards.d.ts +34 -0
  188. package/dist/shared/construction-dimension-standards.d.ts.map +1 -0
  189. package/dist/shared/construction-dimension-standards.js +18 -0
  190. package/dist/shared/construction-length.d.ts +10 -0
  191. package/dist/shared/construction-length.d.ts.map +1 -0
  192. package/dist/shared/construction-length.js +57 -0
  193. package/dist/shared/dimension-string.d.ts +20 -0
  194. package/dist/shared/dimension-string.d.ts.map +1 -0
  195. package/dist/shared/dimension-string.js +19 -0
  196. package/dist/shared/draft-axis-guides.d.ts +36 -0
  197. package/dist/shared/draft-axis-guides.d.ts.map +1 -0
  198. package/dist/shared/draft-axis-guides.js +107 -0
  199. package/dist/shared/move-roof-tool.d.ts.map +1 -1
  200. package/dist/shared/move-roof-tool.js +17 -3
  201. package/dist/shared/opening-documentation-fields.d.ts +16 -0
  202. package/dist/shared/opening-documentation-fields.d.ts.map +1 -0
  203. package/dist/shared/opening-documentation-fields.js +54 -0
  204. package/dist/shared/opening-documentation.d.ts +47 -0
  205. package/dist/shared/opening-documentation.d.ts.map +1 -0
  206. package/dist/shared/opening-documentation.js +300 -0
  207. package/dist/shared/opening-guides-runtime.d.ts.map +1 -1
  208. package/dist/shared/opening-guides-runtime.js +3 -2
  209. package/dist/shared/opening-placement-dimensions.d.ts.map +1 -1
  210. package/dist/shared/opening-placement-dimensions.js +14 -5
  211. package/dist/shared/wall-opening-ceiling.d.ts +30 -0
  212. package/dist/shared/wall-opening-ceiling.d.ts.map +1 -0
  213. package/dist/shared/wall-opening-ceiling.js +29 -0
  214. package/dist/shelf/floorplan-affordances.d.ts.map +1 -1
  215. package/dist/shelf/floorplan-affordances.js +10 -5
  216. package/dist/shelf/floorplan-move.d.ts +2 -16
  217. package/dist/shelf/floorplan-move.d.ts.map +1 -1
  218. package/dist/shelf/floorplan-move.js +19 -40
  219. package/dist/shelf/tool.d.ts.map +1 -1
  220. package/dist/shelf/tool.js +8 -3
  221. package/dist/site/recessed-slab-ground-holes.d.ts.map +1 -1
  222. package/dist/site/recessed-slab-ground-holes.js +1 -4
  223. package/dist/site/renderer.d.ts.map +1 -1
  224. package/dist/site/renderer.js +118 -28
  225. package/dist/site/terrain-drape.d.ts +91 -0
  226. package/dist/site/terrain-drape.d.ts.map +1 -0
  227. package/dist/site/terrain-drape.js +205 -0
  228. package/dist/site/terrain-geometry.d.ts +131 -0
  229. package/dist/site/terrain-geometry.d.ts.map +1 -0
  230. package/dist/site/terrain-geometry.js +259 -0
  231. package/dist/site/terrain-mesh.d.ts +49 -0
  232. package/dist/site/terrain-mesh.d.ts.map +1 -0
  233. package/dist/site/terrain-mesh.js +122 -0
  234. package/dist/site/terrain-renderer.d.ts +28 -0
  235. package/dist/site/terrain-renderer.d.ts.map +1 -0
  236. package/dist/site/terrain-renderer.js +69 -0
  237. package/dist/skylight/definition.d.ts.map +1 -1
  238. package/dist/skylight/definition.js +1 -0
  239. package/dist/skylight/tool.d.ts.map +1 -1
  240. package/dist/skylight/tool.js +16 -1
  241. package/dist/slab/definition.d.ts.map +1 -1
  242. package/dist/slab/definition.js +87 -10
  243. package/dist/slab/elevation-limit.d.ts +43 -0
  244. package/dist/slab/elevation-limit.d.ts.map +1 -0
  245. package/dist/slab/elevation-limit.js +124 -0
  246. package/dist/slab/floorplan-affordances.d.ts +16 -0
  247. package/dist/slab/floorplan-affordances.d.ts.map +1 -1
  248. package/dist/slab/geometry.d.ts.map +1 -1
  249. package/dist/slab/geometry.js +98 -5
  250. package/dist/slab/panel.d.ts.map +1 -1
  251. package/dist/slab/panel.js +82 -4
  252. package/dist/slab/parametrics.d.ts +1 -1
  253. package/dist/slab/parametrics.d.ts.map +1 -1
  254. package/dist/slab/parametrics.js +13 -1
  255. package/dist/slab/placement-ownership.d.ts +3 -0
  256. package/dist/slab/placement-ownership.d.ts.map +1 -0
  257. package/dist/slab/placement-ownership.js +3 -0
  258. package/dist/slab/quick-measurement.d.ts.map +1 -1
  259. package/dist/slab/quick-measurement.js +3 -2
  260. package/dist/slab/system.d.ts.map +1 -1
  261. package/dist/slab/system.js +14 -4
  262. package/dist/slab/tool.d.ts.map +1 -1
  263. package/dist/slab/tool.js +81 -23
  264. package/dist/solar-panel/definition.d.ts.map +1 -1
  265. package/dist/solar-panel/definition.js +1 -0
  266. package/dist/solar-panel/tool.d.ts.map +1 -1
  267. package/dist/solar-panel/tool.js +17 -1
  268. package/dist/spawn/definition.d.ts.map +1 -1
  269. package/dist/spawn/definition.js +1 -0
  270. package/dist/spawn/floorplan-affordances.d.ts.map +1 -1
  271. package/dist/spawn/floorplan-affordances.js +7 -4
  272. package/dist/spawn/floorplan-move.d.ts.map +1 -1
  273. package/dist/spawn/floorplan-move.js +8 -6
  274. package/dist/spawn/renderer.d.ts +4 -0
  275. package/dist/spawn/renderer.d.ts.map +1 -1
  276. package/dist/spawn/renderer.js +32 -18
  277. package/dist/spawn/tool.d.ts +2 -2
  278. package/dist/spawn/tool.d.ts.map +1 -1
  279. package/dist/spawn/tool.js +78 -11
  280. package/dist/stair/definition.d.ts.map +1 -1
  281. package/dist/stair/definition.js +15 -7
  282. package/dist/stair/destination.d.ts +16 -0
  283. package/dist/stair/destination.d.ts.map +1 -0
  284. package/dist/stair/destination.js +24 -0
  285. package/dist/stair/documentation.d.ts +8 -0
  286. package/dist/stair/documentation.d.ts.map +1 -0
  287. package/dist/stair/documentation.js +245 -0
  288. package/dist/stair/floor-stack.d.ts.map +1 -1
  289. package/dist/stair/floor-stack.js +38 -0
  290. package/dist/stair/floorplan-affordances.d.ts.map +1 -1
  291. package/dist/stair/floorplan-affordances.js +28 -9
  292. package/dist/stair/floorplan-move.d.ts +3 -5
  293. package/dist/stair/floorplan-move.d.ts.map +1 -1
  294. package/dist/stair/floorplan-move.js +14 -16
  295. package/dist/stair/floorplan.d.ts.map +1 -1
  296. package/dist/stair/floorplan.js +27 -14
  297. package/dist/stair/panel.d.ts.map +1 -1
  298. package/dist/stair/panel.js +35 -2
  299. package/dist/stair/renderer.d.ts.map +1 -1
  300. package/dist/stair/renderer.js +4 -3
  301. package/dist/structural-grid/coordination.d.ts +16 -0
  302. package/dist/structural-grid/coordination.d.ts.map +1 -0
  303. package/dist/structural-grid/coordination.js +105 -0
  304. package/dist/structural-grid/definition.d.ts +4 -0
  305. package/dist/structural-grid/definition.d.ts.map +1 -0
  306. package/dist/structural-grid/definition.js +51 -0
  307. package/dist/structural-grid/floorplan-tool.d.ts +16 -0
  308. package/dist/structural-grid/floorplan-tool.d.ts.map +1 -0
  309. package/dist/structural-grid/floorplan-tool.js +183 -0
  310. package/dist/structural-grid/floorplan.d.ts +3 -0
  311. package/dist/structural-grid/floorplan.d.ts.map +1 -0
  312. package/dist/structural-grid/floorplan.js +61 -0
  313. package/dist/structural-grid/index.d.ts +2 -0
  314. package/dist/structural-grid/index.d.ts.map +1 -0
  315. package/dist/structural-grid/index.js +1 -0
  316. package/dist/structural-grid/schema.d.ts +2 -0
  317. package/dist/structural-grid/schema.d.ts.map +1 -0
  318. package/dist/structural-grid/schema.js +1 -0
  319. package/dist/wall/construction-dimensions.d.ts +32 -0
  320. package/dist/wall/construction-dimensions.d.ts.map +1 -0
  321. package/dist/wall/construction-dimensions.js +1031 -0
  322. package/dist/wall/contextual-dimensions.d.ts +13 -0
  323. package/dist/wall/contextual-dimensions.d.ts.map +1 -0
  324. package/dist/wall/contextual-dimensions.js +221 -0
  325. package/dist/wall/definition.d.ts.map +1 -1
  326. package/dist/wall/definition.js +20 -1
  327. package/dist/wall/floorplan-affordances.d.ts.map +1 -1
  328. package/dist/wall/floorplan-affordances.js +23 -8
  329. package/dist/wall/floorplan-overrides.d.ts +9 -6
  330. package/dist/wall/floorplan-overrides.d.ts.map +1 -1
  331. package/dist/wall/floorplan-overrides.js +20 -11
  332. package/dist/wall/floorplan.d.ts +12 -3
  333. package/dist/wall/floorplan.d.ts.map +1 -1
  334. package/dist/wall/floorplan.js +121 -79
  335. package/dist/wall/measurement.d.ts.map +1 -1
  336. package/dist/wall/measurement.js +17 -4
  337. package/dist/wall/move-endpoint-tool.d.ts.map +1 -1
  338. package/dist/wall/move-endpoint-tool.js +74 -27
  339. package/dist/wall/move-shared.d.ts.map +1 -1
  340. package/dist/wall/move-shared.js +3 -2
  341. package/dist/wall/move-tool.d.ts.map +1 -1
  342. package/dist/wall/move-tool.js +15 -3
  343. package/dist/wall/paint.d.ts.map +1 -1
  344. package/dist/wall/paint.js +10 -6
  345. package/dist/wall/panel.d.ts.map +1 -1
  346. package/dist/wall/panel.js +32 -5
  347. package/dist/wall/parametrics.d.ts.map +1 -1
  348. package/dist/wall/parametrics.js +2 -0
  349. package/dist/wall/preview.d.ts +8 -0
  350. package/dist/wall/preview.d.ts.map +1 -0
  351. package/dist/wall/preview.js +39 -0
  352. package/dist/wall/quick-measurement.d.ts.map +1 -1
  353. package/dist/wall/quick-measurement.js +3 -2
  354. package/dist/wall/renderer.d.ts.map +1 -1
  355. package/dist/wall/renderer.js +7 -5
  356. package/dist/wall/system.d.ts +1 -1
  357. package/dist/wall/system.d.ts.map +1 -1
  358. package/dist/wall/system.js +37 -2
  359. package/dist/wall/tool.d.ts.map +1 -1
  360. package/dist/wall/tool.js +148 -96
  361. package/dist/wall/treatment-level-data.d.ts +14 -0
  362. package/dist/wall/treatment-level-data.d.ts.map +1 -0
  363. package/dist/wall/treatment-level-data.js +31 -0
  364. package/dist/wall/treatments.d.ts +8 -2
  365. package/dist/wall/treatments.d.ts.map +1 -1
  366. package/dist/wall/treatments.js +58 -15
  367. package/dist/window/contextual-dimensions.d.ts +3 -0
  368. package/dist/window/contextual-dimensions.d.ts.map +1 -0
  369. package/dist/window/contextual-dimensions.js +7 -0
  370. package/dist/window/definition.d.ts.map +1 -1
  371. package/dist/window/definition.js +15 -10
  372. package/dist/window/floorplan-affordances.d.ts +2 -5
  373. package/dist/window/floorplan-affordances.d.ts.map +1 -1
  374. package/dist/window/floorplan-affordances.js +9 -15
  375. package/dist/window/floorplan-move.d.ts.map +1 -1
  376. package/dist/window/floorplan-move.js +1 -1
  377. package/dist/window/floorplan.d.ts.map +1 -1
  378. package/dist/window/floorplan.js +7 -1
  379. package/dist/window/move-tool.d.ts.map +1 -1
  380. package/dist/window/move-tool.js +3 -3
  381. package/dist/window/panel.d.ts.map +1 -1
  382. package/dist/window/panel.js +4 -1
  383. package/dist/window/schema.d.ts +1 -1
  384. package/dist/window/schema.d.ts.map +1 -1
  385. package/dist/window/schema.js +1 -1
  386. package/dist/window/tool.d.ts.map +1 -1
  387. package/dist/window/tool.js +64 -7
  388. package/dist/window/window-math.d.ts +7 -3
  389. package/dist/window/window-math.d.ts.map +1 -1
  390. package/dist/window/window-math.js +8 -3
  391. package/dist/zone/contextual-dimensions.d.ts +3 -0
  392. package/dist/zone/contextual-dimensions.d.ts.map +1 -0
  393. package/dist/zone/contextual-dimensions.js +43 -0
  394. package/dist/zone/definition.d.ts.map +1 -1
  395. package/dist/zone/definition.js +9 -1
  396. package/dist/zone/floorplan-affordances.d.ts +36 -0
  397. package/dist/zone/floorplan-affordances.d.ts.map +1 -1
  398. package/dist/zone/floorplan.d.ts.map +1 -1
  399. package/dist/zone/floorplan.js +59 -3
  400. package/dist/zone/quantities-panel.d.ts.map +1 -1
  401. package/dist/zone/quantities-panel.js +48 -4
  402. package/dist/zone/room-clear-dimensions.d.ts +3 -0
  403. package/dist/zone/room-clear-dimensions.d.ts.map +1 -0
  404. package/dist/zone/room-clear-dimensions.js +406 -0
  405. package/dist/zone/room-documentation.d.ts +11 -0
  406. package/dist/zone/room-documentation.d.ts.map +1 -0
  407. package/dist/zone/room-documentation.js +97 -0
  408. package/package.json +7 -7
@@ -10,8 +10,9 @@ import { cabinetPaint } from './paint';
10
10
  import { cabinetModuleParametrics, cabinetParametrics } from './parametrics';
11
11
  import useCabinetPlacementType from './placement-type';
12
12
  import { cabinetQuickActions } from './quick-actions';
13
- import { moduleSideOpen } from './run-layout';
14
- import { backAlignZ, bumpCabinetRunLayoutRevision, cabinetMetadataRecord, cabinetModulesForRun, totalCabinetHeight as cabinetTotalHeight, cornerLinkedSourceModuleForRun, resolveCabinetType, runModuleBaseY, syncCornerRunsFromSourceModule, wallChildOf, } from './run-ops';
13
+ import { cabinetConnectedDepthBounds, cabinetResizeUpperBound, MAX_CABINET_DEPTH, MAX_CABINET_WIDTH, MIN_CABINET_DEPTH, MIN_CABINET_WIDTH, } from './resize-limits';
14
+ import { moduleSideOpen, sortRunModules } from './run-layout';
15
+ import { backAlignedRunDepthOverrides, backAlignZ, buildWallCornerDepthIndex, bumpCabinetRunLayoutRevision, cabinetMetadataRecord, cabinetModulesForRun, totalCabinetHeight as cabinetTotalHeight, cornerSourceWidthOverridesForDerivedDepth, previewCornerRunsFromRunSources, resolveCabinetType, runModuleBaseY, syncCornerRunsFromRunSources, syncCornerRunsFromSourceModule, wallChildOf, wallCornerWidthOverridesForDepthTargets, } from './run-ops';
15
16
  import { cabinetSceneAction } from './scene-action';
16
17
  import { CabinetModuleNode, CabinetNode } from './schema';
17
18
  import { cabinetSlots } from './slots';
@@ -131,10 +132,9 @@ const SIDE_HANDLE_OFFSET = 0.18;
131
132
  const HEIGHT_HANDLE_OFFSET = 0.22;
132
133
  const ROTATE_CORNER_OFFSET = 0.32;
133
134
  const ROTATE_RING_OFFSET = 0.04;
134
- const MIN_CABINET_WIDTH = 0.3;
135
- const MIN_CABINET_DEPTH = 0.3;
136
135
  const MIN_CABINET_CARCASS_HEIGHT = 0.4;
137
136
  const CABINET_ADJACENCY_EPSILON = 1e-4;
137
+ const CABINET_DEPTH_SNAP_THRESHOLD = 0.02;
138
138
  function isCabinetModule(node) {
139
139
  return node?.type === 'cabinet-module';
140
140
  }
@@ -370,6 +370,17 @@ function cabinetLocalBounds(node, nodes) {
370
370
  includeChildRunBounds(child, nodes, bounds);
371
371
  }
372
372
  }
373
+ if (isCabinetModule(node) && nodes) {
374
+ for (const childId of node.children ?? []) {
375
+ const child = nodes[childId];
376
+ if (isCabinetModule(child)) {
377
+ includeCabinetModuleBounds(child, nodes, [0, 0, 0], bounds);
378
+ }
379
+ else if (isCabinetRun(child)) {
380
+ includeChildRunBounds(child, nodes, bounds);
381
+ }
382
+ }
383
+ }
373
384
  const width = Math.max(0.01, bounds.maxX - bounds.minX);
374
385
  const height = Math.max(0.01, bounds.maxY - bounds.minY);
375
386
  const depth = Math.max(0.01, bounds.maxZ - bounds.minZ);
@@ -412,22 +423,260 @@ function cabinetModuleSideOpen(module, side, sceneApi) {
412
423
  return true;
413
424
  return moduleSideOpen(cabinetModulesForRun(parent, sceneApi.nodes()), module.id, side, CABINET_ADJACENCY_EPSILON);
414
425
  }
415
- function commitRunResize(run, patch, sceneApi) {
426
+ function cabinetModuleConnectedNeighbor(module, side, sceneApi) {
427
+ const parent = module.parentId ? sceneApi.get(module.parentId) : undefined;
428
+ if (!isCabinetRun(parent))
429
+ return undefined;
430
+ const modules = sortRunModules(cabinetModulesForRun(parent, sceneApi.nodes()));
431
+ const index = modules.findIndex((entry) => entry.id === module.id);
432
+ if (index < 0 || cabinetModuleSideOpen(module, side, sceneApi))
433
+ return undefined;
434
+ return side === 'left' ? modules[index - 1] : modules[index + 1];
435
+ }
436
+ function cabinetWidthConnectedNeighbor(module, side, sceneApi) {
437
+ const parent = module.parentId ? sceneApi.get(module.parentId) : undefined;
438
+ if (isCabinetRun(parent))
439
+ return cabinetModuleConnectedNeighbor(module, side, sceneApi);
440
+ if (!isCabinetModule(parent))
441
+ return undefined;
442
+ if (wallChildOf(parent, sceneApi.nodes())?.id !== module.id)
443
+ return undefined;
444
+ const connectedHost = cabinetModuleConnectedNeighbor(parent, side, sceneApi);
445
+ if (!connectedHost || isCabinetWidthFiller(connectedHost))
446
+ return undefined;
447
+ return wallChildOf(connectedHost, sceneApi.nodes()) ?? undefined;
448
+ }
449
+ function cabinetModuleRunLocalCenterX(module, sceneApi) {
450
+ const parent = module.parentId ? sceneApi.get(module.parentId) : undefined;
451
+ if (isCabinetRun(parent))
452
+ return module.position[0];
453
+ if (!isCabinetModule(parent))
454
+ return null;
455
+ const run = parent.parentId ? sceneApi.get(parent.parentId) : undefined;
456
+ return isCabinetRun(run) ? parent.position[0] + module.position[0] : null;
457
+ }
458
+ function cabinetWallWidthGap(module, side, sceneApi) {
459
+ const parent = module.parentId ? sceneApi.get(module.parentId) : undefined;
460
+ const isWallModule = (isCabinetRun(parent) && parent.runTier === 'wall') ||
461
+ (isCabinetModule(parent) && wallChildOf(parent, sceneApi.nodes())?.id === module.id);
462
+ if (!isWallModule)
463
+ return 0;
464
+ const connected = cabinetWidthConnectedNeighbor(module, side, sceneApi);
465
+ if (!connected || isCabinetWidthFiller(connected))
466
+ return 0;
467
+ const moduleCenter = cabinetModuleRunLocalCenterX(module, sceneApi);
468
+ const connectedCenter = cabinetModuleRunLocalCenterX(connected, sceneApi);
469
+ if (moduleCenter === null || connectedCenter === null)
470
+ return 0;
471
+ const direction = side === 'right' ? 1 : -1;
472
+ return Math.max(0, direction * (connectedCenter - moduleCenter) - (module.width + connected.width) / 2);
473
+ }
474
+ function isCabinetWidthFiller(module) {
475
+ return (module.moduleKind === 'corner-filler' ||
476
+ module.name === 'Corner Filler' ||
477
+ module.name === 'Wall Bridge Filler' ||
478
+ module.name === 'Corner Wall Filler');
479
+ }
480
+ function cabinetNodeAttachedToAncestor(node, ancestorId, nodes) {
481
+ let current = node;
482
+ const visited = new Set();
483
+ while (current.parentId) {
484
+ const currentId = current.id;
485
+ if (visited.has(currentId))
486
+ return false;
487
+ visited.add(currentId);
488
+ const parent = nodes[current.parentId];
489
+ if (parent?.type !== 'cabinet' && parent?.type !== 'cabinet-module')
490
+ return false;
491
+ if (!(parent.children ?? []).includes(currentId))
492
+ return false;
493
+ if (parent.id === ancestorId)
494
+ return true;
495
+ current = parent;
496
+ }
497
+ return false;
498
+ }
499
+ function cabinetSubtreeHasNamedFiller(root, name, sceneApi) {
500
+ const pending = [...(root.children ?? [])];
501
+ const visited = new Set();
502
+ while (pending.length > 0) {
503
+ const id = pending.pop();
504
+ if (visited.has(id))
505
+ continue;
506
+ visited.add(id);
507
+ const node = sceneApi.get(id);
508
+ if (!node)
509
+ continue;
510
+ if (node.type === 'cabinet-module' && isCabinetWidthFiller(node) && node.name === name) {
511
+ return true;
512
+ }
513
+ if (node.type === 'cabinet' || node.type === 'cabinet-module') {
514
+ pending.push(...(node.children ?? []));
515
+ }
516
+ }
517
+ return false;
518
+ }
519
+ function wallCabinetSideHasFiller(hostModule, side, sceneApi) {
520
+ const hostRun = hostModule.parentId
521
+ ? sceneApi.get(hostModule.parentId)
522
+ : undefined;
523
+ if (!hostRun || !isCabinetRun(hostRun))
524
+ return false;
525
+ const connected = cabinetModuleConnectedNeighbor(hostModule, side, sceneApi);
526
+ if (connected &&
527
+ isCabinetWidthFiller(connected) &&
528
+ cabinetSubtreeHasNamedFiller(connected, 'Corner Wall Filler', sceneApi)) {
529
+ return true;
530
+ }
531
+ const nodes = sceneApi.nodes();
532
+ for (const candidate of Object.values(nodes)) {
533
+ if (candidate?.type !== 'cabinet')
534
+ continue;
535
+ const derived = cabinetMetadataRecord(candidate.metadata).cabinetCornerDerivedRun;
536
+ if (!derived || typeof derived !== 'object' || Array.isArray(derived))
537
+ continue;
538
+ if (derived.role !== 'bridge' ||
539
+ derived.side !== side ||
540
+ derived.sourceModuleId !== hostModule.id ||
541
+ derived.sourceRunId !== hostRun.id ||
542
+ !cabinetNodeAttachedToAncestor(candidate, hostRun.id, nodes)) {
543
+ continue;
544
+ }
545
+ if (cabinetModulesForRun(candidate, nodes).some((entry) => entry.name === 'Wall Bridge Filler')) {
546
+ return true;
547
+ }
548
+ }
549
+ return false;
550
+ }
551
+ function cabinetModuleSideHasCornerFiller(module, side, sceneApi) {
552
+ const parent = module.parentId ? sceneApi.get(module.parentId) : undefined;
553
+ if (isCabinetModule(parent)) {
554
+ return wallCabinetSideHasFiller(parent, side, sceneApi);
555
+ }
556
+ if (!isCabinetRun(parent))
557
+ return false;
558
+ const connected = cabinetModuleConnectedNeighbor(module, side, sceneApi);
559
+ if (connected && isCabinetWidthFiller(connected)) {
560
+ return true;
561
+ }
562
+ for (const candidate of Object.values(sceneApi.nodes())) {
563
+ if (candidate?.type !== 'cabinet')
564
+ continue;
565
+ const derived = cabinetMetadataRecord(candidate.metadata).cabinetCornerDerivedRun;
566
+ if (!derived || typeof derived !== 'object' || Array.isArray(derived))
567
+ continue;
568
+ if (candidate.parentId !== parent.id ||
569
+ !(parent.children ?? []).includes(candidate.id) ||
570
+ derived.role !== 'base-leg' ||
571
+ derived.side !== side ||
572
+ derived.sourceModuleId !== module.id ||
573
+ derived.sourceRunId !== parent.id) {
574
+ continue;
575
+ }
576
+ if (cabinetModulesForRun(candidate, sceneApi.nodes()).some((entry) => entry.moduleKind === 'corner-filler')) {
577
+ return true;
578
+ }
579
+ }
580
+ return false;
581
+ }
582
+ function connectedCabinetWidthResize(module, side, delta, sceneApi) {
583
+ const connected = cabinetWidthConnectedNeighbor(module, side, sceneApi);
584
+ if (!connected || isCabinetWidthFiller(connected))
585
+ return null;
586
+ const direction = side === 'right' ? 1 : -1;
587
+ return {
588
+ module: connected,
589
+ patch: {
590
+ width: connected.width - delta,
591
+ position: [
592
+ connected.position[0] + (direction * delta) / 2,
593
+ connected.position[1],
594
+ connected.position[2],
595
+ ],
596
+ },
597
+ };
598
+ }
599
+ function wallCabinetWidthOverride(module, width, sceneApi) {
600
+ const parent = module.parentId
601
+ ? sceneApi.get(module.parentId)
602
+ : undefined;
603
+ if (!parent || !isCabinetRun(parent) || resolveCabinetType(module, parent) !== 'base')
604
+ return null;
605
+ const wallChild = wallChildOf(module, sceneApi.nodes());
606
+ return wallChild ? [wallChild.id, { width }] : null;
607
+ }
608
+ function parentRunGeometryPreviewOverride(node, sceneApi) {
609
+ if (!isCabinetModule(node) || !node.parentId)
610
+ return null;
611
+ const parent = sceneApi.get(node.parentId);
612
+ return isCabinetRun(parent) ? [parent.id, {}] : null;
613
+ }
614
+ function sharedDepthBounds(referenceDepth, targets, nodes) {
615
+ const depthOwners = targets.flatMap((target) => {
616
+ if (!isCabinetRun(target))
617
+ return [target];
618
+ const modules = cabinetModulesForRun(target, nodes);
619
+ return modules.length > 0 ? [target, ...modules] : [target];
620
+ });
621
+ const minDelta = Math.max(...depthOwners.map((target) => MIN_CABINET_DEPTH - target.depth));
622
+ const maxDelta = Math.min(...depthOwners.map((target) => cabinetResizeUpperBound(target.depth, MAX_CABINET_DEPTH) - target.depth));
623
+ return {
624
+ min: referenceDepth + minDelta,
625
+ max: referenceDepth + maxDelta,
626
+ };
627
+ }
628
+ function depthDeltaRunOverrides(run, nodes, depthDelta) {
629
+ const overrides = [];
630
+ for (const module of cabinetModulesForRun(run, nodes)) {
631
+ const depth = module.depth + depthDelta;
632
+ const positionZ = backAnchoredModuleZ(module.position[2], module.depth, depth);
633
+ const parentShiftZ = positionZ - module.position[2];
634
+ overrides.push([
635
+ module.id,
636
+ {
637
+ depth,
638
+ position: [module.position[0], module.position[1], positionZ],
639
+ },
640
+ ]);
641
+ for (const childId of module.children ?? []) {
642
+ const child = nodes[childId];
643
+ if (child?.type !== 'cabinet')
644
+ continue;
645
+ overrides.push([
646
+ child.id,
647
+ {
648
+ position: [child.position[0], child.position[1], child.position[2] - parentShiftZ],
649
+ },
650
+ ]);
651
+ }
652
+ const wallChild = wallChildOf(module, nodes);
653
+ if (wallChild) {
654
+ overrides.push([
655
+ wallChild.id,
656
+ {
657
+ position: [
658
+ wallChild.position[0],
659
+ wallChild.position[1],
660
+ backAlignZ(depth, wallChild.depth),
661
+ ],
662
+ },
663
+ ]);
664
+ }
665
+ }
666
+ return overrides;
667
+ }
668
+ function commitRunResize(run, patch, sceneApi, options = {}) {
416
669
  sceneApi.update(run.id, patch);
417
670
  const nextRun = { ...run, ...patch };
418
671
  const syncDepth = typeof patch.depth === 'number';
419
672
  const syncHeight = typeof patch.carcassHeight === 'number';
420
673
  const syncPosition = patch.showPlinth !== undefined || typeof patch.plinthHeight === 'number';
421
674
  if (syncDepth || syncHeight || syncPosition) {
675
+ const depthOverrides = new Map(syncDepth ? backAlignedRunDepthOverrides(run, sceneApi.nodes(), nextRun.depth) : []);
422
676
  for (const module of cabinetModulesForRun(run, sceneApi.nodes())) {
423
677
  const modulePatch = {};
424
678
  if (syncDepth) {
425
- modulePatch.depth = nextRun.depth;
426
- modulePatch.position = [
427
- module.position[0],
428
- module.position[1],
429
- backAnchoredModuleZ(module.position[2], module.depth, nextRun.depth),
430
- ];
679
+ Object.assign(modulePatch, depthOverrides.get(module.id));
431
680
  }
432
681
  if (syncHeight) {
433
682
  modulePatch.carcassHeight = Math.max(nextRun.carcassHeight, minCabinetCarcassHeightForStack(module));
@@ -453,19 +702,37 @@ function commitRunResize(run, patch, sceneApi) {
453
702
  }
454
703
  }
455
704
  }
705
+ if (syncDepth) {
706
+ for (const [id, depthPatch] of depthOverrides) {
707
+ if (sceneApi.get(id)?.type !== 'cabinet')
708
+ continue;
709
+ sceneApi.update(id, depthPatch);
710
+ }
711
+ }
456
712
  }
457
713
  if (syncDepth || syncHeight || syncPosition) {
458
714
  bumpCabinetRunLayoutRevision(sceneApi, nextRun);
459
- const cornerSource = cornerLinkedSourceModuleForRun(nextRun, sceneApi.nodes());
460
- if (cornerSource) {
461
- syncCornerRunsFromSourceModule({
462
- module: cornerSource,
463
- run: nextRun,
464
- sceneApi,
465
- });
466
- }
715
+ syncCornerRunsFromRunSources({
716
+ baseLayout: options.cornerSync ?? 'full',
717
+ run: nextRun,
718
+ sceneApi,
719
+ });
467
720
  }
468
721
  }
722
+ function commitRunDepthDelta(run, depth, sceneApi, options = {}) {
723
+ const depthDelta = depth - run.depth;
724
+ for (const [id, patch] of depthDeltaRunOverrides(run, sceneApi.nodes(), depthDelta)) {
725
+ sceneApi.update(id, patch);
726
+ }
727
+ sceneApi.update(run.id, { depth });
728
+ const nextRun = { ...run, depth };
729
+ bumpCabinetRunLayoutRevision(sceneApi, nextRun);
730
+ syncCornerRunsFromRunSources({
731
+ baseLayout: options.cornerSync ?? 'full',
732
+ run: nextRun,
733
+ sceneApi,
734
+ });
735
+ }
469
736
  function commitModuleResize(module, patch, sceneApi) {
470
737
  const nodes = sceneApi.nodes();
471
738
  const parent = module.parentId ? nodes[module.parentId] : undefined;
@@ -479,23 +746,13 @@ function commitModuleResize(module, patch, sceneApi) {
479
746
  }
480
747
  if (typeof patch.width === 'number') {
481
748
  sceneApi.update(module.id, patch);
482
- const wallChild = wallChildOf(module, sceneApi.nodes());
483
- if (wallChild) {
484
- sceneApi.update(wallChild.id, {
485
- width: patch.width,
486
- position: [
487
- wallChild.position[0],
488
- wallChild.position[1],
489
- backAlignZ(patch.depth ?? module.depth, wallChild.depth),
490
- ],
491
- });
749
+ if (resolveCabinetType(module, parentRun) === 'base') {
750
+ const wallChild = wallChildOf(module, sceneApi.nodes());
751
+ if (wallChild) {
752
+ sceneApi.update(wallChild.id, { width: patch.width });
753
+ }
492
754
  }
493
755
  bumpCabinetRunLayoutRevision(sceneApi, parentRun);
494
- syncCornerRunsFromSourceModule({
495
- module: sceneApi.get(module.id) ?? module,
496
- run: sceneApi.get(parentRun.id) ?? parentRun,
497
- sceneApi,
498
- });
499
756
  return;
500
757
  }
501
758
  const modulePatch = { ...patch };
@@ -509,8 +766,6 @@ function commitModuleResize(module, patch, sceneApi) {
509
766
  sceneApi.update(module.id, modulePatch);
510
767
  if (resolveCabinetType(module, parentRun) === 'base') {
511
768
  const runPatch = {};
512
- if (typeof patch.depth === 'number')
513
- runPatch.depth = patch.depth;
514
769
  if (typeof patch.carcassHeight === 'number') {
515
770
  runPatch.carcassHeight = patch.carcassHeight;
516
771
  }
@@ -558,17 +813,71 @@ function cabinetWidthHandle(side) {
558
813
  kind: 'linear-resize',
559
814
  axis: 'x',
560
815
  anchor: side === 'right' ? 'min' : 'max',
561
- min: MIN_CABINET_WIDTH,
816
+ min: (node, sceneApi) => {
817
+ if (!isCabinetModule(node))
818
+ return MIN_CABINET_WIDTH;
819
+ const gap = cabinetWallWidthGap(node, side, sceneApi);
820
+ const connected = cabinetWidthConnectedNeighbor(node, side, sceneApi);
821
+ if (!connected || isCabinetWidthFiller(connected))
822
+ return MIN_CABINET_WIDTH - gap;
823
+ const connectedMax = cabinetResizeUpperBound(connected.width, MAX_CABINET_WIDTH);
824
+ return Math.max(MIN_CABINET_WIDTH - gap, node.width - (connectedMax - connected.width));
825
+ },
826
+ max: (node, sceneApi) => {
827
+ const ownMax = cabinetResizeUpperBound(node.width, MAX_CABINET_WIDTH);
828
+ if (!isCabinetModule(node))
829
+ return ownMax;
830
+ const gap = cabinetWallWidthGap(node, side, sceneApi);
831
+ const connected = cabinetWidthConnectedNeighbor(node, side, sceneApi);
832
+ if (!connected || isCabinetWidthFiller(connected))
833
+ return ownMax - gap;
834
+ return Math.min(ownMax - gap, node.width + connected.width - MIN_CABINET_WIDTH);
835
+ },
562
836
  currentValue: (node) => node.width,
563
- apply: (node, width) => ({
564
- width,
565
- position: [
566
- node.position[0] + (sign * (width - node.width)) / 2,
567
- node.position[1],
568
- node.position[2],
569
- ],
570
- }),
571
- commit: commitCabinetResize,
837
+ apply: (node, width, sceneApi) => {
838
+ const gap = isCabinetModule(node) ? cabinetWallWidthGap(node, side, sceneApi) : 0;
839
+ const effectiveWidth = width + gap;
840
+ return {
841
+ width: effectiveWidth,
842
+ position: [
843
+ node.position[0] + (sign * (effectiveWidth - node.width)) / 2,
844
+ node.position[1],
845
+ node.position[2],
846
+ ],
847
+ };
848
+ },
849
+ previewOverrides: (node, width, sceneApi) => {
850
+ if (!isCabinetModule(node))
851
+ return [];
852
+ const overrides = [];
853
+ const parentRunOverride = parentRunGeometryPreviewOverride(node, sceneApi);
854
+ if (parentRunOverride)
855
+ overrides.push(parentRunOverride);
856
+ const gap = cabinetWallWidthGap(node, side, sceneApi);
857
+ const selectedWallOverride = wallCabinetWidthOverride(node, width + gap, sceneApi);
858
+ if (selectedWallOverride)
859
+ overrides.push(selectedWallOverride);
860
+ const connectedResize = connectedCabinetWidthResize(node, side, width - node.width, sceneApi);
861
+ if (connectedResize) {
862
+ overrides.push([
863
+ connectedResize.module.id,
864
+ connectedResize.patch,
865
+ ]);
866
+ const connectedWallOverride = wallCabinetWidthOverride(connectedResize.module, connectedResize.patch.width, sceneApi);
867
+ if (connectedWallOverride)
868
+ overrides.push(connectedWallOverride);
869
+ }
870
+ return overrides;
871
+ },
872
+ commit: (node, patch, sceneApi) => {
873
+ const connectedResize = isCabinetModule(node) && typeof patch.width === 'number'
874
+ ? connectedCabinetWidthResize(node, side, patch.width - node.width - cabinetWallWidthGap(node, side, sceneApi), sceneApi)
875
+ : null;
876
+ commitCabinetResize(node, patch, sceneApi);
877
+ if (connectedResize) {
878
+ commitCabinetResize(connectedResize.module, connectedResize.patch, sceneApi);
879
+ }
880
+ },
572
881
  visible: (node, sceneApi) => !isCabinetModule(node) || cabinetModuleSideOpen(node, side, sceneApi),
573
882
  placement: {
574
883
  position: (node) => [
@@ -580,23 +889,439 @@ function cabinetWidthHandle(side) {
580
889
  },
581
890
  };
582
891
  }
892
+ function cabinetDepthResizePatch(node, depth) {
893
+ return {
894
+ depth,
895
+ position: [node.position[0], node.position[1], node.position[2] + (depth - node.depth) / 2],
896
+ };
897
+ }
898
+ function snapCabinetDepth(node, requestedDepth, sceneApi) {
899
+ if (!isCabinetModule(node))
900
+ return requestedDepth;
901
+ let snappedDepth = requestedDepth;
902
+ let closestDistance = Number.POSITIVE_INFINITY;
903
+ for (const side of ['left', 'right']) {
904
+ const connected = cabinetWidthConnectedNeighbor(node, side, sceneApi);
905
+ if (!connected || isCabinetWidthFiller(connected))
906
+ continue;
907
+ const distance = Math.abs(requestedDepth - connected.depth);
908
+ if (distance <= CABINET_DEPTH_SNAP_THRESHOLD && distance < closestDistance) {
909
+ snappedDepth = connected.depth;
910
+ closestDistance = distance;
911
+ }
912
+ }
913
+ return snappedDepth;
914
+ }
583
915
  function cabinetDepthHandle() {
584
916
  return {
585
917
  kind: 'linear-resize',
586
918
  axis: 'z',
587
919
  anchor: 'min',
588
920
  min: MIN_CABINET_DEPTH,
921
+ max: (node) => cabinetResizeUpperBound(node.depth, MAX_CABINET_DEPTH),
589
922
  currentValue: (node) => node.depth,
590
- apply: (node, depth) => ({
591
- depth,
592
- position: [node.position[0], node.position[1], node.position[2] + (depth - node.depth) / 2],
593
- }),
923
+ apply: cabinetDepthResizePatch,
924
+ magneticSnap: snapCabinetDepth,
925
+ previewOverrides: (node, _depth, sceneApi) => {
926
+ const parentRunOverride = parentRunGeometryPreviewOverride(node, sceneApi);
927
+ return parentRunOverride ? [parentRunOverride] : [];
928
+ },
594
929
  commit: commitCabinetResize,
595
930
  placement: {
596
931
  position: (node) => [0, cabinetTotalHeight(node) / 2, node.depth / 2 + SIDE_HANDLE_OFFSET],
597
932
  },
598
933
  };
599
934
  }
935
+ function cornerBaseSourceRunId(node) {
936
+ const value = cabinetMetadataRecord(node.metadata).cabinetCornerDerivedRun;
937
+ if (!value || typeof value !== 'object' || Array.isArray(value))
938
+ return null;
939
+ const role = value.role;
940
+ const sourceRunId = value.sourceRunId;
941
+ return role === 'base-leg' && typeof sourceRunId === 'string' ? sourceRunId : null;
942
+ }
943
+ function connectedBaseRuns(node, sceneApi) {
944
+ const runs = new Map();
945
+ for (const candidate of Object.values(sceneApi.nodes())) {
946
+ if (isCabinetRun(candidate) && candidate.runTier === 'base') {
947
+ runs.set(candidate.id, candidate);
948
+ }
949
+ }
950
+ runs.set(node.id, node);
951
+ const neighbors = new Map();
952
+ const connect = (a, b) => {
953
+ const aNeighbors = neighbors.get(a) ?? new Set();
954
+ const bNeighbors = neighbors.get(b) ?? new Set();
955
+ aNeighbors.add(b);
956
+ bNeighbors.add(a);
957
+ neighbors.set(a, aNeighbors);
958
+ neighbors.set(b, bNeighbors);
959
+ };
960
+ for (const run of runs.values()) {
961
+ const sourceRunId = cornerBaseSourceRunId(run);
962
+ if (sourceRunId && runs.has(sourceRunId))
963
+ connect(sourceRunId, run.id);
964
+ }
965
+ const connected = [];
966
+ const pending = [node.id];
967
+ const visited = new Set();
968
+ while (pending.length > 0) {
969
+ const id = pending.shift();
970
+ if (visited.has(id))
971
+ continue;
972
+ visited.add(id);
973
+ const run = runs.get(id);
974
+ if (run)
975
+ connected.push(run);
976
+ for (const neighbor of neighbors.get(id) ?? [])
977
+ pending.push(neighbor);
978
+ }
979
+ return connected;
980
+ }
981
+ function cabinetRunFrontCenter(run, sceneApi) {
982
+ const modules = cabinetModulesForRun(run, sceneApi.nodes());
983
+ if (modules.length === 0) {
984
+ return [0, cabinetTotalHeight(run) / 2, run.depth / 2 + SIDE_HANDLE_OFFSET];
985
+ }
986
+ const minX = Math.min(...modules.map((module) => module.position[0] - module.width / 2));
987
+ const maxX = Math.max(...modules.map((module) => module.position[0] + module.width / 2));
988
+ const maxZ = Math.max(...modules.map((module) => module.position[2] + module.depth / 2));
989
+ return [(minX + maxX) / 2, cabinetTotalHeight(run) / 2, maxZ + SIDE_HANDLE_OFFSET];
990
+ }
991
+ function cabinetRunPointInSelectedFrame(selected, target, point, sceneApi) {
992
+ const nodes = sceneApi.nodes();
993
+ const selectedWorld = cabinetDuplicateWorldPose(selected, nodes);
994
+ const targetWorld = cabinetDuplicateWorldPose(target, nodes);
995
+ if (!selectedWorld || !targetWorld)
996
+ return [point[0], point[1], point[2]];
997
+ const worldPoint = composeCabinetDuplicatePose(targetWorld.position, targetWorld.rotation, point, 0).position;
998
+ const dx = worldPoint[0] - selectedWorld.position[0];
999
+ const dz = worldPoint[2] - selectedWorld.position[2];
1000
+ const cos = Math.cos(selectedWorld.rotation);
1001
+ const sin = Math.sin(selectedWorld.rotation);
1002
+ return [cos * dx - sin * dz, worldPoint[1] - selectedWorld.position[1], sin * dx + cos * dz];
1003
+ }
1004
+ function cabinetRootRun(node, sceneApi) {
1005
+ let current = node;
1006
+ let root = isCabinetRun(current) ? current : null;
1007
+ const visited = new Set();
1008
+ while (current.parentId && !visited.has(current.parentId)) {
1009
+ visited.add(current.parentId);
1010
+ const parent = sceneApi.get(current.parentId);
1011
+ if (!isCabinetRun(parent) && !isCabinetModule(parent))
1012
+ break;
1013
+ current = parent;
1014
+ if (isCabinetRun(current))
1015
+ root = current;
1016
+ }
1017
+ return root;
1018
+ }
1019
+ function cabinetWallTargets(node, sceneApi) {
1020
+ const root = cabinetRootRun(node, sceneApi);
1021
+ if (!root)
1022
+ return [];
1023
+ const targets = [];
1024
+ const visited = new Set();
1025
+ const visit = (current) => {
1026
+ if (visited.has(current.id))
1027
+ return;
1028
+ visited.add(current.id);
1029
+ if (isCabinetRun(current) && current.runTier === 'wall') {
1030
+ targets.push(current);
1031
+ return;
1032
+ }
1033
+ const parent = current.parentId ? sceneApi.get(current.parentId) : undefined;
1034
+ if (isCabinetModule(current) && isCabinetModule(parent)) {
1035
+ if (!isHoodOnlyCabinet(current))
1036
+ targets.push(current);
1037
+ return;
1038
+ }
1039
+ for (const childId of current.children ?? []) {
1040
+ const child = sceneApi.get(childId);
1041
+ if (isCabinetRun(child) || isCabinetModule(child))
1042
+ visit(child);
1043
+ }
1044
+ };
1045
+ visit(root);
1046
+ return targets;
1047
+ }
1048
+ function cabinetWallDepthPreview(targets, depth, sceneApi, adjustCornerWidths, widthMode, cornerIndex) {
1049
+ const overrides = new Map();
1050
+ const liveTargets = targets.map((target) => sceneApi.get(target.id) ?? target);
1051
+ const depthDelta = depth - (liveTargets[0]?.depth ?? depth);
1052
+ for (const live of liveTargets) {
1053
+ const nextDepth = live.depth + depthDelta;
1054
+ if (isCabinetRun(live)) {
1055
+ overrides.set(live.id, { depth: nextDepth });
1056
+ for (const [id, patch] of depthDeltaRunOverrides(live, sceneApi.nodes(), depthDelta)) {
1057
+ overrides.set(id, { ...(overrides.get(id) ?? {}), ...patch });
1058
+ }
1059
+ continue;
1060
+ }
1061
+ overrides.set(live.id, cabinetDepthResizePatch(live, nextDepth));
1062
+ }
1063
+ if (adjustCornerWidths) {
1064
+ for (const [id, patch] of wallCornerWidthOverridesForDepthTargets({
1065
+ cornerIndex,
1066
+ depth,
1067
+ nodes: sceneApi.nodes(),
1068
+ targets: liveTargets,
1069
+ widthMode,
1070
+ })) {
1071
+ const existing = overrides.get(id);
1072
+ const cornerPatch = patch;
1073
+ const merged = { ...(existing ?? {}), ...cornerPatch };
1074
+ if (existing?.position && cornerPatch.position) {
1075
+ merged.position = [cornerPatch.position[0], cornerPatch.position[1], existing.position[2]];
1076
+ }
1077
+ overrides.set(id, merged);
1078
+ }
1079
+ }
1080
+ return [...overrides];
1081
+ }
1082
+ function commitCabinetWallDepth(targets, depth, sceneApi, adjustCornerWidths, widthMode, cornerIndex) {
1083
+ const preview = cabinetWallDepthPreview(targets, depth, sceneApi, adjustCornerWidths, widthMode, cornerIndex);
1084
+ for (const [id, patch] of preview) {
1085
+ sceneApi.update(id, patch);
1086
+ }
1087
+ const bumpedRuns = new Set();
1088
+ for (const [id] of preview) {
1089
+ const live = sceneApi.get(id);
1090
+ if (isCabinetRun(live)) {
1091
+ if (!bumpedRuns.has(live.id)) {
1092
+ bumpedRuns.add(live.id);
1093
+ bumpCabinetRunLayoutRevision(sceneApi, live);
1094
+ }
1095
+ continue;
1096
+ }
1097
+ const parent = live?.parentId ? sceneApi.get(live.parentId) : undefined;
1098
+ if (isCabinetRun(parent) && !bumpedRuns.has(parent.id)) {
1099
+ bumpedRuns.add(parent.id);
1100
+ bumpCabinetRunLayoutRevision(sceneApi, parent);
1101
+ }
1102
+ else if (isCabinetModule(parent)) {
1103
+ sceneApi.markDirty(parent.id);
1104
+ }
1105
+ }
1106
+ }
1107
+ function cabinetWallDepthBounds(targets, sceneApi, adjustCornerWidths, widthMode, cornerIndex) {
1108
+ const liveTargets = targets.map((target) => sceneApi.get(target.id) ?? target);
1109
+ const currentDepth = liveTargets[0]?.depth ?? MIN_CABINET_DEPTH;
1110
+ const sharedBounds = sharedDepthBounds(currentDepth, liveTargets, sceneApi.nodes());
1111
+ let min = sharedBounds.min;
1112
+ let max = sharedBounds.max;
1113
+ if (!adjustCornerWidths)
1114
+ return { min, max };
1115
+ const baselineAdjustments = new Map(wallCornerWidthOverridesForDepthTargets({
1116
+ clampWidths: false,
1117
+ cornerIndex,
1118
+ depth: currentDepth,
1119
+ nodes: sceneApi.nodes(),
1120
+ targets: liveTargets,
1121
+ widthMode,
1122
+ }));
1123
+ const unitAdjustments = wallCornerWidthOverridesForDepthTargets({
1124
+ clampWidths: false,
1125
+ cornerIndex,
1126
+ depth: currentDepth + 1,
1127
+ nodes: sceneApi.nodes(),
1128
+ targets: liveTargets,
1129
+ widthMode,
1130
+ });
1131
+ for (const [id, patch] of unitAdjustments) {
1132
+ const cabinetPatch = patch;
1133
+ if (typeof cabinetPatch.width !== 'number')
1134
+ continue;
1135
+ const node = sceneApi.get(id);
1136
+ if (!isCabinetModule(node))
1137
+ continue;
1138
+ const baselinePatch = baselineAdjustments.get(id);
1139
+ const baselineWidth = typeof baselinePatch?.width === 'number' ? baselinePatch.width : node.width;
1140
+ const factor = cabinetPatch.width - baselineWidth;
1141
+ if (Math.abs(factor) <= CABINET_ADJACENCY_EPSILON)
1142
+ continue;
1143
+ const minWidth = node.name === 'Wall Bridge Filler'
1144
+ ? 0
1145
+ : node.name?.includes('Filler')
1146
+ ? 0.05
1147
+ : MIN_CABINET_WIDTH;
1148
+ const maxWidth = cabinetResizeUpperBound(baselineWidth, MAX_CABINET_WIDTH);
1149
+ const firstDepth = currentDepth + (minWidth - baselineWidth) / factor;
1150
+ const secondDepth = currentDepth + (maxWidth - baselineWidth) / factor;
1151
+ min = Math.max(min, Math.min(firstDepth, secondDepth));
1152
+ max = Math.min(max, Math.max(firstDepth, secondDepth));
1153
+ }
1154
+ return {
1155
+ min: Math.min(currentDepth, min),
1156
+ max: Math.max(currentDepth, max),
1157
+ };
1158
+ }
1159
+ function cabinetWallGroupDepthHandles(selected, sceneApi) {
1160
+ const targets = cabinetWallTargets(selected, sceneApi);
1161
+ if (targets.length < 2)
1162
+ return [];
1163
+ const cornerIndex = buildWallCornerDepthIndex(sceneApi.nodes());
1164
+ const groups = [];
1165
+ for (const target of targets) {
1166
+ const rotation = cabinetDuplicateWorldPose(target, sceneApi.nodes())?.rotation ?? target.rotation;
1167
+ const group = groups.find((candidate) => Math.abs(Math.atan2(Math.sin(rotation - candidate.rotation), Math.cos(rotation - candidate.rotation))) < 1e-3);
1168
+ if (group)
1169
+ group.targets.push(target);
1170
+ else
1171
+ groups.push({ rotation, targets: [target] });
1172
+ }
1173
+ const selectedRotation = cabinetDuplicateWorldPose(selected, sceneApi.nodes())?.rotation ?? 0;
1174
+ return groups.map((group) => {
1175
+ const representative = group.targets[0];
1176
+ const relativeRotation = group.rotation - selectedRotation;
1177
+ const frontX = Math.sin(relativeRotation);
1178
+ const frontZ = Math.cos(relativeRotation);
1179
+ const axis = Math.abs(frontX) > Math.abs(frontZ) ? 'x' : 'z';
1180
+ const positive = axis === 'x' ? frontX >= 0 : frontZ >= 0;
1181
+ const adjustCornerWidths = true;
1182
+ const widthMode = Math.abs(frontX) < 1e-3 && frontZ > 0 ? 'corner-pair' : 'bridge';
1183
+ const depthBounds = () => cabinetWallDepthBounds(group.targets, sceneApi, adjustCornerWidths, widthMode, cornerIndex);
1184
+ const clampedDepth = (requestedDepth, liveSceneApi) => {
1185
+ const bounds = cabinetWallDepthBounds(group.targets, liveSceneApi, adjustCornerWidths, widthMode, cornerIndex);
1186
+ return Math.min(bounds.max, Math.max(bounds.min, requestedDepth));
1187
+ };
1188
+ return {
1189
+ kind: 'linear-resize',
1190
+ axis,
1191
+ anchor: positive ? 'min' : 'max',
1192
+ min: () => depthBounds().min,
1193
+ max: () => depthBounds().max,
1194
+ currentValue: () => sceneApi.get(representative.id)?.depth ??
1195
+ representative.depth,
1196
+ overrideTarget: () => representative.id,
1197
+ apply: (_node, depth) => ({ depth }),
1198
+ previewOverrides: (_node, depth, liveSceneApi) => cabinetWallDepthPreview(group.targets, clampedDepth(depth, liveSceneApi), liveSceneApi, adjustCornerWidths, widthMode, cornerIndex),
1199
+ commit: (_node, patch, liveSceneApi) => {
1200
+ if (typeof patch.depth === 'number') {
1201
+ commitCabinetWallDepth(group.targets, clampedDepth(patch.depth, liveSceneApi), liveSceneApi, adjustCornerWidths, widthMode, cornerIndex);
1202
+ }
1203
+ },
1204
+ placement: {
1205
+ position: (node, liveSceneApi) => {
1206
+ const points = group.targets.map((target) => {
1207
+ const liveTarget = liveSceneApi.get(target.id) ?? target;
1208
+ const point = isCabinetRun(liveTarget)
1209
+ ? cabinetRunFrontCenter(liveTarget, liveSceneApi)
1210
+ : [
1211
+ 0,
1212
+ cabinetTotalHeight(liveTarget) / 2,
1213
+ liveTarget.depth / 2 + SIDE_HANDLE_OFFSET,
1214
+ ];
1215
+ return cabinetRunPointInSelectedFrame(node, liveTarget, point, liveSceneApi);
1216
+ });
1217
+ return [
1218
+ points.reduce((sum, point) => sum + point[0], 0) / points.length,
1219
+ points.reduce((sum, point) => sum + point[1], 0) / points.length,
1220
+ points.reduce((sum, point) => sum + point[2], 0) / points.length,
1221
+ ];
1222
+ },
1223
+ rotationY: () => (axis === 'x' ? relativeRotation - Math.PI / 2 : relativeRotation),
1224
+ },
1225
+ };
1226
+ });
1227
+ }
1228
+ function cabinetConnectedRunDepthHandle(selected, target, sceneApi) {
1229
+ const nodes = sceneApi.nodes();
1230
+ const selectedWorld = cabinetDuplicateWorldPose(selected, nodes);
1231
+ const targetWorld = cabinetDuplicateWorldPose(target, nodes);
1232
+ const relativeRotation = (targetWorld?.rotation ?? 0) - (selectedWorld?.rotation ?? 0);
1233
+ const frontX = Math.sin(relativeRotation);
1234
+ const frontZ = Math.cos(relativeRotation);
1235
+ const axis = Math.abs(frontX) > Math.abs(frontZ) ? 'x' : 'z';
1236
+ const positive = axis === 'x' ? frontX >= 0 : frontZ >= 0;
1237
+ const targetDepthBounds = (liveSceneApi) => {
1238
+ const liveTarget = liveSceneApi.get(target.id) ?? target;
1239
+ const compensatedWidths = [];
1240
+ const derived = cabinetMetadataRecord(liveTarget.metadata).cabinetCornerDerivedRun;
1241
+ if (derived && typeof derived === 'object' && !Array.isArray(derived)) {
1242
+ const role = derived.role;
1243
+ const side = derived.side;
1244
+ const turnSide = derived.turnSide;
1245
+ const sourceModuleId = derived.sourceModuleId;
1246
+ const sourceModule = role === 'base-leg' &&
1247
+ (turnSide === side || (turnSide !== 'left' && turnSide !== 'right')) &&
1248
+ typeof sourceModuleId === 'string'
1249
+ ? liveSceneApi.get(sourceModuleId)
1250
+ : undefined;
1251
+ if (sourceModule?.type === 'cabinet-module') {
1252
+ compensatedWidths.push(sourceModule.width);
1253
+ }
1254
+ }
1255
+ for (const childId of liveTarget.children ?? []) {
1256
+ const child = liveSceneApi.get(childId);
1257
+ if (child?.type !== 'cabinet')
1258
+ continue;
1259
+ const childDerived = cabinetMetadataRecord(child.metadata).cabinetCornerDerivedRun;
1260
+ if (!childDerived || typeof childDerived !== 'object' || Array.isArray(childDerived))
1261
+ continue;
1262
+ if (childDerived.role !== 'base-leg' ||
1263
+ childDerived.sourceRunId !== target.id) {
1264
+ continue;
1265
+ }
1266
+ const connectedModule = cabinetModulesForRun(child, liveSceneApi.nodes()).find((module) => module.name === 'Base Cabinet');
1267
+ if (connectedModule)
1268
+ compensatedWidths.push(connectedModule.width);
1269
+ }
1270
+ const connectedBounds = cabinetConnectedDepthBounds(liveTarget.depth, compensatedWidths);
1271
+ const deltaBounds = sharedDepthBounds(liveTarget.depth, [liveTarget], liveSceneApi.nodes());
1272
+ return {
1273
+ min: Math.max(connectedBounds.min, deltaBounds.min),
1274
+ max: Math.min(connectedBounds.max, deltaBounds.max),
1275
+ };
1276
+ };
1277
+ const clampedDepth = (depth, liveSceneApi) => {
1278
+ const bounds = targetDepthBounds(liveSceneApi);
1279
+ return Math.min(bounds.max, Math.max(bounds.min, depth));
1280
+ };
1281
+ return {
1282
+ kind: 'linear-resize',
1283
+ axis,
1284
+ anchor: positive ? 'min' : 'max',
1285
+ min: (_node, liveSceneApi) => targetDepthBounds(liveSceneApi).min,
1286
+ max: (_node, liveSceneApi) => targetDepthBounds(liveSceneApi).max,
1287
+ currentValue: (node) => node.id === target.id
1288
+ ? node.depth
1289
+ : (sceneApi.get(target.id)?.depth ?? target.depth),
1290
+ overrideTarget: () => target.id,
1291
+ apply: (_node, depth) => ({ depth }),
1292
+ previewOverrides: (_node, depth, liveSceneApi) => {
1293
+ const liveTarget = liveSceneApi.get(target.id) ?? target;
1294
+ const nextDepth = clampedDepth(depth, liveSceneApi);
1295
+ const moduleOverrides = depthDeltaRunOverrides(liveTarget, liveSceneApi.nodes(), nextDepth - liveTarget.depth);
1296
+ const sourceOverrides = cornerSourceWidthOverridesForDerivedDepth(liveTarget, liveSceneApi.nodes(), nextDepth);
1297
+ return previewCornerRunsFromRunSources({
1298
+ baseLayout: 'width-only',
1299
+ initialOverrides: [...moduleOverrides, ...sourceOverrides],
1300
+ run: { ...liveTarget, depth: nextDepth },
1301
+ sceneApi: liveSceneApi,
1302
+ });
1303
+ },
1304
+ commit: (_node, patch, liveSceneApi) => {
1305
+ if (typeof patch.depth !== 'number')
1306
+ return;
1307
+ const liveTarget = liveSceneApi.get(target.id) ?? target;
1308
+ const nextDepth = clampedDepth(patch.depth, liveSceneApi);
1309
+ for (const [id, sourcePatch] of cornerSourceWidthOverridesForDerivedDepth(liveTarget, liveSceneApi.nodes(), nextDepth)) {
1310
+ liveSceneApi.update(id, sourcePatch);
1311
+ }
1312
+ commitRunDepthDelta(liveTarget, nextDepth, liveSceneApi, {
1313
+ cornerSync: 'width-only',
1314
+ });
1315
+ },
1316
+ placement: {
1317
+ position: (node, liveSceneApi) => {
1318
+ const liveTarget = liveSceneApi.get(target.id) ?? target;
1319
+ return cabinetRunPointInSelectedFrame(node, liveTarget, cabinetRunFrontCenter(liveTarget, liveSceneApi), liveSceneApi);
1320
+ },
1321
+ rotationY: () => (axis === 'x' ? relativeRotation - Math.PI / 2 : relativeRotation),
1322
+ },
1323
+ };
1324
+ }
600
1325
  function cabinetHeightHandle() {
601
1326
  return {
602
1327
  kind: 'linear-resize',
@@ -655,9 +1380,18 @@ function cabinetRotateHandle() {
655
1380
  },
656
1381
  };
657
1382
  }
658
- function cabinetHandles(node) {
1383
+ function cabinetHandles(node, sceneApi) {
659
1384
  if ((node.children ?? []).length > 0) {
660
- return [cabinetRotateHandle()];
1385
+ const connectedRuns = sceneApi && node.runTier === 'base' ? connectedBaseRuns(node, sceneApi) : [];
1386
+ const depthHandles = sceneApi
1387
+ ? connectedRuns.map((run) => cabinetConnectedRunDepthHandle(node, run, sceneApi))
1388
+ : [];
1389
+ const wallDepthHandles = sceneApi ? cabinetWallGroupDepthHandles(node, sceneApi) : [];
1390
+ return [
1391
+ ...depthHandles,
1392
+ ...wallDepthHandles,
1393
+ cabinetRotateHandle(),
1394
+ ];
661
1395
  }
662
1396
  const handles = [
663
1397
  cabinetDepthHandle(),
@@ -673,16 +1407,21 @@ function isHoodOnlyCabinet(node) {
673
1407
  const stack = stackForCabinet(node);
674
1408
  return stack.length > 0 && stack.every((compartment) => isHoodCompartmentType(compartment.type));
675
1409
  }
676
- function cabinetModuleHandles(node) {
677
- const handles = [
678
- cabinetWidthHandle('left'),
679
- cabinetWidthHandle('right'),
680
- cabinetRotateHandle(),
1410
+ function cabinetModuleHandles() {
1411
+ return [
1412
+ {
1413
+ ...cabinetWidthHandle('left'),
1414
+ visible: (node, sceneApi) => !isCabinetWidthFiller(node) && !cabinetModuleSideHasCornerFiller(node, 'left', sceneApi),
1415
+ },
1416
+ {
1417
+ ...cabinetWidthHandle('right'),
1418
+ visible: (node, sceneApi) => !isCabinetWidthFiller(node) && !cabinetModuleSideHasCornerFiller(node, 'right', sceneApi),
1419
+ },
1420
+ {
1421
+ ...cabinetDepthHandle(),
1422
+ visible: (node) => !isCabinetWidthFiller(node),
1423
+ },
681
1424
  ];
682
- if (!isHoodOnlyCabinet(node)) {
683
- handles.splice(1, 0, cabinetDepthHandle(), cabinetHeightHandle());
684
- }
685
- return handles;
686
1425
  }
687
1426
  export const cabinetDefinition = {
688
1427
  kind: 'cabinet',
@@ -701,8 +1440,8 @@ export const cabinetDefinition = {
701
1440
  rotation: 0,
702
1441
  runTier: 'base',
703
1442
  children: [],
704
- width: 0.6,
705
- depth: 0.58,
1443
+ width: 0.5,
1444
+ depth: 0.5,
706
1445
  carcassHeight: 0.72,
707
1446
  operationState: 0,
708
1447
  plinthHeight: 0.1,
@@ -814,6 +1553,7 @@ export const cabinetDefinition = {
814
1553
  floorplan: buildCabinetFloorplan,
815
1554
  floorplanSiblingOverrides: cabinetFloorplanSiblingOverrides,
816
1555
  floorplanAffectedIds: cabinetFloorplanAffectedIds,
1556
+ quickActionNodeScope: 'level',
817
1557
  quickActions: cabinetQuickActions,
818
1558
  // Corner-derived leg runs hide their own tree rows; their modules are
819
1559
  // flattened into the source run's hierarchy.
@@ -876,8 +1616,8 @@ export const cabinetModuleDefinition = {
876
1616
  rotation: 0,
877
1617
  children: [],
878
1618
  cabinetType: 'base',
879
- width: 0.6,
880
- depth: 0.58,
1619
+ width: 0.5,
1620
+ depth: 0.5,
881
1621
  carcassHeight: 0.72,
882
1622
  operationState: 0,
883
1623
  plinthHeight: 0,
@@ -932,13 +1672,9 @@ export const cabinetModuleDefinition = {
932
1672
  },
933
1673
  collides: true,
934
1674
  },
935
- dragBounds: (node) => {
936
- const n = node;
937
- const height = cabinetTotalHeight(n);
938
- return {
939
- size: [n.width, height, n.depth],
940
- center: [0, height / 2, 0],
941
- };
1675
+ dragBounds: (node, nodes) => {
1676
+ const bounds = cabinetLocalBounds(node, nodes);
1677
+ return { size: bounds.size, center: bounds.center };
942
1678
  },
943
1679
  paint: cabinetPaint,
944
1680
  sceneAction: cabinetSceneAction,
@@ -983,6 +1719,7 @@ export const cabinetModuleDefinition = {
983
1719
  // 2D ↔ 3D parity: module position is run-local, so the generic overlay's
984
1720
  // plan-space translate would corrupt it on any rotated / offset run.
985
1721
  floorplanMoveTarget: cabinetModuleFloorplanMoveTarget,
1722
+ quickActionNodeScope: 'level',
986
1723
  quickActions: cabinetQuickActions,
987
1724
  tree: {
988
1725
  label: cabinetTreeLabel,