Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 96 additions & 42 deletions src/components/DimensionOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,48 @@ interface Props {

const SNAP_THRESHOLD_PX = 16
const SNAP_MARKER_SIZE = 5
const SNAP_SEARCH_RADIUS_PX = 220

const isCursorNearBoundingBoxInPx = (
point: { x: number; y: number } | null,
bounds: BoundingBox,
transform: Matrix,
radiusPx: number,
) => {
if (!point) return true

const cursorInScreen = applyToPoint(transform, point)
const boundsTopLeftInScreen = applyToPoint(transform, {
x: bounds.minX,
y: bounds.minY,
})
const boundsBottomRightInScreen = applyToPoint(transform, {
x: bounds.maxX,
y: bounds.maxY,
})

const minX = Math.min(boundsTopLeftInScreen.x, boundsBottomRightInScreen.x)
const maxX = Math.max(boundsTopLeftInScreen.x, boundsBottomRightInScreen.x)
const minY = Math.min(boundsTopLeftInScreen.y, boundsBottomRightInScreen.y)
const maxY = Math.max(boundsTopLeftInScreen.y, boundsBottomRightInScreen.y)

const closestX = Math.max(minX, Math.min(cursorInScreen.x, maxX))
const closestY = Math.max(minY, Math.min(cursorInScreen.y, maxY))

const distancePx = Math.hypot(
cursorInScreen.x - closestX,
cursorInScreen.y - closestY,
)

if (distancePx <= radiusPx) return true

return (
cursorInScreen.x >= minX - radiusPx &&
cursorInScreen.x <= maxX + radiusPx &&
cursorInScreen.y >= minY - radiusPx &&
cursorInScreen.y <= maxY + radiusPx
)
}

const shouldExcludePrimitiveFromSnapping = (primitive: Primitive) => {
if (primitive.pcb_drawing_type === "text") return true
Expand Down Expand Up @@ -76,61 +118,64 @@ export const DimensionOverlay = ({
const [dStart, setDStart] = useState({ x: 0, y: 0 })
// End of dimension tool line in real-world coordinates (not screen)
const [dEnd, setDEnd] = useState({ x: 0, y: 0 })
const [cursorRwPoint, setCursorRwPoint] = useState<{
x: number
y: number
} | null>(null)
const mousePosRef = useRef({ x: 0, y: 0 })
const containerRef = useRef<HTMLDivElement | null>(null)
const container = containerRef.current!
const containerBounds = container?.getBoundingClientRect()

const elementBoundingBoxes = useMemo(() => {
const boundingBoxes = new Map<object, BoundingBox>()
const elementSnapData = useMemo(() => {
const data = new Map<
object,
{
bounds?: BoundingBox
points: {
anchor: NinePointAnchor | string
point: { x: number; y: number }
element: object
}[]
}
>()

for (const primitive of primitives) {
if (!primitive._element) continue
if (shouldExcludePrimitiveFromSnapping(primitive)) continue
if (primitive.pcb_drawing_type === "pill") continue
if (
primitive.pcb_drawing_type === "rect" &&
primitive.ccw_rotation &&
primitive.ccw_rotation !== 0
)
continue
const bbox = getPrimitiveBoundingBox(primitive)
if (!bbox) continue

const existing = boundingBoxes.get(primitive._element as object)
boundingBoxes.set(
primitive._element as object,
mergeBoundingBoxes(existing ?? undefined, bbox),
)
}

return boundingBoxes
}, [primitives])

const primitiveSnappingPoints = useMemo(() => {
const snapPoints: {
anchor: NinePointAnchor | string
point: { x: number; y: number }
element: object
}[] = []

for (const primitive of primitives) {
if (!primitive._element) continue
if (shouldExcludePrimitiveFromSnapping(primitive)) continue
const element = primitive._element as object
const existing = data.get(element)
const entry = existing ?? { bounds: undefined, points: [] }

if (primitive.pcb_drawing_type !== "pill") {
if (
!(
primitive.pcb_drawing_type === "rect" &&
primitive.ccw_rotation &&
primitive.ccw_rotation !== 0
)
) {
const bbox = getPrimitiveBoundingBox(primitive)
if (bbox) {
entry.bounds = mergeBoundingBoxes(entry.bounds, bbox)
}
}
}

const primitivePoints = getPrimitiveSnapPoints(primitive)
if (primitivePoints.length === 0) continue

for (const snap of primitivePoints) {
snapPoints.push({
entry.points.push({
anchor: snap.anchor,
point: snap.point,
element: primitive._element as object,
element,
})
}

data.set(element, entry)
}

return snapPoints
return data
}, [primitives])

const snappingPoints = useMemo(() => {
Expand All @@ -140,8 +185,19 @@ export const DimensionOverlay = ({
element: object | null
}[] = []

elementBoundingBoxes.forEach((bounds, element) => {
elementSnapData.forEach((entry, element) => {
const bounds = entry.bounds
if (!bounds) return
if (
!isCursorNearBoundingBoxInPx(
cursorRwPoint,
bounds,
transform!,
SNAP_SEARCH_RADIUS_PX,
)
) {
return
}

const centerX = (bounds.minX + bounds.maxX) / 2
const centerY = (bounds.minY + bounds.maxY) / 2
Expand All @@ -168,20 +224,17 @@ export const DimensionOverlay = ({
element,
})
}
points.push(...entry.points)
})

for (const snap of primitiveSnappingPoints) {
points.push(snap)
}

points.push({
anchor: "origin",
point: { x: 0, y: 0 },
element: null,
})

return points
}, [elementBoundingBoxes, primitiveSnappingPoints])
}, [cursorRwPoint, elementSnapData, transform])

const snappingPointsWithScreen = useMemo(() => {
return snappingPoints.map((snap, index) => ({
Expand Down Expand Up @@ -340,6 +393,7 @@ export const DimensionOverlay = ({
const rwPoint = applyToPoint(inverse(transform!), { x, y })
mousePosRef.current.x = rwPoint.x
mousePosRef.current.y = rwPoint.y
setCursorRwPoint({ x: rwPoint.x, y: rwPoint.y })

if (dimensionToolStretching) {
const snap = findSnap(rwPoint)
Expand Down
Loading