@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,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
- }