@pascal-app/editor 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) 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 +11 -11
  5. package/src/components/editor/editor-layout-v2.tsx +1 -1
  6. package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
  7. package/src/components/editor/first-person-controls.tsx +20 -1981
  8. package/src/components/editor/floating-action-menu.tsx +1 -1
  9. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  10. package/src/components/editor/floor-region-controls.tsx +6 -2
  11. package/src/components/editor/floorplan-navigation-presentation.test.ts +0 -40
  12. package/src/components/editor/floorplan-navigation-presentation.ts +0 -66
  13. package/src/components/editor/floorplan-panel.tsx +26 -22
  14. package/src/components/editor/floorplan-wall-typing-keys.ts +1 -1
  15. package/src/components/editor/follow-pascal.test.tsx +1 -2
  16. package/src/components/editor/follow-pascal.tsx +1 -1
  17. package/src/components/editor/footprint-height-handle.tsx +1 -1
  18. package/src/components/editor/group-actions.ts +1 -2
  19. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  20. package/src/components/editor/group-move-3d.ts +1 -2
  21. package/src/components/editor/group-rotate-handle.tsx +1 -2
  22. package/src/components/editor/handles/use-handle-drag.ts +1 -2
  23. package/src/components/editor/index.tsx +24 -14
  24. package/src/components/editor/inspector-extension-actions.tsx +1 -1
  25. package/src/components/editor/node-arrow-handles.tsx +1 -1
  26. package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -1
  27. package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
  28. package/src/components/editor/room-controls.tsx +7 -4
  29. package/src/components/editor/room-handles-2d.tsx +2 -3
  30. package/src/components/editor/room-handles.tsx +1 -2
  31. package/src/components/editor/room-highlight.tsx +6 -105
  32. package/src/components/editor/room-labels.test.ts +8 -822
  33. package/src/components/editor/room-labels.tsx +40 -1671
  34. package/src/components/editor/room-webgpu-materials.test.ts +1 -1
  35. package/src/components/editor/selection-manager.tsx +5 -3
  36. package/src/components/editor/site-edge-labels.tsx +1 -1
  37. package/src/components/editor/snapshot-capture-overlay.tsx +2 -5
  38. package/src/components/editor/use-floorplan-background-placement.ts +2 -1
  39. package/src/components/editor/views/plugins-view.tsx +1 -1
  40. package/src/components/editor/views/view-bar.tsx +9 -9
  41. package/src/components/editor/wall-measurement-label.tsx +1 -1
  42. package/src/components/editor/wall-move-side-handles.tsx +1 -1
  43. package/src/components/editor/wall-pull-grip.tsx +2 -2
  44. package/src/components/editor-2d/floorplan-group-move.tsx +2 -3
  45. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1 -1
  46. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +1 -1
  47. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +1 -2
  48. package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +0 -37
  49. package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +0 -30
  50. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +13 -3
  51. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +1 -2
  52. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +8 -10
  53. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +25 -33
  54. package/src/components/systems/roof/roof-edit-system.tsx +4 -3
  55. package/src/components/tools/distribution/distribution-run-tool.tsx +1 -1
  56. package/src/components/tools/item/placement-history.test.tsx +1 -1
  57. package/src/components/tools/item/use-placement-coordinator.tsx +2 -3
  58. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +6 -2
  59. package/src/components/tools/registry/move-registry-node-tool.tsx +1 -2
  60. package/src/components/tools/shared/cursor-sphere.tsx +1 -1
  61. package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
  62. package/src/components/tools/shared/placement-box.tsx +1 -1
  63. package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
  64. package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
  65. package/src/components/ui/action-menu/camera-actions.tsx +2 -2
  66. package/src/components/ui/action-menu/control-modes.tsx +1 -1
  67. package/src/components/ui/action-menu/follow-pascal-pill.tsx +1 -1
  68. package/src/components/ui/action-menu/index.tsx +3 -3
  69. package/src/components/ui/action-menu/measurement-control.tsx +3 -3
  70. package/src/components/ui/action-menu/placement.ts +0 -18
  71. package/src/components/ui/action-menu/point-ask-button.tsx +1 -1
  72. package/src/components/ui/action-menu/view-toggles.tsx +5 -5
  73. package/src/components/ui/controls/finish-swatch.tsx +1 -1
  74. package/src/components/ui/controls/material-paint-panel.tsx +2 -2
  75. package/src/components/ui/controls/scene-material-list.tsx +2 -2
  76. package/src/components/ui/controls/select-control.tsx +1 -1
  77. package/src/components/ui/controls/terrain-sculpt-panel.tsx +1 -1
  78. package/src/components/ui/floating-level-selector.tsx +3 -3
  79. package/src/components/ui/helpers/contextual-helper-panel.tsx +1 -1
  80. package/src/components/ui/helpers/helper-manager.test.tsx +1 -1
  81. package/src/components/ui/helpers/helper-manager.tsx +1 -1
  82. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  83. package/src/components/ui/node-icon-image.test.tsx +46 -0
  84. package/src/components/ui/node-icon-image.tsx +33 -1
  85. package/src/components/ui/panels/collections/collections-popover.tsx +2 -2
  86. package/src/components/ui/panels/floor-foundation-panel.tsx +2 -2
  87. package/src/components/ui/panels/floor-opening-panel.tsx +4 -4
  88. package/src/components/ui/panels/node-display.ts +1 -1
  89. package/src/components/ui/panels/panel-manager.tsx +1 -1
  90. package/src/components/ui/panels/panel-wrapper.tsx +1 -1
  91. package/src/components/ui/panels/parametric-inspector.tsx +1 -1
  92. package/src/components/ui/panels/room-construction-rows.tsx +6 -6
  93. package/src/components/ui/panels/room-name-options.ts +1 -5
  94. package/src/components/ui/panels/room-panel.tsx +6 -8
  95. package/src/components/ui/primitives/color-dot.tsx +2 -2
  96. package/src/components/ui/primitives/opacity-control.tsx +2 -2
  97. package/src/components/ui/primitives/sidebar.tsx +3 -3
  98. package/src/components/ui/right-stack.tsx +1 -1
  99. package/src/components/ui/sidebar/icon-rail.tsx +1 -1
  100. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  101. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
  102. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +1 -1
  103. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +2 -2
  104. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +1 -1
  105. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -1
  106. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
  107. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +1 -1
  108. package/src/components/ui/sidebar/panels/site-panel/index.tsx +2 -2
  109. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +1 -1
  110. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
  111. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +46 -7
  112. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +1 -1
  113. package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +14 -5
  114. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -2
  115. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +3 -3
  116. package/src/components/ui/sidebar/tab-bar.tsx +1 -1
  117. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +1 -1
  118. package/src/components/ui/wall-type/wall-type-menu.tsx +1 -1
  119. package/src/components/viewer-overlay.tsx +1 -2
  120. package/src/hooks/use-auto-frame.ts +1 -2
  121. package/src/hooks/use-keyboard.ts +1 -2
  122. package/src/hooks/use-selected-room.ts +5 -7
  123. package/src/i18n/floor-footprint-name.ts +1 -1
  124. package/src/i18n/locales/en/messages.po +180 -182
  125. package/src/i18n/locales/es/messages.po +180 -182
  126. package/src/i18n/locales/fr/messages.po +180 -182
  127. package/src/i18n/locales/ja/messages.po +180 -182
  128. package/src/i18n/locales/zh-CN/messages.po +180 -182
  129. package/src/i18n/locales.test.ts +1 -1
  130. package/src/index.tsx +109 -131
  131. package/src/lib/below-grade.test.tsx +4 -4
  132. package/src/lib/camera-facing.test.ts +1 -1
  133. package/src/lib/camera-facing.ts +2 -3
  134. package/src/lib/editor-level-display.ts +1 -1
  135. package/src/lib/floor-footprints.test.ts +2 -2
  136. package/src/lib/floor-opening-draft.ts +1 -1
  137. package/src/lib/floorplan/annotation-visibility.test.ts +1 -1
  138. package/src/lib/floorplan/annotation-visibility.ts +1 -1
  139. package/src/lib/floorplan/floorplan-export.test.ts +1 -1
  140. package/src/lib/floorplan/floorplan-export.tsx +8 -11
  141. package/src/lib/floorplan/floorplan-extension.ts +4 -157
  142. package/src/lib/floorplan/floorplan-mode.ts +2 -1
  143. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +1 -1
  144. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +8 -6
  145. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +3 -4
  146. package/src/lib/floorplan/geometry.ts +2 -112
  147. package/src/lib/floorplan/index.ts +0 -9
  148. package/src/lib/floorplan/items.ts +2 -1
  149. package/src/lib/floorplan/schedules.ts +6 -3
  150. package/src/lib/floorplan/sheet-export.ts +7 -3
  151. package/src/lib/floorplan/site-plan/site-parts.ts +7 -1
  152. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +1 -1
  153. package/src/lib/follow-pascal-presentation.ts +1 -1
  154. package/src/lib/glb-export.ts +1 -1
  155. package/src/lib/material-paint.ts +1 -1
  156. package/src/lib/measurements.test.ts +2 -1
  157. package/src/lib/mezzanine-selection.test.ts +1 -1
  158. package/src/lib/paint-region-mode.ts +1 -1
  159. package/src/lib/point-ask/session.ts +1 -2
  160. package/src/lib/roof-duplication.test.ts +100 -0
  161. package/src/lib/roof-duplication.ts +30 -5
  162. package/src/lib/room-handle-drag.ts +2 -12
  163. package/src/lib/room-name-catalog.test.ts +2 -1
  164. package/src/lib/room-name-messages.test.ts +2 -1
  165. package/src/lib/room-name-messages.ts +1 -118
  166. package/src/lib/room-name-storage.test.ts +2 -1
  167. package/src/lib/room-selection-commands.ts +2 -2
  168. package/src/lib/room-selection.test.ts +6 -6
  169. package/src/lib/room-transform-session.ts +1 -2
  170. package/src/lib/room-zone-routing.ts +2 -2
  171. package/src/lib/selection-routing.test.ts +0 -28
  172. package/src/lib/selection-routing.ts +1 -40
  173. package/src/lib/sfx-bus.ts +8 -32
  174. package/src/lib/sfx-player.test.ts +72 -1
  175. package/src/lib/sfx-player.ts +49 -337
  176. package/src/lib/viewer-selection.test.ts +38 -10
  177. package/src/lib/wall-pull.test.ts +1 -1
  178. package/src/lib/wall-pull.ts +1 -27
  179. package/src/store/use-editor.tsx +17 -26
  180. package/src/store/use-floorplan-annotation-visibility.ts +5 -5
  181. package/src/store/use-wall-region-handles-selection.ts +1 -2
  182. package/src/components/editor/__bench__/room-label-placement.bench.ts +0 -108
  183. package/src/components/editor/first-person/build-collider-world.test.ts +0 -596
  184. package/src/components/editor/first-person/build-collider-world.ts +0 -549
  185. package/src/components/editor/first-person/ground-holes.test.ts +0 -128
  186. package/src/components/editor/first-person/ground-holes.ts +0 -130
  187. package/src/components/editor/first-person/mechanism-targets.test.ts +0 -162
  188. package/src/components/editor/first-person/mechanism-targets.ts +0 -88
  189. package/src/components/editor/first-person/terrain-collider.test.ts +0 -132
  190. package/src/components/editor/first-person/terrain-collider.ts +0 -212
  191. package/src/components/editor/floorplan-camera-sync.test.ts +0 -249
  192. package/src/components/editor/floorplan-camera-sync.ts +0 -213
  193. package/src/components/editor/localized-html.tsx +0 -22
  194. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +0 -236
  195. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +0 -708
  196. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +0 -385
  197. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +0 -670
  198. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +0 -89
  199. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +0 -106
  200. package/src/components/editor-2d/svg-paths.ts +0 -124
  201. package/src/components/ui/action-menu/action-button.tsx +0 -89
  202. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +0 -42
  203. package/src/components/ui/primitives/button.tsx +0 -69
  204. package/src/components/ui/primitives/dropdown-menu.tsx +0 -232
  205. package/src/components/ui/primitives/popover.tsx +0 -42
  206. package/src/components/ui/primitives/switch.tsx +0 -29
  207. package/src/components/ui/primitives/tooltip.tsx +0 -57
  208. package/src/components/ui/scene-loader.tsx +0 -73
  209. package/src/components/viewer/floorplan-compass-button.tsx +0 -67
  210. package/src/components/viewer/floorplan-preview-geometry.test.ts +0 -107
  211. package/src/components/viewer/floorplan-preview-geometry.ts +0 -281
  212. package/src/components/viewer/floorplan-preview-navigation.test.ts +0 -43
  213. package/src/components/viewer/floorplan-preview-navigation.ts +0 -62
  214. package/src/components/viewer/floorplan-preview-visibility.test.ts +0 -35
  215. package/src/components/viewer/floorplan-preview-visibility.ts +0 -19
  216. package/src/components/viewer/floorplan-preview.tsx +0 -1065
  217. package/src/components/viewer/render-settings-menu.tsx +0 -294
  218. package/src/components/viewer/sky-control.tsx +0 -710
  219. package/src/components/viewer/use-viewer-camera-navigation-sync.ts +0 -140
  220. package/src/components/viewer/use-viewer-ui-sounds.test.ts +0 -15
  221. package/src/components/viewer/use-viewer-ui-sounds.ts +0 -46
  222. package/src/components/viewer/viewer-controls-bar.tsx +0 -496
  223. package/src/components/viewer/viewer-hover-label.tsx +0 -42
  224. package/src/components/viewer/viewer-rooms.tsx +0 -248
  225. package/src/components/viewer/viewer-scene-header.tsx +0 -286
  226. package/src/components/viewer/viewer-selection-manager.tsx +0 -124
  227. package/src/components/viewer/viewer-stage-modes.test.ts +0 -38
  228. package/src/components/viewer/viewer-stage-modes.ts +0 -49
  229. package/src/components/viewer/viewer-stage-switcher.tsx +0 -97
  230. package/src/components/viewer/viewer-stage.test.tsx +0 -47
  231. package/src/components/viewer/viewer-stage.tsx +0 -221
  232. package/src/components/walkthrough-hud.tsx +0 -193
  233. package/src/hooks/use-mobile.ts +0 -19
  234. package/src/i18n/level-name.ts +0 -39
  235. package/src/i18n/scene-theme-name.ts +0 -16
  236. package/src/lib/below-grade.ts +0 -133
  237. package/src/lib/camera-pose.test.ts +0 -237
  238. package/src/lib/camera-pose.ts +0 -172
  239. package/src/lib/floorplan/floorplan-extension.test.ts +0 -42
  240. package/src/lib/floorplan/floorplan-readonly.test.ts +0 -47
  241. package/src/lib/floorplan/floorplan-readonly.ts +0 -95
  242. package/src/lib/floorplan/marks.test.ts +0 -213
  243. package/src/lib/floorplan/marks.ts +0 -238
  244. package/src/lib/measurements.ts +0 -8
  245. package/src/lib/node-display-label.ts +0 -37
  246. package/src/lib/room-assembly-overlay.test.ts +0 -275
  247. package/src/lib/room-assembly-overlay.ts +0 -265
  248. package/src/lib/room-selection.ts +0 -417
  249. package/src/lib/scene-bounds.test.ts +0 -327
  250. package/src/lib/scene-bounds.ts +0 -233
  251. package/src/lib/typing-target.ts +0 -9
  252. package/src/lib/viewer-selection.ts +0 -595
  253. package/src/lib/zone-content.ts +0 -133
  254. package/src/store/camera-pose-store.test.ts +0 -29
  255. package/src/store/camera-pose-store.ts +0 -27
  256. package/src/store/navigation-sync-pose-store.test.ts +0 -34
  257. package/src/store/navigation-sync-pose-store.ts +0 -27
  258. package/src/store/use-audio.test.ts +0 -146
  259. package/src/store/use-audio.tsx +0 -169
  260. package/src/store/use-first-person-hud.ts +0 -40
  261. package/src/store/use-follow-pascal.test.ts +0 -62
  262. package/src/store/use-follow-pascal.ts +0 -53
