@pascal-app/nodes 1.0.0 → 1.0.3

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 (439) hide show
  1. package/dist/block/definition.d.ts.map +1 -1
  2. package/dist/block/definition.js +4 -1
  3. package/dist/block/face-host.d.ts.map +1 -1
  4. package/dist/block/face-host.js +19 -2
  5. package/dist/block/hosted-edit.d.ts +13 -0
  6. package/dist/block/hosted-edit.d.ts.map +1 -0
  7. package/dist/block/hosted-edit.js +205 -0
  8. package/dist/block/last-operation.d.ts +1 -1
  9. package/dist/block/last-operation.d.ts.map +1 -1
  10. package/dist/block/last-operation.js +6 -4
  11. package/dist/block/modal-session.d.ts +2 -1
  12. package/dist/block/modal-session.d.ts.map +1 -1
  13. package/dist/block/modal-session.js +2 -2
  14. package/dist/block/selection.d.ts.map +1 -1
  15. package/dist/block/selection.js +82 -24
  16. package/dist/block/surface.d.ts +4 -0
  17. package/dist/block/surface.d.ts.map +1 -0
  18. package/dist/block/surface.js +54 -0
  19. package/dist/block/use-block-face-operation.d.ts +2 -2
  20. package/dist/block/use-block-face-operation.d.ts.map +1 -1
  21. package/dist/block/use-block-face-operation.js +25 -11
  22. package/dist/box-vent/definition.d.ts.map +1 -1
  23. package/dist/box-vent/definition.js +1 -0
  24. package/dist/box-vent/panel.js +4 -4
  25. package/dist/building/renderer.d.ts.map +1 -1
  26. package/dist/building/renderer.js +1 -1
  27. package/dist/cabinet/appliance-layout.d.ts +26 -0
  28. package/dist/cabinet/appliance-layout.d.ts.map +1 -0
  29. package/dist/cabinet/appliance-layout.js +52 -0
  30. package/dist/cabinet/countertop-layout.d.ts +173 -0
  31. package/dist/cabinet/countertop-layout.d.ts.map +1 -0
  32. package/dist/cabinet/countertop-layout.js +80 -0
  33. package/dist/cabinet/definition.d.ts.map +1 -1
  34. package/dist/cabinet/definition.js +17 -8
  35. package/dist/cabinet/geometry/cooktop.d.ts.map +1 -1
  36. package/dist/cabinet/geometry/cooktop.js +2 -2
  37. package/dist/cabinet/geometry/run.d.ts.map +1 -1
  38. package/dist/cabinet/geometry/run.js +10 -71
  39. package/dist/cabinet/geometry/sink.d.ts +2 -12
  40. package/dist/cabinet/geometry/sink.d.ts.map +1 -1
  41. package/dist/cabinet/geometry/sink.js +8 -39
  42. package/dist/cabinet/hosted-resize.d.ts +5 -0
  43. package/dist/cabinet/hosted-resize.d.ts.map +1 -0
  44. package/dist/cabinet/hosted-resize.js +14 -0
  45. package/dist/cabinet/placement-snap.d.ts.map +1 -1
  46. package/dist/cabinet/placement-snap.js +4 -2
  47. package/dist/cabinet/run-layout.d.ts +8 -1
  48. package/dist/cabinet/run-layout.d.ts.map +1 -1
  49. package/dist/cabinet/run-layout.js +22 -14
  50. package/dist/cabinet/run-ops.d.ts +3 -3
  51. package/dist/cabinet/run-ops.js +3 -3
  52. package/dist/cabinet/run-panel.d.ts +1 -1
  53. package/dist/cabinet/run-panel.d.ts.map +1 -1
  54. package/dist/cabinet/run-panel.js +16 -7
  55. package/dist/cabinet/surfaces.d.ts +5 -0
  56. package/dist/cabinet/surfaces.d.ts.map +1 -0
  57. package/dist/cabinet/surfaces.js +113 -0
  58. package/dist/cabinet/tool.d.ts.map +1 -1
  59. package/dist/cabinet/tool.js +8 -4
  60. package/dist/cabinet/tree-structure.d.ts.map +1 -1
  61. package/dist/cabinet/tree-structure.js +10 -9
  62. package/dist/ceiling/definition.d.ts.map +1 -1
  63. package/dist/ceiling/definition.js +6 -1
  64. package/dist/ceiling/floorplan-affordances.d.ts +4 -4
  65. package/dist/ceiling/panel.js +1 -1
  66. package/dist/ceiling/renderer.d.ts.map +1 -1
  67. package/dist/ceiling/renderer.js +3 -2
  68. package/dist/chimney/definition.d.ts.map +1 -1
  69. package/dist/chimney/definition.js +1 -0
  70. package/dist/chimney/panel.js +9 -9
  71. package/dist/column/definition.d.ts.map +1 -1
  72. package/dist/column/definition.js +10 -3
  73. package/dist/column/floorplan-affordances.d.ts.map +1 -1
  74. package/dist/column/floorplan-affordances.js +6 -1
  75. package/dist/column/floorplan.d.ts.map +1 -1
  76. package/dist/column/floorplan.js +23 -11
  77. package/dist/column/hosted-resize.d.ts +116 -0
  78. package/dist/column/hosted-resize.d.ts.map +1 -0
  79. package/dist/column/hosted-resize.js +9 -0
  80. package/dist/column/panel.d.ts.map +1 -1
  81. package/dist/column/panel.js +6 -3
  82. package/dist/column/renderer.d.ts.map +1 -1
  83. package/dist/column/renderer.js +25 -103
  84. package/dist/column/shape.d.ts +22 -0
  85. package/dist/column/shape.d.ts.map +1 -0
  86. package/dist/column/shape.js +120 -0
  87. package/dist/column/surface.d.ts +4 -0
  88. package/dist/column/surface.d.ts.map +1 -0
  89. package/dist/column/surface.js +66 -0
  90. package/dist/cupola/definition.d.ts.map +1 -1
  91. package/dist/cupola/definition.js +1 -0
  92. package/dist/cupola/panel.js +1 -1
  93. package/dist/door/definition.d.ts.map +1 -1
  94. package/dist/door/definition.js +40 -1
  95. package/dist/door/door-math.d.ts +1 -1
  96. package/dist/door/door-math.d.ts.map +1 -1
  97. package/dist/door/door-math.js +7 -10
  98. package/dist/door/floorplan-affordances.d.ts.map +1 -1
  99. package/dist/door/floorplan-affordances.js +8 -12
  100. package/dist/door/move-tool.d.ts.map +1 -1
  101. package/dist/door/move-tool.js +3 -4
  102. package/dist/door/panel.d.ts.map +1 -1
  103. package/dist/door/panel.js +28 -57
  104. package/dist/door/tool.d.ts.map +1 -1
  105. package/dist/door/tool.js +20 -2
  106. package/dist/dormer/definition.d.ts.map +1 -1
  107. package/dist/dormer/definition.js +1 -0
  108. package/dist/dormer/panel-position-section.js +2 -2
  109. package/dist/dormer/panel.js +1 -1
  110. package/dist/downspout/definition.d.ts.map +1 -1
  111. package/dist/downspout/definition.js +1 -0
  112. package/dist/duct-segment/parametrics.d.ts.map +1 -1
  113. package/dist/duct-segment/parametrics.js +5 -1
  114. package/dist/duct-segment/tool.d.ts +1 -0
  115. package/dist/duct-segment/tool.d.ts.map +1 -1
  116. package/dist/duct-segment/tool.js +21 -8
  117. package/dist/duct-terminal/tool.d.ts.map +1 -1
  118. package/dist/duct-terminal/tool.js +77 -8
  119. package/dist/elevator/definition.d.ts.map +1 -1
  120. package/dist/elevator/definition.js +2 -0
  121. package/dist/elevator/parametrics.d.ts.map +1 -1
  122. package/dist/elevator/parametrics.js +98 -1
  123. package/dist/eyebrow-vent/definition.d.ts.map +1 -1
  124. package/dist/eyebrow-vent/definition.js +1 -0
  125. package/dist/eyebrow-vent/panel.js +1 -1
  126. package/dist/fence/definition.d.ts.map +1 -1
  127. package/dist/fence/definition.js +1 -0
  128. package/dist/fence/inspector-editors.js +1 -1
  129. package/dist/fence/tool.js +2 -2
  130. package/dist/guide/definition.d.ts.map +1 -1
  131. package/dist/guide/definition.js +1 -0
  132. package/dist/gutter/definition.d.ts.map +1 -1
  133. package/dist/gutter/definition.js +1 -0
  134. package/dist/hvac-equipment/tool.d.ts.map +1 -1
  135. package/dist/hvac-equipment/tool.js +8 -2
  136. package/dist/imported-mesh/definition.d.ts +5 -0
  137. package/dist/imported-mesh/definition.d.ts.map +1 -0
  138. package/dist/imported-mesh/definition.js +38 -0
  139. package/dist/imported-mesh/floorplan.d.ts +4 -0
  140. package/dist/imported-mesh/floorplan.d.ts.map +1 -0
  141. package/dist/imported-mesh/floorplan.js +34 -0
  142. package/dist/imported-mesh/geometry.d.ts +5 -0
  143. package/dist/imported-mesh/geometry.d.ts.map +1 -0
  144. package/dist/imported-mesh/geometry.js +34 -0
  145. package/dist/imported-mesh/index.d.ts +4 -0
  146. package/dist/imported-mesh/index.d.ts.map +1 -0
  147. package/dist/imported-mesh/index.js +3 -0
  148. package/dist/imported-mesh/schema.d.ts +2 -0
  149. package/dist/imported-mesh/schema.d.ts.map +1 -0
  150. package/dist/imported-mesh/schema.js +1 -0
  151. package/dist/index.d.ts +2 -0
  152. package/dist/index.d.ts.map +1 -1
  153. package/dist/index.js +8 -0
  154. package/dist/item/__fixtures__/main-floorplan.d.ts +26 -0
  155. package/dist/item/__fixtures__/main-floorplan.d.ts.map +1 -0
  156. package/dist/item/__fixtures__/main-floorplan.js +297 -0
  157. package/dist/item/definition.d.ts.map +1 -1
  158. package/dist/item/definition.js +3 -1
  159. package/dist/item/floorplan-move.d.ts +9 -1
  160. package/dist/item/floorplan-move.d.ts.map +1 -1
  161. package/dist/item/floorplan-move.js +231 -99
  162. package/dist/item/floorplan.d.ts +22 -0
  163. package/dist/item/floorplan.d.ts.map +1 -1
  164. package/dist/item/floorplan.js +72 -2
  165. package/dist/item/move-tool.d.ts +1 -1
  166. package/dist/item/move-tool.d.ts.map +1 -1
  167. package/dist/item/move-tool.js +15 -7
  168. package/dist/item/panel.js +1 -1
  169. package/dist/item/renderer.d.ts.map +1 -1
  170. package/dist/item/renderer.js +8 -2
  171. package/dist/level/renderer.js +1 -1
  172. package/dist/lineset/tool.d.ts.map +1 -1
  173. package/dist/lineset/tool.js +1 -0
  174. package/dist/liquid-line/tool.d.ts.map +1 -1
  175. package/dist/liquid-line/tool.js +5 -1
  176. package/dist/measurement/definition.d.ts.map +1 -1
  177. package/dist/measurement/definition.js +1 -0
  178. package/dist/pipe-segment/parametrics.d.ts.map +1 -1
  179. package/dist/pipe-segment/parametrics.js +5 -1
  180. package/dist/pipe-segment/tool.d.ts.map +1 -1
  181. package/dist/pipe-segment/tool.js +14 -3
  182. package/dist/pipe-trap/tool.d.ts.map +1 -1
  183. package/dist/pipe-trap/tool.js +7 -8
  184. package/dist/procedural-item/definition.d.ts +4 -0
  185. package/dist/procedural-item/definition.d.ts.map +1 -0
  186. package/dist/procedural-item/definition.js +257 -0
  187. package/dist/procedural-item/geometry.d.ts +31 -0
  188. package/dist/procedural-item/geometry.d.ts.map +1 -0
  189. package/dist/procedural-item/geometry.js +88 -0
  190. package/dist/procedural-item/index.d.ts +3 -0
  191. package/dist/procedural-item/index.d.ts.map +1 -0
  192. package/dist/procedural-item/index.js +2 -0
  193. package/dist/procedural-item/move-session.d.ts +15 -0
  194. package/dist/procedural-item/move-session.d.ts.map +1 -0
  195. package/dist/procedural-item/move-session.js +197 -0
  196. package/dist/procedural-item/move-tool.d.ts +5 -0
  197. package/dist/procedural-item/move-tool.d.ts.map +1 -0
  198. package/dist/procedural-item/move-tool.js +193 -0
  199. package/dist/procedural-item/renderer.d.ts +5 -0
  200. package/dist/procedural-item/renderer.d.ts.map +1 -0
  201. package/dist/procedural-item/renderer.js +66 -0
  202. package/dist/ridge-vent/definition.d.ts.map +1 -1
  203. package/dist/ridge-vent/definition.js +1 -0
  204. package/dist/ridge-vent/panel.js +4 -4
  205. package/dist/roof/definition.d.ts.map +1 -1
  206. package/dist/roof/definition.js +4 -1
  207. package/dist/roof/panel-model.d.ts +4 -0
  208. package/dist/roof/panel-model.d.ts.map +1 -0
  209. package/dist/roof/panel-model.js +151 -0
  210. package/dist/roof/panel.d.ts.map +1 -1
  211. package/dist/roof/panel.js +9 -8
  212. package/dist/roof/parametrics.d.ts.map +1 -1
  213. package/dist/roof/parametrics.js +16 -1
  214. package/dist/roof-segment/definition.d.ts.map +1 -1
  215. package/dist/roof-segment/definition.js +1 -0
  216. package/dist/roof-segment/panel.d.ts.map +1 -1
  217. package/dist/roof-segment/panel.js +5 -5
  218. package/dist/roof-segment/parametrics.d.ts.map +1 -1
  219. package/dist/roof-segment/parametrics.js +176 -1
  220. package/dist/scan/definition.d.ts.map +1 -1
  221. package/dist/scan/definition.js +1 -0
  222. package/dist/shared/automatic-run-end-cap.d.ts +1 -0
  223. package/dist/shared/automatic-run-end-cap.d.ts.map +1 -1
  224. package/dist/shared/automatic-run-end-cap.js +10 -0
  225. package/dist/shared/commit-opening-move.d.ts +4 -0
  226. package/dist/shared/commit-opening-move.d.ts.map +1 -0
  227. package/dist/shared/commit-opening-move.js +9 -0
  228. package/dist/shared/curtain-opening-limits.d.ts +14 -0
  229. package/dist/shared/curtain-opening-limits.d.ts.map +1 -0
  230. package/dist/shared/curtain-opening-limits.js +53 -0
  231. package/dist/shared/distribution-run-tool.d.ts +10 -0
  232. package/dist/shared/distribution-run-tool.d.ts.map +1 -1
  233. package/dist/shared/distribution-run-tool.js +56 -11
  234. package/dist/shared/draft-axis-guides.d.ts +0 -6
  235. package/dist/shared/draft-axis-guides.d.ts.map +1 -1
  236. package/dist/shared/draft-axis-guides.js +1 -8
  237. package/dist/shared/hosted-resize.d.ts +12 -0
  238. package/dist/shared/hosted-resize.d.ts.map +1 -0
  239. package/dist/shared/hosted-resize.js +166 -0
  240. package/dist/shared/opening-grid-follow.d.ts +8 -0
  241. package/dist/shared/opening-grid-follow.d.ts.map +1 -0
  242. package/dist/shared/opening-grid-follow.js +12 -0
  243. package/dist/shared/opening-property-preview-host.d.ts +3 -0
  244. package/dist/shared/opening-property-preview-host.d.ts.map +1 -0
  245. package/dist/shared/opening-property-preview-host.js +21 -0
  246. package/dist/shared/opening-property-preview.d.ts +19 -0
  247. package/dist/shared/opening-property-preview.d.ts.map +1 -0
  248. package/dist/shared/opening-property-preview.js +99 -0
  249. package/dist/shared/panel-fields.d.ts +15 -0
  250. package/dist/shared/panel-fields.d.ts.map +1 -0
  251. package/dist/shared/panel-fields.js +31 -0
  252. package/dist/shared/resting-surface-plan.d.ts +4 -0
  253. package/dist/shared/resting-surface-plan.d.ts.map +1 -0
  254. package/dist/shared/resting-surface-plan.js +53 -0
  255. package/dist/shared/surface-counterparts.d.ts +12 -0
  256. package/dist/shared/surface-counterparts.d.ts.map +1 -0
  257. package/dist/shared/surface-counterparts.js +27 -0
  258. package/dist/shared/wall-attach-target.d.ts +1 -0
  259. package/dist/shared/wall-attach-target.d.ts.map +1 -1
  260. package/dist/shared/wall-attach-target.js +4 -1
  261. package/dist/shared/wall-event-from-grid.d.ts +5 -0
  262. package/dist/shared/wall-event-from-grid.d.ts.map +1 -0
  263. package/dist/shared/wall-event-from-grid.js +36 -0
  264. package/dist/shelf/definition.d.ts.map +1 -1
  265. package/dist/shelf/definition.js +5 -2
  266. package/dist/shelf/floorplan-affordances.d.ts.map +1 -1
  267. package/dist/shelf/floorplan-affordances.js +5 -3
  268. package/dist/shelf/floorplan-move.d.ts.map +1 -1
  269. package/dist/shelf/floorplan-move.js +3 -2
  270. package/dist/shelf/floorplan.d.ts.map +1 -1
  271. package/dist/shelf/floorplan.js +9 -2
  272. package/dist/shelf/geometry.d.ts.map +1 -1
  273. package/dist/shelf/geometry.js +2 -10
  274. package/dist/site/renderer.d.ts.map +1 -1
  275. package/dist/site/renderer.js +9 -2
  276. package/dist/skylight/definition.d.ts.map +1 -1
  277. package/dist/skylight/definition.js +1 -0
  278. package/dist/skylight/panel.js +6 -6
  279. package/dist/slab/definition.d.ts.map +1 -1
  280. package/dist/slab/definition.js +4 -0
  281. package/dist/slab/panel.js +1 -1
  282. package/dist/solar-panel/definition.d.ts.map +1 -1
  283. package/dist/solar-panel/definition.js +1 -0
  284. package/dist/solar-panel/panel.js +2 -2
  285. package/dist/spawn/definition.d.ts.map +1 -1
  286. package/dist/spawn/definition.js +1 -0
  287. package/dist/spawn/panel.js +1 -1
  288. package/dist/stair/definition.d.ts.map +1 -1
  289. package/dist/stair/definition.js +2 -0
  290. package/dist/stair/panel.d.ts.map +1 -1
  291. package/dist/stair/panel.js +9 -7
  292. package/dist/stair/parametrics.d.ts.map +1 -1
  293. package/dist/stair/parametrics.js +164 -1
  294. package/dist/stair-segment/definition.d.ts.map +1 -1
  295. package/dist/stair-segment/definition.js +1 -0
  296. package/dist/stair-segment/panel.js +4 -4
  297. package/dist/stair-segment/parametrics.d.ts.map +1 -1
  298. package/dist/stair-segment/parametrics.js +81 -1
  299. package/dist/turbine-vent/definition.d.ts.map +1 -1
  300. package/dist/turbine-vent/definition.js +1 -0
  301. package/dist/turbine-vent/panel.js +1 -1
  302. package/dist/unit/definition.d.ts +10 -0
  303. package/dist/unit/definition.d.ts.map +1 -0
  304. package/dist/unit/definition.js +38 -0
  305. package/dist/unit/index.d.ts +2 -0
  306. package/dist/unit/index.d.ts.map +1 -0
  307. package/dist/unit/index.js +1 -0
  308. package/dist/unit/parametrics.d.ts +3 -0
  309. package/dist/unit/parametrics.d.ts.map +1 -0
  310. package/dist/unit/parametrics.js +4 -0
  311. package/dist/unit/schema.d.ts +2 -0
  312. package/dist/unit/schema.d.ts.map +1 -0
  313. package/dist/unit/schema.js +1 -0
  314. package/dist/unit/unit-panel.d.ts +5 -0
  315. package/dist/unit/unit-panel.d.ts.map +1 -0
  316. package/dist/unit/unit-panel.js +61 -0
  317. package/dist/wall/actions.d.ts +3 -0
  318. package/dist/wall/actions.d.ts.map +1 -0
  319. package/dist/wall/actions.js +56 -0
  320. package/dist/wall/curtain-opening-frame.d.ts +14 -0
  321. package/dist/wall/curtain-opening-frame.d.ts.map +1 -0
  322. package/dist/wall/curtain-opening-frame.js +111 -0
  323. package/dist/wall/curtain-panel-highlight.d.ts +3 -0
  324. package/dist/wall/curtain-panel-highlight.d.ts.map +1 -0
  325. package/dist/wall/curtain-panel-highlight.js +113 -0
  326. package/dist/wall/curtain-wall-adapter.d.ts +3 -0
  327. package/dist/wall/curtain-wall-adapter.d.ts.map +1 -0
  328. package/dist/wall/curtain-wall-adapter.js +55 -0
  329. package/dist/wall/curtain-wall-floorplan.d.ts +3 -0
  330. package/dist/wall/curtain-wall-floorplan.d.ts.map +1 -0
  331. package/dist/wall/curtain-wall-floorplan.js +91 -0
  332. package/dist/wall/curtain-wall-geometry.d.ts +4 -0
  333. package/dist/wall/curtain-wall-geometry.d.ts.map +1 -0
  334. package/dist/wall/curtain-wall-geometry.js +226 -0
  335. package/dist/wall/curtain-wall-layout.d.ts +16 -0
  336. package/dist/wall/curtain-wall-layout.d.ts.map +1 -0
  337. package/dist/wall/curtain-wall-layout.js +176 -0
  338. package/dist/wall/curtain-wall-materials.d.ts +5 -0
  339. package/dist/wall/curtain-wall-materials.d.ts.map +1 -0
  340. package/dist/wall/curtain-wall-materials.js +55 -0
  341. package/dist/wall/curtain-wall-panel.d.ts +14 -0
  342. package/dist/wall/curtain-wall-panel.d.ts.map +1 -0
  343. package/dist/wall/curtain-wall-panel.js +167 -0
  344. package/dist/wall/curtain-wall-piece-geometry.d.ts +5 -0
  345. package/dist/wall/curtain-wall-piece-geometry.d.ts.map +1 -0
  346. package/dist/wall/curtain-wall-piece-geometry.js +220 -0
  347. package/dist/wall/curtain-wall-shadow.d.ts +4 -0
  348. package/dist/wall/curtain-wall-shadow.d.ts.map +1 -0
  349. package/dist/wall/curtain-wall-shadow.js +15 -0
  350. package/dist/wall/definition.d.ts +0 -15
  351. package/dist/wall/definition.d.ts.map +1 -1
  352. package/dist/wall/definition.js +65 -4
  353. package/dist/wall/drawing-mode.d.ts +12 -0
  354. package/dist/wall/drawing-mode.d.ts.map +1 -0
  355. package/dist/wall/drawing-mode.js +38 -0
  356. package/dist/wall/floorplan-tool.d.ts +4 -0
  357. package/dist/wall/floorplan-tool.d.ts.map +1 -0
  358. package/dist/wall/floorplan-tool.js +107 -0
  359. package/dist/wall/floorplan.d.ts.map +1 -1
  360. package/dist/wall/floorplan.js +6 -2
  361. package/dist/wall/length-patch.d.ts +3 -0
  362. package/dist/wall/length-patch.d.ts.map +1 -0
  363. package/dist/wall/length-patch.js +14 -0
  364. package/dist/wall/move-tool.d.ts.map +1 -1
  365. package/dist/wall/move-tool.js +23 -1
  366. package/dist/wall/paint.d.ts.map +1 -1
  367. package/dist/wall/paint.js +39 -4
  368. package/dist/wall/panel-model.d.ts +4 -0
  369. package/dist/wall/panel-model.d.ts.map +1 -0
  370. package/dist/wall/panel-model.js +117 -0
  371. package/dist/wall/panel.d.ts.map +1 -1
  372. package/dist/wall/panel.js +20 -24
  373. package/dist/wall/property-preview.d.ts +7 -0
  374. package/dist/wall/property-preview.d.ts.map +1 -0
  375. package/dist/wall/property-preview.js +31 -0
  376. package/dist/wall/rectangle-command.d.ts +149 -0
  377. package/dist/wall/rectangle-command.d.ts.map +1 -0
  378. package/dist/wall/rectangle-command.js +22 -0
  379. package/dist/wall/rectangle-tool.d.ts +2 -0
  380. package/dist/wall/rectangle-tool.d.ts.map +1 -0
  381. package/dist/wall/rectangle-tool.js +147 -0
  382. package/dist/wall/renderer.d.ts.map +1 -1
  383. package/dist/wall/renderer.js +7 -4
  384. package/dist/wall/slots.d.ts +2 -2
  385. package/dist/wall/slots.d.ts.map +1 -1
  386. package/dist/wall/slots.js +10 -2
  387. package/dist/wall/split-floorplan-layer.d.ts +4 -0
  388. package/dist/wall/split-floorplan-layer.d.ts.map +1 -0
  389. package/dist/wall/split-floorplan-layer.js +71 -0
  390. package/dist/wall/split-pointer.d.ts +3 -0
  391. package/dist/wall/split-pointer.d.ts.map +1 -0
  392. package/dist/wall/split-pointer.js +95 -0
  393. package/dist/wall/split-preview.d.ts +55 -0
  394. package/dist/wall/split-preview.d.ts.map +1 -0
  395. package/dist/wall/split-preview.js +106 -0
  396. package/dist/wall/split-session.d.ts +16 -0
  397. package/dist/wall/split-session.d.ts.map +1 -0
  398. package/dist/wall/split-session.js +138 -0
  399. package/dist/wall/split-store.d.ts +16 -0
  400. package/dist/wall/split-store.d.ts.map +1 -0
  401. package/dist/wall/split-store.js +6 -0
  402. package/dist/wall/split-tool.d.ts +10 -0
  403. package/dist/wall/split-tool.d.ts.map +1 -0
  404. package/dist/wall/split-tool.js +95 -0
  405. package/dist/wall/system.d.ts.map +1 -1
  406. package/dist/wall/system.js +3 -1
  407. package/dist/wall/tool.d.ts.map +1 -1
  408. package/dist/wall/tool.js +14 -6
  409. package/dist/wall/wall-batch-system.d.ts.map +1 -1
  410. package/dist/wall/wall-batch-system.js +3 -0
  411. package/dist/window/definition.d.ts.map +1 -1
  412. package/dist/window/definition.js +48 -1
  413. package/dist/window/floorplan-affordances.d.ts.map +1 -1
  414. package/dist/window/floorplan-affordances.js +8 -10
  415. package/dist/window/move-tool.d.ts.map +1 -1
  416. package/dist/window/move-tool.js +4 -6
  417. package/dist/window/panel.d.ts.map +1 -1
  418. package/dist/window/panel.js +22 -60
  419. package/dist/window/tool.d.ts.map +1 -1
  420. package/dist/window/tool.js +34 -2
  421. package/dist/window/window-math.d.ts +1 -1
  422. package/dist/window/window-math.d.ts.map +1 -1
  423. package/dist/window/window-math.js +8 -11
  424. package/dist/zone/definition.d.ts.map +1 -1
  425. package/dist/zone/definition.js +8 -1
  426. package/dist/zone/floorplan.d.ts +1 -3
  427. package/dist/zone/floorplan.d.ts.map +1 -1
  428. package/dist/zone/floorplan.js +43 -28
  429. package/dist/zone/quantities-panel.d.ts.map +1 -1
  430. package/dist/zone/quantities-panel.js +32 -44
  431. package/dist/zone/renderer.d.ts.map +1 -1
  432. package/dist/zone/renderer.js +21 -7
  433. package/dist/zone/unit-membership.d.ts +12 -0
  434. package/dist/zone/unit-membership.d.ts.map +1 -0
  435. package/dist/zone/unit-membership.js +22 -0
  436. package/package.json +15 -9
  437. package/dist/shared/opening-documentation-fields.d.ts +0 -16
  438. package/dist/shared/opening-documentation-fields.d.ts.map +0 -1
  439. package/dist/shared/opening-documentation-fields.js +0 -54
