From 9ec91d4b8ebdecde404459c584e7f57e6fcb247b Mon Sep 17 00:00:00 2001 From: Valentino Zegna Date: Sat, 21 Mar 2026 11:42:15 -0700 Subject: [PATCH] fix: add collinearity check to pointOnSegment() for diagonal wires Bounding-box-only containment falsely merged nets when diagonal wire bounding boxes overlapped. Add cross-product collinearity check with 2-unit perpendicular distance tolerance to handle Altium coordinate rounding. Also clean up undefined mpn/description keys in component output. Closes #54 --- src/parsers/altium/connectivity.ts | 7 +++++++ src/parsers/altium/index.test.ts | 22 ++++++++++++++++++++++ src/parsers/altium/index.ts | 10 ++++++++-- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/parsers/altium/connectivity.ts b/src/parsers/altium/connectivity.ts index d27d4c2..14853a3 100644 --- a/src/parsers/altium/connectivity.ts +++ b/src/parsers/altium/connectivity.ts @@ -180,6 +180,13 @@ const pointOnSegment = (point: Coordinate, segment: LineSegment): boolean => { const [p1, p2] = segment; const [px, py] = point; + // Cross product collinearity check: perpendicular distance must be within tolerance. + // Altium coordinates (10000 units/mil) can have small rounding errors (~2 units), + // so we use tolerance of 2 units (0.0002 mil). Squared form avoids sqrt. + const cross = (p2[0] - p1[0]) * (py - p1[1]) - (p2[1] - p1[1]) * (px - p1[0]); + const segLenSq = (p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2; + if (segLenSq > 0 && cross * cross > 4 * segLenSq) return false; + const minX = Math.min(p1[0], p2[0]); const maxX = Math.max(p1[0], p2[0]); const minY = Math.min(p1[1], p2[1]); diff --git a/src/parsers/altium/index.test.ts b/src/parsers/altium/index.test.ts index 468add0..55b5c7c 100644 --- a/src/parsers/altium/index.test.ts +++ b/src/parsers/altium/index.test.ts @@ -323,6 +323,28 @@ describe("Connectivity", () => { expect(isConnected(wireA, wireB)).toBe(false); }); + it("should detect disconnected wires with overlapping bounding boxes", () => { + const wireA: AltiumRecord = { + index: 0, + RECORD: RECORD_TYPES.WIRE, + coords: [ + [0, 0], + [100, 100], + ], + }; + + const wireB: AltiumRecord = { + index: 1, + RECORD: RECORD_TYPES.WIRE, + coords: [ + [100, 0], + [0, 100], + ], + }; + + expect(isConnected(wireA, wireB)).toBe(false); + }); + it("should detect pin connected to wire", () => { const wire: AltiumRecord = { index: 0, diff --git a/src/parsers/altium/index.ts b/src/parsers/altium/index.ts index eaf0665..629ad67 100644 --- a/src/parsers/altium/index.ts +++ b/src/parsers/altium/index.ts @@ -333,11 +333,17 @@ export const extractComponents = (schematic: AltiumSchematic): ComponentDetails } const component: ComponentDetails[string] = { - mpn, - description: extractedDescription, pins, }; + if (mpn !== undefined) { + component.mpn = mpn; + } + + if (extractedDescription !== undefined) { + component.description = extractedDescription; + } + if (comment !== undefined) { component.comment = comment; }