@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
@@ -1,5 +1,6 @@
1
1
  import { calculateLevelMiters, getWallPlanFootprint, resolveLevelId, selectionProxyIdFromMetadata, } from '@pascal-app/core';
2
- import { moduleMaxX, moduleMinX, planToRunLocal, runLocalToPlan, runLocalXExtent, sideInsertX, } from './run-layout';
2
+ import { MAX_CABINET_WIDTH, MIN_CABINET_WIDTH } from './resize-limits';
3
+ import { moduleMaxX, moduleMinX, planToRunLocal, runLocalToPlan, runLocalXExtent, sideInsertX, sortRunModules, } from './run-layout';
3
4
  import { CabinetModuleNode as CabinetModuleNodeSchema, CabinetNode as CabinetNodeSchema, } from './schema';
4
5
  import { backAnchoredModuleZ, hoodCompartmentHeight, newCabinetCompartment, stackForCabinet, } from './stack';
5
6
  /**
@@ -10,8 +11,9 @@ import { backAnchoredModuleZ, hoodCompartmentHeight, newCabinetCompartment, stac
10
11
  * per surface and had already drifted (gap checks, hood support, revision
11
12
  * scope).
12
13
  */
13
- export const CABINET_BASE_WIDTH = 0.6;
14
+ export const CABINET_BASE_WIDTH = 0.5;
14
15
  export const CABINET_WALL_DEPTH = 0.32;
16
+ export const CABINET_BASE_DEPTH = 0.5;
15
17
  export const CABINET_WALL_CARCASS_HEIGHT = 0.72;
16
18
  export const CABINET_TALL_DEPTH = 0.58;
17
19
  export const CABINET_TALL_PLINTH_HEIGHT = 0.1;
@@ -19,6 +21,7 @@ export const CABINET_TALL_CARCASS_HEIGHT = 2.07;
19
21
  export const CABINET_EDGE_EPSILON = 1e-4;
20
22
  const MIN_CORNER_CONNECTED_WIDTH = 0.3;
21
23
  const MIN_TRIMMED_CORNER_CONNECTED_WIDTH = 0.05;
24
+ const MIN_CORNER_BRIDGE_WIDTH = 0.05;
22
25
  const CORNER_WIDTH_SEARCH_STEP = 0.01;
23
26
  const WALL_CLEARANCE_EPSILON = 1e-5;
24
27
  export function cabinetMetadataRecord(metadata) {
@@ -70,6 +73,36 @@ function cornerDerivedRunLink(metadata) {
70
73
  sourceRunId: sourceRunId,
71
74
  };
72
75
  }