@@ -1,6 +1,9 @@
1
1
  import { getDormerWallHorizontalBoundsAtHeight, getDormerWallOpeningVerticalBounds, } from '@pascal-app/core';
2
+ import { curtainOpeningResizeMax } from '../shared/curtain-opening-limits';
2
3
  import { buildWindowFloorplanSchedule, computeWindowFloorplanLevelData, } from '../shared/opening-documentation';
3
4
  import { publishOpeningResizeGuides } from '../shared/opening-guides-runtime';
5
+ import { createOpeningPropertyPreview } from '../shared/opening-property-preview';
6
+ import { openingPropertyPreviewHost } from '../shared/opening-property-preview-host';
4
7
  import { readRoofFaceHeightMax, readRoofFaceWidthMax } from '../shared/roof-opening-host';
5
8
  import { buildRoofWallOpeningCut } from '../shared/roof-wall-opening-cut';
6
9
  import { readHostWallCeiling } from '../shared/wall-opening-ceiling';
@@ -82,7 +85,7 @@ function windowWidthHandle(side) {
82
85
  const roofMax = readRoofFaceWidthMax(n, scene, sign);
83
86
  if (roofMax !== null)
84
87
  return Math.max(MIN_WINDOW_WIDTH, roofMax);
85
- return readWallLength(n, scene);
88
+ return curtainOpeningResizeMax(n, scene.nodes(), 'x', sign) ?? readWallLength(n, scene);
86
89
  },
