@pascal-app/editor 0.9.1 → 1.0.0-beta.1

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 (366) hide show
  1. package/package.json +14 -9
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +10 -2
  3. package/src/components/editor/bake-exporter.tsx +49 -0
  4. package/src/components/editor/bake-thumbnail.tsx +95 -0
  5. package/src/components/editor/camera-dragging-lifecycle.test.ts +49 -0
  6. package/src/components/editor/camera-dragging-lifecycle.ts +41 -0
  7. package/src/components/editor/custom-camera-controls.tsx +332 -199
  8. package/src/components/editor/delete-confirmation-dialog.tsx +54 -0
  9. package/src/components/editor/editor-layout-mobile.tsx +5 -0
  10. package/src/components/editor/editor-layout-v2.tsx +18 -0
  11. package/src/components/editor/elevation-3d-guide-layer.tsx +120 -0
  12. package/src/components/editor/export-manager.tsx +71 -63
  13. package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
  14. package/src/components/editor/first-person/build-collider-world.test.ts +102 -5
  15. package/src/components/editor/first-person/build-collider-world.ts +52 -5
  16. package/src/components/editor/first-person/terrain-collider.test.ts +132 -0
  17. package/src/components/editor/first-person/terrain-collider.ts +212 -0
  18. package/src/components/editor/first-person-controls.tsx +410 -136
  19. package/src/components/editor/floating-action-menu.tsx +435 -64
  20. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  21. package/src/components/editor/floorplan-background-selection.test.ts +72 -0
  22. package/src/components/editor/floorplan-background-selection.ts +7 -20
  23. package/src/components/editor/floorplan-camera-sync.test.ts +249 -0
  24. package/src/components/editor/floorplan-camera-sync.ts +213 -0
  25. package/src/components/editor/floorplan-mode-coordinator.tsx +82 -0
  26. package/src/components/editor/floorplan-navigation-presentation.test.ts +142 -0
  27. package/src/components/editor/floorplan-navigation-presentation.ts +156 -0
  28. package/src/components/editor/floorplan-panel.tsx +2517 -1492
  29. package/src/components/editor/grid.tsx +166 -39
  30. package/src/components/editor/group-actions.ts +597 -0
  31. package/src/components/editor/group-floating-action-menu.tsx +130 -0
  32. package/src/components/editor/group-move-3d.ts +397 -0
  33. package/src/components/editor/group-rotate-handle.tsx +102 -64
  34. package/src/components/editor/group-selection-box-3d.tsx +173 -0
  35. package/src/components/editor/group-transform-shared.test.ts +192 -1
  36. package/src/components/editor/group-transform-shared.ts +240 -7
  37. package/src/components/editor/handles/handle-arrow.tsx +81 -23
  38. package/src/components/editor/handles/handle-drag-history.test.ts +96 -0
  39. package/src/components/editor/handles/handle-drag-history.ts +14 -0
  40. package/src/components/editor/handles/preview-overrides.test.ts +26 -0
  41. package/src/components/editor/handles/preview-overrides.ts +13 -0
  42. package/src/components/editor/handles/resize-snap.test.ts +52 -0
  43. package/src/components/editor/handles/resize-snap.ts +23 -0
  44. package/src/components/editor/handles/use-handle-drag.ts +49 -6
  45. package/src/components/editor/index.tsx +289 -35
  46. package/src/components/editor/measurement-pill.tsx +68 -23
  47. package/src/components/editor/node-action-menu.tsx +14 -1
  48. package/src/components/editor/node-arrow-handles.tsx +409 -180
  49. package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
  50. package/src/components/editor/quick-measurement-card.tsx +76 -0
  51. package/src/components/editor/quick-measurement-hud.tsx +34 -0
  52. package/src/components/editor/riser-diagram-panel.tsx +137 -0
  53. package/src/components/editor/selection-manager.tsx +814 -466
  54. package/src/components/editor/site-edge-labels.tsx +28 -6
  55. package/src/components/editor/slab-hole-highlights.tsx +13 -5
  56. package/src/components/editor/snapshot-capture-overlay.tsx +258 -115
  57. package/src/components/editor/three-context-bridge.ts +22 -0
  58. package/src/components/editor/thumbnail-generator.tsx +118 -253
  59. package/src/components/editor/use-floorplan-background-placement.ts +113 -45
  60. package/src/components/editor/use-floorplan-hit-testing.test.ts +82 -0
  61. package/src/components/editor/use-floorplan-hit-testing.ts +144 -161
  62. package/src/components/editor/use-floorplan-scene-data.ts +5 -5
  63. package/src/components/editor/use-mesh-settle-epoch.ts +27 -0
  64. package/src/components/editor/wall-measurement-label.tsx +10 -28
  65. package/src/components/editor/wall-move-side-handles.tsx +229 -39
  66. package/src/components/editor/wall-opening-highlights.tsx +63 -32
  67. package/src/components/editor/wall-snap-beacon-layer.tsx +162 -6
  68. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +9 -5
  69. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +2 -1
  70. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +40 -9
  71. package/src/components/editor-2d/floorplan-cursor-indicator-position.test.ts +31 -0
  72. package/src/components/editor-2d/floorplan-cursor-indicator-position.ts +39 -0
  73. package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
  74. package/src/components/editor-2d/floorplan-group-move.tsx +744 -0
  75. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
  76. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1672 -0
  77. package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
  78. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +72 -0
  79. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +219 -20
  80. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +327 -59
  81. package/src/components/editor-2d/floorplan-render-context.test.ts +27 -0
  82. package/src/components/editor-2d/floorplan-render-context.tsx +113 -8
  83. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +5 -3
  84. package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +285 -0
  85. package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +516 -0
  86. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +236 -0
  87. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +618 -0
  88. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +369 -0
  89. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +431 -32
  90. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +89 -0
  91. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +106 -0
  92. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +94 -26
  93. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +587 -0
  94. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +2280 -578
  95. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +20 -10
  96. package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
  97. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +425 -61
  98. package/src/components/systems/ceiling/ceiling-system.tsx +97 -4
  99. package/src/components/systems/roof/roof-edit-system.tsx +1768 -13
  100. package/src/components/systems/selection-affordance-manager.tsx +38 -0
  101. package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
  102. package/src/components/systems/zone/zone-system.tsx +42 -13
  103. package/src/components/tools/elevator/elevator-tool.tsx +34 -8
  104. package/src/components/tools/elevator/move-elevator-tool.tsx +13 -2
  105. package/src/components/tools/fence/fence-drafting.ts +107 -8
  106. package/src/components/tools/item/move-tool.tsx +17 -13
  107. package/src/components/tools/item/placement-math.test.ts +13 -1
  108. package/src/components/tools/item/placement-math.ts +28 -2
  109. package/src/components/tools/item/placement-strategies.ts +246 -5
  110. package/src/components/tools/item/placement-types.ts +13 -1
  111. package/src/components/tools/item/use-draft-node.ts +146 -57
  112. package/src/components/tools/item/use-placement-coordinator.tsx +813 -125
  113. package/src/components/tools/registry/move-registry-node-tool.tsx +636 -103
  114. package/src/components/tools/roof/roof-tool.tsx +336 -94
  115. package/src/components/tools/select/box-select-state.ts +18 -7
  116. package/src/components/tools/select/box-select-tool.tsx +176 -40
  117. package/src/components/tools/select/marquee-geometry.test.ts +161 -0
  118. package/src/components/tools/select/marquee-geometry.ts +155 -0
  119. package/src/components/tools/select/plane-box-select-tool.tsx +1 -8
  120. package/src/components/tools/select/select-candidates.ts +1 -1
  121. package/src/components/tools/shared/cursor-sphere.tsx +62 -26
  122. package/src/components/tools/shared/drag-bounding-box.tsx +28 -4
  123. package/src/components/tools/shared/facing-indicator.tsx +87 -0
  124. package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
  125. package/src/components/tools/shared/floor-stack-preview.ts +4 -0
  126. package/src/components/tools/shared/horizontal-construction-plane.ts +123 -0
  127. package/src/components/tools/shared/placement-box.tsx +183 -1
  128. package/src/components/tools/shared/pointer-support-cap.test.ts +98 -0
  129. package/src/components/tools/shared/pointer-support-cap.ts +273 -0
  130. package/src/components/tools/shared/polygon-editor.tsx +276 -35
  131. package/src/components/tools/site/site-boundary-editor.tsx +101 -42
  132. package/src/components/tools/site/stroke-pointer-ownership.test.ts +91 -0
  133. package/src/components/tools/site/stroke-pointer-ownership.ts +57 -0
  134. package/src/components/tools/site/terrain-brush-cursor.tsx +247 -0
  135. package/src/components/tools/site/terrain-sculpt-grid.tsx +192 -0
  136. package/src/components/tools/site/terrain-sculpt-tool.tsx +356 -0
  137. package/src/components/tools/stair/stair-click-guard.test.ts +58 -0
  138. package/src/components/tools/stair/stair-click-guard.ts +72 -0
  139. package/src/components/tools/stair/stair-defaults.ts +1 -0
  140. package/src/components/tools/stair/stair-tool.tsx +164 -42
  141. package/src/components/tools/tool-manager.tsx +149 -30
  142. package/src/components/tools/wall/wall-drafting.test.ts +447 -0
  143. package/src/components/tools/wall/wall-drafting.ts +304 -108
  144. package/src/components/tools/wall/wall-snap-geometry.test.ts +52 -0
  145. package/src/components/tools/wall/wall-snap-geometry.ts +137 -9
  146. package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
  147. package/src/components/tools/zone/zone-tool.tsx +98 -88
  148. package/src/components/ui/action-menu/camera-actions.tsx +24 -17
  149. package/src/components/ui/action-menu/control-modes.tsx +44 -94
  150. package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
  151. package/src/components/ui/action-menu/index.tsx +2 -4
  152. package/src/components/ui/action-menu/measurement-control.tsx +267 -0
  153. package/src/components/ui/action-menu/structure-tools.tsx +22 -13
  154. package/src/components/ui/action-menu/view-toggles.tsx +42 -90
  155. package/src/components/ui/command-palette/editor-commands.tsx +16 -3
  156. package/src/components/ui/command-palette/index.tsx +4 -3
  157. package/src/components/ui/controls/material-paint-panel.tsx +93 -18
  158. package/src/components/ui/controls/material-picker.tsx +162 -152
  159. package/src/components/ui/controls/material-properties-editor.tsx +108 -0
  160. package/src/components/ui/controls/metric-control.tsx +118 -44
  161. package/src/components/ui/controls/scene-material-list.tsx +247 -0
  162. package/src/components/ui/controls/slider-control.tsx +88 -38
  163. package/src/components/ui/controls/terrain-sculpt-panel.tsx +193 -0
  164. package/src/components/ui/floating-level-selector.tsx +74 -20
  165. package/src/components/ui/helpers/building-helper.tsx +10 -23
  166. package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
  167. package/src/components/ui/helpers/helper-manager.tsx +273 -14
  168. package/src/components/ui/helpers/item-helper.tsx +28 -32
  169. package/src/components/ui/helpers/registered-tool-helper.tsx +45 -11
  170. package/src/components/ui/helpers/roof-helper.tsx +10 -12
  171. package/src/components/ui/icon-ref.tsx +47 -0
  172. package/src/components/ui/item-catalog/catalog-items.tsx +39 -1
  173. package/src/components/ui/item-catalog/item-catalog.tsx +13 -36
  174. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  175. package/src/components/ui/panels/multi-selection-panel.tsx +57 -0
  176. package/src/components/ui/panels/node-display.ts +17 -17
  177. package/src/components/ui/panels/panel-manager.tsx +36 -14
  178. package/src/components/ui/panels/panel-wrapper.tsx +24 -3
  179. package/src/components/ui/panels/parametric-inspector.tsx +19 -4
  180. package/src/components/ui/panels/reference-panel.tsx +54 -19
  181. package/src/components/ui/panels/selection-breakdown.test.ts +24 -0
  182. package/src/components/ui/panels/selection-breakdown.ts +20 -0
  183. package/src/components/ui/primitives/shortcut-token.tsx +39 -3
  184. package/src/components/ui/primitives/sidebar.tsx +1 -0
  185. package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
  186. package/src/components/ui/sidebar/icon-rail.tsx +50 -31
  187. package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
  188. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +82 -41
  189. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +76 -6
  190. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +12 -4
  191. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -1
  192. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -3
  193. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +10 -7
  194. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
  195. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
  196. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +10 -7
  197. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -1
  198. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +1 -1
  199. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +10 -7
  200. package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -17
  201. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
  202. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
  203. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +72 -25
  204. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
  205. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +12 -8
  206. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +4 -3
  207. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +10 -7
  208. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
  209. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
  210. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +44 -3
  211. package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
  212. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
  213. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
  214. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
  215. package/src/components/ui/sidebar/tab-bar.tsx +42 -28
  216. package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
  217. package/src/components/ui/snap-target-badge.test.tsx +40 -0
  218. package/src/components/ui/snap-target-badge.tsx +88 -0
  219. package/src/components/viewer/viewer-controls-bar.tsx +466 -0
  220. package/src/components/viewer/viewer-scene-header.tsx +235 -0
  221. package/src/components/viewer-overlay.tsx +36 -596
  222. package/src/components/viewer-zone-system.tsx +6 -1
  223. package/src/components/walkthrough-hud.tsx +111 -0
  224. package/src/hooks/use-auto-save.test.ts +14 -0
  225. package/src/hooks/use-auto-save.ts +54 -7
  226. package/src/hooks/use-ceiling-events.ts +3 -2
  227. package/src/hooks/use-drag-action.ts +4 -1
  228. package/src/hooks/use-grid-events.ts +24 -0
  229. package/src/hooks/use-keyboard.ts +438 -70
  230. package/src/index.tsx +355 -9
  231. package/src/lib/active-placement-surface.test.ts +24 -0
  232. package/src/lib/active-placement-surface.ts +42 -0
  233. package/src/lib/camera-pose.test.ts +237 -0
  234. package/src/lib/camera-pose.ts +172 -0
  235. package/src/lib/ceiling-plan-snap.ts +24 -0
  236. package/src/lib/contextual-help.test.ts +112 -0
  237. package/src/lib/contextual-help.ts +144 -0
  238. package/src/lib/continuation.ts +64 -0
  239. package/src/lib/direct-manipulation.test.ts +275 -0
  240. package/src/lib/direct-manipulation.ts +118 -0
  241. package/src/lib/door-interaction.ts +1 -1
  242. package/src/lib/editor-api.ts +23 -35
  243. package/src/lib/elevation-guides.test.ts +107 -0
  244. package/src/lib/elevation-guides.ts +236 -0
  245. package/src/lib/floorplan/annotation-visibility.test.ts +327 -0
  246. package/src/lib/floorplan/annotation-visibility.ts +133 -0
  247. package/src/lib/floorplan/apply-alignment.test.ts +25 -0
  248. package/src/lib/floorplan/apply-alignment.ts +18 -12
  249. package/src/lib/floorplan/drawing-coordination.test.ts +46 -0
  250. package/src/lib/floorplan/drawing-coordination.ts +13 -0
  251. package/src/lib/floorplan/floorplan-export.test.ts +319 -0
  252. package/src/lib/floorplan/floorplan-export.tsx +978 -0
  253. package/src/lib/floorplan/floorplan-extension.test.ts +43 -0
  254. package/src/lib/floorplan/floorplan-extension.ts +181 -0
  255. package/src/lib/floorplan/floorplan-mode.test.ts +120 -0
  256. package/src/lib/floorplan/floorplan-mode.ts +73 -0
  257. package/src/lib/floorplan/floorplan-pdfkit-document.ts +194 -0
  258. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +107 -0
  259. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +680 -0
  260. package/src/lib/floorplan/geometry.ts +52 -0
  261. package/src/lib/floorplan/index.ts +3 -0
  262. package/src/lib/floorplan/items.ts +5 -2
  263. package/src/lib/floorplan/plan-coords.ts +21 -0
  264. package/src/lib/fresh-planar-placement.test.ts +283 -3
  265. package/src/lib/fresh-planar-placement.ts +85 -1
  266. package/src/lib/glb-export.test.ts +556 -0
  267. package/src/lib/glb-export.ts +1071 -0
  268. package/src/lib/ground-surface.ts +95 -0
  269. package/src/lib/history.test.ts +96 -0
  270. package/src/lib/history.ts +31 -0
  271. package/src/lib/interaction/hot-set.test.ts +133 -0
  272. package/src/lib/interaction/hot-set.ts +67 -0
  273. package/src/lib/interaction/overlay-policy.test.ts +52 -0
  274. package/src/lib/interaction/overlay-policy.ts +59 -0
  275. package/src/lib/interaction/scope.ts +243 -0
  276. package/src/lib/level-duplication.test.ts +2 -1
  277. package/src/lib/level-selection.ts +2 -2
  278. package/src/lib/material-paint.ts +36 -49
  279. package/src/lib/measurement-kind.test.ts +30 -0
  280. package/src/lib/measurement-kind.ts +19 -0
  281. package/src/lib/measurement-label.test.ts +47 -0
  282. package/src/lib/measurement-label.ts +68 -0
  283. package/src/lib/measurement-parser.ts +116 -0
  284. package/src/lib/measurements.test.ts +188 -0
  285. package/src/lib/measurements.ts +210 -0
  286. package/src/lib/paint-scope.test.ts +454 -0
  287. package/src/lib/paint-scope.ts +461 -0
  288. package/src/lib/placement-drag-release.ts +23 -0
  289. package/src/lib/planar-cursor-placement.test.ts +57 -0
  290. package/src/lib/plugin-panels.test.ts +19 -0
  291. package/src/lib/plugin-panels.ts +91 -0
  292. package/src/lib/quick-action-feedback.ts +33 -0
  293. package/src/lib/quick-action-nodes.test.ts +94 -0
  294. package/src/lib/quick-action-nodes.ts +47 -0
  295. package/src/lib/quick-measurement.test.ts +77 -0
  296. package/src/lib/quick-measurement.ts +109 -0
  297. package/src/lib/roof-duplication.ts +6 -6
  298. package/src/lib/roof-hover-outline-proxy.ts +22 -0
  299. package/src/lib/roof-wall-hit.ts +164 -0
  300. package/src/lib/scene-clipboard.test.ts +325 -0
  301. package/src/lib/scene-clipboard.ts +310 -21
  302. package/src/lib/scene.ts +28 -5
  303. package/src/lib/selection-routing.test.ts +313 -0
  304. package/src/lib/selection-routing.ts +180 -0
  305. package/src/lib/sfx/index.ts +1 -0
  306. package/src/lib/sfx/movement-tick.test.ts +65 -0
  307. package/src/lib/sfx/movement-tick.ts +18 -0
  308. package/src/lib/sfx-bus.ts +82 -14
  309. package/src/lib/sfx-player.test.ts +266 -0
  310. package/src/lib/sfx-player.ts +262 -69
  311. package/src/lib/site-boundary.test.ts +53 -0
  312. package/src/lib/site-boundary.ts +27 -0
  313. package/src/lib/slab-plan-snap.test.ts +93 -0
  314. package/src/lib/slab-plan-snap.ts +108 -0
  315. package/src/lib/snapping-mode.test.ts +165 -0
  316. package/src/lib/snapping-mode.ts +191 -0
  317. package/src/lib/stair-duplication.test.ts +103 -0
  318. package/src/lib/stair-duplication.ts +58 -86
  319. package/src/lib/stair-levels.ts +2 -0
  320. package/src/lib/surface-plan-snap.test.ts +71 -0
  321. package/src/lib/surface-plan-snap.ts +256 -0
  322. package/src/lib/terrain-sculpt.test.ts +429 -0
  323. package/src/lib/terrain-sculpt.ts +301 -0
  324. package/src/lib/terrain-verb-color.test.ts +51 -0
  325. package/src/lib/terrain-verb-color.ts +58 -0
  326. package/src/lib/touch-gesture-priority.test.ts +45 -0
  327. package/src/lib/touch-gesture-priority.ts +30 -0
  328. package/src/lib/use-linear-display.ts +40 -0
  329. package/src/lib/window-interaction.ts +1 -1
  330. package/src/lib/world-grid-snap.ts +37 -5
  331. package/src/store/camera-pose-store.test.ts +29 -0
  332. package/src/store/camera-pose-store.ts +27 -0
  333. package/src/store/live-draft-preview-stores.test.ts +136 -0
  334. package/src/store/navigation-sync-pose-store.test.ts +34 -0
  335. package/src/store/navigation-sync-pose-store.ts +27 -0
  336. package/src/store/terrain-sculpt-mode.test.ts +223 -0
  337. package/src/store/use-delete-confirmation.ts +27 -0
  338. package/src/store/use-direct-manipulation-feedback.ts +20 -0
  339. package/src/store/use-drawing-view.test.ts +23 -0
  340. package/src/store/use-drawing-view.ts +84 -0
  341. package/src/store/use-editor.tsx +665 -126
  342. package/src/store/use-elevation-guides.ts +40 -0
  343. package/src/store/use-facing-pose.ts +44 -0
  344. package/src/store/use-fence-curve-draft.ts +51 -0
  345. package/src/store/use-first-person-hud.ts +40 -0
  346. package/src/store/use-floorplan-annotation-visibility.ts +60 -0
  347. package/src/store/use-floorplan-draft-preview.ts +151 -0
  348. package/src/store/use-floorplan-marquee.ts +50 -0
  349. package/src/store/use-floorplan-mode.ts +110 -0
  350. package/src/store/use-interaction-scope.test.ts +206 -0
  351. package/src/store/use-interaction-scope.ts +140 -0
  352. package/src/store/use-measurement-draft.test.ts +559 -0
  353. package/src/store/use-measurement-draft.ts +570 -0
  354. package/src/store/use-opening-guides.ts +41 -0
  355. package/src/store/use-path-draft-preview.ts +90 -0
  356. package/src/store/use-placement-preview.ts +11 -3
  357. package/src/store/use-quick-measurement-hud.test.ts +53 -0
  358. package/src/store/use-quick-measurement-hud.ts +77 -0
  359. package/src/store/use-segment-draft-chain.ts +28 -0
  360. package/src/store/use-stair-build-preview.ts +53 -0
  361. package/src/store/use-wall-snap-indicator.ts +2 -0
  362. package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
  363. package/src/components/editor/group-move-handle.tsx +0 -316
  364. package/src/components/ui/panels/paint-panel.tsx +0 -163
  365. package/src/lib/floorplan/selection-tool.ts +0 -271
  366. package/src/lib/level-name.ts +0 -11
