From 6abb836b7ce538f5a628d0faabe658193750eeeb Mon Sep 17 00:00:00 2001 From: seveibar Date: Fri, 31 Jul 2026 19:21:11 -0700 Subject: [PATCH 1/2] Respect keepout component exclusions --- .../check-each-pcb-trace-non-overlapping.ts | 57 +++++++++++++++- .../keepout-excluded-components.test.ts | 65 +++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 tests/lib/check-each-pcb-trace-non-overlapping/keepout-excluded-components.test.ts diff --git a/lib/check-each-pcb-trace-non-overlapping/check-each-pcb-trace-non-overlapping.ts b/lib/check-each-pcb-trace-non-overlapping/check-each-pcb-trace-non-overlapping.ts index 3bc96ea..0be4c76 100644 --- a/lib/check-each-pcb-trace-non-overlapping/check-each-pcb-trace-non-overlapping.ts +++ b/lib/check-each-pcb-trace-non-overlapping/check-each-pcb-trace-non-overlapping.ts @@ -5,7 +5,14 @@ import { segmentToCircleMinDistance, } from "@tscircuit/math-utils" import { segmentToSegmentMinDistance } from "@tscircuit/math-utils" -import type { AnyCircuitElement, PcbTraceError } from "circuit-json" +import type { + AnyCircuitElement, + PCBKeepout, + PcbPlatedHole, + PcbPort, + PcbSmtPad, + PcbTraceError, +} from "circuit-json" import { type ConnectivityMap, getFullConnectivityMapFromCircuitJson, @@ -37,6 +44,20 @@ import { import { getPcbPortIdsConnectedToTraces } from "./getPcbPortIdsConnectedToTraces" import { getRadiusOfCircuitJsonElement } from "./getRadiusOfCircuitJsonElement" +type PCBKeepoutWithExclusions = PCBKeepout & { + excluded_pcb_component_ids?: string[] +} + +type PcbComponentConnectionElement = PcbPort | PcbSmtPad | PcbPlatedHole + +const getPcbComponentConnectionElementId = ( + element: PcbComponentConnectionElement, +): string => { + if (element.type === "pcb_port") return element.pcb_port_id + if (element.type === "pcb_smtpad") return element.pcb_smtpad_id + return element.pcb_plated_hole_id +} + export function checkEachPcbTraceNonOverlapping( circuitJson: AnyCircuitElement[], { @@ -85,10 +106,35 @@ export function checkEachPcbTraceNonOverlapping( }) const pcbSmtPads = cju(circuitJson).pcb_smtpad.list() const pcbPlatedHoles = cju(circuitJson).pcb_plated_hole.list() + const pcbPorts = cju(circuitJson).pcb_port.list() const pcbHoles = cju(circuitJson).pcb_hole.list() const pcbVias = cju(circuitJson).pcb_via.list() const pcbKeepouts = cju(circuitJson).pcb_keepout.list() + const pcbComponentConnectionElements: PcbComponentConnectionElement[] = [ + ...pcbPorts, + ...pcbSmtPads, + ...pcbPlatedHoles, + ] + const excludedConnectionIdsByKeepoutId = new Map() + for (const keepout of pcbKeepouts) { + const excludedPcbComponentIds = new Set( + (keepout as PCBKeepoutWithExclusions).excluded_pcb_component_ids ?? [], + ) + if (excludedPcbComponentIds.size === 0) continue + + excludedConnectionIdsByKeepoutId.set( + keepout.pcb_keepout_id, + pcbComponentConnectionElements + .filter( + (element) => + element.pcb_component_id && + excludedPcbComponentIds.has(element.pcb_component_id), + ) + .map(getPcbComponentConnectionElementId), + ) + } + const allObjects: Collidable[] = [ ...pcbTraceSegments, ...pcbSmtPads, @@ -135,6 +181,15 @@ export function checkEachPcbTraceNonOverlapping( if (!getLayersOfPcbElement(obj).includes(segmentA.layer)) { continue } + if ( + obj.type === "pcb_keepout" && + (excludedConnectionIdsByKeepoutId.get(obj.pcb_keepout_id) ?? []).some( + (connectionId) => + connMap.areIdsConnected(segmentA.pcb_trace_id, connectionId), + ) + ) { + continue + } if (obj.type === "pcb_trace_segment") { const segmentB = obj diff --git a/tests/lib/check-each-pcb-trace-non-overlapping/keepout-excluded-components.test.ts b/tests/lib/check-each-pcb-trace-non-overlapping/keepout-excluded-components.test.ts new file mode 100644 index 0000000..ec780ff --- /dev/null +++ b/tests/lib/check-each-pcb-trace-non-overlapping/keepout-excluded-components.test.ts @@ -0,0 +1,65 @@ +import { expect, test } from "bun:test" +import type { AnyCircuitElement } from "circuit-json" +import type { ConnectivityMap } from "circuit-json-to-connectivity-map" +import { checkEachPcbTraceNonOverlapping } from "lib/check-each-pcb-trace-non-overlapping/check-each-pcb-trace-non-overlapping" + +test("keepout component exclusions suppress only connected trace violations", () => { + const circuitJson = [ + { + type: "pcb_trace", + pcb_trace_id: "trace_excluded", + route: [ + { route_type: "wire", x: -3, y: 0, width: 0.1, layer: "top" }, + { route_type: "wire", x: 3, y: 0, width: 0.1, layer: "top" }, + ], + }, + { + type: "pcb_trace", + pcb_trace_id: "trace_reported", + route: [ + { route_type: "wire", x: -3, y: 1, width: 0.1, layer: "top" }, + { route_type: "wire", x: 3, y: 1, width: 0.1, layer: "top" }, + ], + }, + { + type: "pcb_smtpad", + pcb_smtpad_id: "pad_ant1", + pcb_component_id: "pcb_component_ant1", + shape: "rect", + x: -10, + y: 0, + width: 1, + height: 1, + layer: "top", + }, + { + type: "pcb_keepout", + shape: "rect", + pcb_keepout_id: "keepout_antenna", + center: { x: 0, y: 0 }, + width: 4, + height: 4, + layers: ["top"], + excluded_pcb_component_ids: ["pcb_component_ant1"], + }, + ] as AnyCircuitElement[] + + const connMap = { + areIdsConnected: (idA: string, idB: string) => { + if (idA === idB) return true + return ( + (idA === "trace_excluded" && idB === "pad_ant1") || + (idA === "pad_ant1" && idB === "trace_excluded") + ) + }, + } as unknown as ConnectivityMap + + const errors = checkEachPcbTraceNonOverlapping(circuitJson, { + connMap, + minClearance: 0, + }) + expect(errors.map((error) => error.pcb_trace_id)).toEqual(["trace_reported"]) + expect(errors[0]?.pcb_trace_error_id).toBe( + "overlap_trace_reported_keepout_antenna", + ) +}) From 639121885e7fd273c4cd498ea0027d6710dfe291 Mon Sep 17 00:00:00 2001 From: seveibar Date: Fri, 31 Jul 2026 19:48:53 -0700 Subject: [PATCH 2/2] Use released keepout exclusion schema --- .../check-each-pcb-trace-non-overlapping.ts | 7 +------ package.json | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/check-each-pcb-trace-non-overlapping/check-each-pcb-trace-non-overlapping.ts b/lib/check-each-pcb-trace-non-overlapping/check-each-pcb-trace-non-overlapping.ts index 0be4c76..0ce110f 100644 --- a/lib/check-each-pcb-trace-non-overlapping/check-each-pcb-trace-non-overlapping.ts +++ b/lib/check-each-pcb-trace-non-overlapping/check-each-pcb-trace-non-overlapping.ts @@ -7,7 +7,6 @@ import { import { segmentToSegmentMinDistance } from "@tscircuit/math-utils" import type { AnyCircuitElement, - PCBKeepout, PcbPlatedHole, PcbPort, PcbSmtPad, @@ -44,10 +43,6 @@ import { import { getPcbPortIdsConnectedToTraces } from "./getPcbPortIdsConnectedToTraces" import { getRadiusOfCircuitJsonElement } from "./getRadiusOfCircuitJsonElement" -type PCBKeepoutWithExclusions = PCBKeepout & { - excluded_pcb_component_ids?: string[] -} - type PcbComponentConnectionElement = PcbPort | PcbSmtPad | PcbPlatedHole const getPcbComponentConnectionElementId = ( @@ -119,7 +114,7 @@ export function checkEachPcbTraceNonOverlapping( const excludedConnectionIdsByKeepoutId = new Map() for (const keepout of pcbKeepouts) { const excludedPcbComponentIds = new Set( - (keepout as PCBKeepoutWithExclusions).excluded_pcb_component_ids ?? [], + keepout.excluded_pcb_component_ids ?? [], ) if (excludedPcbComponentIds.size === 0) continue diff --git a/package.json b/package.json index 898b3df..4744591 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@types/bun": "^1.2.8", "@types/debug": "^4.1.12", "bun-match-svg": "^0.0.11", - "circuit-json": "^0.0.453", + "circuit-json": "^0.0.457", "circuit-to-svg": "^0.0.388", "debug": "^4.3.5", "format-si-unit": "^0.0.7",