87
90
  currentValue: (n) => n.width,
88
91
  onDrag: (node) => publishOpeningResizeGuides(node, true),
@@ -131,6 +134,9 @@ function windowHeightHandle(edge) {
131
134
  // Maximum: distance from the anchored edge to the wall's allowed Y
132
135
  // bounds. Top arrow caps at the wall's resolved ceiling - bottom;
133
136
  // bottom arrow caps at top (positive Y room above the floor).
137
+ const curtainMax = curtainOpeningResizeMax(n, scene.nodes(), 'y', sign);
138
+ if (curtainMax !== undefined)
139
+ return curtainMax;
134
140
  const wallH = readHostWallCeiling(n.wallId, scene);
135
141
  const anchored = edge === 'top' ? n.position[1] - n.height / 2 : n.position[1] + n.height / 2;
136
142
  return edge === 'top'
@@ -157,11 +163,51 @@ function windowHeightHandle(edge) {
157
163
  portalTarget: resolveWindowHandlePortalTarget,
158
164
  };
159
165
  }
166
+ function windowRadiusHandle(index) {
167
+ const corners = [
168
+ [-1, 1],
169
+ [1, 1],
170
+ [1, -1],
171
+ [-1, -1],
172
+ ];
173
+ return {
174
+ kind: 'corner-radius',
175
+ corner: corners[index],
176
+ width: (node) => node.width,
177
+ height: (node) => node.height,
178
+ currentValue: (node) => node.openingRadiusMode === 'individual'
179
+ ? (node.openingCornerRadii[index] ?? 0)
180
+ : node.cornerRadius,
181
+ max: (node) => Math.min(node.width, node.height) / 2,
182
+ apply: (node, radius, _scene, modifiers) => {
183
+ if (!modifiers.shiftKey) {
184
+ return { openingShape: 'rounded', openingRadiusMode: 'all', cornerRadius: radius };
185
+ }
186
+ const radii = node.openingRadiusMode === 'individual'
187
+ ? [...node.openingCornerRadii]
188
+ : [node.cornerRadius, node.cornerRadius, node.cornerRadius, node.cornerRadius];
189
+ radii[index] = radius;
190
+ return {
191
+ openingShape: 'rounded',
192
+ openingRadiusMode: 'individual',
193
+ openingCornerRadii: radii,
194
+ };
195
+ },
196
+ createPreview: (node) => createOpeningPropertyPreview(node.id, openingPropertyPreviewHost),
197
+ visible: (node) => node.openingShape !== 'arch',
198
+ portal: 'grandparent',
199
+ portalTarget: resolveWindowHandlePortalTarget,
200
+ };
201
+ }
160
202
  const windowHandles = [
161
203
  windowWidthHandle('left'),
162
204
  windowWidthHandle('right'),
163
205
  windowHeightHandle('top'),
164
206
  windowHeightHandle('bottom'),
207
+ windowRadiusHandle(0),
208
+ windowRadiusHandle(1),
209
+ windowRadiusHandle(2),
210
+ windowRadiusHandle(3),
165
211
  ];
