@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,220 @@
1
+ import { subtractPolygonsFromPolygon } from '@pascal-app/core';
2
+ import { ensureRenderableGeometryAttributes } from '@pascal-app/viewer';
3
+ import { BufferGeometry, Float32BufferAttribute, ShapeUtils, Vector2 } from 'three';
4
+ export function buildStraightCurtainPiecesWithCutouts(pieces, base, cutouts) {
5
+ const positions = [];
6
+ const normals = [];
7
+ const preparedCutouts = cutouts.map((cutout) => {
8
+ const points = cutout.map((point) => [point.x, point.y]);
9
+ const xs = points.map(([x]) => x);
10
+ const ys = points.map(([, y]) => y);
11
+ return {
12
+ points,
13
+ minX: Math.min(...xs),
14
+ maxX: Math.max(...xs),
15
+ minY: Math.min(...ys),
16
+ maxY: Math.max(...ys),
17
+ };
18
+ });
19
+ const vertex = (point, normal) => {
20
+ positions.push(point[0], point[1], point[2]);
21
+ normals.push(normal[0], normal[1], normal[2]);
22
+ };
23
+ const contains = (ring, point) => {
24
+ let inside = false;
25
+ for (let index = 0, previous = ring.length - 1; index < ring.length; previous = index++) {
26
+ const a = ring[index];
27
+ const b = ring[previous];
28
+ if (a[1] > point[1] !== b[1] > point[1] &&
29
+ point[0] < ((b[0] - a[0]) * (point[1] - a[1])) / (b[1] - a[1]) + a[0]) {
30
+ inside = !inside;
31
+ }
32
+ }
33
+ return inside;
34
+ };
35
+ const area = (ring) => Math.abs(ring.reduce((sum, point, index) => {
36
+ const next = ring[(index + 1) % ring.length];
37
+ return sum + point[0] * next[1] - next[0] * point[1];
38
+ }, 0) / 2);
39
+ for (const piece of pieces) {
40
+ const bottom = piece.bottom + base;
41
+ const top = piece.top + base;
42
+ const overlapping = preparedCutouts.filter((cutout) => cutout.maxX > piece.left &&
43
+ cutout.minX < piece.right &&
44
+ cutout.maxY > bottom &&
45
+ cutout.minY < top);
46
+ const subject = [
47
+ [piece.left, bottom],
48
+ [piece.right, bottom],
49
+ [piece.right, top],
50
+ [piece.left, top],
51
+ ];
52
+ const rings = subtractPolygonsFromPolygon(subject, overlapping.map(({ points }) => points)).sort((a, b) => area(b) - area(a));
53
+ const groups = [];
54
+ for (const ring of rings) {
55
+ const parent = groups.find(({ contour }) => contains(contour, ring[0]));
56
+ if (parent)
57
+ parent.holes.push([...ring].reverse());
58
+ else
59
+ groups.push({ contour: ring, holes: [] });
60
+ }
61
+ for (const { contour, holes } of groups) {
62
+ const flattened = [contour, ...holes].flat();
63
+ const triangles = ShapeUtils.triangulateShape(contour.map(([x, y]) => new Vector2(x, y)), holes.map((hole) => hole.map(([x, y]) => new Vector2(x, y))));
64
+ for (const triangle of triangles) {
65
+ const aIndex = triangle[0];
66
+ const bIndex = triangle[1];
67
+ const cIndex = triangle[2];
68
+ const a = flattened[aIndex];
69
+ const b = flattened[bIndex];
70
+ const c = flattened[cIndex];
71
+ vertex([a[0], a[1], piece.front], [0, 0, 1]);
72
+ vertex([b[0], b[1], piece.front], [0, 0, 1]);
73
+ vertex([c[0], c[1], piece.front], [0, 0, 1]);
74
+ vertex([c[0], c[1], piece.back], [0, 0, -1]);
75
+ vertex([b[0], b[1], piece.back], [0, 0, -1]);
76
+ vertex([a[0], a[1], piece.back], [0, 0, -1]);
77
+ }
78
+ for (const ring of [contour, ...holes]) {
79
+ for (let index = 0; index < ring.length; index++) {
80
+ const a = ring[index];
81
+ const b = ring[(index + 1) % ring.length];
82
+ const dx = b[0] - a[0];
83
+ const dy = b[1] - a[1];
84
+ const length = Math.hypot(dx, dy);
85
+ if (length <= 1e-6)
86
+ continue;
87
+ const normal = [dy / length, -dx / length, 0];
88
+ vertex([a[0], a[1], piece.back], normal);
89
+ vertex([b[0], b[1], piece.back], normal);
90
+ vertex([b[0], b[1], piece.front], normal);
91
+ vertex([a[0], a[1], piece.back], normal);
92
+ vertex([b[0], b[1], piece.front], normal);
93
+ vertex([a[0], a[1], piece.front], normal);
94
+ }
95
+ }
96
+ }
97
+ }
98
+ const geometry = new BufferGeometry();
99
+ geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
100
+ geometry.setAttribute('normal', new Float32BufferAttribute(normals, 3));
101
+ ensureRenderableGeometryAttributes(geometry);
102
+ return geometry;
103
+ }
104
+ // Adjacent fragments of one pane share a boundary, not a second glass surface.
105
+ export function buildStraightCurtainPieces(pieces, base, cullSharedFaces = true) {
106
+ if (!cullSharedFaces) {
107
+ const valuesPerPiece = 6 * 6 * 3;
108
+ const positions = new Float32Array(pieces.length * valuesPerPiece);
109
+ const normals = new Float32Array(pieces.length * valuesPerPiece);
110
+ let offset = 0;
111
+ const vertex = (point, normal) => {
112
+ positions[offset] = point[0];
113
+ normals[offset++] = normal[0];
114
+ positions[offset] = point[1];
115
+ normals[offset++] = normal[1];
116
+ positions[offset] = point[2];
117
+ normals[offset++] = normal[2];
118
+ };
119
+ const quad = (a, b, c, d, normal) => {
120
+ vertex(a, normal);
121
+ vertex(b, normal);
122
+ vertex(c, normal);
123
+ vertex(a, normal);
124
+ vertex(c, normal);
125
+ vertex(d, normal);
126
+ };
127
+ for (const piece of pieces) {
128
+ const { left: l, right: r, bottom, top, front: f, back: k } = piece;
129
+ const b = bottom + base;
130
+ const t = top + base;
131
+ quad([l, b, f], [r, b, f], [r, t, f], [l, t, f], [0, 0, 1]);
132
+ quad([r, b, k], [l, b, k], [l, t, k], [r, t, k], [0, 0, -1]);
133
+ quad([l, b, k], [l, b, f], [l, t, f], [l, t, k], [-1, 0, 0]);
134
+ quad([r, b, f], [r, b, k], [r, t, k], [r, t, f], [1, 0, 0]);
135
+ quad([l, b, k], [r, b, k], [r, b, f], [l, b, f], [0, -1, 0]);
136
+ quad([l, t, f], [r, t, f], [r, t, k], [l, t, k], [0, 1, 0]);
137
+ }
138
+ const geometry = new BufferGeometry();
139
+ geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
140
+ geometry.setAttribute('normal', new Float32BufferAttribute(normals, 3));
141
+ ensureRenderableGeometryAttributes(geometry);
142
+ return geometry;
143
+ }
144
+ const positions = new Float32Array(pieces.length * 6 * 6 * 3);
145
+ const normals = new Float32Array(positions.length);
146
+ let offset = 0;
147
+ const vertex = (point, normal) => {
148
+ positions[offset] = point[0];
149
+ normals[offset++] = normal[0];
150
+ positions[offset] = point[1];
151
+ normals[offset++] = normal[1];
152
+ positions[offset] = point[2];
153
+ normals[offset++] = normal[2];
154
+ };
155
+ const quad = (a, b, c, d, normal) => {
156
+ vertex(a, normal);
157
+ vertex(b, normal);
158
+ vertex(c, normal);
159
+ vertex(a, normal);
160
+ vertex(c, normal);
161
+ vertex(d, normal);
162
+ };
163
+ const sides = ['left', 'right', 'bottom', 'top'];
164
+ const key = (piece, side, coordinate = piece[side]) => `${side}:${coordinate.toFixed(7)}:${piece.front.toFixed(7)}:${piece.back.toFixed(7)}`;
165
+ const edges = new Map();
166
+ if (cullSharedFaces) {
167
+ for (const piece of pieces) {
168
+ for (const side of sides) {
169
+ const id = key(piece, side);
170
+ const neighbors = edges.get(id) ?? [];
171
+ neighbors.push(piece);
172
+ edges.set(id, neighbors);
173
+ }
174
+ }
175
+ }
176
+ for (const piece of pieces) {
177
+ const { left: l, right: r, bottom, top, front: f, back: k } = piece;
178
+ const b = bottom + base;
179
+ const t = top + base;
180
+ quad([l, b, f], [r, b, f], [r, t, f], [l, t, f], [0, 0, 1]);
181
+ quad([r, b, k], [l, b, k], [l, t, k], [r, t, k], [0, 0, -1]);
182
+ for (const side of sides) {
183
+ const vertical = side === 'left' || side === 'right';
184
+ let runs = vertical ? [[bottom, top]] : [[l, r]];
185
+ const opposite = side === 'left' ? 'right' : side === 'right' ? 'left' : side === 'bottom' ? 'top' : 'bottom';
186
+ if (cullSharedFaces) {
187
+ for (const other of edges.get(key(piece, opposite, piece[side])) ?? []) {
188
+ if (other === piece)
189
+ continue;
190
+ const low = vertical ? other.bottom : other.left;
191
+ const high = vertical ? other.top : other.right;
192
+ runs = runs.flatMap(([start, end]) => high <= start || low >= end
193
+ ? [[start, end]]
194
+ : [
195
+ ...(low > start ? [[start, low]] : []),
196
+ ...(high < end ? [[high, end]] : []),
197
+ ]);
198
+ }
199
+ }
200
+ for (const [start, end] of runs) {
201
+ if (vertical) {
202
+ const y0 = start + base, y1 = end + base;
203
+ if (side === 'left')
204
+ quad([l, y0, k], [l, y0, f], [l, y1, f], [l, y1, k], [-1, 0, 0]);
205
+ else
206
+ quad([r, y0, f], [r, y0, k], [r, y1, k], [r, y1, f], [1, 0, 0]);
207
+ }
208
+ else if (side === 'bottom')
209
+ quad([start, b, k], [end, b, k], [end, b, f], [start, b, f], [0, -1, 0]);
210
+ else
211
+ quad([start, t, f], [end, t, f], [end, t, k], [start, t, k], [0, 1, 0]);
212
+ }
213
+ }
214
+ }
215
+ const geometry = new BufferGeometry();
216
+ geometry.setAttribute('position', new Float32BufferAttribute(positions.subarray(0, offset), 3));
217
+ geometry.setAttribute('normal', new Float32BufferAttribute(normals.subarray(0, offset), 3));
218
+ ensureRenderableGeometryAttributes(geometry);
219
+ return geometry;
220
+ }
@@ -0,0 +1,4 @@
1
+ import type { BufferGeometry } from 'three';
2
+ export declare const CURTAIN_WALL_SHADOW_NAME = "curtain-wall-shadow";
3
+ export declare function buildCurtainWallShadowGeometry(source: BufferGeometry, opaqueGlass: boolean): BufferGeometry;
4
+ //# sourceMappingURL=curtain-wall-shadow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curtain-wall-shadow.d.ts","sourceRoot":"","sources":["../../src/wall/curtain-wall-shadow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAE3C,eAAO,MAAM,wBAAwB,wBAAwB,CAAA;AAE7D,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,cAAc,EACtB,WAAW,EAAE,OAAO,GACnB,cAAc,CAYhB"}
@@ -0,0 +1,15 @@
1
+ export const CURTAIN_WALL_SHADOW_NAME = 'curtain-wall-shadow';
2
+ export function buildCurtainWallShadowGeometry(source, opaqueGlass) {
3
+ const geometry = source.clone();
4
+ const indices = [];
5
+ for (const group of source.groups) {
6
+ if (group.materialIndex === 1 && !opaqueGlass)
7
+ continue;
8
+ for (let i = group.start; i < group.start + group.count; i++)
9
+ indices.push(source.index ? source.index.getX(i) : i);
10
+ }
11
+ geometry.clearGroups();
12
+ geometry.setIndex(indices);
13
+ geometry.setDrawRange(0, indices.length);
14
+ return geometry;
15
+ }
@@ -1,19 +1,4 @@
1
1
  import { type NodeDefinition } from '@pascal-app/core';
