@pascal-app/editor 1.1.0 → 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 (260) 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/panels/collections/collections-popover.tsx +2 -2
  84. package/src/components/ui/panels/floor-foundation-panel.tsx +2 -2
  85. package/src/components/ui/panels/floor-opening-panel.tsx +4 -4
  86. package/src/components/ui/panels/node-display.ts +1 -1
  87. package/src/components/ui/panels/panel-manager.tsx +1 -1
  88. package/src/components/ui/panels/panel-wrapper.tsx +1 -1
  89. package/src/components/ui/panels/parametric-inspector.tsx +1 -1
  90. package/src/components/ui/panels/room-construction-rows.tsx +6 -6
  91. package/src/components/ui/panels/room-name-options.ts +1 -5
  92. package/src/components/ui/panels/room-panel.tsx +6 -8
  93. package/src/components/ui/primitives/color-dot.tsx +2 -2
  94. package/src/components/ui/primitives/opacity-control.tsx +2 -2
  95. package/src/components/ui/primitives/sidebar.tsx +3 -3
  96. package/src/components/ui/right-stack.tsx +1 -1
  97. package/src/components/ui/sidebar/icon-rail.tsx +1 -1
  98. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  99. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
  100. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +1 -1
  101. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +2 -2
  102. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +1 -1
  103. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -1
  104. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
  105. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +1 -1
  106. package/src/components/ui/sidebar/panels/site-panel/index.tsx +2 -2
  107. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +1 -1
  108. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
  109. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +46 -7
  110. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +1 -1
  111. package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +14 -5
  112. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -2
  113. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +3 -3
  114. package/src/components/ui/sidebar/tab-bar.tsx +1 -1
  115. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +1 -1
  116. package/src/components/ui/wall-type/wall-type-menu.tsx +1 -1
  117. package/src/components/viewer-overlay.tsx +1 -2
  118. package/src/hooks/use-auto-frame.ts +1 -2
  119. package/src/hooks/use-keyboard.ts +1 -2
  120. package/src/hooks/use-selected-room.ts +5 -7
  121. package/src/i18n/floor-footprint-name.ts +1 -1
  122. package/src/i18n/locales/en/messages.po +180 -182
  123. package/src/i18n/locales/es/messages.po +180 -182
  124. package/src/i18n/locales/fr/messages.po +180 -182
  125. package/src/i18n/locales/ja/messages.po +180 -182
  126. package/src/i18n/locales/zh-CN/messages.po +180 -182
  127. package/src/i18n/locales.test.ts +1 -1
  128. package/src/index.tsx +109 -131
  129. package/src/lib/below-grade.test.tsx +4 -4
  130. package/src/lib/camera-facing.test.ts +1 -1
  131. package/src/lib/camera-facing.ts +2 -3
  132. package/src/lib/editor-level-display.ts +1 -1
  133. package/src/lib/floor-footprints.test.ts +2 -2
  134. package/src/lib/floor-opening-draft.ts +1 -1
  135. package/src/lib/floorplan/annotation-visibility.test.ts +1 -1
  136. package/src/lib/floorplan/annotation-visibility.ts +1 -1
  137. package/src/lib/floorplan/floorplan-export.test.ts +1 -1
  138. package/src/lib/floorplan/floorplan-export.tsx +8 -11
  139. package/src/lib/floorplan/floorplan-extension.ts +4 -157
  140. package/src/lib/floorplan/floorplan-mode.ts +2 -1
  141. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +1 -1
  142. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +8 -6
  143. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +3 -4
  144. package/src/lib/floorplan/geometry.ts +2 -112
  145. package/src/lib/floorplan/index.ts +0 -9
  146. package/src/lib/floorplan/items.ts +2 -1
  147. package/src/lib/floorplan/schedules.ts +6 -3
  148. package/src/lib/floorplan/sheet-export.ts +7 -3
  149. package/src/lib/floorplan/site-plan/site-parts.ts +7 -1
  150. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +1 -1
  151. package/src/lib/follow-pascal-presentation.ts +1 -1
  152. package/src/lib/glb-export.ts +1 -1
  153. package/src/lib/material-paint.ts +1 -1
  154. package/src/lib/measurements.test.ts +2 -1
  155. package/src/lib/mezzanine-selection.test.ts +1 -1
  156. package/src/lib/paint-region-mode.ts +1 -1
  157. package/src/lib/point-ask/session.ts +1 -2
  158. package/src/lib/roof-duplication.test.ts +100 -0
  159. package/src/lib/roof-duplication.ts +30 -5
  160. package/src/lib/room-handle-drag.ts +2 -12
  161. package/src/lib/room-name-catalog.test.ts +2 -1
  162. package/src/lib/room-name-messages.test.ts +2 -1
  163. package/src/lib/room-name-messages.ts +1 -118
  164. package/src/lib/room-name-storage.test.ts +2 -1
  165. package/src/lib/room-selection-commands.ts +2 -2
  166. package/src/lib/room-selection.test.ts +6 -6
  167. package/src/lib/room-transform-session.ts +1 -2
  168. package/src/lib/room-zone-routing.ts +2 -2
  169. package/src/lib/selection-routing.test.ts +0 -28
  170. package/src/lib/selection-routing.ts +1 -40
  171. package/src/lib/sfx-bus.ts +8 -32
  172. package/src/lib/sfx-player.test.ts +72 -1
  173. package/src/lib/sfx-player.ts +49 -337
  174. package/src/lib/viewer-selection.test.ts +38 -10
  175. package/src/lib/wall-pull.test.ts +1 -1
  176. package/src/lib/wall-pull.ts +1 -27
  177. package/src/store/use-editor.tsx +17 -26
  178. package/src/store/use-floorplan-annotation-visibility.ts +5 -5
  179. package/src/store/use-wall-region-handles-selection.ts +1 -2
  180. package/src/components/editor/__bench__/room-label-placement.bench.ts +0 -108
  181. package/src/components/editor/first-person/build-collider-world.test.ts +0 -596
  182. package/src/components/editor/first-person/build-collider-world.ts +0 -549
  183. package/src/components/editor/first-person/ground-holes.test.ts +0 -128
  184. package/src/components/editor/first-person/ground-holes.ts +0 -130
  185. package/src/components/editor/first-person/mechanism-targets.test.ts +0 -162
  186. package/src/components/editor/first-person/mechanism-targets.ts +0 -88
  187. package/src/components/editor/first-person/terrain-collider.test.ts +0 -132
  188. package/src/components/editor/first-person/terrain-collider.ts +0 -212
  189. package/src/components/editor/floorplan-camera-sync.test.ts +0 -249
  190. package/src/components/editor/floorplan-camera-sync.ts +0 -213
  191. package/src/components/editor/localized-html.tsx +0 -22
  192. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +0 -236
  193. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +0 -708
  194. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +0 -385
  195. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +0 -670
  196. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +0 -89
  197. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +0 -106
  198. package/src/components/editor-2d/svg-paths.ts +0 -124
  199. package/src/components/ui/action-menu/action-button.tsx +0 -89
  200. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +0 -42
  201. package/src/components/ui/primitives/button.tsx +0 -69
  202. package/src/components/ui/primitives/dropdown-menu.tsx +0 -232
  203. package/src/components/ui/primitives/popover.tsx +0 -42
  204. package/src/components/ui/primitives/switch.tsx +0 -29
  205. package/src/components/ui/primitives/tooltip.tsx +0 -57
  206. package/src/components/ui/scene-loader.tsx +0 -73
  207. package/src/components/viewer/floorplan-compass-button.tsx +0 -67
  208. package/src/components/viewer/floorplan-preview-geometry.test.ts +0 -107
  209. package/src/components/viewer/floorplan-preview-geometry.ts +0 -281
  210. package/src/components/viewer/floorplan-preview-navigation.test.ts +0 -43
  211. package/src/components/viewer/floorplan-preview-navigation.ts +0 -62
  212. package/src/components/viewer/floorplan-preview-visibility.test.ts +0 -35
  213. package/src/components/viewer/floorplan-preview-visibility.ts +0 -19
  214. package/src/components/viewer/floorplan-preview.tsx +0 -1065
  215. package/src/components/viewer/render-settings-menu.tsx +0 -294
  216. package/src/components/viewer/sky-control.tsx +0 -710
  217. package/src/components/viewer/use-viewer-camera-navigation-sync.ts +0 -140
  218. package/src/components/viewer/use-viewer-ui-sounds.test.ts +0 -15
  219. package/src/components/viewer/use-viewer-ui-sounds.ts +0 -46
  220. package/src/components/viewer/viewer-controls-bar.tsx +0 -496
  221. package/src/components/viewer/viewer-hover-label.tsx +0 -42
  222. package/src/components/viewer/viewer-rooms.tsx +0 -248
  223. package/src/components/viewer/viewer-scene-header.tsx +0 -286
  224. package/src/components/viewer/viewer-selection-manager.tsx +0 -124
  225. package/src/components/viewer/viewer-stage-modes.test.ts +0 -38
  226. package/src/components/viewer/viewer-stage-modes.ts +0 -49
  227. package/src/components/viewer/viewer-stage-switcher.tsx +0 -97
  228. package/src/components/viewer/viewer-stage.test.tsx +0 -47
  229. package/src/components/viewer/viewer-stage.tsx +0 -221
  230. package/src/components/walkthrough-hud.tsx +0 -193
  231. package/src/hooks/use-mobile.ts +0 -19
  232. package/src/i18n/level-name.ts +0 -39
  233. package/src/i18n/scene-theme-name.ts +0 -16
  234. package/src/lib/below-grade.ts +0 -133
  235. package/src/lib/camera-pose.test.ts +0 -237
  236. package/src/lib/camera-pose.ts +0 -172
  237. package/src/lib/floorplan/floorplan-extension.test.ts +0 -42
  238. package/src/lib/floorplan/floorplan-readonly.test.ts +0 -47
  239. package/src/lib/floorplan/floorplan-readonly.ts +0 -95
  240. package/src/lib/floorplan/marks.test.ts +0 -213
  241. package/src/lib/floorplan/marks.ts +0 -238
  242. package/src/lib/measurements.ts +0 -8
  243. package/src/lib/node-display-label.ts +0 -37
  244. package/src/lib/room-assembly-overlay.test.ts +0 -275
  245. package/src/lib/room-assembly-overlay.ts +0 -265
  246. package/src/lib/room-selection.ts +0 -417
  247. package/src/lib/scene-bounds.test.ts +0 -327
  248. package/src/lib/scene-bounds.ts +0 -233
  249. package/src/lib/typing-target.ts +0 -9
  250. package/src/lib/viewer-selection.ts +0 -595
  251. package/src/lib/zone-content.ts +0 -133
  252. package/src/store/camera-pose-store.test.ts +0 -29
  253. package/src/store/camera-pose-store.ts +0 -27
  254. package/src/store/navigation-sync-pose-store.test.ts +0 -34
  255. package/src/store/navigation-sync-pose-store.ts +0 -27
  256. package/src/store/use-audio.test.ts +0 -146
  257. package/src/store/use-audio.tsx +0 -169
  258. package/src/store/use-first-person-hud.ts +0 -40
  259. package/src/store/use-follow-pascal.test.ts +0 -62
  260. package/src/store/use-follow-pascal.ts +0 -53
