@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
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { emitter, sceneRegistry, useLiveNodeOverrides, } from '@pascal-app/core';
3
+ import { emitter, sceneRegistry, } from '@pascal-app/core';
4
4
  import { cn, EDITOR_LAYER, getFloatingMenuScale, isAngleSnapActive, isGridSnapActive, isMagneticSnapActive, markToolCancelConsumed, meshEditScope, NodeActionMenu, swallowNextClick, triggerSFX, useEditor, useInteractionScope, } from '@pascal-app/editor';
5
5
  import { Html } from '@react-three/drei';
6
6
  import { createPortal, useFrame, useThree } from '@react-three/fiber';
@@ -13,6 +13,7 @@ import useBlockEditSession from './edit-session';
13
13
  import { triangulateBlockFace } from './geometry';
14
14
  import { blockGeometrySnapThreshold, resolveBlockGeometrySnap } from './geometry-snap';
15
15
  import { BLOCK_WHEEL_OPTIONS, consumeBlockGestureWheel } from './gesture-wheel';
16
+ import { BLOCK_SUPPORT_REFUSAL, commitBlockTopologyEdit, createBlockTopologyPreview, } from './hosted-edit';
16
17
  import { blockSfx } from './interaction-sfx';
17
18
  import { commitBlockOperation, repeatCommittedBlockOperation, replaceCommittedBlockOperation, } from './last-operation';
18
19
  import { resolveLoopCutPointerAction, resolveLoopCutSlideFactor } from './loop-cut-interaction';
@@ -645,6 +646,7 @@ function LastOperationPanel({ operation, onChange, onClose, onRepeat, }) {
645
646
  }
646
647
  function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, target, mirrorTarget, }) {
647
648
  const { camera, gl } = useThree();
649
+ const topologyPreview = useMemo(() => createBlockTopologyPreview(node.id, sceneApi), [node.id, sceneApi]);
648
650
  const outerRef = useRef(null);
649
651
  const menuScaleRef = useRef(null);
650
652
  const menuWorldPositionRef = useRef(new Vector3());
@@ -726,7 +728,7 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
726
728
  const exitEditMode = useCallback(() => {
727
729
  cancelDragRef.current?.();
728
730
  cancelDragRef.current = null;
729
- useLiveNodeOverrides.getState().clear(node.id);
731
+ topologyPreview.clear();
730
732
  sceneApi.markDirty(node.id);
731
733
  endOwnedScope();
732
734
  useBlockEditSession.getState().end(node.id);
@@ -743,22 +745,22 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
743
745
  setToolbarPanel(null);
744
746
  setError(null);
745
747
  playBlockSfx('finish');
746
- }, [endOwnedScope, node.id, sceneApi.markDirty]);
748
+ }, [endOwnedScope, node.id, sceneApi.markDirty, topologyPreview]);
747
749
  useEffect(() => () => {
748
750
  cancelDragRef.current?.();
749
- useLiveNodeOverrides.getState().clear(node.id);
751
+ topologyPreview.clear();
750
752
  sceneApi.markDirty(node.id);
751
753
  endOwnedScope();
752
754
  useBlockEditSession.getState().end(node.id);
753
755
  if (document.body.style.cursor === 'grabbing')
754
756
  document.body.style.cursor = '';
755
- }, [endOwnedScope, node.id, sceneApi.markDirty]);
757
+ }, [endOwnedScope, node.id, sceneApi.markDirty, topologyPreview]);
756
758
  useEffect(() => {
757
759
  if (editing)
758
760
  return;
759
761
  cancelDragRef.current?.();
760
762
  cancelDragRef.current = null;
761
- useLiveNodeOverrides.getState().clear(node.id);
763
+ topologyPreview.clear();
762
764
  sceneApi.markDirty(node.id);
763
765
  setPreviewTopology(null);
764
766
  setToolbarPanel(null);
@@ -771,7 +773,7 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
771
773
  setActiveFaceOperation(null);
772
774
  setFaceOperationValue('');
773
775
  useBlockEditSession.getState().end(node.id);
774
- }, [editing, node.id, sceneApi.markDirty]);
776
+ }, [editing, node.id, sceneApi.markDirty, topologyPreview]);
775
777
  useEffect(() => {
776
778
  if (!editing)
777
779
  return;
@@ -1192,15 +1194,18 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1192
1194
  setError(result.error);
1193
1195
  return;
1194
1196
  }
1197
+ if (!topologyPreview.set(result.topology)) {
1198
+ setError(BLOCK_SUPPORT_REFUSAL);
1199
+ return;
1200
+ }
1195
1201
  latestTopology = result.topology;
1196
1202
  latestCommand = command;
1197
1203
  setPreviewTopology(result.topology);
1198
- useLiveNodeOverrides.getState().set(node.id, { topology: result.topology });
1199
1204
  sceneApi.markDirty(node.id);
1200
1205
  setError(null);
1201
1206
  };
