@pascal-app/editor 1.0.3 → 1.0.4

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 (440) hide show
  1. package/package.json +11 -9
  2. package/src/components/editor/action-menu-button.tsx +83 -0
  3. package/src/components/editor/bake-thumbnail.tsx +315 -4
  4. package/src/components/editor/capture-level-picker.tsx +72 -0
  5. package/src/components/editor/custom-camera-controls.tsx +202 -57
  6. package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
  7. package/src/components/editor/duplicate-node.ts +244 -0
  8. package/src/components/editor/editor-layout-mobile.tsx +36 -25
  9. package/src/components/editor/editor-layout-v2.tsx +131 -58
  10. package/src/components/editor/export-manager.tsx +34 -8
  11. package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
  12. package/src/components/editor/first-person/build-collider-world.ts +6 -2
  13. package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
  14. package/src/components/editor/first-person/mechanism-targets.ts +36 -0
  15. package/src/components/editor/first-person-controls.tsx +205 -2
  16. package/src/components/editor/floating-action-menu.tsx +63 -207
  17. package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
  18. package/src/components/editor/floor-aware-camera-controls.ts +324 -0
  19. package/src/components/editor/floor-openings-overlay.tsx +243 -0
  20. package/src/components/editor/floor-region-controls.tsx +846 -0
  21. package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
  22. package/src/components/editor/floor-surface.test.ts +165 -0
  23. package/src/components/editor/floor-surface.ts +182 -0
  24. package/src/components/editor/floorplan-background-selection.test.ts +11 -2
  25. package/src/components/editor/floorplan-background-selection.ts +1 -3
  26. package/src/components/editor/floorplan-panel.tsx +344 -342
  27. package/src/components/editor/footprint-height-handle.tsx +204 -0
  28. package/src/components/editor/group-actions.ts +54 -2
  29. package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
  30. package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
  31. package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
  32. package/src/components/editor/handles/use-handle-drag.ts +269 -197
  33. package/src/components/editor/index.tsx +294 -169
  34. package/src/components/editor/join-walls-pill.tsx +44 -0
  35. package/src/components/editor/node-action-menu.tsx +68 -70
  36. package/src/components/editor/node-arrow-handles.tsx +25 -6
  37. package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
  38. package/src/components/editor/open-wall-ends-hint.tsx +89 -0
  39. package/src/components/editor/quick-measurement-hud.tsx +3 -2
  40. package/src/components/editor/registry-action-contributions.test.tsx +63 -0
  41. package/src/components/editor/registry-action-contributions.tsx +80 -1
  42. package/src/components/editor/room-controls.tsx +847 -0
  43. package/src/components/editor/room-floor-highlight.tsx +128 -0
  44. package/src/components/editor/room-handles-2d.tsx +354 -0
  45. package/src/components/editor/room-handles.tsx +675 -0
  46. package/src/components/editor/room-highlight.tsx +225 -0
  47. package/src/components/editor/room-labels.test.ts +357 -0
  48. package/src/components/editor/room-labels.tsx +990 -0
  49. package/src/components/editor/room-transform-ghost.tsx +256 -0
  50. package/src/components/editor/room-webgpu-materials.test.ts +27 -0
  51. package/src/components/editor/selection-manager.tsx +451 -317
  52. package/src/components/editor/slab-hole-highlights.tsx +3 -0
  53. package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
  54. package/src/components/editor/snapshot-capture.test.ts +52 -4
  55. package/src/components/editor/snapshot-capture.ts +30 -5
  56. package/src/components/editor/thumbnail-generator.test.ts +308 -0
  57. package/src/components/editor/thumbnail-generator.tsx +1035 -229
  58. package/src/components/editor/use-floorplan-background-placement.ts +31 -11
  59. package/src/components/editor/vector-edge-extractor.tsx +121 -0
  60. package/src/components/editor/views/host-view.tsx +61 -0
  61. package/src/components/editor/views/plugins-view.tsx +670 -0
  62. package/src/components/editor/views/scene-region.tsx +59 -0
  63. package/src/components/editor/views/use-editor-views.ts +57 -0
  64. package/src/components/editor/views/view-bar.tsx +220 -0
  65. package/src/components/editor/wall-justification.test.ts +140 -0
  66. package/src/components/editor/wall-move-side-handles.tsx +43 -142
  67. package/src/components/editor/wall-push-arrow.tsx +92 -0
  68. package/src/components/editor/wall-region-handles.tsx +509 -0
  69. package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
  70. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
  71. package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
  72. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
  73. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
  74. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +113 -102
  75. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
  76. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
  77. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
  78. package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
  79. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
  80. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
  81. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +291 -227
  82. package/src/components/editor-2d/svg-paths.ts +12 -15
  83. package/src/components/procedural-items/inspector.tsx +40 -15
  84. package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
  85. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
  86. package/src/components/systems/zone/zone-system.test.ts +47 -0
  87. package/src/components/systems/zone/zone-system.tsx +29 -68
  88. package/src/components/tools/item/placement-history.test.tsx +474 -0
  89. package/src/components/tools/item/placement-strategies.ts +8 -4
  90. package/src/components/tools/item/use-draft-node.test.tsx +432 -1
  91. package/src/components/tools/item/use-draft-node.ts +166 -95
  92. package/src/components/tools/item/use-placement-coordinator.tsx +93 -46
  93. package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
  94. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
  95. package/src/components/tools/registry/move-registry-node-tool.tsx +3 -1
  96. package/src/components/tools/select/box-select-tool.tsx +2 -12
  97. package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
  98. package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
  99. package/src/components/tools/select/select-candidates.test.ts +37 -4
  100. package/src/components/tools/select/select-candidates.ts +15 -11
  101. package/src/components/tools/shared/cursor-sphere.tsx +36 -12
  102. package/src/components/tools/site/site-boundary-editor.tsx +1 -2
  103. package/src/components/tools/stair/stair-defaults.ts +0 -3
  104. package/src/components/tools/stair/stair-tool.tsx +90 -42
  105. package/src/components/tools/tool-manager.tsx +46 -20
  106. package/src/components/tools/wall/wall-drafting.test.ts +19 -4
  107. package/src/components/tools/zone/zone-tool.tsx +4 -12
  108. package/src/components/ui/action-menu/action-button.tsx +3 -0
  109. package/src/components/ui/action-menu/camera-actions.tsx +2 -1
  110. package/src/components/ui/action-menu/control-modes.tsx +2 -35
  111. package/src/components/ui/action-menu/measurement-control.tsx +3 -10
  112. package/src/components/ui/build-panel/build-panel.tsx +235 -0
  113. package/src/components/ui/command-palette/editor-commands.tsx +4 -30
  114. package/src/components/ui/command-palette/index.tsx +5 -24
  115. package/src/components/ui/controls/finish-swatch.tsx +110 -0
  116. package/src/components/ui/controls/material-paint-panel.tsx +97 -26
  117. package/src/components/ui/controls/material-picker.tsx +4 -1
  118. package/src/components/ui/controls/scene-material-list.tsx +4 -32
  119. package/src/components/ui/controls/select-control.tsx +65 -0
  120. package/src/components/ui/floating-level-selector.tsx +9 -14
  121. package/src/components/ui/helpers/building-helper.tsx +4 -1
  122. package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
  123. package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
  124. package/src/components/ui/helpers/helper-manager.tsx +325 -14
  125. package/src/components/ui/helpers/item-helper.tsx +8 -0
  126. package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
  127. package/src/components/ui/icon-ref.tsx +45 -1
  128. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  129. package/src/components/ui/node-icon-image.tsx +16 -0
  130. package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
  131. package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
  132. package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
  133. package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
  134. package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
  135. package/src/components/ui/panels/multi-field-value.test.ts +26 -3
  136. package/src/components/ui/panels/multi-field-value.ts +6 -3
  137. package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
  138. package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
  139. package/src/components/ui/panels/node-display.ts +8 -3
  140. package/src/components/ui/panels/panel-manager.tsx +103 -68
  141. package/src/components/ui/panels/panel-wrapper.tsx +78 -36
  142. package/src/components/ui/panels/parametric-field-control.tsx +1 -1
  143. package/src/components/ui/panels/parametric-inspector.tsx +4 -4
  144. package/src/components/ui/panels/reference-panel.tsx +2 -3
  145. package/src/components/ui/panels/room-construction-rows.tsx +774 -0
  146. package/src/components/ui/panels/room-name-options.test.ts +55 -0
  147. package/src/components/ui/panels/room-name-options.ts +47 -0
  148. package/src/components/ui/panels/room-openings-field.tsx +110 -0
  149. package/src/components/ui/panels/room-panel.tsx +725 -0
  150. package/src/components/ui/panels/shape-choice.tsx +58 -0
  151. package/src/components/ui/panels/wall-region-list.tsx +109 -0
  152. package/src/components/ui/primitives/color-dot.tsx +36 -24
  153. package/src/components/ui/primitives/sidebar.tsx +2 -0
  154. package/src/components/ui/right-stack.test.ts +29 -0
  155. package/src/components/ui/right-stack.tsx +145 -0
  156. package/src/components/ui/sidebar/icon-rail.tsx +5 -0
  157. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +31 -4
  158. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
  159. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
  160. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
  161. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
  162. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
  163. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
  164. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
  165. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
  166. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
  167. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
  168. package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
  169. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
  170. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
  171. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
  172. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
  173. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
  174. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
  175. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
  176. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
  177. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
  178. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
  179. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
  180. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
  181. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
  182. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
  183. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
  184. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
  185. package/src/components/ui/sidebar/tab-bar.tsx +10 -0
  186. package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
  187. package/src/components/viewer/floorplan-compass-button.tsx +9 -1
  188. package/src/components/viewer/floorplan-preview.tsx +5 -6
  189. package/src/components/viewer/viewer-hover-label.tsx +34 -0
  190. package/src/components/viewer/viewer-rooms.tsx +175 -0
  191. package/src/components/viewer/viewer-scene-header.tsx +181 -145
  192. package/src/components/viewer/viewer-selection-manager.tsx +124 -0
  193. package/src/components/viewer/viewer-stage.tsx +2 -2
  194. package/src/components/viewer-zone-system.tsx +3 -8
  195. package/src/hooks/use-auto-save.integration.test.tsx +123 -9
  196. package/src/hooks/use-auto-save.ts +34 -8
  197. package/src/hooks/use-keyboard.test.ts +178 -3
  198. package/src/hooks/use-keyboard.ts +89 -71
  199. package/src/hooks/use-open-wall-ends.test.ts +99 -0
  200. package/src/hooks/use-open-wall-ends.ts +238 -0
  201. package/src/hooks/use-placement-notice.test.ts +48 -0
  202. package/src/hooks/use-placement-notice.ts +42 -0
  203. package/src/hooks/use-selected-room.ts +143 -0
  204. package/src/index.tsx +246 -39
  205. package/src/lib/ceiling-edit-session.test.ts +321 -0
  206. package/src/lib/ceiling-edit-session.ts +206 -0
  207. package/src/lib/ceiling-region-session.test.ts +179 -0
  208. package/src/lib/continuation.test.ts +19 -1
  209. package/src/lib/continuation.ts +25 -4
  210. package/src/lib/door-interaction.ts +14 -0
  211. package/src/lib/editor-level-display.test.ts +96 -0
  212. package/src/lib/editor-level-display.ts +48 -0
  213. package/src/lib/editor-views.ts +144 -0
  214. package/src/lib/export-geometry-wait.test.ts +90 -0
  215. package/src/lib/floor-edit-session.ts +153 -0
  216. package/src/lib/floor-footprints.test.ts +563 -0
  217. package/src/lib/floor-footprints.ts +395 -0
  218. package/src/lib/floor-foundation-support.test.ts +283 -0
  219. package/src/lib/floor-opening-draft.ts +218 -0
  220. package/src/lib/floor-openings.test.ts +216 -0
  221. package/src/lib/floor-region-geometry.ts +191 -0
  222. package/src/lib/floor-region-session.test.ts +485 -0
  223. package/src/lib/floor-region-session.ts +589 -0
  224. package/src/lib/floor-region-snap.ts +101 -0
  225. package/src/lib/floor-step-hover.test.ts +32 -0
  226. package/src/lib/floorplan/annotation-visibility.ts +17 -0
  227. package/src/lib/floorplan/dimension-drive.test.ts +360 -0
  228. package/src/lib/floorplan/dimension-drive.ts +479 -0
  229. package/src/lib/floorplan/floorplan-export.test.ts +12 -8
  230. package/src/lib/floorplan/floorplan-export.tsx +32 -8
  231. package/src/lib/floorplan/floorplan-extension.ts +32 -0
  232. package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
  233. package/src/lib/floorplan/floorplan-mode.ts +2 -0
  234. package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
  235. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
  236. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
  237. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
  238. package/src/lib/floorplan/floorplan-readonly.ts +6 -0
  239. package/src/lib/floorplan/geometry.ts +13 -6
  240. package/src/lib/floorplan/index.ts +0 -9
  241. package/src/lib/floorplan/items.ts +5 -2
  242. package/src/lib/floorplan/marks.test.ts +213 -0
  243. package/src/lib/floorplan/marks.ts +238 -0
  244. package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
  245. package/src/lib/floorplan/open-wall-ends.ts +162 -0
  246. package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
  247. package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
  248. package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
  249. package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
  250. package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
  251. package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
  252. package/src/lib/floorplan/schedules.test.ts +189 -0
  253. package/src/lib/floorplan/schedules.ts +339 -0
  254. package/src/lib/floorplan/sheet-export.test.ts +100 -0
  255. package/src/lib/floorplan/sheet-export.ts +318 -0
  256. package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
  257. package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
  258. package/src/lib/floorplan/site-plan/contributors.ts +97 -0
  259. package/src/lib/floorplan/site-plan/coverage.ts +232 -0
  260. package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
  261. package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
  262. package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
  263. package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
  264. package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
  265. package/src/lib/floorplan/site-plan/geometry.ts +557 -0
  266. package/src/lib/floorplan/site-plan/index.ts +71 -0
  267. package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
  268. package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
  269. package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
  270. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
  271. package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
  272. package/src/lib/floorplan/site-plan/street-name.ts +302 -0
  273. package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
  274. package/src/lib/floorplan/types.ts +1 -27
  275. package/src/lib/fresh-planar-placement.test.ts +36 -0
  276. package/src/lib/fresh-planar-placement.ts +59 -36
  277. package/src/lib/geometry-script/author.ts +152 -0
  278. package/src/lib/geometry-script/client.ts +152 -0
  279. package/src/lib/geometry-script/protocol.ts +12 -0
  280. package/src/lib/gesture-lifecycle.test.ts +595 -0
  281. package/src/lib/gesture-lifecycle.ts +290 -0
  282. package/src/lib/glb-export-zone-holes.test.ts +41 -0
  283. package/src/lib/glb-export.test.ts +485 -0
  284. package/src/lib/glb-export.ts +237 -119
  285. package/src/lib/history.test.ts +6 -0
  286. package/src/lib/history.ts +7 -0
  287. package/src/lib/hud-title.ts +109 -0
  288. package/src/lib/hydration-gated-storage.ts +49 -0
  289. package/src/lib/ifc-export.test.ts +174 -0
  290. package/src/lib/ifc-export.ts +201 -0
  291. package/src/lib/ifc-parts.ts +15 -0
  292. package/src/lib/inspector-expanded.test.ts +44 -0
  293. package/src/lib/inspector-expanded.ts +36 -0
  294. package/src/lib/interaction/scope.ts +18 -0
  295. package/src/lib/keyboard-pan.test.ts +129 -7
  296. package/src/lib/keyboard-pan.ts +161 -12
  297. package/src/lib/live-gesture-round-trip.test.ts +360 -0
  298. package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
  299. package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
  300. package/src/lib/lot/clean-ring.test.ts +186 -0
  301. package/src/lib/lot/clean-ring.ts +217 -0
  302. package/src/lib/lot/dossier.test.ts +202 -0
  303. package/src/lib/lot/dossier.ts +498 -0
  304. package/src/lib/lot/drop-in.test.ts +326 -0
  305. package/src/lib/lot/drop-in.ts +407 -0
  306. package/src/lib/lot/index.ts +43 -0
  307. package/src/lib/lot/lot-patch.test.ts +340 -0
  308. package/src/lib/lot/lot-patch.ts +394 -0
  309. package/src/lib/lot/parcel-provider.ts +43 -0
  310. package/src/lib/lot/terrain.test.ts +161 -0
  311. package/src/lib/lot/terrain.ts +266 -0
  312. package/src/lib/material-catalog-model.ts +3 -1
  313. package/src/lib/material-paint-panel-model.ts +9 -5
  314. package/src/lib/material-paint.ts +4 -0
  315. package/src/lib/mezzanine-actions.test.ts +342 -0
  316. package/src/lib/mezzanine-draft.test.ts +501 -0
  317. package/src/lib/mezzanine-draft.ts +278 -0
  318. package/src/lib/mezzanine-messages.ts +26 -0
  319. package/src/lib/mezzanine-selection.test.ts +207 -0
  320. package/src/lib/mezzanine-stairs.ts +46 -0
  321. package/src/lib/model-export.ts +3 -1
  322. package/src/lib/node-action-movement.ts +17 -6
  323. package/src/lib/node-display-label.ts +37 -0
  324. package/src/lib/orbit-floor-clearance.test.ts +165 -0
  325. package/src/lib/orbit-floor-clearance.ts +106 -0
  326. package/src/lib/paint-commit-route.test.ts +37 -0
  327. package/src/lib/paint-commit-route.ts +17 -0
  328. package/src/lib/paint-eyedropper.test.tsx +165 -0
  329. package/src/lib/paint-eyedropper.ts +99 -0
  330. package/src/lib/paint-pass-through.test.ts +111 -0
  331. package/src/lib/paint-pass-through.ts +76 -0
  332. package/src/lib/paint-region-hover.ts +25 -0
  333. package/src/lib/paint-region-mode.ts +154 -0
  334. package/src/lib/paint-regions.test.ts +166 -0
  335. package/src/lib/paint-regions.ts +192 -0
  336. package/src/lib/paint-scope.test.ts +317 -35
  337. package/src/lib/paint-scope.ts +266 -25
  338. package/src/lib/panel-rows.ts +21 -0
  339. package/src/lib/parametric-node-update.ts +1 -1
  340. package/src/lib/plate-paint-affected.test.ts +300 -0
  341. package/src/lib/plate-paint-affected.ts +177 -0
  342. package/src/lib/plugin-chat.ts +50 -0
  343. package/src/lib/plugin-panels.test.ts +25 -0
  344. package/src/lib/plugin-panels.ts +94 -0
  345. package/src/lib/print-shell-compiler.ts +1 -0
  346. package/src/lib/registered-draft-snap.ts +12 -0
  347. package/src/lib/roof-duplication.ts +10 -2
  348. package/src/lib/room-assembly-overlay.test.ts +275 -0
  349. package/src/lib/room-assembly-overlay.ts +266 -0
  350. package/src/lib/room-built-on.test.ts +379 -0
  351. package/src/lib/room-built-on.ts +138 -0
  352. package/src/lib/room-construction-commands.ts +712 -0
  353. package/src/lib/room-construction.test.ts +746 -0
  354. package/src/lib/room-construction.ts +385 -0
  355. package/src/lib/room-controls-phase6.test.ts +630 -0
  356. package/src/lib/room-divide-session.ts +523 -0
  357. package/src/lib/room-handle-drag.ts +1068 -0
  358. package/src/lib/room-name-catalog.test.ts +90 -0
  359. package/src/lib/room-name-catalog.ts +168 -0
  360. package/src/lib/room-push-dimensions.test.ts +311 -0
  361. package/src/lib/room-push-dimensions.ts +251 -0
  362. package/src/lib/room-selection-commands.ts +62 -0
  363. package/src/lib/room-selection.test.ts +973 -0
  364. package/src/lib/room-selection.ts +417 -0
  365. package/src/lib/room-structure-commands.ts +94 -0
  366. package/src/lib/room-structure.test.ts +921 -0
  367. package/src/lib/room-transform-session.ts +570 -0
  368. package/src/lib/room-zone-routing.test.ts +294 -0
  369. package/src/lib/room-zone-routing.ts +197 -0
  370. package/src/lib/scene-clipboard.test.ts +364 -0
  371. package/src/lib/scene-clipboard.ts +229 -48
  372. package/src/lib/scene-material-refs.test.ts +79 -0
  373. package/src/lib/scene-material-refs.ts +79 -0
  374. package/src/lib/scene.test.ts +24 -36
  375. package/src/lib/scene.ts +33 -57
  376. package/src/lib/selection-routing.test.ts +129 -45
  377. package/src/lib/selection-routing.ts +53 -74
  378. package/src/lib/session-writes.test.ts +120 -0
  379. package/src/lib/session-writes.ts +205 -0
  380. package/src/lib/sidebar-panel.ts +62 -0
  381. package/src/lib/site-boundary.test.ts +4 -6
  382. package/src/lib/site-boundary.ts +1 -1
  383. package/src/lib/snapping-mode.test.ts +4 -0
  384. package/src/lib/snapping-mode.ts +8 -1
  385. package/src/lib/terrace-draft.test.ts +145 -0
  386. package/src/lib/terrace-draft.ts +213 -0
  387. package/src/lib/tool-cursor-icon.test.ts +16 -0
  388. package/src/lib/tool-cursor-icon.ts +11 -0
  389. package/src/lib/typing-target.ts +9 -0
  390. package/src/lib/units.ts +77 -39
  391. package/src/lib/vector-edges.test.ts +58 -0
  392. package/src/lib/vector-edges.ts +400 -0
  393. package/src/lib/viewer-selection.test.ts +577 -0
  394. package/src/lib/viewer-selection.ts +569 -0
  395. package/src/lib/wall-draw-variant.test.ts +71 -0
  396. package/src/lib/wall-draw-variant.ts +91 -0
  397. package/src/lib/wall-paint-region-session.ts +246 -0
  398. package/src/lib/wall-paint-region.test.ts +373 -0
  399. package/src/lib/wall-polygon-draft.test.ts +262 -0
  400. package/src/lib/wall-polygon-draft.ts +286 -0
  401. package/src/lib/wall-push.test.ts +453 -0
  402. package/src/lib/wall-region-face.ts +352 -0
  403. package/src/lib/wall-region-handles.test.ts +304 -0
  404. package/src/lib/wall-region-handles.ts +368 -0
  405. package/src/lib/wall-region-list.test.ts +119 -0
  406. package/src/lib/wall-region-list.ts +136 -0
  407. package/src/lib/wall-region-snap.ts +110 -0
  408. package/src/lib/window-interaction.ts +14 -0
  409. package/src/lib/zone-content.ts +4 -1
  410. package/src/lib/zone-name.test.ts +17 -0
  411. package/src/lib/zone-name.ts +17 -0
  412. package/src/store/opens-in-select-mode.test.ts +223 -0
  413. package/src/store/paint-mode.test.ts +188 -0
  414. package/src/store/terrain-sculpt-mode.test.ts +19 -21
  415. package/src/store/tool-mode.test.ts +137 -6
  416. package/src/store/use-delete-confirmation.ts +28 -1
  417. package/src/store/use-drawing-view.test.ts +24 -3
  418. package/src/store/use-drawing-view.ts +29 -1
  419. package/src/store/use-editor.tsx +432 -321
  420. package/src/store/use-floorplan-draft-preview.ts +13 -0
  421. package/src/store/use-floorplan-marquee.ts +12 -2
  422. package/src/store/use-quick-measurement-hud.ts +3 -3
  423. package/src/store/use-stair-build-preview.ts +13 -5
  424. package/src/store/use-wall-move-ghosts.ts +4 -2
  425. package/src/store/use-wall-region-handles-selection.test.ts +222 -0
  426. package/src/store/use-wall-region-handles-selection.ts +139 -0
  427. package/src/store/view-layout.test.ts +175 -0
  428. package/src/store/view-layout.ts +196 -0
  429. package/src/test-utils/immediate-animation-frames.ts +16 -0
  430. package/src/test-utils/live-gesture-round-trip.ts +224 -0
  431. package/src/test-utils/selection-harness.ts +82 -0
  432. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
  433. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
  434. package/src/components/tools/fence/fence-drafting.test.ts +0 -100
  435. package/src/components/tools/fence/fence-drafting.ts +0 -266
  436. package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
  437. package/src/components/viewer/viewer-units-panel.tsx +0 -116
  438. package/src/lib/floorplan/stairs.ts +0 -489
  439. package/src/lib/level-duplication.test.ts +0 -144
  440. package/src/lib/level-duplication.ts +0 -195