166
212
  /**
167
213
  * Window — Phase 5 batch kind. Mirrors door's shape: hosted on walls,
@@ -217,6 +263,7 @@ export const windowDefinition = {
217
263
  },
218
264
  parametrics: windowParametrics,
219
265
  handles: windowHandles,
266
+ rendersChildren: false,
220
267
  renderer: {
221
268
  kind: 'parametric',
222
269
  module: () => import('./renderer'),
@@ -1 +1 @@
1
- {"version":3,"file":"floorplan-affordances.d.ts","sourceRoot":"","sources":["../../src/window/floorplan-affordances.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,EAKxB,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAA;AAMzB;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB,EAAE,mBAAmB,CAAC,UAAU,CAqEjE,CAAA"}
1
+ {"version":3,"file":"floorplan-affordances.d.ts","sourceRoot":"","sources":["../../src/window/floorplan-affordances.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,EAMxB,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAA;AAQzB;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB,EAAE,mBAAmB,CAAC,UAAU,CAkEjE,CAAA"}
@@ -1,4 +1,6 @@
1
- import { useLiveNodeOverrides, useScene, } from '@pascal-app/core';
1
+ import { getWallCurveLength, useLiveNodeOverrides, useScene, } from '@pascal-app/core';
2
+ import { curtainOpeningLimits } from '../shared/curtain-opening-limits';
3
+ import { projectPlanPointToWallLocalX } from '../shared/wall-attach-target';
2
4
  const MIN_WINDOW_WIDTH = 0.3;
3
5
  /**
4
6
  * 2D drag affordance for the window's width side-arrows. Sister to the 3D
@@ -27,16 +29,12 @@ export const windowWidthAffordance = {
27
29
  const initialWindowZ = node.position[2];
28
30
  const growDir = side === 'end' ? 1 : -1;
29
31
  const anchorX = side === 'end' ? initialWindowX - initialWidth / 2 : initialWindowX + initialWidth / 2;
30
- const wallStart = wall ? wall.start : [0, 0];
31
- const wallEnd = wall ? wall.end : [1, 0];
32
- const dx = wallEnd[0] - wallStart[0];
33
- const dz = wallEnd[1] - wallStart[1];
34
- const wallLength = Math.hypot(dx, dz) || 1;
35
- const dirX = dx / wallLength;
36
- const dirZ = dz / wallLength;
37
- const maxWidth = growDir > 0 ? wallLength - anchorX : anchorX;
32
+ const wallLength = wall ? getWallCurveLength(wall) : 1;
33
+ const limits = curtainOpeningLimits(node, nodes);
34
+ const margin = limits?.margin ?? 0;
35
+ const maxWidth = growDir > 0 ? (limits?.length ?? wallLength) - margin - anchorX : anchorX - margin;
38
36
  const projectToWallLocalX = (planPoint) => {
39
- return (planPoint[0] - wallStart[0]) * dirX + (planPoint[1] - wallStart[1]) * dirZ;
37
+ return wall ? projectPlanPointToWallLocalX(wall, planPoint) : planPoint[0];
40
38
  };
41
39
  const initialPointerLocalX = projectToWallLocalX(initialPlanPoint);
42
40
  let lastWidth = initialWidth;
@@ -1 +1 @@
1
- {"version":3,"file":"move-tool.d.ts","sourceRoot":"","sources":["../../src/window/move-tool.tsx"],"names":[],"mappings":"AAAA,OAAO,EAiBL,UAAU,EACX,MAAM,kBAAkB,CAAA;AAiEzB;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAoyClD,CAAA;AAED,eAAe,cAAc,CAAA"}
1
+ {"version":3,"file":"move-tool.d.ts","sourceRoot":"","sources":["../../src/window/move-tool.tsx"],"names":[],"mappings":"AAAA,OAAO,EAiBL,UAAU,EACX,MAAM,kBAAkB,CAAA;AAkEzB;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAiyClD,CAAA;AAED,eAAe,cAAc,CAAA"}
@@ -4,6 +4,7 @@ import { calculateItemRotation, clearPlacementSurface, consumePlacementDragRelea
4
4
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
5
  import { BoxGeometry, EdgesGeometry, Vector3 } from 'three';
6
6
  import { LineBasicNodeMaterial } from 'three/webgpu';
7
+ import { commitOpeningMove } from '../shared/commit-opening-move';
7
8
  import { dormerEventFromHostedWindow, getDormerWindowWorldNormal, getDormerWindowWorldYaw, resolveDormerWindowTarget, } from '../shared/dormer-wall-opening-placement';
8
9
  import { clearOpeningGuides3D, publishOpeningGuidesForWallEvent, resolveSillSnap, } from '../shared/opening-guides-runtime';
9
10
  import { beginOpeningMoveHistorySession } from '../shared/opening-move-history';
@@ -493,14 +494,13 @@ const MoveWindowTool = ({ node: movingWindowNode }) => {
493
494
  visible: original.visible,
494
495
  });
495
496
  history.commitStep(() => {
496
- useScene.getState().updateNode(movingWindowNode.id, {
497
+ commitOpeningMove(movingWindowNode.id, {
497
498
  position: [target.clampedX, target.clampedY, 0],
498
499
  rotation: [0, target.itemRotation, 0],
499
500
  side: target.side,
500
501
  parentId: target.wallId,
501
502
  wallId: target.wallId,
502
503
  roofSegmentId: undefined,
503
- metadata: {},
504
504
  visible: true,
505
505
  });
506
506
  });
@@ -736,7 +736,7 @@ const MoveWindowTool = ({ node: movingWindowNode }) => {
736
736
  visible: original.visible,
737
737
  });
738
738
  history.commitStep(() => {
739
- useScene.getState().updateNode(movingWindowNode.id, {
739
+ commitOpeningMove(movingWindowNode.id, {
740
740
  position: target.position,
741
741
  rotation,
742
742
  side,
@@ -746,7 +746,6 @@ const MoveWindowTool = ({ node: movingWindowNode }) => {
746
746
  wallId: undefined,
747
747
  roofSegmentId: undefined,
748
748
  roofFace: undefined,
749
- metadata: {},
750
749
  visible: true,
751
750
  });
752
751
  });
@@ -937,7 +936,7 @@ const MoveWindowTool = ({ node: movingWindowNode }) => {
937
936
  visible: original.visible,
938
937
  });
939
938
  history.commitStep(() => {
940
- useScene.getState().updateNode(movingWindowNode.id, {
939
+ commitOpeningMove(movingWindowNode.id, {
941
940
  position: target.position,
942
941
  rotation: [0, 0, 0],
943
942
  side: 'front',
@@ -945,7 +944,6 @@ const MoveWindowTool = ({ node: movingWindowNode }) => {
945
944
  wallId: undefined,
946
945
  roofSegmentId: segmentId,
947
946
  roofFace: target.face.id,
948
- metadata: {},
949
947
  visible: true,
950
948
  });
951
949
  });
@@ -1 +1 @@
1
- {"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/window/panel.tsx"],"names":[],"mappings":"AA6FA,MAAM,CAAC,OAAO,UAAU,WAAW,uCA21BlC"}
1
+ {"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/window/panel.tsx"],"names":[],"mappings":"AAwFA,MAAM,CAAC,OAAO,UAAU,WAAW,uCA20BlC"}
@@ -4,8 +4,10 @@ import { useInteractive, useScene, WindowNode, } from '@pascal-app/core';
4
4
  import { ActionButton, ActionGroup, cn, PanelSection, PanelWrapper, SegmentedControl, SliderControl, ToggleControl, triggerSFX, useEditor, } from '@pascal-app/editor';
5
5
  import { useViewer } from '@pascal-app/viewer';
6
6
  import { Copy, FlipHorizontal2, Move, Trash2 } from 'lucide-react';
7
- import { useCallback, useRef } from 'react';
8
- import { OpeningDocumentationFields } from '../shared/opening-documentation-fields';
7
+ import { useCallback, useEffect, useMemo } from 'react';
8
+ import { constrainCurtainOpening, curtainOpeningLimits } from '../shared/curtain-opening-limits';
9
+ import { createOpeningPropertyPreview } from '../shared/opening-property-preview';
10
+ import { openingPropertyPreviewHost } from '../shared/opening-property-preview-host';
9
11
  function isSameWindowValue(current, next) {
10
12
  if (typeof current === 'number' && typeof next === 'number') {
11
13
  return Math.abs(current - next) < 1e-6;
@@ -26,9 +28,6 @@ function normalizeWindowCornerRadii(radii, width, height) {
26
28
  return next;
27
29
  return next.map((radius) => radius * scale);
28
30
  }
29
- function isSameRadiusTuple(current, next) {
30
- return current.every((value, index) => Math.abs(value - (next[index] ?? 0)) < 1e-6);
31
- }
32
31
  const windowTypeOptions = [
33
32
  { label: 'Fixed', value: 'fixed' },
34
33
  { label: 'Sliding', value: 'sliding' },
@@ -53,7 +52,10 @@ export default function WindowPanel() {
53
52
  const setSelection = useViewer((s) => s.setSelection);
54
53
  const deleteNode = useScene((s) => s.deleteNode);
55
54
  const setMovingNode = useEditor((s) => s.setMovingNode);
56
- const previewRef = useRef(null);
55
+ const preview = useMemo(() => selectedId
56
+ ? createOpeningPropertyPreview(selectedId, openingPropertyPreviewHost)
57
+ : null, [selectedId]);
58
+ useEffect(() => () => preview?.cancel(), [preview]);
57
59
  const node = useScene((s) => selectedId ? s.nodes[selectedId] : undefined);
58
60
  // Panel slider-drag fix recipe (plans/editor-node-registry.md). Without
59
61
  // it, the 15+ SliderControls in this panel would loop on drag.
@@ -63,6 +65,7 @@ export default function WindowPanel() {
63
65
  const liveNode = useScene.getState().nodes[selectedId];
64
66
  if (liveNode?.type !== 'window')
65
67
  return;
68
+ updates = constrainCurtainOpening(liveNode, updates, useScene.getState().nodes);
66
69
  const hasChange = Object.entries(updates).some(([key, value]) => {
67
70
  const currentValue = liveNode[key];
68
71
  return !isSameWindowValue(currentValue, value);
@@ -75,43 +78,8 @@ export default function WindowPanel() {
75
78
  if (liveNode.parentId)
76
79
  scene.dirtyNodes.add(liveNode.parentId);
77
80
  }, [selectedId]);
78
- const previewWindowUpdate = useCallback((key, value) => {
79
- if (!selectedId)
80
- return;
81
- const liveNode = useScene.getState().nodes[selectedId];
82
- if (liveNode?.type !== 'window')
83
- return;
84
- if (!(previewRef.current &&
85
- previewRef.current.id === selectedId &&
86
- previewRef.current.key === key)) {
87
- previewRef.current = {
88
- id: selectedId,
89
- key,
90
- value: liveNode[key],
91
- };
92
- }
93
- if (isSameWindowValue(liveNode[key], value))
94
- return;
95
- liveNode[key] = value;
96
- useScene.getState().dirtyNodes.add(selectedId);
97
- }, [selectedId]);
98
- const commitWindowPreview = useCallback((key, value) => {
99
- if (!selectedId)
100
- return;
101
- const scene = useScene.getState();
102
- const liveNode = scene.nodes[selectedId];
103
- const preview = previewRef.current;
104
- if (liveNode?.type === 'window' && preview?.id === selectedId && preview.key === key) {
105
- ;
106
- liveNode[key] = preview.value;
107
- scene.dirtyNodes.add(selectedId);
108
- }
109
- previewRef.current = null;
110
- useScene.getState().updateNode(selectedId, {
111
- [key]: value,
112
- });
113
- scene.dirtyNodes.add(selectedId);
114
- }, [selectedId]);
81
+ const previewWindowUpdate = (key, value) => preview?.preview({ [key]: value });
82
+ const commitWindowPreview = (key, value) => preview?.commit({ [key]: value });
115
83
  const handleClose = useCallback(() => {
116
84
  setSelection({ selectedIds: [] });
117
85
  }, [setSelection]);
@@ -187,6 +155,7 @@ export default function WindowPanel() {
187
155
  }, [node, setMovingNode, setSelection]);
188
156
  if (!(node && node.type === 'window' && selectedId))
189
157
  return null;
158
+ const limits = curtainOpeningLimits(node, useScene.getState().nodes);
190
159
  const numCols = node.columnRatios.length;
191
160
  const numRows = node.rowRatios.length;
192
161
  const colSum = node.columnRatios.reduce((a, b) => a + b, 0);
@@ -245,24 +214,17 @@ export default function WindowPanel() {
245
214
  const nextUpdates = { ...updates };
246
215
  if (openingShape === 'rounded') {
247
216
  if (openingRadiusMode === 'individual') {
248
- const currentRadii = openingCornerRadii;
249
217
  const nextRadii = normalizeWindowCornerRadii(openingCornerRadii, nextWidth, nextHeight);
250
- if (!isSameRadiusTuple(currentRadii, nextRadii)) {
251
- nextUpdates.openingCornerRadii = nextRadii;
252
- }
218
+ nextUpdates.openingCornerRadii = nextRadii;
253
219
  }
254
220
  else {
255
221
  const nextRadius = Math.min(Math.max(cornerRadius, 0), getMaxSharedWindowRadius(nextWidth, nextHeight));
256
- if (Math.abs(nextRadius - cornerRadius) > 1e-6) {
257
- nextUpdates.cornerRadius = nextRadius;
258
- }
222
+ nextUpdates.cornerRadius = nextRadius;
259
223
  }
260
224
  }
261
225
  if (openingShape === 'arch') {
262
226
  const nextArchHeight = Math.min(Math.max(archHeight, 0.05), Math.max(nextHeight, 0.05));
263
- if (Math.abs(nextArchHeight - archHeight) > 1e-6) {
264
- nextUpdates.archHeight = nextArchHeight;
265
- }
227
+ nextUpdates.archHeight = nextArchHeight;
266
228
  }
267
229
  return nextUpdates;
268
230
  };
@@ -319,7 +281,7 @@ export default function WindowPanel() {
319
281
  }), options: [
320
282
  { value: 'window', label: 'Window' },
321
283
  { value: 'opening', label: 'Opening' },
322
- ], value: node.openingKind ?? 'window' }) }), _jsx(PanelSection, { title: "Documentation", children: _jsx(OpeningDocumentationFields, { constructionType: node.constructionType, dimensionReference: node.dimensionReference, finishOpeningHeight: node.finishOpeningHeight, finishOpeningWidth: node.finishOpeningWidth, mark: node.mark, masonryOpeningHeight: node.masonryOpeningHeight, masonryOpeningWidth: node.masonryOpeningWidth, onChange: handleUpdate, roughOpeningHeight: node.roughOpeningHeight, roughOpeningWidth: node.roughOpeningWidth }) }), showWindowTypeSection && (_jsxs(PanelSection, { title: "Window Type", children: [_jsx("div", { className: "grid grid-cols-2 gap-2 px-1 pt-1", children: windowTypeOptions.map((option) => {
284
+ ], value: node.openingKind ?? 'window' }) }), showWindowTypeSection && (_jsxs(PanelSection, { title: "Window Type", children: [_jsx("div", { className: "grid grid-cols-2 gap-2 px-1 pt-1", children: windowTypeOptions.map((option) => {
323
285
  const isSelected = displayedWindowType === option.value;
324
286
  return (_jsx("button", { className: cn('flex min-h-12 items-center rounded-lg border px-3 py-2.5 text-left text-xs transition-colors', isSelected
325
287
  ? 'border-orange-400/60 bg-orange-400/10 text-foreground'
@@ -343,7 +305,7 @@ export default function WindowPanel() {
343
305
  ], value: node.casementStyle ?? 'single' }), (node.casementStyle ?? 'single') === 'single' && (_jsx(SegmentedControl, { onChange: (value) => handleUpdate({ hingesSide: value }), options: [
344
306
  { value: 'left', label: 'Left' },
345
307
  { value: 'right', label: 'Right' },
346
- ], value: node.hingesSide ?? 'left' }))] })), showOperationSection && (_jsx("div", { className: "mt-2", children: _jsx(SliderControl, { label: operationLabel, max: 1, min: 0, onChange: setOperationState, precision: 2, restoreOnCommit: false, step: 0.05, value: Math.round((node.operationState ?? 0) * 100) / 100 }) }))] })), _jsxs(PanelSection, { title: "Position", children: [_jsx(SliderControl, { label: _jsxs(_Fragment, { children: ["X", _jsx("sub", { className: "ml-[1px] text-[11px] opacity-70", children: "pos" })] }), onChange: (v) => handleUpdate({ position: [v, node.position[1], node.position[2]] }), precision: 2, step: 0.1, unit: "m", value: Math.round(node.position[0] * 100) / 100 }), _jsx(SliderControl, { label: _jsxs(_Fragment, { children: ["Y", _jsx("sub", { className: "ml-[1px] text-[11px] opacity-70", children: "pos" })] }), onChange: (v) => handleUpdate({ position: [node.position[0], v, node.position[2]] }), precision: 2, step: 0.1, unit: "m", value: Math.round(node.position[1] * 100) / 100 }), showFlipSide && (_jsx("div", { className: "px-1 pt-2 pb-1", children: _jsx(ActionButton, { className: "w-full", icon: _jsx(FlipHorizontal2, { className: "h-4 w-4" }), label: "Flip Side", onClick: handleFlip }) }))] }), _jsxs(PanelSection, { title: "Dimensions", children: [_jsx(SliderControl, { label: "Width", min: 0, onChange: (v) => handleUpdate(getDimensionUpdates({ width: v })), precision: 2, restoreOnCommit: false, step: 0.1, unit: "m", value: Math.round(node.width * 100) / 100 }), _jsx(SliderControl, { label: "Height", min: 0, onChange: (v) => handleUpdate(getDimensionUpdates({ height: v })), precision: 2, restoreOnCommit: false, step: 0.1, unit: "m", value: Math.round(node.height * 100) / 100 })] }), showWindowShapeSection && (_jsxs(PanelSection, { title: "Top Shape", children: [_jsx(SegmentedControl, { onChange: (value) => handleUpdate({
308
+ ], value: node.hingesSide ?? 'left' }))] })), showOperationSection && (_jsx("div", { className: "mt-2", children: _jsx(SliderControl, { label: operationLabel, max: 1, min: 0, onChange: setOperationState, precision: 2, restoreOnCommit: false, step: 0.05, value: Math.round((node.operationState ?? 0) * 100) / 100 }) }))] })), _jsxs(PanelSection, { title: "Position", children: [_jsx(SliderControl, { label: _jsxs(_Fragment, { children: ["X", _jsx("sub", { className: "ml-[1px] text-[11px] opacity-70", children: "pos" })] }), onChange: (v) => handleUpdate({ position: [v, node.position[1], node.position[2]] }), precision: 2, step: 0.1, unit: "m", value: node.position[0] }), _jsx(SliderControl, { label: _jsxs(_Fragment, { children: ["Y", _jsx("sub", { className: "ml-[1px] text-[11px] opacity-70", children: "pos" })] }), onChange: (v) => handleUpdate({ position: [node.position[0], v, node.position[2]] }), precision: 2, step: 0.1, unit: "m", value: node.position[1] }), showFlipSide && (_jsx("div", { className: "px-1 pt-2 pb-1", children: _jsx(ActionButton, { className: "w-full", icon: _jsx(FlipHorizontal2, { className: "h-4 w-4" }), label: "Flip Side", onClick: handleFlip }) }))] }), _jsxs(PanelSection, { title: "Dimensions", children: [limits && (_jsx("p", { className: "text-[11px] text-muted-foreground", children: "Size is limited to the wall, including clearance for the opening frame." })), _jsx(SliderControl, { label: "Width", max: limits?.width, min: 0.01, onChange: (v) => preview?.preview(getDimensionUpdates({ width: v })), onCommit: (v) => preview?.commit(getDimensionUpdates({ width: v })), onCancel: () => preview?.cancel(), previewWhileTyping: true, precision: 2, restoreOnCommit: false, step: 0.01, unit: "m", value: node.width }), _jsx(SliderControl, { label: "Height", max: limits?.height, min: 0.01, onChange: (v) => preview?.preview(getDimensionUpdates({ height: v })), onCommit: (v) => preview?.commit(getDimensionUpdates({ height: v })), onCancel: () => preview?.cancel(), previewWhileTyping: true, precision: 2, restoreOnCommit: false, step: 0.01, unit: "m", value: node.height })] }), showWindowShapeSection && (_jsxs(PanelSection, { title: "Top Shape", children: [_jsx(SegmentedControl, { onChange: (value) => handleUpdate({
347
309
  openingShape: value,
348
310
  ...(value === 'rounded'
349
311
  ? {
@@ -362,28 +324,28 @@ export default function WindowPanel() {
362
324
  ], value: windowShape }), windowShape === 'rounded' && (_jsxs("div", { className: "mt-2 flex flex-col gap-1", children: [_jsx(SegmentedControl, { onChange: (value) => handleUpdate({ openingRadiusMode: value }), options: [
363
325
  { value: 'all', label: 'All' },
364
326
  { value: 'individual', label: 'Individual' },
365
- ], value: openingRadiusMode }), openingRadiusMode === 'all' ? (_jsx(SliderControl, { label: "Corner Radius", max: maxRoundedRadius, min: 0, onChange: (value) => previewWindowUpdate('cornerRadius', value), onCommit: (value) => commitWindowPreview('cornerRadius', value), precision: 2, step: 0.05, unit: "m", value: Math.round(cornerRadius * 100) / 100 })) : (_jsx(_Fragment, { children: [
327
+ ], value: openingRadiusMode }), openingRadiusMode === 'all' ? (_jsx(SliderControl, { label: "Corner Radius", max: maxRoundedRadius, min: 0, onChange: (value) => previewWindowUpdate('cornerRadius', value), onCommit: (value) => commitWindowPreview('cornerRadius', value), onCancel: () => preview?.cancel(), previewWhileTyping: true, restoreOnCommit: false, precision: 2, step: 0.05, unit: "m", value: cornerRadius })) : (_jsx(_Fragment, { children: [
366
328
  ['Top Left', 0],
367
329
  ['Top Right', 1],
368
330
  ['Bottom Right', 2],
369
331
  ['Bottom Left', 3],
370
- ].map(([label, index]) => (_jsx(SliderControl, { label: label, max: maxRoundedRadius, min: 0, onChange: (value) => setOpeningCornerRadius(index, value), onCommit: (value) => setOpeningCornerRadius(index, value, true), precision: 2, step: 0.05, unit: "m", value: Math.round((openingCornerRadii[index] ?? 0) * 100) / 100 }, label))) })), _jsx(SliderControl, { label: "Reveal Radius", max: 0.08, min: 0, onChange: (value) => previewWindowUpdate('openingRevealRadius', value), onCommit: (value) => commitWindowPreview('openingRevealRadius', value), precision: 3, step: 0.005, unit: "m", value: Math.round(openingRevealRadius * 1000) / 1000 })] })), windowShape === 'arch' && (_jsx("div", { className: "mt-2 flex flex-col gap-1", children: _jsx(SliderControl, { label: "Arch Height", max: Math.max(0.05, node.height), min: 0.05, onChange: (value) => handleUpdate({ archHeight: value }), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value: Math.round(archHeight * 100) / 100 }) }))] })), showOpeningShapeSection && (_jsxs(PanelSection, { title: "Opening Shape", children: [_jsx(SegmentedControl, { onChange: (value) => handleUpdate({ openingShape: value }), options: [
332
+ ].map(([label, index]) => (_jsx(SliderControl, { label: label, max: maxRoundedRadius, min: 0, onChange: (value) => setOpeningCornerRadius(index, value), onCommit: (value) => setOpeningCornerRadius(index, value, true), onCancel: () => preview?.cancel(), previewWhileTyping: true, restoreOnCommit: false, precision: 2, step: 0.05, unit: "m", value: openingCornerRadii[index] ?? 0 }, label))) })), _jsx(SliderControl, { label: "Reveal Radius", max: 0.08, min: 0, onChange: (value) => previewWindowUpdate('openingRevealRadius', value), onCommit: (value) => commitWindowPreview('openingRevealRadius', value), onCancel: () => preview?.cancel(), previewWhileTyping: true, restoreOnCommit: false, precision: 3, step: 0.005, unit: "m", value: openingRevealRadius })] })), windowShape === 'arch' && (_jsx("div", { className: "mt-2 flex flex-col gap-1", children: _jsx(SliderControl, { label: "Arch Height", max: Math.max(0.05, node.height), min: 0.05, onChange: (value) => previewWindowUpdate('archHeight', value), onCommit: (value) => commitWindowPreview('archHeight', value), onCancel: () => preview?.cancel(), previewWhileTyping: true, precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value: archHeight }) }))] })), showOpeningShapeSection && (_jsxs(PanelSection, { title: "Opening Shape", children: [_jsx(SegmentedControl, { onChange: (value) => handleUpdate({ openingShape: value }), options: [
371
333
  { value: 'rectangle', label: 'Rect' },
372
334
  { value: 'rounded', label: 'Rounded' },
373
335
  { value: 'arch', label: 'Arch' },
374
336
  ], value: openingShape }), openingShape === 'rounded' && (_jsxs("div", { className: "mt-2 flex flex-col gap-1", children: [_jsx(SegmentedControl, { onChange: (value) => handleUpdate({ openingRadiusMode: value }), options: [
375
337
  { value: 'all', label: 'All' },
376
338
  { value: 'individual', label: 'Individual' },
377
- ], value: openingRadiusMode }), openingRadiusMode === 'all' ? (_jsx(SliderControl, { label: "Corner Radius", max: maxRoundedRadius, min: 0, onChange: (value) => previewWindowUpdate('cornerRadius', value), onCommit: (value) => commitWindowPreview('cornerRadius', value), precision: 2, step: 0.05, unit: "m", value: Math.round(cornerRadius * 100) / 100 })) : (_jsx(_Fragment, { children: [
339
+ ], value: openingRadiusMode }), openingRadiusMode === 'all' ? (_jsx(SliderControl, { label: "Corner Radius", max: maxRoundedRadius, min: 0, onChange: (value) => previewWindowUpdate('cornerRadius', value), onCommit: (value) => commitWindowPreview('cornerRadius', value), onCancel: () => preview?.cancel(), previewWhileTyping: true, restoreOnCommit: false, precision: 2, step: 0.05, unit: "m", value: cornerRadius })) : (_jsx(_Fragment, { children: [
378
340
  ['Top Left', 0],
379
341
  ['Top Right', 1],
380
342
  ['Bottom Right', 2],
381
343
  ['Bottom Left', 3],
382
- ].map(([label, index]) => (_jsx(SliderControl, { label: label, max: maxRoundedRadius, min: 0, onChange: (value) => setOpeningCornerRadius(index, value), onCommit: (value) => setOpeningCornerRadius(index, value, true), precision: 2, step: 0.05, unit: "m", value: Math.round((openingCornerRadii[index] ?? 0) * 100) / 100 }, label))) })), _jsx(SliderControl, { label: "Reveal Radius", max: 0.08, min: 0, onChange: (value) => previewWindowUpdate('openingRevealRadius', value), onCommit: (value) => commitWindowPreview('openingRevealRadius', value), precision: 3, step: 0.005, unit: "m", value: Math.round(openingRevealRadius * 1000) / 1000 })] })), openingShape === 'arch' && (_jsx("div", { className: "mt-2 flex flex-col gap-1", children: _jsx(SliderControl, { label: "Arch Height", max: Math.max(0.05, node.height), min: 0.05, onChange: (value) => handleUpdate({ archHeight: value }), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value: Math.round(archHeight * 100) / 100 }) }))] })), !isOpening && (_jsxs(_Fragment, { children: [showFrameSection && (_jsxs(PanelSection, { title: "Frame", children: [_jsx(SliderControl, { label: "Thickness", min: 0, onChange: (v) => handleUpdate({ frameThickness: v }), precision: 3, step: 0.01, unit: "m", value: Math.round(node.frameThickness * 1000) / 1000 }), _jsx(SliderControl, { label: "Depth", min: 0, onChange: (v) => handleUpdate({ frameDepth: v }), precision: 3, step: 0.01, unit: "m", value: Math.round(node.frameDepth * 1000) / 1000 })] })), showGridSection && (_jsxs(PanelSection, { title: "Grid", children: [_jsx(SliderControl, { label: "Columns", max: 8, min: 1, onChange: (v) => {
344
+ ].map(([label, index]) => (_jsx(SliderControl, { label: label, max: maxRoundedRadius, min: 0, onChange: (value) => setOpeningCornerRadius(index, value), onCommit: (value) => setOpeningCornerRadius(index, value, true), onCancel: () => preview?.cancel(), previewWhileTyping: true, restoreOnCommit: false, precision: 2, step: 0.05, unit: "m", value: openingCornerRadii[index] ?? 0 }, label))) })), _jsx(SliderControl, { label: "Reveal Radius", max: 0.08, min: 0, onChange: (value) => previewWindowUpdate('openingRevealRadius', value), onCommit: (value) => commitWindowPreview('openingRevealRadius', value), onCancel: () => preview?.cancel(), previewWhileTyping: true, restoreOnCommit: false, precision: 3, step: 0.005, unit: "m", value: openingRevealRadius })] })), openingShape === 'arch' && (_jsx("div", { className: "mt-2 flex flex-col gap-1", children: _jsx(SliderControl, { label: "Arch Height", max: Math.max(0.05, node.height), min: 0.05, onChange: (value) => previewWindowUpdate('archHeight', value), onCommit: (value) => commitWindowPreview('archHeight', value), onCancel: () => preview?.cancel(), previewWhileTyping: true, precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value: archHeight }) }))] })), !isOpening && (_jsxs(_Fragment, { children: [showFrameSection && (_jsxs(PanelSection, { title: "Frame", children: [_jsx(SliderControl, { label: "Thickness", min: 0, onChange: (v) => previewWindowUpdate('frameThickness', v), onCommit: (v) => commitWindowPreview('frameThickness', v), onCancel: () => preview?.cancel(), previewWhileTyping: true, restoreOnCommit: false, precision: 3, step: 0.01, unit: "m", value: node.frameThickness }), _jsx(SliderControl, { label: "Depth", min: 0, onChange: (v) => previewWindowUpdate('frameDepth', v), onCommit: (v) => commitWindowPreview('frameDepth', v), onCancel: () => preview?.cancel(), previewWhileTyping: true, restoreOnCommit: false, precision: 3, step: 0.01, unit: "m", value: node.frameDepth })] })), showGridSection && (_jsxs(PanelSection, { title: "Grid", children: [_jsx(SliderControl, { label: "Columns", max: 8, min: 1, onChange: (v) => {
383
345
  const n = Math.max(1, Math.min(8, Math.round(v)));
384
346
  handleUpdate({ columnRatios: Array(n).fill(1 / n) });
385
347
  }, precision: 0, step: 1, value: numCols }), _jsx(SliderControl, { label: "Rows", max: 8, min: 1, onChange: (v) => {
386
348
  const n = Math.max(1, Math.min(8, Math.round(v)));
387
349
  handleUpdate({ rowRatios: Array(n).fill(1 / n) });
388
- }, precision: 0, step: 1, value: numRows }), numCols > 1 && (_jsxs("div", { className: "mt-2 flex flex-col gap-1", children: [_jsx("div", { className: "mb-1 px-1 font-medium text-[10px] text-muted-foreground/80 uppercase tracking-wider", children: "Col Widths" }), normCols.map((ratio, i) => (_jsx(SliderControl, { label: `C${i + 1}`, max: 95, min: 5, onChange: (v) => setColumnRatio(i, v / 100), precision: 1, step: 1, unit: "%", value: Math.round(ratio * 100 * 10) / 10 }, `c-${i}`))), _jsx("div", { className: "mt-1 border-border/50 border-t pt-1", children: _jsx(SliderControl, { label: "Divider", max: 0.1, min: 0.005, onChange: (v) => handleUpdate({ columnDividerThickness: v }), precision: 3, step: 0.01, unit: "m", value: Math.round((node.columnDividerThickness ?? 0.03) * 1000) / 1000 }) })] })), numRows > 1 && (_jsxs("div", { className: "mt-2 flex flex-col gap-1", children: [_jsx("div", { className: "mb-1 px-1 font-medium text-[10px] text-muted-foreground/80 uppercase tracking-wider", children: "Row Heights" }), normRows.map((ratio, i) => (_jsx(SliderControl, { label: `R${i + 1}`, max: 95, min: 5, onChange: (v) => setRowRatio(i, v / 100), precision: 1, step: 1, unit: "%", value: Math.round(ratio * 100 * 10) / 10 }, `r-${i}`))), _jsx("div", { className: "mt-1 border-border/50 border-t pt-1", children: _jsx(SliderControl, { label: "Divider", max: 0.1, min: 0.005, onChange: (v) => handleUpdate({ rowDividerThickness: v }), precision: 3, step: 0.01, unit: "m", value: Math.round((node.rowDividerThickness ?? 0.03) * 1000) / 1000 }) })] }))] })), showSillSection && (_jsxs(PanelSection, { title: "Sill", children: [_jsx(ToggleControl, { checked: node.sill, label: "Enable Sill", onChange: (checked) => handleUpdate({ sill: checked }) }), node.sill && (_jsxs("div", { className: "mt-1 flex flex-col gap-1", children: [_jsx(SliderControl, { label: "Depth", min: 0, onChange: (v) => handleUpdate({ sillDepth: v }), precision: 3, step: 0.01, unit: "m", value: Math.round(node.sillDepth * 1000) / 1000 }), _jsx(SliderControl, { label: "Thickness", min: 0, onChange: (v) => handleUpdate({ sillThickness: v }), precision: 3, step: 0.01, unit: "m", value: Math.round(node.sillThickness * 1000) / 1000 })] }))] }))] })), _jsx(PanelSection, { title: "Actions", children: _jsxs(ActionGroup, { children: [_jsx(ActionButton, { icon: _jsx(Move, { className: "h-3.5 w-3.5" }), label: "Move", onClick: handleMove }), _jsx(ActionButton, { icon: _jsx(Copy, { className: "h-3.5 w-3.5" }), label: "Duplicate", onClick: handleDuplicate }), _jsx(ActionButton, { className: "hover:bg-red-500/20", icon: _jsx(Trash2, { className: "h-3.5 w-3.5 text-red-400" }), label: "Delete", onClick: handleDelete })] }) })] }));
350
+ }, precision: 0, step: 1, value: numRows }), numCols > 1 && (_jsxs("div", { className: "mt-2 flex flex-col gap-1", children: [_jsx("div", { className: "mb-1 px-1 font-medium text-[10px] text-muted-foreground/80 uppercase tracking-wider", children: "Col Widths" }), normCols.map((ratio, i) => (_jsx(SliderControl, { label: `C${i + 1}`, max: 95, min: 5, onChange: (v) => setColumnRatio(i, v / 100), precision: 1, step: 1, unit: "%", value: Math.round(ratio * 100 * 10) / 10 }, `c-${i}`))), _jsx("div", { className: "mt-1 border-border/50 border-t pt-1", children: _jsx(SliderControl, { label: "Divider", max: 0.1, min: 0.005, onChange: (v) => handleUpdate({ columnDividerThickness: v }), precision: 3, step: 0.01, unit: "m", value: node.columnDividerThickness ?? 0.03 }) })] })), numRows > 1 && (_jsxs("div", { className: "mt-2 flex flex-col gap-1", children: [_jsx("div", { className: "mb-1 px-1 font-medium text-[10px] text-muted-foreground/80 uppercase tracking-wider", children: "Row Heights" }), normRows.map((ratio, i) => (_jsx(SliderControl, { label: `R${i + 1}`, max: 95, min: 5, onChange: (v) => setRowRatio(i, v / 100), precision: 1, step: 1, unit: "%", value: Math.round(ratio * 100 * 10) / 10 }, `r-${i}`))), _jsx("div", { className: "mt-1 border-border/50 border-t pt-1", children: _jsx(SliderControl, { label: "Divider", max: 0.1, min: 0.005, onChange: (v) => handleUpdate({ rowDividerThickness: v }), precision: 3, step: 0.01, unit: "m", value: node.rowDividerThickness ?? 0.03 }) })] }))] })), showSillSection && (_jsxs(PanelSection, { title: "Sill", children: [_jsx(ToggleControl, { checked: node.sill, label: "Enable Sill", onChange: (checked) => handleUpdate({ sill: checked }) }), node.sill && (_jsxs("div", { className: "mt-1 flex flex-col gap-1", children: [_jsx(SliderControl, { label: "Depth", min: 0, onChange: (v) => handleUpdate({ sillDepth: v }), precision: 3, step: 0.01, unit: "m", value: node.sillDepth }), _jsx(SliderControl, { label: "Thickness", min: 0, onChange: (v) => handleUpdate({ sillThickness: v }), precision: 3, step: 0.01, unit: "m", value: node.sillThickness })] }))] }))] })), _jsx(PanelSection, { title: "Actions", children: _jsxs(ActionGroup, { children: [_jsx(ActionButton, { icon: _jsx(Move, { className: "h-3.5 w-3.5" }), label: "Move", onClick: handleMove }), _jsx(ActionButton, { icon: _jsx(Copy, { className: "h-3.5 w-3.5" }), label: "Duplicate", onClick: handleDuplicate }), _jsx(ActionButton, { className: "hover:bg-red-500/20", icon: _jsx(Trash2, { className: "h-3.5 w-3.5 text-red-400" }), label: "Delete", onClick: handleDelete })] }) })] }));
389
351
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/window/tool.tsx"],"names":[],"mappings":"AA+FA;;;;;;;;;GASG;AACH,QAAA,MAAM,UAAU,EAAE,KAAK,CAAC,EA09BvB,CAAA;AAED,eAAe,UAAU,CAAA"}
1
+ {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/window/tool.tsx"],"names":[],"mappings":"AAoGA;;;;;;;;;GASG;AACH,QAAA,MAAM,UAAU,EAAE,KAAK,CAAC,EAmgCvB,CAAA;AAED,eAAe,UAAU,CAAA"}
@@ -5,8 +5,10 @@ import { useEffect, useMemo, useRef, useState } from 'react';
5
5
  import { BoxGeometry, EdgesGeometry, Vector3 } from 'three';
6
6
  import { LineBasicNodeMaterial } from 'three/webgpu';
7
7
  import { dormerEventFromHostedWindow, getDormerWindowWorldNormal, getDormerWindowWorldYaw, resolveDormerWindowTarget, } from '../shared/dormer-wall-opening-placement';
8
+ import { shouldFollowOpeningGrid, shouldHandleOpeningHostLeave, } from '../shared/opening-grid-follow';
8
9
  import { clearOpeningGuides3D, publishOpeningGuidesForWallEvent, resolveSillSnap, } from '../shared/opening-guides-runtime';
9
10
  import { getRoofWallOpeningCursorPose, resolveRoofWallOpeningTarget, worldToSelectedBuildingLocal, } from '../shared/roof-wall-opening-placement';
11
+ import { wallEventFromGrid } from '../shared/wall-event-from-grid';
10
12
  import { collectWallOpeningAlignmentCandidates, resolveWallSlideAlignment, } from '../shared/wall-opening-alignment';
11
13
  import { WindowFloorProjection } from './floor-projection';
12
14
  import WindowPreview from './preview';
@@ -519,7 +521,16 @@ const WindowTool = () => {
519
521
  commitWindowAtWall(event.node, clampedX, clampedY, side, itemRotation);
520
522
  event.stopPropagation();
521
523
  };
522
- const onWallLeave = () => {
524
+ // XR selectend produces a grid click without replaying the wall mesh's
525
+ // R3F pointer-up event. Use the latest valid wall hover for that release.
526
+ const onXRGridClick = (event) => {
527
+ if (event.nativeEvent?.pointerType !== 'xr' || !lastWallEvent)
528
+ return;
529
+ onWallClick(lastWallEvent);
530
+ };
531
+ const onWallLeave = (event) => {
532
+ if (!shouldHandleOpeningHostLeave(event.nativeEvent))
533
+ return;
523
534
  if (hostKind !== 'wall')
524
535
  return;
525
536
  lastWallEvent = null;
@@ -538,8 +549,18 @@ const WindowTool = () => {
538
549
  // timeStamp) — it owns the frame and has snapped the draft, so skip the
539
550
  // floor follow this tick.
540
551
  const ts = event.nativeEvent?.timeStamp ?? -1;
541
- if (ts === lastMeshEventTime)
552
+ if (!shouldFollowOpeningGrid({
553
+ eventTime: ts,
554
+ hasActiveHost: hostKind !== null,
555
+ lastHostEventTime: lastMeshEventTime,
556
+ pointerType: event.nativeEvent?.pointerType,
557
+ }))
558
+ return;
559
+ const wallEvent = wallEventFromGrid(event, activeLevelId, useScene.getState().nodes, sceneRegistry.nodes);
560
+ if (wallEvent) {
561
+ onWallHover(wallEvent);
542
562
  return;
563
+ }
543
564
  // Fresh floor-only frame: the cursor is off any wall/roof. Drop any draft
544
565
  // and free-follow the cursor with the invalid (unplaceable) ghost.
545
566
  hostKind = null;
@@ -548,6 +569,13 @@ const WindowTool = () => {
548
569
  destroyDraft();
549
570
  showGhostAt([x, y + FALLBACK_HEIGHT / 2 + FALLBACK_SILL_LIFT, z], y);
550
571
  };
572
+ const onGridPointerUp = (event) => {
573
+ if (isCameraDragging() || !draftRef.current)
574
+ return;
575
+ const wallEvent = wallEventFromGrid(event, activeLevelId, useScene.getState().nodes, sceneRegistry.nodes);
576
+ if (wallEvent)
577
+ onWallClick(wallEvent);
578
+ };
551
579
  // ── Dormer wall faces ──────────────────────────────────────────
552
580
  // Dormer windows use the same WindowNode mesh and inspector as regular
553
581
  // windows, but their host frame is supplied by DormerRenderer.
@@ -786,6 +814,7 @@ const WindowTool = () => {
786
814
  emitter.on('wall:enter', onWallHover);
787
815
  emitter.on('wall:move', onWallHover);
788
816
  emitter.on('wall:click', onWallClick);
817
+ emitter.on('grid:click', onXRGridClick);
789
818
  emitter.on('wall:leave', onWallLeave);
790
819
  emitter.on('roof:enter', onRoofHover);
791
820
  emitter.on('roof:move', onRoofHover);
@@ -800,6 +829,7 @@ const WindowTool = () => {
800
829
  emitter.on('window:click', onDormerWindowClick);
801
830
  emitter.on('window:leave', onDormerWindowLeave);
802
831
  emitter.on('grid:move', onGridFreeFollow);
832
+ emitter.on('grid:pointerup', onGridPointerUp);
803
833
  emitter.on('tool:cancel', onCancel);
804
834
  window.addEventListener('keydown', onKeyDown);
805
835
  // Placement tracks the cursor through wall events; keep walls hidden by
@@ -818,6 +848,7 @@ const WindowTool = () => {
818
848
  emitter.off('wall:enter', onWallHover);
819
849
  emitter.off('wall:move', onWallHover);
820
850
  emitter.off('wall:click', onWallClick);
851
+ emitter.off('grid:click', onXRGridClick);
821
852
  emitter.off('wall:leave', onWallLeave);
822
853
  emitter.off('roof:enter', onRoofHover);
823
854
  emitter.off('roof:move', onRoofHover);
@@ -832,6 +863,7 @@ const WindowTool = () => {
832
863
  emitter.off('window:click', onDormerWindowClick);
833
864
  emitter.off('window:leave', onDormerWindowLeave);
834
865
  emitter.off('grid:move', onGridFreeFollow);
866
+ emitter.off('grid:pointerup', onGridPointerUp);
835
867
  emitter.off('tool:cancel', onCancel);
836
868
  window.removeEventListener('keydown', onKeyDown);
837
869
  };
@@ -1,4 +1,4 @@
1
- import type { AnyNode, AnyNodeId, WallNode } from '@pascal-app/core';
1
+ import { type AnyNode, type AnyNodeId, type WallNode } from '@pascal-app/core';
2
2
  /**
3
3
  * Default sill height (metres from the floor to the BOTTOM of a window) for a
4
4
  * fresh window that has no wall-face height yet — the off-wall ghost and the
@@ -1 +1 @@
1
- {"version":3,"file":"window-math.d.ts","sourceRoot":"","sources":["../../src/window/window-math.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAGpE;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,MAAM,CAAA;AAExC;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,YAAY,SAAI,EAChB,aAAa,SAAI,GAChB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAU1B;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAC1C;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CASxC;AAED;;;;GAIG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAA"}
1
+ {"version":3,"file":"window-math.d.ts","sourceRoot":"","sources":["../../src/window/window-math.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EAId,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AAGzB;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,MAAM,CAAA;AAExC;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,YAAY,SAAI,EAChB,aAAa,SAAI,GAChB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAI1B;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAC1C;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAQxC;AAED;;;;GAIG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAA"}
@@ -1,3 +1,4 @@
1
+ import { getCurtainWallConfig, getWallCurveFrameAt, getWallCurveLength, } from '@pascal-app/core';
1
2
  import { resolveWallOpeningCeiling } from '../shared/wall-opening-ceiling';
2
3
  /**
3
4
  * Default sill height (metres from the floor to the BOTTOM of a window) for a
@@ -15,12 +16,9 @@ export const DEFAULT_WINDOW_SILL_M = 0.5;
15
16
  * height — matching how WallSystem positions the wall mesh at slabElevation.
16
17
  */