1202
1207
  const complete = (commit) => {
1203
- useLiveNodeOverrides.getState().clear(node.id);
1208
+ topologyPreview.clear();
1204
1209
  sceneApi.markDirty(node.id);
1205
1210
  setPreviewTopology(null);
1206
1211
  setActiveTransform(null);
@@ -1291,6 +1296,7 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1291
1296
  setModalFeedbackMode('free');
1292
1297
  setError(null);
1293
1298
  beginBlockModalSession({
1299
+ canCommit: () => topologyPreview.valid,
1294
1300
  beginInputDrag: interactionApi.beginInputDrag,
1295
1301
  cancelRef: cancelDragRef,
1296
1302
  cursor: operation === 'translate' ? 'move' : 'crosshair',
@@ -1314,6 +1320,7 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1314
1320
  selection,
1315
1321
  target,
1316
1322
  sceneApi.markDirty,
1323
+ topologyPreview,
1317
1324
  ]);
1318
1325
  const beginTranslationDrag = useCallback((constraint, event) => {
1319
1326
  if (!ownsEditSession() || selectedIds.length === 0 || cancelDragRef.current)
@@ -1409,10 +1416,16 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1409
1416
  setError(result.error);
1410
1417
  return;
1411
1418
  }
1419
+ const wasSupportRefused = !topologyPreview.valid;
1420
+ if (!topologyPreview.set(result.topology)) {
1421
+ setError(BLOCK_SUPPORT_REFUSAL);
1422
+ return;
1423
+ }
1424
+ if (wasSupportRefused)
1425
+ setError(null);
1412
1426
  latestDelta = delta;
1413
1427
  latestTopology = result.topology;
1414
1428
  setPreviewTopology(result.topology);
1415
- useLiveNodeOverrides.getState().set(node.id, { topology: result.topology });
1416
1429
  sceneApi.markDirty(node.id);
1417
1430
  };
1418
1431
  const finish = (commit) => {
@@ -1424,7 +1437,13 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1424
1437
  window.removeEventListener('pointercancel', onPointerCancel);
1425
1438
  window.removeEventListener('blur', onPointerCancel);
1426
1439
  cancelDragRef.current = null;
1427
- useLiveNodeOverrides.getState().clear(node.id);
1440
+ if (!topologyPreview.valid) {
1441
+ // Release keeps the last visible preview; an entirely refused drag cancels.
1442
+ if (!latestTopology)
1443
+ commit = false;
1444
+ setError(null);
1445
+ }
1446
+ topologyPreview.clear();
1428
1447
  sceneApi.markDirty(node.id);
1429
1448
  restoreInputDragging();
1430
1449
  document.body.style.cursor = previousCursor;
@@ -1465,6 +1484,7 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1465
1484
  selection,
1466
1485
  target,
1467
1486
  sceneApi.markDirty,
1487
+ topologyPreview,
1468
1488
  ]);
1469
1489
  const beginRotationDrag = useCallback((axis, event) => {
1470
1490
  if (!ownsEditSession() || selectedIds.length === 0 || cancelDragRef.current)
@@ -1541,10 +1561,16 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1541
1561
  setError(result.error);
1542
1562
  return;
1543
1563
  }
1564
+ const wasSupportRefused = !topologyPreview.valid;
1565
+ if (!topologyPreview.set(result.topology)) {
1566
+ setError(BLOCK_SUPPORT_REFUSAL);
1567
+ return;
1568
+ }
1569
+ if (wasSupportRefused)
1570
+ setError(null);
1544
1571
  latestAngle = angle;
1545
1572
  latestTopology = result.topology;
1546
1573
  setPreviewTopology(result.topology);
1547
- useLiveNodeOverrides.getState().set(node.id, { topology: result.topology });
1548
1574
  sceneApi.markDirty(node.id);
1549
1575
  };
1550
1576
  const finish = (commit) => {
@@ -1556,7 +1582,12 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1556
1582
  window.removeEventListener('pointercancel', onPointerCancel);
1557
1583
  window.removeEventListener('blur', onPointerCancel);
1558
1584
  cancelDragRef.current = null;
1559
- useLiveNodeOverrides.getState().clear(node.id);
1585
+ if (!topologyPreview.valid) {
1586
+ if (!latestTopology)
1587
+ commit = false;
1588
+ setError(null);
1589
+ }
1590
+ topologyPreview.clear();
1560
1591
  sceneApi.markDirty(node.id);
1561
1592
  restoreInputDragging();
1562
1593
  document.body.style.cursor = previousCursor;
@@ -1600,6 +1631,7 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1600
1631
  selection,
1601
1632
  target,
1602
1633
  sceneApi.markDirty,
1634
+ topologyPreview,
1603
1635
  ]);
1604
1636
  const beginScaleDrag = useCallback((axis, event) => {
1605
1637
  if (!ownsEditSession() || selectedIds.length === 0 || cancelDragRef.current)
@@ -1659,10 +1691,13 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1659
1691
  setError(result.error);
1660
1692
  return;
1661
1693
  }
1694
+ if (!topologyPreview.set(result.topology)) {
1695
+ setError(BLOCK_SUPPORT_REFUSAL);
1696
+ return;
1697
+ }
1662
1698
  latestFactor = factor;
1663
1699
  latestTopology = result.topology;
1664
1700
  setPreviewTopology(result.topology);
1665
- useLiveNodeOverrides.getState().set(node.id, { topology: result.topology });
1666
1701
  sceneApi.markDirty(node.id);
1667
1702
  setError(null);
1668
1703
  };
