@pascal-app/nodes 1.0.1 → 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 (319) 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/building/renderer.d.ts.map +1 -1
  25. package/dist/building/renderer.js +1 -1
  26. package/dist/cabinet/definition.d.ts.map +1 -1
  27. package/dist/cabinet/definition.js +3 -1
  28. package/dist/cabinet/hosted-resize.d.ts +3 -6
  29. package/dist/cabinet/hosted-resize.d.ts.map +1 -1
  30. package/dist/cabinet/hosted-resize.js +9 -153
  31. package/dist/cabinet/placement-snap.d.ts.map +1 -1
  32. package/dist/cabinet/placement-snap.js +4 -2
  33. package/dist/cabinet/surfaces.d.ts +1 -0
  34. package/dist/cabinet/surfaces.d.ts.map +1 -1
  35. package/dist/cabinet/surfaces.js +13 -1
  36. package/dist/cabinet/tool.d.ts.map +1 -1
  37. package/dist/cabinet/tool.js +8 -4
  38. package/dist/ceiling/definition.d.ts.map +1 -1
  39. package/dist/ceiling/definition.js +5 -0
  40. package/dist/ceiling/renderer.d.ts.map +1 -1
  41. package/dist/ceiling/renderer.js +3 -2
  42. package/dist/chimney/definition.d.ts.map +1 -1
  43. package/dist/chimney/definition.js +1 -0
  44. package/dist/column/definition.d.ts.map +1 -1
  45. package/dist/column/definition.js +10 -3
  46. package/dist/column/floorplan-affordances.d.ts.map +1 -1
  47. package/dist/column/floorplan-affordances.js +6 -1
  48. package/dist/column/floorplan.d.ts.map +1 -1
  49. package/dist/column/floorplan.js +23 -11
  50. package/dist/column/hosted-resize.d.ts +116 -0
  51. package/dist/column/hosted-resize.d.ts.map +1 -0
  52. package/dist/column/hosted-resize.js +9 -0
  53. package/dist/column/panel.d.ts.map +1 -1
  54. package/dist/column/panel.js +6 -3
  55. package/dist/column/renderer.d.ts.map +1 -1
  56. package/dist/column/renderer.js +25 -103
  57. package/dist/column/shape.d.ts +22 -0
  58. package/dist/column/shape.d.ts.map +1 -0
  59. package/dist/column/shape.js +120 -0
  60. package/dist/column/surface.d.ts +4 -0
  61. package/dist/column/surface.d.ts.map +1 -0
  62. package/dist/column/surface.js +66 -0
  63. package/dist/cupola/definition.d.ts.map +1 -1
  64. package/dist/cupola/definition.js +1 -0
  65. package/dist/door/definition.d.ts.map +1 -1
  66. package/dist/door/definition.js +40 -1
  67. package/dist/door/door-math.d.ts +1 -1
  68. package/dist/door/door-math.d.ts.map +1 -1
  69. package/dist/door/door-math.js +7 -10
  70. package/dist/door/floorplan-affordances.d.ts.map +1 -1
  71. package/dist/door/floorplan-affordances.js +8 -12
  72. package/dist/door/move-tool.d.ts.map +1 -1
  73. package/dist/door/move-tool.js +3 -4
  74. package/dist/door/panel.d.ts.map +1 -1
  75. package/dist/door/panel.js +23 -52
  76. package/dist/door/tool.d.ts.map +1 -1
  77. package/dist/door/tool.js +20 -2
  78. package/dist/dormer/definition.d.ts.map +1 -1
  79. package/dist/dormer/definition.js +1 -0
  80. package/dist/downspout/definition.d.ts.map +1 -1
  81. package/dist/downspout/definition.js +1 -0
  82. package/dist/duct-segment/tool.d.ts.map +1 -1
  83. package/dist/duct-segment/tool.js +11 -0
  84. package/dist/duct-terminal/tool.d.ts.map +1 -1
  85. package/dist/duct-terminal/tool.js +77 -8
  86. package/dist/elevator/definition.d.ts.map +1 -1
  87. package/dist/elevator/definition.js +2 -0
  88. package/dist/elevator/parametrics.d.ts.map +1 -1
  89. package/dist/elevator/parametrics.js +98 -1
  90. package/dist/eyebrow-vent/definition.d.ts.map +1 -1
  91. package/dist/eyebrow-vent/definition.js +1 -0
  92. package/dist/fence/definition.d.ts.map +1 -1
  93. package/dist/fence/definition.js +1 -0
  94. package/dist/fence/tool.js +2 -2
  95. package/dist/guide/definition.d.ts.map +1 -1
  96. package/dist/guide/definition.js +1 -0
  97. package/dist/gutter/definition.d.ts.map +1 -1
  98. package/dist/gutter/definition.js +1 -0
  99. package/dist/hvac-equipment/tool.d.ts.map +1 -1
  100. package/dist/hvac-equipment/tool.js +8 -2
  101. package/dist/item/__fixtures__/main-floorplan.d.ts +26 -0
  102. package/dist/item/__fixtures__/main-floorplan.d.ts.map +1 -0
  103. package/dist/item/__fixtures__/main-floorplan.js +297 -0
  104. package/dist/item/definition.d.ts.map +1 -1
  105. package/dist/item/definition.js +3 -1
  106. package/dist/item/floorplan-move.d.ts.map +1 -1
  107. package/dist/item/floorplan-move.js +209 -130
  108. package/dist/item/floorplan.d.ts +22 -0
  109. package/dist/item/floorplan.d.ts.map +1 -1
  110. package/dist/item/floorplan.js +61 -2
  111. package/dist/item/move-tool.d.ts +1 -1
  112. package/dist/item/move-tool.d.ts.map +1 -1
  113. package/dist/item/move-tool.js +15 -7
  114. package/dist/item/renderer.d.ts.map +1 -1
  115. package/dist/item/renderer.js +8 -2
  116. package/dist/level/renderer.js +1 -1
  117. package/dist/lineset/tool.d.ts.map +1 -1
  118. package/dist/lineset/tool.js +1 -0
  119. package/dist/liquid-line/tool.d.ts.map +1 -1
  120. package/dist/liquid-line/tool.js +5 -1
  121. package/dist/measurement/definition.d.ts.map +1 -1
  122. package/dist/measurement/definition.js +1 -0
  123. package/dist/pipe-segment/tool.d.ts.map +1 -1
  124. package/dist/pipe-segment/tool.js +14 -3
  125. package/dist/pipe-trap/tool.d.ts.map +1 -1
  126. package/dist/pipe-trap/tool.js +7 -8
  127. package/dist/procedural-item/definition.d.ts.map +1 -1
  128. package/dist/procedural-item/definition.js +4 -1
  129. package/dist/ridge-vent/definition.d.ts.map +1 -1
  130. package/dist/ridge-vent/definition.js +1 -0
  131. package/dist/roof/definition.d.ts.map +1 -1
  132. package/dist/roof/definition.js +4 -1
  133. package/dist/roof/panel-model.d.ts +4 -0
  134. package/dist/roof/panel-model.d.ts.map +1 -0
  135. package/dist/roof/panel-model.js +151 -0
  136. package/dist/roof/panel.d.ts.map +1 -1
  137. package/dist/roof/panel.js +3 -4
  138. package/dist/roof/parametrics.d.ts.map +1 -1
  139. package/dist/roof/parametrics.js +16 -1
  140. package/dist/roof-segment/definition.d.ts.map +1 -1
  141. package/dist/roof-segment/definition.js +1 -0
  142. package/dist/roof-segment/parametrics.d.ts.map +1 -1
  143. package/dist/roof-segment/parametrics.js +176 -1
  144. package/dist/scan/definition.d.ts.map +1 -1
  145. package/dist/scan/definition.js +1 -0
  146. package/dist/shared/commit-opening-move.d.ts +4 -0
  147. package/dist/shared/commit-opening-move.d.ts.map +1 -0
  148. package/dist/shared/commit-opening-move.js +9 -0
  149. package/dist/shared/curtain-opening-limits.d.ts +14 -0
  150. package/dist/shared/curtain-opening-limits.d.ts.map +1 -0
  151. package/dist/shared/curtain-opening-limits.js +53 -0
  152. package/dist/shared/draft-axis-guides.d.ts +0 -6
  153. package/dist/shared/draft-axis-guides.d.ts.map +1 -1
  154. package/dist/shared/draft-axis-guides.js +1 -8
  155. package/dist/shared/hosted-resize.d.ts +12 -0
  156. package/dist/shared/hosted-resize.d.ts.map +1 -0
  157. package/dist/shared/hosted-resize.js +166 -0
  158. package/dist/shared/opening-grid-follow.d.ts +8 -0
  159. package/dist/shared/opening-grid-follow.d.ts.map +1 -0
  160. package/dist/shared/opening-grid-follow.js +12 -0
  161. package/dist/shared/opening-property-preview-host.d.ts +3 -0
  162. package/dist/shared/opening-property-preview-host.d.ts.map +1 -0
  163. package/dist/shared/opening-property-preview-host.js +21 -0
  164. package/dist/shared/opening-property-preview.d.ts +19 -0
  165. package/dist/shared/opening-property-preview.d.ts.map +1 -0
  166. package/dist/shared/opening-property-preview.js +99 -0
  167. package/dist/shared/resting-surface-plan.d.ts +2 -1
  168. package/dist/shared/resting-surface-plan.d.ts.map +1 -1
  169. package/dist/shared/resting-surface-plan.js +29 -4
  170. package/dist/shared/surface-counterparts.d.ts +12 -0
  171. package/dist/shared/surface-counterparts.d.ts.map +1 -0
  172. package/dist/shared/surface-counterparts.js +27 -0
  173. package/dist/shared/wall-attach-target.d.ts +1 -0
  174. package/dist/shared/wall-attach-target.d.ts.map +1 -1
  175. package/dist/shared/wall-attach-target.js +4 -1
  176. package/dist/shared/wall-event-from-grid.d.ts +5 -0
  177. package/dist/shared/wall-event-from-grid.d.ts.map +1 -0
  178. package/dist/shared/wall-event-from-grid.js +36 -0
  179. package/dist/shelf/definition.d.ts.map +1 -1
  180. package/dist/shelf/definition.js +3 -1
  181. package/dist/shelf/floorplan.d.ts.map +1 -1
  182. package/dist/shelf/floorplan.js +9 -2
  183. package/dist/site/renderer.d.ts.map +1 -1
  184. package/dist/site/renderer.js +9 -2
  185. package/dist/skylight/definition.d.ts.map +1 -1
  186. package/dist/skylight/definition.js +1 -0
  187. package/dist/slab/definition.d.ts.map +1 -1
  188. package/dist/slab/definition.js +4 -0
  189. package/dist/solar-panel/definition.d.ts.map +1 -1
  190. package/dist/solar-panel/definition.js +1 -0
  191. package/dist/spawn/definition.d.ts.map +1 -1
  192. package/dist/spawn/definition.js +1 -0
  193. package/dist/stair/definition.d.ts.map +1 -1
  194. package/dist/stair/definition.js +2 -0
  195. package/dist/stair/parametrics.d.ts.map +1 -1
  196. package/dist/stair/parametrics.js +164 -1
  197. package/dist/stair-segment/definition.d.ts.map +1 -1
  198. package/dist/stair-segment/definition.js +1 -0
  199. package/dist/stair-segment/parametrics.d.ts.map +1 -1
  200. package/dist/stair-segment/parametrics.js +81 -1
  201. package/dist/turbine-vent/definition.d.ts.map +1 -1
  202. package/dist/turbine-vent/definition.js +1 -0
  203. package/dist/wall/actions.d.ts +3 -0
  204. package/dist/wall/actions.d.ts.map +1 -0
  205. package/dist/wall/actions.js +56 -0
  206. package/dist/wall/curtain-opening-frame.d.ts +14 -0
  207. package/dist/wall/curtain-opening-frame.d.ts.map +1 -0
  208. package/dist/wall/curtain-opening-frame.js +111 -0
  209. package/dist/wall/curtain-panel-highlight.d.ts +3 -0
  210. package/dist/wall/curtain-panel-highlight.d.ts.map +1 -0
  211. package/dist/wall/curtain-panel-highlight.js +113 -0
  212. package/dist/wall/curtain-wall-adapter.d.ts +3 -0
  213. package/dist/wall/curtain-wall-adapter.d.ts.map +1 -0
  214. package/dist/wall/curtain-wall-adapter.js +55 -0
  215. package/dist/wall/curtain-wall-floorplan.d.ts +3 -0
  216. package/dist/wall/curtain-wall-floorplan.d.ts.map +1 -0
  217. package/dist/wall/curtain-wall-floorplan.js +91 -0
  218. package/dist/wall/curtain-wall-geometry.d.ts +4 -0
  219. package/dist/wall/curtain-wall-geometry.d.ts.map +1 -0
  220. package/dist/wall/curtain-wall-geometry.js +226 -0
  221. package/dist/wall/curtain-wall-layout.d.ts +16 -0
  222. package/dist/wall/curtain-wall-layout.d.ts.map +1 -0
  223. package/dist/wall/curtain-wall-layout.js +176 -0
  224. package/dist/wall/curtain-wall-materials.d.ts +5 -0
  225. package/dist/wall/curtain-wall-materials.d.ts.map +1 -0
  226. package/dist/wall/curtain-wall-materials.js +55 -0
  227. package/dist/wall/curtain-wall-panel.d.ts +14 -0
  228. package/dist/wall/curtain-wall-panel.d.ts.map +1 -0
  229. package/dist/wall/curtain-wall-panel.js +167 -0
  230. package/dist/wall/curtain-wall-piece-geometry.d.ts +5 -0
  231. package/dist/wall/curtain-wall-piece-geometry.d.ts.map +1 -0
  232. package/dist/wall/curtain-wall-piece-geometry.js +220 -0
  233. package/dist/wall/curtain-wall-shadow.d.ts +4 -0
  234. package/dist/wall/curtain-wall-shadow.d.ts.map +1 -0
  235. package/dist/wall/curtain-wall-shadow.js +15 -0
  236. package/dist/wall/definition.d.ts +0 -15
  237. package/dist/wall/definition.d.ts.map +1 -1
  238. package/dist/wall/definition.js +65 -4
  239. package/dist/wall/drawing-mode.d.ts +12 -0
  240. package/dist/wall/drawing-mode.d.ts.map +1 -0
  241. package/dist/wall/drawing-mode.js +38 -0
  242. package/dist/wall/floorplan-tool.d.ts +4 -0
  243. package/dist/wall/floorplan-tool.d.ts.map +1 -0
  244. package/dist/wall/floorplan-tool.js +107 -0
  245. package/dist/wall/floorplan.d.ts.map +1 -1
  246. package/dist/wall/floorplan.js +6 -2
  247. package/dist/wall/length-patch.d.ts +3 -0
  248. package/dist/wall/length-patch.d.ts.map +1 -0
  249. package/dist/wall/length-patch.js +14 -0
  250. package/dist/wall/move-tool.d.ts.map +1 -1
  251. package/dist/wall/move-tool.js +23 -1
  252. package/dist/wall/paint.d.ts.map +1 -1
  253. package/dist/wall/paint.js +39 -4
  254. package/dist/wall/panel-model.d.ts +4 -0
  255. package/dist/wall/panel-model.d.ts.map +1 -0
  256. package/dist/wall/panel-model.js +117 -0
  257. package/dist/wall/panel.d.ts.map +1 -1
  258. package/dist/wall/panel.js +20 -24
  259. package/dist/wall/property-preview.d.ts +7 -0
  260. package/dist/wall/property-preview.d.ts.map +1 -0
  261. package/dist/wall/property-preview.js +31 -0
  262. package/dist/wall/rectangle-command.d.ts +149 -0
  263. package/dist/wall/rectangle-command.d.ts.map +1 -0
  264. package/dist/wall/rectangle-command.js +22 -0
  265. package/dist/wall/rectangle-tool.d.ts +2 -0
  266. package/dist/wall/rectangle-tool.d.ts.map +1 -0
  267. package/dist/wall/rectangle-tool.js +147 -0
  268. package/dist/wall/renderer.d.ts.map +1 -1
  269. package/dist/wall/renderer.js +7 -4
  270. package/dist/wall/slots.d.ts +2 -2
  271. package/dist/wall/slots.d.ts.map +1 -1
  272. package/dist/wall/slots.js +10 -2
  273. package/dist/wall/split-floorplan-layer.d.ts +4 -0
  274. package/dist/wall/split-floorplan-layer.d.ts.map +1 -0
  275. package/dist/wall/split-floorplan-layer.js +71 -0
  276. package/dist/wall/split-pointer.d.ts +3 -0
  277. package/dist/wall/split-pointer.d.ts.map +1 -0
  278. package/dist/wall/split-pointer.js +95 -0
  279. package/dist/wall/split-preview.d.ts +55 -0
  280. package/dist/wall/split-preview.d.ts.map +1 -0
  281. package/dist/wall/split-preview.js +106 -0
  282. package/dist/wall/split-session.d.ts +16 -0
  283. package/dist/wall/split-session.d.ts.map +1 -0
  284. package/dist/wall/split-session.js +138 -0
  285. package/dist/wall/split-store.d.ts +16 -0
  286. package/dist/wall/split-store.d.ts.map +1 -0
  287. package/dist/wall/split-store.js +6 -0
  288. package/dist/wall/split-tool.d.ts +10 -0
  289. package/dist/wall/split-tool.d.ts.map +1 -0
  290. package/dist/wall/split-tool.js +95 -0
  291. package/dist/wall/system.d.ts.map +1 -1
  292. package/dist/wall/system.js +3 -1
  293. package/dist/wall/tool.d.ts.map +1 -1
  294. package/dist/wall/tool.js +14 -6
  295. package/dist/wall/wall-batch-system.d.ts.map +1 -1
  296. package/dist/wall/wall-batch-system.js +3 -0
  297. package/dist/window/definition.d.ts.map +1 -1
  298. package/dist/window/definition.js +48 -1
  299. package/dist/window/floorplan-affordances.d.ts.map +1 -1
  300. package/dist/window/floorplan-affordances.js +8 -10
  301. package/dist/window/move-tool.d.ts.map +1 -1
  302. package/dist/window/move-tool.js +4 -6
  303. package/dist/window/panel.d.ts.map +1 -1
  304. package/dist/window/panel.js +21 -59
  305. package/dist/window/tool.d.ts.map +1 -1
  306. package/dist/window/tool.js +34 -2
  307. package/dist/window/window-math.d.ts +1 -1
  308. package/dist/window/window-math.d.ts.map +1 -1
  309. package/dist/window/window-math.js +8 -11
  310. package/dist/zone/definition.d.ts.map +1 -1
  311. package/dist/zone/definition.js +1 -0
  312. package/dist/zone/quantities-panel.d.ts.map +1 -1
  313. package/dist/zone/quantities-panel.js +3 -19
  314. package/dist/zone/renderer.d.ts.map +1 -1
  315. package/dist/zone/renderer.js +1 -1
  316. package/package.json +8 -8
  317. package/dist/shared/opening-documentation-fields.d.ts +0 -16
  318. package/dist/shared/opening-documentation-fields.d.ts.map +0 -1
  319. package/dist/shared/opening-documentation-fields.js +0 -54