@@ -3,6 +3,7 @@ import {
3
3
  type AlignmentAnchor,
4
4
  type AnyNode,
5
5
  type AnyNodeId,
6
+ beginSceneHistoryPauseSession,
6
7
  type CeilingEvent,
7
8
  canHostSurfaceChild,
8
9
  collectAlignmentAnchors,
@@ -10,6 +11,8 @@ import {
10
11
  findLevelAncestorId,
11
12
  type GridEvent,
12
13
  getScaledDimensions,
14
+ getWallBodyCenterOffset,
15
+ getWallLocalFaceZ,
13
16
  type ItemEvent,
14
17
  movingFootprintAnchors,
15
18
  type NodeEvent,
@@ -25,7 +28,6 @@ import {
25
28
  useScene,
26
29
  useSpatialQuery,
27
30
  type WallEvent,
28
- type WallNode,
29
31
  } from '@pascal-app/core'
30
32
  import { useViewer } from '@pascal-app/viewer'
31
33
  import { Html } from '@react-three/drei'
@@ -55,7 +57,7 @@ import { sfxEmitter } from '../../../lib/sfx-bus'
55
57
  import {
56
58
  surfaceAttachmentId,
57
59
  surfaceFramePose,
58
- updateSurfaceNode,
60
+ updateSurfaceNode as writeSurfaceNode,
59
61
  } from '../../../lib/surface-attachment'
60
62
  import {
61
63
  projectAlignmentGuidesWorldToActiveBuildingLocal,
@@ -68,6 +70,7 @@ import useEditor, {
68
70
  isMagneticSnapActive,
69
71
  } from '../../../store/use-editor'
70
72
  import useFacingPose from '../../../store/use-facing-pose'
73
+ import { getMovingNode } from '../../../store/use-interaction-scope'
71
74
  import usePlacementPreview from '../../../store/use-placement-preview'
72
75
  import {
73
76
  createItemSurfaceGridDispatch,
@@ -116,7 +119,7 @@ import {
116
119
  } from './placement-strategies'
117
120
  import { resolveItemPlacementSurfaceNormal } from './placement-surface'
118
121
  import type { PlacementState, TransitionResult } from './placement-types'
119
- import type { DraftNodeHandle } from './use-draft-node'
122
+ import { type DraftNodeHandle, type ItemCopy, pausedDraftWrite } from './use-draft-node'
120
123
 
121
124
  const DEFAULT_DIMENSIONS: [number, number, number] = [1, 1, 1]
122
125
 
@@ -262,20 +265,20 @@ basePlaneMaterial.opacityNode = radialOpacity
262
265
 
263
266
  export interface PlacementCoordinatorConfig {
264
267
  asset: AssetInput | null
268
+ /** Scale to use when lazily creating a draft (e.g. for wall/ceiling duplicates). Defaults to [1,1,1]. */
269
+ defaultScale?: [number, number, number]
265
270
  draftNode: DraftNodeHandle
266
271
  initDraft: (gridPosition: Vector3) => void
267
- onCommitted: () => boolean
268
- onCancel?: () => void
269
272
  initialState?: PlacementState
270
- /** Scale to use when lazily creating a draft (e.g. for wall/ceiling duplicates). Defaults to [1,1,1]. */
271
- defaultScale?: [number, number, number]
272
- /** Painted slot overrides to seed onto a lazily-created draft (wall/ceiling
273
- * duplicates) so the duplicate keeps its materials. */
274
- slots?: ItemNode['slots']
273
+ onCancel?: () => void
274
+ onCommitted: () => boolean
275
275
  /** Move-mode sessions keep the grabbed item offset from the first surface hit
276
276
  * (floor / wall / ceiling / item-surface / shelf) instead of snapping the
277
277
  * item's origin under the cursor. */
278
278
  preserveDragOffset?: boolean
279
+ /** The item a duplicate copies, for a lazily-created draft (wall/ceiling
280
+ * duplicates) to keep its materials and collections. */
281
+ copied?: ItemCopy
279
282
  }
280
283
 
281
284
  export function usePlacementCoordinator(config: PlacementCoordinatorConfig): React.ReactNode {
@@ -528,7 +531,14 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
528
531
 
529
532
  useEffect(() => {
530
533
  if (!asset) return
531
- useScene.temporal.getState().pause()
534
+ // History is paused only around the placement's own writes, never for the whole carry:
535
+ // the draft stays out of history snapshots (core's history-drafts), so writes others make
536
+ // mid-carry record as their own steps, with space detection. In split view the 2D move
537
+ // overlay co-owns the gesture (keyed by the moving node), and the drop's commitStep lifts
538
+ // its pause too.
539
+ const movingNodeId = getMovingNode()?.id
540
+ const updateSurfaceNode: typeof writeSurfaceNode = (...args) =>
541
+ pausedDraftWrite(() => writeSurfaceNode(...args))
532
542
 
533
543
  const validators = { canPlaceOnFloor, canPlaceOnWall, canPlaceOnCeiling }
534
544
 
@@ -608,7 +618,6 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
608
618
  }
609
619
 
610
620
  useAlignmentGuides.getState().clear()
611
- useScene.temporal.getState().resume()
612
621
  if (committedId) {
613
622
  useViewer.getState().setSelection({ selectedIds: [committedId as AnyNodeId] })
614
623
  }
@@ -631,14 +640,22 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
631
640
  ) => {
632
641
  const draftId = draftNode.current?.id ?? null
633
642
  const wasAdopted = draftNode.isAdopted
634
- const finalId = draftNode.commit(nodeUpdate, {
635
- ...options,
636
- onReject: (reason) => {
637
- feedback.reject(reason)
638
- edgeMaterial.color.setHex(0xef_44_44)
639
- basePlaneMaterial.color.setHex(0xef_44_44)
640
- },
641
- })
643
+ const drop = beginSceneHistoryPauseSession(useScene, { gesture: movingNodeId })
644
+ let finalId: string | null
645
+ try {
646
+ finalId = drop.commitStep(() =>
647
+ draftNode.commit(nodeUpdate, {
648
+ ...options,
649
+ onReject: (reason) => {
650
+ feedback.reject(reason)
651
+ edgeMaterial.color.setHex(0xef_44_44)
652
+ basePlaneMaterial.color.setHex(0xef_44_44)
653
+ },
654
+ }),
655
+ )
656
+ } finally {
657
+ drop.end()
658
+ }
642
659
  if (finalId && draftId) {
643
660
  useLiveTransforms.getState().clear(draftId)
644
661
  useLiveNodeOverrides.getState().clearFields(draftId, faceHostClearFields(draftNode.current))
@@ -832,16 +849,15 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
832
849
  asset,
833
850
  initRotation,
834
851
  configRef.current.defaultScale,
835
- configRef.current.slots,
852
+ configRef.current.copied,
836
853
  )
837
854
 
838
855
  const draft = draftNode.current
839
856
  if (draft) {
840
857
  Object.assign(draft, result.nodeUpdate)
841
858
  // One-time setup: put node in the right parent so it renders correctly
842
- if (result.surfaceId !== undefined)
843
- draftNode.updateSurface(result.nodeUpdate, result.surfaceId)
844
- else updateSurfaceNode(draft.id, result.nodeUpdate)
859
+ if (result.surfaceId === undefined) updateSurfaceNode(draft.id, result.nodeUpdate)
860
+ else draftNode.updateSurface(result.nodeUpdate, result.surfaceId)
845
861
  disableDraftRaycastNow()
846
862
  }
847
863
 
@@ -1276,7 +1292,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
1276
1292
  asset,
1277
1293
  currentRotation,
1278
1294
  configRef.current.defaultScale,
1279
- configRef.current.slots,
1295
+ configRef.current.copied,
1280
1296
  )
1281
1297
  const previewBounds = expandBoundsToGrid(
1282
1298
  getFallbackPreviewBounds(draftNode.current, asset, asset.attachTo),
@@ -1427,12 +1443,13 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
1427
1443
  const rot = result.nodeUpdate?.rotation
1428
1444
  if (rot) mesh.rotation.y = rot[1]
1429
1445
 
1430
- // Push wall-side items out by half the parent wall's thickness
1431
- if (asset.attachTo === 'wall-side' && placementState.current.wallId) {
1446
+ if (placementState.current.wallId) {
1432
1447
  const parentWall = useScene.getState().nodes[placementState.current.wallId as AnyNodeId]
1433
1448
  if (parentWall?.type === 'wall') {
1434
- const wallThickness = (parentWall as WallNode).thickness ?? 0.1
1435
- mesh.position.z = (wallThickness / 2) * (draft.side === 'front' ? 1 : -1)
1449
+ mesh.position.z =
1450
+ asset.attachTo === 'wall-side'
1451
+ ? getWallLocalFaceZ(parentWall, draft.side === 'front' ? 'a' : 'b')
1452
+ : draft.position[2] + getWallBodyCenterOffset(parentWall)
1436
1453
  }
1437
1454
  }
1438
1455
  }
@@ -1600,7 +1617,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
1600
1617
  const segment =
1601
1618
  useScene.getState().nodes[placementState.current.roofSegmentId as AnyNodeId]
1602
1619
  if (segment?.type === 'roof-segment') {
1603
- mesh.position.z = (segment.wallThickness ?? 0.1) / 2
1620
+ mesh.position.z = getWallLocalFaceZ({ thickness: segment.wallThickness ?? 0.1 }, 'a')
1604
1621
  }
1605
1622
  }
1606
1623
  const rot = result.nodeUpdate?.rotation
@@ -1640,10 +1657,12 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
1640
1657
  applyTransition(result)
1641
1658
  const draft = draftNode.current
1642
1659
  if (draft) {
1643
- useScene.getState().updateNode(draft.id, {
1644
- parentId: result.nodeUpdate.parentId as string,
1645
- roofSegmentId: undefined,
1646
- })
1660
+ pausedDraftWrite(() =>
1661
+ useScene.getState().updateNode(draft.id, {
1662
+ parentId: result.nodeUpdate.parentId as string,
1663
+ roofSegmentId: undefined,
1664
+ }),
1665
+ )
1647
1666
  }
1648
1667
  } else {
1649
1668
  // Create mode: destroy transient and reset state
@@ -1758,7 +1777,8 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
1758
1777
  }
1759
1778
  if (draftNode.isAdopted) {
1760
1779
  applyTransition(result)
1761
- if (draft) useScene.getState().updateNode(draft.id, result.nodeUpdate)
1780
+ if (draft)
1781
+ pausedDraftWrite(() => useScene.getState().updateNode(draft.id, result.nodeUpdate))
1762
1782
  } else {
1763
1783
  draftNode.destroy()
1764
1784
  gridPosition.current.set(...result.gridPosition)
@@ -1863,6 +1883,8 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
1863
1883
  }
1864
1884
  if (event.node.id === draftNode.current?.id) return
1865
1885
  has3DPointerDrivenMoveRef.current = true
1886
+ // An authored object's underside takes ceiling items (a pendant under a vault).
1887
+ if (enterFaceHost(event)) return
1866
1888
  const result = itemSurfaceStrategy.enter(surfaceContext(event, generic), event)
1867
1889
  if (!result) return
1868
1890
  feedback.clear()
@@ -1880,6 +1902,17 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
1880
1902
 
1881
1903
  const onItemMove = (event: NodeEvent<AnyNode>, generic = false) => {
1882
1904
  if (event.node.id === draftNode.current?.id) return
1905
+ if (
1906
+ placementState.current.surface === 'block-face' &&
1907
+ placementState.current.blockId === event.node.id
1908
+ ) {
1909
+ onFaceHostMove(event)
1910
+ return
1911
+ }
1912
+ if (placementState.current.surface !== 'block-face' && enterFaceHost(event)) {
1913
+ releaseCommit = () => onFaceHostClick(event)
1914
+ return
1915
+ }
1883
1916
  releaseCommit = () => onItemClick(event)
1884
1917
  has3DPointerDrivenMoveRef.current = true
1885
1918
  if (!cursorGroupRef.current) return
@@ -1931,9 +1964,9 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
1931
1964
  }
1932
1965
 
1933
1966
  const surfaceWorld =
1934
- ctx.state.surfaceItemId !== null
1935
- ? resolveHostSurfaceWorld(ctx.state.surfaceItemId, event.position)
1936
- : null
1967
+ ctx.state.surfaceItemId === null
1968
+ ? null
1969
+ : resolveHostSurfaceWorld(ctx.state.surfaceItemId, event.position)
1937
1970
  const itemMoveEvent = surfaceWorld ? { ...event, position: surfaceWorld } : event
1938
1971
  lastRawPos.current.set(
1939
1972
  itemMoveEvent.position[0],
@@ -1980,6 +2013,13 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
1980
2013
  const onItemLeave = (event: NodeEvent<AnyNode>) => {
1981
2014
  if (event.node.type === 'cabinet') return
1982
2015
  if (event.node.id === draftNode.current?.id) return
2016
+ if (
2017
+ placementState.current.surface === 'block-face' &&
2018
+ placementState.current.blockId === event.node.id
2019
+ ) {
2020
+ onFaceHostLeave(event)
2021
+ return
2022
+ }
1983
2023
  if (placementState.current.surface !== 'item-surface') return
1984
2024
 
1985
2025
  event.stopPropagation()
@@ -1995,6 +2035,13 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
1995
2035
  const onItemClick = (event: NodeEvent<AnyNode>) => {
1996
2036
  gridDispatch.flush()
1997
2037
  if (feedback.reason) return
2038
+ if (
2039
+ placementState.current.surface === 'block-face' &&
2040
+ placementState.current.blockId === event.node.id
2041
+ ) {
2042
+ onFaceHostClick(event)
2043
+ return
2044
+ }
1998
2045
  // Click on the draft item itself. R3F dispatches click events to
1999
2046
  // the closest intersected mesh only — when the draft is hovering
2000
2047
  // on a host (shelf / table / etc.) the draft's mesh is *above*
@@ -2309,9 +2356,9 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
2309
2356
  return
2310
2357
  }
2311
2358
  const shelfWorld =
2312
- ctx.state.shelfId !== null
2313
- ? resolveHostSurfaceWorld(ctx.state.shelfId, event.position)
2314
- : null
2359
+ ctx.state.shelfId === null
2360
+ ? null
2361
+ : resolveHostSurfaceWorld(ctx.state.shelfId, event.position)
2315
2362
  const shelfMoveEvent = shelfWorld ? { ...event, position: shelfWorld } : event
2316
2363
  const result = shelfSurfaceStrategy.move(ctx, shelfMoveEvent)
2317
2364
  if (!result) return
@@ -2613,7 +2660,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
2613
2660
  if (event.button !== 2) return
2614
2661
  const down = rightDown
2615
2662
  rightDown = null
2616
- if (!down || !configRef.current.onCancel) return
2663
+ if (!(down && configRef.current.onCancel)) return
2617
2664
  const movedSq = (event.clientX - down.x) ** 2 + (event.clientY - down.y) ** 2
2618
2665
  const elapsed = performance.now() - down.t
2619
2666
  if (movedSq <= RIGHT_CLICK_CANCEL_MAX_MOVE_PX ** 2 && elapsed <= RIGHT_CLICK_CANCEL_MAX_MS) {
@@ -2662,8 +2709,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
2662
2709
  const draft = draftNode.current
2663
2710
  if (draft === null) return
2664
2711
  if (draft.id in useScene.getState().nodes) return
2665
- // Temporal is paused during placement, createNode won't be tracked
2666
- useScene.getState().createNode(draft, draft.parentId as AnyNodeId)
2712
+ pausedDraftWrite(() => useScene.getState().createNode(draft, draft.parentId as AnyNodeId))
2667
2713
  })
2668
2714
  })
2669
2715
 
@@ -2765,7 +2811,6 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
2765
2811
  useLiveNodeOverrides.getState().clearFields(draftNode.current.id, ['rotation'])
2766
2812
  }
2767
2813
  draftNode.destroy()
2768
- useScene.temporal.getState().resume()
2769
2814
  gridDispatch.cancel()
2770
2815
  emitter.off('grid:move', gridDispatch.schedule)
2771
2816
  emitter.off('grid:click', onGridClick)
@@ -2862,7 +2907,9 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
2862
2907
  )
2863
2908
  return
2864
2909
  draft.parentId = viewerLevelId
2865
- useScene.getState().updateNode(draft.id as AnyNodeId, { parentId: viewerLevelId })
2910
+ pausedDraftWrite(() =>
2911
+ useScene.getState().updateNode(draft.id as AnyNodeId, { parentId: viewerLevelId }),
2912
+ )
2866
2913
  }, [viewerLevelId, draftNode, asset])