@@ -1675,7 +1710,12 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1675
1710
  window.removeEventListener('pointercancel', onPointerCancel);
1676
1711
  window.removeEventListener('blur', onPointerCancel);
1677
1712
  cancelDragRef.current = null;
1678
- useLiveNodeOverrides.getState().clear(node.id);
1713
+ if (!topologyPreview.valid) {
1714
+ if (!latestTopology)
1715
+ commit = false;
1716
+ setError(null);
1717
+ }
1718
+ topologyPreview.clear();
1679
1719
  sceneApi.markDirty(node.id);
1680
1720
  restoreInputDragging();
1681
1721
  document.body.style.cursor = previousCursor;
@@ -1719,6 +1759,7 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1719
1759
  selection,
1720
1760
  target,
1721
1761
  sceneApi.markDirty,
1762
+ topologyPreview,
1722
1763
  ]);
1723
1764
  const beginUniformScaleModal = useCallback(() => {
1724
1765
  if (!ownsEditSession() || selectedIds.length === 0 || cancelDragRef.current)
@@ -1771,17 +1812,20 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1771
1812
  setError(result.error);
1772
1813
  return;
1773
1814
  }
1774
- latestFactor = factor;
1775
1815
  setTransformNumericInput(typedInput || blockTransformDisplayValue('scale', factor));
1776
1816
  setModalFeedbackMode(typedInput ? 'exact' : snapStep > 0 ? 'grid' : 'free');
1817
+ if (!topologyPreview.set(result.topology)) {
1818
+ setError(BLOCK_SUPPORT_REFUSAL);
1819
+ return;
1820
+ }
1821
+ latestFactor = factor;
1777
1822
  latestTopology = result.topology;
1778
1823
  setPreviewTopology(result.topology);
1779
- useLiveNodeOverrides.getState().set(node.id, { topology: result.topology });
1780
1824
  sceneApi.markDirty(node.id);
1781
1825
  setError(null);
1782
1826
  };
1783
1827
  const complete = (commit) => {
1784
- useLiveNodeOverrides.getState().clear(node.id);
1828
+ topologyPreview.clear();
1785
1829
  sceneApi.markDirty(node.id);
1786
1830
  setPreviewTopology(null);
1787
1831
  setActiveTransform(null);
@@ -1857,6 +1901,7 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1857
1901
  setModalFeedbackMode('free');
1858
1902
  setError(null);
1859
1903
  beginBlockModalSession({
1904
+ canCommit: () => topologyPreview.valid,
1860
1905
  beginInputDrag: interactionApi.beginInputDrag,
1861
1906
  cancelRef: cancelDragRef,
1862
1907
  cursor: 'nwse-resize',
@@ -1867,6 +1912,7 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1867
1912
  });
1868
1913
  return true;
1869
1914
  }, [
1915
+ topologyPreview,
1870
1916
  camera,
1871
1917
  commitAdjustableOperation,
1872
1918
  displayTopology,
@@ -1926,13 +1972,16 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1926
1972
  setError(result.error);
1927
1973
  return false;
1928
1974
  }
1975
+ setBevelWidth(width);
1976
+ if (!topologyPreview.set(result.topology)) {
1977
+ setError(BLOCK_SUPPORT_REFUSAL);
1978
+ return false;
1979
+ }
1929
1980
  activeSegments = segments;
1930
1981
  latestWidth = width;
1931
- setBevelWidth(width);
1932
1982
  latestTopology = result.topology;
1933
1983
  latestSelection = result.selection;
1934
1984
  setPreviewTopology(result.topology);
1935
- useLiveNodeOverrides.getState().set(node.id, { topology: result.topology });
1936
1985
  sceneApi.markDirty(node.id);
1937
1986
  setError(null);
1938
1987
  return true;
@@ -1976,7 +2025,7 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
1976
2025
  window.removeEventListener('pointercancel', onPointerCancel);
1977
2026
  window.removeEventListener('blur', onPointerCancel);
1978
2027
  cancelDragRef.current = null;
1979
- useLiveNodeOverrides.getState().clear(node.id);
2028
+ topologyPreview.clear();
1980
2029
  sceneApi.markDirty(node.id);
1981
2030
  restoreInputDragging();
1982
2031
  document.body.style.cursor = previousCursor;
@@ -2022,6 +2071,7 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
2022
2071
  selectedIds,
2023
2072
  target,
2024
2073
  sceneApi.markDirty,
2074
+ topologyPreview,
2025
2075
  ]);
