@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
@@ -5,6 +5,7 @@ import { ActionButton, ActionGroup, cn, PanelSection, PanelWrapper, SliderContro
5
5
  import { useViewer } from '@pascal-app/viewer';
6
6
  import { Move, Trash2 } from 'lucide-react';
7
7
  import { useCallback } from 'react';
8
+ import { planColumnEdit } from './hosted-resize';
8
9
  const SELECT_CLASS = 'h-10 w-full rounded-lg border border-border/50 bg-[#2C2C2E] px-3 text-sm text-foreground outline-none transition-colors hover:bg-[#3e3e3e] focus:ring-1 focus:ring-border';
9
10
  const MANAGED_LEAN_TO_LAYOUT_FIELDS = new Set([
10
11
  'position',
@@ -246,7 +247,7 @@ export default function ColumnPanel() {
246
247
  const selectedId = useViewer((s) => s.selection.selectedIds[0]);
247
248
  const selectedCount = useViewer((s) => s.selection.selectedIds.length);
248
249
  const setSelection = useViewer((s) => s.setSelection);
249
- const updateNode = useScene((s) => s.updateNode);
250
+ const updateNodes = useScene((s) => s.updateNodes);
250
251
  const deleteNode = useScene((s) => s.deleteNode);
251
252
  const setMovingNode = useEditor((s) => s.setMovingNode);
252
253
  const node = useScene((s) => selectedId ? s.nodes[selectedId] : undefined);
@@ -256,8 +257,10 @@ export default function ColumnPanel() {
256
257
  const nextUpdates = node && isManagedLeanToPost(node) ? filterManagedLeanToLayoutUpdates(updates) : updates;
257
258
  if (Object.keys(nextUpdates).length === 0)
258
259
  return;
259
- updateNode(selectedId, nextUpdates);
260
- }, [node, selectedId, updateNode]);
260
+ const updatesToApply = planColumnEdit(selectedId, nextUpdates);
261
+ if (updatesToApply)
262
+ updateNodes(updatesToApply.map(([id, data]) => ({ id, data })));
263
+ }, [node, selectedId, updateNodes]);
261
264
  const handleClose = useCallback(() => {
262
265
  setSelection({ selectedIds: [] });
263
266
  }, [setSelection]);
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/column/renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,UAAU,EAKhB,MAAM,kBAAkB,CAAA;AAwwEzB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,GAAI,UAAU;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,gCA6C3D,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,mBAAmB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,gCAgDrE,CAAA;AAED,eAAe,cAAc,CAAA"}
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/column/renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,UAAU,EAOhB,MAAM,kBAAkB,CAAA;AA6nEzB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,GAAI,UAAU;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,gCA6C3D,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,mBAAmB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,gCAgErE,CAAA;AAED,eAAe,cAAc,CAAA"}
@@ -1,9 +1,10 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useLiveNodeOverrides, useLiveTransforms, useRegistry, useScene, } from '@pascal-app/core';
4
- import { baseMaterial, createColumnBoxGeometry, createColumnCylinderGeometry, createColumnSphereGeometry, createColumnTorusGeometry, createMaterial, createMaterialFromPresetRef, createSurfaceRoleMaterial, resolveMaterialRef, resolveSlotDefaultMaterial, useNodeEvents, useViewer, } from '@pascal-app/viewer';
3
+ import { collectDescendants, createSceneApi, useLiveNodeOverrides, useLiveTransforms, useRegistry, useScene, } from '@pascal-app/core';
4
+ import { baseMaterial, createColumnBoxGeometry, createColumnCylinderGeometry, createColumnSphereGeometry, createColumnTorusGeometry, createMaterial, createMaterialFromPresetRef, createSurfaceRoleMaterial, NodeRenderer, resolveMaterialRef, resolveSlotDefaultMaterial, useNodeEvents, useViewer, } from '@pascal-app/viewer';
5
5
  import { createContext, useContext, useEffect, useMemo, useRef } from 'react';
6
6
  import { BufferGeometry, Float32BufferAttribute } from 'three';
7
+ import { columnCapitalBlocks, columnShaftLayout, getSegments, getShaftScaleAt, getShaftSegmentCount, getShaftTwistRadians, } from './shape';
7
8
  import { COLUMN_BASE_DEFAULT, COLUMN_CAPITAL_DEFAULT, COLUMN_FRAME_DEFAULT, COLUMN_SHAFT_DEFAULT, } from './slots';
8
9
  const DEFAULT_COLUMN_MATERIAL = baseMaterial();
9
10
  const DEFAULT_COLUMN_SLOT_MATERIALS = createSingleColumnMaterialMap(DEFAULT_COLUMN_MATERIAL);
@@ -99,51 +100,6 @@ function createColumnSlotMaterials({ colorPreset, material, materialPreset, node
99
100
  }),
100
101
  };
101
102
  }
