@pascal-app/editor 1.0.7 → 1.1.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 (336) hide show
  1. package/package.json +9 -15
  2. package/src/components/editor/action-menu-button.tsx +1 -1
  3. package/src/components/editor/alignment-3d-guide-layer.tsx +1 -1
  4. package/src/components/editor/custom-camera-controls.tsx +101 -45
  5. package/src/components/editor/duplicate-node.ts +2 -1
  6. package/src/components/editor/editor-layout-v2.tsx +1 -1
  7. package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
  8. package/src/components/editor/first-person-controls.tsx +20 -1983
  9. package/src/components/editor/fit-scene-framing.test.ts +318 -0
  10. package/src/components/editor/fit-scene-framing.ts +142 -0
  11. package/src/components/editor/floating-action-menu.tsx +5 -3
  12. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  13. package/src/components/editor/floor-openings-overlay.tsx +1 -1
  14. package/src/components/editor/floor-region-controls.tsx +8 -4
  15. package/src/components/editor/floorplan-navigation-presentation.test.ts +0 -40
  16. package/src/components/editor/floorplan-navigation-presentation.ts +0 -66
  17. package/src/components/editor/floorplan-panel.tsx +199 -30
  18. package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
  19. package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
  20. package/src/components/editor/follow-pascal.test.tsx +1 -2
  21. package/src/components/editor/follow-pascal.tsx +1 -1
  22. package/src/components/editor/footprint-height-handle.tsx +2 -2
  23. package/src/components/editor/group-actions.ts +1 -2
  24. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  25. package/src/components/editor/group-move-3d.ts +1 -2
  26. package/src/components/editor/group-rotate-handle.tsx +1 -2
  27. package/src/components/editor/group-transform-shared.ts +3 -1
  28. package/src/components/editor/handles/use-handle-drag.ts +1 -2
  29. package/src/components/editor/index.tsx +43 -13
  30. package/src/components/editor/inspector-extension-actions.tsx +1 -1
  31. package/src/components/editor/measurement-pill.tsx +1 -1
  32. package/src/components/editor/node-arrow-handles.tsx +2 -2
  33. package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
  34. package/src/components/editor/open-wall-ends-hint.tsx +1 -1
  35. package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
  36. package/src/components/editor/quick-measurement-card.tsx +1 -1
  37. package/src/components/editor/room-controls.tsx +10 -6
  38. package/src/components/editor/room-handles-2d.tsx +3 -3
  39. package/src/components/editor/room-handles.tsx +4 -5
  40. package/src/components/editor/room-highlight.tsx +6 -105
  41. package/src/components/editor/room-labels.test.ts +8 -822
  42. package/src/components/editor/room-labels.tsx +41 -1670
  43. package/src/components/editor/room-webgpu-materials.test.ts +1 -1
  44. package/src/components/editor/selection-manager.tsx +5 -3
  45. package/src/components/editor/site-edge-labels.tsx +7 -3
  46. package/src/components/editor/snapshot-capture-overlay.tsx +2 -5
  47. package/src/components/editor/use-floorplan-background-placement.ts +2 -1
  48. package/src/components/editor/views/plugins-view.tsx +1 -1
  49. package/src/components/editor/views/view-bar.tsx +9 -9
  50. package/src/components/editor/wall-measurement-label.tsx +31 -15
  51. package/src/components/editor/wall-move-side-handles.tsx +11 -7
  52. package/src/components/editor/wall-pull-grip.tsx +2 -2
  53. package/src/components/editor/wall-region-handles.tsx +2 -2
  54. package/src/components/editor/wall-snap-beacon-layer.tsx +53 -32
  55. package/src/components/editor-2d/floorplan-group-move.tsx +3 -4
  56. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +13 -13
  57. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +2 -3
  58. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
  59. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +23 -6
  60. package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +0 -37
  61. package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +0 -30
  62. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +13 -3
  63. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
  64. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +14 -3
  65. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
  66. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
  67. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +30 -10
  68. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +39 -35
  69. package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
  70. package/src/components/systems/roof/roof-edit-system.tsx +33 -21
  71. package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
  72. package/src/components/tools/distribution/distribution-run-tool.tsx +151 -33
  73. package/src/components/tools/item/move-tool.tsx +1 -6
  74. package/src/components/tools/item/placement-history.test.tsx +1 -1
  75. package/src/components/tools/item/use-placement-coordinator.tsx +3 -4
  76. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +7 -3
  77. package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
  78. package/src/components/tools/registry/move-registry-node-tool.tsx +39 -16
  79. package/src/components/tools/shared/cursor-sphere.tsx +1 -1
  80. package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
  81. package/src/components/tools/shared/placement-box.tsx +2 -6
  82. package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
  83. package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
  84. package/src/components/tools/shared/segment-angle.ts +0 -4
  85. package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
  86. package/src/components/tools/tool-manager.tsx +1 -24
  87. package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
  88. package/src/components/tools/wall/wall-typed-entry.ts +70 -0
  89. package/src/components/ui/action-menu/camera-actions.tsx +2 -2
  90. package/src/components/ui/action-menu/control-modes.tsx +1 -1
  91. package/src/components/ui/action-menu/follow-pascal-pill.tsx +1 -1
  92. package/src/components/ui/action-menu/index.tsx +3 -3
  93. package/src/components/ui/action-menu/measurement-control.tsx +3 -3
  94. package/src/components/ui/action-menu/placement.ts +0 -18
  95. package/src/components/ui/action-menu/point-ask-button.tsx +1 -1
  96. package/src/components/ui/action-menu/view-toggles.tsx +5 -5
  97. package/src/components/ui/controls/finish-swatch.tsx +1 -1
  98. package/src/components/ui/controls/material-paint-panel.tsx +2 -2
  99. package/src/components/ui/controls/scene-material-list.tsx +2 -2
  100. package/src/components/ui/controls/select-control.tsx +1 -1
  101. package/src/components/ui/controls/slider-control.tsx +18 -6
  102. package/src/components/ui/controls/terrain-sculpt-panel.tsx +1 -1
  103. package/src/components/ui/controls/tool-options-panel.tsx +74 -44
  104. package/src/components/ui/floating-level-selector.tsx +3 -3
  105. package/src/components/ui/helpers/contextual-helper-panel.tsx +1 -1
  106. package/src/components/ui/helpers/helper-manager.test.tsx +54 -1
  107. package/src/components/ui/helpers/helper-manager.tsx +55 -1
  108. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  109. package/src/components/ui/panels/collections/collections-popover.tsx +2 -2
  110. package/src/components/ui/panels/floor-foundation-panel.tsx +3 -3
  111. package/src/components/ui/panels/floor-opening-panel.tsx +5 -5
  112. package/src/components/ui/panels/mobile-selection-bar.tsx +28 -24
  113. package/src/components/ui/panels/multi-height-mode.tsx +3 -3
  114. package/src/components/ui/panels/multi-selection-panel.tsx +1 -1
  115. package/src/components/ui/panels/node-display.ts +1 -1
  116. package/src/components/ui/panels/panel-manager.tsx +5 -3
  117. package/src/components/ui/panels/panel-wrapper.tsx +42 -19
  118. package/src/components/ui/panels/parametric-inspector.tsx +1 -1
  119. package/src/components/ui/panels/room-construction-rows.tsx +7 -7
  120. package/src/components/ui/panels/room-name-options.ts +1 -5
  121. package/src/components/ui/panels/room-openings-field.tsx +1 -1
  122. package/src/components/ui/panels/room-panel.tsx +7 -9
  123. package/src/components/ui/primitives/color-dot.tsx +2 -2
  124. package/src/components/ui/primitives/opacity-control.tsx +2 -2
  125. package/src/components/ui/primitives/sidebar.tsx +3 -3
  126. package/src/components/ui/right-stack.test.ts +13 -6
  127. package/src/components/ui/right-stack.tsx +22 -11
  128. package/src/components/ui/sidebar/icon-rail.tsx +1 -1
  129. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  130. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
  131. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +1 -1
  132. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +2 -2
  133. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +1 -1
  134. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +2 -6
  135. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
  136. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +1 -1
  137. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +1 -1
  138. package/src/components/ui/sidebar/panels/site-panel/index.tsx +3 -10
  139. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +1 -1
  140. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
  141. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +46 -7
  142. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +1 -1
  143. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +1 -1
  144. package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +14 -5
  145. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -2
  146. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +1 -1
  147. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +3 -3
  148. package/src/components/ui/sidebar/tab-bar.tsx +1 -1
  149. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +2 -2
  150. package/src/components/ui/wall-type/wall-type-menu.tsx +1 -1
  151. package/src/components/viewer-overlay.tsx +1 -2
  152. package/src/hooks/use-auto-frame.ts +1 -2
  153. package/src/hooks/use-keyboard.ts +1 -2
  154. package/src/hooks/use-selected-room.ts +5 -7
  155. package/src/i18n/floor-footprint-name.ts +1 -1
  156. package/src/i18n/locales/en/messages.po +1036 -306
  157. package/src/i18n/locales/en/messages.ts +1 -1
  158. package/src/i18n/locales/en-XA/messages.ts +1 -1
  159. package/src/i18n/locales/es/messages.po +2267 -439
  160. package/src/i18n/locales/fr/messages.po +2267 -439
  161. package/src/i18n/locales/ja/messages.po +2267 -439
  162. package/src/i18n/locales/zh-CN/messages.po +2267 -439
  163. package/src/i18n/locales.test.ts +1 -1
  164. package/src/index.tsx +183 -178
  165. package/src/lib/below-grade.test.tsx +4 -4
  166. package/src/lib/camera-facing.test.ts +1 -1
  167. package/src/lib/camera-facing.ts +2 -3
  168. package/src/lib/contextual-help.ts +6 -0
  169. package/src/lib/editor-level-display.ts +1 -1
  170. package/src/lib/elevation-guides.ts +21 -6
  171. package/src/lib/elevator-support.ts +1 -72
  172. package/src/lib/floor-footprints.test.ts +3 -3
  173. package/src/lib/floor-footprints.ts +1 -9
  174. package/src/lib/floor-foundation-support.test.ts +8 -3
  175. package/src/lib/floor-opening-draft.ts +2 -2
  176. package/src/lib/floor-region-session.ts +1 -1
  177. package/src/lib/floorplan/annotation-visibility.test.ts +1 -1
  178. package/src/lib/floorplan/annotation-visibility.ts +1 -1
  179. package/src/lib/floorplan/floorplan-export.test.ts +1 -1
  180. package/src/lib/floorplan/floorplan-export.tsx +8 -11
  181. package/src/lib/floorplan/floorplan-extension.ts +4 -157
  182. package/src/lib/floorplan/floorplan-mode.ts +2 -1
  183. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +1 -1
  184. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +8 -6
  185. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +3 -4
  186. package/src/lib/floorplan/geometry.ts +2 -112
  187. package/src/lib/floorplan/index.ts +0 -9
  188. package/src/lib/floorplan/items.ts +2 -1
  189. package/src/lib/floorplan/open-wall-ends.ts +2 -1
  190. package/src/lib/floorplan/schedules.ts +6 -3
  191. package/src/lib/floorplan/sheet-export.ts +7 -3
  192. package/src/lib/floorplan/site-plan/site-parts.ts +7 -1
  193. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +1 -1
  194. package/src/lib/follow-pascal-presentation.ts +1 -1
  195. package/src/lib/gesture-lifecycle.ts +3 -1
  196. package/src/lib/glb-export.ts +1 -1
  197. package/src/lib/interaction/scope.ts +1 -0
  198. package/src/lib/layer-thickness.ts +1 -1
  199. package/src/lib/level-selection.test.ts +18 -2
  200. package/src/lib/level-selection.ts +31 -8
  201. package/src/lib/linear-display.ts +1 -1
  202. package/src/lib/live-gesture-round-trip.test.ts +1 -1
  203. package/src/lib/material-paint.ts +1 -1
  204. package/src/lib/measurement-label.test.ts +1 -1
  205. package/src/lib/measurements.test.ts +7 -5
  206. package/src/lib/mezzanine-selection.test.ts +1 -1
  207. package/src/lib/node-action-movement.test.tsx +115 -0
  208. package/src/lib/node-action-movement.ts +2 -0
  209. package/src/lib/paint-region-mode.ts +1 -1
  210. package/src/lib/placement-metadata.ts +4 -0
  211. package/src/lib/point-ask/describe-target.ts +6 -8
  212. package/src/lib/point-ask/region.ts +1 -1
  213. package/src/lib/point-ask/session.ts +1 -2
  214. package/src/lib/print-shell-compiler.ts +12 -1
  215. package/src/lib/print-wall-solids.test.ts +11 -0
  216. package/src/lib/print-wall-solids.ts +9 -0
  217. package/src/lib/roof-duplication.test.ts +100 -0
  218. package/src/lib/roof-duplication.ts +30 -5
  219. package/src/lib/room-built-on.test.ts +2 -5
  220. package/src/lib/room-built-on.ts +1 -15
  221. package/src/lib/room-construction-commands.ts +3 -2
  222. package/src/lib/room-controls-phase6.test.ts +1 -1
  223. package/src/lib/room-handle-drag.ts +8 -37
  224. package/src/lib/room-name-catalog.test.ts +2 -1
  225. package/src/lib/room-name-messages.test.ts +2 -1
  226. package/src/lib/room-name-messages.ts +1 -118
  227. package/src/lib/room-name-storage.test.ts +2 -1
  228. package/src/lib/room-push-dimensions.test.ts +1 -1
  229. package/src/lib/room-selection-commands.ts +2 -2
  230. package/src/lib/room-selection.test.ts +6 -6
  231. package/src/lib/room-transform-session.ts +1 -2
  232. package/src/lib/room-zone-routing.ts +2 -2
  233. package/src/lib/scene-clipboard.ts +2 -1
  234. package/src/lib/selection-routing.test.ts +0 -28
  235. package/src/lib/selection-routing.ts +1 -40
  236. package/src/lib/sfx-bus.ts +8 -32
  237. package/src/lib/sfx-player.test.ts +72 -1
  238. package/src/lib/sfx-player.ts +49 -337
  239. package/src/lib/snapping-mode.test.ts +23 -1
  240. package/src/lib/viewer-selection.test.ts +38 -10
  241. package/src/lib/wall-pull.test.ts +1 -1
  242. package/src/lib/wall-pull.ts +1 -27
  243. package/src/lib/wall-region-list.ts +3 -1
  244. package/src/lib/window-interaction.test.ts +4 -2
  245. package/src/store/use-editor.tsx +22 -26
  246. package/src/store/use-floorplan-annotation-visibility.ts +5 -5
  247. package/src/store/use-placement-typing.test.ts +165 -0
  248. package/src/store/use-placement-typing.ts +186 -0
  249. package/src/store/use-wall-region-handles-selection.ts +1 -2
  250. package/src/components/editor/__bench__/room-label-placement.bench.ts +0 -108
  251. package/src/components/editor/first-person/build-collider-world.test.ts +0 -596
  252. package/src/components/editor/first-person/build-collider-world.ts +0 -549
  253. package/src/components/editor/first-person/ground-holes.test.ts +0 -128
  254. package/src/components/editor/first-person/ground-holes.ts +0 -130
  255. package/src/components/editor/first-person/mechanism-targets.test.ts +0 -162
  256. package/src/components/editor/first-person/mechanism-targets.ts +0 -88
  257. package/src/components/editor/first-person/terrain-collider.test.ts +0 -132
  258. package/src/components/editor/first-person/terrain-collider.ts +0 -212
  259. package/src/components/editor/floorplan-camera-sync.test.ts +0 -249
  260. package/src/components/editor/floorplan-camera-sync.ts +0 -213
  261. package/src/components/editor/localized-html.tsx +0 -22
  262. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +0 -236
  263. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +0 -708
  264. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +0 -385
  265. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +0 -670
  266. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +0 -89
  267. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +0 -106
  268. package/src/components/editor-2d/svg-paths.ts +0 -124
  269. package/src/components/tools/elevator/elevator-defaults.ts +0 -8
  270. package/src/components/tools/elevator/elevator-tool.tsx +0 -372
  271. package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
  272. package/src/components/ui/action-menu/action-button.tsx +0 -89
  273. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +0 -42
  274. package/src/components/ui/primitives/button.tsx +0 -69
  275. package/src/components/ui/primitives/dropdown-menu.tsx +0 -232
  276. package/src/components/ui/primitives/popover.tsx +0 -42
  277. package/src/components/ui/primitives/switch.tsx +0 -29
  278. package/src/components/ui/primitives/tooltip.tsx +0 -57
  279. package/src/components/ui/scene-loader.tsx +0 -73
  280. package/src/components/viewer/floorplan-compass-button.tsx +0 -67
  281. package/src/components/viewer/floorplan-preview-geometry.test.ts +0 -107
  282. package/src/components/viewer/floorplan-preview-geometry.ts +0 -281
  283. package/src/components/viewer/floorplan-preview-navigation.test.ts +0 -43
  284. package/src/components/viewer/floorplan-preview-navigation.ts +0 -62
  285. package/src/components/viewer/floorplan-preview-visibility.test.ts +0 -35
  286. package/src/components/viewer/floorplan-preview-visibility.ts +0 -19
  287. package/src/components/viewer/floorplan-preview.tsx +0 -1065
  288. package/src/components/viewer/render-settings-menu.tsx +0 -294
  289. package/src/components/viewer/sky-control.tsx +0 -710
  290. package/src/components/viewer/use-viewer-camera-navigation-sync.ts +0 -140
  291. package/src/components/viewer/use-viewer-ui-sounds.test.ts +0 -15
  292. package/src/components/viewer/use-viewer-ui-sounds.ts +0 -46
  293. package/src/components/viewer/viewer-controls-bar.tsx +0 -496
  294. package/src/components/viewer/viewer-hover-label.tsx +0 -42
  295. package/src/components/viewer/viewer-rooms.tsx +0 -248
  296. package/src/components/viewer/viewer-scene-header.tsx +0 -286
  297. package/src/components/viewer/viewer-selection-manager.tsx +0 -124
  298. package/src/components/viewer/viewer-stage-modes.test.ts +0 -38
  299. package/src/components/viewer/viewer-stage-modes.ts +0 -49
  300. package/src/components/viewer/viewer-stage-switcher.tsx +0 -97
  301. package/src/components/viewer/viewer-stage.test.tsx +0 -47
  302. package/src/components/viewer/viewer-stage.tsx +0 -221
  303. package/src/components/walkthrough-hud.tsx +0 -193
  304. package/src/hooks/use-mobile.ts +0 -19
  305. package/src/i18n/level-name.ts +0 -39
  306. package/src/i18n/scene-theme-name.ts +0 -16
  307. package/src/lib/below-grade.ts +0 -133
  308. package/src/lib/camera-pose.test.ts +0 -237
  309. package/src/lib/camera-pose.ts +0 -172
  310. package/src/lib/door-interaction.ts +0 -102
  311. package/src/lib/floorplan/floorplan-extension.test.ts +0 -42
  312. package/src/lib/floorplan/floorplan-readonly.test.ts +0 -47
  313. package/src/lib/floorplan/floorplan-readonly.ts +0 -95
  314. package/src/lib/floorplan/marks.test.ts +0 -213
  315. package/src/lib/floorplan/marks.ts +0 -238
  316. package/src/lib/measurement-label.ts +0 -68
  317. package/src/lib/measurements.ts +0 -225
  318. package/src/lib/node-display-label.ts +0 -37
  319. package/src/lib/room-assembly-overlay.test.ts +0 -275
  320. package/src/lib/room-assembly-overlay.ts +0 -265
  321. package/src/lib/room-selection.ts +0 -417
  322. package/src/lib/scene-bounds.test.ts +0 -183
  323. package/src/lib/scene-bounds.ts +0 -169
  324. package/src/lib/typing-target.ts +0 -9
  325. package/src/lib/viewer-selection.ts +0 -595
  326. package/src/lib/window-interaction.ts +0 -142
  327. package/src/lib/zone-content.ts +0 -133
  328. package/src/store/camera-pose-store.test.ts +0 -29
  329. package/src/store/camera-pose-store.ts +0 -27
  330. package/src/store/navigation-sync-pose-store.test.ts +0 -34
  331. package/src/store/navigation-sync-pose-store.ts +0 -27
  332. package/src/store/use-audio.test.ts +0 -146
  333. package/src/store/use-audio.tsx +0 -169
  334. package/src/store/use-first-person-hud.ts +0 -40
  335. package/src/store/use-follow-pascal.test.ts +0 -62
  336. package/src/store/use-follow-pascal.ts +0 -53
