From 11eab05c04c86e7fd000e39bb0d6758d23157ab2 Mon Sep 17 00:00:00 2001 From: abdalrouf-AAA <236259618+technologyet31-create@users.noreply.github.com> Date: Sun, 12 Jul 2026 23:13:58 +0200 Subject: [PATCH] Fix isPointInPad treating oval/pill plated holes as rectangles isPointInPad tested oval and pill plated holes against their bounding rectangle, so a point in the empty corner counted as on-pad and suppressed a real missing-connection error. Use an ellipse test for ovals and a stadium test for pills. Co-authored-by: abdalrouf-AAA <236259618+technologyet31-create@users.noreply.github.com> --- .../is-point-in-pad.ts | 25 ++++++++++++++--- tests/lib/is-point-in-pad.test.ts | 27 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 tests/lib/is-point-in-pad.test.ts diff --git a/lib/check-traces-are-contiguous/is-point-in-pad.ts b/lib/check-traces-are-contiguous/is-point-in-pad.ts index e786e83..99291bb 100644 --- a/lib/check-traces-are-contiguous/is-point-in-pad.ts +++ b/lib/check-traces-are-contiguous/is-point-in-pad.ts @@ -116,11 +116,28 @@ export function isPointInPad( return getDistanceBetweenPoints(point, pad) <= pad.outer_diameter / 2 } - if (pad.shape === "oval" || pad.shape === "pill") { - return ( - Math.abs(point.x - pad.x) <= pad.outer_width / 2 && - Math.abs(point.y - pad.y) <= pad.outer_height / 2 + if (pad.shape === "oval") { + const nx = (point.x - pad.x) / (pad.outer_width / 2) + const ny = (point.y - pad.y) / (pad.outer_height / 2) + return nx * nx + ny * ny <= 1 + } + + if (pad.shape === "pill") { + const halfWidth = pad.outer_width / 2 + const halfHeight = pad.outer_height / 2 + const radius = Math.min(halfWidth, halfHeight) + // clamp the point to the pill's central segment, then distance-to-that <= radius + const dx = Math.max( + -(halfWidth - radius), + Math.min(halfWidth - radius, point.x - pad.x), + ) + const dy = Math.max( + -(halfHeight - radius), + Math.min(halfHeight - radius, point.y - pad.y), ) + const cx = pad.x + dx + const cy = pad.y + dy + return Math.hypot(point.x - cx, point.y - cy) <= radius } if (pad.shape === "circular_hole_with_rect_pad") { diff --git a/tests/lib/is-point-in-pad.test.ts b/tests/lib/is-point-in-pad.test.ts new file mode 100644 index 0000000..eb6f92d --- /dev/null +++ b/tests/lib/is-point-in-pad.test.ts @@ -0,0 +1,27 @@ +import { expect, test, describe } from "bun:test" +import { isPointInPad } from "lib/check-traces-are-contiguous/is-point-in-pad" +import type { PcbPlatedHole } from "circuit-json" + +describe("isPointInPad oval plated hole", () => { + const ovalPad = { + type: "pcb_plated_hole", + shape: "oval", + x: 0, + y: 0, + outer_width: 2, + outer_height: 1, + } as unknown as PcbPlatedHole + + test("bounding-box corner is NOT in the oval pad", () => { + // (0.95, 0.45) is inside the 2x1 bounding box but outside the ellipse + expect(isPointInPad({ x: 0.95, y: 0.45 }, ovalPad)).toBe(false) + }) + + test("center IS in the oval pad", () => { + expect(isPointInPad({ x: 0, y: 0 }, ovalPad)).toBe(true) + }) + + test("a clearly-outside point is NOT in the oval pad", () => { + expect(isPointInPad({ x: 5, y: 5 }, ovalPad)).toBe(false) + }) +})