102
- function getSegments(node) {
103
- if (node.crossSection === 'octagonal')
104
- return 8;
105
- if (node.crossSection === 'sixteen-sided')
106
- return 16;
107
- return 32;
108
- }
109
- function getShaftProfile(node) {
110
- return node.shaftProfile ?? (node.shaftTaper > 0 ? 'tapered' : 'straight');
111
- }
112
- function getShaftSegmentCount(node) {
113
- const shaftProfile = getShaftProfile(node);
114
- const shaftTaper = node.shaftTaper ?? 0;
115
- const hasTwist = Math.abs(node.shaftTwistStep ?? 0) > 0.001;
116
- return Math.max(hasTwist ? 4 : 1, shaftProfile === 'straight' && shaftTaper <= 0 && !hasTwist
117
- ? 1
118
- : (node.shaftSegmentCount ?? (hasTwist ? 12 : 24)));
119
- }
120
- function getShaftTwistRadians(node, index) {
121
- return ((node.shaftTwistStep ?? 0) * Math.PI * index) / 180;
122
- }
123
- function getShaftScaleAt(node, t) {
124
- const shaftProfile = getShaftProfile(node);
125
- const shaftTaper = Math.min(node.shaftTaper ?? 0, 0.85);
126
- const startScale = node.shaftStartScale ?? 0.72;
127
- const endScale = node.shaftEndScale ?? startScale;
128
- const shaftBulge = node.shaftBulge ??
129
- (shaftProfile === 'bulged'
130
- ? 0.16
131
- : shaftProfile === 'baluster'
132
- ? 0.2
133
- : shaftProfile === 'hourglass'
134
- ? 0.18
135
- : 0);
136
- const taperedScale = 1 - shaftTaper * t;
137
- const linearScale = (startScale + (endScale - startScale) * t) * taperedScale;
138
- const bulgeCurve = Math.sin(Math.PI * t);
139
- const hourglassCurve = Math.abs(t - 0.5) * 2;
140
- const profileScale = shaftProfile === 'bulged' || shaftProfile === 'baluster'
141
- ? linearScale + shaftBulge * bulgeCurve
142
- : shaftProfile === 'hourglass'
143
- ? linearScale - shaftBulge * (1 - hourglassCurve)
144
- : linearScale;
145
- return Math.max(0.1, profileScale);
146
- }
147
103
  function clamp(value, min, max) {
148
104
  return Math.min(max, Math.max(min, value));
149
105
  }
@@ -868,58 +824,15 @@ function LeafCarvings({ node, capitalY, capitalHeight, }) {
868
824
  })) }));
869
825
  }
870
826
  function Capital({ node, y, height }) {
871
- if (height <= 0)
827
+ const blocks = columnCapitalBlocks(node, y, height);
828
+ if (!blocks.length)
872
829
  return null;
873
- const capitalStyle = node.capitalStyle ?? 'simple';
874
- if (capitalStyle === 'none')
875
- return null;
876
- if (capitalStyle === 'south-indian-bracket' || capitalStyle === 'wood-bracket') {
877
- const tierCount = Math.max(1, node.bracketTierCount ?? 3);
878
- const tierHeight = height / tierCount;
879
- const bracketDepth = node.bracketDepth ?? 0.35;
880
- return (_jsxs("group", { children: [Array.from({ length: tierCount }, (_, index) => {
881
- const t = index / Math.max(1, tierCount - 1);
882
- const scale = (node.capitalWidthScale ?? 1.6) + t * 0.32;
883
- return (_jsx(SquareBlock, { depth: node.depth * scale + bracketDepth * t, height: tierHeight, width: node.width * scale + bracketDepth * t, y: y + index * tierHeight }, index));
884
- }), Array.from({ length: node.pendantCount ?? 0 }, (_, index) => {
885
- const count = Math.max(1, node.pendantCount ?? 0);
886
- const angle = (index / count) * Math.PI * 2;
830
+ return (_jsxs("group", { children: [blocks.map((block, index) => block.kind === 'box' ? (_jsx(SquareBlock, { y: block.y, height: block.height, width: block.width, depth: block.depth }, index)) : block.kind === 'oval' ? (_jsx(OvalBlock, { y: block.y, height: block.height, width: block.width, depth: block.depth }, index)) : block.kind === 'round' ? (_jsx(RoundBlock, { y: block.y, height: block.height, radius: block.radius, segments: getSegments(node) }, index)) : (_jsx(ColumnBlock, { node: node, y: block.y, height: block.height, scale: block.scale }, index))), (node.capitalStyle === 'south-indian-bracket' || node.capitalStyle === 'wood-bracket') &&
831
+ Array.from({ length: node.pendantCount ?? 0 }, (_, index) => {
832
+ const angle = (index / Math.max(1, node.pendantCount ?? 0)) * Math.PI * 2;
887
833
  const distance = Math.max(node.width, node.depth) * 0.56;
888
834
  return (_jsx(MappedCone, { height: height * 0.28, position: [Math.cos(angle) * distance, y - height * 0.1, Math.sin(angle) * distance], radiusX: 0.035, rotation: [0, 0, 0], segments: 6 }, index));
889
- })] }));
890
- }
891
- if (capitalStyle === 'rounded' || capitalStyle === 'doric') {
892
- const topWidth = node.width * (node.capitalWidthScale ?? 1.34);
893
- const topDepth = node.depth * (node.capitalDepthScale ?? node.capitalWidthScale ?? 1.34);
894
- return (_jsxs("group", { children: [_jsx(OvalBlock, { depth: topDepth * 0.72, height: height * 0.24, segments: 32, width: topWidth * 0.72, y: y }), _jsx(OvalBlock, { depth: topDepth * 0.92, height: height * 0.32, segments: 32, width: topWidth * 0.92, y: y + height * 0.24 }), _jsx(SquareBlock, { depth: topDepth, height: height * 0.44, width: topWidth, y: y + height * 0.56 })] }));
895
- }
896
- if (capitalStyle === 'stepped') {
897
- const widthScale = node.capitalWidthScale ?? 1.46;
898
- const depthScale = node.capitalDepthScale ?? widthScale;
899
- const tierCount = Math.max(3, node.capitalTierCount ?? 3);
900
- const tierHeight = height / tierCount;
901
- const stepSpread = node.capitalStepSpread ?? 0.42;
902
- return (_jsx("group", { children: Array.from({ length: tierCount }, (_, index) => {
903
- const t = index / Math.max(1, tierCount - 1);
904
- const widthScaleAt = Math.max(0.5, widthScale - (1 - t) * stepSpread);
905
- const depthScaleAt = Math.max(0.5, depthScale - (1 - t) * stepSpread);
906
- return (_jsx(SquareBlock, { depth: node.depth * depthScaleAt, height: tierHeight * 1.01, width: node.width * widthScaleAt, y: y + index * tierHeight }, index));
907
- }) }));
908
- }
909
- if (capitalStyle === 'volute' ||
910
- capitalStyle === 'ionic-volute' ||
911
- capitalStyle === 'leaf-carved' ||
912
- capitalStyle === 'corinthian-leaf') {
913
- const topWidth = node.width * (node.capitalWidthScale ?? 1.46);
914
- const topDepth = node.depth * (node.capitalDepthScale ?? node.capitalWidthScale ?? 1.46);
915
- return (_jsxs("group", { children: [_jsx(ColumnBlock, { height: height * 0.24, node: node, scale: 0.9, y: y }), _jsx(ColumnBlock, { height: height * 0.2, node: node, scale: 1.08, y: y + height * 0.24 }), _jsx(SquareBlock, { depth: topDepth, height: height * 0.28, width: topWidth, y: y + height * 0.44 }), _jsx(Volutes, { capitalHeight: height, capitalY: y, node: node }), _jsx(LeafCarvings, { capitalHeight: height, capitalY: y, node: node })] }));
916
- }
917
- const widthScale = node.capitalWidthScale ?? (capitalStyle === 'simple-slab' ? 1.28 : 1.18);
918
- const depthScale = node.capitalDepthScale ?? widthScale;
919
- if (node.crossSection === 'square' || node.crossSection === 'rectangular') {
920
- return (_jsx(SquareBlock, { depth: node.depth * depthScale, height: height, width: node.width * widthScale, y: y }));
921
- }
922
- return (_jsx(RoundBlock, { height: height, radius: Math.max(node.radius * widthScale, node.width * widthScale * 0.5), segments: getSegments(node), y: y }));
835
+ }), _jsx(Volutes, { capitalHeight: height, capitalY: y, node: node }), _jsx(LeafCarvings, { capitalHeight: height, capitalY: y, node: node })] }));
923
836
  }
