@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,447 +0,0 @@
1
- import {
2
- type AnyNodeId,
3
- emitter,
4
- isCurvedWall,
5
- sceneRegistry,
6
- spatialGridManager,
7
- useLiveTransforms,
8
- useScene,
9
- type WallEvent,
10
- WindowNode,
11
- } from '@pascal-app/core'
12
- import { useViewer } from '@pascal-app/viewer'
13
- import { useCallback, useEffect, useMemo, useRef } from 'react'
14
- import { BoxGeometry, EdgesGeometry, type Group } from 'three'
15
- import { LineBasicNodeMaterial } from 'three/webgpu'
16
- import { EDITOR_LAYER } from '../../../lib/constants'
17
- import { sfxEmitter } from '../../../lib/sfx-bus'
18
- import useEditor from '../../../store/use-editor'
19
- import {
20
- calculateCursorRotation,
21
- calculateItemRotation,
22
- getSideFromNormal,
23
- isValidWallSideFace,
24
- snapToHalf,
25
- } from '../item/placement-math'
26
- import { clampToWall, hasWallChildOverlap, wallLocalToWorld } from './window-math'
27
-
28
- const edgeMaterial = new LineBasicNodeMaterial({
29
- color: 0xef_44_44,
30
- linewidth: 3,
31
- depthTest: false,
32
- depthWrite: false,
33
- })
34
-
35
- /**
36
- * Move/duplicate tool for WindowNodes — wall-only, same guardrails as WindowTool.
37
- *
38
- * Move mode (metadata.isNew falsy):
39
- * Adopts the existing window, pauses temporal. On commit: restores original state
40
- * (clean undo baseline) then resumes + updateNode (undo reverts to original position).
41
- * On cancel: restores original state.
42
- *
43
- * Duplicate mode (metadata.isNew = true):
44
- * The node is a freshly created transient copy. On commit: deletes transient + resumes
45
- * + createNode (undo removes the new window entirely). On cancel: deletes the node.
46
- */
47
- export const MoveWindowTool: React.FC<{ node: WindowNode }> = ({ node: movingWindowNode }) => {
48
- const cursorGroupRef = useRef<Group>(null!)
49
-
50
- const exitMoveMode = useCallback(() => {
51
- useEditor.getState().setMovingNode(null)
52
- }, [])
53
-
54
- useEffect(() => {
55
- useScene.temporal.getState().pause()
56
-
57
- const meta =
58
- typeof movingWindowNode.metadata === 'object' && movingWindowNode.metadata !== null
59
- ? (movingWindowNode.metadata as Record<string, unknown>)
60
- : {}
61
- const isNew = !!meta.isNew
62
-
63
- // Save original state (only used in move mode)
64
- const original = {
65
- position: [...movingWindowNode.position] as [number, number, number],
66
- rotation: [...movingWindowNode.rotation] as [number, number, number],
67
- side: movingWindowNode.side,
68
- parentId: movingWindowNode.parentId,
69
- wallId: movingWindowNode.wallId,
70
- metadata: movingWindowNode.metadata,
71
- }
72
-
73
- if (!isNew) {
74
- // Move mode: mark the existing window as transient so it hides while being repositioned
75
- useScene.getState().updateNode(movingWindowNode.id, {
76
- metadata: { ...meta, isTransient: true },
77
- })
78
- }
79
-
80
- let currentWallId: string | null = movingWindowNode.parentId
81
-
82
- const markWallDirty = (wallId: string | null) => {
83
- if (wallId) useScene.getState().dirtyNodes.add(wallId as AnyNodeId)
84
- }
85
-
86
- const getLevelId = () => useViewer.getState().selection.levelId
87
- const getLevelYOffset = () => {
88
- const id = getLevelId()
89
- return id ? (sceneRegistry.nodes.get(id as AnyNodeId)?.position.y ?? 0) : 0
90
- }
91
- const getSlabElevation = (wallEvent: WallEvent) =>
92
- spatialGridManager.getSlabElevationForWall(
93
- wallEvent.node.parentId ?? '',
94
- wallEvent.node.start,
95
- wallEvent.node.end,
96
- )
97
-
98
- const hideCursor = () => {
99
- if (cursorGroupRef.current) cursorGroupRef.current.visible = false
100
- }
101
-
102
- const updateCursor = (
103
- worldPosition: [number, number, number],
104
- cursorRotationY: number,
105
- valid: boolean,
106
- ) => {
107
- const group = cursorGroupRef.current
108
- if (!group) return
109
- group.visible = true
110
- group.position.set(...worldPosition)
111
- group.rotation.y = cursorRotationY
112
- edgeMaterial.color.setHex(valid ? 0x22_c5_5e : 0xef_44_44)
113
- }
114
-
115
- const onWallEnter = (event: WallEvent) => {
116
- if (!isValidWallSideFace(event.normal)) return
117
- if (isCurvedWall(event.node)) {
118
- hideCursor()
119
- return
120
- }
121
- // Only interact with walls on the current level
122
- if (event.node.parentId !== getLevelId()) return
123
-
124
- const side = getSideFromNormal(event.normal)
125
- const itemRotation = calculateItemRotation(event.normal)
126
- const cursorRotation = calculateCursorRotation(event.normal, event.node.start, event.node.end)
127
-
128
- const localX = snapToHalf(event.localPosition[0])
129
- const localY = snapToHalf(event.localPosition[1])
130
- const { clampedX, clampedY } = clampToWall(
131
- event.node,
132
- localX,
133
- localY,
134
- movingWindowNode.width,
135
- movingWindowNode.height,
136
- )
137
-
138
- const prevWallId = currentWallId
139
- currentWallId = event.node.id
140
-
141
- useScene.getState().updateNode(movingWindowNode.id, {
142
- position: [clampedX, clampedY, 0],
143
- rotation: [0, itemRotation, 0],
144
- side,
145
- parentId: event.node.id,
146
- wallId: event.node.id,
147
- })
148
- useLiveTransforms.getState().set(movingWindowNode.id, {
149
- position: [clampedX, clampedY, 0],
150
- rotation: itemRotation,
151
- })
152
-
153
- if (prevWallId && prevWallId !== event.node.id) markWallDirty(prevWallId)
154
- markWallDirty(event.node.id)
155
-
156
- const valid = !hasWallChildOverlap(
157
- event.node.id,
158
- clampedX,
159
- clampedY,
160
- movingWindowNode.width,
161
- movingWindowNode.height,
162
- movingWindowNode.id,
163
- )
164
-
165
- updateCursor(
166
- wallLocalToWorld(
167
- event.node,
168
- clampedX,
169
- clampedY,
170
- getLevelYOffset(),
171
- getSlabElevation(event),
172
- ),
173
- cursorRotation,
174
- valid,
175
- )
176
- event.stopPropagation()
177
- }
178
-
179
- const onWallMove = (event: WallEvent) => {
180
- if (!isValidWallSideFace(event.normal)) return
181
- if (isCurvedWall(event.node)) {
182
- hideCursor()
183
- return
184
- }
185
- // Only interact with walls on the current level
186
- if (event.node.parentId !== getLevelId()) return
187
-
188
- const side = getSideFromNormal(event.normal)
189
- const itemRotation = calculateItemRotation(event.normal)
190
- const cursorRotation = calculateCursorRotation(event.normal, event.node.start, event.node.end)
191
-
192
- const localX = snapToHalf(event.localPosition[0])
193
- const localY = snapToHalf(event.localPosition[1])
194
- const { clampedX, clampedY } = clampToWall(
195
- event.node,
196
- localX,
197
- localY,
198
- movingWindowNode.width,
199
- movingWindowNode.height,
200
- )
201
-
202
- if (currentWallId !== event.node.id) {
203
- // Wall changed mid-move: must updateNode to reparent
204
- useScene.getState().updateNode(movingWindowNode.id, {
205
- position: [clampedX, clampedY, 0],
206
- rotation: [0, itemRotation, 0],
207
- side,
208
- parentId: event.node.id,
209
- wallId: event.node.id,
210
- })
211
- markWallDirty(currentWallId)
212
- currentWallId = event.node.id
213
- } else {
214
- // Same wall: update Three.js mesh directly to avoid store churn
215
- // collectCutoutBrushes reads cutoutMesh.matrixWorld, not scene store positions
216
- const windowMesh = sceneRegistry.nodes.get(movingWindowNode.id as AnyNodeId)
217
- if (windowMesh) {
218
- windowMesh.position.set(clampedX, clampedY, 0)
219
- windowMesh.rotation.set(0, itemRotation, 0)
220
- windowMesh.updateMatrixWorld(true)
221
- }
222
- }
223
- useLiveTransforms.getState().set(movingWindowNode.id, {
224
- position: [clampedX, clampedY, 0],
225
- rotation: itemRotation,
226
- })
227
- markWallDirty(event.node.id)
228
-
229
- const valid = !hasWallChildOverlap(
230
- event.node.id,
231
- clampedX,
232
- clampedY,
233
- movingWindowNode.width,
234
- movingWindowNode.height,
235
- movingWindowNode.id,
236
- )
237
-
238
- updateCursor(
239
- wallLocalToWorld(
240
- event.node,
241
- clampedX,
242
- clampedY,
243
- getLevelYOffset(),
244
- getSlabElevation(event),
245
- ),
246
- cursorRotation,
247
- valid,
248
- )
249
- event.stopPropagation()
250
- }
251
-
252
- const onWallClick = (event: WallEvent) => {
253
- if (!isValidWallSideFace(event.normal)) return
254
- if (isCurvedWall(event.node)) return
255
- // Only interact with walls on the current level
256
- if (event.node.parentId !== getLevelId()) return
257
-
258
- const side = getSideFromNormal(event.normal)
259
- const itemRotation = calculateItemRotation(event.normal)
260
-
261
- const localX = snapToHalf(event.localPosition[0])
262
- const localY = snapToHalf(event.localPosition[1])
263
- const { clampedX, clampedY } = clampToWall(
264
- event.node,
265
- localX,
266
- localY,
267
- movingWindowNode.width,
268
- movingWindowNode.height,
269
- )
270
-
271
- const valid = !hasWallChildOverlap(
272
- event.node.id,
273
- clampedX,
274
- clampedY,
275
- movingWindowNode.width,
276
- movingWindowNode.height,
277
- movingWindowNode.id,
278
- )
279
- if (!valid) return
280
-
281
- let placedId: string
282
-
283
- if (isNew) {
284
- // Duplicate mode: delete transient + resume + createNode
285
- // Undo will remove the newly created node entirely
286
- useScene.getState().deleteNode(movingWindowNode.id)
287
- useScene.temporal.getState().resume()
288
-
289
- const node = WindowNode.parse({
290
- position: [clampedX, clampedY, 0],
291
- rotation: [0, itemRotation, 0],
292
- side,
293
- wallId: event.node.id,
294
- parentId: event.node.id,
295
- width: movingWindowNode.width,
296
- height: movingWindowNode.height,
297
- windowType: movingWindowNode.windowType,
298
- operationState: movingWindowNode.operationState,
299
- awningDirection: movingWindowNode.awningDirection,
300
- casementStyle: movingWindowNode.casementStyle,
301
- hingesSide: movingWindowNode.hingesSide,
302
- frameThickness: movingWindowNode.frameThickness,
303
- frameDepth: movingWindowNode.frameDepth,
304
- columnRatios: movingWindowNode.columnRatios,
305
- rowRatios: movingWindowNode.rowRatios,
306
- columnDividerThickness: movingWindowNode.columnDividerThickness,
307
- rowDividerThickness: movingWindowNode.rowDividerThickness,
308
- sill: movingWindowNode.sill,
309
- sillDepth: movingWindowNode.sillDepth,
310
- sillThickness: movingWindowNode.sillThickness,
311
- })
312
- useScene.getState().createNode(node, event.node.id as AnyNodeId)
313
- placedId = node.id
314
- } else {
315
- // Move mode: restore original (clean baseline) + resume + updateNode
316
- // Undo will revert to the original position
317
- useScene.getState().updateNode(movingWindowNode.id, {
318
- position: original.position,
319
- rotation: original.rotation,
320
- side: original.side,
321
- parentId: original.parentId,
322
- wallId: original.wallId,
323
- metadata: original.metadata,
324
- })
325
- useScene.temporal.getState().resume()
326
-
327
- useScene.getState().updateNode(movingWindowNode.id, {
328
- position: [clampedX, clampedY, 0],
329
- rotation: [0, itemRotation, 0],
330
- side,
331
- parentId: event.node.id,
332
- wallId: event.node.id,
333
- metadata: {},
334
- })
335
-
336
- if (original.parentId && original.parentId !== event.node.id) {
337
- markWallDirty(original.parentId)
338
- }
339
- placedId = movingWindowNode.id
340
- }
341
-
342
- markWallDirty(event.node.id)
343
- useLiveTransforms.getState().clear(movingWindowNode.id)
344
- useScene.temporal.getState().pause()
345
-
346
- sfxEmitter.emit('sfx:item-place')
347
- hideCursor()
348
- useViewer.getState().setSelection({ selectedIds: [placedId] })
349
- exitMoveMode()
350
- event.stopPropagation()
351
- }
352
-
353
- const onWallLeave = () => {
354
- hideCursor()
355
- useLiveTransforms.getState().clear(movingWindowNode.id)
356
- if (isNew) return // No original to restore for duplicates
357
- // Move mode: restore to original position while off-wall
358
- if (currentWallId && currentWallId !== original.parentId) {
359
- markWallDirty(currentWallId)
360
- }
361
- currentWallId = original.parentId
362
- useScene.getState().updateNode(movingWindowNode.id, {
363
- position: original.position,
364
- rotation: original.rotation,
365
- side: original.side,
366
- parentId: original.parentId,
367
- wallId: original.wallId,
368
- })
369
- if (original.parentId) markWallDirty(original.parentId)
370
- }
371
-
372
- const onCancel = () => {
373
- useLiveTransforms.getState().clear(movingWindowNode.id)
374
- if (isNew) {
375
- useScene.getState().deleteNode(movingWindowNode.id)
376
- if (currentWallId) markWallDirty(currentWallId)
377
- } else {
378
- useScene.getState().updateNode(movingWindowNode.id, {
379
- position: original.position,
380
- rotation: original.rotation,
381
- side: original.side,
382
- parentId: original.parentId,
383
- wallId: original.wallId,
384
- metadata: original.metadata,
385
- })
386
- if (original.parentId) markWallDirty(original.parentId)
387
- }
388
- useScene.temporal.getState().resume()
389
- hideCursor()
390
- exitMoveMode()
391
- }
392
-
393
- emitter.on('wall:enter', onWallEnter)
394
- emitter.on('wall:move', onWallMove)
395
- emitter.on('wall:click', onWallClick)
396
- emitter.on('wall:leave', onWallLeave)
397
- emitter.on('tool:cancel', onCancel)
398
-
399
- return () => {
400
- // Safety cleanup: if still transient on unmount (e.g. phase switch mid-move)
401
- const current = useScene.getState().nodes[movingWindowNode.id as AnyNodeId] as
402
- | WindowNode
403
- | undefined
404
- const currentMeta = current?.metadata as Record<string, unknown> | undefined
405
- if (currentMeta?.isTransient) {
406
- if (isNew) {
407
- useScene.getState().deleteNode(movingWindowNode.id)
408
- if (currentWallId) markWallDirty(currentWallId)
409
- } else {
410
- useScene.getState().updateNode(movingWindowNode.id, {
411
- position: original.position,
412
- rotation: original.rotation,
413
- side: original.side,
414
- parentId: original.parentId,
415
- wallId: original.wallId,
416
- metadata: original.metadata,
417
- })
418
- if (original.parentId) markWallDirty(original.parentId)
419
- }
420
- }
421
- useLiveTransforms.getState().clear(movingWindowNode.id)
422
- useScene.temporal.getState().resume()
423
- emitter.off('wall:enter', onWallEnter)
424
- emitter.off('wall:move', onWallMove)
425
- emitter.off('wall:click', onWallClick)
426
- emitter.off('wall:leave', onWallLeave)
427
- emitter.off('tool:cancel', onCancel)
428
- }
429
- }, [movingWindowNode, exitMoveMode])
430
-
431
- const edgesGeo = useMemo(() => {
432
- const boxGeo = new BoxGeometry(
433
- movingWindowNode.width,
434
- movingWindowNode.height,
435
- movingWindowNode.frameDepth ?? 0.07,
436
- )
437
- const geo = new EdgesGeometry(boxGeo)
438
- boxGeo.dispose()
439
- return geo
440
- }, [movingWindowNode])
441
-
442
- return (
443
- <group ref={cursorGroupRef} visible={false}>
444
- <lineSegments geometry={edgesGeo} layers={EDITOR_LAYER} material={edgeMaterial} />
445
- </group>
446
- )
447
- }
@@ -1,117 +0,0 @@
1
- import {
2
- type AnyNodeId,
3
- type DoorNode,
4
- getScaledDimensions,
5
- type ItemNode,
6
- useScene,
7
- type WallNode,
8
- type WindowNode,
9
- } from '@pascal-app/core'
10
-
11
- /**
12
- * Converts wall-local (X along wall, Y = height above wall base) to world XYZ.
13
- * Wall XZ uses level-local coordinates (levels only offset in Y, not XZ).
14
- * Pass levelYOffset (the level group's current world Y) and slabElevation (the
15
- * wall mesh's Y within the level group) so the cursor lands at the correct world
16
- * height — matching how WallSystem positions the wall mesh at slabElevation.
17
- */
18
- export function wallLocalToWorld(
19
- wallNode: WallNode,
20
- localX: number,
21
- localY: number,
22
- levelYOffset = 0,
23
- slabElevation = 0,
24
- ): [number, number, number] {
25
- const wallAngle = Math.atan2(
26
- wallNode.end[1] - wallNode.start[1],
27
- wallNode.end[0] - wallNode.start[0],
28
- )
29
- return [
30
- wallNode.start[0] + localX * Math.cos(wallAngle),
31
- slabElevation + localY + levelYOffset,
32
- wallNode.start[1] + localX * Math.sin(wallAngle),
33
- ]
34
- }
35
-
36
- /**
37
- * Clamps window center position so it stays fully within wall bounds.
38
- */
39
- export function clampToWall(
40
- wallNode: WallNode,
41
- localX: number,
42
- localY: number,
43
- width: number,
44
- height: number,
45
- ): { clampedX: number; clampedY: number } {
46
- const dx = wallNode.end[0] - wallNode.start[0]
47
- const dz = wallNode.end[1] - wallNode.start[1]
48
- const wallLength = Math.sqrt(dx * dx + dz * dz)
49
- const wallHeight = wallNode.height ?? 2.5
50
-
51
- const clampedX = Math.max(width / 2, Math.min(wallLength - width / 2, localX))
52
- const clampedY = Math.max(height / 2, Math.min(wallHeight - height / 2, localY))
53
- return { clampedX, clampedY }
54
- }
55
-
56
- /**
57
- * Directly checks the wall's children for bounding-box overlap with a proposed window.
58
- * Works for both `item` type (position[1] = bottom) and `window` type (position[1] = center).
59
- * The spatial grid only tracks `item` nodes, so windows must be checked this way.
60
- * Reads the wall's latest children from the store (not the event node) to avoid stale data.
61
- */
62
- export function hasWallChildOverlap(
63
- wallId: string,
64
- clampedX: number,
65
- clampedY: number,
66
- width: number,
67
- height: number,
68
- ignoreId?: string,
69
- ): boolean {
70
- const nodes = useScene.getState().nodes
71
- const wallNode = nodes[wallId as AnyNodeId] as WallNode | undefined
72
- if (!wallNode) return true // Block if wall not found
73
- const halfW = width / 2
74
- const halfH = height / 2
75
- const newBottom = clampedY - halfH
76
- const newTop = clampedY + halfH
77
- const newLeft = clampedX - halfW
78
- const newRight = clampedX + halfW
79
-
80
- for (const childId of Array.isArray(wallNode.children) ? wallNode.children : []) {
81
- if (childId === ignoreId) continue
82
- const child = nodes[childId as AnyNodeId]
83
- if (!child) continue
84
-
85
- let childLeft: number, childRight: number, childBottom: number, childTop: number
86
-
87
- if (child.type === 'item') {
88
- const item = child as ItemNode
89
- if (item.asset.attachTo !== 'wall' && item.asset.attachTo !== 'wall-side') continue
90
- const [w, h] = getScaledDimensions(item)
91
- childLeft = item.position[0] - w / 2
92
- childRight = item.position[0] + w / 2
93
- childBottom = item.position[1] // items store bottom Y
94
- childTop = item.position[1] + h
95
- } else if (child.type === 'window') {
96
- const win = child as WindowNode
97
- childLeft = win.position[0] - win.width / 2
98
- childRight = win.position[0] + win.width / 2
99
- childBottom = win.position[1] - win.height / 2 // windows store center Y
100
- childTop = win.position[1] + win.height / 2
101
- } else if (child.type === 'door') {
102
- const door = child as DoorNode
103
- childLeft = door.position[0] - door.width / 2
104
- childRight = door.position[0] + door.width / 2
105
- childBottom = door.position[1] - door.height / 2 // doors store center Y
106
- childTop = door.position[1] + door.height / 2
107
- } else {
108
- continue
109
- }
110
-
111
- const xOverlap = newLeft < childRight && newRight > childLeft
112
- const yOverlap = newBottom < childTop && newTop > childBottom
113
- if (xOverlap && yOverlap) return true
114
- }
115
-
116
- return false
117
- }