17
18
  export function wallLocalToWorld(wallNode, localX, localY, levelYOffset = 0, slabElevation = 0) {
18
- const wallAngle = Math.atan2(wallNode.end[1] - wallNode.start[1], wallNode.end[0] - wallNode.start[0]);
19
- return [
20
- wallNode.start[0] + localX * Math.cos(wallAngle),
21
- slabElevation + localY + levelYOffset,
22
- wallNode.start[1] + localX * Math.sin(wallAngle),
23
- ];
19
+ const wallLength = getWallCurveLength(wallNode);
20
+ const frame = getWallCurveFrameAt(wallNode, wallLength > 1e-6 ? localX / wallLength : 0);
21
+ return [frame.point.x, slabElevation + localY + levelYOffset, frame.point.y];
24
22
  }
25
23
  /**
26
24
  * Clamps window center position so it stays fully within wall bounds. The Y
@@ -30,12 +28,11 @@ export function wallLocalToWorld(wallNode, localX, localY, levelYOffset = 0, sla
30
28
  * wall record.
31
29
  */
32
30
  export function clampToWall(wallNode, localX, localY, width, height, nodes) {
33
- const dx = wallNode.end[0] - wallNode.start[0];
34
- const dz = wallNode.end[1] - wallNode.start[1];
35
- const wallLength = Math.sqrt(dx * dx + dz * dz);
31
+ const wallLength = getWallCurveLength(wallNode);
36
32
  const wallHeight = resolveWallOpeningCeiling(wallNode, nodes);
37
- const clampedX = Math.max(width / 2, Math.min(wallLength - width / 2, localX));
38
- const clampedY = Math.max(height / 2, Math.min(wallHeight - height / 2, localY));
33
+ const margin = wallNode.wallType === 'curtain' ? getCurtainWallConfig(wallNode).perimeterWidth : 0;
34
+ const clampedX = Math.max(margin + width / 2, Math.min(wallLength - margin - width / 2, localX));
35
+ const clampedY = Math.max(margin + height / 2, Math.min(wallHeight - margin - height / 2, localY));
39
36
  return { clampedX, clampedY };
40
37
  }
