From e177d6836951240771a87ef74d48fb7fc0edd821 Mon Sep 17 00:00:00 2001 From: seveibar Date: Mon, 3 Aug 2026 10:12:47 -0700 Subject: [PATCH 1/3] Add visual repros for PCB placement layer bugs --- .../courtyard-polygon-layer-repro.snap.svg | 1 + ...ultiple-hole-opposite-layer-repro.snap.svg | 1 + .../opposite-layer-components.snap.svg | 1 + ...posite-layer-through-hole-overlap.snap.svg | 1 + .../courtyard-polygon-layer-repro.test.ts | 83 +++++++++++++++++++ ...ultiple-hole-opposite-layer-repro.test.tsx | 55 ++++++++++++ .../opposite-layer-components.test.tsx | 56 +++++++++++++ ...posite-layer-through-hole-overlap.test.tsx | 64 ++++++++++++++ 8 files changed, 262 insertions(+) create mode 100644 tests/lib/check-pcb-component-overlap/__snapshots__/courtyard-polygon-layer-repro.snap.svg create mode 100644 tests/lib/check-pcb-component-overlap/__snapshots__/multiple-hole-opposite-layer-repro.snap.svg create mode 100644 tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-components.snap.svg create mode 100644 tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-through-hole-overlap.snap.svg create mode 100644 tests/lib/check-pcb-component-overlap/courtyard-polygon-layer-repro.test.ts create mode 100644 tests/lib/check-pcb-component-overlap/multiple-hole-opposite-layer-repro.test.tsx create mode 100644 tests/lib/check-pcb-component-overlap/opposite-layer-components.test.tsx create mode 100644 tests/lib/check-pcb-component-overlap/opposite-layer-through-hole-overlap.test.tsx diff --git a/tests/lib/check-pcb-component-overlap/__snapshots__/courtyard-polygon-layer-repro.snap.svg b/tests/lib/check-pcb-component-overlap/__snapshots__/courtyard-polygon-layer-repro.snap.svg new file mode 100644 index 0000000..4e36934 --- /dev/null +++ b/tests/lib/check-pcb-component-overlap/__snapshots__/courtyard-polygon-layer-repro.snap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/lib/check-pcb-component-overlap/__snapshots__/multiple-hole-opposite-layer-repro.snap.svg b/tests/lib/check-pcb-component-overlap/__snapshots__/multiple-hole-opposite-layer-repro.snap.svg new file mode 100644 index 0000000..2f5df64 --- /dev/null +++ b/tests/lib/check-pcb-component-overlap/__snapshots__/multiple-hole-opposite-layer-repro.snap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-components.snap.svg b/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-components.snap.svg new file mode 100644 index 0000000..3df8a39 --- /dev/null +++ b/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-components.snap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-through-hole-overlap.snap.svg b/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-through-hole-overlap.snap.svg new file mode 100644 index 0000000..974d028 --- /dev/null +++ b/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-through-hole-overlap.snap.svg @@ -0,0 +1 @@ +pcb_smtpad U1.A overlaps with pcb_plated_hole J1.A \ No newline at end of file diff --git a/tests/lib/check-pcb-component-overlap/courtyard-polygon-layer-repro.test.ts b/tests/lib/check-pcb-component-overlap/courtyard-polygon-layer-repro.test.ts new file mode 100644 index 0000000..38e835a --- /dev/null +++ b/tests/lib/check-pcb-component-overlap/courtyard-polygon-layer-repro.test.ts @@ -0,0 +1,83 @@ +import { expect, test } from "bun:test" +import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" +import type { AnyCircuitElement } from "circuit-json" +import { checkCourtyardOverlap } from "lib/check-courtyard-overlap/checkCourtyardOverlap" + +const square = (centerX: number) => [ + { x: centerX - 1.5, y: -1.5 }, + { x: centerX + 1.5, y: -1.5 }, + { x: centerX + 1.5, y: 1.5 }, + { x: centerX - 1.5, y: 1.5 }, +] + +const makePolygonComponent = ({ + id, + layer, + x, +}: { + id: string + layer: "top" | "bottom" + x: number +}): AnyCircuitElement[] => + [ + { + type: "source_component", + source_component_id: `source_${id}`, + ftype: "simple_chip", + name: id, + }, + { + type: "pcb_component", + pcb_component_id: `component_${id}`, + source_component_id: `source_${id}`, + center: { x, y: 0 }, + width: 3, + height: 3, + layer, + rotation: 0, + }, + { + type: "pcb_courtyard_polygon", + pcb_courtyard_polygon_id: `courtyard_${id}`, + pcb_component_id: `component_${id}`, + points: square(x), + layer, + color: layer === "bottom" ? "#00BFFF" : "#FF00FF", + }, + ] as AnyCircuitElement[] + +test.failing( + "checks same-layer polygon courtyards while allowing opposite layers", + () => { + const circuitJson: AnyCircuitElement[] = [ + { + type: "pcb_board", + pcb_board_id: "board", + center: { x: 0, y: 0 }, + width: 14, + height: 8, + thickness: 1.6, + num_layers: 2, + material: "fr4", + }, + ...makePolygonComponent({ id: "top_a", layer: "top", x: -1 }), + ...makePolygonComponent({ id: "top_b", layer: "top", x: 1 }), + ...makePolygonComponent({ id: "bottom", layer: "bottom", x: -1.2 }), + ] + + const errors = checkCourtyardOverlap(circuitJson) + + expect( + convertCircuitJsonToPcbSvg([...circuitJson, ...errors], { + shouldDrawErrors: true, + showCourtyards: true, + }), + ).toMatchSvgSnapshot(import.meta.path) + + expect(errors).toHaveLength(1) + expect(errors[0].pcb_component_ids).toEqual([ + "component_top_a", + "component_top_b", + ]) + }, +) diff --git a/tests/lib/check-pcb-component-overlap/multiple-hole-opposite-layer-repro.test.tsx b/tests/lib/check-pcb-component-overlap/multiple-hole-opposite-layer-repro.test.tsx new file mode 100644 index 0000000..d15c89f --- /dev/null +++ b/tests/lib/check-pcb-component-overlap/multiple-hole-opposite-layer-repro.test.tsx @@ -0,0 +1,55 @@ +import { expect, test } from "bun:test" +import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" +import { Circuit } from "tscircuit" +import { checkPcbComponentOverlap } from "lib/check-pcb-components-overlap/checkPcbComponentOverlap" + +test.failing( + "checks every hole in an opposite-layer component against top courtyards", + async () => { + const circuit = new Circuit({ + platform: { placementDrcChecksDisabled: true }, + }) + circuit.add( + + + + + + } + /> + + + + } + /> + , + ) + + await circuit.renderUntilSettled() + const circuitJson = circuit.getCircuitJson() + const errors = checkPcbComponentOverlap(circuitJson) + + expect( + convertCircuitJsonToPcbSvg([...circuitJson, ...errors], { + shouldDrawErrors: true, + showCourtyards: true, + }), + ).toMatchSvgSnapshot(import.meta.path) + + expect(errors).toContainEqual( + expect.objectContaining({ + type: "pcb_footprint_overlap_error", + pcb_hole_ids: ["pcb_hole_1"], + }), + ) + }, +) diff --git a/tests/lib/check-pcb-component-overlap/opposite-layer-components.test.tsx b/tests/lib/check-pcb-component-overlap/opposite-layer-components.test.tsx new file mode 100644 index 0000000..71c2916 --- /dev/null +++ b/tests/lib/check-pcb-component-overlap/opposite-layer-components.test.tsx @@ -0,0 +1,56 @@ +import { expect, test } from "bun:test" +import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" +import { Circuit } from "tscircuit" +import { runAllPlacementChecks } from "lib/run-all-checks" + +const footprint = ( + + + + +) + +test("allows SMT components to share coordinates on opposite layers", async () => { + const circuit = new Circuit({ + platform: { placementDrcChecksDisabled: true }, + }) + circuit.add( + + + + , + ) + + await circuit.renderUntilSettled() + const circuitJson = circuit.getCircuitJson() + const errors = await runAllPlacementChecks(circuitJson) + + expect(errors).toEqual([]) + expect( + convertCircuitJsonToPcbSvg(circuitJson, { + shouldDrawErrors: true, + showCourtyards: true, + }), + ).toMatchSvgSnapshot(import.meta.path) +}) diff --git a/tests/lib/check-pcb-component-overlap/opposite-layer-through-hole-overlap.test.tsx b/tests/lib/check-pcb-component-overlap/opposite-layer-through-hole-overlap.test.tsx new file mode 100644 index 0000000..e48523b --- /dev/null +++ b/tests/lib/check-pcb-component-overlap/opposite-layer-through-hole-overlap.test.tsx @@ -0,0 +1,64 @@ +import { expect, test } from "bun:test" +import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" +import { Circuit } from "tscircuit" +import { checkPcbComponentOverlap } from "lib/check-pcb-components-overlap/checkPcbComponentOverlap" + +test("still reports through-hole copper collisions across component layers", async () => { + const circuit = new Circuit({ + platform: { placementDrcChecksDisabled: true }, + }) + circuit.add( + + + + + } + /> + + + + } + /> + , + ) + + await circuit.renderUntilSettled() + const circuitJson = circuit.getCircuitJson() + const errors = checkPcbComponentOverlap(circuitJson) + + expect(errors).toHaveLength(1) + expect(errors[0]).toMatchObject({ + type: "pcb_footprint_overlap_error", + pcb_smtpad_ids: ["pcb_smtpad_0"], + pcb_plated_hole_ids: ["pcb_plated_hole_0"], + }) + expect( + convertCircuitJsonToPcbSvg([...circuitJson, ...errors], { + shouldDrawErrors: true, + }), + ).toMatchSvgSnapshot(import.meta.path) +}) From 8a0ccd62b1c033bcd6b3982a6d7029ef8cf735d4 Mon Sep 17 00:00:00 2001 From: seveibar Date: Mon, 3 Aug 2026 10:21:56 -0700 Subject: [PATCH 2/3] Label PCB placement repro snapshots --- .../courtyard-polygon-layer-repro.snap.svg | 2 +- ...ultiple-hole-opposite-layer-repro.snap.svg | 2 +- .../opposite-layer-components.snap.svg | 2 +- ...posite-layer-through-hole-overlap.snap.svg | 2 +- .../courtyard-polygon-layer-repro.test.ts | 22 +++++++++++++++++++ ...ultiple-hole-opposite-layer-repro.test.tsx | 14 ++++++++++++ .../opposite-layer-components.test.tsx | 14 ++++++++++++ ...posite-layer-through-hole-overlap.test.tsx | 14 ++++++++++++ 8 files changed, 68 insertions(+), 4 deletions(-) diff --git a/tests/lib/check-pcb-component-overlap/__snapshots__/courtyard-polygon-layer-repro.snap.svg b/tests/lib/check-pcb-component-overlap/__snapshots__/courtyard-polygon-layer-repro.snap.svg index 4e36934..ece5527 100644 --- a/tests/lib/check-pcb-component-overlap/__snapshots__/courtyard-polygon-layer-repro.snap.svg +++ b/tests/lib/check-pcb-component-overlap/__snapshots__/courtyard-polygon-layer-repro.snap.svg @@ -1 +1 @@ - \ No newline at end of file +Top polygons overlap: expected errorBottom polygon (cyan): overlap allowed \ No newline at end of file diff --git a/tests/lib/check-pcb-component-overlap/__snapshots__/multiple-hole-opposite-layer-repro.snap.svg b/tests/lib/check-pcb-component-overlap/__snapshots__/multiple-hole-opposite-layer-repro.snap.svg index 2f5df64..dd8c235 100644 --- a/tests/lib/check-pcb-component-overlap/__snapshots__/multiple-hole-opposite-layer-repro.snap.svg +++ b/tests/lib/check-pcb-component-overlap/__snapshots__/multiple-hole-opposite-layer-repro.snap.svg @@ -1 +1 @@ - \ No newline at end of file +Expected error: left hole crosses top courtyardBottom J1 footprint: two drilled holes \ No newline at end of file diff --git a/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-components.snap.svg b/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-components.snap.svg index 3df8a39..f5d38b3 100644 --- a/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-components.snap.svg +++ b/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-components.snap.svg @@ -1 +1 @@ - \ No newline at end of file +Top U1 pad (red)Bottom U2 pad (blue) \ No newline at end of file diff --git a/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-through-hole-overlap.snap.svg b/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-through-hole-overlap.snap.svg index 974d028..ec14a6f 100644 --- a/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-through-hole-overlap.snap.svg +++ b/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-through-hole-overlap.snap.svg @@ -1 +1 @@ -pcb_smtpad U1.A overlaps with pcb_plated_hole J1.A \ No newline at end of file +Top SMT pad (red)Bottom plated hole spans both layerspcb_smtpad U1.A overlaps with pcb_plated_hole J1.A \ No newline at end of file diff --git a/tests/lib/check-pcb-component-overlap/courtyard-polygon-layer-repro.test.ts b/tests/lib/check-pcb-component-overlap/courtyard-polygon-layer-repro.test.ts index 38e835a..01c45e0 100644 --- a/tests/lib/check-pcb-component-overlap/courtyard-polygon-layer-repro.test.ts +++ b/tests/lib/check-pcb-component-overlap/courtyard-polygon-layer-repro.test.ts @@ -63,6 +63,28 @@ test.failing( ...makePolygonComponent({ id: "top_a", layer: "top", x: -1 }), ...makePolygonComponent({ id: "top_b", layer: "top", x: 1 }), ...makePolygonComponent({ id: "bottom", layer: "bottom", x: -1.2 }), + { + type: "pcb_note_text", + pcb_note_text_id: "note_top", + text: "Top polygons overlap: expected error", + font: "tscircuit2024", + font_size: 0.45, + anchor_position: { x: 0, y: 2.8 }, + anchor_alignment: "center", + layer: "top", + color: "#FF00FF", + }, + { + type: "pcb_note_text", + pcb_note_text_id: "note_bottom", + text: "Bottom polygon (cyan): overlap allowed", + font: "tscircuit2024", + font_size: 0.45, + anchor_position: { x: 0, y: -2.8 }, + anchor_alignment: "center", + layer: "top", + color: "#00BFFF", + }, ] const errors = checkCourtyardOverlap(circuitJson) diff --git a/tests/lib/check-pcb-component-overlap/multiple-hole-opposite-layer-repro.test.tsx b/tests/lib/check-pcb-component-overlap/multiple-hole-opposite-layer-repro.test.tsx index d15c89f..ddb35fb 100644 --- a/tests/lib/check-pcb-component-overlap/multiple-hole-opposite-layer-repro.test.tsx +++ b/tests/lib/check-pcb-component-overlap/multiple-hole-opposite-layer-repro.test.tsx @@ -31,6 +31,20 @@ test.failing( } /> + + , ) diff --git a/tests/lib/check-pcb-component-overlap/opposite-layer-components.test.tsx b/tests/lib/check-pcb-component-overlap/opposite-layer-components.test.tsx index 71c2916..af7bdd1 100644 --- a/tests/lib/check-pcb-component-overlap/opposite-layer-components.test.tsx +++ b/tests/lib/check-pcb-component-overlap/opposite-layer-components.test.tsx @@ -39,6 +39,20 @@ test("allows SMT components to share coordinates on opposite layers", async () = pcbX={0} pcbY={0} /> + + , ) diff --git a/tests/lib/check-pcb-component-overlap/opposite-layer-through-hole-overlap.test.tsx b/tests/lib/check-pcb-component-overlap/opposite-layer-through-hole-overlap.test.tsx index e48523b..2140e00 100644 --- a/tests/lib/check-pcb-component-overlap/opposite-layer-through-hole-overlap.test.tsx +++ b/tests/lib/check-pcb-component-overlap/opposite-layer-through-hole-overlap.test.tsx @@ -43,6 +43,20 @@ test("still reports through-hole copper collisions across component layers", asy } /> + + , ) From 933d709e1c02c49133481330269c0f25d175386d Mon Sep 17 00:00:00 2001 From: seveibar Date: Mon, 3 Aug 2026 11:06:10 -0700 Subject: [PATCH 3/3] Keep only failing PCB layer repros --- .../opposite-layer-components.snap.svg | 1 - ...posite-layer-through-hole-overlap.snap.svg | 1 - .../opposite-layer-components.test.tsx | 70 ----------------- ...posite-layer-through-hole-overlap.test.tsx | 78 ------------------- 4 files changed, 150 deletions(-) delete mode 100644 tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-components.snap.svg delete mode 100644 tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-through-hole-overlap.snap.svg delete mode 100644 tests/lib/check-pcb-component-overlap/opposite-layer-components.test.tsx delete mode 100644 tests/lib/check-pcb-component-overlap/opposite-layer-through-hole-overlap.test.tsx diff --git a/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-components.snap.svg b/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-components.snap.svg deleted file mode 100644 index f5d38b3..0000000 --- a/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-components.snap.svg +++ /dev/null @@ -1 +0,0 @@ -Top U1 pad (red)Bottom U2 pad (blue) \ No newline at end of file diff --git a/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-through-hole-overlap.snap.svg b/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-through-hole-overlap.snap.svg deleted file mode 100644 index ec14a6f..0000000 --- a/tests/lib/check-pcb-component-overlap/__snapshots__/opposite-layer-through-hole-overlap.snap.svg +++ /dev/null @@ -1 +0,0 @@ -Top SMT pad (red)Bottom plated hole spans both layerspcb_smtpad U1.A overlaps with pcb_plated_hole J1.A \ No newline at end of file diff --git a/tests/lib/check-pcb-component-overlap/opposite-layer-components.test.tsx b/tests/lib/check-pcb-component-overlap/opposite-layer-components.test.tsx deleted file mode 100644 index af7bdd1..0000000 --- a/tests/lib/check-pcb-component-overlap/opposite-layer-components.test.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { expect, test } from "bun:test" -import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" -import { Circuit } from "tscircuit" -import { runAllPlacementChecks } from "lib/run-all-checks" - -const footprint = ( - - - - -) - -test("allows SMT components to share coordinates on opposite layers", async () => { - const circuit = new Circuit({ - platform: { placementDrcChecksDisabled: true }, - }) - circuit.add( - - - - - - , - ) - - await circuit.renderUntilSettled() - const circuitJson = circuit.getCircuitJson() - const errors = await runAllPlacementChecks(circuitJson) - - expect(errors).toEqual([]) - expect( - convertCircuitJsonToPcbSvg(circuitJson, { - shouldDrawErrors: true, - showCourtyards: true, - }), - ).toMatchSvgSnapshot(import.meta.path) -}) diff --git a/tests/lib/check-pcb-component-overlap/opposite-layer-through-hole-overlap.test.tsx b/tests/lib/check-pcb-component-overlap/opposite-layer-through-hole-overlap.test.tsx deleted file mode 100644 index 2140e00..0000000 --- a/tests/lib/check-pcb-component-overlap/opposite-layer-through-hole-overlap.test.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { expect, test } from "bun:test" -import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" -import { Circuit } from "tscircuit" -import { checkPcbComponentOverlap } from "lib/check-pcb-components-overlap/checkPcbComponentOverlap" - -test("still reports through-hole copper collisions across component layers", async () => { - const circuit = new Circuit({ - platform: { placementDrcChecksDisabled: true }, - }) - circuit.add( - - - - - } - /> - - - - } - /> - - - , - ) - - await circuit.renderUntilSettled() - const circuitJson = circuit.getCircuitJson() - const errors = checkPcbComponentOverlap(circuitJson) - - expect(errors).toHaveLength(1) - expect(errors[0]).toMatchObject({ - type: "pcb_footprint_overlap_error", - pcb_smtpad_ids: ["pcb_smtpad_0"], - pcb_plated_hole_ids: ["pcb_plated_hole_0"], - }) - expect( - convertCircuitJsonToPcbSvg([...circuitJson, ...errors], { - shouldDrawErrors: true, - }), - ).toMatchSvgSnapshot(import.meta.path) -})