@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
@@ -0,0 +1,25 @@
1
+ import { afterEach, describe, expect, test } from 'bun:test'
2
+ import useAlignmentGuides from '../../store/use-alignment-guides'
3
+ import { applyFloorplanAlignment } from './apply-alignment'
4
+
5
+ describe('applyFloorplanAlignment', () => {
6
+ afterEach(() => {
7
+ useAlignmentGuides.getState().clear()
8
+ })
9
+
10
+ test('can publish passive guides without applying snap', () => {
11
+ useAlignmentGuides.getState().clear()
12
+
13
+ const result = applyFloorplanAlignment(
14
+ [0.04, 2],
15
+ [{ nodeId: 'draft', kind: 'corner', x: 0.04, z: 2 }],
16
+ [{ nodeId: 'wall_a', kind: 'corner', x: 0, z: 0 }],
17
+ { applySnap: false },
18
+ )
19
+
20
+ expect(result.point).toEqual([0.04, 2])
21
+ expect(result.snapped).toBe(false)
22
+ expect(result.guides).toHaveLength(1)
23
+ expect(useAlignmentGuides.getState().guides).toHaveLength(1)
24
+ })
25
+ })
@@ -2,10 +2,10 @@ import {
2
2
  type AlignmentAnchor,
3
3
  type AlignmentGuide,
4
4
  collectAlignmentAnchors,
5
- resolveAlignment,
6
5
  useScene,
7
6
  } from '@pascal-app/core'
8
- import { useAlignmentGuides } from '@pascal-app/editor'
7
+ import useAlignmentGuides from '../../store/use-alignment-guides'
8
+ import { resolveAlignmentForFloorplanView } from '../world-grid-snap'
9
9
 
10
10
  /**
11
11
  * Fixed Figma-style alignment threshold (meters) for floor-plan placement /
@@ -30,9 +30,9 @@ export type FloorplanAlignmentResult = {
30
30
  *
31
31
  * Publishes guides to the `useAlignmentGuides` store as a side effect — set
32
32
  * on a match, cleared otherwise — so the mounted `FloorplanAlignmentGuideLayer`
33
- * renders them. Returns the adjusted point. When `bypass` is true (Alt held)
34
- * the point is returned unchanged and guides are cleared, matching the
35
- * "No snap" affordance the placement tools advertise.
33
+ * renders them. Returns the adjusted point. When `bypass` is true (Alt for
34
+ * alignment-only bypass, or Shift for the full guided-constraint bypass) the
35
+ * point is returned unchanged and guides are cleared.
36
36
  *
37
37
  * `candidates` should be gathered ONCE per drag (`collectAlignmentAnchors`);
38
38
  * the scene is stable during a single drag, so re-collecting per pointer-move
@@ -42,14 +42,14 @@ export function applyFloorplanAlignment(
42
42
  point: readonly [number, number],
43
43
  movingAnchors: AlignmentAnchor[],
44
44
  candidates: AlignmentAnchor[],
45
- opts?: { bypass?: boolean; threshold?: number },
45
+ opts?: { applySnap?: boolean; bypass?: boolean; threshold?: number },
46
46
  ): FloorplanAlignmentResult {
47
47
  if (opts?.bypass) {
48
48
  useAlignmentGuides.getState().clear()
49
49
  return { point: [point[0], point[1]], snapped: false, guides: [] }
50
50
  }
51
51
 
52
- const result = resolveAlignment({
52
+ const result = resolveAlignmentForFloorplanView({
53
53
  moving: movingAnchors,
54
54
  candidates,
55
55
  threshold: opts?.threshold ?? FLOORPLAN_ALIGNMENT_THRESHOLD_M,
@@ -57,7 +57,7 @@ export function applyFloorplanAlignment(
57
57
 
58
58
  useAlignmentGuides.getState().set(result.guides)
59
59
 
60
- if (!result.snap) {
60
+ if (!result.snap || opts?.applySnap === false) {
61
61
  return { point: [point[0], point[1]], snapped: false, guides: result.guides }
62
62
  }
63
63
  return {
@@ -81,8 +81,9 @@ export const FLOORPLAN_DRAFT_ALIGN_ID = '__floorplan_draft__'
81
81
  *
82
82
  * Used by BOTH the move-preview branch and the click-commit handler so the
83
83
  * committed vertex lands exactly where the preview showed it. Caller owns the
84
- * per-kind precedence (existing-wall endpoint/join snap wins; angle-snap
85
- * suppresses alignment) and only calls this when alignment should apply.
84
+ * per-kind precedence: existing-wall endpoint/join snap can still win, while
85
+ * angle-locked segments can pass `applySnap: false` to publish passive guide
86
+ * feedback without pulling the endpoint off its constrained ray.
86
87
  *
87
88
  * `excludeIds` drops those nodes' anchors from the candidate pool — used when
88
89
  * dragging a wall / fence endpoint so the moving endpoint doesn't try to
@@ -90,7 +91,12 @@ export const FLOORPLAN_DRAFT_ALIGN_ID = '__floorplan_draft__'
90
91
  */
