@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,111 @@
1
+ import { getWallCurveFrameAt, getWallCurveLength, isCurvedWall, } from '@pascal-app/core';
2
+ import { buildOpeningCutoutShape, ensureRenderableGeometryAttributes } from '@pascal-app/viewer';
3
+ import { ExtrudeGeometry, Path, Shape, Vector2 } from 'three';
4
+ export function mapCurtainOpeningGeometryToWall(geometry, wall) {
5
+ if (!isCurvedWall(wall))
6
+ return;
7
+ const length = getWallCurveLength(wall);
8
+ if (length <= 1e-6)
9
+ return;
10
+ const angle = Math.atan2(wall.end[1] - wall.start[1], wall.end[0] - wall.start[0]);
11
+ const cos = Math.cos(angle);
12
+ const sin = Math.sin(angle);
13
+ const position = geometry.getAttribute('position');
14
+ for (let index = 0; index < position.count; index++) {
15
+ const along = position.getX(index);
16
+ const depth = position.getZ(index);
17
+ const clampedAlong = Math.max(0, Math.min(length, along));
18
+ const frame = getWallCurveFrameAt(wall, clampedAlong / length);
19
+ const extension = along - clampedAlong;
20
+ const x = frame.point.x + frame.tangent.x * extension + frame.normal.x * depth - wall.start[0];
21
+ const z = frame.point.y + frame.tangent.y * extension + frame.normal.y * depth - wall.start[1];
22
+ position.setXYZ(index, x * cos + z * sin, position.getY(index), -x * sin + z * cos);
23
+ }
24
+ position.needsUpdate = true;
25
+ geometry.boundingBox = null;
26
+ geometry.boundingSphere = null;
27
+ geometry.computeVertexNormals();
28
+ }
29
+ export function curtainOpeningProfile(opening, width) {
30
+ const bottom = opening.position[1] - opening.height / 2;
31
+ if (Math.cos(opening.rotation[1]) < 0 && opening.openingRadiusMode === 'individual') {
32
+ if (opening.type === 'window') {
33
+ const [topLeft, topRight, bottomRight, bottomLeft] = opening.openingCornerRadii;
34
+ opening = {
35
+ ...opening,
36
+ openingCornerRadii: [topRight, topLeft, bottomLeft, bottomRight],
37
+ };
38
+ }
39
+ else {
40
+ const [topLeft, topRight] = opening.openingTopRadii;
41
+ opening = { ...opening, openingTopRadii: [topRight, topLeft] };
42
+ }
43
+ }
44
+ const points = buildOpeningCutoutShape(opening, {
45
+ left: opening.position[0] - opening.width / 2,
46
+ right: opening.position[0] + opening.width / 2,
47
+ bottom,
48
+ top: bottom + opening.height,
49
+ })
50
+ .getPoints(4)
51
+ .filter((point, index, all) => index === 0 || point.distanceToSquared(all[index - 1]) > 1e-14);
52
+ if (points[0].distanceToSquared(points.at(-1)) < 1e-14)
53
+ points.pop();
54
+ const outer = points.map((point, index) => {
55
+ const previous = points[(index + points.length - 1) % points.length];
56
+ const next = points[(index + 1) % points.length];
57
+ const before = point.clone().sub(previous).normalize();
58
+ const after = next.clone().sub(point).normalize();
59
+ const n1 = new Vector2(before.y, -before.x);
60
+ const n2 = new Vector2(after.y, -after.x);
61
+ const result = point
62
+ .clone()
63
+ .addScaledVector(n1.clone().add(n2), width / Math.max(1e-6, 1 + n1.dot(n2)));
64
+ if (opening.type === 'door' && Math.abs(point.y - bottom) < 1e-7)
65
+ result.y = bottom;
66
+ return result;
67
+ });
68
+ return { inner: points, outer };
69
+ }
70
+ export function buildCurtainOpeningFrame(opening, width, depth) {
71
+ const { inner, outer } = curtainOpeningProfile(opening, width);
72
+ const outline = new Shape(outer);
73
+ outline.closePath();
74
+ let ring;
75
+ if (opening.type === 'door') {
76
+ // The door has jambs and a head; its threshold remains owned by the door.
77
+ ring = new Shape([...outer.slice(1), outer[0], inner[0], ...inner.slice(1).reverse()]);
78
+ ring.closePath();
79
+ }
80
+ else {
81
+ ring = new Shape(outer);
82
+ ring.closePath();
83
+ const hole = new Path([...inner].reverse());
84
+ hole.closePath();
85
+ ring.holes.push(hole);
86
+ }
87
+ const extrude = (shape, thickness) => {
88
+ const indexed = new ExtrudeGeometry(shape, {
89
+ depth: thickness,
90
+ bevelEnabled: false,
91
+ steps: 1,
92
+ });
93
+ indexed.translate(0, 0, -thickness / 2);
94
+ ensureRenderableGeometryAttributes(indexed);
95
+ if (!indexed.index)
96
+ return indexed;
97
+ const geometry = indexed.toNonIndexed();
98
+ indexed.dispose();
99
+ return geometry;
100
+ };
101
+ return { frame: extrude(ring, depth), cutter: extrude(outline, depth * 3), cutout: outer };
102
+ }
103
+ export function curtainProfileSpan(points, height) {
104
+ const xs = [];
105
+ for (let i = 0; i < points.length; i++) {
106
+ const a = points[i], b = points[(i + 1) % points.length];
107
+ if ((a.y <= height && b.y > height) || (b.y <= height && a.y > height))
108
+ xs.push(a.x + ((b.x - a.x) * (height - a.y)) / (b.y - a.y));
109
+ }
110
+ return xs.length >= 2 ? [Math.min(...xs), Math.max(...xs)] : null;
111
+ }
@@ -0,0 +1,3 @@
1
+ import { type WallNode } from '@pascal-app/core';
2
+ export declare function useCurtainPanelHighlight(wall: WallNode, left: number, right: number, bottom: number, top: number, enabled: boolean): void;
3
+ //# sourceMappingURL=curtain-panel-highlight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curtain-panel-highlight.d.ts","sourceRoot":"","sources":["../../src/wall/curtain-panel-highlight.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AAezB,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,OAAO,QA2GjB"}
@@ -0,0 +1,113 @@
1
+ import { getWallCurveFrameAt, getWallCurveLength, getWallThickness, sceneRegistry, } from '@pascal-app/core';
2
+ import { EDITOR_LAYER } from '@pascal-app/editor';
3
+ import { useEffect } from 'react';
4
+ import { BufferGeometry, DoubleSide, Float32BufferAttribute, Line, LineBasicMaterial, Mesh, MeshBasicMaterial, Vector3, } from 'three';
5
+ export function useCurtainPanelHighlight(wall, left, right, bottom, top, enabled) {
6
+ const wallId = wall.id;
7
+ const startX = wall.start[0];
8
+ const startZ = wall.start[1];
9
+ const endX = wall.end[0];
10
+ const endZ = wall.end[1];
11
+ const curveOffset = wall.curveOffset;
12
+ const thickness = getWallThickness(wall);
13
+ useEffect(() => {
14
+ if (!enabled)
15
+ return;
16
+ const curveWall = {
17
+ start: [startX, startZ],
18
+ end: [endX, endZ],
19
+ curveOffset,
20
+ };
21
+ const host = sceneRegistry.nodes.get(wallId);
22
+ if (!host)
23
+ return;
24
+ const length = getWallCurveLength(curveWall);
25
+ if (length <= 0)
26
+ return;
27
+ const angle = Math.atan2(endZ - startZ, endX - startX);
28
+ const cos = Math.cos(angle), sin = Math.sin(angle);
29
+ const points = [];
30
+ for (const y of [bottom, top]) {
31
+ const edge = [];
32
+ for (let i = 0; i <= 24; i++) {
33
+ const frame = getWallCurveFrameAt(curveWall, (left + ((right - left) * i) / 24) / length);
34
+ const offset = thickness / 2 + 0.005;
35
+ const x = frame.point.x + frame.normal.x * offset - startX;
36
+ const z = frame.point.y + frame.normal.y * offset - startZ;
37
+ edge.push(new Vector3(x * cos + z * sin, y, -x * sin + z * cos));
38
+ }
39
+ points.push(...(y === bottom ? edge : edge.reverse()));
40
+ }
41
+ points.push(points[0].clone());
42
+ const geometry = new BufferGeometry().setFromPoints(points);
43
+ const material = new LineBasicMaterial({
44
+ color: '#fb923c',
45
+ depthTest: false,
46
+ depthWrite: false,
47
+ });
48
+ const outline = new Line(geometry, material);
49
+ outline.layers.set(EDITOR_LAYER);
50
+ outline.raycast = () => { };
51
+ const vertices = [];
52
+ for (let i = 0; i < 24; i++) {
53
+ for (const index of [i, i + 1, 49 - i, i + 1, 48 - i, 49 - i]) {
54
+ vertices.push(...points[index].toArray());
55
+ }
56
+ }
57
+ const fillGeometry = new BufferGeometry();
58
+ fillGeometry.setAttribute('position', new Float32BufferAttribute(vertices, 3));
59
+ const fillMaterial = new MeshBasicMaterial({
60
+ color: '#fb923c',
61
+ transparent: true,
62
+ opacity: 0.6,
63
+ side: DoubleSide,
64
+ depthTest: false,
65
+ depthWrite: false,
66
+ });
67
+ const fill = new Mesh(fillGeometry, fillMaterial);
68
+ fill.layers.set(EDITOR_LAYER);
69
+ fill.raycast = () => { };
70
+ let scene = host;
71
+ while (scene.parent)
72
+ scene = scene.parent;
73
+ outline.matrixAutoUpdate = false;
74
+ fill.matrixAutoUpdate = false;
75
+ scene.add(outline, fill);
76
+ let frameId = 0;
77
+ const sync = () => {
78
+ const current = sceneRegistry.nodes.get(wallId);
79
+ outline.visible = fill.visible = Boolean(current?.visible);
80
+ if (current) {
81
+ current.updateWorldMatrix(true, false);
82
+ outline.matrix.copy(current.matrixWorld);
83
+ fill.matrix.copy(current.matrixWorld);
84
+ outline.matrixWorld.copy(current.matrixWorld);
85
+ fill.matrixWorld.copy(current.matrixWorld);
86
+ }
87
+ frameId = requestAnimationFrame(sync);
88
+ };
89
+ sync();
90
+ return () => {
91
+ cancelAnimationFrame(frameId);
92
+ outline.removeFromParent();
93
+ fill.removeFromParent();
94
+ fillGeometry.dispose();
95
+ fillMaterial.dispose();
96
+ geometry.dispose();
97
+ material.dispose();
98
+ };
99
+ }, [
100
+ enabled,
101
+ wallId,
102
+ startX,
103
+ startZ,
104
+ endX,
105
+ endZ,
106
+ curveOffset,
107
+ thickness,
108
+ left,
109
+ right,
110
+ bottom,
111
+ top,
112
+ ]);
113
+ }
@@ -0,0 +1,3 @@
1
+ import type { WallGeometryAdapter } from '@pascal-app/viewer';
2
+ export declare const curtainWallGeometryAdapter: WallGeometryAdapter;
3
+ //# sourceMappingURL=curtain-wall-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curtain-wall-adapter.d.ts","sourceRoot":"","sources":["../../src/wall/curtain-wall-adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAW7D,eAAO,MAAM,0BAA0B,EAAE,mBAgDxC,CAAA"}
@@ -0,0 +1,55 @@
1
+ import { useLiveNodeOverrides } from '@pascal-app/core';
2
+ import { buildCurtainWallGeometry } from './curtain-wall-geometry';
3
+ import { buildCurtainWallShadowGeometry, CURTAIN_WALL_SHADOW_NAME } from './curtain-wall-shadow';
4
+ function isOpaque(material) {
5
+ return material !== undefined && (!material.transparent || material.opacity >= 1);
6
+ }
7
+ const deferredShadowWalls = new Set();
8
+ export const curtainWallGeometryAdapter = {
9
+ prepareChildren(wall, children, context) {
10
+ if (wall.wallType !== 'curtain') {
11
+ return { envelopeChildren: [...children], renderChildren: [...children] };
12
+ }
13
+ const renderChildren = children.map((child) => {
14
+ if ((child.type !== 'door' && child.type !== 'window') || child.openingShape === 'rectangle')
15
+ return child;
16
+ const override = useLiveNodeOverrides.getState().get(child.id);
17
+ const liveBounds = context.isLive(child.id) &&
18
+ (!override || 'position' in override || 'width' in override || 'height' in override);
19
+ return liveBounds ? { ...child, openingShape: 'rectangle' } : child;
20
+ });
21
+ return {
22
+ envelopeChildren: renderChildren.filter((child) => child.type !== 'door' && child.type !== 'window'),
23
+ renderChildren,
24
+ };
25
+ },
26
+ buildGeometry(wall, envelope, children) {
27
+ if (wall.wallType !== 'curtain')
28
+ return envelope;
29
+ const livePreview = children.some((child) => (child.type === 'door' || child.type === 'window') &&
30
+ useLiveNodeOverrides.getState().get(child.id) !== undefined);
31
+ const deferShadow = children.some((child) => {
32
+ if (child.type !== 'door' && child.type !== 'window')
33
+ return false;
34
+ const metadata = useLiveNodeOverrides.getState().get(child.id)?.metadata;
35
+ return metadata?.deferParentRebuild === true;
36
+ });
37
+ if (deferShadow)
38
+ deferredShadowWalls.add(wall.id);
39
+ else
40
+ deferredShadowWalls.delete(wall.id);
41
+ return buildCurtainWallGeometry(wall, envelope, children, livePreview);
42
+ },
43
+ syncAuxiliaryGeometry(wall, mesh, geometry) {
44
+ if (wall.wallType !== 'curtain')
45
+ return;
46
+ if (deferredShadowWalls.has(wall.id))
47
+ return;
48
+ const shadowMesh = mesh.getObjectByName(CURTAIN_WALL_SHADOW_NAME);
49
+ if (!shadowMesh)
50
+ return;
51
+ const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
52
+ shadowMesh.geometry.dispose();
53
+ shadowMesh.geometry = buildCurtainWallShadowGeometry(geometry, isOpaque(materials[1]));
54
+ },
55
+ };
@@ -0,0 +1,3 @@
1
+ import { type AnyNode, type FloorplanGeometry, type WallNode } from '@pascal-app/core';
2
+ export declare function buildCurtainWallFloorplan(wall: WallNode, height: number, stroke: string, children?: readonly AnyNode[]): FloorplanGeometry[];
3
+ //# sourceMappingURL=curtain-wall-floorplan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curtain-wall-floorplan.d.ts","sourceRoot":"","sources":["../../src/wall/curtain-wall-floorplan.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,iBAAiB,EAKtB,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AAIzB,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,SAAS,OAAO,EAAO,GAChC,iBAAiB,EAAE,CAwFrB"}
@@ -0,0 +1,91 @@
1
+ import { getCurtainWallConfig, getWallCurveFrameAt, getWallCurveLength, getWallThickness, } from '@pascal-app/core';
2
+ import { curtainOpeningProfile, curtainProfileSpan } from './curtain-opening-frame';
3
+ import { buildCurtainWallLayout } from './curtain-wall-layout';
4
+ export function buildCurtainWallFloorplan(wall, height, stroke, children = []) {
5
+ const length = getWallCurveLength(wall);
6
+ if (length <= 1e-6)
7
+ return [];
8
+ const config = getCurtainWallConfig(wall);
9
+ const cutHeight = Math.min(1.2, height * 0.5);
10
+ const openings = children
11
+ .filter((child) => child.type === 'door' || child.type === 'window')
12
+ .map((opening) => ({
13
+ opening,
14
+ profile: curtainOpeningProfile(opening, config.perimeterWidth),
15
+ framed: opening.openingShape !== 'rectangle',
16
+ }));
17
+ const pieces = buildCurtainWallLayout(length, height, getWallThickness(wall), config, children.filter((child) => child.type === 'door' || child.type === 'window'))
18
+ .filter((piece) => piece.bottom <= cutHeight && piece.top > cutHeight)
19
+ .flatMap((piece) => {
20
+ let runs = [piece];
21
+ for (const { profile, framed } of openings) {
22
+ const span = curtainProfileSpan(framed ? profile.outer : profile.inner, cutHeight);
23
+ if (!span)
24
+ continue;
25
+ const [left, right] = span;
26
+ runs = runs.flatMap((run) => {
27
+ if (right <= run.left || left >= run.right)
28
+ return [run];
29
+ return [
30
+ ...(left > run.left ? [{ ...run, right: left }] : []),
31
+ ...(right < run.right ? [{ ...run, left: right }] : []),
32
+ ];
33
+ });
34
+ }
35
+ return runs;
36
+ });
37
+ for (const { profile, framed } of openings) {
38
+ if (!framed)
39
+ continue;
40
+ const outer = curtainProfileSpan(profile.outer, cutHeight);
41
+ const inner = curtainProfileSpan(profile.inner, cutHeight);
42
+ if (!outer)
43
+ continue;
44
+ const spans = inner
45
+ ? [
46
+ [outer[0], inner[0]],
47
+ [inner[1], outer[1]],
48
+ ]
49
+ : [outer];
50
+ for (const [left, right] of spans) {
51
+ if (right - left <= 1e-6)
52
+ continue;
53
+ pieces.push({
54
+ left: Math.max(0, left),
55
+ right: Math.min(length, right),
56
+ bottom: 0,
57
+ top: height,
58
+ front: getWallThickness(wall) / 2,
59
+ back: -getWallThickness(wall) / 2,
60
+ role: 'frame',
61
+ });
62
+ }
63
+ }
64
+ return pieces.map((piece) => {
65
+ const points = [];
66
+ for (const offset of [piece.front, piece.back]) {
67
+ const side = [];
68
+ for (let i = 0; i <= 8; i++) {
69
+ const along = piece.left + ((piece.right - piece.left) * i) / 8;
70
+ const frame = getWallCurveFrameAt(wall, along / length);
71
+ side.push([
72
+ frame.point.x + frame.normal.x * offset,
73
+ frame.point.y + frame.normal.y * offset,
74
+ ]);
75
+ }
76
+ points.push(...(offset === piece.front ? side : side.reverse()));
77
+ }
78
+ return {
79
+ kind: 'polygon',
80
+ points,
81
+ fill: piece.role === 'glass'
82
+ ? config.glassColor
83
+ : piece.role === 'frame'
84
+ ? config.frameColor
85
+ : config.solidColor,
86
+ stroke,
87
+ strokeWidth: 0.008,
88
+ pointerEvents: 'none',
89
+ };
90
+ });
91
+ }
@@ -0,0 +1,4 @@
1
+ import { type AnyNode, type WallNode } from '@pascal-app/core';
2
+ import { BufferGeometry } from 'three';
3
+ export declare function buildCurtainWallGeometry(wall: WallNode, envelope: BufferGeometry, children?: readonly AnyNode[], livePreview?: boolean): BufferGeometry;
4
+ //# sourceMappingURL=curtain-wall-geometry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curtain-wall-geometry.d.ts","sourceRoot":"","sources":["../../src/wall/curtain-wall-geometry.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAMZ,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AASzB,OAAO,EAAE,cAAc,EAA0B,MAAM,OAAO,CAAA;AAkD9D,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,cAAc,EACxB,QAAQ,GAAE,SAAS,OAAO,EAAO,EACjC,WAAW,UAAQ,GAClB,cAAc,CA+MhB"}
@@ -0,0 +1,226 @@
1
+ import { getCurtainWallConfig, getWallCurveFrameAt, getWallCurveLength, getWallThickness, isCurvedWall, } from '@pascal-app/core';
2
+ import { Brush, Evaluator, ensureRenderableGeometryAttributes, INTERSECTION, prepareBrushForCSG, SUBTRACTION, } from '@pascal-app/viewer';
3
+ import { BufferGeometry, ExtrudeGeometry, Shape } from 'three';
4
+ import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
5
+ import { buildCurtainOpeningFrame, mapCurtainOpeningGeometryToWall } from './curtain-opening-frame';
6
+ import { buildCurtainWallLayout, curtainGridPositions, } from './curtain-wall-layout';
7
+ import { buildStraightCurtainPieces, buildStraightCurtainPiecesWithCutouts, } from './curtain-wall-piece-geometry';
8
+ function pieceGeometry(wall, piece, length, base) {
9
+ const angle = Math.atan2(wall.end[1] - wall.start[1], wall.end[0] - wall.start[0]);
10
+ const cos = Math.cos(angle), sin = Math.sin(angle);
11
+ const samples = isCurvedWall(wall)
12
+ ? Math.max(1, Math.ceil(((piece.right - piece.left) / length) * 48))
13
+ : 1;
14
+ const points = [];
15
+ for (const offset of [piece.front, piece.back]) {
16
+ const side = [];
17
+ for (let i = 0; i <= samples; i++) {
18
+ const along = piece.left + ((piece.right - piece.left) * i) / samples;
19
+ if (!isCurvedWall(wall)) {
20
+ side.push([along, -offset]);
21
+ continue;
22
+ }
23
+ const frame = getWallCurveFrameAt(wall, along / length);
24
+ const x = frame.point.x + frame.normal.x * offset - wall.start[0];
25
+ const z = frame.point.y + frame.normal.y * offset - wall.start[1];
26
+ side.push([x * cos + z * sin, -(-x * sin + z * cos)]);
27
+ }
28
+ points.push(...(offset === piece.front ? side : side.reverse()));
29
+ }
30
+ const shape = new Shape();
31
+ shape.moveTo(...points[0]);
32
+ for (const point of points.slice(1))
33
+ shape.lineTo(...point);
34
+ shape.closePath();
35
+ const geometry = new ExtrudeGeometry(shape, {
36
+ depth: piece.top - piece.bottom,
37
+ bevelEnabled: false,
38
+ });
39
+ geometry.rotateX(-Math.PI / 2);
40
+ geometry.translate(0, base + piece.bottom, 0);
41
+ ensureRenderableGeometryAttributes(geometry);
42
+ return geometry;
43
+ }
44
+ export function buildCurtainWallGeometry(wall, envelope, children = [], livePreview = false) {
45
+ const length = getWallCurveLength(wall);
46
+ envelope.computeBoundingBox();
47
+ const bounds = envelope.boundingBox;
48
+ if (!bounds || bounds.isEmpty() || length <= 1e-6)
49
+ return envelope;
50
+ const pieces = buildCurtainWallLayout(length, bounds.max.y - bounds.min.y, getWallThickness(wall), getCurtainWallConfig(wall), children.filter((child) => child.type === 'door' || child.type === 'window'), bounds.min.y);
51
+ const shapedFrames = children
52
+ .filter((child) => (child.type === 'window' || child.type === 'door') && child.openingShape !== 'rectangle')
53
+ .map((opening) => {
54
+ const geometry = buildCurtainOpeningFrame(opening, getCurtainWallConfig(wall).perimeterWidth, getWallThickness(wall));
55
+ mapCurtainOpeningGeometryToWall(geometry.frame, wall);
56
+ mapCurtainOpeningGeometryToWall(geometry.cutter, wall);
57
+ return geometry;
58
+ });
59
+ const hasRectangularOpenings = children.some((child) => (child.type === 'window' || child.type === 'door') && child.openingShape === 'rectangle');
60
+ const evaluator = new Evaluator();
61
+ evaluator.attributes = ['position', 'normal', 'uv', 'uv2'];
62
+ evaluator.useGroups = false;
63
+ const shell = new Brush(envelope);
64
+ const position = envelope.getAttribute('position');
65
+ const near = (a, b) => Math.abs(a - b) < 1e-6;
66
+ const halfDepth = getWallThickness(wall) / 2;
67
+ const rectangular = !isCurvedWall(wall) &&
68
+ Array.from({ length: position.count }, (_, i) => i).every((i) => (near(position.getX(i), 0) || near(position.getX(i), length)) &&
69
+ (near(position.getY(i), bounds.min.y) || near(position.getY(i), bounds.max.y)) &&
70
+ (near(position.getZ(i), -halfDepth) || near(position.getZ(i), halfDepth)));
71
+ if (rectangular) {
72
+ for (const { frame } of shapedFrames) {
73
+ const framePosition = frame.getAttribute('position');
74
+ for (let index = 0; index < framePosition.count; index++) {
75
+ framePosition.setX(index, Math.max(bounds.min.x, Math.min(bounds.max.x, framePosition.getX(index))));
76
+ framePosition.setY(index, Math.max(bounds.min.y, Math.min(bounds.max.y, framePosition.getY(index))));
77
+ }
78
+ framePosition.needsUpdate = true;
79
+ frame.computeVertexNormals();
80
+ }
81
+ }
82
+ if (!rectangular)
83
+ prepareBrushForCSG(shell);
84
+ const leftEnvelopeXs = [];
85
+ const rightEnvelopeXs = [];
86
+ if (!rectangular && !isCurvedWall(wall)) {
87
+ for (let index = 0; index < position.count; index++) {
88
+ const x = position.getX(index);
89
+ (x < length / 2 ? leftEnvelopeXs : rightEnvelopeXs).push(x);
90
+ }
91
+ }
92
+ const safeLeft = leftEnvelopeXs.length ? Math.max(...leftEnvelopeXs) : 0;
93
+ const safeRight = rightEnvelopeXs.length ? Math.min(...rightEnvelopeXs) : length;
94
+ const xs = curtainGridPositions(length, getCurtainWallConfig(wall).columns);
95
+ const ys = curtainGridPositions(bounds.max.y - bounds.min.y, getCurtainWallConfig(wall).rows);
96
+ const cutRegions = shapedFrames.map(({ cutter }) => {
97
+ cutter.computeBoundingBox();
98
+ const box = cutter.boundingBox;
99
+ // Keep every fragment of a pane in the same partition to avoid new glass seams.
100
+ const lower = (positions, value) => [...positions].reverse().find((p) => p <= value) ?? positions[0];
101
+ const upper = (positions, value) => positions.find((p) => p >= value) ?? positions.at(-1);
102
+ return {
103
+ left: lower(xs, box.min.x),
104
+ right: upper(xs, box.max.x),
105
+ bottom: lower(ys, box.min.y - bounds.min.y),
106
+ top: upper(ys, box.max.y - bounds.min.y),
107
+ };
108
+ });
109
+ const results = [];
110
+ const roles = ['frame', 'glass', 'solid'];
111
+ try {
112
+ for (const [materialIndex, role] of roles.entries()) {
113
+ const allRolePieces = pieces.filter((piece) => piece.role === role);
114
+ const nearCut = (piece) => isCurvedWall(wall) ||
115
+ (!rectangular && (piece.left < safeLeft - 1e-6 || piece.right > safeRight + 1e-6)) ||
116
+ cutRegions.some((region) => piece.left < region.right &&
117
+ piece.right > region.left &&
118
+ piece.bottom < region.top &&
119
+ piece.top > region.bottom);
120
+ const partitionPieces = !rectangular || cutRegions.length > 0;
121
+ const rolePieces = allRolePieces.filter(nearCut);
122
+ const untouchedPieces = partitionPieces
123
+ ? allRolePieces.filter((piece) => !nearCut(piece))
124
+ : [];
125
+ const builtPieces = partitionPieces ? rolePieces : allRolePieces;
126
+ if (!allRolePieces.length && !(role === 'frame' && shapedFrames.length))
127
+ continue;
128
+ let merged = null;
129
+ if (!isCurvedWall(wall) && builtPieces.length) {
130
+ const straightPieces = builtPieces.map((piece) => ({
131
+ ...piece,
132
+ left: piece.left === 0 ? Math.min(0, bounds.min.x) : piece.left,
133
+ right: piece.right === length ? Math.max(length, bounds.max.x) : piece.right,
134
+ }));
135
+ merged =
136
+ rectangular && shapedFrames.length
137
+ ? buildStraightCurtainPiecesWithCutouts(straightPieces, bounds.min.y, shapedFrames.map(({ cutout }) => cutout))
138
+ : buildStraightCurtainPieces(straightPieces, bounds.min.y, livePreview ? false : hasRectangularOpenings || shapedFrames.length > 0);
139
+ }
140
+ else if (builtPieces.length) {
141
+ const sources = rolePieces.map((piece) => pieceGeometry(wall, piece, length, bounds.min.y));
142
+ merged = mergeGeometries(sources, false);
143
+ for (const source of sources)
144
+ source.dispose();
145
+ }
146
+ if (merged && !rectangular) {
147
+ for (const opening of shapedFrames) {
148
+ if (!merged.getAttribute('position').count)
149
+ break;
150
+ const brush = new Brush(merged);
151
+ const cutter = new Brush(opening.cutter);
152
+ prepareBrushForCSG(brush);
153
+ prepareBrushForCSG(cutter);
154
+ const cut = evaluator.evaluate(brush, cutter, SUBTRACTION).geometry;
155
+ merged.dispose();
156
+ merged = cut.index ? cut.toNonIndexed() : cut;
157
+ if (merged !== cut)
158
+ cut.dispose();
159
+ }
160
+ }
161
+ if (role === 'frame' && shapedFrames.length) {
162
+ const frames = shapedFrames.map(({ frame }) => frame);
163
+ const combined = mergeGeometries(merged ? [merged, ...frames] : frames, false);
164
+ merged?.dispose();
165
+ if (!combined)
166
+ continue;
167
+ merged = combined;
168
+ }
169
+ if (merged && !rectangular) {
170
+ const brush = new Brush(merged);
171
+ prepareBrushForCSG(brush);
172
+ try {
173
+ // The existing wall envelope owns miter joins, support profiles, and all hosted cuts.
174
+ const clipped = evaluator.evaluate(brush, shell, INTERSECTION).geometry;
175
+ const geometry = clipped.index ? clipped.toNonIndexed() : clipped;
176
+ if (geometry !== clipped)
177
+ clipped.dispose();
178
+ merged.dispose();
179
+ merged = geometry;
180
+ }
181
+ catch (error) {
182
+ merged.dispose();
183
+ throw error;
184
+ }
185
+ }
186
+ if (untouchedPieces.length) {
187
+ const untouched = buildStraightCurtainPieces(untouchedPieces, bounds.min.y, false);
188
+ if (merged) {
189
+ const combined = mergeGeometries([merged, untouched], false);
190
+ untouched.dispose();
191
+ merged.dispose();
192
+ if (!combined)
193
+ continue;
194
+ merged = combined;
195
+ }
196
+ else {
197
+ merged = untouched;
198
+ }
199
+ }
200
+ if (!merged)
201
+ continue;
202
+ merged.clearGroups();
203
+ merged.addGroup(0, merged.getAttribute('position').count, materialIndex);
204
+ results.push(merged);
205
+ }
206
+ if (!results.length)
207
+ return new BufferGeometry();
208
+ const combined = mergeGeometries(results, false) ?? new BufferGeometry();
209
+ let start = 0;
210
+ for (const result of results) {
211
+ const count = result.getAttribute('position').count;
212
+ combined.addGroup(start, count, result.groups[0].materialIndex);
213
+ start += count;
214
+ }
215
+ return combined;
216
+ }
217
+ finally {
218
+ for (const opening of shapedFrames) {
219
+ opening.frame.dispose();
220
+ opening.cutter.dispose();
221
+ }
222
+ envelope.dispose();
223
+ for (const result of results)
224
+ result.dispose();
225
+ }
226
+ }
@@ -0,0 +1,16 @@
1
+ import type { CurtainGrid, CurtainPanelType, CurtainWallConfig, DoorNode } from '@pascal-app/core';
2
+ export declare function curtainGridPositions(length: number, grid: CurtainGrid): number[];
3
+ export type CurtainWallPiece = {
4
+ left: number;
5
+ right: number;
6
+ bottom: number;
7
+ top: number;
8
+ front: number;
9
+ back: number;
10
+ role: 'frame' | 'glass' | 'solid';
11
+ };
12
+ export declare function curtainPanelType(config: CurtainWallConfig, column: number, row: number, rowCount: number): CurtainPanelType;
13
+ export declare function buildCurtainWallLayout(length: number, height: number, depth: number, config: CurtainWallConfig, openings?: readonly (Pick<DoorNode, 'position' | 'width' | 'height' | 'openingShape'> & {
14
+ type: 'door' | 'window';
15
+ })[], baseElevation?: number): CurtainWallPiece[];
16
+ //# sourceMappingURL=curtain-wall-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curtain-wall-layout.d.ts","sourceRoot":"","sources":["../../src/wall/curtain-wall-layout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAElG,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,EAAE,CAqBhF;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAA;CAClC,CAAA;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,iBAAiB,EACzB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACf,gBAAgB,CASlB;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,iBAAiB,EACzB,QAAQ,GAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,cAAc,CAAC,GAAG;IACrF,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAA;CACxB,CAAC,EAAO,EACT,aAAa,SAAI,GAChB,gBAAgB,EAAE,CAqMpB"}