diff --git a/src/components/DimensionOverlay.tsx b/src/components/DimensionOverlay.tsx index de7a55d..e990ee7 100644 --- a/src/components/DimensionOverlay.tsx +++ b/src/components/DimensionOverlay.tsx @@ -10,6 +10,7 @@ import { } from "lib/util/get-primitive-bounding-box" import type { BoundingBox } from "lib/util/get-primitive-bounding-box" import { useDiagonalLabel } from "hooks/useDiagonalLabel" +import { getPrimitiveSnapPoints } from "lib/util/get-primitive-snap-points" interface Props { transform?: Matrix @@ -83,6 +84,13 @@ export const DimensionOverlay = ({ 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 @@ -96,9 +104,35 @@ export const DimensionOverlay = ({ 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 primitivePoints = getPrimitiveSnapPoints(primitive) + if (primitivePoints.length === 0) continue + + for (const snap of primitivePoints) { + snapPoints.push({ + anchor: snap.anchor, + point: snap.point, + element: primitive._element as object, + }) + } + } + + return snapPoints + }, [primitives]) + const snappingPoints = useMemo(() => { const points: { - anchor: NinePointAnchor | "origin" + anchor: NinePointAnchor | "origin" | string point: { x: number; y: number } element: object | null }[] = [] @@ -133,6 +167,10 @@ export const DimensionOverlay = ({ } }) + for (const snap of primitiveSnappingPoints) { + points.push(snap) + } + points.push({ anchor: "origin", point: { x: 0, y: 0 }, @@ -140,7 +178,7 @@ export const DimensionOverlay = ({ }) return points - }, [elementBoundingBoxes]) + }, [elementBoundingBoxes, primitiveSnappingPoints]) const snappingPointsWithScreen = useMemo(() => { return snappingPoints.map((snap, index) => ({ diff --git a/src/lib/util/get-primitive-snap-points.ts b/src/lib/util/get-primitive-snap-points.ts new file mode 100644 index 0000000..e8d9c7c --- /dev/null +++ b/src/lib/util/get-primitive-snap-points.ts @@ -0,0 +1,151 @@ +import type { NinePointAnchor } from "circuit-json" +import type { Primitive } from "../types" + +export interface PrimitiveSnapPoint { + anchor: NinePointAnchor | string + point: { x: number; y: number } +} + +const rotatePoint = ( + point: { x: number; y: number }, + center: { x: number; y: number }, + rotationDeg: number, +) => { + const radians = (rotationDeg * Math.PI) / 180 + const cos = Math.cos(radians) + const sin = Math.sin(radians) + + const translatedX = point.x - center.x + const translatedY = point.y - center.y + + const rotatedX = translatedX * cos - translatedY * sin + const rotatedY = translatedX * sin + translatedY * cos + + return { + x: rotatedX + center.x, + y: rotatedY + center.y, + } +} + +const getNinePointAnchors = ( + center: { x: number; y: number }, + halfWidth: number, + halfHeight: number, + rotationDeg: number, +): PrimitiveSnapPoint[] => { + const basePoints: Record = { + top_left: { x: center.x - halfWidth, y: center.y - halfHeight }, + top_center: { x: center.x, y: center.y - halfHeight }, + top_right: { x: center.x + halfWidth, y: center.y - halfHeight }, + center_left: { x: center.x - halfWidth, y: center.y }, + center: { x: center.x, y: center.y }, + center_right: { x: center.x + halfWidth, y: center.y }, + bottom_left: { x: center.x - halfWidth, y: center.y + halfHeight }, + bottom_center: { x: center.x, y: center.y + halfHeight }, + bottom_right: { x: center.x + halfWidth, y: center.y + halfHeight }, + } + + if (rotationDeg === 0) { + return Object.entries(basePoints).map(([anchor, point]) => ({ + anchor: anchor as NinePointAnchor, + point, + })) + } + + return Object.entries(basePoints).map(([anchor, point]) => ({ + anchor: anchor as NinePointAnchor, + point: rotatePoint(point, center, rotationDeg), + })) +} + +export const getPrimitiveSnapPoints = ( + primitive: Primitive, +): PrimitiveSnapPoint[] => { + switch (primitive.pcb_drawing_type) { + case "rect": { + const rotation = primitive.ccw_rotation ?? 0 + return getNinePointAnchors( + { x: primitive.x, y: primitive.y }, + primitive.w / 2, + primitive.h / 2, + rotation, + ) + } + case "pill": { + const rotation = primitive.ccw_rotation ?? 0 + return getNinePointAnchors( + { x: primitive.x, y: primitive.y }, + primitive.w / 2, + primitive.h / 2, + rotation, + ) + } + case "circle": { + return [ + { anchor: "circle_center", point: { x: primitive.x, y: primitive.y } }, + { + anchor: "circle_right", + point: { x: primitive.x + primitive.r, y: primitive.y }, + }, + { + anchor: "circle_left", + point: { x: primitive.x - primitive.r, y: primitive.y }, + }, + { + anchor: "circle_top", + point: { x: primitive.x, y: primitive.y - primitive.r }, + }, + { + anchor: "circle_bottom", + point: { x: primitive.x, y: primitive.y + primitive.r }, + }, + ] + } + case "oval": { + return [ + { anchor: "oval_center", point: { x: primitive.x, y: primitive.y } }, + { + anchor: "oval_right", + point: { x: primitive.x + primitive.rX, y: primitive.y }, + }, + { + anchor: "oval_left", + point: { x: primitive.x - primitive.rX, y: primitive.y }, + }, + { + anchor: "oval_top", + point: { x: primitive.x, y: primitive.y - primitive.rY }, + }, + { + anchor: "oval_bottom", + point: { x: primitive.x, y: primitive.y + primitive.rY }, + }, + ] + } + case "line": { + const midPoint = { + x: (primitive.x1 + primitive.x2) / 2, + y: (primitive.y1 + primitive.y2) / 2, + } + return [ + { anchor: "line_start", point: { x: primitive.x1, y: primitive.y1 } }, + { anchor: "line_mid", point: midPoint }, + { anchor: "line_end", point: { x: primitive.x2, y: primitive.y2 } }, + ] + } + case "polygon": { + return primitive.points.map((point, index) => ({ + anchor: `polygon_vertex_${index}`, + point, + })) + } + case "polygon_with_arcs": { + return primitive.brep_shape.outer_ring.vertices.map((vertex, index) => ({ + anchor: `polygon_with_arcs_vertex_${index}`, + point: { x: vertex.x, y: vertex.y }, + })) + } + default: + return [] + } +}