Skip to content

Commit 52ee4a3

Browse files
authored
feat: Add diagonal measurement label to dimension tool (#92)
1 parent eb79a10 commit 52ee4a3

File tree

3 files changed

+173
-0
lines changed

3 files changed

+173
-0
lines changed

site/components/DimensionOverlay.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useCallback, useEffect, useRef, useState } from "react"
22
import { applyToPoint, identity, inverse } from "transformation-matrix"
33
import type { Matrix } from "transformation-matrix"
4+
import { useDiagonalLabel } from "../hooks/useDiagonalLabel"
45

56
interface Props {
67
transform?: Matrix
@@ -72,6 +73,15 @@ export const DimensionOverlay: React.FC<Props> = ({ children, transform }) => {
7273
arrowScreenBounds.width = arrowScreenBounds.right - arrowScreenBounds.left
7374
arrowScreenBounds.height = arrowScreenBounds.bottom - arrowScreenBounds.top
7475

76+
const diagonalLabel = useDiagonalLabel({
77+
dimensionStart: dStart,
78+
dimensionEnd: dEnd,
79+
screenDimensionStart: screenDStart,
80+
screenDimensionEnd: screenDEnd,
81+
flipX: arrowScreenBounds.flipX,
82+
flipY: arrowScreenBounds.flipY,
83+
})
84+
7585
return (
7686
<div
7787
ref={containerRef}
@@ -102,6 +112,24 @@ export const DimensionOverlay: React.FC<Props> = ({ children, transform }) => {
102112
{children}
103113
{dimensionToolVisible && (
104114
<>
115+
{diagonalLabel.show && (
116+
<div
117+
style={{
118+
position: "absolute",
119+
left: diagonalLabel.x,
120+
top: diagonalLabel.y,
121+
color: "red",
122+
mixBlendMode: "difference",
123+
pointerEvents: "none",
124+
fontSize: 12,
125+
fontFamily: "sans-serif",
126+
whiteSpace: "nowrap",
127+
zIndex: 30,
128+
}}
129+
>
130+
{diagonalLabel.distance.toFixed(2)}
131+
</div>
132+
)}
105133
<div
106134
style={{
107135
position: "absolute",

site/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 "../utils/calculateDiagonalLabel"
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)