@pascal-app/editor 0.8.0 → 0.9.2

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 (350) hide show
  1. package/package.json +15 -13
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +168 -0
  3. package/src/components/editor/bake-exporter.tsx +47 -0
  4. package/src/components/editor/custom-camera-controls.tsx +795 -28
  5. package/src/components/editor/editor-layout-mobile.tsx +8 -3
  6. package/src/components/editor/editor-layout-v2.tsx +76 -51
  7. package/src/components/editor/export-manager.tsx +69 -32
  8. package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
  9. package/src/components/editor/first-person/build-collider-world.test.ts +182 -0
  10. package/src/components/editor/first-person/build-collider-world.ts +228 -83
  11. package/src/components/editor/first-person-controls.tsx +943 -27
  12. package/src/components/editor/floating-action-menu.tsx +676 -208
  13. package/src/components/editor/floating-building-action-menu.tsx +2 -2
  14. package/src/components/editor/floorplan-background-selection.test.ts +58 -0
  15. package/src/components/editor/floorplan-background-selection.ts +3 -2
  16. package/src/components/editor/floorplan-panel.tsx +6916 -12137
  17. package/src/components/editor/grid.tsx +197 -36
  18. package/src/components/editor/group-actions.ts +457 -0
  19. package/src/components/editor/group-floating-action-menu.tsx +130 -0
  20. package/src/components/editor/group-move-3d.ts +397 -0
  21. package/src/components/editor/group-rotate-handle.tsx +420 -0
  22. package/src/components/editor/group-selection-box-3d.tsx +173 -0
  23. package/src/components/editor/group-transform-shared.test.ts +396 -0
  24. package/src/components/editor/group-transform-shared.ts +489 -0
  25. package/src/components/editor/handles/handle-arrow.tsx +581 -0
  26. package/src/components/editor/handles/use-handle-drag.ts +246 -0
  27. package/src/components/editor/index.tsx +412 -162
  28. package/src/components/editor/measurement-pill.tsx +115 -0
  29. package/src/components/editor/node-action-menu.tsx +14 -1
  30. package/src/components/editor/node-arrow-handles.tsx +1646 -0
  31. package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
  32. package/src/components/editor/quick-measurement-card.tsx +70 -0
  33. package/src/components/editor/quick-measurement-hud.tsx +28 -0
  34. package/src/components/editor/riser-diagram-panel.tsx +137 -0
  35. package/src/components/editor/selection-manager.tsx +1106 -483
  36. package/src/components/editor/site-edge-labels.tsx +45 -21
  37. package/src/components/editor/slab-hole-highlights.tsx +474 -0
  38. package/src/components/editor/snapshot-capture-overlay.tsx +369 -155
  39. package/src/components/editor/three-context-bridge.ts +22 -0
  40. package/src/components/editor/thumbnail-generator.tsx +140 -41
  41. package/src/components/editor/use-floorplan-background-placement.ts +190 -35
  42. package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
  43. package/src/components/editor/use-floorplan-scene-data.ts +19 -7
  44. package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
  45. package/src/components/editor/wall-measurement-label.tsx +14 -19
  46. package/src/components/editor/wall-move-side-handles.tsx +870 -0
  47. package/src/components/editor/wall-opening-highlights.tsx +181 -0
  48. package/src/components/editor/wall-snap-beacon-layer.tsx +304 -0
  49. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +15 -6
  50. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
  51. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
  52. package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
  53. package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
  54. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
  55. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
  56. package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
  57. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +392 -0
  58. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +857 -0
  59. package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
  60. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +77 -0
  61. package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
  62. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
  63. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
  64. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
  65. package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
  66. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +62 -0
  67. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
  68. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +3325 -0
  69. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
  70. package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
  71. package/src/components/editor-2d/svg-paths.ts +10 -2
  72. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +439 -57
  73. package/src/components/systems/ceiling/ceiling-system.tsx +109 -12
  74. package/src/components/systems/roof/roof-edit-system.tsx +1851 -22
  75. package/src/components/systems/selection-affordance-manager.tsx +38 -0
  76. package/src/components/systems/stair/stair-edit-system.tsx +1 -1
  77. package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
  78. package/src/components/systems/zone/zone-system.tsx +42 -13
  79. package/src/components/tools/elevator/elevator-defaults.ts +8 -0
  80. package/src/components/tools/elevator/elevator-tool.tsx +375 -0
  81. package/src/components/tools/elevator/move-elevator-tool.tsx +263 -0
  82. package/src/components/tools/fence/fence-drafting.ts +141 -27
  83. package/src/components/tools/item/move-tool.tsx +45 -105
  84. package/src/components/tools/item/placement-math.test.ts +22 -0
  85. package/src/components/tools/item/placement-math.ts +59 -7
  86. package/src/components/tools/item/placement-strategies.ts +432 -16
  87. package/src/components/tools/item/placement-types.ts +22 -1
  88. package/src/components/tools/item/use-draft-node.ts +51 -2
  89. package/src/components/tools/item/use-placement-coordinator.tsx +1399 -256
  90. package/src/components/tools/registry/move-registry-node-tool.tsx +1064 -0
  91. package/src/components/tools/roof/roof-tool.tsx +350 -28
  92. package/src/components/tools/select/box-select-state.ts +88 -0
  93. package/src/components/tools/select/box-select-tool.tsx +442 -520
  94. package/src/components/tools/select/marquee-geometry.test.ts +161 -0
  95. package/src/components/tools/select/marquee-geometry.ts +155 -0
  96. package/src/components/tools/select/plane-box-select-tool.tsx +570 -0
  97. package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
  98. package/src/components/tools/select/select-candidates.test.ts +109 -0
  99. package/src/components/tools/select/select-candidates.ts +85 -0
  100. package/src/components/tools/shared/affordance-dispatch.ts +30 -0
  101. package/src/components/tools/shared/cursor-sphere.tsx +62 -26
  102. package/src/components/tools/shared/drag-bounding-box.tsx +195 -0
  103. package/src/components/tools/shared/facing-indicator.tsx +87 -0
  104. package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
  105. package/src/components/tools/shared/floor-stack-preview.ts +25 -0
  106. package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
  107. package/src/components/tools/shared/placement-box-geometry.ts +123 -0
  108. package/src/components/tools/shared/placement-box.tsx +305 -0
  109. package/src/components/tools/shared/polygon-editor.tsx +866 -175
  110. package/src/components/tools/shared/segment-angle.ts +56 -0
  111. package/src/components/tools/site/site-boundary-editor.tsx +520 -16
  112. package/src/components/tools/site/site-flag-model.ts +18 -0
  113. package/src/components/tools/stair/stair-defaults.ts +2 -1
  114. package/src/components/tools/stair/stair-tool.tsx +342 -37
  115. package/src/components/tools/tool-manager.tsx +239 -62
  116. package/src/components/tools/wall/wall-drafting.test.ts +330 -0
  117. package/src/components/tools/wall/wall-drafting.ts +248 -171
  118. package/src/components/tools/wall/wall-snap-geometry.test.ts +123 -0
  119. package/src/components/tools/wall/wall-snap-geometry.ts +331 -0
  120. package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
  121. package/src/components/tools/zone/zone-tool.tsx +86 -82
  122. package/src/components/ui/action-menu/action-button.tsx +21 -1
  123. package/src/components/ui/action-menu/camera-actions.tsx +24 -17
  124. package/src/components/ui/action-menu/control-modes.tsx +40 -170
  125. package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
  126. package/src/components/ui/action-menu/index.tsx +3 -117
  127. package/src/components/ui/action-menu/measurement-control.tsx +188 -0
  128. package/src/components/ui/action-menu/structure-tools.tsx +27 -93
  129. package/src/components/ui/action-menu/view-toggles.tsx +279 -90
  130. package/src/components/ui/command-palette/editor-commands.tsx +20 -15
  131. package/src/components/ui/command-palette/index.tsx +5 -3
  132. package/src/components/ui/controls/material-paint-panel.tsx +152 -0
  133. package/src/components/ui/controls/material-picker.tsx +87 -177
  134. package/src/components/ui/controls/material-properties-editor.tsx +108 -0
  135. package/src/components/ui/controls/metric-control.tsx +142 -46
  136. package/src/components/ui/controls/scene-material-list.tsx +247 -0
  137. package/src/components/ui/controls/slider-control.tsx +88 -38
  138. package/src/components/ui/floating-level-selector.tsx +47 -10
  139. package/src/components/ui/helpers/building-helper.tsx +10 -23
  140. package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
  141. package/src/components/ui/helpers/helper-manager.tsx +235 -23
  142. package/src/components/ui/helpers/item-helper.tsx +28 -32
  143. package/src/components/ui/helpers/registered-tool-helper.tsx +59 -0
  144. package/src/components/ui/helpers/roof-helper.tsx +10 -12
  145. package/src/components/ui/icon-ref.tsx +47 -0
  146. package/src/components/ui/item-catalog/catalog-items.tsx +40 -1
  147. package/src/components/ui/item-catalog/item-catalog.tsx +16 -36
  148. package/src/components/ui/level-duplicate-dialog.tsx +2 -1
  149. package/src/components/ui/panels/node-display.ts +17 -16
  150. package/src/components/ui/panels/panel-manager.tsx +55 -58
  151. package/src/components/ui/panels/panel-wrapper.tsx +238 -12
  152. package/src/components/ui/panels/parametric-inspector.tsx +482 -0
  153. package/src/components/ui/panels/reference-panel.tsx +54 -19
  154. package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
  155. package/src/components/ui/primitives/shortcut-token.tsx +39 -3
  156. package/src/components/ui/primitives/sidebar.tsx +1 -0
  157. package/src/components/ui/scene-loader.tsx +1 -3
  158. package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
  159. package/src/components/ui/sidebar/icon-rail.tsx +50 -31
  160. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
  161. package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
  162. package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
  163. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +122 -41
  164. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +66 -7
  165. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +267 -0
  166. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
  167. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +9 -5
  168. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +86 -0
  169. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
  170. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
  171. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +92 -0
  172. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
  173. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +3 -3
  174. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +86 -0
  175. package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -21
  176. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
  177. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
  178. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +140 -0
  179. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +48 -8
  180. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +132 -0
  181. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -5
  182. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +86 -0
  183. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -1
  184. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
  185. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +112 -39
  186. package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
  187. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
  188. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
  189. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
  190. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +51 -2
  191. package/src/components/ui/sidebar/tab-bar.tsx +75 -1
  192. package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
  193. package/src/components/ui/snap-target-badge.test.tsx +40 -0
  194. package/src/components/ui/snap-target-badge.tsx +88 -0
  195. package/src/components/viewer-overlay.tsx +253 -61
  196. package/src/components/viewer-zone-system.tsx +7 -2
  197. package/src/hooks/use-auto-save.test.ts +14 -0
  198. package/src/hooks/use-auto-save.ts +64 -10
  199. package/src/hooks/use-ceiling-events.ts +176 -0
  200. package/src/hooks/use-drag-action.ts +127 -0
  201. package/src/hooks/use-keyboard.ts +493 -95
  202. package/src/hooks/use-selection.ts +64 -0
  203. package/src/index.tsx +451 -6
  204. package/src/lib/active-placement-surface.ts +35 -0
  205. package/src/lib/ceiling-plan-snap.ts +24 -0
  206. package/src/lib/constants.ts +6 -2
  207. package/src/lib/contextual-help.test.ts +112 -0
  208. package/src/lib/contextual-help.ts +144 -0
  209. package/src/lib/continuation.ts +64 -0
  210. package/src/lib/direct-manipulation.test.ts +192 -0
  211. package/src/lib/direct-manipulation.ts +109 -0
  212. package/src/lib/editor-api.ts +50 -0
  213. package/src/lib/elevator-support.ts +96 -0
  214. package/src/lib/floorplan/apply-alignment.test.ts +25 -0
  215. package/src/lib/floorplan/apply-alignment.ts +117 -0
  216. package/src/lib/floorplan/floorplan-export.tsx +364 -0
  217. package/src/lib/floorplan/geometry.ts +53 -0
  218. package/src/lib/floorplan/index.ts +10 -0
  219. package/src/lib/floorplan/items.ts +5 -2
  220. package/src/lib/floorplan/plan-coords.ts +21 -0
  221. package/src/lib/floorplan/selection-tool.ts +40 -0
  222. package/src/lib/floorplan/stairs.ts +12 -1
  223. package/src/lib/fresh-planar-placement.test.ts +339 -0
  224. package/src/lib/fresh-planar-placement.ts +130 -0
  225. package/src/lib/glb-export.test.ts +441 -0
  226. package/src/lib/glb-export.ts +874 -0
  227. package/src/lib/history.ts +11 -1
  228. package/src/lib/interaction/hot-set.test.ts +133 -0
  229. package/src/lib/interaction/hot-set.ts +67 -0
  230. package/src/lib/interaction/overlay-policy.test.ts +51 -0
  231. package/src/lib/interaction/overlay-policy.ts +59 -0
  232. package/src/lib/interaction/scope.ts +211 -0
  233. package/src/lib/level-duplication.ts +54 -42
  234. package/src/lib/level-selection.ts +2 -2
  235. package/src/lib/material-paint.ts +227 -48
  236. package/src/lib/measurement-kind.test.ts +30 -0
  237. package/src/lib/measurement-kind.ts +19 -0
  238. package/src/lib/measurement-label.test.ts +47 -0
  239. package/src/lib/measurement-label.ts +68 -0
  240. package/src/lib/measurement-parser.ts +116 -0
  241. package/src/lib/measurements.test.ts +183 -0
  242. package/src/lib/measurements.ts +199 -0
  243. package/src/lib/paint-scope.test.ts +454 -0
  244. package/src/lib/paint-scope.ts +461 -0
  245. package/src/lib/placement-drag-release.ts +23 -0
  246. package/src/lib/placement-metadata.ts +29 -0
  247. package/src/lib/planar-cursor-placement.test.ts +100 -0
  248. package/src/lib/planar-cursor-placement.ts +42 -0
  249. package/src/lib/plugin-panels.test.ts +19 -0
  250. package/src/lib/plugin-panels.ts +91 -0
  251. package/src/lib/quick-measurement.test.ts +77 -0
  252. package/src/lib/quick-measurement.ts +109 -0
  253. package/src/lib/roof-duplication.ts +7 -7
  254. package/src/lib/roof-hover-outline-proxy.ts +22 -0
  255. package/src/lib/roof-wall-hit.ts +164 -0
  256. package/src/lib/scene-clipboard.test.ts +196 -0
  257. package/src/lib/scene-clipboard.ts +332 -0
  258. package/src/lib/scene.ts +40 -8
  259. package/src/lib/selection-routing.test.ts +298 -0
  260. package/src/lib/selection-routing.ts +175 -0
  261. package/src/lib/sfx/index.ts +1 -0
  262. package/src/lib/sfx/movement-tick.test.ts +65 -0
  263. package/src/lib/sfx/movement-tick.ts +18 -0
  264. package/src/lib/sfx-bus.ts +69 -10
  265. package/src/lib/sfx-player.test.ts +124 -0
  266. package/src/lib/sfx-player.ts +148 -55
  267. package/src/lib/site-boundary.ts +1 -0
  268. package/src/lib/slab-plan-snap.test.ts +93 -0
  269. package/src/lib/slab-plan-snap.ts +108 -0
  270. package/src/lib/snapping-mode.test.ts +138 -0
  271. package/src/lib/snapping-mode.ts +179 -0
  272. package/src/lib/stair-duplication.ts +4 -4
  273. package/src/lib/stair-levels.test.ts +146 -0
  274. package/src/lib/stair-levels.ts +177 -0
  275. package/src/lib/surface-plan-snap.test.ts +71 -0
  276. package/src/lib/surface-plan-snap.ts +256 -0
  277. package/src/lib/use-linear-display.ts +40 -0
  278. package/src/lib/world-grid-snap.ts +231 -0
  279. package/src/lib/zone-content.ts +130 -0
  280. package/src/store/use-alignment-guides.ts +21 -0
  281. package/src/store/use-direct-manipulation-feedback.ts +20 -0
  282. package/src/store/use-editor.tsx +767 -114
  283. package/src/store/use-facing-pose.ts +44 -0
  284. package/src/store/use-fence-curve-draft.ts +21 -0
  285. package/src/store/use-floorplan-draft-preview.ts +101 -0
  286. package/src/store/use-floorplan-marquee.ts +50 -0
  287. package/src/store/use-interaction-scope.test.ts +186 -0
  288. package/src/store/use-interaction-scope.ts +132 -0
  289. package/src/store/use-measurement-draft.test.ts +530 -0
  290. package/src/store/use-measurement-draft.ts +543 -0
  291. package/src/store/use-opening-guides.ts +41 -0
  292. package/src/store/use-placement-preview.ts +39 -0
  293. package/src/store/use-quick-measurement-hud.test.ts +53 -0
  294. package/src/store/use-quick-measurement-hud.ts +77 -0
  295. package/src/store/use-segment-draft-chain.ts +28 -0
  296. package/src/store/use-stair-build-preview.ts +43 -0
  297. package/src/store/use-wall-move-ghosts.ts +36 -0
  298. package/src/store/use-wall-snap-indicator.ts +34 -0
  299. package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
  300. package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
  301. package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
  302. package/src/components/tools/building/move-building-tool.tsx +0 -157
  303. package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
  304. package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
  305. package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
  306. package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -264
  307. package/src/components/tools/column/column-tool.tsx +0 -97
  308. package/src/components/tools/column/move-column-tool.tsx +0 -105
  309. package/src/components/tools/door/door-math.ts +0 -110
  310. package/src/components/tools/door/door-tool.tsx +0 -324
  311. package/src/components/tools/door/move-door-tool.tsx +0 -412
  312. package/src/components/tools/fence/curve-fence-tool.tsx +0 -178
  313. package/src/components/tools/fence/fence-tool.tsx +0 -346
  314. package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
  315. package/src/components/tools/fence/move-fence-tool.tsx +0 -302
  316. package/src/components/tools/item/item-tool.tsx +0 -26
  317. package/src/components/tools/roof/move-roof-tool.tsx +0 -364
  318. package/src/components/tools/slab/move-slab-tool.tsx +0 -182
  319. package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
  320. package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
  321. package/src/components/tools/slab/slab-tool.tsx +0 -322
  322. package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
  323. package/src/components/tools/spawn/spawn-tool.tsx +0 -130
  324. package/src/components/tools/wall/curve-wall-tool.tsx +0 -178
  325. package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
  326. package/src/components/tools/wall/move-wall-tool.tsx +0 -358
  327. package/src/components/tools/wall/wall-tool.tsx +0 -332
  328. package/src/components/tools/window/move-window-tool.tsx +0 -447
  329. package/src/components/tools/window/window-math.ts +0 -117
  330. package/src/components/tools/window/window-tool.tsx +0 -332
  331. package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
  332. package/src/components/ui/helpers/slab-helper.tsx +0 -20
  333. package/src/components/ui/helpers/wall-helper.tsx +0 -20
  334. package/src/components/ui/panels/ceiling-panel.tsx +0 -249
  335. package/src/components/ui/panels/column-panel.tsx +0 -759
  336. package/src/components/ui/panels/door-panel.tsx +0 -1299
  337. package/src/components/ui/panels/fence-panel.tsx +0 -222
  338. package/src/components/ui/panels/item-panel.tsx +0 -306
  339. package/src/components/ui/panels/paint-panel.tsx +0 -163
  340. package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
  341. package/src/components/ui/panels/roof-panel.tsx +0 -280
  342. package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
  343. package/src/components/ui/panels/slab-panel.tsx +0 -250
  344. package/src/components/ui/panels/spawn-panel.tsx +0 -161
  345. package/src/components/ui/panels/stair-panel.tsx +0 -577
  346. package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
  347. package/src/components/ui/panels/wall-panel.tsx +0 -273
  348. package/src/components/ui/panels/window-panel.tsx +0 -968
  349. package/src/contexts/presets-context.tsx +0 -121
  350. package/src/hooks/use-contextual-tools.ts +0 -61