@@ -6,24 +6,45 @@ import { furnishTools } from '../../../components/ui/action-menu/furnish-tools'
6
6
  import { tools } from '../../../components/ui/action-menu/structure-tools'
7
7
  import { EDITOR_LAYER } from '../../../lib/constants'
8
8
  import useEditor from '../../../store/use-editor'
9
+ import useWallSnapIndicator from '../../../store/use-wall-snap-indicator'
9
10
 
10
11
  interface CursorSphereProps extends Omit<ThreeElements['group'], 'ref'> {
11
12
  color?: string
12
13
  depthWrite?: boolean
13
14
  showTooltip?: boolean
14
15
  height?: number
16
+ /**
17
+ * Put the bright marker dot at the TIP of the vertical line (y = height)
18
+ * instead of on the ground ring. Used when the point being placed hangs
19
+ * above the floor (e.g. duct drawn against the ceiling): the dot rides at
20
+ * the cursor / placement point while the line drops to a floor ring that
21
+ * keeps the plan position readable.
22
+ */
23
+ dotAtTip?: boolean
15
24
  /** Custom tooltip content — overrides the auto-detected build tool icon */
16
25
  tooltipContent?: React.ReactNode
17
26
  }
18
27
 
19
28
  export const CursorSphere = forwardRef<Group, CursorSphereProps>(function CursorSphere(
20
- { color = '#818cf8', showTooltip = true, height = 2.5, visible = true, tooltipContent, ...props },
29
+ {
30
+ color = '#818cf8',
31
+ showTooltip = true,
32
+ height = 2.5,
33
+ dotAtTip = false,
34
+ visible = true,
35
+ tooltipContent,
36
+ ...props
37
+ },
21
38
  ref,
22
39
  ) {
23
40
  const tool = useEditor((s) => s.tool)
24
41
  const mode = useEditor((s) => s.mode)
25
42
  const catalogCategory = useEditor((s) => s.catalogCategory)
26
43
  const isFloorplanHovered = useEditor((s) => s.isFloorplanHovered)
44
+ // While a wall snap is active the beacon layer already marks the point
45
+ // (green square at a corner); hide the cursor's ground dot/ring so it
46
+ // doesn't sit on top of that glyph.
47
+ const isSnapping = useWallSnapIndicator((s) => s.point !== null)
27
48
 
28
49
  // Find the icon for the current tool
29
50
  let activeToolConfig = null
@@ -39,32 +60,38 @@ export const CursorSphere = forwardRef<Group, CursorSphereProps>(function Cursor
39
60
 
40
61
  return (
41
62
  <group ref={ref} {...props} visible={isVisible}>
42
- {/* Flat marker on the ground */}
43
- <group rotation={[-Math.PI / 2, 0, 0]}>
44
- {/* Center dot */}
45
- <mesh layers={EDITOR_LAYER} renderOrder={2}>
46
- <circleGeometry args={[0.06, 32]} />
47
- <meshBasicMaterial
48
- color={color}
49
- depthTest={false}
50
- depthWrite={false}
51
- opacity={0.9}
52
- transparent
53
- />
54
- </mesh>
63
+ {/* Flat marker on the ground. The bright center dot moves to the tip
64
+ of the line in `dotAtTip` mode (the placement point hangs above the
65
+ floor), leaving a faint ring here so the plan position stays read. */}
66
+ {!isSnapping && (
67
+ <group rotation={[-Math.PI / 2, 0, 0]}>
68
+ {/* Center dot — at the ground unless the placement point is elevated */}
69
+ {!dotAtTip && (
70
+ <mesh layers={EDITOR_LAYER} renderOrder={2}>
71
+ <circleGeometry args={[0.06, 32]} />
72
+ <meshBasicMaterial
73
+ color={color}
74
+ depthTest={false}
75
+ depthWrite={false}
76
+ opacity={0.9}
77
+ transparent
78
+ />
79
+ </mesh>
80
+ )}
55
81
 
56
- {/* Outer ring / glow */}
57
- <mesh layers={EDITOR_LAYER} renderOrder={2}>
58
- <circleGeometry args={[0.2, 32]} />
59
- <meshBasicMaterial
60
- color={color}
61
- depthTest={false}
62
- depthWrite={false}
63
- opacity={0.25}
64
- transparent
65
- />
66
- </mesh>
67
- </group>
82
+ {/* Outer ring / glow */}
83
+ <mesh layers={EDITOR_LAYER} renderOrder={2}>
84
+ <circleGeometry args={[0.2, 32]} />
85
+ <meshBasicMaterial
86
+ color={color}
87
+ depthTest={false}
88
+ depthWrite={false}
89
+ opacity={dotAtTip ? 0.2 : 0.25}
90
+ transparent
91
+ />
92
+ </mesh>
93
+ </group>
94
+ )}
68
95
 
69
96
  {/* Vertical line */}
70
97
  {height > 0 && (
@@ -80,6 +107,15 @@ export const CursorSphere = forwardRef<Group, CursorSphereProps>(function Cursor
80
107
  </mesh>
81
108
  )}
82
109
 
110
+ {/* Bright marker dot at the tip of the line — the actual placement
111
+ point, riding at the cursor while the line drops to the floor. */}
112
+ {dotAtTip && height > 0 && (
113
+ <mesh layers={EDITOR_LAYER} position={[0, height, 0]} renderOrder={2}>
114
+ <sphereGeometry args={[0.08, 20, 14]} />
115
+ <meshBasicMaterial color={color} depthTest={false} depthWrite={false} />
116
+ </mesh>
117
+ )}
118
+
83
119
  {/* Tool Icon Tooltip at the top of the line */}
84
120
  {isVisible && showTooltip && (activeToolConfig || tooltipContent) && (
85
121
  <Html
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { sceneRegistry } from '@pascal-app/core'
3
+ import { type AnyNodeId, resolveFacingIndicator, sceneRegistry, useScene } from '@pascal-app/core'
4
4
  import { useEffect, useMemo } from 'react'
5
5
  import {
6
6
  Box3,
@@ -15,6 +15,7 @@ import {
15
15
  import { distance, smoothstep, uv, vec2 } from 'three/tsl'
16
16
  import { LineBasicNodeMaterial, MeshBasicNodeMaterial } from 'three/webgpu'
17
17
  import { EDITOR_LAYER } from '../../../lib/constants'
18
+ import useFacingPose from '../../../store/use-facing-pose'
18
19
 
19
20
  const NO_RAYCAST = () => null
20
21
 
@@ -71,6 +72,8 @@ interface DragBoundingBoxProps {
71
72
  * includes per-level landings outside the shaft footprint).
72
73
  */
73
74
  size?: [number, number, number]
75
+ /** Local center of `size`. Defaults to the node origin plus half-height. */
76
+ center?: [number, number, number]
74
77
  /** Y center of the box in the node's local frame. Defaults to `size[1] / 2`. */
75
78
  centerY?: number
76
79
  color?: number
@@ -90,9 +93,13 @@ export function DragBoundingBox({
90
93
  rotationY = 0,
91
94
  fallbackSize = [0, 0, 0],
92
95
  size,
96
+ center,
93
97
  centerY,
94
98
  color = DEFAULT_COLOR,
95
99
  }: DragBoundingBoxProps) {
100
+ const nodeType = useScene((state) => state.nodes[nodeId as AnyNodeId]?.type)
101
+ const facing = nodeType ? resolveFacingIndicator(nodeType) : null
102
+
96
103
  const measured = useMemo(() => {
97
104
  if (size) return null
98
105
  const obj = sceneRegistry.nodes.get(nodeId)
@@ -101,9 +108,10 @@ export function DragBoundingBox({
101
108
 
102
109
  const [w, h, d] = size ?? measured?.size ?? fallbackSize
103
110
  const [cx, cy, cz] = size
104
- ? [0, centerY ?? size[1] / 2, 0]
111
+ ? (center ?? [0, centerY ?? size[1] / 2, 0])
105
112
  : (measured?.center ?? [0, fallbackSize[1] / 2, 0])
106
113
  const minY = cy - h / 2
114
+ const groundY = minY + 0.01
107
115
 
108
116
  const edgeGeometry = useMemo(() => {
109
117
  const box = new BoxGeometry(w, h, d)
@@ -117,9 +125,9 @@ export function DragBoundingBox({
117
125
  const planeGeometry = useMemo(() => {
118
126
  const plane = new PlaneGeometry(w, d)
119
127
  plane.rotateX(-Math.PI / 2)
120
- plane.translate(cx, minY + 0.01, cz)
128
+ plane.translate(cx, groundY, cz)
121
129
  return plane
122
- }, [w, d, cx, minY, cz])
130
+ }, [w, d, cx, groundY, cz])
123
131
 
124
132
  const edgeMaterial = useMemo(
125
133
  () => new LineBasicNodeMaterial({ color, linewidth: 3, depthTest: false, depthWrite: false }),
@@ -147,6 +155,22 @@ export function DragBoundingBox({
147
155
  [edgeGeometry, planeGeometry, edgeMaterial, planeMaterial],
148
156
  )
149
157
 
158
+ // Publish the facing pose to the editor-side overlay (the single triangle
159
+ // renderer) rather than drawing it here. The node origin is `position`; the
160
+ // footprint centre is `[cx, cz]` in the node's local frame. Runs each drag
161
+ // frame so the triangle follows; a separate mount/unmount effect clears it.
162
+ useEffect(() => {
163
+ if (!facing || d <= 0) return
164
+ useFacingPose.getState().set({
165
+ position: [position[0], position[1] + groundY, position[2]],
166
+ rotationY,
167
+ depth: d,
168
+ center: [cx, cz],
169
+ reversed: facing.reversed,
170
+ })
171
+ }, [facing, position, rotationY, d, cx, cz, groundY])
172
+ useEffect(() => () => useFacingPose.getState().clear(), [])
173
+
150
174
  if (w <= 0 || h <= 0 || d <= 0) return null
151
175
 
152
176
  return (
@@ -0,0 +1,87 @@
1
+ import { useEffect, useMemo } from 'react'
2
+ import { BufferGeometry, DoubleSide, Float32BufferAttribute } from 'three'
3
+ import { MeshBasicNodeMaterial } from 'three/webgpu'
4
+ import { EDITOR_LAYER } from '../../../lib/constants'
5
+
6
+ // A flat forward-pointing triangle drawn on the floor just in front of a
7
+ // placement ghost, so the direction the node will face is obvious. Tip at local
8
+ // +Z (every kind's forward face); render it inside the ghost's rotated group so
9
+ // it inherits the node's yaw. Matches the item coordinator / drag-bounding-box
10
+ // indicators (same size, colour, double-sided so it shows from above).
11
+ const FACING_INDICATOR_WIDTH = 0.4
12
+ const FACING_INDICATOR_LENGTH = 0.46
13
+ const FACING_INDICATOR_GAP = 0.45
14
+
15
+ /**
16
+ * @param depth bbox depth (along local Z) of the ghost — positions the
17
+ * triangle just past the front edge.
18
+ * @param center optional [x, z] of the bbox centre in the ghost's local frame.
19
+ * @param reversed point along local -Z (the front is the -Z side, e.g. a stair
20
+ * entry) instead of +Z.
21
+ * @param y small lift off the floor to avoid z-fighting.
22
+ */
23
+ export function FacingIndicator({
24
+ depth,
25
+ center = [0, 0],
26
+ reversed = false,
27
+ y = 0.02,
28
+ }: {
29
+ depth: number
30
+ center?: [number, number]
31
+ reversed?: boolean
32
+ y?: number
33
+ }) {
34
+ const dir = reversed ? -1 : 1
35
+ // Per-instance geometry/material (not module singletons) so this works no
36
+ // matter which package mounts it (the tools live in `nodes`, imported via
37
+ // `@pascal-app/editor`). Disposed on unmount.
38
+ const geometry = useMemo(() => {
39
+ const g = new BufferGeometry()
40
+ g.setAttribute(
41
+ 'position',
42
+ new Float32BufferAttribute(
43
+ [
44
+ 0,
45
+ 0,
46
+ dir * FACING_INDICATOR_LENGTH,
47
+ FACING_INDICATOR_WIDTH / 2,
48
+ 0,
49
+ 0,
50
+ -FACING_INDICATOR_WIDTH / 2,
51
+ 0,
52
+ 0,
53
+ ],
54
+ 3,
55
+ ),
56
+ )
57
+ return g
58
+ }, [dir])
59
+ const material = useMemo(
60
+ () =>
61
+ new MeshBasicNodeMaterial({
62
+ color: 0x22_c5_5e, // green-500 (forward)
63
+ depthTest: false,
64
+ depthWrite: false,
65
+ side: DoubleSide,
66
+ }),
67
+ [],
68
+ )
69
+ useEffect(
70
+ () => () => {
71
+ geometry.dispose()
72
+ material.dispose()
73
+ },
74
+ [geometry, material],
75
+ )
76
+
77
+ return (
78
+ <mesh
79
+ frustumCulled={false}
80
+ geometry={geometry}
81
+ layers={EDITOR_LAYER}
82
+ material={material}
83
+ position={[center[0], y, center[1] + dir * (depth / 2 + FACING_INDICATOR_GAP)]}
84
+ renderOrder={1001}
85
+ />
86
+ )
87
+ }
@@ -0,0 +1,54 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+ import type { Group } from 'three'
3
+ import useFacingPose, { type FacingPose } from '../../../store/use-facing-pose'
4
+ import { FacingIndicator } from './facing-indicator'
5
+
6
+ // The single editor-side renderer for the placement/move facing triangle.
7
+ // Mounted once inside ToolManager's building-local group; every tool publishes
8
+ // its ghost pose to `useFacingPose` and this draws the triangle. The pose
9
+ // (position/yaw) is applied imperatively to a ref so the per-frame cursor
10
+ // updates don't re-render React — only a change in footprint shape (depth /
11
+ // centre), which is constant per tool session, triggers a re-render.
12
+ export function FacingPoseIndicator() {
13
+ const groupRef = useRef<Group>(null)
14
+ const [shape, setShape] = useState<Pick<FacingPose, 'depth' | 'center' | 'reversed'> | null>(null)
15
+
16
+ useEffect(() => {
17
+ const apply = (pose: FacingPose | null) => {
18
+ const group = groupRef.current
19
+ if (group) {
20
+ if (pose) {
21
+ group.visible = true
22
+ group.position.set(...pose.position)
23
+ group.rotation.y = pose.rotationY
24
+ } else {
25
+ group.visible = false
26
+ }
27
+ }
28
+ setShape((prev) => {
29
+ if (!pose) return null
30
+ const center = pose.center ?? [0, 0]
31
+ if (
32
+ prev &&
33
+ prev.depth === pose.depth &&
34
+ prev.reversed === pose.reversed &&
35
+ (prev.center ?? [0, 0])[0] === center[0] &&
36
+ (prev.center ?? [0, 0])[1] === center[1]
37
+ ) {
38
+ return prev
39
+ }
40
+ return { depth: pose.depth, center, reversed: pose.reversed }
41
+ })
42
+ }
43
+ apply(useFacingPose.getState().pose)
44
+ return useFacingPose.subscribe((state) => apply(state.pose))
45
+ }, [])
46
+
47
+ return (
48
+ <group ref={groupRef} visible={false}>
49
+ {shape ? (
50
+ <FacingIndicator center={shape.center} depth={shape.depth} reversed={shape.reversed} />
51
+ ) : null}
52
+ </group>
53
+ )
54
+ }
@@ -6,6 +6,8 @@ type FloorStackPreviewArgs = {
6
6
  rotation?: unknown
7
7
  levelId?: string | null
8
8
  nodes?: Record<AnyNodeId, AnyNode>
9
+ /** Pointer-decided support cap — see `FloorPlacedElevationArgs.maxElevation`. */
10
+ maxElevation?: number | null
9
11
  }
10
12
 
11
13
  export function getFloorStackPreviewPosition({
@@ -14,6 +16,7 @@ export function getFloorStackPreviewPosition({
14
16
  rotation,
15
17
  levelId,
16
18
  nodes,
19
+ maxElevation,
17
20
  }: FloorStackPreviewArgs): [number, number, number] {
18
21
  return getFloorStackedPosition({
19
22
  node,
@@ -21,5 +24,6 @@ export function getFloorStackPreviewPosition({
21
24
  position,
22
25
  rotation,
23
26
  levelId,
27
+ maxElevation,
24
28
  })
25
29
  }
@@ -0,0 +1,123 @@
1
+ import {
2
+ type AnyNodeId,
3
+ GROUND_SUPPORT_ID,
4
+ type GridEvent,
5
+ sceneRegistry,
6
+ terrainSupportLift,
7
+ useScene,
8
+ } from '@pascal-app/core'
9
+ import { useViewer } from '@pascal-app/viewer'
10
+ import { Vector3 } from 'three'
11
+ import { publishPlacementSurface } from '../../../lib/active-placement-surface'
12
+ import type { PointerSupportSurface } from './pointer-support-cap'
13
+
14
+ const SURFACE_UP = new Vector3(0, 1, 0)
15
+ const worldScratch = new Vector3()
16
+ const localScratch = new Vector3()
17
+ const surfacePointScratch = new Vector3()
18
+
19
+ export type HorizontalConstructionPlane = {
20
+ /** Building-local Y used by the cursor and draft preview. */
21
+ localY: number
22
+ /** World Y used by the shared placement grid. */
23
+ worldY: number
24
+ /** Level-local base elevation used when the node is committed. */
25
+ elevation: number | null
26
+ /** Support source selected at the first click. */
27
+ supportSlabId: string | null
28
+ /** Optional scene node from which this frozen plane was derived. */
29
+ sourceNodeId: AnyNodeId | null
30
+ }
31
+
32
+ export function resolveEventConstructionPlane(
33
+ event: GridEvent,
34
+ pointed: PointerSupportSurface | null,
35
+ ): HorizontalConstructionPlane {
36
+ if (pointed) {
37
+ return {
38
+ localY: pointed.localPoint?.[1] ?? event.localPosition[1],
39
+ worldY: pointed.worldY,
40
+ elevation: pointed.elevation,
41
+ supportSlabId: pointed.supportSlabId,
42
+ sourceNodeId: pointed.sourceNodeId,
43
+ }
44
+ }
45
+
46
+ let elevation: number | null = null
47
+ const levelId = useViewer.getState().selection.levelId
48
+ const levelMesh = levelId ? sceneRegistry.nodes.get(levelId as AnyNodeId) : undefined
49
+ if (levelMesh) {
50
+ localScratch.set(event.position[0], event.position[1], event.position[2])
51
+ levelMesh.worldToLocal(localScratch)
52
+ elevation = localScratch.y
53
+ }
54
+
55
+ return {
56
+ localY: event.localPosition[1],
57
+ worldY: event.position[1],
58
+ elevation,
59
+ supportSlabId: null,
60
+ sourceNodeId: null,
61
+ }
62
+ }
63
+
64
+ export function resolveLevelConstructionPlane(): HorizontalConstructionPlane | null {
65
+ const { buildingId, levelId } = useViewer.getState().selection
66
+ if (!levelId) return null
67
+
68
+ const levelMesh = sceneRegistry.nodes.get(levelId as AnyNodeId)
69
+ worldScratch.set(0, 0, 0)
70
+ if (levelMesh) levelMesh.localToWorld(worldScratch)
71
+
72
+ const buildingMesh = buildingId ? sceneRegistry.nodes.get(buildingId as AnyNodeId) : undefined
73
+ localScratch.copy(worldScratch)
74
+ if (buildingMesh) buildingMesh.worldToLocal(localScratch)
75
+
76
+ return {
77
+ localY: localScratch.y,
78
+ worldY: worldScratch.y,
79
+ elevation: 0,
80
+ supportSlabId: null,
81
+ sourceNodeId: null,
82
+ }
83
+ }
84
+
85
+ export function resampleTerrainConstructionPlane(
86
+ plane: HorizontalConstructionPlane,
87
+ point: readonly [number, number],
88
+ ): HorizontalConstructionPlane {
89
+ if (plane.supportSlabId !== GROUND_SUPPORT_ID || plane.sourceNodeId) return plane
90
+
91
+ const levelId = useViewer.getState().selection.levelId
92
+ if (!levelId) return plane
93
+ const elevation = terrainSupportLift(useScene.getState().nodes, levelId, point[0], point[1])
94
+ if (elevation == null) return plane
95
+
96
+ const levelMesh = sceneRegistry.nodes.get(levelId as AnyNodeId)
97
+ worldScratch.set(point[0], elevation, point[1])
98
+ if (levelMesh) levelMesh.localToWorld(worldScratch)
99
+
100
+ const buildingId = useViewer.getState().selection.buildingId
101
+ const buildingMesh = buildingId ? sceneRegistry.nodes.get(buildingId as AnyNodeId) : undefined
102
+ localScratch.copy(worldScratch)
103
+ if (buildingMesh) buildingMesh.worldToLocal(localScratch)
104
+
105
+ return {
106
+ localY: localScratch.y,
107
+ worldY: worldScratch.y,
108
+ elevation,
109
+ supportSlabId: GROUND_SUPPORT_ID,
110
+ sourceNodeId: null,
111
+ }
112
+ }
113
+
114
+ export function publishHorizontalConstructionPlane(
115
+ event: GridEvent,
116
+ plane: HorizontalConstructionPlane,
117
+ ): void {
118
+ publishPlacementSurface(
119
+ surfacePointScratch.set(event.position[0], plane.worldY, event.position[2]),
120
+ SURFACE_UP,
121
+ 'fixed-plane',
122
+ )
123
+ }