41
38
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/zone/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,kBAAkB,CAAA;AAezB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAEnC;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,cAAc,CAAC,OAAO,QAAQ,CA8E1D,CAAA"}
1
+ {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/zone/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,kBAAkB,CAAA;AAezB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAGnC;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,cAAc,CAAC,OAAO,QAAQ,CAoF1D,CAAA"}
@@ -8,6 +8,7 @@ import { zoneParametrics } from './parametrics';
8
8
  import { zoneQuickMeasurement } from './quick-measurement';
9
9
  import { buildRoomFloorplanSchedule } from './room-documentation';
10
10
  import { ZoneNode } from './schema';
11
+ import { buildingUnitsForZone } from './unit-membership';
11
12
  /**
12
13
  * Zone — Stage A. Custom-behavior escape hatch: zone uses TSL shader
13
14
  * materials + `<Html>` portals + per-frame uniform poking, so it
@@ -51,6 +52,7 @@ export const zoneDefinition = {
51
52
  },
52
53
  // No dirty consumer rebuilds this kind — see NodeDefinition.dirtyTracking.
53
54
  dirtyTracking: false,
55
+ rendersChildren: false,
54
56
  renderer: {
55
57
  kind: 'parametric',
56
58
  module: () => import('./renderer'),
@@ -60,7 +62,12 @@ export const zoneDefinition = {
60
62
  priority: 4,
61
63
  },
62
64
  floorplan: buildZoneFloorplan,
63
- floorplanDependencies: (node) => (node.autoFromWalls ? node.boundaryWallIds : []),
65
+ // Every unit under the building, not just the current owner: a zone that
66
+ // joins its first unit has no owner to depend on until the unit changes.
67
+ floorplanDependencies: (node, nodes) => [
68
+ ...(node.autoFromWalls ? node.boundaryWallIds : []),
69
+ ...buildingUnitsForZone(node, (id) => nodes[id]).map((unit) => unit.id),
70
+ ],
64
71
  // 2D body move — centroid-pivot polygon mover (same as slab / ceiling).
65
72
  // Without this, zone fell through to the overlay's generic free-translate
66
73
  // path, which committed a `position` field zone has no schema for, so the
@@ -1,8 +1,6 @@
1
1
  import { type FloorplanGeometry, type GeometryContext, type ZoneNode } from '@pascal-app/core';
2
2
  /**
3
- * Stage C floor-plan builder for zone. Zones are colored polygons —
4
- * fill + outline both come from `zone.color`. Selection adds an
5
- * accent-colored outline.
3
+ * Stage C floor-plan builder for zone. Zones are colored polygons.
6
4
  *
7
5
  * The zone's `name` renders as a centered text label at the polygon's
8
6
  * geometric centroid. The registry layer sorts zones before every
@@ -1 +1 @@
1
- {"version":3,"file":"floorplan.d.ts","sourceRoot":"","sources":["../../src/zone/floorplan.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EAEpB,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AAQzB;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,eAAe,GAAG,iBAAiB,GAAG,IAAI,CAqHjG"}
1
+ {"version":3,"file":"floorplan.d.ts","sourceRoot":"","sources":["../../src/zone/floorplan.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EAEpB,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AASzB;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,eAAe,GAAG,iBAAiB,GAAG,IAAI,CAmIjG"}