@@ -1,39 +1,62 @@
1
1
  'use client'
2
2
 
3
3
  import { useScene } from '@pascal-app/core'
4
- import { useViewer } from '@pascal-app/viewer'
5
4
  import { useCallback, useEffect, useRef, useState } from 'react'
5
+ import {
6
+ lingoUnitSpec,
7
+ measurementHint,
8
+ parseMeasurement,
9
+ } from '../../../lib/measurement-parser'
10
+ import { useLinearDisplay } from '../../../lib/use-linear-display'
6
11
  import { cn } from '../../../lib/utils'
7
12
 
8
13
  interface MetricControlProps {
9
14
  label: React.ReactNode
10
15
  value: number
11
16
  onChange: (value: number) => void
17
+ onCommit?: (value: number) => void
12
18
  min?: number
13
19
  max?: number
14
20
  precision?: number
15
21
  step?: number
16
22
  className?: string
17
23
  unit?: string
24
+ restoreOnCommit?: boolean
18
25
  }
19
26
 
20
27
  export function MetricControl({
21
28
  label,
22
29
  value,
23
30
  onChange,
31
+ onCommit,
24
32
  min = Number.NEGATIVE_INFINITY,
25
33
  max = Number.POSITIVE_INFINITY,
26
34
  precision = 2,
27
35
  step = 1,
28
36
  className,
29
37
  unit = '',
38
+ restoreOnCommit = true,
30
39
  }: MetricControlProps) {
31
- const viewerUnit = useViewer((state) => state.unit)
32
- const isImperial = viewerUnit === 'imperial' && unit === 'm'
33
- const multiplier = isImperial ? 3.280_84 : 1
34
- const displayUnit = isImperial ? 'ft' : unit
40
+ const {
41
+ isImperial,
42
+ displayUnit,
43
+ toDisplay: toDisplayValue,
44
+ toStored: toStoredValue,
45
+ } = useLinearDisplay(unit, precision)
35
46
 
36
- const displayValue = value * multiplier
47
+ const clamp = useCallback(
48
+ (val: number) => {
49
+ return Math.min(Math.max(val, min), max)
50
+ },
51
+ [min, max],
52
+ )
53
+ const roundStoredValueForDisplayPrecision = useCallback(
54
+ (storedValue: number) =>
55
+ clamp(toStoredValue(Number.parseFloat(toDisplayValue(storedValue).toFixed(precision)))),
56
+ [clamp, precision, toDisplayValue, toStoredValue],
57
+ )
58
+
59
+ const displayValue = toDisplayValue(value)
37
60
 
38
61
  const [isEditing, setIsEditing] = useState(false)
39
62
  const [isDragging, setIsDragging] = useState(false)
@@ -46,11 +69,15 @@ export function MetricControl({
46
69
  const valueRef = useRef(value)
47
70
  valueRef.current = value
48
71
 
49
- const clamp = useCallback(
50
- (val: number) => {
51
- return Math.min(Math.max(val, min), max)
72
+ const applyCommittedValue = useCallback(
73
+ (nextValue: number) => {
74
+ if (onCommit) {
75
+ onCommit(nextValue)
76
+ } else {
77
+ onChange(nextValue)
78
+ }
52
79
  },
53
- [min, max],
80
+ [onChange, onCommit],
54
81
  )
55
82
 
56
83
  useEffect(() => {
@@ -69,21 +96,21 @@ export function MetricControl({
69
96
  e.preventDefault()
70
97
 
71
98
  const direction = e.deltaY < 0 ? 1 : -1
72
- let scrollStep = step / multiplier
73
- if (e.shiftKey) scrollStep = (step * 10) / multiplier
74
- else if (e.altKey) scrollStep = (step * 0.1) / multiplier
99
+ let scrollStep = toStoredValue(step)
100
+ if (e.shiftKey) scrollStep = toStoredValue(step * 10)
101
+ else if (e.altKey) scrollStep = toStoredValue(step * 0.1)
75
102
 
76
103
  const newValue = clamp(valueRef.current + direction * scrollStep)
77
- const finalValue = Number.parseFloat((newValue * multiplier).toFixed(precision)) / multiplier
104
+ const finalValue = roundStoredValueForDisplayPrecision(newValue)
78
105
 
79
106
  if (Math.abs(finalValue - valueRef.current) > 1e-6) {
80
- onChange(finalValue)
107
+ applyCommittedValue(finalValue)
81
108
  }
82
109
  }
83
110
 
84
111
  container.addEventListener('wheel', handleWheel, { passive: false })
85
112
  return () => container.removeEventListener('wheel', handleWheel)
86
- }, [isEditing, step, clamp, onChange, precision, multiplier])
113
+ }, [isEditing, step, clamp, applyCommittedValue, toStoredValue, roundStoredValueForDisplayPrecision])
87
114
 
88
115
  useEffect(() => {
89
116
  if (!isHovered || isEditing) return
@@ -95,23 +122,30 @@ export function MetricControl({
95
122
 
96
123
  if (direction !== 0) {
97
124
  e.preventDefault()
98
- let scrollStep = step / multiplier
99
- if (e.shiftKey) scrollStep = (step * 10) / multiplier
100
- else if (e.altKey) scrollStep = (step * 0.1) / multiplier
125
+ let scrollStep = toStoredValue(step)
126
+ if (e.shiftKey) scrollStep = toStoredValue(step * 10)
127
+ else if (e.altKey) scrollStep = toStoredValue(step * 0.1)
101
128
 
102
129
  const newValue = clamp(valueRef.current + direction * scrollStep)
103
- const finalValue =
104
- Number.parseFloat((newValue * multiplier).toFixed(precision)) / multiplier
130
+ const finalValue = roundStoredValueForDisplayPrecision(newValue)
105
131
 
106
132
  if (Math.abs(finalValue - valueRef.current) > 1e-6) {
107
- onChange(finalValue)
133
+ applyCommittedValue(finalValue)
108
134
  }
109
135
  }
110
136
  }
111
137
 
112
138
  window.addEventListener('keydown', handleKeyDown)
113
139
  return () => window.removeEventListener('keydown', handleKeyDown)
114
- }, [isHovered, isEditing, step, clamp, onChange, precision, multiplier])
140
+ }, [
141
+ isHovered,
142
+ isEditing,
143
+ step,
144
+ clamp,
145
+ applyCommittedValue,
146
+ toStoredValue,
147
+ roundStoredValueForDisplayPrecision,
148
+ ])
115
149
 
116
150
  const handlePointerDown = useCallback(
117
151
  (e: React.PointerEvent) => {
@@ -128,14 +162,13 @@ export function MetricControl({
128
162
  const handlePointerMove = (moveEvent: PointerEvent) => {
129
163
  const deltaX = moveEvent.clientX - startXRef.current
130
164
 
131
- let dragStep = step / multiplier
132
- if (moveEvent.shiftKey) dragStep = (step * 10) / multiplier
133
- else if (moveEvent.altKey) dragStep = (step * 0.1) / multiplier
165
+ let dragStep = toStoredValue(step)
166
+ if (moveEvent.shiftKey) dragStep = toStoredValue(step * 10)
167
+ else if (moveEvent.altKey) dragStep = toStoredValue(step * 0.1)
134
168
 
135
169
  const deltaValue = deltaX * dragStep
136
170
  const newValue = clamp(startValueRef.current + deltaValue)
137
- const newFinalValue =
138
- Number.parseFloat((newValue * multiplier).toFixed(precision)) / multiplier
171
+ const newFinalValue = roundStoredValueForDisplayPrecision(newValue)
139
172
 
140
173
  if (Math.abs(newFinalValue - finalValue) > 1e-6) {
141
174
  finalValue = newFinalValue
@@ -148,7 +181,14 @@ export function MetricControl({
148
181
  document.removeEventListener('pointermove', handlePointerMove)
149
182
  document.removeEventListener('pointerup', handlePointerUp)
150
183
 
151
- if (Math.abs(finalValue - startValueRef.current) > 1e-6) {
184
+ const changed = Math.abs(finalValue - startValueRef.current) > 1e-6
185
+ if (onCommit) {
186
+ if (changed && restoreOnCommit) {
187
+ onChange(startValueRef.current)
188
+ }
189
+ useScene.temporal.getState().resume()
190
+ onCommit(finalValue)
191
+ } else if (changed) {
152
192
  onChange(startValueRef.current)
153
193
  useScene.temporal.getState().resume()
154
194
  onChange(finalValue)
@@ -160,27 +200,69 @@ export function MetricControl({
160
200
  document.addEventListener('pointermove', handlePointerMove)
161
201
  document.addEventListener('pointerup', handlePointerUp)
162
202
  },
163
- [isEditing, value, onChange, clamp, precision, step, multiplier],
203
+ [
204
+ isEditing,
205
+ value,
206
+ onChange,
207
+ onCommit,
208
+ restoreOnCommit,
209
+ clamp,
210
+ step,
211
+ toStoredValue,
212
+ roundStoredValueForDisplayPrecision,
213
+ ],
164
214
  )
165
215
 
166
216
  const handleValueClick = useCallback(() => {
167
217
  setIsEditing(true)
168
- setInputValue((value * multiplier).toFixed(precision))
169
- }, [value, multiplier, precision])
218
+ setInputValue(toDisplayValue(value).toFixed(precision))
219
+ }, [value, toDisplayValue, precision])
170
220
 
171
221
  const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
172
222
  setInputValue(e.target.value)
173
223
  }, [])
174
224
 
175
225
  const submitValue = useCallback(() => {
176
- const numValue = Number.parseFloat(inputValue)
177
- if (Number.isNaN(numValue)) {
178
- setInputValue((value * multiplier).toFixed(precision))
226
+ const spec = lingoUnitSpec(unit)
227
+ let stored = spec
228
+ ? parseMeasurement(inputValue, spec, {
229
+ bareUnit: isImperial ? 'ft' : spec.unitId,
230
+ system: isImperial ? 'us' : 'metric',
231
+ })
232
+ : null
233
+ if (stored === null) {
234
+ const numValue = Number.parseFloat(inputValue)
235
+ stored = Number.isFinite(numValue) ? toStoredValue(numValue) : null
236
+ }
237
+ if (stored === null) {
238
+ setInputValue(toDisplayValue(value).toFixed(precision))
179
239
  } else {
180
- onChange(clamp(numValue / multiplier))
240
+ applyCommittedValue(clamp(stored))
181
241
  }
182
242
  setIsEditing(false)
183
- }, [inputValue, onChange, clamp, multiplier, value, precision])
243
+ }, [
244
+ inputValue,
245
+ unit,
246
+ isImperial,
247
+ applyCommittedValue,
248
+ clamp,
249
+ toStoredValue,
250
+ value,
251
+ precision,
252
+ toDisplayValue,
253
+ ])
254
+
255
+ const spec = lingoUnitSpec(unit)
256
+ const hint =
257
+ isEditing && spec
258
+ ? measurementHint(inputValue, spec, {
259
+ bareUnit: isImperial ? 'ft' : spec.unitId,
260
+ system: isImperial ? 'us' : 'metric',
261
+ displayUnit: isImperial ? 'ft' : spec.unitId,
262
+ precision,
263
+ clamp,
264
+ })
265
+ : null
184
266
 
185
267
  const handleInputBlur = useCallback(() => {
186
268
  submitValue()
@@ -191,21 +273,21 @@ export function MetricControl({
191
273
  if (e.key === 'Enter') {
192
274
  submitValue()
193
275
  } else if (e.key === 'Escape') {
194
- setInputValue((value * multiplier).toFixed(precision))
276
+ setInputValue(toDisplayValue(value).toFixed(precision))
195
277
  setIsEditing(false)
196
278
  } else if (e.key === 'ArrowUp') {
197
279
  e.preventDefault()
198
- const newV = clamp(value + step / multiplier)
199
- onChange(newV)
200
- setInputValue((newV * multiplier).toFixed(precision))
280
+ const newV = clamp(value + toStoredValue(step))
281
+ applyCommittedValue(newV)
282
+ setInputValue(toDisplayValue(newV).toFixed(precision))
201
283
  } else if (e.key === 'ArrowDown') {
202
284
  e.preventDefault()
203
- const newV = clamp(value - step / multiplier)
204
- onChange(newV)
205
- setInputValue((newV * multiplier).toFixed(precision))
285
+ const newV = clamp(value - toStoredValue(step))
286
+ applyCommittedValue(newV)
287
+ setInputValue(toDisplayValue(newV).toFixed(precision))
206
288
  }
207
289
  },
208
- [submitValue, value, multiplier, precision, step, clamp, onChange],
290
+ [submitValue, value, toDisplayValue, precision, step, clamp, applyCommittedValue, toStoredValue],
209
291
  )
210
292
 
211
293
  return (
@@ -234,6 +316,11 @@ export function MetricControl({
234
316
  <div className="flex shrink-0 justify-end">
235
317
  {isEditing ? (
236
318
  <div className="flex items-center">
319
+ {hint && (
320
+ <span className="mr-1.5 shrink-0 whitespace-nowrap text-[11px] text-muted-foreground/50 tabular-nums">
321
+ {hint}
322
+ </span>
323
+ )}
237
324
  <input
238
325
  autoFocus
239
326
  className="w-full bg-transparent p-0 text-right font-mono text-foreground outline-none selection:bg-primary/30"
@@ -243,7 +330,16 @@ export function MetricControl({
243
330
  type="text"
244
331
  value={inputValue}
245
332
  />
246
- {displayUnit && <span className="ml-[1px] text-muted-foreground">{displayUnit}</span>}
333
+ {displayUnit && (
334
+ <span
335
+ className={cn(
336
+ 'ml-[1px] transition-opacity duration-150',
337
+ hint ? 'opacity-0' : 'text-muted-foreground/40',
338
+ )}
339
+ >
340
+ {displayUnit}
341
+ </span>
342
+ )}
247
343
  </div>
248
344
  ) : (
249
345
  <div
@@ -0,0 +1,247 @@
1
+ 'use client'
2
+
3
+ import {
4
+ generateSceneMaterialId,
5
+ type MaterialSchema,
6
+ type SceneMaterial,
7
+ type SceneMaterialId,
8
+ toSceneMaterialRef,
9
+ useScene,
10
+ } from '@pascal-app/core'
11
+ import { Copy, Paintbrush, Pencil, Trash2 } from 'lucide-react'
12
+ import { useEffect, useMemo, useState } from 'react'
13
+ import useEditor from '../../../store/use-editor'
14
+ import { Button } from '../primitives/button'
15
+ import { Input } from '../primitives/input'
16
+ import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip'
17
+ import { MaterialPropertiesEditor } from './material-properties-editor'
18
+
19
+ type SlotRecord = Record<string, string | undefined>
20
+
21
+ function getSlotRecord(node: unknown): SlotRecord | null {
22
+ if (!node || typeof node !== 'object' || !('slots' in node)) return null
23
+ const slots = (node as { slots?: unknown }).slots
24
+ if (!slots || typeof slots !== 'object' || Array.isArray(slots)) return null
25
+ return slots as SlotRecord
26
+ }
27
+
28
+ export function SceneMaterialList({ autoEditId }: { autoEditId?: SceneMaterialId | null }) {
29
+ const materials = useScene((state) => state.materials)
30
+ const nodes = useScene((state) => state.nodes)
31
+ const addSceneMaterial = useScene((state) => state.addSceneMaterial)
32
+ const updateSceneMaterial = useScene((state) => state.updateSceneMaterial)
33
+ const removeSceneMaterial = useScene((state) => state.removeSceneMaterial)
34
+ const activePaintTarget = useEditor((state) => state.activePaintTarget)
35
+ const activePaintRef = useEditor((state) => state.activePaintMaterial?.materialPreset)
36
+ const setActivePaintMaterial = useEditor((state) => state.setActivePaintMaterial)
37
+
38
+ const materialEntries = useMemo(
39
+ () => Object.entries(materials) as [SceneMaterialId, SceneMaterial][],
40
+ [materials],
41
+ )
42
+
43
+ const usageCounts = useMemo(() => {
44
+ const counts = new Map<SceneMaterialId, number>()
45
+ const refToId = new Map<string, SceneMaterialId>()
46
+
47
+ for (const [id] of materialEntries) {
48
+ counts.set(id, 0)
49
+ refToId.set(toSceneMaterialRef(id), id)
50
+ }
51
+
52
+ for (const node of Object.values(nodes)) {
53
+ const slots = getSlotRecord(node)
54
+ if (!slots) continue
55
+
56
+ for (const value of Object.values(slots)) {
57
+ if (typeof value !== 'string') continue
58
+ const materialId = refToId.get(value)
59
+ if (!materialId) continue
60
+ counts.set(materialId, (counts.get(materialId) ?? 0) + 1)
61
+ }
62
+ }
63
+
64
+ return counts
65
+ }, [materialEntries, nodes])
66
+
67
+ return (
68
+ <div className="space-y-2">
69
+ {materialEntries.map(([id, sceneMaterial]) => (
70
+ <SceneMaterialRow
71
+ addSceneMaterial={addSceneMaterial}
72
+ activePaintTarget={activePaintTarget}
73
+ autoEdit={autoEditId === id}
74
+ id={id}
75
+ isActive={activePaintRef === toSceneMaterialRef(id)}
76
+ key={id}
77
+ removeSceneMaterial={removeSceneMaterial}
78
+ sceneMaterial={sceneMaterial}
79
+ setActivePaintMaterial={setActivePaintMaterial}
80
+ updateSceneMaterial={updateSceneMaterial}
81
+ usageCount={usageCounts.get(id) ?? 0}
82
+ />
83
+ ))}
84
+ </div>
85
+ )
86
+ }
87
+
88
+ function SceneMaterialRow({
89
+ id,
90
+ sceneMaterial,
91
+ usageCount,
92
+ activePaintTarget,
93
+ autoEdit,
94
+ isActive,
95
+ addSceneMaterial,
96
+ updateSceneMaterial,
97
+ removeSceneMaterial,
98
+ setActivePaintMaterial,
99
+ }: {
100
+ id: SceneMaterialId
101
+ sceneMaterial: SceneMaterial
102
+ usageCount: number
103
+ activePaintTarget: ReturnType<typeof useEditor.getState>['activePaintTarget']
104
+ autoEdit: boolean
105
+ isActive: boolean
106
+ addSceneMaterial: ReturnType<typeof useScene.getState>['addSceneMaterial']
107
+ updateSceneMaterial: ReturnType<typeof useScene.getState>['updateSceneMaterial']
108
+ removeSceneMaterial: ReturnType<typeof useScene.getState>['removeSceneMaterial']
109
+ setActivePaintMaterial: ReturnType<typeof useEditor.getState>['setActivePaintMaterial']
110
+ }) {
111
+ // A freshly-created material (via "+ Custom") mounts with its editor open.
112
+ const [isEditingMaterial, setIsEditingMaterial] = useState(autoEdit)
113
+ const [draftName, setDraftName] = useState(sceneMaterial.name)
114
+ const swatchColor = sceneMaterial.material.properties?.color ?? '#ffffff'
115
+
116
+ useEffect(() => {
117
+ setDraftName(sceneMaterial.name)
118
+ }, [sceneMaterial.name])
119
+
120
+ const commitName = () => {
121
+ const nextName = draftName.trim()
122
+ if (!nextName) {
123
+ setDraftName(sceneMaterial.name)
124
+ return
125
+ }
126
+ if (nextName !== sceneMaterial.name) {
127
+ updateSceneMaterial(id, { name: nextName })
128
+ }
129
+ }
130
+
131
+ const duplicateMaterial = () => {
132
+ addSceneMaterial({
133
+ id: generateSceneMaterialId(),
134
+ name: `${sceneMaterial.name} copy`,
135
+ material: structuredClone(sceneMaterial.material) as MaterialSchema,
136
+ })
137
+ }
138
+
139
+ return (
140
+ <div
141
+ className={`rounded-md border border-border/60 bg-background/40 p-2 ${
142
+ isActive ? 'ring-1 ring-primary ring-inset' : ''
143
+ }`}
144
+ >
145
+ <div className="flex items-center gap-2">
146
+ <span
147
+ className="h-8 w-8 shrink-0 rounded-md border border-border/70"
148
+ style={{ backgroundColor: swatchColor }}
149
+ />
150
+ <Input
151
+ className="h-8 px-2 text-sm"
152
+ onBlur={commitName}
153
+ onChange={(e) => setDraftName(e.target.value)}
154
+ onKeyDown={(e) => {
155
+ if (e.key === 'Enter') {
156
+ e.currentTarget.blur()
157
+ }
158
+ if (e.key === 'Escape') {
159
+ setDraftName(sceneMaterial.name)
160
+ e.currentTarget.blur()
161
+ }
162
+ }}
163
+ value={draftName}
164
+ />
165
+ </div>
166
+
167
+ <div className="mt-2 flex items-center justify-between gap-2">
168
+ <span className="text-muted-foreground text-xs">
169
+ Used by {usageCount} {usageCount === 1 ? 'part' : 'parts'}
170
+ </span>
171
+ <div className="flex items-center gap-1">
172
+ <Tooltip>
173
+ <TooltipTrigger asChild>
174
+ <Button
175
+ aria-label="Paint with"
176
+ onClick={() =>
177
+ setActivePaintMaterial({
178
+ materialPreset: toSceneMaterialRef(id),
179
+ sourceTarget: activePaintTarget,
180
+ })
181
+ }
182
+ size="icon-sm"
183
+ type="button"
184
+ variant="outline"
185
+ >
186
+ <Paintbrush />
187
+ </Button>
188
+ </TooltipTrigger>
189
+ <TooltipContent>Paint with</TooltipContent>
190
+ </Tooltip>
191
+ <Tooltip>
192
+ <TooltipTrigger asChild>
193
+ <Button
194
+ aria-label="Edit"
195
+ aria-pressed={isEditingMaterial}
196
+ onClick={() => setIsEditingMaterial((value) => !value)}
197
+ size="icon-sm"
198
+ type="button"
199
+ variant={isEditingMaterial ? 'default' : 'outline'}
200
+ >
201
+ <Pencil />
202
+ </Button>
203
+ </TooltipTrigger>
204
+ <TooltipContent>Edit</TooltipContent>
205
+ </Tooltip>
206
+ <Tooltip>
207
+ <TooltipTrigger asChild>
208
+ <Button
209
+ aria-label="Duplicate"
210
+ onClick={duplicateMaterial}
211
+ size="icon-sm"
212
+ type="button"
213
+ variant="outline"
214
+ >
215
+ <Copy />
216
+ </Button>
217
+ </TooltipTrigger>
218
+ <TooltipContent>Duplicate</TooltipContent>
219
+ </Tooltip>
220
+ <Tooltip>
221
+ <TooltipTrigger asChild>
222
+ <Button
223
+ aria-label="Delete"
224
+ onClick={() => removeSceneMaterial(id)}
225
+ size="icon-sm"
226
+ type="button"
227
+ variant="outline"
228
+ >
229
+ <Trash2 />
230
+ </Button>
231
+ </TooltipTrigger>
232
+ <TooltipContent>Delete</TooltipContent>
233
+ </Tooltip>
234
+ </div>
235
+ </div>
236
+
237
+ {isEditingMaterial ? (
238
+ <div className="mt-3 border-border/60 border-t pt-3">
239
+ <MaterialPropertiesEditor
240
+ onChange={(material) => updateSceneMaterial(id, { material })}
241
+ value={sceneMaterial.material}
242
+ />
243
+ </div>
244
+ ) : null}
245
+ </div>
246
+ )
247
+ }