91
92
  export function alignFloorplanDraftPoint(
92
93
  point: readonly [number, number],
93
- opts?: { bypass?: boolean; threshold?: number; excludeIds?: readonly string[] },
94
+ opts?: {
95
+ applySnap?: boolean
96
+ bypass?: boolean
97
+ threshold?: number
98
+ excludeIds?: readonly string[]
99
+ },
94
100
  ): [number, number] {
95
101
  if (opts?.bypass) {
96
102
  useAlignmentGuides.getState().clear()
@@ -105,7 +111,7 @@ export function alignFloorplanDraftPoint(
105
111
  point,
106
112
  [{ nodeId: FLOORPLAN_DRAFT_ALIGN_ID, kind: 'corner', x: point[0], z: point[1] }],
107
113
  candidates,
108
- { threshold: opts?.threshold },
114
+ { applySnap: opts?.applySnap, threshold: opts?.threshold },
109
115
  )
110
116
  return snapped
111
117
  }
@@ -0,0 +1,46 @@
1
+ import { afterEach, describe, expect, test } from 'bun:test'
2
+ import {
3
+ type AnyNode,
4
+ type ConstructionDrawingType,
5
+ nodeRegistry,
6
+ registerNode,
7
+ } from '@pascal-app/core'
8
+ import { z } from 'zod'
9
+ import { resolveNodeForDrawingType } from './drawing-coordination'
10
+ import { FLOORPLAN_NODE_EXTENSION_KEY } from './floorplan-extension'
11
+
12
+ describe('resolveNodeForDrawingType', () => {
13
+ afterEach(() => nodeRegistry._reset())
14
+
15
+ test('dispatches drawing coordination through the registered extension', () => {
16
+ const node = {
17
+ id: 'drawing-test_main',
18
+ type: 'drawing-test',
19
+ object: 'node',
20
+ parentId: null,
21
+ visible: true,
22
+ metadata: {},
23
+ } as unknown as AnyNode
24
+ registerNode({
25
+ kind: 'drawing-test',
26
+ schemaVersion: 1,
27
+ schema: z.object({ type: z.literal('drawing-test') }) as never,
28
+ category: 'utility',
29
+ defaults: () => ({}) as never,
30
+ extensions: {
31
+ [FLOORPLAN_NODE_EXTENSION_KEY]: {
32
+ resolveForDrawing: ({ drawingType }: { drawingType: ConstructionDrawingType }) =>
33
+ drawingType === 'floor-plan' ? null : node,
34
+ },
35
+ },
36
+ } as never)
37
+
38
+ expect(resolveNodeForDrawingType(node, { [node.id]: node }, 'floor-plan')).toBeNull()
39
+ expect(resolveNodeForDrawingType(node, { [node.id]: node }, 'foundation-plan')).toBe(node)
40
+ })
41
+
42
+ test('leaves nodes without a drawing extension unchanged', () => {
43
+ const node = { id: 'unknown', type: 'unknown' } as unknown as AnyNode
44
+ expect(resolveNodeForDrawingType(node, { [node.id]: node }, 'floor-plan')).toBe(node)
45
+ })
46
+ })
@@ -0,0 +1,13 @@
1
+ import { type AnyNode, type ConstructionDrawingType, nodeRegistry } from '@pascal-app/core'
2
+ import { getFloorplanNodeExtension } from './floorplan-extension'
3
+
4
+ export function resolveNodeForDrawingType(
5
+ node: AnyNode,
6
+ nodes: Record<string, AnyNode>,
7
+ drawingType: ConstructionDrawingType,
8
+ ): AnyNode | null {
9
+ const extension = getFloorplanNodeExtension(nodeRegistry.get(node.type))
10
+ return extension?.resolveForDrawing
11
+ ? extension.resolveForDrawing({ node, nodes, drawingType })
12
+ : node
13
+ }
@@ -0,0 +1,319 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import type { FloorplanGeometry } from '@pascal-app/core'
3
+ import { splitFloorplanOverlay } from '../../components/editor-2d/renderers/floorplan-registry-layer'
4
+ import { DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY } from './annotation-visibility'
5
+ import {
6
+ filterFloorplanExportOverlay,
7
+ fitPlanToBox,
8
+ isFloorplanExportAnnotationGeometry,
9
+ partitionFloorplanExportOverlay,
10
+ resolveFloorplanExportAnnotationVisibility,
11
+ resolveFloorplanExportNodeGeometry,
12
+ resolveFloorplanExportPlacement,
13
+ resolveFloorplanExportRotationDeg,
14
+ resolveFloorplanExportViewport,
15
+ resolveFloorplanExportViewState,
16
+ resolveFloorplanMeasurementSize,
17
+ resolveFloorplanPageLayout,
18
+ resolveFloorplanScreenUnitsPerPixel,
19
+ rotateFloorplanExportBounds,
20
+ } from './floorplan-export'
21
+ import { floorplanGeometryMetadata } from './floorplan-extension'
22
+
23
+ describe('filterFloorplanExportOverlay', () => {
24
+ test('preserves annotation metadata while splitting geometry passes', () => {
25
+ const contextualDimension = {
26
+ kind: 'group',
27
+ metadata: floorplanGeometryMetadata({ annotationRole: 'contextual-dimension' }),
28
+ children: [
29
+ {
30
+ kind: 'dimension',
31
+ start: [0, 0],
32
+ end: [2, 0],
33
+ offsetNormal: [0, 1],
34
+ offsetDistance: 0.3,
35
+ extensionOvershoot: 0.08,
36
+ text: '2m',
37
+ },
38
+ ],
39
+ } satisfies FloorplanGeometry
40
+
41
+ expect(splitFloorplanOverlay(contextualDimension).overlay).toMatchObject(contextualDimension)
42
+ })
43
+
44
+ test('preserves value labels and removes editing handles', () => {
45
+ const label = {
46
+ kind: 'dimension-label',
47
+ appearance: 'outlined',
48
+ cx: 1,
49
+ cy: 0,
50
+ text: '2.00m',
51
+ angle: 0,
52
+ } satisfies FloorplanGeometry
53
+ const overlay = {
54
+ kind: 'group',
55
+ children: [
56
+ label,
57
+ {
58
+ kind: 'endpoint-handle',
59
+ point: [0, 0],
60
+ state: 'idle',
61
+ affordance: 'move-measurement-vertex',
62
+ payload: { vertexIndex: 0 },
63
+ },
64
+ ],
65
+ } satisfies FloorplanGeometry
66
+
67
+ expect(filterFloorplanExportOverlay(overlay)).toEqual({
68
+ kind: 'group',
69
+ children: [label],
70
+ })
71
+ })
72
+
73
+ test('preserves wall, door, and window shapes used as annotation obstacles', () => {
74
+ const fixedGeometry = {
75
+ kind: 'group',
76
+ children: [
77
+ {
78
+ kind: 'polygon',
79
+ points: [
80
+ [0, 0],
81
+ [4, 0],
82
+ [4, 0.2],
83
+ [0, 0.2],
84
+ ],
85
+ fill: '#374151',
86
+ stroke: '#1f2937',
87
+ metadata: floorplanGeometryMetadata({ annotationObstacle: 'outline' }),
88
+ },
89
+ {
90
+ kind: 'path',
91
+ d: 'M 1 0 A 1 1 0 0 1 2 1',
92
+ fill: 'none',
93
+ stroke: '#64748b',
94
+ metadata: floorplanGeometryMetadata({ annotationObstacle: 'bounds' }),
95
+ },
96
+ {
97
+ kind: 'line',
98
+ x1: 2.5,
99
+ y1: 0,
100
+ x2: 3.5,
101
+ y2: 0,
102
+ stroke: '#1f2937',
103
+ metadata: floorplanGeometryMetadata({ annotationObstacle: 'bounds' }),
104
+ },
105
+ { kind: 'move-handle', point: [2, 0.1] },
106
+ ],
107
+ } satisfies FloorplanGeometry
108
+
109
+ const { overlay } = splitFloorplanOverlay(fixedGeometry)
110
+ expect(overlay).not.toBeNull()
111
+ expect(filterFloorplanExportOverlay(overlay!)).toEqual({
112
+ kind: 'group',
113
+ children: fixedGeometry.children.slice(0, 3),
114
+ transform: undefined,
115
+ })
116
+ })
117
+
118
+ test('keeps structural obstacles in model bounds while leaving marks as annotations', () => {
119
+ const wall = {
120
+ kind: 'polygon',
121
+ points: [
122
+ [0, 0],
123
+ [4, 0],
124
+ [4, 0.2],
125
+ [0, 0.2],
126
+ ],
127
+ fill: '#374151',
128
+ metadata: floorplanGeometryMetadata({ annotationObstacle: 'outline' }),
129
+ } satisfies FloorplanGeometry
130
+ const openingMark = {
131
+ kind: 'group',
132
+ metadata: floorplanGeometryMetadata({ annotationRole: 'opening-mark' }),
133
+ children: [
134
+ {
135
+ kind: 'rect',
136
+ x: 1,
137
+ y: 1,
138
+ width: 0.4,
139
+ height: 0.2,
140
+ fill: '#ffffff',
141
+ stroke: '#334155',
142
+ },
143
+ { kind: 'text', x: 1.2, y: 1.1, text: 'W01', fontSize: 0.1, upright: true },
144
+ ],
145
+ } satisfies FloorplanGeometry
146
+
147
+ expect(
148
+ partitionFloorplanExportOverlay({ kind: 'group', children: [wall, openingMark] }),
149
+ ).toEqual({
150
+ model: { kind: 'group', children: [wall], transform: undefined },
151
+ annotations: { kind: 'group', children: [openingMark], transform: undefined },
152
+ })
153
+ })
154
+
155
+ test('moves automatic dimensions embedded in base wall geometry into the PDF annotation layer', () => {
156
+ const wall = {
157
+ kind: 'polygon',
158
+ points: [
159
+ [0, 0],
160
+ [4, 0],
161
+ [4, 0.2],
162
+ [0, 0.2],
163
+ ],
164
+ fill: '#374151',
165
+ } satisfies FloorplanGeometry
166
+ const dimensions = {
167
+ kind: 'dimension-string',
168
+ segments: [{ start: [0, 0], end: [4, 0], text: '4m' }],
169
+ offsetNormal: [0, -1],
170
+ offsetDistance: 1,
171
+ extensionOvershoot: 0.12,
172
+ } satisfies FloorplanGeometry
173
+
174
+ expect(
175
+ resolveFloorplanExportNodeGeometry(
176
+ { kind: 'group', children: [wall, dimensions] },
177
+ null,
178
+ false,
179
+ ),
180
+ ).toEqual({
181
+ model: { kind: 'group', children: [wall], transform: undefined },
182
+ annotations: { kind: 'group', children: [dimensions], transform: undefined },
183
+ })
184
+ })
185
+ })
186
+
187
+ describe('fitPlanToBox', () => {
188
+ test('preserves aspect ratio and centers the plan', () => {
189
+ expect(fitPlanToBox(20, 10, 10, 20, 400, 300)).toEqual({
190
+ x: 10,
191
+ y: 70,
192
+ width: 400,
193
+ height: 200,
194
+ })
195
+ })
196
+ })
197
+
198
+ describe('floor plan export policy', () => {
199
+ test('uses the live floor-plan formatting profile for metric and imperial dimensions', () => {
200
+ expect(resolveFloorplanExportViewState('metric', 'millimeters')).toMatchObject({
201
+ purpose: 'edit',
202
+ unit: 'metric',
203
+ metricNotation: 'millimeters',
204
+ })
205
+ expect(resolveFloorplanExportViewState('imperial', 'meters')).toMatchObject({
206
+ purpose: 'edit',
207
+ unit: 'imperial',
208
+ metricNotation: 'meters',
209
+ })
210
+ })
211
+
212
+ test('fits an oversized plan inside the complete export viewport', () => {
213
+ const placement = resolveFloorplanExportPlacement(30, 20, 10, 20, 400, 300)
214
+
215
+ expect(placement.x).toBe(10)
216
+ expect(placement.y).toBeCloseTo(36.67, 2)
217
+ expect(placement.width).toBe(400)
218
+ expect(placement.height).toBeCloseTo(266.67, 2)
219
+ expect(placement.x).toBeGreaterThanOrEqual(10)
220
+ expect(placement.y).toBeGreaterThanOrEqual(20)
221
+ expect(placement.x + placement.width).toBeLessThanOrEqual(410)
222
+ expect(placement.y + placement.height).toBeLessThanOrEqual(320)
223
+ })
224
+
225
+ test('exports the same annotation categories that are visible in the live view', () => {
226
+ const liveVisibility = {
227
+ automaticDimensions: true,
228
+ contextualDimensions: false,
229
+ manualDimensions: false,
230
+ measurements: true,
231
+ openingMarks: true,
232
+ structuralGrids: false,
233
+ roomLabels: false,
234
+ stairAnnotations: true,
235
+ }
236
+
237
+ expect(resolveFloorplanExportAnnotationVisibility('expert', liveVisibility)).toEqual(
238
+ liveVisibility,
239
+ )
240
+ })
241
+
242
+ test('exports only model geometry and room labels in Default', () => {
243
+ expect(
244
+ resolveFloorplanExportAnnotationVisibility(
245
+ 'default',
246
+ DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY,
247
+ ),
248
+ ).toEqual({
249
+ automaticDimensions: false,
250
+ contextualDimensions: false,
251
+ manualDimensions: false,
252
+ measurements: false,
253
+ openingMarks: false,
254
+ structuralGrids: false,
255
+ roomLabels: true,
256
+ stairAnnotations: false,
257
+ })
258
+ })
259
+
260
+ test('matches live screen sizing to the fitted export viewport', () => {
261
+ expect(resolveFloorplanScreenUnitsPerPixel(7, 4.5, 572, 463)).toBeCloseTo(0.012_237_762, 8)
262
+ })
263
+
264
+ test('keeps the export viewport anchored to the structural drawing bounds', () => {
265
+ expect(resolveFloorplanExportViewport({ x: -5, y: -6, width: 13, height: 13.5 })).toEqual({
266
+ x: -7.7,
267
+ y: -8.7,
268
+ width: 18.4,
269
+ height: 18.9,
270
+ })
271
+ })
272
+
273
+ test('fits the viewport around the rotated plan instead of clipping its corners', () => {
274
+ const bounds = rotateFloorplanExportBounds({ x: 0, y: 0, width: 10, height: 5 }, 90)
275
+
276
+ expect(bounds.x).toBeCloseTo(-5, 8)
277
+ expect(bounds.y).toBeCloseTo(0, 8)
278
+ expect(bounds.width).toBeCloseTo(5, 8)
279
+ expect(bounds.height).toBeCloseTo(10, 8)
280
+ })
281
+
282
+ test('keeps annotation-only nodes out of primary model bounds', () => {
283
+ expect(
284
+ isFloorplanExportAnnotationGeometry({
285
+ kind: 'group',
286
+ children: [],
287
+ metadata: { 'pascal:editor/floorplan': { annotationRole: 'measurement' } },
288
+ }),
289
+ ).toBe(true)
290
+ expect(
291
+ isFloorplanExportAnnotationGeometry({
292
+ kind: 'group',
293
+ children: [],
294
+ metadata: { 'pascal:editor/floorplan': { annotationRole: 'manual-dimension' } },
295
+ }),
296
+ ).toBe(true)
297
+ expect(isFloorplanExportAnnotationGeometry({ kind: 'polygon', points: [] })).toBe(false)
298
+ })
299
+
300
+ test('matches the current floor-plan rotation instead of forcing north-up', () => {
301
+ expect(resolveFloorplanExportRotationDeg(Math.PI / 6, Math.PI / 2)).toBeCloseTo(60, 8)
302
+ })
303
+ })
304
+
305
+ describe('resolveFloorplanMeasurementSize', () => {
306
+ test('sizes the hidden SVG in screen pixels before resolving label collisions', () => {
307
+ expect(
308
+ resolveFloorplanMeasurementSize({ x: -2, y: -3, width: 18.4, height: 18.9 }, 0.024),
309
+ ).toEqual({ width: 18.4 / 0.024, height: 18.9 / 0.024 })
310
+ })
311
+ })
312
+
313
+ describe('resolveFloorplanPageLayout', () => {
314
+ test('uses the available A4 page area for the fitted plan', () => {
315
+ expect(resolveFloorplanPageLayout(842, 595)).toEqual({
316
+ planBox: { x: 36, y: 64, width: 770, height: 495 },
317
+ })
318
+ })
319
+ })