@@ -6,7 +6,7 @@ import { readFileSync } from 'node:fs'
6
6
  // draft instead, so it only has to stay clear of drei fat lines.
7
7
  for (const [file, drawsLines] of [
8
8
  ['./room-controls.tsx', false],
9
- ['./room-highlight.tsx', true],
9
+ ['../../../../viewer/src/ui/components/rooms/room-highlight.tsx', true],
10
10
  ['../../../../nodes/src/separator/renderer.tsx', true],
11
11
  ] as const) {
12
12
  test(`${file} uses WebGPU node materials instead of drei fat lines`, () => {
@@ -35,6 +35,11 @@ import {
35
35
  tintEmissive,
36
36
  useViewer,
37
37
  } from '@pascal-app/viewer'
38
+ import {
39
+ type SelectionModifierKeys,
40
+ sameRoom,
41
+ selectionModifiersFromEvent,
42
+ } from '@pascal-app/viewer/ui'
38
43
  import { useThree } from '@react-three/fiber'
39
44
  import { useCallback, useEffect, useRef } from 'react'
40
45
  import { type BufferGeometry, Color, type Material, type Mesh, type Object3D, Vector3 } from 'three'
@@ -105,7 +110,6 @@ import {
105
110
  usePaintOutline,
106
111
  } from '../../lib/plate-paint-affected'
107
112
  import { getHoveredRoofSegmentOutlineProxy } from '../../lib/roof-hover-outline-proxy'
108
- import { sameRoom } from '../../lib/room-selection'
109
113
  import { selectRoom, shouldInterceptRoom } from '../../lib/room-selection-commands'
110
114
  import { roomKeyForZone } from '../../lib/room-zone-routing'
111
115
  import {
@@ -114,8 +118,6 @@ import {
114
118
  resolveCanvasBuildingId,
115
119
  resolveCanvasSelectionNode,
116
120
  resolveSelectedIdsForNodeClick,
117
- type SelectionModifierKeys,
118
- selectionModifiersFromEvent,
119
121
  shouldPreserveSelectedRoofHostTarget,
120
122
  } from '../../lib/selection-routing'
121
123
  import { emitDeleteSFX, sfxEmitter } from '../../lib/sfx-bus'
@@ -1,16 +1,20 @@
1
1
  'use client'
2
2
 
3
3
  import type { SiteNode } from '@pascal-app/core'
4
- import { sceneRegistry, useLiveNodeOverrides, useScene } from '@pascal-app/core'
4
+ import {
5
+ formatLinearMeasurement,
6
+ sceneRegistry,
7
+ useLiveNodeOverrides,
8
+ useScene,
9
+ } from '@pascal-app/core'
5
10
  import { getSceneTheme, useViewer } from '@pascal-app/viewer'
11
+ import { LocalizedHtml as Html } from '@pascal-app/viewer/ui'
6
12
  import { createPortal, useFrame, useThree } from '@react-three/fiber'
7
13
  import { useCallback, useMemo, useRef, useState } from 'react'
8
14
  import { type Camera, type Object3D, Vector3 } from 'three'
9
15
  import { groundHeightAt } from '../../lib/ground-surface'
10
- import { formatLinearMeasurement } from '../../lib/measurements'
11
16
  import { SITE_BOUNDARY_DRAG_LABEL } from '../../lib/site-boundary'
12
17
  import { useActiveHandleDrag } from '../../store/use-interaction-scope'
13
- import { Html } from './localized-html'
14
18
 
15
19
  type ViewportSize = {
16
20
  width: number
@@ -1,7 +1,8 @@
1
1
  'use client'
2
2
 
3
3
  import { emitter, type SnapshotSavedEvent } from '@pascal-app/core'
4
- import { SNAPSHOT_MAX_EDGE } from '@pascal-app/viewer'
4
+ import { isTypingTarget, SNAPSHOT_MAX_EDGE } from '@pascal-app/viewer'
5
+ import { useFirstPersonHud, useIsMobile, WalkthroughCrosshair } from '@pascal-app/viewer/ui'
5
6
  import {
6
7
  Check,
7
8
  Crop,
@@ -16,9 +17,7 @@ import {
16
17
  } from 'lucide-react'
17
18
  import { useCallback, useEffect, useRef, useState } from 'react'
18
19
  import { flushSync } from 'react-dom'
19
- import { useIsMobile } from '../../hooks/use-mobile'
20
20
  import { triggerSFX } from '../../lib/sfx-bus'
21
- import { isTypingTarget } from '../../lib/typing-target'
22
21
  import { requestWalkthroughPointerLock } from '../../lib/walkthrough-pointer-lock'
23
22
  import useEditor, {
24
23
  CAPTURE_FOV_MAX,
@@ -27,9 +26,7 @@ import useEditor, {
27
26
  type SnapshotCropMode,
28
27
  type SnapshotStandardAspect,
29
28
  } from '../../store/use-editor'
30
- import { useFirstPersonHud } from '../../store/use-first-person-hud'
31
29
  import { Slider } from '../ui/slider'
32
- import { WalkthroughCrosshair } from '../walkthrough-hud'
33
30
  import { CaptureLevelPicker } from './capture-level-picker'
34
31
  import { isOverlaySnapshotSave } from './snapshot-capture'
35
32
 
@@ -3,6 +3,7 @@
3
3
  import {
4
4
  emitter,
5
5
  type FenceNode,
6
+ getPlanPointDistance,
6
7
  isCurvedWall,
7
8
  nodeRegistry,
8
9
  type WallNode,
@@ -14,7 +15,7 @@ import {
14
15
  useSyncExternalStore,
15
16
  } from 'react'
16
17
  import { resolveCeilingPlanPointSnap } from '../../lib/ceiling-plan-snap'
17
- import { alignFloorplanDraftPoint, getPlanPointDistance } from '../../lib/floorplan'
18
+ import { alignFloorplanDraftPoint } from '../../lib/floorplan'
18
19
  import { resolveGenericFloorplanGridEventPoint } from '../../lib/floorplan-grid-event-point'
19
20
  import { snapRegisteredDraftPoint } from '../../lib/registered-draft-snap'
20
21
  import { resolveSlabPlanPointSnap } from '../../lib/slab-plan-snap'
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import { useScene } from '@pascal-app/core'
4
+ import { Button } from '@pascal-app/viewer/ui'
4
5
  import {
5
6
  Check,
6
7
  ChevronRight,
@@ -31,7 +32,6 @@ import { cn } from '../../../lib/utils'
31
32
  import useEditor from '../../../store/use-editor'
32
33
  import { IconRefGlyph } from '../../ui/icon-ref'
33
34
  import { Badge } from '../../ui/primitives/badge'
34
- import { Button } from '../../ui/primitives/button'
35
35
  import { PLUGINS_VIEW_ID } from './use-editor-views'
36
36
 
37
37
  const PLUGIN_AUTHORING_URL = 'https://editor.pascal.app/docs/developers/plugins'
@@ -2,14 +2,6 @@
2
2
 
3
3
  import { type MessageDescriptor, type PascalI18n, usePascalI18n } from '@pascal-app/core'
4
4
  import { type GroundMode, useViewer } from '@pascal-app/viewer'
5
- import { ArrowLeftRight, Check, Columns2, MoreHorizontal, Pin, PinOff, X } from 'lucide-react'
6
- import { type ReactNode, useEffect, useRef } from 'react'
7
- import { useIsMobile } from '../../../hooks/use-mobile'
8
- import { type EditorViewDescriptor, isSceneView, isViewPinned } from '../../../lib/editor-views'
9
- import { cn } from '../../../lib/utils'
10
- import useEditor from '../../../store/use-editor'
11
- import { VIEW_2D, VIEW_3D, type ViewPaneIndex } from '../../../store/view-layout'
12
- import { IconRefGlyph } from '../../ui/icon-ref'
13
5
  import {
14
6
  DropdownMenu,
15
7
  DropdownMenuContent,
@@ -17,7 +9,15 @@ import {
17
9
  DropdownMenuLabel,
18
10
  DropdownMenuSeparator,
19
11
  DropdownMenuTrigger,
20
- } from '../../ui/primitives/dropdown-menu'
12
+ useIsMobile,
13
+ } from '@pascal-app/viewer/ui'
14
+ import { ArrowLeftRight, Check, Columns2, MoreHorizontal, Pin, PinOff, X } from 'lucide-react'
15
+ import { type ReactNode, useEffect, useRef } from 'react'
16
+ import { type EditorViewDescriptor, isSceneView, isViewPinned } from '../../../lib/editor-views'
17
+ import { cn } from '../../../lib/utils'
18
+ import useEditor from '../../../store/use-editor'
19
+ import { VIEW_2D, VIEW_3D, type ViewPaneIndex } from '../../../store/view-layout'
20
+ import { IconRefGlyph } from '../../ui/icon-ref'
21
21
  import { PLUGINS_VIEW_ID, useActiveViewLayout, useEditorViews } from './use-editor-views'
22
22
 
23
23
  const GROUP = cn(
@@ -4,6 +4,7 @@ import {
4
4
  type AnyNode,
5
5
  type AnyNodeId,
6
6
  calculateLevelMiters,
7
+ formatLinearMeasurement,
7
8
  getWallCurveLength,
8
9
  getWallEffectiveHeightForNodes,
9
10
  getWallMiterBoundaryPoints,
@@ -20,11 +21,10 @@ import {
20
21
  type WallNode,
21
22
  } from '@pascal-app/core'
22
23
  import { getSceneTheme, useViewer } from '@pascal-app/viewer'
24
+ import { LocalizedHtml as Html } from '@pascal-app/viewer/ui'
23
25
  import { createPortal, useFrame } from '@react-three/fiber'
24
26
  import { useMemo, useState } from 'react'
25
27
  import * as THREE from 'three'
26
- import { formatLinearMeasurement } from '../../lib/measurements'
27
- import { Html } from './localized-html'
28
28
 
29
29
  const GUIDE_Y_OFFSET = 0.08
30
30
  const LABEL_LIFT = 0.08
@@ -315,16 +315,25 @@ function buildMeasurementGuide(
315
315
  const measurementPoints = measurementLine ?? fallbackMiddlePoints
316
316
  if (!measurementPoints) return null
317
317
 
318
- const height = getWallEffectiveHeightForNodes(wall, nodes)
318
+ const heightStart = getWallEffectiveHeightForNodes(wall, nodes, 0)
319
+ const heightEnd = getWallEffectiveHeightForNodes(wall, nodes, 1)
319
320
  const startLocal = worldPointToWallLocal(wall, measurementPoints.start)
320
321
  const endLocal = worldPointToWallLocal(wall, measurementPoints.end)
321
322
  const curvedMeasurementPath = isCurvedWall(wall)
322
323
  ? getCurvedWallMeasurementPath(wall, miterData, levelWalls)
323
324
  : null
325
+ const dx = wall.end[0] - wall.start[0]
326
+ const dz = wall.end[1] - wall.start[1]
327
+ const wallChordLength = Math.hypot(dx, dz)
328
+ const getChordT = (localX: number) =>
329
+ wallChordLength > 1e-6 ? Math.max(0, Math.min(1, localX / wallChordLength)) : 0
330
+
324
331
  const guidePath: Vec3[] = curvedMeasurementPath
325
332
  ? curvedMeasurementPath.map((point) => {
326
333
  const localPoint = worldPointToWallLocal(wall, point)
327
- return [localPoint[0], height + GUIDE_Y_OFFSET, localPoint[2]]
334
+ const t = getChordT(localPoint[0])
335
+ const h = getWallEffectiveHeightForNodes(wall, nodes, t)
336
+ return [localPoint[0], h + GUIDE_Y_OFFSET, localPoint[2]]
328
337
  })
329
338
  : isCurvedWall(wall)
330
339
  ? sampleWallCenterline(wall, 24).map((point, index, points) => {
@@ -334,12 +343,14 @@ function buildMeasurementGuide(
334
343
  : index === points.length - 1
335
344
  ? endLocal
336
345
  : worldPointToWallLocal(wall, point)
346
+ const t = getChordT(localPoint[0])
347
+ const h = getWallEffectiveHeightForNodes(wall, nodes, t)
337
348
 
338
- return [localPoint[0], height + GUIDE_Y_OFFSET, localPoint[2]]
349
+ return [localPoint[0], h + GUIDE_Y_OFFSET, localPoint[2]]
339
350
  })
340
351
  : [
341
- [startLocal[0], height + GUIDE_Y_OFFSET, startLocal[2]],
342
- [endLocal[0], height + GUIDE_Y_OFFSET, endLocal[2]],
352
+ [startLocal[0], heightStart + GUIDE_Y_OFFSET, startLocal[2]],
353
+ [endLocal[0], heightEnd + GUIDE_Y_OFFSET, endLocal[2]],
343
354
  ]
344
355
 
345
356
  if (guidePath.length < 2) return null
@@ -397,26 +408,30 @@ function buildMeasurementGuide(
397
408
  ],
398
409
  })
399
410
  const bottomHeightTick = getHorizontalHeightTick(0)
400
- const topHeightTick = getHorizontalHeightTick(height)
411
+ const topHeightTick = getHorizontalHeightTick(heightEnd)
401
412
 
402
413
  return {
403
414
  guidePath,
404
- extStartStart: [extensionStartBase[0], height, extensionStartBase[2]],
415
+ extStartStart: [extensionStartBase[0], heightStart, extensionStartBase[2]],
405
416
  extStartEnd: [
406
417
  extensionStartBase[0],
407
- height + GUIDE_Y_OFFSET + extOvershoot,
418
+ heightStart + GUIDE_Y_OFFSET + extOvershoot,
408
419
  extensionStartBase[2],
409
420
  ],
410
- extEndStart: [extensionEndBase[0], height, extensionEndBase[2]],
411
- extEndEnd: [extensionEndBase[0], height + GUIDE_Y_OFFSET + extOvershoot, extensionEndBase[2]],
421
+ extEndStart: [extensionEndBase[0], heightEnd, extensionEndBase[2]],
422
+ extEndEnd: [
423
+ extensionEndBase[0],
424
+ heightEnd + GUIDE_Y_OFFSET + extOvershoot,
425
+ extensionEndBase[2],
426
+ ],
412
427
  labelPosition: [midpoint[0], midpoint[1] + LABEL_LIFT, midpoint[2]],
413
428
  heightStart: [heightGuidePosition[0], 0, heightGuidePosition[2]],
414
- heightEnd: [heightGuidePosition[0], height, heightGuidePosition[2]],
429
+ heightEnd: [heightGuidePosition[0], heightEnd, heightGuidePosition[2]],
415
430
  heightBottomTickStart: bottomHeightTick.start,
416
431
  heightBottomTickEnd: bottomHeightTick.end,
417
432
  heightTopTickStart: topHeightTick.start,
418
433
  heightTopTickEnd: topHeightTick.end,
419
- heightLabelPosition: [heightGuidePosition[0], height / 2, heightGuidePosition[2]],
434
+ heightLabelPosition: [heightGuidePosition[0], heightEnd / 2, heightGuidePosition[2]],
420
435
  }
421
436
  }
422
437
 
@@ -530,7 +545,8 @@ function WallMeasurementAnnotation({ wall }: { wall: WallNode }) {
530
545
  return total
531
546
  }, [guide, wall])
532
547
  const label = formatLinearMeasurement(length, unit, metricNotation)
533
- const height = useMemo(() => getWallEffectiveHeightForNodes(wall, nodes), [nodes, wall])
548
+ // Height annotation uses t=1 (wall end) because the vertical guide is drawn at the endpoint
549
+ const height = useMemo(() => getWallEffectiveHeightForNodes(wall, nodes, 1), [nodes, wall])
534
550
  const heightLabel = `H ${formatLinearMeasurement(height, unit, metricNotation)}`
535
551
 
536
552
  if (!(guide && Number.isFinite(length) && length >= 0.01)) return null
@@ -19,6 +19,7 @@ import {
19
19
  type WallNode,
20
20
  } from '@pascal-app/core'
21
21
  import { useViewer } from '@pascal-app/viewer'
22
+ import { LocalizedHtml as Html } from '@pascal-app/viewer/ui'
22
23
  import { createPortal, type ThreeEvent, useFrame, useThree } from '@react-three/fiber'
23
24
  import { useEffect, useMemo, useRef, useState } from 'react'
24
25
  import {
@@ -60,7 +61,6 @@ import useInteractionScope, {
60
61
  import { suppressBoxSelectForPointer } from '../tools/select/box-select-state'
61
62
  import { resolveResizeSnapValue } from './handles/resize-snap'
62
63
  import { type HandleDragControls, useHandleDrag } from './handles/use-handle-drag'
63
- import { Html } from './localized-html'
64
64
  import { MeasurementPill } from './measurement-pill'
65
65
  import {
66
66
  createArrowHitAreaGeometry,
@@ -301,7 +301,7 @@ function StructureThicknessHandle({
301
301
  const structureHeight =
302
302
  node.type === 'fence'
303
303
  ? (node.height ?? 1.8)
304
- : getWallEffectiveHeightForNodes(node, useScene.getState().nodes)
304
+ : getWallEffectiveHeightForNodes(node, useScene.getState().nodes, 0.5)
305
305
  const outward = new Vector2(frame.normal.x * side, frame.normal.y * side)
306
306
  const faceOffset = getWallLocalFaceZ({ ...node, thickness }, side > 0 ? 'a' : 'b') * side + 0.006
307
307
  const position: [number, number, number] = [
@@ -375,7 +375,7 @@ function StructureThicknessHandle({
375
375
  const initialStructureHeight =
376
376
  initialNode.type === 'fence'
377
377
  ? (initialNode.height ?? 1.8)
378
- : getWallEffectiveHeightForNodes(initialNode, useScene.getState().nodes)
378
+ : getWallEffectiveHeightForNodes(initialNode, useScene.getState().nodes, 0.5)
379
379
  const initialStructureThickness =
380
380
  initialNode.type === 'fence'
381
381
  ? (initialNode.thickness ?? 0.08)
@@ -578,7 +578,11 @@ function WallCornerLeaderHandle({ wall, endpoint }: { wall: WallNode; endpoint:
578
578
  const corner = endpoint === 'start' ? wall.start : wall.end
579
579
  const x = corner[0]
580
580
  const z = corner[1]
581
- const wallHeight = getWallEffectiveHeightForNodes(wall, useScene.getState().nodes)
581
+ const wallHeight = getWallEffectiveHeightForNodes(
582
+ wall,
583
+ useScene.getState().nodes,
584
+ endpoint === 'start' ? 0 : 1,
585
+ )
582
586
 
583
587
  const dashedGeometry = useMemo(() => buildDashedVerticalGeometry(wallHeight), [wallHeight])
584
588
  const hitGeometry = useMemo(() => createEndpointHitAreaGeometry(CORNER_HEX_RADIUS), [])
@@ -784,7 +788,7 @@ function WallBaseElevationHandle({
784
788
  levelObject.updateWorldMatrix(true, false)
785
789
  const initialBase = getWallBaseElevationForNodes(initialNode, sceneApi.nodes())
786
790
  const supportBase = initialBase - (initialNode.supportOffset ?? 0)
787
- const initialHeight = getWallEffectiveHeightForNodes(initialNode, sceneApi.nodes())
791
+ const initialHeight = getWallEffectiveHeightForNodes(initialNode, sceneApi.nodes(), 0)
788
792
  const midpointWorld = new Vector3(midpoint[0], initialBase, midpoint[1]).applyMatrix4(
789
793
  levelObject.matrixWorld,
790
794
  )
@@ -920,7 +924,7 @@ function WallHeightArrowHandle({ wall }: { wall: WallNode }) {
920
924
  const wallAngle = Math.atan2(-dirZ, dirX)
921
925
  // `wall` is the override-merged effective wall (see
922
926
  // WallMoveSideHandlesForWall), so this height is already live during a drag.
923
- const wallHeight = getWallEffectiveHeightForNodes(wall, useScene.getState().nodes)
927
+ const wallHeight = getWallEffectiveHeightForNodes(wall, useScene.getState().nodes, 0.5)
924
928
  const handleY = wallHeight + HEIGHT_HANDLE_OFFSET
925
929
 
926
930
  const activateHeightResize = (event: ThreeEvent<PointerEvent>) => {
@@ -973,7 +977,7 @@ function WallHeightArrowHandle({ wall }: { wall: WallNode }) {
973
977
 
974
978
  // Dragging the top makes the wall custom-height; seed from the resolved
975
979
  // effective height so a plane-bound wall's drag starts at its real top.
976
- const initialHeight = getWallEffectiveHeightForNodes(wall, useScene.getState().nodes)
980
+ const initialHeight = getWallEffectiveHeightForNodes(wall, useScene.getState().nodes, 0)
977
981
  const wallId = wall.id as AnyNodeId
978
982
  let pendingHeight = initialHeight
979
983
  let releaseSpatialCapture: (() => void) | null = null
@@ -1,13 +1,13 @@
1
1
  'use client'
2
2
 
3
- import { nodeRegistry, type WallNode } from '@pascal-app/core'
3
+ import { nodeRegistry, WALL_PULL_GRIP_TITLE, type WallNode, wallPullSeed } from '@pascal-app/core'
4
4
  import { type ThreeEvent, useThree } from '@react-three/fiber'
5
5
  import { useEffect, useMemo, useState } from 'react'
6
6
  import { Color, DoubleSide, ExtrudeGeometry, OrthographicCamera, Shape } from 'three'
7
7
  import { MeshBasicNodeMaterial } from 'three/webgpu'
8
8
  import { EDITOR_LAYER } from '../../lib/constants'
9
9
  import { sfxEmitter } from '../../lib/sfx-bus'
10
- import { startWallPull, WALL_PULL_GRIP_TITLE, wallPullSeed } from '../../lib/wall-pull'
10
+ import { startWallPull } from '../../lib/wall-pull'
11
11
  import { suppressBoxSelectForPointer } from '../tools/select/box-select-state'
12
12
  import {
13
13
  createEndpointHitAreaGeometry,
@@ -203,7 +203,7 @@ function WallRegionHandlesForWall({ wall }: { wall: WallNode }) {
203
203
  const frame: WallFrame = {
204
204
  wall: effectiveWall,
205
205
  baseElevation: getWallBaseElevationForNodes(effectiveWall, nodes),
206
- wallHeight: getWallEffectiveHeightForNodes(effectiveWall, nodes),
206
+ wallHeight: getWallEffectiveHeightForNodes(effectiveWall, nodes, 0.5),
207
207
  angle: Math.atan2(
208
208
  effectiveWall.end[1] - effectiveWall.start[1],
209
209
  effectiveWall.end[0] - effectiveWall.start[0],
@@ -426,7 +426,7 @@ function RegionEdgeHandle({
426
426
  const face = region.face
427
427
  const faceSurface = getWallFaceSurface(initialNode, face)
428
428
  const baseAt = faceBaseSampler(initialNode.id, face)
429
- const wallHeight = getWallEffectiveHeightForNodes(initialNode, nodes)
429
+ const wallHeight = getWallEffectiveHeightForNodes(initialNode, nodes, 0.5)
430
430
  const faceHeight = regionFaceHeight(region, faceSurface.length, wallHeight, baseAt)
431
431
  const bounds = resolveRegionBounds(region, faceSurface.length, faceHeight)
432
432
  const midU = (bounds.u0 + bounds.u1) / 2
@@ -165,9 +165,10 @@ type WallTopHighlightSegment = {
165
165
  angle: number
166
166
  center: [number, number]
167
167
  length: number
168
+ tCenter: number
168
169
  }
169
170
 
170
- function getWallTopY(wall: WallNode, nodes: Readonly<Record<string, AnyNode>>) {
171
+ function getWallTopY(wall: WallNode, nodes: Readonly<Record<string, AnyNode>>, t = 0.5): number {
171
172
  const levelId = resolveLevelId(wall, nodes as Record<string, AnyNode>)
172
173
  const support = spatialGridManager.getSlabSupportForWall(
173
174
  levelId,
@@ -181,12 +182,16 @@ function getWallTopY(wall: WallNode, nodes: Readonly<Record<string, AnyNode>>) {
181
182
  wall.justification,
182
183
  )
183
184
  return (
184
- getWallTop(wall, levelId, nodes as Record<string, AnyNode>, support.elevation) +
185
+ getWallTop(wall, levelId, nodes as Record<string, AnyNode>, support.elevation, t) +
185
186
  WALL_TOP_HIGHLIGHT_LIFT
186
187
  )
187
188
  }
188
189
 
189
- function buildHighlightSegment(start: [number, number], end: [number, number]) {
190
+ function buildHighlightSegment(
191
+ start: [number, number],
192
+ end: [number, number],
193
+ tCenter = 0.5,
194
+ ): WallTopHighlightSegment | null {
190
195
  const dx = end[0] - start[0]
191
196
  const dz = end[1] - start[1]
192
197
  const length = Math.hypot(dx, dz)
@@ -196,15 +201,20 @@ function buildHighlightSegment(start: [number, number], end: [number, number]) {
196
201
  angle: -Math.atan2(dz, dx),
197
202
  center: [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2] as [number, number],
198
203
  length,
204
+ tCenter,
199
205
  }
200
206
  }
201
207
 
202
208
  function buildWallTopHighlightSegments(wall: WallNode): WallTopHighlightSegment[] {
203
209
  if (!isCurvedWall(wall)) {
204
- const segment = buildHighlightSegment(wall.start, wall.end)
210
+ const segment = buildHighlightSegment(wall.start, wall.end, 0.5)
205
211
  return segment ? [segment] : []
206
212
  }
207
213
 
214
+ const dx = wall.end[0] - wall.start[0]
215
+ const dz = wall.end[1] - wall.start[1]
216
+ const chordLenSq = dx * dx + dz * dz
217
+
208
218
  const sampleCount = Math.max(
209
219
  8,
210
220
  Math.ceil(getWallCurveLength(wall) / CURVED_WALL_HIGHLIGHT_SEGMENT_LENGTH),
@@ -213,7 +223,16 @@ function buildWallTopHighlightSegments(wall: WallNode): WallTopHighlightSegment[
213
223
  let previous = getWallCurveFrameAt(wall, 0).point
214
224
  for (let index = 1; index <= sampleCount; index += 1) {
215
225
  const current = getWallCurveFrameAt(wall, index / sampleCount).point
216
- const segment = buildHighlightSegment([previous.x, previous.y], [current.x, current.y])
226
+ const midX = (previous.x + current.x) / 2
227
+ const midY = (previous.y + current.y) / 2
228
+ const tCenter =
229
+ chordLenSq > 1e-12
230
+ ? Math.max(
231
+ 0,
232
+ Math.min(1, ((midX - wall.start[0]) * dx + (midY - wall.start[1]) * dz) / chordLenSq),
233
+ )
234
+ : 0.5
235
+ const segment = buildHighlightSegment([previous.x, previous.y], [current.x, current.y], tCenter)
217
236
  if (segment) segments.push(segment)
218
237
  previous = current
219
238
  }
@@ -228,38 +247,40 @@ function WallTopHighlight({
228
247
  wall: WallNode
229
248
  }) {
230
249
  const segments = useMemo(() => buildWallTopHighlightSegments(wall), [wall])
231
- const y = getWallTopY(wall, nodes)
232
250
  const width = Math.max(getWallThickness(wall) + WALL_TOP_HIGHLIGHT_OVERHANG, 0.24)
233
251
  const glowWidth = Math.max(getWallThickness(wall) + WALL_TOP_GLOW_OVERHANG, 0.42)
234
252
 
235
253
  return (
236
254
  <>
237
- {segments.map((segment, index) => (
238
- <group key={`${wall.id}:${index}`}>
239
- <mesh
240
- frustumCulled={false}
241
- geometry={WALL_TOP_HIGHLIGHT_GEOMETRY}
242
- layers={EDITOR_LAYER}
243
- material={wallTopHighlightGlowMaterial}
244
- position={[segment.center[0], y - 0.003, segment.center[1]]}
245
- raycast={NO_RAYCAST}
246
- renderOrder={1003}
247
- rotation={[0, segment.angle, 0]}
248
- scale={[segment.length + WALL_TOP_END_OVERHANG, WALL_TOP_GLOW_HEIGHT, glowWidth]}
249
- />
250
- <mesh
251
- frustumCulled={false}
252
- geometry={WALL_TOP_HIGHLIGHT_GEOMETRY}
253
- layers={EDITOR_LAYER}
254
- material={wallTopHighlightMaterial}
255
- position={[segment.center[0], y + 0.002, segment.center[1]]}
256
- raycast={NO_RAYCAST}
257
- renderOrder={1004}
258
- rotation={[0, segment.angle, 0]}
259
- scale={[segment.length + WALL_TOP_END_OVERHANG, WALL_TOP_HIGHLIGHT_HEIGHT, width]}
260
- />
261
- </group>
262
- ))}
255
+ {segments.map((segment, index) => {
256
+ const y = getWallTopY(wall, nodes, segment.tCenter)
257
+ return (
258
+ <group key={`${wall.id}:${index}`}>
259
+ <mesh
260
+ frustumCulled={false}
261
+ geometry={WALL_TOP_HIGHLIGHT_GEOMETRY}
262
+ layers={EDITOR_LAYER}
263
+ material={wallTopHighlightGlowMaterial}
264
+ position={[segment.center[0], y - 0.003, segment.center[1]]}
265
+ raycast={NO_RAYCAST}
266
+ renderOrder={1003}
267
+ rotation={[0, segment.angle, 0]}
268
+ scale={[segment.length + WALL_TOP_END_OVERHANG, WALL_TOP_GLOW_HEIGHT, glowWidth]}
269
+ />
270
+ <mesh
271
+ frustumCulled={false}
272
+ geometry={WALL_TOP_HIGHLIGHT_GEOMETRY}
273
+ layers={EDITOR_LAYER}
274
+ material={wallTopHighlightMaterial}
275
+ position={[segment.center[0], y + 0.002, segment.center[1]]}
276
+ raycast={NO_RAYCAST}
277
+ renderOrder={1004}
278
+ rotation={[0, segment.angle, 0]}
279
+ scale={[segment.length + WALL_TOP_END_OVERHANG, WALL_TOP_HIGHLIGHT_HEIGHT, width]}
280
+ />
281
+ </group>
282
+ )
283
+ })}
263
284
  </>
264
285
  )
265
286
  }
@@ -8,6 +8,7 @@ import {
8
8
  DEFAULT_ANGLE_STEP,
9
9
  type FloorplanGeometry,
10
10
  type FloorplanPalette,
11
+ formatLinearMeasurement,
11
12
  pauseSceneHistory,
12
13
  pauseSpaceDetection,
13
14
  resumeSceneHistory,
@@ -16,18 +17,17 @@ import {
16
17
  useLiveTransforms,
17
18
  useScene,
18
19
  } from '@pascal-app/core'
19
- import { useViewer } from '@pascal-app/viewer'
20
+ import { isTypingTarget, useViewer } from '@pascal-app/viewer'
21
+ import { FloorplanDimensionRenderer } from '@pascal-app/viewer/ui'
20
22
  import { memo, type PointerEvent as ReactPointerEvent, useEffect, useMemo, useState } from 'react'
21
23
  import { create } from 'zustand'
22
24
  import { GROUP_MOVE_DRAG_LABEL, GROUP_ROTATE_DRAG_LABEL } from '../../lib/contextual-help'
23
25
  import { applyFloorplanAlignment } from '../../lib/floorplan/apply-alignment'
24
26
  import { clientToPlan } from '../../lib/floorplan/plan-coords'
25
27
  import { isHistoryShortcut } from '../../lib/history'
26
- import { formatLinearMeasurement } from '../../lib/measurements'
27
28
  import type { WallGroupPush } from '../../lib/room-handle-drag'
28
29
  import { useRoomHandleDrag } from '../../lib/room-handle-drag'
29
30
  import { sfxEmitter } from '../../lib/sfx-bus'
30
- import { isTypingTarget } from '../../lib/typing-target'
31
31
  import useAlignmentGuides from '../../store/use-alignment-guides'
32
32
  import useEditor, {
33
33
  isAlignmentGuideActive,
@@ -55,7 +55,6 @@ import {
55
55
  import { swallowNextClick } from '../editor/handles/use-handle-drag'
56
56
  import { useMeshSettleEpoch } from '../editor/use-mesh-settle-epoch'
57
57
  import { useFloorplanSceneRotation } from './floorplan-render-context'
58
- import { FloorplanDimensionRenderer } from './renderers/floorplan-dimension-renderer'
59
58
 
60
59
  // 2D sibling of the 3D body-drag group move (`group-move-3d.ts`): dragging
61
60
  // any selected element of a multi-selection slides the whole selection
@@ -3,11 +3,17 @@
3
3
  import {
4
4
  type AnyNode,
5
5
  type AnyNodeId,
6
+ buildMeasurementAngleArcPoints,
6
7
  closestMeasurementFeatureBinding,
7
8
  collectAlignmentAnchors,
8
9
  emitter,
10
+ formatAngleRadians,
11
+ formatAreaLabel,
12
+ formatLinearMeasurement,
9
13
  formatNumber,
14
+ formatVolumeLabel,
10
15
  type GeometryContext,
16
+ getLinearUnitLabel,
11
17
  type MeasurementFeatureAnchor,
12
18
  type MeasurementSnapKind,
13
19
  measurementAngle,
@@ -16,23 +22,19 @@ import {
16
22
  measurementFeatureLength,
17
23
  measurementPerimeter,
18
24
  measurementPrismVolume,
25
+ metersToLinearUnit,
19
26
  nodeRegistry,
27
+ parseLinearMeasurementInput,
20
28
  useLocale,
21
29
  useScene,
22
30
  } from '@pascal-app/core'
23
- import { useViewer } from '@pascal-app/viewer'
24
- import { useEffect, useMemo, useRef, useState } from 'react'
25
- import { measurementPolygonLabelAnchor } from '../../lib/measurement-label'
26
31
  import {
27
- buildMeasurementAngleArcPoints,
28
- formatAreaLabel,
29
- formatLinearMeasurement,
30
- formatVolumeLabel,
31
- getLinearUnitLabel,
32
32
  MEASUREMENT_ACTIVE_COLOR,
33
- metersToLinearUnit,
34
- parseLinearMeasurementInput,
35
- } from '../../lib/measurements'
33
+ measurementPolygonLabelAnchor,
34
+ useViewer,
35
+ } from '@pascal-app/viewer'
36
+ import { resolveFloorplanLabelAngle } from '@pascal-app/viewer/ui'
37
+ import { useEffect, useMemo, useRef, useState } from 'react'
36
38
  import {
37
39
  clearSurfacePlanSnapFeedback,
38
40
  resolveSurfacePlanPointSnap,
@@ -45,10 +47,8 @@ import {
45
47
  measurementPolygonMidpoints,
46
48
  useMeasurementDraft,
47
49
  } from '../../store/use-measurement-draft'
48
- import { formatAngleRadians } from '../tools/shared/segment-angle'
49
50
  import { FloorplanQuickMeasureLayer } from './floorplan-quick-measure-layer'
50
51
  import { useFloorplanRender } from './floorplan-render-context'
51
- import { resolveFloorplanLabelAngle } from './renderers/floorplan-label-angle'
52
52
 
53
53
  const DRAFT_COLOR = MEASUREMENT_ACTIVE_COLOR
54
54
  const X_AXIS_COLOR = '#ef4444'
@@ -1,7 +1,8 @@
1
1
  'use client'
2
2
 
3
3
  import type { AnyNodeId, OpenWallEnd } from '@pascal-app/core'
4
- import { useViewer } from '@pascal-app/viewer'
4
+ import { MEASUREMENT_DANGLING_COLOR, useViewer } from '@pascal-app/viewer'
5
+ import { resolveFloorplanLabelAngle } from '@pascal-app/viewer/ui'
5
6
  import { memo, useEffect, useState } from 'react'
6
7
  import { createPortal } from 'react-dom'
7
8
  import {
@@ -10,13 +11,11 @@ import {
10
11
  useOpenWallEndsSuppressed,
11
12
  } from '../../hooks/use-open-wall-ends'
12
13
  import { openWallEndKey, openWallEndLabel } from '../../lib/floorplan/open-wall-ends'
13
- import { MEASUREMENT_DANGLING_COLOR } from '../../lib/measurements'
14
14
  import useEditor from '../../store/use-editor'
15
15
  import { isViewVisible, VIEW_2D } from '../../store/view-layout'
16
16
  import { JoinWallsPill } from '../editor/join-walls-pill'
17
17
  import { OpenWallEndsHint } from '../editor/open-wall-ends-hint'
18
18
  import { useFloorplanRender } from './floorplan-render-context'
19
- import { resolveFloorplanLabelAngle } from './renderers/floorplan-label-angle'
20
19
 
21
20
  function sceneScreenPoint(point: [number, number]): { left: number; top: number } | null {
22
21
  const scene = document.querySelector<SVGGElement>('[data-floorplan-scene]')