76
+ export function buildWallCornerDepthIndex(nodes) {
77
+ const groups = new Map();
78
+ for (const node of Object.values(nodes)) {
79
+ if (node?.type !== 'cabinet')
80
+ continue;
81
+ const link = cornerDerivedRunLink(node.metadata);
82
+ if (!link)
83
+ continue;
84
+ const key = [link.sourceRunId, link.sourceModuleId, link.side, link.turnSide].join('\u0000');
85
+ const group = groups.get(key) ?? { link, runIdsByRole: {} };
86
+ group.runIdsByRole[link.role] = node.id;
87
+ groups.set(key, group);
88
+ }
89
+ return [...groups.values()].flatMap(({ link, runIdsByRole }) => {
90
+ const wallLegRunId = runIdsByRole['wall-leg'];
91
+ if (!wallLegRunId)
92
+ return [];
93
+ return [
94
+ {
95
+ baseLegRunId: runIdsByRole['base-leg'],
96
+ bridgeRunId: runIdsByRole.bridge,
97
+ side: link.side,
98
+ sourceModuleId: link.sourceModuleId,
99
+ sourceRunId: link.sourceRunId,
100
+ turnSide: link.turnSide,
101
+ wallLegRunId,
102
+ },
103
+ ];
104
+ });
105
+ }
73
106
  /**
74
107
  * Deleting one member of an L-corner group removes ONLY that node (plus its
75
108
  * normal descendants) — never the other corner runs. These patches keep the
@@ -200,6 +233,245 @@ export function cabinetModulesForRun(run, nodes) {
200
233
  .map((id) => nodes[id])
201
234
  .filter((child) => child?.type === 'cabinet-module');
202
235
  }
236
+ export function backAlignedRunDepthOverrides(run, nodes, depth) {
237
+ const modules = cabinetModulesForRun(run, nodes);
238
+ if (modules.length === 0)
239
+ return [];
240
+ const backZ = runBackLineZ(modules);
241
+ const overrides = [];
242
+ for (const module of modules) {
243
+ const positionZ = backZ + depth / 2;
244
+ const parentShiftZ = positionZ - module.position[2];
245
+ overrides.push([
246
+ module.id,
247
+ {
248
+ depth,
249
+ position: [module.position[0], module.position[1], positionZ],
250
+ },
251
+ ]);
252
+ for (const childId of module.children ?? []) {
253
+ const child = nodes[childId];
254
+ if (child?.type !== 'cabinet')
255
+ continue;
256
+ overrides.push([
257
+ child.id,
258
+ {
259
+ position: [child.position[0], child.position[1], child.position[2] - parentShiftZ],
260
+ },
261
+ ]);
262
+ }
263
+ const wallChild = wallChildOf(module, nodes);
264
+ if (wallChild) {
265
+ overrides.push([
266
+ wallChild.id,
267
+ {
268
+ position: [
269
+ wallChild.position[0],
270
+ wallChild.position[1],
271
+ backAlignZ(depth, wallChild.depth),
272
+ ],
273
+ },
274
+ ]);
275
+ }
276
+ }
277
+ return overrides;
278
+ }
279
+ export function wallCornerWidthOverridesForDepthTargets({ clampWidths = true, cornerIndex, depth, nodes, targets, widthMode = 'bridge', }) {
280
+ const initialDepth = targets[0]?.depth;
281
+ if (typeof initialDepth !== 'number')
282
+ return [];
283
+ const depthDelta = depth - initialDepth;
284
+ const targetIds = new Set(targets.map((target) => target.id));
285
+ const indexedCorners = cornerIndex ?? buildWallCornerDepthIndex(nodes);
286
+ const overrides = new Map();
287
+ const setWidth = (node, requestedWidth, anchor) => {
288
+ if (!node)
289
+ return;
290
+ const existing = overrides.get(node.id);
291
+ const existingPosition = existing?.position;
292
+ const currentWidth = typeof existing?.width === 'number' ? existing.width : node.width;
293
+ const width = clampWidths ? Math.max(0, requestedWidth) : requestedWidth;
294
+ const appliedWidthDelta = width - currentWidth;
295
+ const centerDelta = anchor === 'min' ? appliedWidthDelta / 2 : -appliedWidthDelta / 2;
296
+ overrides.set(node.id, {
297
+ ...existing,
298
+ width,
299
+ position: [
300
+ (existingPosition?.[0] ?? node.position[0]) + centerDelta,
301
+ existingPosition?.[1] ?? node.position[1],
302
+ existingPosition?.[2] ?? node.position[2],
303
+ ],
304
+ });
305
+ };
306
+ for (const corner of indexedCorners) {
307
+ const wallLegRun = nodes[corner.wallLegRunId];
308
+ if (wallLegRun?.type !== 'cabinet')
309
+ continue;
310
+ const baseLegRun = corner.baseLegRunId ? nodes[corner.baseLegRunId] : undefined;
311
+ const bridgeRun = corner.bridgeRunId ? nodes[corner.bridgeRunId] : undefined;
312
+ const sourceModule = nodes[corner.sourceModuleId];
313
+ const sourceRun = nodes[corner.sourceRunId];
314
+ if (sourceModule?.type !== 'cabinet-module')
315
+ continue;
316
+ const sourceWall = wallChildOf(sourceModule, nodes);
317
+ const wallLegModules = cabinetModulesForRun(wallLegRun, nodes);
318
+ const cornerWallFiller = wallLegModules.find((module) => module.name === 'Corner Wall Filler') ?? null;
319
+ const connectedWallInRun = wallLegModules.find((module) => module.name === 'Wall Cabinet') ?? null;
320
+ const connectedBase = baseLegRun?.type === 'cabinet'
321
+ ? (cabinetModulesForRun(baseLegRun, nodes).find((module) => module.name === 'Base Cabinet') ?? null)
322
+ : null;
323
+ const connectedWall = connectedWallInRun ?? (connectedBase ? wallChildOf(connectedBase, nodes) : null);
324
+ const bridgeModules = bridgeRun?.type === 'cabinet' ? cabinetModulesForRun(bridgeRun, nodes) : [];
325
+ const bridgeFiller = bridgeModules.find((module) => module.name === 'Wall Bridge Filler') ?? null;
326
+ const standaloneBridgeFiller = bridgeModules.length === 1 && bridgeModules[0] === bridgeFiller;
327
+ const anchor = bridgeFiller?.openSide === 'left'
328
+ ? 'min'
329
+ : bridgeFiller?.openSide === 'right'
330
+ ? 'max'
331
+ : corner.side === 'right'
332
+ ? 'min'
333
+ : 'max';
334
+ const sourceDirectionChanged = (sourceWall && targetIds.has(sourceWall.id)) ||
335
+ (bridgeRun?.type === 'cabinet' && targetIds.has(bridgeRun.id));
336
+ const connectedDirectionChanged = (connectedWall && targetIds.has(connectedWall.id)) ||
337
+ targetIds.has(wallLegRun.id);
338
+ if (widthMode === 'bridge' && (sourceDirectionChanged || connectedDirectionChanged)) {
339
+ const depthReferenceRun = connectedDirectionChanged && baseLegRun?.type === 'cabinet'
340
+ ? baseLegRun
341
+ : sourceRun?.type === 'cabinet'
342
+ ? sourceRun
343
+ : null;
344
+ const requestedWidth = depthReferenceRun?.type === 'cabinet'
345
+ ? depthReferenceRun.depth - depth
346
+ : (bridgeFiller?.width ?? 0) - depthDelta;
347
+ setWidth(bridgeFiller, requestedWidth, anchor);
348
+ if (standaloneBridgeFiller &&
349
+ bridgeRun?.type === 'cabinet' &&
350
+ bridgeFiller &&
351
+ sourceRun?.type === 'cabinet') {
352
+ const fillerPatch = overrides.get(bridgeFiller.id);
353
+ const bridgeWidth = fillerPatch?.width ?? bridgeFiller.width;
354
+ overrides.set(bridgeFiller.id, {
355
+ ...fillerPatch,
356
+ position: [0, bridgeFiller.position[1], 0],
357
+ });
358
+ const bridgeSide = bridgeFiller.openSide === 'left'
359
+ ? 'right'
360
+ : bridgeFiller.openSide === 'right'
361
+ ? 'left'
362
+ : corner.side;
363
+ const bridgeWorldPosition = anchoredBridgeRunWorldPosition({
364
+ sourceWallTop: sourceWall,
365
+ sourceRun,
366
+ bridgeWidth,
367
+ side: bridgeSide,
368
+ fallbackPosition: resolveCabinetWorldTransform(bridgeRun, nodes).position,
369
+ nodes,
370
+ });
371
+ const frameParent = cabinetFrameParent(bridgeRun, nodes);
372
+ overrides.set(bridgeRun.id, {
373
+ ...(overrides.get(bridgeRun.id) ?? {}),
374
+ position: frameParent
375
+ ? worldToCabinetLocalPosition(frameParent, nodes, bridgeWorldPosition)
376
+ : bridgeWorldPosition,
377
+ });
378
+ }
379
+ }
380
+ if (widthMode === 'corner-pair' &&
381
+ (sourceDirectionChanged || connectedDirectionChanged) &&
382
+ cornerWallFiller &&
383
+ connectedWall) {
384
+ const cornerAnchor = corner.side === 'right' ? 'min' : 'max';
385
+ const connectedAnchor = corner.side === 'right' ? 'max' : 'min';
386
+ setWidth(cornerWallFiller, cornerWallFiller.width + depthDelta, cornerAnchor);
387
+ setWidth(connectedWall, connectedWall.width - depthDelta, connectedAnchor);
388
+ }
389
+ }
390
+ return [...overrides];
391
+ }
392
+ export function cornerSourceWidthOverridesForDerivedDepth(run, nodes, depth) {
393
+ const link = cornerDerivedRunLink(run.metadata);
394
+ if (link?.role !== 'base-leg' || link.turnSide !== link.side)
395
+ return [];
396
+ const sourceModule = nodes[link.sourceModuleId];
397
+ const sourceRun = nodes[link.sourceRunId];
398
+ if (sourceModule?.type !== 'cabinet-module' || sourceRun?.type !== 'cabinet')
399
+ return [];
400
+ const width = Math.max(MIN_TRIMMED_CORNER_CONNECTED_WIDTH, sourceModule.width - (depth - run.depth));
401
+ const widthDelta = width - sourceModule.width;
402
+ const direction = link.side === 'right' ? 1 : -1;
403
+ const overrides = [
404
+ [
405
+ sourceModule.id,
406
+ {
407
+ width,
408
+ position: [
409
+ sourceModule.position[0] + (direction * widthDelta) / 2,
410
+ sourceModule.position[1],
411
+ sourceModule.position[2],
412
+ ],
413
+ },
414
+ ],
415
+ ];
416
+ const wallChild = wallChildOf(sourceModule, nodes);
417
+ if (wallChild) {
418
+ overrides.push([
419
+ wallChild.id,
420
+ {
421
+ width,
422
+ position: [0, wallChild.position[1], backAlignZ(sourceModule.depth, wallChild.depth)],
423
+ },
424
+ ]);
425
+ }
426
+ const sourceLink = cornerSourceLink(sourceModule.metadata);
427
+ for (const linkedRunId of sourceLink?.linkedRunIds ?? []) {
428
+ const linkedRun = nodes[linkedRunId];
429
+ if (linkedRun?.type !== 'cabinet')
430
+ continue;
431
+ const derivedLink = cornerDerivedRunLink(linkedRun.metadata);
432
+ if (derivedLink?.role !== 'bridge' ||
433
+ derivedLink.side !== link.side ||
434
+ derivedLink.sourceModuleId !== sourceModule.id) {
435
+ continue;
436
+ }
437
+ const bridge = cabinetModulesForRun(linkedRun, nodes).find((module) => module.name === 'Wall Bridge Filler');
438
+ if (!bridge)
439
+ continue;
440
+ const bridgeWidth = Math.max(0.01, bridge.width - widthDelta);
441
+ const bridgeWidthDelta = bridgeWidth - bridge.width;
442
+ overrides.push([
443
+ bridge.id,
444
+ {
445
+ width: bridgeWidth,
446
+ position: [
447
+ bridge.position[0] - (direction * bridgeWidthDelta) / 2,
448
+ bridge.position[1],
449
+ bridge.position[2],
450
+ ],
451
+ },
452
+ ]);
453
+ const linkedMetadata = cabinetMetadataRecord(linkedRun.metadata);
454
+ const linkedRevision = typeof linkedMetadata.cabinetLayoutRevision === 'number'
455
+ ? linkedMetadata.cabinetLayoutRevision
456
+ : 0;
457
+ overrides.push([
458
+ linkedRun.id,
459
+ {
460
+ metadata: {
461
+ ...linkedMetadata,
462
+ cabinetLayoutRevision: linkedRevision + 1,
463
+ },
464
+ },
465
+ ]);
466
+ }
467
+ const metadata = cabinetMetadataRecord(sourceRun.metadata);
468
+ const revision = typeof metadata.cabinetLayoutRevision === 'number' ? metadata.cabinetLayoutRevision : 0;
469
+ overrides.push([
470
+ sourceRun.id,
471
+ { metadata: { ...metadata, cabinetLayoutRevision: revision + 1 } },
472
+ ]);
473
+ return overrides;
474
+ }
203
475
  export function cornerSourceModulesForRun(run, nodes) {
204
476
  return cabinetModulesForRun(run, nodes).filter((module) => cornerSourceLink(module.metadata) != null);
205
477
  }
@@ -539,7 +811,7 @@ function resolveCornerSourceSideWallLimitedWidth({ desiredWidth, module, nodes,
539
811
  if (side === 'right') {
540
812
  if (overlap.maxX <= fixedEdge + WALL_CLEARANCE_EPSILON)
541
813
  continue;
542
- const maxSourceRight = overlap.minX - run.depth;
814
+ const maxSourceRight = overlap.minX - module.depth;
543
815
  if (maxSourceRight <= fixedEdge + desiredWidth + WALL_CLEARANCE_EPSILON) {
544
816
  cappedWidth = Math.min(cappedWidth, Math.max(0, maxSourceRight - fixedEdge));
545
817
  }
@@ -547,15 +819,19 @@ function resolveCornerSourceSideWallLimitedWidth({ desiredWidth, module, nodes,
547
819
  }
548
820
  if (overlap.minX >= fixedEdge - WALL_CLEARANCE_EPSILON)
549
821
  continue;
550
- const minSourceLeft = overlap.maxX + run.depth;
822
+ const minSourceLeft = overlap.maxX + module.depth;
551
823
  if (minSourceLeft >= fixedEdge - desiredWidth - WALL_CLEARANCE_EPSILON) {
552
824
  cappedWidth = Math.min(cappedWidth, Math.max(0, fixedEdge - minSourceLeft));
553
825
  }
554
826
  }
555
827
  return cappedWidth;
556
828
  }
557
- function computeCornerRunLayout({ module, run, nodes, side, turnSide = side, sourceModuleOverride, minConnectedWidth = MIN_CORNER_CONNECTED_WIDTH, }) {
829
+ function computeCornerRunLayout({ module, run, nodes, side, turnSide = side, sourceModuleOverride, baseLegDepthOverride, minConnectedWidth = MIN_CORNER_CONNECTED_WIDTH, }) {
558
830
  const sourceModule = sourceModuleOverride ?? module;
831
+ const sourceDepth = sourceModule.depth;
832
+ const baseLegDepth = baseLegDepthOverride ?? CABINET_BASE_DEPTH;
833
+ const wallDepth = wallChildOf(sourceModule, nodes)?.depth ?? CABINET_WALL_DEPTH;
834
+ const wallCornerSpan = wallDepth;
559
835
  const modules = cabinetModulesForRun(run, nodes).map((entry) => entry.id === sourceModule.id ? sourceModule : entry);
560
836
  const extent = runLocalXExtent(modules);
561
837
  if (!extent || modules.length === 0)
@@ -567,8 +843,8 @@ function computeCornerRunLayout({ module, run, nodes, side, turnSide = side, sou
567
843
  const sourceAxis = [Math.cos(runWorld.rotation), -Math.sin(runWorld.rotation)];
568
844
  const sign = side === 'right' ? 1 : -1;
569
845
  const shiftedCorner = [
570
- corner[0] + sign * run.depth * sourceAxis[0],
571
- corner[2] + sign * run.depth * sourceAxis[1],
846
+ corner[0] + sign * baseLegDepth * sourceAxis[0],
847
+ corner[2] + sign * baseLegDepth * sourceAxis[1],
572
848
  ];
573
849
  const legRotation = turnSide === 'right' ? runWorld.rotation - Math.PI / 2 : runWorld.rotation + Math.PI / 2;
574
850
  const legAxis = [Math.cos(legRotation), -Math.sin(legRotation)];
@@ -576,12 +852,12 @@ function computeCornerRunLayout({ module, run, nodes, side, turnSide = side, sou
576
852
  backLeft: side === 'right'
577
853
  ? shiftedCorner
578
854
  : [
579
- shiftedCorner[0] - legAxis[0] * (run.depth + sourceModule.width),
580
- shiftedCorner[1] - legAxis[1] * (run.depth + sourceModule.width),
855
+ shiftedCorner[0] - legAxis[0] * (sourceDepth + sourceModule.width),
856
+ shiftedCorner[1] - legAxis[1] * (sourceDepth + sourceModule.width),
581
857
  ],
582
858
  desiredWidth: sourceModule.width,
583
- depth: run.depth,
584
- leadingOffset: run.depth,
859
+ depth: baseLegDepth,
860
+ leadingOffset: sourceDepth,
585
861
  nodes,
586
862
  rotation: legRotation,
587
863
  sourceNode: sourceModule,
@@ -589,8 +865,8 @@ function computeCornerRunLayout({ module, run, nodes, side, turnSide = side, sou
589
865
  if (connectedWidth < minConnectedWidth - WALL_CLEARANCE_EPSILON)
590
866
  return null;
591
867
  const connectedShelfCount = inheritedShelfCount(module);
592
- const baseLegLength = run.depth + connectedWidth;
593
- const baseFirstWidth = side === 'right' ? run.depth : connectedWidth;
868
+ const baseLegLength = sourceDepth + connectedWidth;
869
+ const baseFirstWidth = side === 'right' ? sourceDepth : connectedWidth;
594
870
  const baseBackLeft = side === 'right'
595
871
  ? shiftedCorner
596
872
  : [
@@ -601,11 +877,11 @@ function computeCornerRunLayout({ module, run, nodes, side, turnSide = side, sou
601
877
  backLeft: baseBackLeft,
602
878
  rotation: legRotation,
603
879
  firstWidth: baseFirstWidth,
604
- depth: run.depth,
880
+ depth: baseLegDepth,
605
881
  y: runWorld.position[1],
606
882
  });
607
- const wallLegLength = run.depth + connectedWidth;
608
- const wallFirstWidth = side === 'right' ? run.depth : connectedWidth;
883
+ const wallLegLength = wallCornerSpan + connectedWidth;
884
+ const wallFirstWidth = side === 'right' ? wallCornerSpan : connectedWidth;
609
885
  const wallBackLeft = side === 'right'
610
886
  ? shiftedCorner
611
887
  : [
@@ -619,7 +895,7 @@ function computeCornerRunLayout({ module, run, nodes, side, turnSide = side, sou
619
895
  depth: CABINET_WALL_DEPTH,
620
896
  y: runWorld.position[1] + wallBottomHeightForTallAlignment(),
621
897
  });
622
- const bridgeWidth = Math.max(0.01, run.depth - CABINET_WALL_DEPTH);
898
+ const bridgeWidth = Math.max(0, baseLegDepth - CABINET_WALL_DEPTH);
623
899
  const sourceCornerModule = side === 'right' ? modules.at(-1) : modules[0];
624
900
  if (!sourceCornerModule)
625
901
  return null;
@@ -650,6 +926,10 @@ function computeCornerRunLayout({ module, run, nodes, side, turnSide = side, sou
650
926
  connectedWidth,
651
927
  connectedShelfCount,
652
928
  bridgeWidth,
929
+ sourceDepth,
930
+ baseLegDepth,
931
+ wallDepth,
932
+ wallCornerSpan,
653
933
  sourceCornerWidth: sourceCornerModule.width,
654
934
  };
655
935
  }
@@ -845,13 +1125,17 @@ function cornerSelectionRootId(sourceRun, derivedRunId) {
845
1125
  ? derivedRunId
846
1126
  : sourceRun.id;
847
1127
  }
848
- function syncDerivedCornerRun({ role, run, sourceModule, sourceRun, side, turnSide, sceneApi, }) {
1128
+ function syncDerivedCornerRun({ baseLayout, role, run, sourceModule, sourceRun, side, turnSide, sceneApi, }) {
1129
+ if (baseLayout !== 'full' && role === 'bridge')
1130
+ return;
1131
+ const sourceDepth = sourceModule.depth;
849
1132
  const layout = computeCornerRunLayout({
850
1133
  module: sourceModule,
851
1134
  run: sourceRun,
852
1135
  nodes: sceneApi.nodes(),
853
1136
  side,
854
1137
  turnSide,
1138
+ baseLegDepthOverride: role === 'base-leg' ? run.depth : undefined,
855
1139
  });
856
1140
  if (!layout)
857
1141
  return;
@@ -861,22 +1145,22 @@ function syncDerivedCornerRun({ role, run, sourceModule, sourceRun, side, turnSi
861
1145
  const fullSpecs = role === 'base-leg'
862
1146
  ? side === 'right'
863
1147
  ? [
864
- ['Corner Filler', run.depth, 'right', 'corner-filler', true],
1148
+ ['Corner Filler', sourceDepth, 'right', 'corner-filler', true],
865
1149
  ['Base Cabinet', layout.connectedWidth, 'left', 'standard', false],
866
1150
  ]
867
1151
  : [
868
1152
  ['Base Cabinet', layout.connectedWidth, 'right', 'standard', false],
869
- ['Corner Filler', run.depth, 'left', 'corner-filler', true],
1153
+ ['Corner Filler', sourceDepth, 'left', 'corner-filler', true],
870
1154
  ]
871
1155
  : role === 'wall-leg'
872
1156
  ? side === 'right'
873
1157
  ? [
874
- ['Corner Wall Filler', sourceRun.depth, 'right', 'corner-filler', true],
1158
+ ['Corner Wall Filler', layout.wallCornerSpan, 'right', 'corner-filler', true],
875
1159
  ['Wall Cabinet', layout.connectedWidth, 'left', 'standard', false],
876
1160
  ]
877
1161
  : [
878
1162
  ['Wall Cabinet', layout.connectedWidth, 'right', 'standard', false],
879
- ['Corner Wall Filler', sourceRun.depth, 'left', 'corner-filler', true],
1163
+ ['Corner Wall Filler', layout.wallCornerSpan, 'left', 'corner-filler', true],
880
1164
  ]
881
1165
  : side === 'right'
882
1166
  ? [
@@ -899,11 +1183,116 @@ function syncDerivedCornerRun({ role, run, sourceModule, sourceRun, side, turnSi
899
1183
  cornerShelf: cornerShelf,
900
1184
  },
901
1185
  ]));
902
- const currentSpecs = modules.map((entry) => specByName.get(entry.name)).filter(Boolean);
1186
+ const currentSpecs = modules
1187
+ .map((entry) => {
1188
+ const spec = specByName.get(entry.name);
1189
+ if (spec)
1190
+ return { ...spec };
1191
+ if (baseLayout === 'full')
1192
+ return null;
1193
+ return {
1194
+ width: entry.width,
1195
+ openSide: entry.openSide,
1196
+ moduleKind: entry.moduleKind,
1197
+ cornerShelf: entry.cornerShelf,
1198
+ };
1199
+ })
1200
+ .filter((entry) => entry != null);
903
1201
  if (currentSpecs.length !== modules.length)
904
1202
  return;
1203
+ if (baseLayout === 'preserve-connected-widths' && (role === 'base-leg' || role === 'wall-leg')) {
1204
+ const fillerName = role === 'base-leg' ? 'Corner Filler' : 'Corner Wall Filler';
1205
+ modules.forEach((entry, index) => {
1206
+ if (entry.name !== fillerName)
1207
+ currentSpecs[index].width = entry.width;
1208
+ });
1209
+ }
1210
+ if (baseLayout === 'width-only' && (role === 'base-leg' || role === 'wall-leg')) {
1211
+ const fillerName = role === 'base-leg' ? 'Corner Filler' : 'Corner Wall Filler';
1212
+ const connectedName = role === 'base-leg' ? 'Base Cabinet' : 'Wall Cabinet';
1213
+ const fillerIndex = modules.findIndex((entry) => entry.name === fillerName);
1214
+ const connectedIndex = modules.findIndex((entry) => entry.name === connectedName);
1215
+ if (fillerIndex >= 0 && connectedIndex >= 0) {
1216
+ const pairWidth = modules[fillerIndex].width + modules[connectedIndex].width;
1217
+ const currentConnectedWidth = modules[connectedIndex].width;
1218
+ const minConnectedWidth = Math.min(currentConnectedWidth, MIN_CABINET_WIDTH);
1219
+ const maxConnectedWidth = Math.max(currentConnectedWidth, MAX_CABINET_WIDTH);
1220
+ currentSpecs[connectedIndex].width = Math.min(maxConnectedWidth, Math.max(minConnectedWidth, pairWidth - currentSpecs[fillerIndex].width));
1221
+ }
1222
+ }
905
1223
  const currentWidths = currentSpecs.map((entry) => entry.width);
906
1224
  const currentCenters = chainModuleCenters(currentWidths);
1225
+ if (baseLayout !== 'full' && (role === 'base-leg' || role === 'wall-leg')) {
1226
+ const nextTotalWidth = currentWidths.reduce((sum, width) => sum + width, 0);
1227
+ const fixedEdge = side === 'right'
1228
+ ? Math.min(...modules.map((entry) => entry.position[0] - entry.width / 2))
1229
+ : Math.max(...modules.map((entry) => entry.position[0] + entry.width / 2)) - nextTotalWidth;
1230
+ let cursor = fixedEdge;
1231
+ modules.forEach((entry, index) => {
1232
+ const spec = currentSpecs[index];
1233
+ if (!spec)
1234
+ return;
1235
+ const positionX = cursor + spec.width / 2;
1236
+ cursor += spec.width;
1237
+ sceneApi.update(entry.id, {
1238
+ width: spec.width,
1239
+ position: [positionX, entry.position[1], entry.position[2]],
1240
+ });
1241
+ const parentShiftX = positionX - entry.position[0];
1242
+ const sourceLink = cornerSourceLink(entry.metadata);
1243
+ const sourceEdgeShift = sourceLink?.side === 'right'
1244
+ ? positionX + spec.width / 2 - (entry.position[0] + entry.width / 2)
1245
+ : sourceLink?.side === 'left'
1246
+ ? positionX - spec.width / 2 - (entry.position[0] - entry.width / 2)
1247
+ : parentShiftX;
1248
+ for (const childId of entry.children ?? []) {
1249
+ const child = sceneApi.get(childId);
1250
+ if (child?.type !== 'cabinet')
1251
+ continue;
1252
+ const derivedLink = cornerDerivedRunLink(child.metadata);
1253
+ if (derivedLink?.sourceModuleId === entry.id && derivedLink.sourceRunId === run.id) {
1254
+ sceneApi.update(child.id, {
1255
+ position: [
1256
+ child.position[0] + sourceEdgeShift - parentShiftX,
1257
+ child.position[1],
1258
+ child.position[2],
1259
+ ],
1260
+ });
1261
+ continue;
1262
+ }
1263
+ sceneApi.update(child.id, {
1264
+ position: [child.position[0] - parentShiftX, child.position[1], child.position[2]],
1265
+ });
1266
+ }
1267
+ const liveRun = sceneApi.get(run.id) ?? run;
1268
+ for (const childId of liveRun.children ?? []) {
1269
+ const child = sceneApi.get(childId);
1270
+ if (child?.type !== 'cabinet')
1271
+ continue;
1272
+ const derivedLink = cornerDerivedRunLink(child.metadata);
1273
+ if (derivedLink?.role !== 'base-leg' ||
1274
+ derivedLink.sourceModuleId !== entry.id ||
1275
+ derivedLink.sourceRunId !== run.id) {
1276
+ continue;
1277
+ }
1278
+ sceneApi.update(child.id, {
1279
+ position: [child.position[0] + sourceEdgeShift, child.position[1], child.position[2]],
1280
+ });
1281
+ }
1282
+ const wallChild = wallChildOf(entry, sceneApi.nodes());
1283
+ if (wallChild) {
1284
+ const offsetX = role === 'base-leg' && entry.name === 'Base Cabinet'
1285
+ ? (side === 'right' ? 1 : -1) * (layout.wallCornerSpan - sourceDepth)
1286
+ : 0;
1287
+ sceneApi.update(wallChild.id, {
1288
+ width: spec.width,
1289
+ position: [offsetX, wallChild.position[1], wallChild.position[2]],
1290
+ });
1291
+ }
1292
+ });
1293
+ bumpCabinetRunLayoutRevision(sceneApi, sceneApi.get(run.id) ?? run);
1294
+ return;
1295
+ }
907
1296
  const firstName = modules[0].name;
908
1297
  const firstIndex = fullNames.indexOf(firstName);
909
1298
  if (firstIndex < 0)
@@ -929,10 +1318,21 @@ function syncDerivedCornerRun({ role, run, sourceModule, sourceRun, side, turnSi
929
1318
  : layout.bridgeRunPosition;
930
1319
  const sourceRunWorld = resolveCabinetWorldTransform(sourceRun, sceneApi.nodes());
931
1320
  const rotation = role === 'bridge' ? sourceRunWorld.rotation : layout.legRotation;
932
- const depth = role === 'base-leg' ? sourceRun.depth : CABINET_WALL_DEPTH;
1321
+ const depth = role === 'bridge' ? layout.wallDepth : run.depth;
1322
+ const depthAdjustedAnchorPosition = role !== 'base-leg' && !isStandaloneBridgeFillerRun
1323
+ ? runLocalToPlan({ position: anchorPosition, rotation }, [
1324
+ 0,
1325
+ 0,
1326
+ (depth - layout.wallDepth) / 2,
1327
+ ])
1328
+ : anchorPosition;
933
1329
  const runWorldPosition = isStandaloneBridgeFillerRun
934
- ? anchorPosition
935
- : runLocalToPlan({ position: anchorPosition, rotation }, [fullCenters[firstIndex] ?? 0, 0, 0]);
1330
+ ? depthAdjustedAnchorPosition
1331
+ : runLocalToPlan({ position: depthAdjustedAnchorPosition, rotation }, [
1332
+ fullCenters[firstIndex] ?? 0,
1333
+ 0,
1334
+ 0,
1335
+ ]);
936
1336
  // Place relative to the derived run's ACTUAL parent frame — source run for
937
1337
  // new scenes, source module for legacy scenes that nested legs under it.
938
1338
  const frameParent = cabinetFrameParent(run, sceneApi.nodes()) ?? sourceRun;
@@ -967,7 +1367,7 @@ function syncDerivedCornerRun({ role, run, sourceModule, sourceRun, side, turnSi
967
1367
  position: [
968
1368
  currentCenters[index] ?? 0,
969
1369
  role === 'base-leg' ? runModuleBaseY(sourceRun) : 0,
970
- 0,
1370
+ role === 'base-leg' ? backAnchoredModuleZ(entry.position[2], entry.depth, depth) : 0,
971
1371
  ],
972
1372
  toeKickDepth: role === 'base-leg' ? sourceRun.toeKickDepth : 0,
973
1373
  countertopThickness: role === 'base-leg' ? sourceRun.countertopThickness : 0,
@@ -992,12 +1392,14 @@ function syncDerivedCornerRun({ role, run, sourceModule, sourceRun, side, turnSi
992
1392
  sceneApi,
993
1393
  shelfCount: layout.connectedShelfCount,
994
1394
  openSide: connectedBaseModule.openSide,
1395
+ offsetX: (side === 'right' ? 1 : -1) * (layout.wallCornerSpan - layout.sourceDepth),
1396
+ wallDepth: CABINET_WALL_DEPTH,
995
1397
  });
996
1398
  }
997
1399
  }
998
1400
  bumpCabinetRunLayoutRevision(sceneApi, sceneApi.get(run.id) ?? run);
999
1401
  }
1000
- export function syncCornerRunsFromSourceModule({ module, run, sceneApi, }) {
1402
+ export function syncCornerRunsFromSourceModule({ baseLayout = 'full', module, run, sceneApi, }) {
1001
1403
  const link = cornerSourceLink(module.metadata);
1002
1404
  if (!link)
1003
1405
  return;
@@ -1009,6 +1411,7 @@ export function syncCornerRunsFromSourceModule({ module, run, sceneApi, }) {
1009
1411
  if (!derivedLink)
1010
1412
  continue;
1011
1413
  syncDerivedCornerRun({
1414
+ baseLayout,
1012
1415
  role: derivedLink.role,
1013
1416
  run: linkedRun,
1014
1417
  sourceModule: module,
@@ -1019,6 +1422,45 @@ export function syncCornerRunsFromSourceModule({ module, run, sceneApi, }) {
1019
1422
  });
1020
1423
  }
1021
1424
  }
1425
+ export function syncCornerRunsFromRunSources({ baseLayout = 'full', run, sceneApi, }) {
1426
+ const effectiveBaseLayout = baseLayout === 'width-only' && !cornerDerivedRunLink(run.metadata)
1427
+ ? 'preserve-connected-widths'
1428
+ : baseLayout;
1429
+ for (const sourceModule of cornerSourceModulesForRun(run, sceneApi.nodes())) {
1430
+ syncCornerRunsFromSourceModule({
1431
+ baseLayout: effectiveBaseLayout,
1432
+ module: sourceModule,
1433
+ run,
1434
+ sceneApi,
1435
+ });
1436
+ }
1437
+ }
1438
+ export function previewCornerRunsFromRunSources({ baseLayout = 'full', initialOverrides = [], run, sceneApi, }) {
1439
+ const overrides = new Map();
1440
+ for (const [id, patch] of initialOverrides) {
1441
+ overrides.set(id, { ...(overrides.get(id) ?? {}), ...patch });
1442
+ }
1443
+ const previewNodes = { ...sceneApi.nodes() };
1444
+ for (const [id, patch] of overrides) {
1445
+ const current = previewNodes[id];
1446
+ if (current)
1447
+ previewNodes[id] = { ...current, ...patch };
1448
+ }
1449
+ const previewSceneApi = {
1450
+ ...sceneApi,
1451
+ get: (id) => previewNodes[id],
1452
+ nodes: () => previewNodes,
1453
+ update: (id, patch) => {
1454
+ overrides.set(id, { ...(overrides.get(id) ?? {}), ...patch });
1455
+ const current = previewNodes[id];
1456
+ if (current)
1457
+ previewNodes[id] = { ...current, ...patch };
1458
+ },
1459
+ markDirty: () => { },
1460
+ };
1461
+ syncCornerRunsFromRunSources({ baseLayout, run, sceneApi: previewSceneApi });
1462
+ return [...overrides];
1463
+ }
1022
1464
  /**
1023
1465
  * Insert a new base module flush against the anchor's side (or the run's
1024
1466
  * outer edge with no anchor). Gap-checked — returns null when a flush
@@ -1035,10 +1477,10 @@ export function planCabinetModuleSideAddition({ anchorModule, nodes, run, side,
1035
1477
  });
1036
1478
  if (x == null)
1037
1479
  return null;
1038
- const depth = run.depth;
1039
- const z = anchorModule
1040
- ? backAnchoredModuleZ(anchorModule.position[2], anchorModule.depth, depth)
1041
- : 0;
1480
+ const sortedModules = sortRunModules(modules);
1481
+ const depthSource = anchorModule ?? (side === 'left' ? sortedModules[0] : sortedModules.at(-1)) ?? null;
1482
+ const depth = depthSource?.depth ?? run.depth;
1483
+ const z = depthSource ? backAnchoredModuleZ(depthSource.position[2], depthSource.depth, depth) : 0;
1042
1484
  const width = resolveSideAddedModuleWidth({
1043
1485
  centerX: x,
1044
1486
  centerZ: z,
@@ -1047,7 +1489,7 @@ export function planCabinetModuleSideAddition({ anchorModule, nodes, run, side,
1047
1489
  nodes,
1048
1490
  run,
1049
1491
  side,
1050
- sourceNode: anchorModule ?? run,
1492
+ sourceNode: depthSource ?? run,
1051
1493
  });
1052
1494
  if (width < MIN_CORNER_CONNECTED_WIDTH - WALL_CLEARANCE_EPSILON)
1053
1495
  return null;
@@ -1135,7 +1577,7 @@ export function addCornerRun({ module, run, sceneApi, side, }) {
1135
1577
  });
1136
1578
  if (!resolvedLayout)
1137
1579
  return null;
1138
- const { baseRunPosition, wallRunPosition, bridgeFillerRunPosition, legRotation, connectedWidth, connectedShelfCount, bridgeWidth, } = resolvedLayout;
1580
+ const { baseRunPosition, wallRunPosition, bridgeFillerRunPosition, legRotation, connectedWidth, connectedShelfCount, bridgeWidth, sourceDepth, baseLegDepth, wallDepth, wallCornerSpan, } = resolvedLayout;
1139
1581
  const runWorld = resolveCabinetWorldTransform(sourceRun, sceneApi.nodes());
1140
1582
  const sourceWallChildId = ensureWallCabinetAbove({
1141
1583
  module: sourceModule,
@@ -1155,7 +1597,7 @@ export function addCornerRun({ module, run, sceneApi, side, }) {
1155
1597
  ? [
1156
1598
  {
1157
1599
  name: 'Corner Filler',
1158
- width: sourceRun.depth,
1600
+ width: sourceDepth,
1159
1601
  moduleKind: 'corner-filler',
1160
1602
  openSide: 'right',
1161
1603
  cornerShelf: true,
@@ -1177,7 +1619,7 @@ export function addCornerRun({ module, run, sceneApi, side, }) {
1177
1619
  },
1178
1620
  {
1179
1621
  name: 'Corner Filler',
1180
- width: sourceRun.depth,
1622
+ width: sourceDepth,
1181
1623
  moduleKind: 'corner-filler',
1182
1624
  openSide: 'left',
1183
1625
  cornerShelf: true,
@@ -1185,7 +1627,7 @@ export function addCornerRun({ module, run, sceneApi, side, }) {
1185
1627
  },
1186
1628
  ];
1187
1629
  const baseLeg = upsertCabinetRunWithModules({
1188
- depth: sourceRun.depth,
1630
+ depth: baseLegDepth,
1189
1631
  modulePatches: baseModules,
1190
1632
  name: 'Corner Base Run',
1191
1633
  parentId: sourceRun.id,
@@ -1218,61 +1660,63 @@ export function addCornerRun({ module, run, sceneApi, side, }) {
1218
1660
  }
1219
1661
  const baseLegRunNode = sceneApi.get(baseLeg.runId) ?? sourceRun;
1220
1662
  const cornerFillerModule = childModuleByName(baseLegRunNode, 'Corner Filler', sceneApi.nodes()) ??
1221
- sceneApi.get(baseLeg.moduleIds[0]);
1663
+ sceneApi.get(baseLeg.moduleIds[endSide === 'right' ? 0 : 1] ?? baseLeg.moduleIds[0]);
1222
1664
  const connectedBaseModule = childModuleByName(baseLegRunNode, 'Base Cabinet', sceneApi.nodes()) ??
1223
- sceneApi.get(baseLeg.moduleIds[1]);
1665
+ sceneApi.get(baseLeg.moduleIds[endSide === 'right' ? 1 : 0] ?? baseLeg.moduleIds[0]);
1224
1666
  if (cornerFillerModule) {
1225
- const bridgeRunWorldPosition = anchoredBridgeRunWorldPosition({
1226
- sourceWallTop: existingWallTop,
1227
- sourceRun,
1228
- bridgeWidth,
1229
- side: endSide,
1230
- fallbackPosition: bridgeFillerRunPosition,
1231
- nodes: sceneApi.nodes(),
1232
- });
1233
- const bridgeRunLocalPosition = worldToCabinetLocalPosition(cornerFillerModule, sceneApi.nodes(), bridgeRunWorldPosition);
1234
- const bridgeRunLocalRotation = worldToCabinetLocalRotation(cornerFillerModule, sceneApi.nodes(), runWorld.rotation);
1235
- const bridgeRun = upsertCabinetRunWithModules({
1236
- depth: CABINET_WALL_DEPTH,
1237
- modulePatches: [
1238
- {
1239
- name: 'Wall Bridge Filler',
1240
- width: bridgeWidth,
1241
- moduleKind: 'corner-filler',
1242
- openSide: endSide === 'right' ? 'left' : 'right',
1243
- cornerShelf: true,
1244
- stack: doorStack(connectedShelfCount),
1245
- },
1246
- ],
1247
- name: 'Corner Wall Bridge',
1248
- parentId: cornerFillerModule.id,
1249
- position: bridgeRunLocalPosition,
1250
- rotation: bridgeRunLocalRotation,
1251
- runTier: 'wall',
1252
- sceneApi,
1253
- sourceRun,
1254
- });
1255
- linkedRunIds.push(bridgeRun.runId);
1256
- const bridgeRunLiveMetadata = sceneApi.get(bridgeRun.runId)?.metadata ?? null;
1257
- const bridgeRunMetadata = cabinetMetadataRecord(bridgeRunLiveMetadata);
1258
- sceneApi.update(bridgeRun.runId, {
1259
- metadata: {
1260
- ...(selectionRootId === bridgeRun.runId
1261
- ? bridgeRunMetadata
1262
- : withSelectionProxyMetadata(bridgeRunLiveMetadata, selectionRootId)),
1263
- cabinetCornerDerivedRun: {
1264
- role: 'bridge',
1265
- side: endSide,
1266
- turnSide,
1267
- sourceModuleId: sourceModule.id,
1268
- sourceRunId: sourceRun.id,
1667
+ if (bridgeWidth >= MIN_CORNER_BRIDGE_WIDTH) {
1668
+ const bridgeRunWorldPosition = anchoredBridgeRunWorldPosition({
1669
+ sourceWallTop: existingWallTop,
1670
+ sourceRun,
1671
+ bridgeWidth,
1672
+ side: endSide,
1673
+ fallbackPosition: bridgeFillerRunPosition,
1674
+ nodes: sceneApi.nodes(),
1675
+ });
1676
+ const bridgeRunLocalPosition = worldToCabinetLocalPosition(cornerFillerModule, sceneApi.nodes(), bridgeRunWorldPosition);
1677
+ const bridgeRunLocalRotation = worldToCabinetLocalRotation(cornerFillerModule, sceneApi.nodes(), runWorld.rotation);
1678
+ const bridgeRun = upsertCabinetRunWithModules({
1679
+ depth: wallDepth,
1680
+ modulePatches: [
1681
+ {
1682
+ name: 'Wall Bridge Filler',
1683
+ width: bridgeWidth,
1684
+ moduleKind: 'corner-filler',
1685
+ openSide: endSide === 'right' ? 'left' : 'right',
1686
+ cornerShelf: true,
1687
+ stack: doorStack(connectedShelfCount),
1688
+ },
1689
+ ],
1690
+ name: 'Corner Wall Bridge',
1691
+ parentId: cornerFillerModule.id,
1692
+ position: bridgeRunLocalPosition,
1693
+ rotation: bridgeRunLocalRotation,
1694
+ runTier: 'wall',
1695
+ sceneApi,
1696
+ sourceRun,
1697
+ });
1698
+ linkedRunIds.push(bridgeRun.runId);
1699
+ const bridgeRunLiveMetadata = sceneApi.get(bridgeRun.runId)?.metadata ?? null;
1700
+ const bridgeRunMetadata = cabinetMetadataRecord(bridgeRunLiveMetadata);
1701
+ sceneApi.update(bridgeRun.runId, {
1702
+ metadata: {
1703
+ ...(selectionRootId === bridgeRun.runId
1704
+ ? bridgeRunMetadata
1705
+ : withSelectionProxyMetadata(bridgeRunLiveMetadata, selectionRootId)),
1706
+ cabinetCornerDerivedRun: {
1707
+ role: 'bridge',
1708
+ side: endSide,
1709
+ turnSide,
1710
+ sourceModuleId: sourceModule.id,
1711
+ sourceRunId: sourceRun.id,
1712
+ },
1269
1713
  },
1270
- },
1271
- });
1272
- for (const moduleId of bridgeRun.moduleIds) {
1273
- setCabinetSelectionProxy(sceneApi, moduleId, selectionRootId);
1714
+ });
1715
+ for (const moduleId of bridgeRun.moduleIds) {
1716
+ setCabinetSelectionProxy(sceneApi, moduleId, selectionRootId);
1717
+ }
1274
1718
  }
1275
- const wallModuleCenters = chainModuleCenters([sourceRun.depth, connectedWidth]);
1719
+ const wallModuleCenters = chainModuleCenters([wallCornerSpan, connectedWidth]);
1276
1720
  const cornerWallFillerCenter = endSide === 'right' ? (wallModuleCenters[0] ?? 0) : (wallModuleCenters[1] ?? 0);
1277
1721
  const cornerWallFillerWorldPosition = runLocalToPlan({ position: wallRunPosition, rotation: legRotation }, [cornerWallFillerCenter, 0, 0]);
1278
1722
  const wallFillerRun = upsertCabinetRunWithModules({
@@ -1280,7 +1724,7 @@ export function addCornerRun({ module, run, sceneApi, side, }) {
1280
1724
  modulePatches: [
1281
1725
  {
1282
1726
  name: 'Corner Wall Filler',
1283
- width: sourceRun.depth,
1727
+ width: wallCornerSpan,
1284
1728
  moduleKind: 'corner-filler',
1285
1729
  openSide: endSide === 'right' ? 'right' : 'left',
1286
1730
  cornerShelf: true,
@@ -1323,32 +1767,112 @@ export function addCornerRun({ module, run, sceneApi, side, }) {
1323
1767
  sceneApi,
1324
1768
  shelfCount: connectedShelfCount,
1325
1769
  openSide: connectedBaseModule.openSide,
1770
+ offsetX: (endSide === 'right' ? 1 : -1) * (wallCornerSpan - sourceDepth),
1771
+ wallDepth: CABINET_WALL_DEPTH,
1326
1772
  });
1327
1773
  if (wallChildId) {
1328
1774
  setCabinetSelectionProxy(sceneApi, wallChildId, selectionRootId);
1329
1775
  }
1330
1776
  }
1777
+ const liveSourceMetadata = sceneApi.get(sourceModule.id)?.metadata ?? null;
1778
+ const sourceMetadata = cabinetMetadataRecord(liveSourceMetadata);
1779
+ const existingSourceLink = cornerSourceLink(liveSourceMetadata);
1331
1780
  sceneApi.update(sourceModule.id, {
1332
1781
  metadata: {
1333
- ...cabinetMetadataRecord(sceneApi.get(sourceModule.id)?.metadata ?? null),
1782
+ ...sourceMetadata,
1334
1783
  cabinetCornerSourceLink: {
1335
1784
  side: endSide,
1336
- linkedRunIds,
1785
+ linkedRunIds: [
1786
+ ...new Set([...(existingSourceLink?.linkedRunIds ?? []), ...linkedRunIds]),
1787
+ ],
1337
1788
  },
1338
1789
  },
1339
1790
  });
1340
1791
  bumpCabinetRunLayoutRevision(sceneApi, sourceRun);
1341
- return baseLeg.moduleIds[1] ?? baseLeg.moduleIds[0] ?? null;
1792
+ return connectedBaseModule?.id ?? null;
1793
+ }
1794
+ function cabinetWorldBoxesOverlap(a, b) {
1795
+ const aTop = a.center[1] + a.height;
1796
+ const bTop = b.center[1] + b.height;
1797
+ if (Math.min(aTop, bTop) - Math.max(a.center[1], b.center[1]) <= CABINET_EDGE_EPSILON) {
1798
+ return false;
1799
+ }
1800
+ const axes = [
1801
+ [Math.cos(a.rotation), -Math.sin(a.rotation)],
1802
+ [Math.sin(a.rotation), Math.cos(a.rotation)],
1803
+ [Math.cos(b.rotation), -Math.sin(b.rotation)],
1804
+ [Math.sin(b.rotation), Math.cos(b.rotation)],
1805
+ ];
1806
+ const aXAxis = axes[0];
1807
+ const aZAxis = axes[1];
1808
+ const bXAxis = axes[2];
1809
+ const bZAxis = axes[3];
1810
+ const dx = b.center[0] - a.center[0];
1811
+ const dz = b.center[2] - a.center[2];
1812
+ for (const axis of axes) {
1813
+ const centerDistance = Math.abs(dx * axis[0] + dz * axis[1]);
1814
+ const aRadius = (a.width / 2) * Math.abs(aXAxis[0] * axis[0] + aXAxis[1] * axis[1]) +
1815
+ (a.depth / 2) * Math.abs(aZAxis[0] * axis[0] + aZAxis[1] * axis[1]);
1816
+ const bRadius = (b.width / 2) * Math.abs(bXAxis[0] * axis[0] + bXAxis[1] * axis[1]) +
1817
+ (b.depth / 2) * Math.abs(bZAxis[0] * axis[0] + bZAxis[1] * axis[1]);
1818
+ if (centerDistance >= aRadius + bRadius - CABINET_EDGE_EPSILON)
1819
+ return false;
1820
+ }
1821
+ return true;
1822
+ }
1823
+ function isWallTierModule(module, nodes) {
1824
+ const parent = module.parentId ? nodes[module.parentId] : undefined;
1825
+ if (parent?.type === 'cabinet')
1826
+ return parent.runTier === 'wall';
1827
+ return parent?.type === 'cabinet-module' && wallChildOf(parent, nodes)?.id === module.id;
1828
+ }
1829
+ export function wallChildAdditionOverlaps(module, run, nodes, { depth = CABINET_WALL_DEPTH, offsetX = 0, } = {}) {
1830
+ const hostLevelId = resolveCabinetHostLevelId(run, nodes);
1831
+ const moduleWorld = resolveCabinetWorldTransform(module, nodes);
1832
+ const candidatePose = composePose(moduleWorld.position, moduleWorld.rotation, [
1833
+ offsetX,
1834
+ wallBottomHeightForTallAlignment() - module.position[1],
1835
+ backAlignZ(module.depth, depth),
1836
+ ]);
1837
+ const candidate = {
1838
+ center: candidatePose.position,
1839
+ depth,
1840
+ height: CABINET_WALL_CARCASS_HEIGHT,
1841
+ rotation: candidatePose.rotation,
1842
+ width: module.width,
1843
+ };
1844
+ return Object.values(nodes).some((node) => {
1845
+ if (node?.type !== 'cabinet-module' || !isWallTierModule(node, nodes))
1846
+ return false;
1847
+ if (hostLevelId && resolveCabinetHostLevelId(node, nodes) !== hostLevelId)
1848
+ return false;
1849
+ const pose = resolveCabinetWorldTransform(node, nodes);
1850
+ return cabinetWorldBoxesOverlap(candidate, {
1851
+ center: pose.position,
1852
+ depth: node.depth,
1853
+ height: node.carcassHeight,
1854
+ rotation: pose.rotation,
1855
+ width: node.width,
1856
+ });
1857
+ });
1342
1858
  }
1343
1859
  /**
1344
1860
  * Nest a wall cabinet (or chimney hood) above a base module. Returns the new
1345
1861
  * node id, or null when the module already carries one / isn't a base unit.
1346
1862
  */
