Skip to content

Commit 2da0827

Browse files
authored
Feat: Add diagonal label for measurement (#477)
* Diagonal snap label support * Properhandling * Proper variable name * Set 15 min angle for diagonal from axis * add proper offset * Fix se and sw quadrants labels
1 parent b11d440 commit 2da0827

File tree

3 files changed

+175
-0
lines changed

3 files changed

+175
-0
lines changed

src/components/DimensionOverlay.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
mergeBoundingBoxes,
1010
} from "lib/util/get-primitive-bounding-box"
1111
import type { BoundingBox } from "lib/util/get-primitive-bounding-box"
12+
import { useDiagonalLabel } from "hooks/useDiagonalLabel"
1213

1314
interface Props {
1415
transform?: Matrix
@@ -269,6 +270,15 @@ export const DimensionOverlay = ({
269270
arrowScreenBounds.width = arrowScreenBounds.right - arrowScreenBounds.left
270271
arrowScreenBounds.height = arrowScreenBounds.bottom - arrowScreenBounds.top
271272

273+
const diagonalLabel = useDiagonalLabel({
274+
dimensionStart: dStart,
275+
dimensionEnd: dEnd,
276+
screenDimensionStart: screenDStart,
277+
screenDimensionEnd: screenDEnd,
278+
flipX: arrowScreenBounds.flipX,
279+
flipY: arrowScreenBounds.flipY,
280+
})
281+
272282
return (
273283
<div
274284
ref={containerRef}
@@ -325,6 +335,25 @@ export const DimensionOverlay = ({
325335
{children}
326336
{dimensionToolVisible && (
327337
<>
338+
{diagonalLabel.show && (
339+
<div
340+
style={{
341+
position: "absolute",
342+
left: diagonalLabel.x,
343+
top: diagonalLabel.y,
344+
color: "red",
345+
mixBlendMode: "difference",
346+
pointerEvents: "none",
347+
fontSize: 12,
348+
fontFamily: "sans-serif",
349+
whiteSpace: "nowrap",
350+
zIndex: zIndexMap.dimensionOverlay,
351+
}}
352+
>
353+
{diagonalLabel.distance.toFixed(2)}
354+
</div>
355+
)}
356+
328357
<div
329358
style={{
330359
position: "absolute",
@@ -343,6 +372,7 @@ export const DimensionOverlay = ({
343372
>
344373
{Math.abs(dStart.x - dEnd.x).toFixed(2)}
345374
</div>
375+
346376
<div
347377
style={{
348378
position: "absolute",

src/hooks/useDiagonalLabel.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { useMemo } from "react"
2+
import {
3+
calculateDiagonalLabel,
4+
type DiagonalLabelResult,
5+
} from "lib/util/calculate-diagonal-label"
6+
7+
export interface UseDiagonalLabelParams {
8+
dimensionStart: { x: number; y: number }
9+
dimensionEnd: { x: number; y: number }
10+
screenDimensionStart: { x: number; y: number }
11+
screenDimensionEnd: { x: number; y: number }
12+
flipX: boolean
13+
flipY: boolean
14+
}
15+
16+
export function useDiagonalLabel(
17+
params: UseDiagonalLabelParams,
18+
): DiagonalLabelResult {
19+
const {
20+
dimensionStart,
21+
dimensionEnd,
22+
screenDimensionStart,
23+
screenDimensionEnd,
24+
flipX,
25+
flipY,
26+
} = params
27+
28+
return useMemo(
29+
() =>
30+
calculateDiagonalLabel({
31+
dimensionStart,
32+
dimensionEnd,
33+
screenDimensionStart,
34+
screenDimensionEnd,
35+
flipX,
36+
flipY,
37+
}),
38+
[
39+
dimensionStart,
40+
dimensionEnd,
41+
screenDimensionStart,
42+
screenDimensionEnd,
43+
flipX,
44+
flipY,
45+
],
46+
)
47+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
export interface DiagonalLabelParams {
2+
dimensionStart: { x: number; y: number }
3+
dimensionEnd: { x: number; y: number }
4+
screenDimensionStart: { x: number; y: number }
5+
screenDimensionEnd: { x: number; y: number }
6+
flipX: boolean
7+
flipY: boolean
8+
}
9+
10+
export interface DiagonalLabelResult {
11+
distance: number
12+
screenDistance: number
13+
x: number
14+
y: number
15+
show: boolean
16+
}
17+
18+
export function calculateDiagonalLabel(
19+
params: DiagonalLabelParams,
20+
): DiagonalLabelResult {
21+
const {
22+
dimensionStart,
23+
dimensionEnd,
24+
screenDimensionStart,
25+
screenDimensionEnd,
26+
flipX,
27+
flipY,
28+
} = params
29+
30+
// Calculate dimension distance in world coordinates
31+
const deltaX = dimensionEnd.x - dimensionStart.x
32+
const deltaY = dimensionEnd.y - dimensionStart.y
33+
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY)
34+
35+
// Calculate screen distance and angle
36+
const screenDeltaX = screenDimensionEnd.x - screenDimensionStart.x
37+
const screenDeltaY = screenDimensionEnd.y - screenDimensionStart.y
38+
const screenDistance = Math.sqrt(
39+
screenDeltaX * screenDeltaX + screenDeltaY * screenDeltaY,
40+
)
41+
42+
// Calculate angle of the measurement line
43+
const angle = Math.atan2(screenDeltaY, screenDeltaX) * (180 / Math.PI)
44+
45+
const normalizedAngle = Math.abs(angle) % 90
46+
const angleFromAxis = Math.min(normalizedAngle, 90 - normalizedAngle)
47+
const isDiagonal = angleFromAxis > 15
48+
49+
// Find midpoint of the diagonal line
50+
const midX = (screenDimensionStart.x + screenDimensionEnd.x) / 2
51+
const midY = (screenDimensionStart.y + screenDimensionEnd.y) / 2
52+
53+
// Offset perpendicular to the line, always pointing away from the inner triangle
54+
const offsetDistance = 15
55+
const perpendicularAngle = angle + 90
56+
57+
let offsetX = Math.cos((perpendicularAngle * Math.PI) / 180) * offsetDistance
58+
let offsetY = Math.sin((perpendicularAngle * Math.PI) / 180) * offsetDistance
59+
60+
const isNE = screenDeltaX > 0 && screenDeltaY < 0
61+
const isNW = screenDeltaX < 0 && screenDeltaY < 0
62+
const isSE = screenDeltaX > 0 && screenDeltaY > 0
63+
const isSW = screenDeltaX < 0 && screenDeltaY > 0
64+
65+
if (flipX !== flipY && !isNE) {
66+
offsetX = -offsetX
67+
offsetY = -offsetY
68+
}
69+
70+
if (isNE) {
71+
const lessOffset = -45
72+
offsetX += Math.cos((perpendicularAngle * Math.PI) / 180) * lessOffset
73+
offsetY += Math.sin((perpendicularAngle * Math.PI) / 180) * lessOffset
74+
}
75+
76+
if (isSE) {
77+
const seAdjust = -10
78+
offsetX += Math.cos((perpendicularAngle * Math.PI) / 180) + seAdjust * 2
79+
offsetY += Math.sin((perpendicularAngle * Math.PI) / 180) + seAdjust
80+
}
81+
82+
if (isSW) {
83+
const reduceOffset = 10
84+
offsetX += Math.cos((perpendicularAngle * Math.PI) / 180) * reduceOffset
85+
offsetY += Math.sin((perpendicularAngle * Math.PI) / 180) * reduceOffset
86+
}
87+
88+
const x = midX + offsetX
89+
const y = midY + offsetY
90+
91+
return {
92+
distance,
93+
screenDistance,
94+
x,
95+
y,
96+
show: distance > 0.01 && screenDistance > 30 && isDiagonal,
97+
}
98+
}

0 commit comments

Comments
 (0)