@@ -0,0 +1,138 @@
1
+ import { getWallCurveLength, planWallDivisions, runAsSingleSceneHistoryStep, useScene, } from '@pascal-app/core';
2
+ import { isGridSnapActive, isMagneticSnapActive, triggerSFX, useEditor, useInteractionScope, } from '@pascal-app/editor';
3
+ import { snapWallSplitDistance, WALL_SPLIT_MAX_CUTS, wallSplitAnchors, wallSplitDistances, wallSplitPreview, } from './split-preview';
4
+ import { useWallSplit } from './split-store';
5
+ // Pointer distance (m) within which a 'lines' alignment target catches the cut.
6
+ const ALIGNMENT_TOLERANCE = 0.2;
7
+ const ownsSplit = () => {
8
+ const { scope } = useInteractionScope.getState();
9
+ return scope.kind === 'reshaping' && scope.reshape === 'split';
10
+ };
11
+ const currentWall = (draft) => {
12
+ const wall = useScene.getState().nodes[draft.wallId];
13
+ return wall?.type === 'wall' ? wall : null;
14
+ };
15
+ const withPreview = (draft, wall) => ({
16
+ ...draft,
17
+ preview: wallSplitPreview(useScene.getState().nodes, wall, wallSplitDistances(getWallCurveLength(wall), draft.cuts, draft.distance)),
18
+ });
19
+ let teardown = null;
20
+ /**
21
+ * A split session: the wall's `reshaping` scope (so the snapping chip and the
22
+ * kind's HUD hints resolve), one centred cut, and the watchers that end it —
23
+ * Esc, the wall or its edit rights going away, another interaction, leaving
24
+ * select mode. Both views render the same draft; either can commit.
25
+ */
26
+ export function openWallSplit(wall) {
27
+ if (useScene.getState().readOnly)
28
+ return;
29
+ closeWallSplit();
30
+ useEditor.getState().setMode('select');
31
+ useInteractionScope
32
+ .getState()
33
+ .begin({ kind: 'reshaping', nodeId: wall.id, reshape: 'split', driver: 'tool' });
34
+ useWallSplit
35
+ .getState()
36
+ .setDraft(withPreview({ wallId: wall.id, cuts: 1, distance: getWallCurveLength(wall) / 2, snap: null }, wall));
37
+ const onKey = (event) => {
38
+ if (event.key !== 'Escape')
39
+ return;
40
+ event.preventDefault();
41
+ event.stopImmediatePropagation();
42
+ closeWallSplit();
43
+ };
44
+ const stopScene = useScene.subscribe((state, previous) => {
45
+ if (state.readOnly || state.nodes[wall.id]?.type !== 'wall')
46
+ closeWallSplit();
47
+ else if (state.nodes !== previous.nodes)
48
+ refreshWallSplit();
49
+ });
50
+ const stopScope = useInteractionScope.subscribe(({ scope }) => {
51
+ if (!(scope.kind === 'reshaping' && scope.reshape === 'split'))
52
+ closeWallSplit();
53
+ });
54
+ const stopEditor = useEditor.subscribe((state) => {
55
+ if (state.mode !== 'select' ||
56
+ state.isCaptureMode ||
57
+ state.isPreviewMode ||
58
+ state.isFirstPersonMode)
59
+ closeWallSplit();
60
+ });
61
+ const hasWindow = typeof window !== 'undefined';
62
+ if (hasWindow)
63
+ window.addEventListener('keydown', onKey, true);
64
+ teardown = () => {
65
+ if (hasWindow)
66
+ window.removeEventListener('keydown', onKey, true);
67
+ stopScene();
68
+ stopScope();
69
+ stopEditor();
70
+ };
71
+ }
72
+ export function closeWallSplit() {
73
+ const stop = teardown;
74
+ teardown = null;
75
+ stop?.();
76
+ useWallSplit.getState().setDraft(null);
77
+ useInteractionScope
78
+ .getState()
79
+ .endIf((scope) => scope.kind === 'reshaping' && scope.reshape === 'split');
80
+ }
81
+ /** Pointer projected onto the wall; Alt (`free`) skips snapping. */
82
+ export function hoverWallSplit(raw, free = false) {
83
+ const draft = useWallSplit.getState().draft;
84
+ // Several cuts stay evenly spaced, so the pointer has nothing to move.
85
+ if (!draft || draft.cuts > 1 || !ownsSplit())
86
+ return;
87
+ const wall = currentWall(draft);
88
+ if (!wall)
89
+ return closeWallSplit();
90
+ const { distance, snap } = free
91
+ ? { distance: raw, snap: null }
92
+ : snapWallSplitDistance(wall, raw, {
93
+ gridStep: isGridSnapActive() ? useEditor.getState().gridSnapStep : null,
94
+ anchors: isMagneticSnapActive() ? wallSplitAnchors(useScene.getState().nodes, wall) : null,
95
+ tolerance: ALIGNMENT_TOLERANCE,
96
+ });
97
+ if (distance === draft.distance && snap?.kind === draft.snap?.kind)
98
+ return;
99
+ useWallSplit.getState().setDraft(withPreview({ ...draft, distance, snap }, wall));
100
+ }
101
+ export function setWallSplitCuts(cuts) {
102
+ const draft = useWallSplit.getState().draft;
103
+ if (!draft || !ownsSplit())
104
+ return;
105
+ const next = Math.min(WALL_SPLIT_MAX_CUTS, Math.max(1, Math.round(cuts)));
106
+ if (next === draft.cuts)
107
+ return;
108
+ const wall = currentWall(draft);
109
+ if (!wall)
110
+ return closeWallSplit();
111
+ useWallSplit.getState().setDraft(withPreview({ ...draft, cuts: next }, wall));
112
+ }
113
+ /** Re-plan against the current scene (after an external edit). */
114
+ export function refreshWallSplit() {
115
+ const draft = useWallSplit.getState().draft;
116
+ if (!draft)
117
+ return;
118
+ const wall = currentWall(draft);
119
+ if (!wall)
120
+ return closeWallSplit();
121
+ useWallSplit.getState().setDraft(withPreview(draft, wall));
122
+ }
123
+ export function commitWallSplit() {
124
+ const draft = useWallSplit.getState().draft;
125
+ const current = useScene.getState();
126
+ if (!draft || current.readOnly || !ownsSplit())
127
+ return;
128
+ const wall = currentWall(draft);
129
+ if (!wall)
130
+ return closeWallSplit();
131
+ const checked = withPreview(draft, wall);
132
+ if (!checked.preview.valid)
133
+ return useWallSplit.getState().setDraft(checked);
134
+ const plan = planWallDivisions(current.nodes, wall.id, checked.preview.distances);
135
+ runAsSingleSceneHistoryStep(useScene, () => current.applyNodeChanges(plan.changes));
136
+ closeWallSplit();
137
+ triggerSFX('sfx:structure-build');
138
+ }
@@ -0,0 +1,16 @@
1
+ import type { WallNode } from '@pascal-app/core';
2
+ import type { WallSplitPreview, WallSplitSnap } from './split-preview';
3
+ export type WallSplitDraft = {
4
+ wallId: WallNode['id'];
5
+ cuts: number;
6
+ /** The single cut's snapped distance from the wall start; more cuts are evenly spaced. */
7
+ distance: number;
8
+ snap: WallSplitSnap;
9
+ preview: WallSplitPreview;
10
+ };
11
+ /** The open split's cut preview. Every transition lives in `split-session.ts`. */
12
+ export declare const useWallSplit: import("zustand").UseBoundStore<import("zustand").StoreApi<{
13
+ draft: WallSplitDraft | null;
14
+ setDraft: (draft: WallSplitDraft | null) => void;
15
+ }>>;
16
+ //# sourceMappingURL=split-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"split-store.d.ts","sourceRoot":"","sources":["../../src/wall/split-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAEhD,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEtE,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,0FAA0F;IAC1F,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,aAAa,CAAA;IACnB,OAAO,EAAE,gBAAgB,CAAA;CAC1B,CAAA;AAED,kFAAkF;AAClF,eAAO,MAAM,YAAY;WAChB,cAAc,GAAG,IAAI;cAClB,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,KAAK,IAAI;GAI/C,CAAA"}
@@ -0,0 +1,6 @@
1
+ import { create } from 'zustand';
2
+ /** The open split's cut preview. Every transition lives in `split-session.ts`. */
3
+ export const useWallSplit = create((set) => ({
4
+ draft: null,
5
+ setDraft: (draft) => set({ draft }),
6
+ }));
@@ -0,0 +1,10 @@
1
+ import { type AnyNode } from '@pascal-app/core';
2
+ /**
3
+ * The wall's `split` affordance (3D): cut markers, segment lengths and the
4
+ * pointer binding. The session (`split-session.ts`) owns the scope and draft;
5
+ * the plan layer renders the same draft.
6
+ */
7
+ export default function WallSplitTool(_props: {
8
+ node: AnyNode;
9
+ }): import("react").JSX.Element | null;
10
+ //# sourceMappingURL=split-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"split-tool.d.ts","sourceRoot":"","sources":["../../src/wall/split-tool.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,OAAO,EAA6C,MAAM,kBAAkB,CAAA;AAgB1F;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,sCA8J9D"}
@@ -0,0 +1,95 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { getWallThickness, sceneRegistry, useScene } from '@pascal-app/core';
4
+ import { DRAFT_LABEL_Y_OFFSET, DraftMeasurementLabel, EDITOR_LAYER, formatLinearMeasurement, NO_RAYCAST, } from '@pascal-app/editor';
5
+ import { BATCHED_LAYER, getSceneTheme, SCENE_LAYER, useViewer } from '@pascal-app/viewer';
6
+ import { useFrame, useThree } from '@react-three/fiber';
7
+ import { useEffect, useRef } from 'react';
8
+ import { Raycaster, Vector2 } from 'three';
9
+ import { bindWallSplitPointer } from './split-pointer';
10
+ import { wallSplitDistance, wallSplitMarkerColor, wallSplitSegmentLabels } from './split-preview';
11
+ import { useWallSplit } from './split-store';
12
+ /**
13
+ * The wall's `split` affordance (3D): cut markers, segment lengths and the
14
+ * pointer binding. The session (`split-session.ts`) owns the scope and draft;
15
+ * the plan layer renders the same draft.
16
+ */
17
+ export default function WallSplitTool(_props) {
18
+ const draft = useWallSplit((s) => s.draft);
19
+ const wallId = draft?.wallId;
20
+ const wall = useScene((s) => (draft ? s.nodes[draft.wallId] : undefined));
21
+ const unit = useViewer((s) => s.unit);
22
+ const metricNotation = useViewer((s) => s.metricNotation);
23
+ const isDark = useViewer((s) => getSceneTheme(s.sceneTheme).appearance === 'dark');
24
+ const { gl, camera } = useThree();
25
+ const root = useRef(null);
26
+ const column = useRef(null);
27
+ const top = useRef(null);
28
+ const base = useRef(null);
29
+ useEffect(() => {
30
+ if (!wallId)
31
+ return;
32
+ const surface = gl.domElement;
33
+ const raycaster = new Raycaster();
34
+ raycaster.layers.set(SCENE_LAYER);
35
+ raycaster.layers.enable(BATCHED_LAYER);
36
+ const pointer = new Vector2();
37
+ return bindWallSplitPointer(surface, (event) => {
38
+ if (event.target !== surface)
39
+ return null;
40
+ const current = useScene.getState().nodes[wallId];
41
+ const object = sceneRegistry.nodes.get(wallId);
42
+ if (current?.type !== 'wall' || !object?.parent)
43
+ return null;
44
+ const rect = surface.getBoundingClientRect();
45
+ if (!rect.width || !rect.height)
46
+ return null;
47
+ pointer.set(((event.clientX - rect.left) / rect.width) * 2 - 1, 1 - ((event.clientY - rect.top) / rect.height) * 2);
48
+ raycaster.setFromCamera(pointer, camera);
49
+ object.updateWorldMatrix(true, true);
50
+ const hit = raycaster.intersectObject(object, true)[0];
51
+ if (!hit)
52
+ return null;
53
+ const local = object.parent.worldToLocal(hit.point.clone());
54
+ return wallSplitDistance(current, [local.x, local.z]);
55
+ });
56
+ }, [wallId, gl, camera]);
57
+ useFrame(() => {
58
+ if (!(root.current && column.current && top.current && base.current))
59
+ return;
60
+ if (wall?.type !== 'wall' || !draft)
61
+ return;
62
+ const object = sceneRegistry.nodes.get(wall.id);
63
+ if (!object?.parent || !object.geometry) {
64
+ root.current.visible = false;
65
+ return;
66
+ }
67
+ root.current.visible = true;
68
+ object.updateWorldMatrix(true, false);
69
+ root.current.matrix.copy(object.parent.matrixWorld);
70
+ if (!object.geometry.boundingBox)
71
+ object.geometry.computeBoundingBox();
72
+ const bounds = object.geometry.boundingBox;
73
+ const bottom = object.position.y + (bounds?.min.y ?? 0);
74
+ const height = bounds ? bounds.max.y - bounds.min.y : (wall.height ?? 3);
75
+ column.current.position.y = bottom + height / 2;
76
+ column.current.scale.y = Math.max(0.05, height + 0.05);
77
+ top.current.position.y = bottom + height + DRAFT_LABEL_Y_OFFSET;
78
+ base.current.position.y = bottom + 0.01;
79
+ });
80
+ if (!draft || wall?.type !== 'wall')
81
+ return null;
82
+ const { preview, snap } = draft;
83
+ const thickness = getWallThickness(wall);
84
+ const color = wallSplitMarkerColor(preview.valid);
85
+ const cut = preview.frames[0]?.point;
86
+ const guide = snap?.kind === 'alignment' && cut
87
+ ? {
88
+ x: (snap.anchor[0] + cut.x) / 2,
89
+ z: (snap.anchor[1] + cut.y) / 2,
90
+ length: Math.hypot(cut.x - snap.anchor[0], cut.y - snap.anchor[1]),
91
+ angle: -Math.atan2(cut.y - snap.anchor[1], cut.x - snap.anchor[0]),
92
+ }
93
+ : null;
94
+ return (_jsxs("group", { ref: root, matrixAutoUpdate: false, children: [_jsx("group", { ref: column, children: preview.frames.map(({ point, tangent }, index) => (_jsxs("group", { position: [point.x, 0, point.y], rotation: [0, -Math.atan2(tangent.y, tangent.x), 0], userData: { testId: 'pascal-split-marker', valid: preview.valid }, children: [_jsxs("mesh", { layers: EDITOR_LAYER, raycast: NO_RAYCAST, renderOrder: 8900, scale: [0.074, 1, thickness + 0.12], children: [_jsx("boxGeometry", {}), _jsx("meshBasicMaterial", { color: preview.valid ? '#f7f3ed' : '#faece9', depthTest: false, depthWrite: false, toneMapped: false })] }), _jsxs("mesh", { layers: EDITOR_LAYER, raycast: NO_RAYCAST, renderOrder: 8910, scale: [0.026, 1.001, thickness + 0.07], children: [_jsx("boxGeometry", {}), _jsx("meshBasicMaterial", { color: color, depthTest: false, depthWrite: false, toneMapped: false })] })] }, index))) }), _jsx("group", { ref: top, children: !preview.valid && cut ? (_jsx(DraftMeasurementLabel, { color: color, label: preview.message, position: [cut.x, 0, cut.y], shadowColor: isDark ? '#111111' : '#ffffff' })) : (wallSplitSegmentLabels(wall, preview).map((segment, index) => (_jsx(DraftMeasurementLabel, { color: isDark ? '#ffffff' : '#111111', label: `${formatLinearMeasurement(segment.length, unit, metricNotation)}${segment.count > 1 ? ` × ${segment.count}` : ''}`, position: [segment.midpoint[0], 0, segment.midpoint[1]], shadowColor: isDark ? '#111111' : '#ffffff' }, index)))) }), _jsx("group", { ref: base, children: guide && (_jsxs("mesh", { layers: EDITOR_LAYER, position: [guide.x, 0, guide.z], raycast: NO_RAYCAST, renderOrder: 8890, rotation: [0, guide.angle, 0], scale: [guide.length, 0.01, 0.02], children: [_jsx("boxGeometry", {}), _jsx("meshBasicMaterial", { color: "#818cf8", depthTest: false, depthWrite: false, opacity: 0.8, toneMapped: false, transparent: true })] })) })] }));
95
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../../src/wall/system.tsx"],"names":[],"mappings":"AAkCA,wBAAgB,wBAAwB,IAAI,IAAI,CAO/C;AAED,wBAAgB,yBAAyB,IAAI,IAAI,CAyChD;AASD;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,WAAW,mCAShB,CAAA;AAED,eAAe,WAAW,CAAA"}
1
+ {"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../../src/wall/system.tsx"],"names":[],"mappings":"AAoCA,wBAAgB,wBAAwB,IAAI,IAAI,CAO/C;AAED,wBAAgB,yBAAyB,IAAI,IAAI,CAyChD;AASD;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,WAAW,mCAShB,CAAA;AAED,eAAe,WAAW,CAAA"}
@@ -4,6 +4,8 @@ import { useLiveNodeOverrides, useScene } from '@pascal-app/core';
4
4
  import { timeSpan, WallCutout, WallSystem } from '@pascal-app/viewer';