924
837
  /**
925
838
  * The column's geometry tree — either a fabricated support frame or the
@@ -931,12 +844,7 @@ function Capital({ node, y, height }) {
931
844
  * wrapping this in its own providers.
932
845
  */
933
846
  function ColumnBody({ node }) {
934
- const shaftLayout = useMemo(() => {
935
- const baseHeight = node.baseStyle === 'none' ? 0 : Math.min(node.baseHeight, node.height * 0.4);
936
- const capitalHeight = node.capitalStyle === 'none' ? 0 : Math.min(node.capitalHeight, node.height * 0.4);
937
- const shaftHeight = Math.max(0.1, node.height - baseHeight - capitalHeight);
938
- return { baseHeight, capitalHeight, shaftY: baseHeight, shaftHeight };
939
- }, [node.baseHeight, node.baseStyle, node.capitalHeight, node.capitalStyle, node.height]);
847
+ const shaftLayout = columnShaftLayout(node);
940
848
  if (node.supportStyle !== 'vertical') {
941
849
  const support = node.supportStyle === 'a-frame' ? (_jsx(AFrameSupport, { node: node })) : node.supportStyle === 'y-frame' ? (_jsx(YFrameSupport, { node: node })) : node.supportStyle === 'v-frame' ? (_jsx(VFrameSupport, { node: node })) : node.supportStyle === 'x-brace' ? (_jsx(XBraceSupport, { node: node })) : node.supportStyle === 'k-brace' ? (_jsx(KBraceSupport, { node: node })) : node.supportStyle === 'single-strut' ? (_jsx(SingleStrutSupport, { node: node })) : node.supportStyle === 'tripod' ? (_jsx(TripodSupport, { node: node })) : node.supportStyle === 'trestle' ? (_jsx(TrestleSupport, { node: node })) : node.supportStyle === 'portal-frame' ? (_jsx(PortalFrameSupport, { node: node })) : (_jsx(BoxFrameSupport, { node: node }));
942
850
  return _jsx(ColumnSlot, { slotId: "frame", children: support });
@@ -1011,6 +919,20 @@ export const ColumnRenderer = ({ node: rawNode }) => {
1011
919
  colorPreset,
1012
920
  }), [shading, textures, colorPreset, node, sceneMaterials]);
1013
921
  useRegistry(node.id, node.type, ref);