2026
2076
  const previewLoopCut = useCallback((edgeId) => {
2027
2077
  if (cancelDragRef.current)
@@ -2124,13 +2174,16 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
2124
2174
  setError(result.ok ? 'Could not preview loop cut' : result.error);
2125
2175
  return false;
2126
2176
  }
2177
+ if (!topologyPreview.set(result.topology)) {
2178
+ setError(BLOCK_SUPPORT_REFUSAL);
2179
+ return false;
2180
+ }
2127
2181
  latestFactor = effectiveFactor;
2128
2182
  latestTopology = result.topology;
2129
2183
  latestSelection = result.selection;
2130
2184
  setPreviewTopology(result.topology);
2131
2185
  setLoopCutSegments(segments);
2132
2186
  setLoopCutFactor(effectiveFactor);
2133
- useLiveNodeOverrides.getState().set(node.id, { topology: result.topology });
2134
2187
  sceneApi.markDirty(node.id);
2135
2188
  setError(null);
2136
2189
  return true;
@@ -2174,7 +2227,7 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
2174
2227
  window.removeEventListener('pointercancel', onPointerCancel);
2175
2228
  window.removeEventListener('blur', onPointerCancel);
2176
2229
  cancelDragRef.current = null;
2177
- useLiveNodeOverrides.getState().clear(node.id);
2230
+ topologyPreview.clear();
2178
2231
  sceneApi.markDirty(node.id);
2179
2232
  restoreInputDragging();
2180
2233
  document.body.style.cursor = previousCursor;
@@ -2233,6 +2286,7 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
2233
2286
  ownsEditSession,
2234
2287
  target,
2235
2288
  sceneApi.markDirty,
2289
+ topologyPreview,
2236
2290
  ]);