2867
2914
 
2868
2915
  // Restore the draft mesh's raycast when the coordinator unmounts (tool change).
@@ -0,0 +1,37 @@
1
+ import {
2
+ getCatalogMaterialById,
3
+ parseMaterialRef,
4
+ type SceneMaterialId,
5
+ useScene,
6
+ } from '@pascal-app/core'
7
+ import { useMemo } from 'react'
8
+ import { usePaintRegionMode } from '../../../lib/paint-region-mode'
9
+ import useEditor from '../../../store/use-editor'
10
+
11
+ /**
12
+ * The paint material's colour for a region preview: a library preset's
13
+ * preview colour, a scene material's colour, or a plain material colour;
14
+ * `fallback` for the eraser or a material with no cheap colour.
15
+ */
16
+ export function usePaintTint(fallback: string): string {
17
+ const active = useEditor((state) => state.activePaintMaterial)
18
+ const eraser = usePaintRegionMode((state) => state.mode) === 'erase'
19
+ const materials = useScene((state) => state.materials)
20
+ return useMemo(() => {
21
+ if (eraser) return fallback
22
+ const parsed = parseMaterialRef(active?.materialPreset)
23
+ if (parsed?.kind === 'library') {
24
+ const color = getCatalogMaterialById(parsed.id)?.previewColor
25
+ if (color) return color
26
+ }
27
+ if (parsed?.kind === 'scene') {
28
+ const color = materials[parsed.id as SceneMaterialId]?.material.properties?.color
29
+ if (color) return color
30
+ }
31
+ return (
32
+ active?.material?.properties?.color ??
33
+ getCatalogMaterialById(active?.material?.id)?.previewColor ??
34
+ fallback
35
+ )
36
+ }, [active, eraser, materials, fallback])
37
+ }