@@ -1,708 +0,0 @@
1
- import type { FloorplanGeometry, FloorplanPoint } from '@pascal-app/core'
2
- import { useDimensionEditAllowed } from './floorplan-dimension-edit-overlay'
3
- import { resolveFloorplanLabelAngle } from './floorplan-label-angle'
4
-
5
- const EXTENSION_START_GAP = 0.075
6
- const TICK_HALF_LENGTH = 0.09
7
- const LABEL_FONT_SIZE = 0.15
8
- const LABEL_BASELINE_OFFSET = 0.12
9
- const LABEL_CHARACTER_WIDTH_RATIO = 0.62
10
- const LABEL_END_GAP = 0.075
11
- const DOCUMENT_EXTENSION_START_GAP_PT = 3
12
- const DOCUMENT_EXTENSION_OVERSHOOT_PT = 4
13
- const DOCUMENT_TICK_HALF_LENGTH_PT = 4.5
14
- const DOCUMENT_LABEL_FONT_SIZE_PT = 8
15
- const DOCUMENT_LABEL_BASELINE_OFFSET_PT = 5
16
- const DOCUMENT_LABEL_END_GAP_PT = 3
17
- const LINE_STROKE_WIDTH_PX = 0.9
18
- const TICK_STROKE_WIDTH_PX = 1.35
19
- const PDF_LINE_STROKE_WIDTH_PT = 0.5
20
- const PDF_TICK_STROKE_WIDTH_PT = 0.75
21
- const SQRT_ONE_HALF = Math.SQRT1_2
22
-
23
- type FloorplanDimensionRenderMode = 'screen' | 'pdf'
24
-
25
- /**
26
- * Click-to-type dimensions (WS3).
27
- *
28
- * On screen the label plate carries an invisible hit rect plus the data the
29
- * editing layer needs to drive geometry from a typed value. The layer picks
30
- * it up by DOM delegation (`FloorplanDimensionEditOverlay` below), so every
31
- * producer of `dimension` / `dimension-string` geometry — wall automatic
32
- * dimensions, contextual dimensions, manual construction-dimension nodes —
33
- * becomes editable without each one wiring up a callback.
34
- *
35
- * Witness points are the measured feature points in level-plan metres; the
36
- * value is the length the label actually reads (dimension-line points when
37
- * the producer supplied them, witness points otherwise).
38
- */
39
- export const FLOORPLAN_DIMENSION_HIT_ATTRIBUTE = 'data-floorplan-dimension-hit'
40
-
41
- type DimensionGeometry = Extract<FloorplanGeometry, { kind: 'dimension' }>
42
- type DimensionStringGeometry = Extract<FloorplanGeometry, { kind: 'dimension-string' }>
43
- type DimensionTerminator = NonNullable<DimensionGeometry['terminator']>
44
-
45
- export type ArchitecturalDimensionLayout = {
46
- dimensionStart: FloorplanPoint
47
- dimensionEnd: FloorplanPoint
48
- dimensionLineStart: FloorplanPoint
49
- dimensionLineEnd: FloorplanPoint
50
- extensionStart: FloorplanPoint
51
- extensionEnd: FloorplanPoint
52
- extensionStartTip: FloorplanPoint
53
- extensionEndTip: FloorplanPoint
54
- tickHalfVector: FloorplanPoint
55
- labelPoint: FloorplanPoint
56
- labelAngleDeg: number
57
- labelPlacement: 'inside' | 'outside-end'
58
- outsideStartLabelPoint?: FloorplanPoint
59
- outsideStartDimensionLineStart?: FloorplanPoint
60
- }
61
-
62
- export function floorplanDimensionAnnotationPriority(offsetDistance: number): number {
63
- return 100 + Math.round(Math.abs(offsetDistance) * 100)
64
- }
65
-
66
- export function computeArchitecturalDimensionLayout(
67
- geometry: DimensionGeometry,
68
- sceneRotationDeg: number,
69
- annotationUnitsPerPoint?: number,
70
- ): ArchitecturalDimensionLayout | null {
71
- const extensionStartGap = annotationUnitsPerPoint
72
- ? DOCUMENT_EXTENSION_START_GAP_PT * annotationUnitsPerPoint
73
- : (geometry.extensionStartGap ?? EXTENSION_START_GAP)
74
- const extensionOvershoot = annotationUnitsPerPoint
75
- ? DOCUMENT_EXTENSION_OVERSHOOT_PT * annotationUnitsPerPoint
76
- : geometry.extensionOvershoot
77
- const tickHalfLength = annotationUnitsPerPoint
78
- ? DOCUMENT_TICK_HALF_LENGTH_PT * annotationUnitsPerPoint
79
- : TICK_HALF_LENGTH
80
- const labelFontSize = annotationUnitsPerPoint
81
- ? DOCUMENT_LABEL_FONT_SIZE_PT * annotationUnitsPerPoint
82
- : LABEL_FONT_SIZE
83
- const labelEndGap = annotationUnitsPerPoint
84
- ? DOCUMENT_LABEL_END_GAP_PT * annotationUnitsPerPoint
85
- : LABEL_END_GAP
86
- const offsetX = geometry.offsetNormal[0] * geometry.offsetDistance
87
- const offsetY = geometry.offsetNormal[1] * geometry.offsetDistance
88
- const dimensionStart: FloorplanPoint = geometry.dimensionStart ?? [
89
- geometry.start[0] + offsetX,
90
- geometry.start[1] + offsetY,
91
- ]
92
- const dimensionEnd: FloorplanPoint = geometry.dimensionEnd ?? [
93
- geometry.end[0] + offsetX,
94
- geometry.end[1] + offsetY,
95
- ]
96
- const dx = dimensionEnd[0] - dimensionStart[0]
97
- const dy = dimensionEnd[1] - dimensionStart[1]
98
- const length = Math.hypot(dx, dy)
99
- if (length < 1e-6) return null
100
-
101
- const dirX = dx / length
102
- const dirY = dy / length
103
- const startOffsetDistance = dot(subtract(dimensionStart, geometry.start), geometry.offsetNormal)
104
- const endOffsetDistance = dot(subtract(dimensionEnd, geometry.end), geometry.offsetNormal)
105
- const startExtensionGap = Math.min(extensionStartGap, Math.max(0, startOffsetDistance - 0.01))
106
- const endExtensionGap = Math.min(extensionStartGap, Math.max(0, endOffsetDistance - 0.01))
107
-
108
- // A 45-degree architectural slash. Every terminator within one string uses
109
- // this same vector instead of rotating independently around its endpoint.
110
- const tickHalfVector: FloorplanPoint = [
111
- (dirX + dirY) * tickHalfLength * SQRT_ONE_HALF,
112
- (dirY - dirX) * tickHalfLength * SQRT_ONE_HALF,
113
- ]
114
- const labelWidth = Math.max(
115
- labelFontSize,
116
- geometry.text.length * labelFontSize * LABEL_CHARACTER_WIDTH_RATIO,
117
- )
118
- const labelPlacement =
119
- length >= labelWidth + labelEndGap * 2 ? ('inside' as const) : ('outside-end' as const)
120
- const direction: FloorplanPoint = [dirX, dirY]
121
- const labelPoint =
122
- labelPlacement === 'inside'
123
- ? ([
124
- (dimensionStart[0] + dimensionEnd[0]) / 2,
125
- (dimensionStart[1] + dimensionEnd[1]) / 2,
126
- ] as FloorplanPoint)
127
- : addScaled(dimensionEnd, direction, labelEndGap + labelWidth / 2)
128
- const dimensionLineEnd =
129
- labelPlacement === 'inside'
130
- ? dimensionEnd
131
- : addScaled(dimensionEnd, direction, labelEndGap * 2 + labelWidth)
132
- const outsideStartLabelPoint =
133
- labelPlacement === 'outside-end'
134
- ? addScaled(dimensionStart, direction, -(labelEndGap + labelWidth / 2))
135
- : undefined
136
- const outsideStartDimensionLineStart =
137
- labelPlacement === 'outside-end'
138
- ? addScaled(dimensionStart, direction, -(labelEndGap * 2 + labelWidth))
139
- : undefined
140
-
141
- return {
142
- dimensionStart,
143
- dimensionEnd,
144
- dimensionLineStart: dimensionStart,
145
- dimensionLineEnd,
146
- extensionStart: addScaled(geometry.start, geometry.offsetNormal, startExtensionGap),
147
- extensionEnd: addScaled(geometry.end, geometry.offsetNormal, endExtensionGap),
148
- extensionStartTip: addScaled(dimensionStart, geometry.offsetNormal, extensionOvershoot),
149
- extensionEndTip: addScaled(dimensionEnd, geometry.offsetNormal, extensionOvershoot),
150
- tickHalfVector,
151
- labelPoint,
152
- labelAngleDeg: resolveFloorplanLabelAngle(Math.atan2(dy, dx), sceneRotationDeg),
153
- labelPlacement,
154
- outsideStartLabelPoint,
155
- outsideStartDimensionLineStart,
156
- }
157
- }
158
-
159
- function subtract(left: FloorplanPoint, right: FloorplanPoint): FloorplanPoint {
160
- return [left[0] - right[0], left[1] - right[1]]
161
- }
162
-
163
- function dot(left: FloorplanPoint, right: FloorplanPoint): number {
164
- return left[0] * right[0] + left[1] * right[1]
165
- }
166
-
167
- function addScaled(
168
- point: FloorplanPoint,
169
- direction: FloorplanPoint,
170
- distance: number,
171
- ): FloorplanPoint {
172
- return [point[0] + direction[0] * distance, point[1] + direction[1] * distance]
173
- }
174
-
175
- export function FloorplanDimensionRenderer({
176
- geometry,
177
- sceneRotationDeg = 0,
178
- stroke = geometry.stroke ?? '#334155',
179
- annotationUnitsPerPoint,
180
- renderMode = 'screen',
181
- onSelect,
182
- }: {
183
- geometry: DimensionGeometry
184
- sceneRotationDeg?: number
185
- stroke?: string
186
- annotationUnitsPerPoint?: number
187
- renderMode?: FloorplanDimensionRenderMode
188
- onSelect?: () => void
189
- }): React.ReactElement | null {
190
- const layout = computeArchitecturalDimensionLayout(
191
- geometry,
192
- sceneRotationDeg,
193
- annotationUnitsPerPoint,
194
- )
195
- if (!layout) return null
196
-
197
- const labelFontSize = annotationUnitsPerPoint
198
- ? DOCUMENT_LABEL_FONT_SIZE_PT * annotationUnitsPerPoint
199
- : LABEL_FONT_SIZE
200
- const labelBaselineOffset = annotationUnitsPerPoint
201
- ? DOCUMENT_LABEL_BASELINE_OFFSET_PT * annotationUnitsPerPoint
202
- : LABEL_BASELINE_OFFSET
203
- const labelY = geometry.textPosition === 'centered' ? labelFontSize * 0.35 : -labelBaselineOffset
204
-
205
- const lineProps = {
206
- stroke,
207
- strokeLinecap: 'butt' as const,
208
- strokeWidth: renderMode === 'pdf' ? PDF_LINE_STROKE_WIDTH_PT : LINE_STROKE_WIDTH_PX,
209
- vectorEffect: 'non-scaling-stroke' as const,
210
- }
211
- const tickStrokeWidth = renderMode === 'pdf' ? PDF_TICK_STROKE_WIDTH_PT : TICK_STROKE_WIDTH_PX
212
- const terminator = geometry.terminator ?? 'architectural-tick'
213
- const labelTransform = `translate(${layout.labelPoint[0]} ${layout.labelPoint[1]}) rotate(${layout.labelAngleDeg})`
214
- const outsideStartLocalShift = layout.outsideStartLabelPoint
215
- ? rotateVector(
216
- subtract(layout.outsideStartLabelPoint, layout.labelPoint),
217
- (-layout.labelAngleDeg * Math.PI) / 180,
218
- )
219
- : undefined
220
-
221
- return (
222
- <g
223
- data-floorplan-dimension=""
224
- onClick={
225
- onSelect
226
- ? (event) => {
227
- event.stopPropagation()
228
- onSelect()
229
- }
230
- : undefined
231
- }
232
- pointerEvents={onSelect ? 'auto' : 'none'}
233
- >
234
- <line
235
- {...lineProps}
236
- x1={layout.extensionStart[0]}
237
- x2={layout.extensionStartTip[0]}
238
- y1={layout.extensionStart[1]}
239
- y2={layout.extensionStartTip[1]}
240
- />
241
- <line
242
- {...lineProps}
243
- x1={layout.extensionEnd[0]}
244
- x2={layout.extensionEndTip[0]}
245
- y1={layout.extensionEnd[1]}
246
- y2={layout.extensionEndTip[1]}
247
- />
248
- <line
249
- {...lineProps}
250
- data-floorplan-dimension-default-x1={layout.dimensionLineStart[0]}
251
- data-floorplan-dimension-default-x2={layout.dimensionLineEnd[0]}
252
- data-floorplan-dimension-default-y1={layout.dimensionLineStart[1]}
253
- data-floorplan-dimension-default-y2={layout.dimensionLineEnd[1]}
254
- data-floorplan-dimension-line=""
255
- data-floorplan-dimension-outside-start-x1={layout.outsideStartDimensionLineStart?.[0]}
256
- data-floorplan-dimension-outside-start-x2={layout.dimensionEnd[0]}
257
- data-floorplan-dimension-outside-start-y1={layout.outsideStartDimensionLineStart?.[1]}
258
- data-floorplan-dimension-outside-start-y2={layout.dimensionEnd[1]}
259
- x1={layout.dimensionLineStart[0]}
260
- x2={layout.dimensionLineEnd[0]}
261
- y1={layout.dimensionLineStart[1]}
262
- y2={layout.dimensionLineEnd[1]}
263
- />
264
- {renderTerminator(
265
- terminator,
266
- layout.dimensionStart,
267
- layout.dimensionEnd,
268
- layout,
269
- lineProps,
270
- tickStrokeWidth,
271
- )}
272
- {renderTerminator(
273
- terminator,
274
- layout.dimensionEnd,
275
- layout.dimensionStart,
276
- layout,
277
- lineProps,
278
- tickStrokeWidth,
279
- )}
280
- {layout.labelPlacement === 'outside-end' && annotationUnitsPerPoint !== undefined ? (
281
- <line
282
- {...lineProps}
283
- data-floorplan-dimension-leader=""
284
- visibility="hidden"
285
- x1={layout.dimensionEnd[0]}
286
- x2={layout.labelPoint[0]}
287
- y1={layout.dimensionEnd[1]}
288
- y2={layout.labelPoint[1]}
289
- />
290
- ) : null}
291
- <g
292
- data-floorplan-annotation-angle-radians={Math.atan2(
293
- geometry.end[1] - geometry.start[1],
294
- geometry.end[0] - geometry.start[0],
295
- )}
296
- data-floorplan-annotation-default-transform={labelTransform}
297
- data-floorplan-annotation-label=""
298
- data-floorplan-annotation-priority={floorplanDimensionAnnotationPriority(
299
- geometry.offsetDistance,
300
- )}
301
- data-floorplan-annotation-transform-before-rotation={`translate(${layout.labelPoint[0]} ${layout.labelPoint[1]})`}
302
- data-floorplan-dimension-label-placement={layout.labelPlacement}
303
- data-floorplan-dimension-outside-start-local-x={outsideStartLocalShift?.[0]}
304
- data-floorplan-dimension-outside-start-local-y={outsideStartLocalShift?.[1]}
305
- data-floorplan-dimension-start-x={layout.dimensionStart[0]}
306
- data-floorplan-dimension-start-y={layout.dimensionStart[1]}
307
- data-floorplan-dimension-end-x={layout.dimensionEnd[0]}
308
- data-floorplan-dimension-end-y={layout.dimensionEnd[1]}
309
- data-floorplan-dimension-text={geometry.text}
310
- data-floorplan-dimension-value={dimensionLabelValue(layout)}
311
- data-floorplan-dimension-witness-start-x={geometry.start[0]}
312
- data-floorplan-dimension-witness-start-y={geometry.start[1]}
313
- data-floorplan-dimension-witness-end-x={geometry.end[0]}
314
- data-floorplan-dimension-witness-end-y={geometry.end[1]}
315
- transform={labelTransform}
316
- >
317
- <DimensionLabel
318
- fontSize={labelFontSize}
319
- // `overridden` is declared on the geometry union in
320
- // packages/core/src/registry/types.ts; the cast keeps this file
321
- // compiling against a core dist built before that field landed.
322
- overridden={(geometry as { overridden?: boolean }).overridden === true}
323
- renderMode={renderMode}
324
- stroke={stroke}
325
- text={geometry.text}
326
- y={labelY}
327
- />
328
- </g>
329
- </g>
330
- )
331
- }
332
-
333
- export function FloorplanDimensionStringRenderer({
334
- geometry,
335
- sceneRotationDeg = 0,
336
- stroke = geometry.stroke ?? '#334155',
337
- annotationUnitsPerPoint,
338
- renderMode = 'screen',
339
- }: {
340
- geometry: DimensionStringGeometry
341
- sceneRotationDeg?: number
342
- stroke?: string
343
- annotationUnitsPerPoint?: number
344
- renderMode?: FloorplanDimensionRenderMode
345
- }): React.ReactElement | null {
346
- const segmentLayouts = geometry.segments.flatMap((segment, index) => {
347
- const segmentGeometry: DimensionGeometry = {
348
- kind: 'dimension',
349
- start: segment.start,
350
- end: segment.end,
351
- dimensionStart: segment.dimensionStart,
352
- dimensionEnd: segment.dimensionEnd,
353
- offsetNormal: geometry.offsetNormal,
354
- offsetDistance: geometry.offsetDistance,
355
- extensionOvershoot: geometry.extensionOvershoot,
356
- extensionStartGap: geometry.extensionStartGap,
357
- terminator: geometry.terminator,
358
- textPosition: geometry.textPosition,
359
- text: segment.text,
360
- stroke: geometry.stroke,
361
- }
362
- const layout = computeArchitecturalDimensionLayout(
363
- segmentGeometry,
364
- sceneRotationDeg,
365
- annotationUnitsPerPoint,
366
- )
367
- return layout ? [{ index, layout, segment: segmentGeometry }] : []
368
- })
369
- if (segmentLayouts.length === 0) return null
370
-
371
- const labelFontSize = annotationUnitsPerPoint
372
- ? DOCUMENT_LABEL_FONT_SIZE_PT * annotationUnitsPerPoint
373
- : LABEL_FONT_SIZE
374
- const labelBaselineOffset = annotationUnitsPerPoint
375
- ? DOCUMENT_LABEL_BASELINE_OFFSET_PT * annotationUnitsPerPoint
376
- : LABEL_BASELINE_OFFSET
377
- const labelY = geometry.textPosition === 'centered' ? labelFontSize * 0.35 : -labelBaselineOffset
378
- const lineProps = {
379
- stroke,
380
- strokeLinecap: 'butt' as const,
381
- strokeWidth: renderMode === 'pdf' ? PDF_LINE_STROKE_WIDTH_PT : LINE_STROKE_WIDTH_PX,
382
- vectorEffect: 'non-scaling-stroke' as const,
383
- }
384
- const tickStrokeWidth = renderMode === 'pdf' ? PDF_TICK_STROKE_WIDTH_PT : TICK_STROKE_WIDTH_PX
385
-
386
- const extensionLines = new Map<string, { start: FloorplanPoint; tip: FloorplanPoint }>()
387
- const ticks = new Map<
388
- string,
389
- { point: FloorplanPoint; toward: FloorplanPoint; tickHalfVector: FloorplanPoint }
390
- >()
391
- for (const { layout } of segmentLayouts) {
392
- extensionLines.set(pointKey(layout.dimensionStart), {
393
- start: layout.extensionStart,
394
- tip: layout.extensionStartTip,
395
- })
396
- extensionLines.set(pointKey(layout.dimensionEnd), {
397
- start: layout.extensionEnd,
398
- tip: layout.extensionEndTip,
399
- })
400
- ticks.set(pointKey(layout.dimensionStart), {
401
- point: layout.dimensionStart,
402
- toward: layout.dimensionEnd,
403
- tickHalfVector: layout.tickHalfVector,
404
- })
405
- ticks.set(pointKey(layout.dimensionEnd), {
406
- point: layout.dimensionEnd,
407
- toward: layout.dimensionStart,
408
- tickHalfVector: layout.tickHalfVector,
409
- })
410
- }
411
- const terminator = geometry.terminator ?? 'architectural-tick'
412
-
413
- return (
414
- <g data-floorplan-dimension-string="" pointerEvents="none">
415
- {[...extensionLines.values()].map((line, index) => (
416
- <line
417
- {...lineProps}
418
- key={`extension-${index}`}
419
- x1={line.start[0]}
420
- x2={line.tip[0]}
421
- y1={line.start[1]}
422
- y2={line.tip[1]}
423
- />
424
- ))}
425
- {segmentLayouts.map(({ index, layout }) => (
426
- <line
427
- {...lineProps}
428
- data-floorplan-dimension-default-x1={layout.dimensionLineStart[0]}
429
- data-floorplan-dimension-default-x2={layout.dimensionLineEnd[0]}
430
- data-floorplan-dimension-default-y1={layout.dimensionLineStart[1]}
431
- data-floorplan-dimension-default-y2={layout.dimensionLineEnd[1]}
432
- data-floorplan-dimension-line=""
433
- data-floorplan-dimension-outside-start-x1={layout.outsideStartDimensionLineStart?.[0]}
434
- data-floorplan-dimension-outside-start-x2={layout.dimensionEnd[0]}
435
- data-floorplan-dimension-outside-start-y1={layout.outsideStartDimensionLineStart?.[1]}
436
- data-floorplan-dimension-outside-start-y2={layout.dimensionEnd[1]}
437
- key={`dimension-line-${index}`}
438
- x1={layout.dimensionLineStart[0]}
439
- x2={layout.dimensionLineEnd[0]}
440
- y1={layout.dimensionLineStart[1]}
441
- y2={layout.dimensionLineEnd[1]}
442
- />
443
- ))}
444
- {[...ticks.values()].map(({ point, toward, tickHalfVector }, index) =>
445
- renderTerminator(
446
- terminator,
447
- point,
448
- toward,
449
- { tickHalfVector },
450
- lineProps,
451
- tickStrokeWidth,
452
- `tick-${index}`,
453
- ),
454
- )}
455
- {segmentLayouts.map(({ index, layout, segment }) => {
456
- const labelTransform = `translate(${layout.labelPoint[0]} ${layout.labelPoint[1]}) rotate(${layout.labelAngleDeg})`
457
- const outsideStartLocalShift = layout.outsideStartLabelPoint
458
- ? rotateVector(
459
- subtract(layout.outsideStartLabelPoint, layout.labelPoint),
460
- (-layout.labelAngleDeg * Math.PI) / 180,
461
- )
462
- : undefined
463
- return (
464
- <g key={`label-${index}`}>
465
- {layout.labelPlacement === 'outside-end' && annotationUnitsPerPoint !== undefined ? (
466
- <line
467
- {...lineProps}
468
- data-floorplan-dimension-leader=""
469
- visibility="hidden"
470
- x1={layout.dimensionEnd[0]}
471
- x2={layout.labelPoint[0]}
472
- y1={layout.dimensionEnd[1]}
473
- y2={layout.labelPoint[1]}
474
- />
475
- ) : null}
476
- <g
477
- data-floorplan-annotation-angle-radians={Math.atan2(
478
- segment.end[1] - segment.start[1],
479
- segment.end[0] - segment.start[0],
480
- )}
481
- data-floorplan-annotation-default-transform={labelTransform}
482
- data-floorplan-annotation-label=""
483
- data-floorplan-annotation-priority={floorplanDimensionAnnotationPriority(
484
- geometry.offsetDistance,
485
- )}
486
- data-floorplan-annotation-transform-before-rotation={`translate(${layout.labelPoint[0]} ${layout.labelPoint[1]})`}
487
- data-floorplan-dimension-label-placement={layout.labelPlacement}
488
- data-floorplan-dimension-outside-start-local-x={outsideStartLocalShift?.[0]}
489
- data-floorplan-dimension-outside-start-local-y={outsideStartLocalShift?.[1]}
490
- data-floorplan-dimension-start-x={layout.dimensionStart[0]}
491
- data-floorplan-dimension-start-y={layout.dimensionStart[1]}
492
- data-floorplan-dimension-end-x={layout.dimensionEnd[0]}
493
- data-floorplan-dimension-end-y={layout.dimensionEnd[1]}
494
- data-floorplan-dimension-text={segment.text}
495
- data-floorplan-dimension-value={dimensionLabelValue(layout)}
496
- data-floorplan-dimension-witness-start-x={segment.start[0]}
497
- data-floorplan-dimension-witness-start-y={segment.start[1]}
498
- data-floorplan-dimension-witness-end-x={segment.end[0]}
499
- data-floorplan-dimension-witness-end-y={segment.end[1]}
500
- transform={labelTransform}
501
- >
502
- <DimensionLabel
503
- fontSize={labelFontSize}
504
- renderMode={renderMode}
505
- stroke={stroke}
506
- text={segment.text}
507
- y={labelY}
508
- />
509
- </g>
510
- </g>
511
- )
512
- })}
513
- </g>
514
- )
515
- }
516
-
517
- /** The length the label reads — the dimension-line span, offset cancels out. */
518
- function dimensionLabelValue(layout: ArchitecturalDimensionLayout): number {
519
- return Math.hypot(
520
- layout.dimensionEnd[0] - layout.dimensionStart[0],
521
- layout.dimensionEnd[1] - layout.dimensionStart[1],
522
- )
523
- }
524
-
525
- function rotateVector(vector: FloorplanPoint, radians: number): FloorplanPoint {
526
- const cosine = Math.cos(radians)
527
- const sine = Math.sin(radians)
528
- return [vector[0] * cosine - vector[1] * sine, vector[0] * sine + vector[1] * cosine]
529
- }
530
-
531
- function DimensionLabel({
532
- text,
533
- y,
534
- fontSize,
535
- stroke,
536
- renderMode,
537
- overridden = false,
538
- }: {
539
- text: string
540
- y: number
541
- fontSize: number
542
- stroke: string
543
- renderMode: FloorplanDimensionRenderMode
544
- /** Draws the "override" badge when the value could not drive geometry. */
545
- overridden?: boolean
546
- }) {
547
- const width = Math.max(fontSize, text.length * fontSize * LABEL_CHARACTER_WIDTH_RATIO)
548
- const plateWidth = width + fontSize * 0.5
549
- const plateHeight = fontSize * 1.2
550
- const plateY = y - fontSize * 0.82
551
- const editable = useDimensionEditAllowed()
552
-
553
- return (
554
- <>
555
- {renderMode === 'pdf' ? (
556
- <rect
557
- data-floorplan-dimension-label-plate=""
558
- fill="#ffffff"
559
- height={plateHeight}
560
- rx={fontSize * 0.12}
561
- ry={fontSize * 0.12}
562
- width={plateWidth}
563
- x={-plateWidth / 2}
564
- y={plateY}
565
- />
566
- ) : null}
567
- <text
568
- fill={stroke}
569
- fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace"
570
- fontSize={fontSize}
571
- fontWeight={500}
572
- paintOrder={renderMode === 'pdf' ? undefined : 'stroke'}
573
- stroke={renderMode === 'pdf' ? undefined : '#ffffff'}
574
- strokeLinejoin={renderMode === 'pdf' ? undefined : 'round'}
575
- strokeWidth={renderMode === 'pdf' ? undefined : 3}
576
- textAnchor="middle"
577
- vectorEffect={renderMode === 'pdf' ? undefined : 'non-scaling-stroke'}
578
- x={0}
579
- y={y}
580
- >
581
- {text}
582
- </text>
583
- {overridden && renderMode !== 'pdf' ? (
584
- <text
585
- fill={stroke}
586
- fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace"
587
- fontSize={fontSize * 0.62}
588
- opacity={0.75}
589
- paintOrder="stroke"
590
- stroke="#ffffff"
591
- strokeLinejoin="round"
592
- strokeWidth={2}
593
- textAnchor="middle"
594
- vectorEffect="non-scaling-stroke"
595
- x={0}
596
- y={y + fontSize * 0.86}
597
- >
598
- override
599
- </text>
600
- ) : null}
601
- {renderMode === 'pdf' || !editable ? null : (
602
- // Invisible click target on the plate. `pointer-events` is inherited
603
- // from the dimension group (`none`), so it is re-enabled here only.
604
- <rect
605
- {...{ [FLOORPLAN_DIMENSION_HIT_ATTRIBUTE]: '' }}
606
- cursor="text"
607
- fill="transparent"
608
- height={plateHeight}
609
- pointerEvents="all"
610
- width={plateWidth}
611
- x={-plateWidth / 2}
612
- y={plateY}
613
- />
614
- )}
615
- </>
616
- )
617
- }
618
-
619
- function renderTerminator(
620
- terminator: DimensionTerminator,
621
- point: FloorplanPoint,
622
- toward: FloorplanPoint,
623
- layout: Pick<ArchitecturalDimensionLayout, 'tickHalfVector'>,
624
- lineProps: {
625
- stroke: string
626
- strokeLinecap: 'butt'
627
- strokeWidth: number
628
- vectorEffect: 'non-scaling-stroke'
629
- },
630
- tickStrokeWidth: number,
631
- key?: string,
632
- ): React.ReactElement | null {
633
- const direction = normalized(point, toward)
634
- if (!direction) return null
635
- const tickHalfLength = Math.hypot(layout.tickHalfVector[0], layout.tickHalfVector[1])
636
- if (terminator === 'dot') {
637
- return (
638
- <circle
639
- fill={lineProps.stroke}
640
- key={key}
641
- r={tickHalfLength * 0.45}
642
- vectorEffect="non-scaling-stroke"
643
- cx={point[0]}
644
- cy={point[1]}
645
- />
646
- )
647
- }
648
- if (terminator === 'filled-arrow' || terminator === 'open-arrow') {
649
- const base = addScaled(point, direction, tickHalfLength * 1.7)
650
- const normal: FloorplanPoint = [-direction[1], direction[0]]
651
- const wing = tickHalfLength * 0.65
652
- const left: FloorplanPoint = [base[0] + normal[0] * wing, base[1] + normal[1] * wing]
653
- const right: FloorplanPoint = [base[0] - normal[0] * wing, base[1] - normal[1] * wing]
654
- if (terminator === 'filled-arrow') {
655
- return (
656
- <polygon
657
- fill={lineProps.stroke}
658
- key={key}
659
- points={`${point[0]},${point[1]} ${left[0]},${left[1]} ${right[0]},${right[1]}`}
660
- vectorEffect="non-scaling-stroke"
661
- />
662
- )
663
- }
664
- return (
665
- <g key={key}>
666
- <line
667
- {...lineProps}
668
- strokeWidth={tickStrokeWidth}
669
- x1={point[0]}
670
- x2={left[0]}
671
- y1={point[1]}
672
- y2={left[1]}
673
- />
674
- <line
675
- {...lineProps}
676
- strokeWidth={tickStrokeWidth}
677
- x1={point[0]}
678
- x2={right[0]}
679
- y1={point[1]}
680
- y2={right[1]}
681
- />
682
- </g>
683
- )
684
- }
685
- const [tickX, tickY] = layout.tickHalfVector
686
- return (
687
- <line
688
- {...lineProps}
689
- key={key}
690
- strokeWidth={tickStrokeWidth}
691
- x1={point[0] - tickX}
692
- x2={point[0] + tickX}
693
- y1={point[1] - tickY}
694
- y2={point[1] + tickY}
695
- />
696
- )
697
- }
698
-
699
- function normalized(start: FloorplanPoint, end: FloorplanPoint): FloorplanPoint | null {
700
- const dx = end[0] - start[0]
701
- const dy = end[1] - start[1]
702
- const magnitude = Math.hypot(dx, dy)
703
- return magnitude <= 1e-6 ? null : [dx / magnitude, dy / magnitude]
704
- }
705
-
706
- function pointKey(point: FloorplanPoint): string {
707
- return `${point[0].toFixed(6)},${point[1].toFixed(6)}`
708
- }