2237
2291
  const beginFaceOperationModal = useBlockFaceOperation({
2238
2292
  camera,
@@ -2270,7 +2324,11 @@ function BlockEditor({ historyApi, interactionApi, node, readOnly, sceneApi, tar
2270
2324
  setError(result.error);
2271
2325
  return;
2272
2326
  }
2273
- sceneApi.update(node.id, { topology: result.topology });
2327
+ if (!commitBlockTopologyEdit(sceneApi, node.id, result.topology)) {
2328
+ useInteractionScope.getState().begin(meshEditScope(node.id));
2329
+ setError(BLOCK_SUPPORT_REFUSAL);
2330
+ return;
2331
+ }
2274
2332
  const session = useBlockEditSession.getState();
2275
2333
  session.setSelection(node.id, {
2276
2334
  ...result.selection,
@@ -0,0 +1,4 @@
1
+ import { type BlockNode, type DeclaredHostSurface, type SurfaceProvider } from '@pascal-app/core';
2
+ export declare function blockTopSurfaces(host: BlockNode): DeclaredHostSurface[];
3
+ export declare const blockSurfaceProvider: SurfaceProvider;
4
+ //# sourceMappingURL=surface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/block/surface.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,mBAAmB,EAExB,KAAK,eAAe,EAErB,MAAM,kBAAkB,CAAA;AAIzB,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,GAAG,mBAAmB,EAAE,CAwBvE;AAED,eAAO,MAAM,oBAAoB,EAAE,eA0BlC,CAAA"}
@@ -0,0 +1,54 @@
1
+ import { getBlockFaceFrame, surfaceRegionContainsPoint, } from '@pascal-app/core';
2
+ const topSurfaces = new WeakMap();
3
+ export function blockTopSurfaces(host) {
4
+ const cached = topSurfaces.get(host.topology);
5
+ if (cached)
6
+ return cached;
7
+ const vertices = new Map(host.topology.vertices.map((v) => [v.id, v.position]));
8
+ const surfaces = host.topology.faces.flatMap((face) => {
9
+ const frame = getBlockFaceFrame(host.topology, face.id);
10
+ if (!frame || frame.normal[1] < 0.75)
11
+ return [];
12
+ return [
13
+ {
14
+ id: face.id,
15
+ position: [0, frame.origin[1], 0],
16
+ normal: frame.normal,
17
+ region: {
18
+ kind: 'polygon',
19
+ points: face.vertexIds.map((id) => {
20
+ const p = vertices.get(id);
21
+ return [p[0], p[2]];
22
+ }),
23
+ },
24
+ },
25
+ ];
26
+ });
27
+ topSurfaces.set(host.topology, surfaces);
28
+ return surfaces;
29
+ }
30
+ export const blockSurfaceProvider = {
31
+ childFrame: 'host-local',
32
+ surfaces: (host) => blockTopSurfaces(host),
33
+ resolveHit(rawHost, hit) {
34
+ if (hit.normalWorldY < 0.75 || !hit.point.every(Number.isFinite))
35
+ return null;
36
+ const host = rawHost;
37
+ const candidates = blockTopSurfaces(host).filter((surface) => surfaceRegionContainsPoint(surface.region, [hit.point[0], hit.point[2]]));
38
+ let nearest = null;
39
+ let distance = Infinity;
40
+ for (const surface of candidates) {
41
+ const face = getBlockFaceFrame(host.topology, surface.id);
42
+ const y = face.origin[1] -
43
+ (face.normal[0] * (hit.point[0] - face.origin[0]) +
44
+ face.normal[2] * (hit.point[2] - face.origin[2])) /
45
+ face.normal[1];
46
+ const delta = Math.abs(y - hit.point[1]);
47
+ if (delta < distance) {
48
+ distance = delta;
49
+ nearest = { ...surface, position: [0, y, 0] };
50
+ }
51
+ }
52
+ return nearest;
53
+ },
54
+ };
@@ -1,4 +1,4 @@
1
- import { type AnyNodeId, type BlockTopology, type SceneApi } from '@pascal-app/core';
1
+ import type { AnyNodeId, BlockTopology, SceneApi } from '@pascal-app/core';
2
2
  import { type SelectionAffordanceProps } from '@pascal-app/editor';
3
3
  import { type Dispatch, type MutableRefObject, type SetStateAction } from 'react';
4
4
  import type { Camera, Object3D } from 'three';
@@ -22,7 +22,7 @@ export type UseBlockFaceOperationOptions = {
22
22
  nodeId: AnyNodeId;
23
23
  ownsEditSession: () => boolean;
24
24
  playSfx: (action: BlockSfxAction) => void;
25
- sceneApi: Pick<SceneApi, 'markDirty'>;
25
+ sceneApi: Pick<SceneApi, 'get' | 'markDirty'>;
26
26
  selectedIds: string[];
27
27
  selection: BlockSelection;
28
28
  setActiveFaceOperation: StateSetter<BlockModalFaceOperation | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"use-block-face-operation.d.ts","sourceRoot":"","sources":["../../src/block/use-block-face-operation.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,QAAQ,EAEd,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAGL,KAAK,wBAAwB,EAI9B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAe,MAAM,OAAO,CAAA;AAC9F,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAC7C,OAAO,EAAE,OAAO,EAAW,MAAM,OAAO,CAAA;AACxC,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,cAAc,EAEpB,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAG7B,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EACL,KAAK,sBAAsB,EAK5B,MAAM,mBAAmB,CAAA;AAO1B,KAAK,WAAW,CAAC,CAAC,IAAI,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAA;AAEjD,MAAM,MAAM,4BAA4B,GAAG;IACzC,cAAc,EAAE,wBAAwB,CAAC,gBAAgB,CAAC,CAAC,gBAAgB,CAAC,CAAA;IAC5E,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,gBAAgB,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;IAChD,MAAM,EAAE,iBAAiB,CAAA;IACzB,YAAY,EAAE,MAAM,IAAI,CAAA;IACxB,MAAM,EAAE,CAAC,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;IACtF,eAAe,EAAE,aAAa,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,oBAAoB,EAAE,gBAAgB,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;IACtD,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,CAAA;IAC5B,MAAM,EAAE,SAAS,CAAA;IACjB,eAAe,EAAE,MAAM,OAAO,CAAA;IAC9B,OAAO,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAA;IACzC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAA;IACrC,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,SAAS,EAAE,cAAc,CAAA;IACzB,sBAAsB,EAAE,WAAW,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAA;IACnE,QAAQ,EAAE,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACpC,oBAAoB,EAAE,WAAW,CAAC,gBAAgB,CAAC,CAAA;IACnD,qBAAqB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC1C,oBAAoB,EAAE,WAAW,CAAC,sBAAsB,CAAC,CAAA;IACzD,kBAAkB,EAAE,WAAW,CAAC,aAAa,GAAG,IAAI,CAAC,CAAA;IACrD,wBAAwB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC7C,MAAM,EAAE,QAAQ,CAAA;CACjB,CAAA;AAED,wBAAgB,qBAAqB,CAAC,EACpC,cAAc,EACd,MAAM,EACN,SAAS,EACT,MAAM,EACN,YAAY,EACZ,MAAM,EACN,eAAe,EACf,MAAM,EACN,oBAAoB,EACpB,IAAI,EACJ,MAAM,EACN,eAAe,EACf,OAAO,EACP,QAAQ,EACR,WAAW,EACX,SAAS,EACT,sBAAsB,EACtB,QAAQ,EACR,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,EACxB,MAAM,GACP,EAAE,4BAA4B,eAEf,uBAAuB,aAwOtC"}
1
+ {"version":3,"file":"use-block-face-operation.d.ts","sourceRoot":"","sources":["../../src/block/use-block-face-operation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC1E,OAAO,EAGL,KAAK,wBAAwB,EAI9B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,cAAc,EAGpB,MAAM,OAAO,CAAA;AACd,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAC7C,OAAO,EAAE,OAAO,EAAW,MAAM,OAAO,CAAA;AACxC,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,cAAc,EAEpB,MAAM,YAAY,CAAA;AAEnB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAG7B,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EACL,KAAK,sBAAsB,EAK5B,MAAM,mBAAmB,CAAA;AAO1B,KAAK,WAAW,CAAC,CAAC,IAAI,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAA;AAEjD,MAAM,MAAM,4BAA4B,GAAG;IACzC,cAAc,EAAE,wBAAwB,CAAC,gBAAgB,CAAC,CAAC,gBAAgB,CAAC,CAAA;IAC5E,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,gBAAgB,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;IAChD,MAAM,EAAE,iBAAiB,CAAA;IACzB,YAAY,EAAE,MAAM,IAAI,CAAA;IACxB,MAAM,EAAE,CAAC,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;IACtF,eAAe,EAAE,aAAa,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,oBAAoB,EAAE,gBAAgB,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;IACtD,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,CAAA;IAC5B,MAAM,EAAE,SAAS,CAAA;IACjB,eAAe,EAAE,MAAM,OAAO,CAAA;IAC9B,OAAO,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAA;IACzC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,GAAG,WAAW,CAAC,CAAA;IAC7C,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,SAAS,EAAE,cAAc,CAAA;IACzB,sBAAsB,EAAE,WAAW,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAA;IACnE,QAAQ,EAAE,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACpC,oBAAoB,EAAE,WAAW,CAAC,gBAAgB,CAAC,CAAA;IACnD,qBAAqB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC1C,oBAAoB,EAAE,WAAW,CAAC,sBAAsB,CAAC,CAAA;IACzD,kBAAkB,EAAE,WAAW,CAAC,aAAa,GAAG,IAAI,CAAC,CAAA;IACrD,wBAAwB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC7C,MAAM,EAAE,QAAQ,CAAA;CACjB,CAAA;AAED,wBAAgB,qBAAqB,CAAC,EACpC,cAAc,EACd,MAAM,EACN,SAAS,EACT,MAAM,EACN,YAAY,EACZ,MAAM,EACN,eAAe,EACf,MAAM,EACN,oBAAoB,EACpB,IAAI,EACJ,MAAM,EACN,eAAe,EACf,OAAO,EACP,QAAQ,EACR,WAAW,EACX,SAAS,EACT,sBAAsB,EACtB,QAAQ,EACR,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,EACxB,MAAM,GACP,EAAE,4BAA4B,eAMf,uBAAuB,aA8OtC"}
@@ -1,13 +1,14 @@
1
- import { useLiveNodeOverrides, } from '@pascal-app/core';
2
1
  import { isGridSnapActive, meshEditScope, swallowNextClick, useEditor, useInteractionScope, } from '@pascal-app/editor';
3
- import { useCallback } from 'react';
2
+ import { useCallback, useMemo, } from 'react';
4
3
  import { Vector2, Vector3 } from 'three';
5
4
  import { applyBlockCommand, blockFaceNormal, } from './commands';
5
+ import { BLOCK_SUPPORT_REFUSAL, createBlockTopologyPreview } from './hosted-edit';
6
6
  import { blockFaceOperationCommand, blockFaceOperationValueFromPointer, } from './modal-face-operation';
7
7
  import { beginBlockModalSession } from './modal-session';
8
8
  import { blockPointerDistanceForAxis, blockTransformAxisFromKey, blockTransformNumericInputFromKey, blockTransformNumericValue, } from './modal-transform';
9
9
  import { blockLocalPointToClient, blockSelectionCentroid, blockTopologyClientExtent, } from './selection-geometry';
10
10
  export function useBlockFaceOperation({ beginInputDrag, camera, cancelRef, canvas, closeToolbar, commit, displayTopology, extent, lastPointerClientRef, mode, nodeId, ownsEditSession, playSfx, sceneApi, selectedIds, selection, setActiveFaceOperation, setError, setFaceOperationAxis, setFaceOperationValue, setModalFeedbackMode, setPreviewTopology, setTransformNumericInput, target, }) {
11
+ const topologyPreview = useMemo(() => createBlockTopologyPreview(nodeId, sceneApi), [nodeId, sceneApi]);
11
12
  return useCallback((operation) => {
12
13
  if (!ownsEditSession() || mode !== 'face' || selectedIds.length === 0 || cancelRef.current) {
13
14
  return false;
@@ -48,7 +49,8 @@ export function useBlockFaceOperation({ beginInputDrag, camera, cancelRef, canva
48
49
  const baseTopology = displayTopology;
49
50
  let latestTopology = null;
50
51
  let latestSelection = null;
51
- let latestValue = 0;
52
+ let latestCommand = null;
53
+ let supportRefused = false;
52
54
  let typedInput = '';
53
55
  let lastClientX = startPointer.x;
54
56
  let lastClientY = startPointer.y;
@@ -77,9 +79,11 @@ export function useBlockFaceOperation({ beginInputDrag, camera, cancelRef, canva
77
79
  if (Math.abs(value) <= 1e-6) {
78
80
  latestTopology = null;
79
81
  latestSelection = null;
80
- latestValue = 0;
82
+ latestCommand = null;
83
+ supportRefused = false;
84
+ setError(null);
81
85
  setPreviewTopology(null);
82
- useLiveNodeOverrides.getState().clear(nodeId);
86
+ topologyPreview.clear();
83
87
  sceneApi.markDirty(nodeId);
84
88
  return;
85
89
  }
@@ -90,21 +94,29 @@ export function useBlockFaceOperation({ beginInputDrag, camera, cancelRef, canva
90
94
  else if (!snapping) {
91
95
  lastSnapValue = null;
92
96
  }
93
- const result = applyBlockCommand(baseTopology, blockFaceOperationCommand(operation, faceIds, value, extrudeAxis));
97
+ const command = blockFaceOperationCommand(operation, faceIds, value, extrudeAxis);
98
+ const result = applyBlockCommand(baseTopology, command);
94
99
  if (!result.ok) {
100
+ // Command errors keep the last accepted preview; only lost support holds the modal open.
101
+ supportRefused = false;
95
102
  setError(result.error);
96
103
  return;
97
104
  }
105
+ if (!topologyPreview.set(result.topology)) {
106
+ supportRefused = true;
107
+ setError(BLOCK_SUPPORT_REFUSAL);
108
+ return;
109
+ }
98
110
  latestTopology = result.topology;
99
111
  latestSelection = result.selection;
100
- latestValue = value;
112
+ latestCommand = command;
113
+ supportRefused = false;
101
114
  setPreviewTopology(result.topology);
102
- useLiveNodeOverrides.getState().set(nodeId, { topology: result.topology });
103
115
  sceneApi.markDirty(nodeId);
104
116
  setError(null);
105
117
  };
106
118
  const complete = (commitOperation) => {
107
- useLiveNodeOverrides.getState().clear(nodeId);
119
+ topologyPreview.clear();
108
120
  sceneApi.markDirty(nodeId);
109
121
  setPreviewTopology(null);
110
122
  setActiveFaceOperation(null);
@@ -112,8 +124,8 @@ export function useBlockFaceOperation({ beginInputDrag, camera, cancelRef, canva
112
124
  setFaceOperationValue('');
113
125
  setTransformNumericInput('');
114
126
  setModalFeedbackMode('free');
115
- if (commitOperation && latestTopology && latestSelection && Math.abs(latestValue) > 1e-6) {
116
- commit(baseTopology, blockFaceOperationCommand(operation, faceIds, latestValue, extrudeAxis), operation === 'extrude' ? 'Extrude' : 'Inset');
127
+ if (commitOperation && latestTopology && latestSelection && latestCommand) {
128
+ commit(baseTopology, latestCommand, operation === 'extrude' ? 'Extrude' : 'Inset');
117
129
  playSfx('operation-commit');
118
130
  }
119
131
  else if (!commitOperation) {
@@ -180,6 +192,7 @@ export function useBlockFaceOperation({ beginInputDrag, camera, cancelRef, canva
180
192
  setModalFeedbackMode('free');
181
193
  setError(null);
182
194
  beginBlockModalSession({
195
+ canCommit: () => !supportRefused,
183
196
  beginInputDrag,
184
197
  cancelRef,
185
198
  cursor: operation === 'extrude' ? 'ns-resize' : 'nwse-resize',
@@ -190,6 +203,7 @@ export function useBlockFaceOperation({ beginInputDrag, camera, cancelRef, canva
190
203
  });
191
204
  return true;
192
205
  }, [
206
+ topologyPreview,
193
207
  beginInputDrag,
194
208
  camera,
195
209
  cancelRef,
@@ -1 +1 @@
1
- {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/box-vent/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAA;AAIzB,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AA4ItC;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,iBAAiB,EAAE,cAAc,CAAC,OAAO,WAAW,CA2DhE,CAAA"}
1
+ {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/box-vent/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAA;AAIzB,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AA4ItC;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,iBAAiB,EAAE,cAAc,CAAC,OAAO,WAAW,CA4DhE,CAAA"}
@@ -187,6 +187,7 @@ export const boxVentDefinition = {
187
187
  parametrics: boxVentParametrics,
188
188
  handles: boxVentHandles,
189
189
  floorplan: buildBoxVentFloorplan,
190
+ rendersChildren: false,
190
191
  renderer: {
191
192
  kind: 'parametric',
192
193
  module: () => import('./renderer'),
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/building/renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAe,MAAM,kBAAkB,CAAA;AAKjE,eAAO,MAAM,gBAAgB,GAAI,UAAU;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,gCAkBhE,CAAA;AAED,eAAe,gBAAgB,CAAA"}
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/building/renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAe,MAAM,kBAAkB,CAAA;AAKjE,eAAO,MAAM,gBAAgB,GAAI,UAAU;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,gCAmBhE,CAAA;AAED,eAAe,gBAAgB,CAAA"}
@@ -7,6 +7,6 @@ export const BuildingRenderer = ({ node }) => {
7
7
  const ref = useRef(null);
8
8
  useRegistry(node.id, node.type, ref);
9
9
  const handlers = useNodeEvents(node, 'building');
10
- return (_jsx("group", { position: node.position, ref: ref, rotation: [node.rotation[0], node.rotation[1], node.rotation[2]], ...handlers, children: (node.children ?? []).map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId))) }));
10
+ return (_jsx("group", { position: node.position, ref: ref, rotation: [node.rotation[0], node.rotation[1], node.rotation[2]], visible: node.visible !== false, ...handlers, children: (node.children ?? []).map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId))) }));
11
11
  };
12
12
  export default BuildingRenderer;
@@ -1 +1 @@
1
- {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/cabinet/definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EAET,WAAW,IAAI,eAAe,EAG9B,oBAAoB,EAMpB,cAAc,EACd,QAAQ,EACT,MAAM,kBAAkB,CAAA;AAyDzB,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAwKzD,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,eAAe,EACrB,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAC3C,oBAAoB,EAAE,CAcxB;AAiKD,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,SAAS,GAAG,IAAI,CAAA;IAC1B,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAC1C,mBAAmB,CAWrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,SAAS,mBAAmB,EAAE,EAC1C,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,QAgD9B;AAED,oEAAoE;AACpE,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAexE;AAgvDD,eAAO,MAAM,iBAAiB,EAAE,cAAc,CAAC,OAAO,WAAW,CA8MhE,CAAA;AAED,eAAO,MAAM,uBAAuB,EAAE,cAAc,CAAC,OAAO,iBAAiB,CAyK5E,CAAA"}
1
+ {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/cabinet/definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EAET,WAAW,IAAI,eAAe,EAG9B,oBAAoB,EAMpB,cAAc,EACd,QAAQ,EACT,MAAM,kBAAkB,CAAA;AAyDzB,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAwKzD,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,eAAe,EACrB,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAC3C,oBAAoB,EAAE,CAcxB;AAiKD,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,SAAS,GAAG,IAAI,CAAA;IAC1B,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAC1C,mBAAmB,CAWrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,SAAS,mBAAmB,EAAE,EAC1C,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,QAgD9B;AAED,oEAAoE;AACpE,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAexE;AAgvDD,eAAO,MAAM,iBAAiB,EAAE,cAAc,CAAC,OAAO,WAAW,CA+MhE,CAAA;AAED,eAAO,MAAM,uBAAuB,EAAE,cAAc,CAAC,OAAO,iBAAiB,CA0K5E,CAAA"}
@@ -22,7 +22,7 @@ import { cabinetSceneAction } from './scene-action';
22
22
  import { CabinetModuleNode, CabinetNode } from './schema';
23
23
  import { cabinetSlots } from './slots';
24
24
  import { backAnchoredModuleZ, isHoodCompartmentType, minCabinetCarcassHeightForStack, stackForCabinet, } from './stack';
25
- import { cabinetSurfaceProvider } from './surfaces';
25
+ import { cabinetModuleSurfaceProvider, cabinetSurfaceProvider } from './surfaces';
26
26
  import { cabinetFloorplanAffectedIds, cabinetTreeChildIds, cabinetTreeHidden, cabinetTreeLabel, } from './tree-structure';
27
27
  import { findClosestCabinetWallInPlan, resolveCabinetModuleWallSnapLocal, resolveCabinetRunWallSnap, resolveCabinetWallFaceOffset, } from './wall-snap';
28
28
  function isCabinetDuplicableNode(node) {
@@ -1801,6 +1801,7 @@ export const cabinetDefinition = {
1801
1801
  // material / materialPreset left undefined — paint mode writes slot refs.
1802
1802
  }),
1803
1803
  capabilities: {
1804
+ surfacePlacement: 'floor-only',
1804
1805
  selectable: { hitVolume: 'bbox' },
1805
1806
  movable: {
1806
1807
  axes: ['x', 'z'],
@@ -1995,6 +1996,7 @@ export const cabinetModuleDefinition = {
1995
1996
  }),
1996
1997
  capabilities: {
1997
1998
  selectable: { hitVolume: 'bbox' },
1999
+ surfaces: { hosting: cabinetModuleSurfaceProvider },
1998
2000
  movable: {
1999
2001
  axes: ['x', 'z'],
2000
2002
  directDrag: true,
@@ -1,8 +1,5 @@
1
- import { type AnyNode, type AnyNodeId, type HandleDescriptor, type SceneApi } from '@pascal-app/core';
2
- type Nodes = Record<AnyNodeId, AnyNode>;
3
- type Update = readonly [AnyNodeId, Partial<AnyNode>];
4
- export declare function cabinetHostedChildUpdates(before: Nodes, after: Nodes): Update[] | null;
5
- export declare function planCabinetHostedEdit(sceneApi: SceneApi, apply: (staged: SceneApi) => void): Update[] | null;
1
+ import type { AnyNode, AnyNodeId, HandleDescriptor, SceneApi } from '@pascal-app/core';
2
+ export declare function cabinetHostedChildUpdates(before: Record<AnyNodeId, AnyNode>, after: Record<AnyNodeId, AnyNode>): import("../shared/hosted-resize").HostedUpdate[] | null;
3
+ export declare function planCabinetHostedEdit(scene: SceneApi, apply: (staged: SceneApi) => void): import("../shared/hosted-resize").HostedUpdate[] | null;
6
4
  export declare function withCabinetHostedChildren<N extends AnyNode>(descriptor: HandleDescriptor<N>): HandleDescriptor<N>;
7
- export {};
8
5
  //# sourceMappingURL=hosted-resize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hosted-resize.d.ts","sourceRoot":"","sources":["../../src/cabinet/hosted-resize.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EAGd,KAAK,gBAAgB,EAErB,KAAK,QAAQ,EAEd,MAAM,kBAAkB,CAAA;AAGzB,KAAK,KAAK,GAAG,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;AACvC,KAAK,MAAM,GAAG,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAA;AASpD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,GAAG,IAAI,CAiFtF;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,CAAC,MAAM,EAAE,QAAQ,KAAK,IAAI,GAChC,MAAM,EAAE,GAAG,IAAI,CA8BjB;AAED,wBAAgB,yBAAyB,CAAC,CAAC,SAAS,OAAO,EACzD,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAC9B,gBAAgB,CAAC,CAAC,CAAC,CA8CrB"}
1
+ {"version":3,"file":"hosted-resize.d.ts","sourceRoot":"","sources":["../../src/cabinet/hosted-resize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAQtF,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EAClC,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,2DAGlC;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,QAAQ,KAAK,IAAI,2DAEvF;AAED,wBAAgB,yBAAyB,CAAC,CAAC,SAAS,OAAO,EACzD,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAC9B,gBAAgB,CAAC,CAAC,CAAC,CAErB"}