2
2
  import { WallNode } from './schema';
3
- /**
4
- * Wall — the Phase 3 stress test of the registry-driven node model.
5
- *
6
- * Stage A: registered (capabilities, relations, parametrics, presentation).
7
- * Stage B: deferred — wall geometry depends on level-batch miter data that
8
- * doesn't fit the generic `(node, ctx) => Group` shape without `ctx.
9
- * levelData?.miters`. See plan's "GeometryContext" extension note.
10
- * `renderer` + `system` keep wrap-exporting legacy WallRenderer +
11
- * WallSystem + WallCutout.
12
- * Stage C: `def.floorplan` builder produces the mitered plan footprint
13
- * polygon from shared floor-plan level data, with `ctx.siblings` as the
14
- * direct-caller fallback.
15
- * floorplan-panel.tsx's `wallPolygons` short-circuits to [] when
16
- * wall is registered.
17
- */
18
3
  export declare const wallDefinition: NodeDefinition<typeof WallNode>;
19
4
  //# sourceMappingURL=definition.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/wall/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,cAAc,EAEpB,MAAM,kBAAkB,CAAA;AAwBzB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAGnC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,EAAE,cAAc,CAAC,OAAO,QAAQ,CA4J1D,CAAA"}
1
+ {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/wall/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,cAAc,EAEpB,MAAM,kBAAkB,CAAA;AA4BzB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAyBnC,eAAO,MAAM,cAAc,EAAE,cAAc,CAAC,OAAO,QAAQ,CAsN1D,CAAA"}
@@ -1,17 +1,22 @@
1
1
  import { getWallBaseElevationForNodes, getWallEffectiveHeightForNodes, } from '@pascal-app/core';
2
- import { DRAFTING_SURFACE_EXTENSION_KEY, } from '@pascal-app/editor';
2
+ import { DRAFTING_SURFACE_EXTENSION_KEY, PANEL_MODEL_EXTENSION, } from '@pascal-app/editor';
3
3
  import { buildWallContextualDimensions } from './contextual-dimensions';
4
4
  import { hasWallCurveBlockingChildren } from './curve-eligibility';
5
+ import { useWallDrawingMode } from './drawing-mode';
5
6
  import { buildWallFloorplan, computeWallFloorplanLevelData } from './floorplan';
6
7
  import { wallCurveAffordance, wallMoveEndpointAffordance, wallThicknessAffordance, } from './floorplan-affordances';
7
8
  import { wallFloorplanMoveTarget } from './floorplan-move';
8
9
  import { wallFloorplanSiblingOverrides } from './floorplan-overrides';
9
10
  import { matchWallMeasurementFeature, resolveWallMeasurementFeature, wallMeasurementFeatures, } from './measurement';
10
11
  import { wallPaint } from './paint';
12
+ import { wallSettings } from './panel-model';
11
13
  import { wallParametrics } from './parametrics';
12
14
  import { wallQuickMeasurement } from './quick-measurement';
13
15
  import { WallNode } from './schema';
14
16
  import { wallSlots } from './slots';
17
+ import { WALL_SPLIT_MAX_CUTS } from './split-preview';
18
+ import { setWallSplitCuts } from './split-session';
19
+ import { useWallSplit } from './split-store';
15
20
  /**
16
21
  * Wall — the Phase 3 stress test of the registry-driven node model.
17
22
  *
@@ -27,14 +32,18 @@ import { wallSlots } from './slots';
27
32
  * floorplan-panel.tsx's `wallPolygons` short-circuits to [] when
28
33
  * wall is registered.
29
34
  */
35
+ const SPLIT_CUT_COUNTS = Array.from({ length: WALL_SPLIT_MAX_CUTS }, (_, index) => String(index + 1));
30
36
  export const wallDefinition = {
31
37
  kind: 'wall',
32
38
  snapProfile: 'structural',
33
- schemaVersion: 8,
39
+ schemaVersion: 9,
34
40
  schema: WallNode,
35
41
  category: 'structure',
36
42
  surfaceRole: 'wall',
37
43
  extensions: {
44
+ [PANEL_MODEL_EXTENSION]: {
45
+ rows: ({ node, nodes, update }) => wallSettings(node, nodes, update),
46
+ },
38
47
  [DRAFTING_SURFACE_EXTENSION_KEY]: {
39
48
  kind: 'wall',
40
49
  classifyFace: (node, localNormal) => {
@@ -55,8 +64,11 @@ export const wallDefinition = {
55
64
  },
56
65
  },
57
66
  'pascal:editor/floorplan': {
67
+ tool: () => import('./floorplan-tool'),
68
+ reshapeLayers: { split: () => import('./split-floorplan-layer') },
58
69
  contextualDimensions: buildWallContextualDimensions,
59
70
  actionMenu: {
71
+ actions: () => import('./actions'),
60
72
  canCurve: ({ node, nodes }) => !hasWallCurveBlockingChildren(node.children.flatMap((childId) => {
61
73
  const child = nodes[childId];
62
74
  return child ? [child] : [];
@@ -82,6 +94,7 @@ export const wallDefinition = {
82
94
  selectable: { hitVolume: 'bbox' },
83
95
  // Front + back faces host items (paintings, shelves, switches).
84
96
  surfaces: {
97
+ hosting: false,
85
98
  top: {
86
99
  height: (node, { nodes }) => {
87
100
  const wall = node;
@@ -101,7 +114,7 @@ export const wallDefinition = {
101
114
  // `{ slotId, label, default }` contract every other paintable kind exposes.
102
115
  // Paint still writes the legacy inline fields for base faces via
103
116
  // `wallPaint`; migrating those fully into `node.slots` is a later step.
104
- slots: () => wallSlots(),
117
+ slots: (node) => wallSlots(node),
105
118
  },
106
119
  relations: {
107
120
  hosts: ['door', 'window', 'item', 'lean-to-extension'],
@@ -125,6 +138,7 @@ export const wallDefinition = {
125
138
  curve: () => import('./curve-tool'),
126
139
  'move-endpoint': () => import('./move-endpoint-tool'),
127
140
  move: () => import('./move-tool'),
141
+ split: () => import('./split-tool'),
128
142
  },
129
143
  renderer: {
130
144
  kind: 'parametric',
@@ -159,8 +173,55 @@ export const wallDefinition = {
159
173
  floorplanSiblingOverrides: wallFloorplanSiblingOverrides,
160
174
  toolHints: [
161
175
  { key: 'Left click', label: 'Set wall start / end' },
176
+ {
177
+ key: 'R',
178
+ label: 'Shape',
179
+ chip: {
180
+ subscribe: (onChange) => useWallDrawingMode.subscribe(onChange),
181
+ value: () => useWallDrawingMode.getState().mode,
182
+ cycle: () => useWallDrawingMode.getState().toggle(),
183
+ labels: { line: 'Shape: Line', rectangle: 'Shape: Rectangle' },
184
+ icons: { line: 'lucide:minus', rectangle: 'lucide:square' },
185
+ tooltip: 'Wall shape — click or press R to toggle',
186
+ },
187
+ },
162
188
  { key: 'Esc', label: 'Cancel' },
163
189
  ],
190
+ // The split session (`split-session.ts`) is the wall's own reshape; the HUD
191
+ // shows these while it runs. The snapping chip comes from the scope.
192
+ affordanceHints: {
193
+ split: [
194
+ { key: 'Left click', label: 'Split at the marks' },
195
+ {
196
+ key: 'Scroll',
197
+ label: 'Cuts',
198
+ chip: {
199
+ subscribe: (onChange) => useWallSplit.subscribe(onChange),
200
+ value: () => String(useWallSplit.getState().draft?.cuts ?? 1),
201
+ cycle: () => {
202
+ const cuts = useWallSplit.getState().draft?.cuts ?? 1;
203
+ setWallSplitCuts(cuts >= WALL_SPLIT_MAX_CUTS ? 1 : cuts + 1);
204
+ },
205
+ labels: Object.fromEntries(SPLIT_CUT_COUNTS.map((count) => [
206
+ count,
207
+ count === '1' ? 'Cuts: 1' : `Cuts: ${count}, even`,
208
+ ])),
209
+ icons: Object.fromEntries(SPLIT_CUT_COUNTS.map((count) => [count, 'lucide:scissors'])),
210
+ tooltip: 'Number of cuts — scroll or click to change',
211
+ },
212
+ },
213
+ {
214
+ key: 'Alt',
215
+ label: 'Free placement',
216
+ // Several cuts are evenly spaced, so there is nothing to place freely.
217
+ visible: {
218
+ subscribe: (onChange) => useWallSplit.subscribe(onChange),
219
+ value: () => (useWallSplit.getState().draft?.cuts ?? 1) === 1,
220
+ },
221
+ },
222
+ { key: 'Esc', label: 'Cancel' },
223
+ ],
224
+ },
164
225
  presentation: {
165
226
  label: 'Wall',
166
227
  description: 'A straight or curved wall segment. Hosts doors, windows, lean-to extensions, and wall-mounted items.',
@@ -169,6 +230,6 @@ export const wallDefinition = {
169
230
  paletteOrder: 10,
170
231
  },
171
232
  mcp: {
172
- description: 'A wall segment defined by start + end points, with optional curve sagitta.',
233
+ description: 'A wall defined by endpoints and optional curve sagitta. wallType selects standard or curtain. curtainWall configures construction, framing, grids, glazing, spandrels, and zero-based panel overrides. Thickness is frame depth for curtain walls.',
173
234
  },
174
235
  };
@@ -0,0 +1,12 @@
1
+ export declare const useWallDrawingMode: import("zustand").UseBoundStore<import("zustand").StoreApi<{
2
+ mode: "line" | "rectangle";
3
+ toggle: () => void;
4
+ }>>;
5
+ /**
6
+ * R toggles line / rectangle drawing (the HUD's Shape chip cycles the same
7
+ * store). Both wall tools mount this: the 3D tool only exists once the canvas
8
+ * does, and split view mounts both, so one listener serves whichever is up.
9
+ * The mode outlives the tool: re-arming walls resumes the last shape used.
10
+ */
11
+ export declare function useWallDrawingModeKeys(): void;
12
+ //# sourceMappingURL=drawing-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drawing-mode.d.ts","sourceRoot":"","sources":["../../src/wall/drawing-mode.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,kBAAkB;UACvB,MAAM,GAAG,WAAW;YAClB,MAAM,IAAI;GAOjB,CAAA;AAWH;;;;;GAKG;AACH,wBAAgB,sBAAsB,SAOrC"}
@@ -0,0 +1,38 @@
1
+ import { emitter } from '@pascal-app/core';
2
+ import { isEditableKeyboardTarget } from '@pascal-app/editor';
3
+ import { useEffect } from 'react';
4
+ import { create } from 'zustand';
5
+ export const useWallDrawingMode = create((set, get) => ({
6
+ mode: 'line',
7
+ toggle: () => {
8
+ emitter.emit('tool:cancel');
9
+ set({ mode: get().mode === 'rectangle' ? 'line' : 'rectangle' });
10
+ },
11
+ }));
12
+ const onKeyDown = (e) => {
13
+ if (e.repeat || e.metaKey || e.ctrlKey || e.altKey)
14
+ return;
15
+ if (e.key !== 'r' && e.key !== 'R')
16
+ return;
17
+ if (isEditableKeyboardTarget(e.target))
18
+ return;
19
+ e.preventDefault();
20
+ useWallDrawingMode.getState().toggle();
21
+ };
22
+ let owners = 0;
23
+ /**
24
+ * R toggles line / rectangle drawing (the HUD's Shape chip cycles the same
25
+ * store). Both wall tools mount this: the 3D tool only exists once the canvas
26
+ * does, and split view mounts both, so one listener serves whichever is up.
27
+ * The mode outlives the tool: re-arming walls resumes the last shape used.
28
+ */
29
+ export function useWallDrawingModeKeys() {
30
+ useEffect(() => {
31
+ if (owners++ === 0)
32
+ window.addEventListener('keydown', onKeyDown);
33
+ return () => {
34
+ if (--owners === 0)
35
+ window.removeEventListener('keydown', onKeyDown);
36
+ };
37
+ }, []);
38
+ }
@@ -0,0 +1,4 @@
1
+ import { type FloorplanToolContext } from '@pascal-app/editor';
2
+ /** The wall's plan tool: line drafting stays with the panel; rectangle mode mounts its own tool. */
3
+ export default function WallFloorplanTool(props: FloorplanToolContext): import("react").JSX.Element | null;
4
+ //# sourceMappingURL=floorplan-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"floorplan-tool.d.ts","sourceRoot":"","sources":["../../src/wall/floorplan-tool.tsx"],"names":[],"mappings":"AAOA,OAAO,EACL,KAAK,oBAAoB,EAO1B,MAAM,oBAAoB,CAAA;AAK3B,oGAAoG;AACpG,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,KAAK,EAAE,oBAAoB,sCAIpE"}
@@ -0,0 +1,107 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { emitter, resolveTerrainWallConstructionOptions, useScene, } from '@pascal-app/core';
4
+ import { markToolCancelConsumed, triggerSFX, useEditor, useFloorplanDraftPreview, useFloorplanRender, useInteractionScope, } from '@pascal-app/editor';
5
+ import { useEffect, useRef, useState } from 'react';
6
+ import { useWallDrawingMode, useWallDrawingModeKeys } from './drawing-mode';
7
+ import { createWallRectangle } from './rectangle-command';
8
+ /** The wall's plan tool: line drafting stays with the panel; rectangle mode mounts its own tool. */
9
+ export default function WallFloorplanTool(props) {
10
+ useWallDrawingModeKeys();
11
+ const mode = useWallDrawingMode((s) => s.mode);
12
+ return mode === 'rectangle' ? _jsx(RectangleFloorplanTool, { ...props }) : null;
13
+ }
14
+ /**
15
+ * Rectangle mode in the floor plan. Pointer moves stay with the panel, so the
16
+ * cursor, snapping, alignment guides and snap beacon are the line wall's own;
17
+ * this tool only claims clicks and publishes the first corner, and the panel's
18
+ * linear draft layer draws the four draft walls from it.
19
+ */
20
+ function RectangleFloorplanTool({ activeLevelId }) {
21
+ const group = useRef(null);
22
+ const renderContext = useFloorplanRender();
23
+ const [error, setError] = useState(null);
24
+ useEffect(() => {
25
+ const svg = group.current?.ownerSVGElement;
26
+ if (!svg || !activeLevelId)
27
+ return;
28
+ const draft = useFloorplanDraftPreview.getState;
29
+ let down = null;
30
+ let construction;
31
+ useInteractionScope.getState().begin({ kind: 'drafting', tool: 'wall' });
32
+ const claim = (e) => {
33
+ e.preventDefault();
34
+ e.stopImmediatePropagation();
35
+ };
36
+ const onDown = (e) => {
37
+ if (e.button !== 0 || e.metaKey || e.ctrlKey)
38
+ return;
39
+ claim(e);
40
+ down = [e.clientX, e.clientY];
41
+ };
42
+ const cancel = () => {
43
+ if (draft().wallRectangleDraftStart)
44
+ markToolCancelConsumed();
45
+ draft().setWallRectangleDraftStart(null);
46
+ down = null;
47
+ setError(null);
48
+ };
49
+ const onClick = (e) => {
50
+ if (e.button !== 0 || e.metaKey || e.ctrlKey)
51
+ return;
52
+ claim(e);
53
+ if (!down || Math.hypot(e.clientX - down[0], e.clientY - down[1]) > 5) {
54
+ down = null;
55
+ return;
56
+ }
57
+ down = null;
58
+ // The panel's snapped cursor, exactly where a line wall would land.
59
+ const point = draft().cursorPoint;
60
+ if (!point)
61
+ return;
62
+ const start = draft().wallRectangleDraftStart;
63
+ if (!start) {
64
+ construction = resolveTerrainWallConstructionOptions(useScene.getState().nodes, activeLevelId, point, useEditor.getState().toolDefaults.wall);
65
+ draft().setWallRectangleDraftStart(point);
66
+ setError(null);
67
+ triggerSFX('sfx:structure-build-start');
68
+ return;
69
+ }
70
+ try {
71
+ createWallRectangle(activeLevelId, start, point, useEditor.getState().toolDefaults.wall ?? {}, construction);
72
+ cancel();
73
+ triggerSFX('sfx:structure-build');
74
+ }
75
+ catch (e) {
76
+ setError({ message: e.message, at: point });
77
+ }
78
+ };
79
+ const stopDouble = (e) => {
80
+ if (e.button === 0)
81
+ claim(e);
82
+ };
83
+ // The panel publishes the snapped cursor only when it moves to a new
84
+ // point, so each change while a corner is down is the line draft's tick.
85
+ const stopTick = useFloorplanDraftPreview.subscribe((state, previous) => {
86
+ if (state.wallRectangleDraftStart &&
87
+ state.cursorPoint &&
88
+ state.cursorPoint !== previous.cursorPoint)
89
+ triggerSFX('sfx:grid-snap');
90
+ });
91
+ svg.addEventListener('pointerdown', onDown, true);
92
+ svg.addEventListener('click', onClick, true);
93
+ svg.addEventListener('dblclick', stopDouble, true);
94
+ emitter.on('tool:cancel', cancel);
95
+ return () => {
96
+ stopTick();
97
+ svg.removeEventListener('pointerdown', onDown, true);
98
+ svg.removeEventListener('click', onClick, true);
99
+ svg.removeEventListener('dblclick', stopDouble, true);
100
+ emitter.off('tool:cancel', cancel);
101
+ draft().setWallRectangleDraftStart(null);
102
+ useInteractionScope.getState().endIf((s) => s.kind === 'drafting' && s.tool === 'wall');
103
+ };
104
+ }, [activeLevelId]);
105
+ const unitsPerPixel = renderContext?.unitsPerPixel ?? 0.01;
106
+ return (_jsx("g", { ref: group, pointerEvents: "none", children: error && (_jsx("text", { x: error.at[0], y: error.at[1] - 0.2, fill: "#ef4444", fontSize: 12 * unitsPerPixel, children: error.message })) }));
107
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"floorplan.d.ts","sourceRoot":"","sources":["../../src/wall/floorplan.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAEZ,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EAOpB,KAAK,aAAa,EAClB,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAKL,KAAK,6BAA6B,EACnC,MAAM,2BAA2B,CAAA;AAYlC,QAAA,MAAM,yBAAyB,yDAA0D,CAAA;AAEzF,KAAK,sBAAsB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAA;AAexE,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,aAAa,CAAA;IACrB,cAAc,EAAE,aAAa,CAAA;IAC7B,iCAAiC,EAAE,MAAM,CAAC,sBAAsB,EAAE,6BAA6B,CAAC,CAAA;CACjG,CAAA;AAED,wBAAgB,6BAA6B,CAAC,EAC5C,QAAQ,EACR,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAA;IACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B,GAAG,sBAAsB,CAqCzB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,eAAe,GAAG,iBAAiB,GAAG,IAAI,CAkNjG"}
1
+ {"version":3,"file":"floorplan.d.ts","sourceRoot":"","sources":["../../src/wall/floorplan.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAEZ,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EAQpB,KAAK,aAAa,EAClB,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAKL,KAAK,6BAA6B,EACnC,MAAM,2BAA2B,CAAA;AAalC,QAAA,MAAM,yBAAyB,yDAA0D,CAAA;AAEzF,KAAK,sBAAsB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAA;AAexE,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,aAAa,CAAA;IACrB,cAAc,EAAE,aAAa,CAAA;IAC7B,iCAAiC,EAAE,MAAM,CAAC,sBAAsB,EAAE,6BAA6B,CAAC,CAAA;CACjG,CAAA;AAED,wBAAgB,6BAA6B,CAAC,EAC5C,QAAQ,EACR,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAA;IACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B,GAAG,sBAAsB,CAqCzB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,eAAe,GAAG,iBAAiB,GAAG,IAAI,CA8NjG"}
@@ -1,7 +1,8 @@
1
- import { calculateLevelMiters, getWallCurveFrameAt, getWallCurveLength, getWallMidpointHandlePoint, getWallPlanFootprint, getWallThickness, isCurvedWall, } from '@pascal-app/core';
1
+ import { calculateLevelMiters, getWallCurveFrameAt, getWallCurveLength, getWallEffectiveHeightForNodes, getWallMidpointHandlePoint, getWallPlanFootprint, getWallThickness, isCurvedWall, } from '@pascal-app/core';
2
2
  import { floorplanGeometryMetadata, readFloorplanContext } from '@pascal-app/editor';
3
3
  import { constructionDimensionStandard } from '../shared/construction-dimension-standards';
4
4
  import { buildCurvedWallConstructionDimensions, buildLevelWallConstructionDimensionPlan, buildWallConstructionDimensions, renderPlannedConstructionDimensions, } from './construction-dimensions';
5
+ import { buildCurtainWallFloorplan } from './curtain-wall-floorplan';
5
6
  import { hasWallCurveBlockingChildren } from './curve-eligibility';
6
7
  // Same constants the legacy `getFloorplanWall` uses (editor/lib/floorplan/walls.ts).
7
8
  // Slightly exaggerates thin walls so the 2D plan stays legible without
@@ -103,7 +104,7 @@ export function buildWallFloorplan(node, ctx) {
103
104
  : isHovered && palette
104
105
  ? palette.wallHoverStroke
105
106
  : '#1f2937';
106
- const fill = showSelectedChrome ? '#ffffff' : '#374151';
107
+ const fill = node.wallType === 'curtain' ? 'transparent' : showSelectedChrome ? '#ffffff' : '#374151';
107
108
  const children = [
108
109
  {
109
110
  kind: 'polygon',
@@ -121,6 +122,9 @@ export function buildWallFloorplan(node, ctx) {
121
122
  cursor: isSelected ? 'default' : undefined,
122
123
  },
123
124
  ];
125
+ if (node.wallType === 'curtain') {
126
+ children.push(...buildCurtainWallFloorplan(node, getWallEffectiveHeightForNodes(node, ctx.sceneNodes ?? {}), stroke, ctx.children));
127
+ }
124
128
  if (automaticDimensions) {
125
129
  const dimensionStroke = isSelected && palette ? palette.selectedStroke : (palette?.measurementStroke ?? '#334155');
126
130
  const dimensionStandard = constructionDimensionStandard({
@@ -0,0 +1,3 @@
1
+ import { type WallNode } from '@pascal-app/core';
2
+ export declare function buildWallLengthPatch(node: WallNode, nextLength: number): Partial<WallNode>;
3
+ //# sourceMappingURL=length-patch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"length-patch.d.ts","sourceRoot":"","sources":["../../src/wall/length-patch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiD,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAE/F,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAW1F"}
@@ -0,0 +1,14 @@
1
+ import { getClampedWallCurveOffset, getWallCurveLength } from '@pascal-app/core';
2
+ export function buildWallLengthPatch(node, nextLength) {
3
+ const length = getWallCurveLength(node);
4
+ if (!length || !Number.isFinite(nextLength) || nextLength <= 0)
5
+ return {};
6
+ const ratio = nextLength / length;
7
+ return {
8
+ end: [
9
+ node.start[0] + (node.end[0] - node.start[0]) * ratio,
10
+ node.start[1] + (node.end[1] - node.start[1]) * ratio,
11
+ ],
12
+ ...(node.curveOffset ? { curveOffset: getClampedWallCurveOffset(node) * ratio } : {}),
13
+ };
14
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"move-tool.d.ts","sourceRoot":"","sources":["../../src/wall/move-tool.tsx"],"names":[],"mappings":"AAEA,OAAO,EAwBL,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AAqIzB,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CA4iBrD,CAAA;AAED,eAAe,YAAY,CAAA"}
1
+ {"version":3,"file":"move-tool.d.ts","sourceRoot":"","sources":["../../src/wall/move-tool.tsx"],"names":[],"mappings":"AAEA,OAAO,EAwBL,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AAqIzB,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CA+jBrD,CAAA;AAED,eAAe,YAAY,CAAA"}