@@ -1,281 +0,0 @@
1
- import type { FloorplanGeometry, FloorplanPoint } from '@pascal-app/core'
2
-
3
- export type FloorplanBounds = {
4
- minX: number
5
- minY: number
6
- maxX: number
7
- maxY: number
8
- }
9
-
10
- export type FloorplanViewBox = { x: number; y: number; width: number; height: number }
11
-
12
- export type FloorplanViewport = {
13
- left: number
14
- top: number
15
- width: number
16
- height: number
17
- }
18
-
19
- type PlanTransform = { tx: number; ty: number; rotation: number }
20
-
21
- const IDENTITY: PlanTransform = { tx: 0, ty: 0, rotation: 0 }
22
- const MIN_VIEWBOX_SIZE = 0.25
23
- const MAX_VIEWBOX_SIZE = 500
24
-
25
- function clientToViewBoxFraction(
26
- viewBox: FloorplanViewBox,
27
- viewport: FloorplanViewport,
28
- clientX: number,
29
- clientY: number,
30
- ): FloorplanPoint {
31
- const viewportWidth = Math.max(viewport.width, 1)
32
- const viewportHeight = Math.max(viewport.height, 1)
33
- const scale = Math.min(viewportWidth / viewBox.width, viewportHeight / viewBox.height)
34
- const renderedWidth = viewBox.width * scale
35
- const renderedHeight = viewBox.height * scale
36
- const renderedLeft = viewport.left + (viewportWidth - renderedWidth) / 2
37
- const renderedTop = viewport.top + (viewportHeight - renderedHeight) / 2
38
- return [(clientX - renderedLeft) / renderedWidth, (clientY - renderedTop) / renderedHeight]
39
- }
40
-
41
- export function clientToFloorplanPoint(
42
- viewBox: FloorplanViewBox,
43
- viewport: FloorplanViewport,
44
- clientX: number,
45
- clientY: number,
46
- ): FloorplanPoint {
47
- const [fractionX, fractionY] = clientToViewBoxFraction(viewBox, viewport, clientX, clientY)
48
- return [viewBox.x + fractionX * viewBox.width, viewBox.y + fractionY * viewBox.height]
49
- }
50
-
51
- export function scaleFloorplanViewBox(
52
- viewBox: FloorplanViewBox,
53
- factor: number,
54
- anchorX = 0.5,
55
- anchorY = 0.5,
56
- ): FloorplanViewBox {
57
- const minFactor = Math.max(MIN_VIEWBOX_SIZE / viewBox.width, MIN_VIEWBOX_SIZE / viewBox.height)
58
- const maxFactor = Math.min(MAX_VIEWBOX_SIZE / viewBox.width, MAX_VIEWBOX_SIZE / viewBox.height)
59
- const clampedFactor = Math.min(Math.max(factor, minFactor), maxFactor)
60
- const width = viewBox.width * clampedFactor
61
- const height = viewBox.height * clampedFactor
62
- return {
63
- x: viewBox.x + (viewBox.width - width) * anchorX,
64
- y: viewBox.y + (viewBox.height - height) * anchorY,
65
- width,
66
- height,
67
- }
68
- }
69
-
70
- export function scaleFloorplanViewBoxBetweenClients(
71
- viewBox: FloorplanViewBox,
72
- factor: number,
73
- viewport: FloorplanViewport,
74
- sourceClient: FloorplanPoint,
75
- targetClient: FloorplanPoint,
76
- ): FloorplanViewBox {
77
- const sourcePoint = clientToFloorplanPoint(viewBox, viewport, sourceClient[0], sourceClient[1])
78
- const [targetX, targetY] = clientToViewBoxFraction(
79
- viewBox,
80
- viewport,
81
- targetClient[0],
82
- targetClient[1],
83
- )
84
- const scaled = scaleFloorplanViewBox(viewBox, factor, 0, 0)
85
- return {
86
- ...scaled,
87
- x: sourcePoint[0] - targetX * scaled.width,
88
- y: sourcePoint[1] - targetY * scaled.height,
89
- }
90
- }
91
-
92
- export function panFloorplanViewBox(
93
- viewBox: FloorplanViewBox,
94
- viewport: FloorplanViewport,
95
- sourceClient: FloorplanPoint,
96
- targetClient: FloorplanPoint,
97
- ): FloorplanViewBox {
98
- const sourcePoint = clientToFloorplanPoint(viewBox, viewport, sourceClient[0], sourceClient[1])
99
- const targetPoint = clientToFloorplanPoint(viewBox, viewport, targetClient[0], targetClient[1])
100
- return {
101
- ...viewBox,
102
- x: viewBox.x + sourcePoint[0] - targetPoint[0],
103
- y: viewBox.y + sourcePoint[1] - targetPoint[1],
104
- }
105
- }
106
-
107
- function applyTransform(point: FloorplanPoint, transform: PlanTransform): FloorplanPoint {
108
- const cos = Math.cos(transform.rotation)
109
- const sin = Math.sin(transform.rotation)
110
- return [
111
- point[0] * cos - point[1] * sin + transform.tx,
112
- point[0] * sin + point[1] * cos + transform.ty,
113
- ]
114
- }
115
-
116
- function composeTransform(
117
- parent: PlanTransform,
118
- child: NonNullable<Extract<FloorplanGeometry, { kind: 'group' }>['transform']>,
119
- ): PlanTransform {
120
- const translated = applyTransform(child.translate ?? [0, 0], parent)
121
- return {
122
- tx: translated[0],
123
- ty: translated[1],
124
- rotation: parent.rotation + (child.rotate ?? 0),
125
- }
126
- }
127
-
128
- function includePoint(bounds: FloorplanBounds | null, point: FloorplanPoint): FloorplanBounds {
129
- if (!bounds) return { minX: point[0], minY: point[1], maxX: point[0], maxY: point[1] }
130
- return {
131
- minX: Math.min(bounds.minX, point[0]),
132
- minY: Math.min(bounds.minY, point[1]),
133
- maxX: Math.max(bounds.maxX, point[0]),
134
- maxY: Math.max(bounds.maxY, point[1]),
135
- }
136
- }
137
-
138
- function includePoints(
139
- bounds: FloorplanBounds | null,
140
- points: readonly FloorplanPoint[],
141
- transform: PlanTransform,
142
- ): FloorplanBounds | null {
143
- let next = bounds
144
- for (const point of points) next = includePoint(next, applyTransform(point, transform))
145
- return next
146
- }
147
-
148
- function geometryBounds(
149
- geometry: FloorplanGeometry,
150
- transform: PlanTransform,
151
- bounds: FloorplanBounds | null,
152
- ): FloorplanBounds | null {
153
- switch (geometry.kind) {
154
- case 'group': {
155
- const nextTransform = geometry.transform
156
- ? composeTransform(transform, geometry.transform)
157
- : transform
158
- return geometry.children.reduce(
159
- (next, child) => geometryBounds(child, nextTransform, next),
160
- bounds,
161
- )
162
- }
163
- case 'polygon':
164
- case 'polyline':
165
- case 'hatch':
166
- return includePoints(bounds, geometry.points, transform)
167
- case 'rect':
168
- return includePoints(
169
- bounds,
170
- [
171
- [geometry.x, geometry.y],
172
- [geometry.x + geometry.width, geometry.y],
173
- [geometry.x + geometry.width, geometry.y + geometry.height],
174
- [geometry.x, geometry.y + geometry.height],
175
- ],
176
- transform,
177
- )
178
- case 'circle': {
179
- const center = applyTransform([geometry.cx, geometry.cy], transform)
180
- return includePoints(
181
- bounds,
182
- [
183
- [center[0] - geometry.r, center[1] - geometry.r],
184
- [center[0] + geometry.r, center[1] + geometry.r],
185
- ],
186
- IDENTITY,
187
- )
188
- }
189
- case 'line':
190
- case 'hit-line':
191
- case 'edge-handle':
192
- return includePoints(
193
- bounds,
194
- [
195
- [geometry.x1, geometry.y1],
196
- [geometry.x2, geometry.y2],
197
- ],
198
- transform,
199
- )
200
- case 'text':
201
- return includePoint(bounds, applyTransform([geometry.x, geometry.y], transform))
202
- case 'image': {
203
- const halfWidth = geometry.width / 2
204
- const halfHeight = geometry.height / 2
205
- const imageTransform = composeTransform(transform, {
206
- translate: geometry.center,
207
- rotate: geometry.rotation,
208
- })
209
- return includePoints(
210
- bounds,
211
- [
212
- [-halfWidth, -halfHeight],
213
- [halfWidth, -halfHeight],
214
- [halfWidth, halfHeight],
215
- [-halfWidth, halfHeight],
216
- ],
217
- imageTransform,
218
- )
219
- }
220
- case 'endpoint-handle':
221
- case 'midpoint-handle':
222
- case 'move-handle':
223
- case 'move-arrow':
224
- case 'rotate-arrow':
225
- case 'equal-spacing-badge':
226
- return includePoint(bounds, applyTransform(geometry.point, transform))
227
- case 'dimension-label':
228
- return includePoint(bounds, applyTransform([geometry.cx, geometry.cy], transform))
229
- case 'dimension':
230
- return includePoints(
231
- bounds,
232
- [
233
- geometry.start,
234
- geometry.end,
235
- geometry.dimensionStart ?? geometry.start,
236
- geometry.dimensionEnd ?? geometry.end,
237
- ],
238
- transform,
239
- )
240
- case 'dimension-string':
241
- return geometry.segments.reduce(
242
- (next, segment) =>
243
- includePoints(
244
- next,
245
- [
246
- segment.start,
247
- segment.end,
248
- segment.dimensionStart ?? segment.start,
249
- segment.dimensionEnd ?? segment.end,
250
- ],
251
- transform,
252
- ),
253
- bounds,
254
- )
255
- case 'path':
256
- return bounds
257
- default:
258
- return bounds
259
- }
260
- }
261
-
262
- export function getFloorplanBounds(
263
- geometries: readonly FloorplanGeometry[],
264
- ): FloorplanBounds | null {
265
- return geometries.reduce(
266
- (bounds, geometry) => geometryBounds(geometry, IDENTITY, bounds),
267
- null as FloorplanBounds | null,
268
- )
269
- }
270
-
271
- export function padFloorplanBounds(bounds: FloorplanBounds, ratio = 0.12): FloorplanBounds {
272
- const width = Math.max(bounds.maxX - bounds.minX, 1)
273
- const height = Math.max(bounds.maxY - bounds.minY, 1)
274
- const padding = Math.max(Math.max(width, height) * ratio, 0.75)
275
- return {
276
- minX: bounds.minX - padding,
277
- minY: bounds.minY - padding,
278
- maxX: bounds.maxX + padding,
279
- maxY: bounds.maxY + padding,
280
- }
281
- }
@@ -1,43 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
- import {
3
- cameraAzimuthFromFloorplanRotation,
4
- floorplanRotationFromCameraAzimuth,
5
- floorplanViewBoxFromNavigationPose,
6
- nearestEquivalentDegrees,
7
- visibleFloorplanViewWidth,
8
- } from './floorplan-preview-navigation'
9
-
10
- describe('floorplan preview navigation', () => {
11
- test('keeps continuous compass rotations across the angle seam', () => {
12
- expect(nearestEquivalentDegrees(-179, 179)).toBe(181)
13
- expect(floorplanRotationFromCameraAzimuth((-179 * Math.PI) / 180, 179)).toBeCloseTo(181)
14
- })
15
-
16
- test('uses the same north-up azimuth convention as the editor', () => {
17
- expect(cameraAzimuthFromFloorplanRotation(0)).toBe(0)
18
- expect(cameraAzimuthFromFloorplanRotation(90)).toBeCloseTo(Math.PI / 2)
19
- })
20
-
21
- test('maps a camera pose to an aspect-correct centered view box', () => {
22
- expect(
23
- floorplanViewBoxFromNavigationPose(
24
- {
25
- source: '3d',
26
- revision: 1,
27
- target: [0, 0, 0],
28
- azimuth: 0,
29
- viewWidth: 20,
30
- },
31
- { x: 4, y: 3 },
32
- 0,
33
- { width: 1000, height: 500 },
34
- ),
35
- ).toEqual({ x: -6, y: -2, width: 20, height: 10 })
36
- })
37
-
38
- test('reports the actual horizontal span for meet-preserved SVG view boxes', () => {
39
- expect(
40
- visibleFloorplanViewWidth({ x: 0, y: 0, width: 10, height: 10 }, { width: 200, height: 100 }),
41
- ).toBe(20)
42
- })
43
- })
@@ -1,62 +0,0 @@
1
- import type { NavigationSyncPose } from '../../store/use-editor'
2
- import type { FloorplanViewBox } from './floorplan-preview-geometry'
3
-
4
- export type FloorplanPreviewViewportSize = {
5
- width: number
6
- height: number
7
- }
8
-
9
- export function nearestEquivalentDegrees(angle: number, reference: number) {
10
- let next = angle
11
- while (next - reference > 180) next -= 360
12
- while (next - reference < -180) next += 360
13
- return next
14
- }
15
-
16
- export function floorplanRotationFromCameraAzimuth(azimuth: number, reference: number) {
17
- return nearestEquivalentDegrees((azimuth * 180) / Math.PI, reference)
18
- }
19
-
20
- export function cameraAzimuthFromFloorplanRotation(rotationDeg: number) {
21
- return (rotationDeg * Math.PI) / 180
22
- }
23
-
24
- export function rotateFloorplanPoint(
25
- point: { x: number; y: number },
26
- rotationDeg: number,
27
- ): { x: number; y: number } {
28
- if (rotationDeg === 0) return point
29
- const radians = (rotationDeg * Math.PI) / 180
30
- const cos = Math.cos(radians)
31
- const sin = Math.sin(radians)
32
- return {
33
- x: point.x * cos - point.y * sin,
34
- y: point.x * sin + point.y * cos,
35
- }
36
- }
37
-
38
- export function visibleFloorplanViewWidth(
39
- viewBox: FloorplanViewBox,
40
- viewport: FloorplanPreviewViewportSize,
41
- ) {
42
- const aspect = Math.max(viewport.width, 1) / Math.max(viewport.height, 1)
43
- return Math.max(viewBox.width, viewBox.height * aspect)
44
- }
45
-
46
- export function floorplanViewBoxFromNavigationPose(
47
- pose: NavigationSyncPose,
48
- localCenter: { x: number; y: number },
49
- sceneRotationDeg: number,
50
- viewport: FloorplanPreviewViewportSize,
51
- ): FloorplanViewBox {
52
- const center = rotateFloorplanPoint(localCenter, sceneRotationDeg)
53
- const aspect = Math.max(viewport.width, 1) / Math.max(viewport.height, 1)
54
- const width = Math.max(pose.viewWidth, 0.001)
55
- const height = width / aspect
56
- return {
57
- x: center.x - width / 2,
58
- y: center.y - height / 2,
59
- width,
60
- height,
61
- }
62
- }
@@ -1,35 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
- import type { AnyNode } from '@pascal-app/core'
3
- import { isVisibleInFloorplan } from './floorplan-preview-visibility'
4
-
5
- const node = (id: string, type: string, parentId: string | null, visible = true) =>
6
- ({ object: 'node', id, type, parentId, visible, metadata: {} }) as unknown as AnyNode
7
-
8
- describe('isVisibleInFloorplan', () => {
9
- test('a hidden Site keeps the buildings on it in the plan', () => {
10
- const nodes: Record<string, AnyNode> = {
11
- site_a: node('site_a', 'site', null, false),
12
- building_a: node('building_a', 'building', 'site_a'),
13
- level_a: node('level_a', 'level', 'building_a'),
14
- wall_a: node('wall_a', 'wall', 'level_a'),
15
- wall_b: node('wall_b', 'wall', 'level_a', false),
16
- }
17
- expect(isVisibleInFloorplan(nodes.wall_a!, nodes)).toBe(true)
18
- expect(isVisibleInFloorplan(nodes.wall_b!, nodes)).toBe(false)
19
- expect(isVisibleInFloorplan(nodes.site_a!, nodes)).toBe(false)
20
- })
21
-
22
- test('a hidden building or level still hides its subtree', () => {
23
- const nodes: Record<string, AnyNode> = {
24
- site_a: node('site_a', 'site', null),
25
- building_a: node('building_a', 'building', 'site_a', false),
26
- level_a: node('level_a', 'level', 'building_a'),
27
- wall_a: node('wall_a', 'wall', 'level_a'),
28
- building_b: node('building_b', 'building', 'site_a'),
29
- level_b: node('level_b', 'level', 'building_b', false),
30
- wall_b: node('wall_b', 'wall', 'level_b'),
31
- }
32
- expect(isVisibleInFloorplan(nodes.wall_a!, nodes)).toBe(false)
33
- expect(isVisibleInFloorplan(nodes.wall_b!, nodes)).toBe(false)
34
- })
35
- })
@@ -1,19 +0,0 @@
1
- import { type AnyNode, hidesDescendants } from '@pascal-app/core'
2
-
3
- /**
4
- * A node draws in the plan unless it, or an ancestor whose flag reaches its
5
- * descendants, is hidden. A hidden Site keeps its buildings on the plan; see
6
- * `hidesDescendants`.
7
- */
8
- export function isVisibleInFloorplan(node: AnyNode, nodes: Record<string, AnyNode>): boolean {
9
- if (node.visible === false) return false
10
- const seen = new Set<string>([node.id])
11
- let current: AnyNode | undefined = node.parentId ? nodes[node.parentId] : undefined
12
- while (current) {
13
- if (seen.has(current.id)) return true
14
- seen.add(current.id)
15
- if (current.visible === false && hidesDescendants(current)) return false
16
- current = current.parentId ? nodes[current.parentId] : undefined
17
- }
18
- return true
19
- }