1014
- return (_jsx(ColumnMaterialContext.Provider, { value: materials, children: _jsx(ColumnEdgeSoftnessContext.Provider, { value: node.edgeSoftness ?? 0.025, children: _jsx("group", { position: liveTransform?.position ?? node.position, ref: ref, rotation: [0, liveTransform?.rotation ?? node.rotation, 0], visible: node.visible, ...handlers, children: _jsx(ColumnBody, { node: node }) }) }) }));
922
+ // This renderer bypasses GeometrySystem, which normally invalidates hosted descendants.
923
+ useEffect(() => {
924
+ const dirtySubtree = () => {
925
+ const scene = createSceneApi(useScene);
926
+ for (const id of collectDescendants(node.id, { scene })) {
927
+ scene.markDirty(id);
928
+ }
929
+ };
930
+ dirtySubtree();
931
+ return useLiveTransforms.subscribe((state, previous) => {
932
+ if (state.transforms.get(node.id) !== previous.transforms.get(node.id))
933
+ dirtySubtree();
934
+ });
935
+ }, [node]);
936
+ return (_jsx(ColumnMaterialContext.Provider, { value: materials, children: _jsx(ColumnEdgeSoftnessContext.Provider, { value: node.edgeSoftness ?? 0.025, children: _jsxs("group", { position: liveTransform?.position ?? node.position, ref: ref, rotation: [0, liveTransform?.rotation ?? node.rotation, 0], visible: node.visible, ...handlers, children: [_jsx(ColumnBody, { node: node }), node.children.map((id) => (_jsx(NodeRenderer, { nodeId: id }, id)))] }) }) }));
1015
937
  };
1016
938
  export default ColumnRenderer;