1347
- export function addWallChildAbove({ kind, module, run, sceneApi, openSide, }) {
1348
- if (resolveCabinetType(module, run) !== 'base')
1863
+ export function addWallChildAbove({ kind, module, run, sceneApi, openSide, frontOverlay = 'full', offsetX = 0, wallDepth = CABINET_WALL_DEPTH, }) {
1864
+ const liveModule = sceneApi.get(module.id) ?? module;
1865
+ const liveRun = sceneApi.get(run.id) ?? run;
1866
+ if (resolveCabinetType(liveModule, liveRun) !== 'base')
1349
1867
  return null;
1350
- if (wallChildOf(module, sceneApi.nodes()))
1868
+ if (wallChildOf(liveModule, sceneApi.nodes()))
1351
1869
  return null;
1870
+ if (wallChildAdditionOverlaps(liveModule, liveRun, sceneApi.nodes(), {
1871
+ depth: wallDepth,
1872
+ offsetX,
1873
+ })) {
1874
+ return null;
1875
+ }
1352
1876
  const isHood = kind === 'hood';
1353
1877
  const carcassHeight = isHood
1354
1878
  ? Math.max(0.4, hoodCompartmentHeight('hood-pyramid'))
@@ -1358,12 +1882,12 @@ export function addWallChildAbove({ kind, module, run, sceneApi, openSide, }) {
1358
1882
  parentId: module.id,
1359
1883
  // Wall cabinet top aligns with the default tall cabinet top.
1360
1884
  position: [
1361
- 0,
1362
- wallBottomHeightForTallAlignment() - module.position[1],
1363
- backAlignZ(module.depth, CABINET_WALL_DEPTH),
1885
+ offsetX,
1886
+ wallBottomHeightForTallAlignment() - liveModule.position[1],
1887
+ backAlignZ(liveModule.depth, wallDepth),
1364
1888
  ],
1365
- width: module.width,
1366
- depth: CABINET_WALL_DEPTH,
1889
+ width: liveModule.width,
1890
+ depth: wallDepth,
1367
1891
  carcassHeight,
1368
1892
  plinthHeight: 0,
1369
1893
  toeKickDepth: 0,
@@ -1372,27 +1896,28 @@ export function addWallChildAbove({ kind, module, run, sceneApi, openSide, }) {
1372
1896
  showPlinth: false,
1373
1897
  withCountertop: false,
1374
1898
  stack: isHood ? [newCabinetCompartment('hood-pyramid')] : doorStack(1),
1375
- frontStyle: module.frontStyle,
1376
- frontOverlay: module.frontOverlay,
1377
- handleStyle: module.handleStyle,
1378
- handlePosition: module.handlePosition,
1899
+ frontStyle: liveModule.frontStyle,
1900
+ frontOverlay,
1901
+ handleStyle: liveModule.handleStyle,
1902
+ handlePosition: liveModule.handlePosition,
1379
1903
  ...(openSide ? { openSide } : {}),
1380
1904
  });
1381
- sceneApi.upsert(wall, module.id);
1382
- sceneApi.markDirty(module.id);
1905
+ sceneApi.upsert(wall, liveModule.id);
1906
+ sceneApi.markDirty(liveModule.id);
1383
1907
  return wall.id;
1384
1908
  }
1385
- function ensureWallCabinetAbove({ module, run, sceneApi, shelfCount, openSide, }) {
1909
+ function ensureWallCabinetAbove({ module, run, sceneApi, shelfCount, openSide, offsetX = 0, wallDepth, }) {
1386
1910
  const existingWall = wallChildOf(module, sceneApi.nodes());
1387
1911
  if (existingWall) {
1912
+ const depth = wallDepth ?? existingWall.depth;
1388
1913
  sceneApi.update(existingWall.id, {
1389
1914
  width: module.width,
1390
- depth: CABINET_WALL_DEPTH,
1915
+ depth,
1391
1916
  carcassHeight: CABINET_WALL_CARCASS_HEIGHT,
1392
1917
  position: [
1393
- 0,
1918
+ offsetX,
1394
1919
  wallBottomHeightForTallAlignment() - module.position[1],
1395
- backAlignZ(module.depth, CABINET_WALL_DEPTH),
1920
+ backAlignZ(module.depth, depth),
1396
1921
  ],
1397
1922
  frontStyle: module.frontStyle,
1398
1923
  frontOverlay: module.frontOverlay,
@@ -1409,10 +1934,21 @@ function ensureWallCabinetAbove({ module, run, sceneApi, shelfCount, openSide, }
1409
1934
  run,
1410
1935
  sceneApi,
1411
1936
  openSide,
1937
+ frontOverlay: module.frontOverlay,
1938
+ offsetX,
1939
+ wallDepth,
1412
1940
  });
1413
1941
  if (!wallChildId)
1414
1942
  return null;
1943
+ const addedWall = sceneApi.get(wallChildId);
1944
+ const depth = wallDepth ?? addedWall?.depth ?? CABINET_WALL_DEPTH;
1415
1945
  sceneApi.update(wallChildId, {
1946
+ depth,
1947
+ ...(addedWall
1948
+ ? {
1949
+ position: [offsetX, addedWall.position[1], backAlignZ(module.depth, depth)],
1950
+ }
1951
+ : {}),
1416
1952
  stack: doorStack(shelfCount),
1417
1953
  });
1418
1954
  return wallChildId;