5
5
  import { useFrame } from '@react-three/fiber';
6
6
  import { useEffect } from 'react';
7
+ import { curtainWallGeometryAdapter } from './curtain-wall-adapter';
8
+ import { getCurtainAwareWallMaterials } from './curtain-wall-materials';
7
9
  import { buildWallTreatmentLevelData, clearWallTreatmentMiterCache, sameTreatmentWalls, treatmentProudKeys, useWallTreatmentLevelData, } from './treatment-level-data';
8
10
  import { wallTreatmentProudOffsets } from './treatments';
9
11
  import { WallBatchSystem } from './wall-batch-system';
@@ -93,6 +95,6 @@ const WallTreatmentMiterSystem = () => {
93
95
  * draw calls instead of one per wall face run.
94
96
  */
95
97
  const WallSystems = () => {
96
- return (_jsxs(_Fragment, { children: [_jsx(WallTreatmentMiterSystem, {}), _jsx(WallSystem, {}), _jsx(WallCutout, {}), _jsx(WallBatchSystem, {})] }));
98
+ return (_jsxs(_Fragment, { children: [_jsx(WallTreatmentMiterSystem, {}), _jsx(WallSystem, { geometryAdapter: curtainWallGeometryAdapter }), _jsx(WallCutout, { materialResolver: getCurtainAwareWallMaterials }), _jsx(WallBatchSystem, {})] }));
97
99
  };
98
100
  export default WallSystems;
@@ -1 +1 @@
1
- {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/wall/tool.tsx"],"names":[],"mappings":"AA6bA,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAuf5B,CAAA;AAED,eAAe,QAAQ,CAAA"}
1
+ {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/wall/tool.tsx"],"names":[],"mappings":"AAs7BA,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAS5B,CAAA;AAED,eAAe,QAAQ,CAAA"}
package/dist/wall/tool.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { calculateLevelMiters, collectAlignmentAnchors, DEFAULT_LEVEL_HEIGHT, emitter, GROUND_SUPPORT_ID, getWallMiterBoundaryPoints, resolveAlignment, resolveBuildingForLevel, sceneRegistry, useScene, wallClosesRoom, } from '@pascal-app/core';
3
- import { CursorSphere, chainEndJoinsExistingWall, clearPlacementSurface, createWallOnCurrentLevel, EDITOR_LAYER, formatAngleRadians, formatLinearMeasurement, getAngleArcToSegmentReference, getAngleToSegmentReference, getSegmentAngleReferenceAtPoint, isAlignmentGuideActive, isAngleSnapActive, isMagneticSnapActive, markToolCancelConsumed, publishHorizontalConstructionPlane, publishPlacementSurface, resampleTerrainConstructionPlane, resolveEventConstructionPlane, resolvePointerSupportSurface, snapWallDraftPointDetailed, triggerSFX, useAlignmentGuides, useEditor, useFloorplanDraftPreview, useSegmentDraftChain, useWallSnapIndicator, WALL_CONNECT_SNAP_RADIUS, WALL_JOIN_SNAP_RADIUS, } from '@pascal-app/editor';
3
+ import { CursorSphere, chainEndJoinsExistingWall, clearPlacementSurface, createWallOnCurrentLevel, DraftMeasurementLabel, EDITOR_LAYER, formatAngleRadians, formatLinearMeasurement, getAngleArcToSegmentReference, getAngleToSegmentReference, getSegmentAngleReferenceAtPoint, isAlignmentGuideActive, isAngleSnapActive, isMagneticSnapActive, markToolCancelConsumed, publishHorizontalConstructionPlane, publishPlacementSurface, resampleTerrainConstructionPlane, resolveEventConstructionPlane, resolvePointerSupportSurface, snapWallDraftPointDetailed, triggerSFX, useAlignmentGuides, useEditor, useFloorplanDraftPreview, useSegmentDraftChain, useWallSnapIndicator, WALL_CONNECT_SNAP_RADIUS, WALL_JOIN_SNAP_RADIUS, } from '@pascal-app/editor';
4
4
  import { getSceneTheme, useViewer } from '@pascal-app/viewer';
5
5
  import { useThree } from '@react-three/fiber';
6
6
  import { useEffect, useRef, useState } from 'react';
7
7
  import { DoubleSide, Vector3 } from 'three';
8
- import { DraftAngleArc, DraftAxisGuides, DraftMeasurementLabel, getNearestAxisAngleLabel, } from '../shared/draft-axis-guides';
8
+ import { DraftAngleArc, DraftAxisGuides, getNearestAxisAngleLabel, } from '../shared/draft-axis-guides';
9
+ import { useWallDrawingMode, useWallDrawingModeKeys } from './drawing-mode';
10
+ import RectangleWallTool from './rectangle-tool';
9
11
  /**
10
12
  * Phase 5 Stage D — wall placement tool (kind-owned).
11
13
  *
@@ -289,7 +291,7 @@ function getBelowLevelWalls() {
289
291
  .sort((a, b) => b.level - a.level)[0];
290
292
  return getLevelWalls(belowLevel?.id ?? null, nodes);
291
293
  }
292
- export const WallTool = () => {
294
+ const LineWallTool = () => {
293
295
  const unit = useViewer((state) => state.unit);
294
296
  const metricNotation = useViewer((state) => state.metricNotation);
295
297
  const isDark = useViewer((state) => getSceneTheme(state.sceneTheme).appearance === 'dark');
@@ -331,9 +333,6 @@ export const WallTool = () => {
331
333
  const [axisGuide, setAxisGuide] = useState(null);
332
334
  const measurementColor = isDark ? '#ffffff' : '#111111';
333
335
  const measurementShadowColor = isDark ? '#111111' : '#ffffff';
334
- // Clear preset-seeded defaults on deactivation so a later manual wall draw
335
- // isn't built with a stale preset's parameters. Unmount-only.
336
- useEffect(() => () => useEditor.getState().setToolDefaults('wall', null), []);
337
336
  useEffect(() => {
338
337
  let gridPosition = [0, 0];
339
338
  let previousWallEnd = null;
@@ -382,6 +381,7 @@ export const WallTool = () => {
382
381
  const pointedSurfaceFor = (event) => event.nativeEvent?.target instanceof HTMLCanvasElement
383
382
  ? resolvePointerSupportSurface(cameraRef.current, event.position, {
384
383
  includeNodeTopSurfaces: true,
384
+ pointerRay: event.nativeEvent.ray,
385
385
  })
386
386
  : null;
387
387
  const snappedWallConstructionPlane = (targetWallIds, walls) => {
@@ -679,4 +679,12 @@ export const WallTool = () => {
679
679
  }, [unit, metricNotation]);
680
680
  return (_jsxs("group", { children: [_jsx(DraftAxisGuides, { guide: axisGuide, labelColor: measurementColor, labelShadowColor: measurementShadowColor }), _jsx(CursorSphere, { height: previewHeight, ref: cursorRef }), _jsxs("mesh", { layers: EDITOR_LAYER, ref: wallPreviewRef, renderOrder: 1, visible: false, children: [_jsx("boxGeometry", {}), _jsx("meshBasicMaterial", { color: "#818cf8", depthTest: false, depthWrite: false, opacity: 0.5, side: DoubleSide, transparent: true })] }), draftMeasurement && (_jsxs(_Fragment, { children: [_jsx(DraftMeasurementLabel, { color: measurementColor, label: draftMeasurement.lengthLabel, position: draftMeasurement.lengthPosition, shadowColor: measurementShadowColor }), draftMeasurement.angleLabels.map((angleLabel) => (_jsxs("group", { children: [_jsx(DraftAngleArc, { arc: angleLabel.arc, color: measurementColor }), _jsx(DraftMeasurementLabel, { color: measurementColor, label: angleLabel.label, position: angleLabel.position, shadowColor: measurementShadowColor })] }, angleLabel.id)))] }))] }));
681
681
  };
682
+ export const WallTool = () => {
683
+ // Clear preset-seeded defaults on deactivation so a later manual wall draw
684
+ // isn't built with a stale preset's parameters. Unmount-only.
685
+ useEffect(() => () => useEditor.getState().setToolDefaults('wall', null), []);
686
+ useWallDrawingModeKeys();
687
+ const mode = useWallDrawingMode((s) => s.mode);
688
+ return mode === 'rectangle' ? _jsx(RectangleWallTool, {}) : _jsx(LineWallTool, {});
689
+ };
682
690
  export default WallTool;
@@ -1 +1 @@
1
- {"version":3,"file":"wall-batch-system.d.ts","sourceRoot":"","sources":["../../src/wall/wall-batch-system.tsx"],"names":[],"mappings":"AAGA,OAAO,EAML,KAAK,QAAQ,EACd,MAAM,oBAAoB,CAAA;AAG3B,OAAO,EAAE,KAAK,QAAQ,EAAW,IAAI,EAAiB,MAAM,OAAO,CAAA;AAkGnE,wBAAgB,4BAA4B,IAAI,IAAI,CAEnD;AAED,wBAAgB,4BAA4B,IAAI,IAAI,CAKnD;AA6BD,MAAM,MAAM,kBAAkB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,QAAQ,EAAE,CAAA;CAAE,CAAA;AA+BtF;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,OAAO,GAAG,OAAO,CAEnF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAW5E;AAMD,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EACf,eAAe,GAAE,WAAW,CAAC,MAAM,CAAa,GAC/C,GAAG,CAAC,MAAM,EAAE,kBAAkB,EAAE,CAAC,CAoBnC;AAsFD,eAAO,MAAM,eAAe,YAmE3B,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,MAAM,IAAI,EACtB,OAAO,EAAE;IAAE,OAAO,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CAAA;CAAE,QAyI3D"}
1
+ {"version":3,"file":"wall-batch-system.d.ts","sourceRoot":"","sources":["../../src/wall/wall-batch-system.tsx"],"names":[],"mappings":"AAGA,OAAO,EAML,KAAK,QAAQ,EACd,MAAM,oBAAoB,CAAA;AAG3B,OAAO,EAAE,KAAK,QAAQ,EAAW,IAAI,EAAiB,MAAM,OAAO,CAAA;AAkGnE,wBAAgB,4BAA4B,IAAI,IAAI,CAEnD;AAED,wBAAgB,4BAA4B,IAAI,IAAI,CAKnD;AA6BD,MAAM,MAAM,kBAAkB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,QAAQ,EAAE,CAAA;CAAE,CAAA;AAiCtF;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,OAAO,GAAG,OAAO,CAEnF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAW5E;AAMD,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EACf,eAAe,GAAE,WAAW,CAAC,MAAM,CAAa,GAC/C,GAAG,CAAC,MAAM,EAAE,kBAAkB,EAAE,CAAC,CAoBnC;AAsFD,eAAO,MAAM,eAAe,YAmE3B,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,MAAM,IAAI,EACtB,OAAO,EAAE;IAAE,OAAO,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CAAA;CAAE,QAyI3D"}
@@ -103,6 +103,9 @@ function toCandidate(nodeId, node, excludedNodeIds = EMPTY_IDS) {
103
103
  return null;
104
104
  if (node.visible === false)
105
105
  return null;
106
+ // Curtain walls use a separate caster that excludes their transparent panes.
107
+ if (node.wallType === 'curtain')
108
+ return null;
106
109
  const mesh = sceneRegistry.nodes.get(nodeId);
107
110
  if (!mesh?.visible)
108
111
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/window/definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EAGT,cAAc,EAEd,QAAQ,EAER,UAAU,IAAI,cAAc,EAC7B,MAAM,kBAAkB,CAAA;AAqBzB,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAQrC,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,GAC3B,SAAS,GAAG,IAAI,CAWlB;AA0JD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAAc,CAAC,OAAO,UAAU,CAqG9D,CAAA"}
1
+ {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/window/definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EAGT,cAAc,EAEd,QAAQ,EAER,UAAU,IAAI,cAAc,EAC7B,MAAM,kBAAkB,CAAA;AAwBzB,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAQrC,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,GAC3B,SAAS,GAAG,IAAI,CAWlB;AAwMD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAAc,CAAC,OAAO,UAAU,CAsG9D,CAAA"}
@@ -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"}