@@ -0,0 +1,22 @@
1
+ import type { ColumnNode } from '@pascal-app/core';
2
+ export declare function getSegments(node: ColumnNode): 8 | 16 | 32;
3
+ export declare function getShaftSegmentCount(node: ColumnNode): number;
4
+ export declare function getShaftTwistRadians(node: ColumnNode, index: number): number;
5
+ export declare function getShaftScaleAt(node: ColumnNode, t: number): number;
6
+ export declare function columnShaftLayout(node: ColumnNode): {
7
+ baseHeight: number;
8
+ capitalHeight: number;
9
+ shaftY: number;
10
+ shaftHeight: number;
11
+ };
12
+ export type CapitalBlock = {
13
+ kind: 'box' | 'round' | 'oval' | 'column';
14
+ y: number;
15
+ height: number;
16
+ width?: number;
17
+ depth?: number;
18
+ radius?: number;
19
+ scale?: number;
20
+ };
21
+ export declare function columnCapitalBlocks(node: ColumnNode, y: number, height: number): CapitalBlock[];
22
+ //# sourceMappingURL=shape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shape.d.ts","sourceRoot":"","sources":["../../src/column/shape.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,eAI3C;AAMD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,UAUpD;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,UAEnE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,UA0B1D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU;;;;;EAMjD;AACD,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAA;IACzC,CAAC,EAAE,MAAM,CAAA;IACT,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AACD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CAyE/F"}
@@ -0,0 +1,120 @@
1
+ export function getSegments(node) {
2
+ if (node.crossSection === 'octagonal')
3
+ return 8;
4
+ if (node.crossSection === 'sixteen-sided')
5
+ return 16;
6
+ return 32;
7
+ }
8
+ function getShaftProfile(node) {
9
+ return node.shaftProfile ?? (node.shaftTaper > 0 ? 'tapered' : 'straight');
10
+ }
11
+ export function getShaftSegmentCount(node) {
12
+ const shaftProfile = getShaftProfile(node);
13
+ const shaftTaper = node.shaftTaper ?? 0;
14
+ const hasTwist = Math.abs(node.shaftTwistStep ?? 0) > 0.001;
15
+ return Math.max(hasTwist ? 4 : 1, shaftProfile === 'straight' && shaftTaper <= 0 && !hasTwist
16
+ ? 1
17
+ : (node.shaftSegmentCount ?? (hasTwist ? 12 : 24)));
18
+ }
19
+ export function getShaftTwistRadians(node, index) {
20
+ return ((node.shaftTwistStep ?? 0) * Math.PI * index) / 180;
21
+ }
22
+ export function getShaftScaleAt(node, t) {
23
+ const shaftProfile = getShaftProfile(node);
24
+ const shaftTaper = Math.min(node.shaftTaper ?? 0, 0.85);
25
+ const startScale = node.shaftStartScale ?? 0.72;
26
+ const endScale = node.shaftEndScale ?? startScale;
27
+ const shaftBulge = node.shaftBulge ??
28
+ (shaftProfile === 'bulged'
29
+ ? 0.16
30
+ : shaftProfile === 'baluster'
31
+ ? 0.2
32
+ : shaftProfile === 'hourglass'
33
+ ? 0.18
34
+ : 0);
35
+ const taperedScale = 1 - shaftTaper * t;
36
+ const linearScale = (startScale + (endScale - startScale) * t) * taperedScale;
37
+ const bulgeCurve = Math.sin(Math.PI * t);
38
+ const hourglassCurve = Math.abs(t - 0.5) * 2;
39
+ const profileScale = shaftProfile === 'bulged' || shaftProfile === 'baluster'
40
+ ? linearScale + shaftBulge * bulgeCurve
41
+ : shaftProfile === 'hourglass'
42
+ ? linearScale - shaftBulge * (1 - hourglassCurve)
43
+ : linearScale;
44
+ return Math.max(0.1, profileScale);
45
+ }
46
+ export function columnShaftLayout(node) {
47
+ const baseHeight = node.baseStyle === 'none' ? 0 : Math.min(node.baseHeight, node.height * 0.4);
48
+ const capitalHeight = node.capitalStyle === 'none' ? 0 : Math.min(node.capitalHeight, node.height * 0.4);
49
+ const shaftHeight = Math.max(0.1, node.height - baseHeight - capitalHeight);
50
+ return { baseHeight, capitalHeight, shaftY: baseHeight, shaftHeight };
51
+ }
52
+ export function columnCapitalBlocks(node, y, height) {
53
+ const style = node.capitalStyle ?? 'simple';
54
+ if (height <= 0 || style === 'none')
55
+ return [];
56
+ if (style === 'south-indian-bracket' || style === 'wood-bracket') {
57
+ const count = Math.max(1, node.bracketTierCount ?? 3);
58
+ return Array.from({ length: count }, (_, index) => {
59
+ const t = index / Math.max(1, count - 1), scale = (node.capitalWidthScale ?? 1.6) + t * 0.32;
60
+ return {
61
+ kind: 'box',
62
+ y: y + (index * height) / count,
63
+ height: height / count,
64
+ width: node.width * scale + (node.bracketDepth ?? 0.35) * t,
65
+ depth: node.depth * scale + (node.bracketDepth ?? 0.35) * t,
66
+ };
67
+ });
68
+ }
69
+ if (style === 'rounded' || style === 'doric') {
70
+ const width = node.width * (node.capitalWidthScale ?? 1.34), depth = node.depth * (node.capitalDepthScale ?? node.capitalWidthScale ?? 1.34);
71
+ return [
72
+ { kind: 'oval', y, height: height * 0.24, width: width * 0.72, depth: depth * 0.72 },
73
+ {
74
+ kind: 'oval',
75
+ y: y + height * 0.24,
76
+ height: height * 0.32,
77
+ width: width * 0.92,
78
+ depth: depth * 0.92,
79
+ },
80
+ { kind: 'box', y: y + height * 0.56, height: height * 0.44, width, depth },
81
+ ];
82
+ }
83
+ if (style === 'stepped') {
84
+ const widthScale = node.capitalWidthScale ?? 1.46, depthScale = node.capitalDepthScale ?? widthScale, count = Math.max(3, node.capitalTierCount ?? 3);
85
+ return Array.from({ length: count }, (_, index) => {
86
+ const t = index / Math.max(1, count - 1), spread = (1 - t) * (node.capitalStepSpread ?? 0.42);
87
+ return {
88
+ kind: 'box',
89
+ y: y + (index * height) / count,
90
+ height: (height / count) * 1.01,
91
+ width: node.width * Math.max(0.5, widthScale - spread),
92
+ depth: node.depth * Math.max(0.5, depthScale - spread),
93
+ };
94
+ });
95
+ }
96
+ if (['volute', 'ionic-volute', 'leaf-carved', 'corinthian-leaf'].includes(style)) {
97
+ return [
98
+ { kind: 'column', y, height: height * 0.24, scale: 0.9 },
99
+ { kind: 'column', y: y + height * 0.24, height: height * 0.2, scale: 1.08 },
100
+ {
101
+ kind: 'box',
102
+ y: y + height * 0.44,
103
+ height: height * 0.28,
104
+ width: node.width * (node.capitalWidthScale ?? 1.46),
105
+ depth: node.depth * (node.capitalDepthScale ?? node.capitalWidthScale ?? 1.46),
106
+ },
107
+ ];
108
+ }
109
+ const widthScale = node.capitalWidthScale ?? (style === 'simple-slab' ? 1.28 : 1.18), depthScale = node.capitalDepthScale ?? widthScale;
110
+ return node.crossSection === 'square' || node.crossSection === 'rectangular'
111
+ ? [{ kind: 'box', y, height, width: node.width * widthScale, depth: node.depth * depthScale }]
112
+ : [
113
+ {
114
+ kind: 'round',
115
+ y,
116
+ height,
117
+ radius: Math.max(node.radius * widthScale, node.width * widthScale * 0.5),
118
+ },
119
+ ];
120
+ }
@@ -0,0 +1,4 @@
1
+ import { type ColumnNode, type DeclaredHostSurface, type SurfaceProvider } from '@pascal-app/core';
2
+ export declare function columnTopSurfaces(node: ColumnNode): DeclaredHostSurface[];
3
+ export declare const columnSurfaceProvider: SurfaceProvider;
4
+ //# sourceMappingURL=surface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/column/surface.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,mBAAmB,EAExB,KAAK,eAAe,EAErB,MAAM,kBAAkB,CAAA;AAoBzB,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,mBAAmB,EAAE,CAgDzE;AAED,eAAO,MAAM,qBAAqB,EAAE,eAUnC,CAAA"}
@@ -0,0 +1,66 @@
1
+ import { hitDerivedSurfaceProvider, } from '@pascal-app/core';
2
+ import { columnCapitalBlocks, columnShaftLayout, getSegments, getShaftScaleAt, getShaftSegmentCount, getShaftTwistRadians, } from './shape';
3
+ function roundRegion(radius, segments, yaw = 0) {
4
+ return {
5
+ kind: 'polygon',
6
+ points: Array.from({ length: segments }, (_, i) => {
7
+ const angle = (i * Math.PI * 2) / segments + yaw;
8
+ return [Math.sin(angle) * radius, Math.cos(angle) * radius];
9
+ }),
10
+ };
11
+ }
12
+ export function columnTopSurfaces(node) {
13
+ if (node.supportStyle !== 'vertical' || node.style === 'cluster')
14
+ return [];
15
+ const layout = columnShaftLayout(node);
16
+ const cap = columnCapitalBlocks(node, layout.shaftY + layout.shaftHeight, layout.capitalHeight).at(-1);
17
+ let y;
18
+ let region;
19
+ if (cap) {
20
+ y = cap.y + cap.height;
21
+ if (cap.kind === 'round')
22
+ region = roundRegion(cap.radius, getSegments(node));
23
+ else {
24
+ const bevel = Math.min(Math.max(0, node.edgeSoftness ?? 0.025), Math.min(cap.width, cap.depth, cap.height) * 0.35);
25
+ region = { kind: 'rect', size: [cap.width / 2 - bevel, cap.depth / 2 - bevel] };
26
+ }
27
+ }
28
+ else {
29
+ const segments = getShaftSegmentCount(node);
30
+ const scale = getShaftScaleAt(node, (segments - 0.5) / segments);
31
+ const yaw = getShaftTwistRadians(node, segments - 1);
32
+ y = layout.shaftY + layout.shaftHeight + (layout.shaftHeight / segments) * 0.015;
33
+ if (node.crossSection === 'square' || node.crossSection === 'rectangular') {
34
+ const corner = Math.min(Math.max(0, node.shaftCornerRadius ?? 0.035), Math.min(node.width, node.depth) * 0.45) * scale;
35
+ const x = (node.width * scale) / 2 - corner;
36
+ const z = (node.depth * scale) / 2 - corner;
37
+ region = {
38
+ kind: 'polygon',
39
+ points: [
40
+ [-x, -z],
41
+ [x, -z],
42
+ [x, z],
43
+ [-x, z],
44
+ ].map(([px, pz]) => [
45
+ px * Math.cos(yaw) + pz * Math.sin(yaw),
46
+ -px * Math.sin(yaw) + pz * Math.cos(yaw),
47
+ ]),
48
+ };
49
+ }
50
+ else
51
+ region = roundRegion(node.radius * scale, getSegments(node), yaw);
52
+ }
53
+ return [{ id: 'top', position: [0, y, 0], normal: [0, 1, 0], region }];
54
+ }
55
+ export const columnSurfaceProvider = {
56
+ ...hitDerivedSurfaceProvider,
57
+ surfaces: (host) => columnTopSurfaces(host),
58
+ resolveHit(host, hit, ctx) {
59
+ const derived = hitDerivedSurfaceProvider.resolveHit(host, hit, ctx);
60
+ if (!derived)
61
+ return null;
62
+ const top = columnTopSurfaces(host)[0];
63
+ // Lower ornaments remain hit-derived; only the principal top has a durable resize identity.
64
+ return top && Math.abs(hit.point[1] - top.position[1]) < 1e-4 ? top : derived;
65
+ },
66
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/cupola/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAA;AAIzB,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AA6FrC;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAAc,CAAC,OAAO,UAAU,CAgE9D,CAAA"}
1
+ {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/cupola/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAA;AAIzB,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AA6FrC;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAAc,CAAC,OAAO,UAAU,CAiE9D,CAAA"}
@@ -126,6 +126,7 @@ export const cupolaDefinition = {
126
126
  parametrics: cupolaParametrics,
127
127
  handles: cupolaHandles,
128
128
  floorplan: buildCupolaFloorplan,
129
+ rendersChildren: false,
129
130
  renderer: {
130
131
  kind: 'parametric',
131
132
  module: () => import('./renderer'),
@@ -1 +1 @@
1
- {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/door/definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,cAAc,EAGf,MAAM,kBAAkB,CAAA;AAkBzB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AA4HnC;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,cAAc,EAAE,cAAc,CAAC,OAAO,QAAQ,CAqH1D,CAAA"}
1
+ {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/door/definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,cAAc,EAGf,MAAM,kBAAkB,CAAA;AAqBzB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAkKnC;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,cAAc,EAAE,cAAc,CAAC,OAAO,QAAQ,CAsH1D,CAAA"}
@@ -1,5 +1,8 @@
1
+ import { curtainOpeningResizeMax } from '../shared/curtain-opening-limits';
1
2
  import { buildDoorFloorplanSchedule, computeDoorFloorplanLevelData, } from '../shared/opening-documentation';
2
3
  import { publishOpeningResizeGuides } from '../shared/opening-guides-runtime';
4
+ import { createOpeningPropertyPreview } from '../shared/opening-property-preview';
5
+ import { openingPropertyPreviewHost } from '../shared/opening-property-preview-host';
3
6
  import { readRoofFaceHeightMax, readRoofFaceWidthMax } from '../shared/roof-opening-host';
4
7
  import { buildRoofWallOpeningCut } from '../shared/roof-wall-opening-cut';
5
8
  import { readHostWallCeiling } from '../shared/wall-opening-ceiling';
@@ -46,7 +49,7 @@ function doorWidthHandle(side) {
46
49
  const roofMax = readRoofFaceWidthMax(n, scene, sign);
47
50
  if (roofMax !== null)
48
51
  return Math.max(MIN_DOOR_WIDTH, roofMax);
49
- return readWallLength(n, scene);
52
+ return curtainOpeningResizeMax(n, scene.nodes(), 'x', sign) ?? readWallLength(n, scene);
50
53
  },
51
54
  currentValue: (n) => n.width,
52
55
  onDrag: (node) => publishOpeningResizeGuides(node, false),
@@ -88,6 +91,9 @@ function doorHeightHandle() {
88
91
  const roofMax = readRoofFaceHeightMax(n, scene, 1);
89
92
  if (roofMax !== null)
90
93
  return Math.max(MIN_DOOR_HEIGHT, roofMax);
94
+ const curtainMax = curtainOpeningResizeMax(n, scene.nodes(), 'y', 1);
95
+ if (curtainMax !== undefined)
96
+ return curtainMax;
91
97
  const bottom = n.position[1] - n.height / 2;
92
98
  return Math.max(MIN_DOOR_HEIGHT, readHostWallCeiling(n.wallId, scene) - bottom);
93
99
  },
@@ -126,11 +132,43 @@ function doorMoveHandle() {
126
132
  },
127
133
  };
128
134
  }
135
+ function doorRadiusHandle(index) {
136
+ const sign = index === 0 ? -1 : 1;
137
+ return {
138
+ kind: 'corner-radius',
139
+ corner: [sign, 1],
140
+ width: (node) => node.width,
141
+ height: (node) => node.height,
142
+ currentValue: (node) => node.openingRadiusMode === 'individual'
143
+ ? (node.openingTopRadii[index] ?? 0)
144
+ : node.cornerRadius,
145
+ max: (node) => Math.min(node.width / 2, node.height),
146
+ apply: (node, radius, _scene, modifiers) => {
147
+ if (!modifiers.shiftKey) {
148
+ return { openingShape: 'rounded', openingRadiusMode: 'all', cornerRadius: radius };
149
+ }
150
+ const radii = node.openingRadiusMode === 'individual'
151
+ ? [...node.openingTopRadii]
152
+ : [node.cornerRadius, node.cornerRadius];
153
+ radii[index] = radius;
154
+ return {
155
+ openingShape: 'rounded',
156
+ openingRadiusMode: 'individual',
157
+ openingTopRadii: radii,
158
+ };
159
+ },
160
+ createPreview: (node) => createOpeningPropertyPreview(node.id, openingPropertyPreviewHost),
161
+ visible: (node) => node.openingShape !== 'arch',
162
+ portal: 'grandparent',
163
+ };
164
+ }
129
165
  const doorHandles = [
130
166
  doorMoveHandle(),
131
167
  doorWidthHandle('left'),
132
168
  doorWidthHandle('right'),
133
169
  doorHeightHandle(),
170
+ doorRadiusHandle(0),
171
+ doorRadiusHandle(1),
134
172
  ];
135
173
  /**
136
174
  * Door — Phase 5 batch kind. Hosted on walls, cuts holes in them,
@@ -200,6 +238,7 @@ export const doorDefinition = {
200
238
  },
201
239
  parametrics: doorParametrics,
202
240
  handles: doorHandles,
241
+ rendersChildren: false,
203
242
  renderer: {
204
243
  kind: 'parametric',
205
244
  module: () => import('./renderer'),
@@ -1,4 +1,4 @@
1
- import type { WallNode } from '@pascal-app/core';
1
+ import { type WallNode } from '@pascal-app/core';
2
2
  /**
3
3
  * Keep the door handle at the same relative height when the door is resized:
4
4
  * scale it by the height ratio, then clamp to the panel's slider bounds
@@ -1 +1 @@
1
- {"version":3,"file":"door-math.d.ts","sourceRoot":"","sources":["../../src/door/door-math.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAEhD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,MAAM,CAGR;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,YAAY,SAAI,EAChB,aAAa,SAAI,GAChB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAU1B;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAQxC;AAKD,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAA"}
1
+ {"version":3,"file":"door-math.d.ts","sourceRoot":"","sources":["../../src/door/door-math.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AAEzB;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,MAAM,CAGR;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,YAAY,SAAI,EAChB,aAAa,SAAI,GAChB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAI1B;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAOxC;AAKD,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAA"}
@@ -1,3 +1,4 @@
1
+ import { getCurtainWallConfig, getWallCurveFrameAt, getWallCurveLength, } from '@pascal-app/core';
1
2
  /**
2
3
  * Keep the door handle at the same relative height when the door is resized:
3
4
  * scale it by the height ratio, then clamp to the panel's slider bounds
@@ -13,22 +14,18 @@ export function scaleHandleHeight(handleHeight, oldHeight, newHeight) {
13
14
  * Converts wall-local (X along wall, Y = height above wall base) to world XYZ.
14
15
  */
15
16
  export function wallLocalToWorld(wallNode, localX, localY, levelYOffset = 0, slabElevation = 0) {
16
- const wallAngle = Math.atan2(wallNode.end[1] - wallNode.start[1], wallNode.end[0] - wallNode.start[0]);
17
- return [
18
- wallNode.start[0] + localX * Math.cos(wallAngle),
19
- slabElevation + localY + levelYOffset,
20
- wallNode.start[1] + localX * Math.sin(wallAngle),
21
- ];
17
+ const wallLength = getWallCurveLength(wallNode);
18
+ const frame = getWallCurveFrameAt(wallNode, wallLength > 1e-6 ? localX / wallLength : 0);
19
+ return [frame.point.x, slabElevation + localY + levelYOffset, frame.point.y];
22
20
  }
23
21
  /**
24
22
  * Clamps door center X so it stays fully within wall bounds.
25
23
  * Y is always height/2 — doors sit at floor level.
26
24
  */
27
25
  export function clampToWall(wallNode, localX, width, height) {
28
- const dx = wallNode.end[0] - wallNode.start[0];
29
- const dz = wallNode.end[1] - wallNode.start[1];
30
- const wallLength = Math.sqrt(dx * dx + dz * dz);
31
- const clampedX = Math.max(width / 2, Math.min(wallLength - width / 2, localX));
26
+ const wallLength = getWallCurveLength(wallNode);
27
+ const margin = wallNode.wallType === 'curtain' ? getCurtainWallConfig(wallNode).perimeterWidth : 0;
28
+ const clampedX = Math.max(margin + width / 2, Math.min(wallLength - margin - width / 2, localX));
32
29
  const clampedY = height / 2; // Doors always sit at floor level
33
30
  return { clampedX, clampedY };
34
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"floorplan-affordances.d.ts","sourceRoot":"","sources":["../../src/door/floorplan-affordances.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,mBAAmB,EAKzB,MAAM,kBAAkB,CAAA;AAMzB;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,EAAE,mBAAmB,CAAC,QAAQ,CAuF7D,CAAA"}
1
+ {"version":3,"file":"floorplan-affordances.d.ts","sourceRoot":"","sources":["../../src/door/floorplan-affordances.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,mBAAmB,EAMzB,MAAM,kBAAkB,CAAA;AAQzB;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,EAAE,mBAAmB,CAAC,QAAQ,CAkF7D,CAAA"}
@@ -1,4 +1,6 @@
1
- import { useLiveNodeOverrides, useScene, } from '@pascal-app/core';
1
+ import { getWallCurveLength, useLiveNodeOverrides, useScene, } from '@pascal-app/core';
2
+ import { curtainOpeningLimits } from '../shared/curtain-opening-limits';
3
+ import { projectPlanPointToWallLocalX } from '../shared/wall-attach-target';
2
4
  const MIN_DOOR_WIDTH = 0.3;
3
5
  /**
4
6
  * 2D drag affordance for the door's width side-arrows. Sister to the 3D
@@ -29,21 +31,15 @@ export const doorWidthAffordance = {
29
31
  // toward wall.end), -1 for 'start' (drag outward toward wall.start).
30
32
  const growDir = side === 'end' ? 1 : -1;
31
33
  const anchorX = side === 'end' ? initialDoorX - initialWidth / 2 : initialDoorX + initialWidth / 2;
32
- // Wall axis (level-local) for projecting pointer movement to a
33
- // wall-local X delta.
34
- const wallStart = wall ? wall.start : [0, 0];
35
- const wallEnd = wall ? wall.end : [1, 0];
36
- const dx = wallEnd[0] - wallStart[0];
37
- const dz = wallEnd[1] - wallStart[1];
38
- const wallLength = Math.hypot(dx, dz) || 1;
39
- const dirX = dx / wallLength;
40
- const dirZ = dz / wallLength;
34
+ const wallLength = wall ? getWallCurveLength(wall) : 1;
41
35
  // Max width keeps the dragged edge inside the wall span. With the
42
36
  // anchor fixed, the moving edge is `anchorX ± width`, so the largest
43
37
  // legal width is the headroom on the grow side.
44
- const maxWidth = growDir > 0 ? wallLength - anchorX : anchorX;
38
+ const limits = curtainOpeningLimits(node, nodes);
39
+ const margin = limits?.margin ?? 0;
40
+ const maxWidth = growDir > 0 ? (limits?.length ?? wallLength) - margin - anchorX : anchorX - margin;
45
41
  const projectToWallLocalX = (planPoint) => {
46
- return (planPoint[0] - wallStart[0]) * dirX + (planPoint[1] - wallStart[1]) * dirZ;
42
+ return wall ? projectPlanPointToWallLocalX(wall, planPoint) : planPoint[0];
47
43
  };
48
44
  // Initial pointer in wall-local X — anchored to where the user
49
45
  // actually pressed, so any subtle offset between the arrow's visual
@@ -1 +1 @@
1
- {"version":3,"file":"move-tool.d.ts","sourceRoot":"","sources":["../../src/door/move-tool.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EAYT,MAAM,kBAAkB,CAAA;AAkDzB,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CA0hC9C,CAAA;AAED,eAAe,YAAY,CAAA"}
1
+ {"version":3,"file":"move-tool.d.ts","sourceRoot":"","sources":["../../src/door/move-tool.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EAYT,MAAM,kBAAkB,CAAA;AAmDzB,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAwhC9C,CAAA;AAED,eAAe,YAAY,CAAA"}