From feb1d95f024af46577ab2ac58ee05c652f44623e Mon Sep 17 00:00:00 2001 From: seveibar Date: Wed, 5 Aug 2026 16:52:15 -0700 Subject: [PATCH 1/3] fix: draw rats nest in autorouter start image --- lib/shared/autorouter-diagnostics.ts | 14 +++- tests/shared/autorouter-diagnostics.test.ts | 88 ++++++++++++++++++++- 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/lib/shared/autorouter-diagnostics.ts b/lib/shared/autorouter-diagnostics.ts index b077a3c5b..1206b935b 100644 --- a/lib/shared/autorouter-diagnostics.ts +++ b/lib/shared/autorouter-diagnostics.ts @@ -289,8 +289,10 @@ export class AutorouterDiagnostics { if (this.options.enabled && !this.hasWrittenPlacementSnapshot) { const placementCircuitJson = this.getCurrentCircuitJson().filter( (element) => !this.isRouteElement(element), - ) - this.writePngSnapshot("placement-unrouted.png", placementCircuitJson) + ) as AnyCircuitElement[] + this.writePngSnapshot("placement-unrouted.png", placementCircuitJson, { + shouldDrawRatsNest: true, + }) this.hasWrittenPlacementSnapshot = true } @@ -650,9 +652,13 @@ export class AutorouterDiagnostics { return filePath } - private writePngSnapshot(fileName: string, circuitJson: CircuitJson) { + private writePngSnapshot( + fileName: string, + circuitJson: CircuitJson, + options?: { shouldDrawRatsNest?: boolean }, + ) { try { - const pcbSvg = convertCircuitJsonToPcbSvg(circuitJson) + const pcbSvg = convertCircuitJsonToPcbSvg(circuitJson, options) const png = convertSvgToPngBuffer(pcbSvg) const debugDir = path.resolve(this.options.debugDir ?? DEFAULT_DEBUG_DIR) fs.mkdirSync(debugDir, { recursive: true }) diff --git a/tests/shared/autorouter-diagnostics.test.ts b/tests/shared/autorouter-diagnostics.test.ts index f71cdb71f..3d8d17664 100644 --- a/tests/shared/autorouter-diagnostics.test.ts +++ b/tests/shared/autorouter-diagnostics.test.ts @@ -3,7 +3,9 @@ import { EventEmitter } from "node:events" import fs from "node:fs" import os from "node:os" import path from "node:path" +<<<<<<< HEAD import type { CircuitJson } from "circuit-json" +import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" import { AutorouterDiagnostics, AutorouterPhaseTimeoutError, @@ -11,6 +13,7 @@ import { parseAutorouterPhaseName, parseAutorouterTimeout, } from "lib/shared/autorouter-diagnostics" +import { convertSvgToPngBuffer } from "lib/shared/convert-svg-to-png" class FakeRootCircuit extends EventEmitter { dbToArrayCallCount = 0 @@ -137,7 +140,77 @@ describe("autorouter diagnostics", () => { test("logs phase start/end and writes SRJ and PNG artifacts", async () => { const debugDir = makeTempDir() const logs: string[] = [] - const root = new FakeRootCircuit() + const circuitJson = [ + { + type: "pcb_board", + pcb_board_id: "board_0", + center: { x: 0, y: 0 }, + width: 10, + height: 10, + thickness: 1.4, + num_layers: 2, + }, + { + type: "source_net", + source_net_id: "source_net_0", + name: "N1", + }, + { + type: "source_port", + source_port_id: "source_port_a", + name: "A", + }, + { + type: "source_port", + source_port_id: "source_port_b", + name: "B", + }, + { + type: "pcb_port", + pcb_port_id: "pcb_port_a", + source_port_id: "source_port_a", + x: -2, + y: 0, + layers: ["top"], + }, + { + type: "pcb_port", + pcb_port_id: "pcb_port_b", + source_port_id: "source_port_b", + x: 2, + y: 0, + layers: ["top"], + }, + { + type: "pcb_plated_hole", + pcb_plated_hole_id: "pcb_plated_hole_a", + pcb_port_id: "pcb_port_a", + x: -2, + y: 0, + shape: "circle", + outer_diameter: 1, + hole_diameter: 0.5, + layers: ["top", "bottom"], + }, + { + type: "pcb_plated_hole", + pcb_plated_hole_id: "pcb_plated_hole_b", + pcb_port_id: "pcb_port_b", + x: 2, + y: 0, + shape: "circle", + outer_diameter: 1, + hole_diameter: 0.5, + layers: ["top", "bottom"], + }, + { + type: "source_trace", + source_trace_id: "source_trace_0", + connected_source_port_ids: ["source_port_a", "source_port_b"], + connected_source_net_ids: ["source_net_0"], + }, + ] + const root = new FakeRootCircuit(circuitJson) const diagnostics = new AutorouterDiagnostics({ enabled: true, dumpSrj: "all", @@ -223,6 +296,19 @@ describe("autorouter diagnostics", () => { expect( fs.readFileSync(path.join(debugDir, "phase-0-routed.png")).subarray(0, 8), ).toEqual(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])) + const startPng = fs.readFileSync( + path.join(debugDir, "placement-unrouted.png"), + ) + const expectedPngWithRatsNest = convertSvgToPngBuffer( + convertCircuitJsonToPcbSvg(circuitJson as any, { + shouldDrawRatsNest: true, + }), + ) + const expectedPngWithoutRatsNest = convertSvgToPngBuffer( + convertCircuitJsonToPcbSvg(circuitJson as any), + ) + expect(startPng).toEqual(Buffer.from(expectedPngWithRatsNest)) + expect(startPng).not.toEqual(Buffer.from(expectedPngWithoutRatsNest)) expect( fs.readFileSync(path.join(debugDir, "phase-0-routed.png")), ).not.toEqual( From 5d606411ec7f106f0de17ecc4550c37b02188b68 Mon Sep 17 00:00:00 2001 From: seveibar Date: Wed, 5 Aug 2026 17:08:31 -0700 Subject: [PATCH 2/3] test: add autorouter rats nest snapshot --- .../autorouter-start-rats-nest.snap.svg | 1 + tests/shared/autorouter-diagnostics.test.ts | 74 +++++++++++++++++-- 2 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 tests/shared/__snapshots__/autorouter-start-rats-nest.snap.svg diff --git a/tests/shared/__snapshots__/autorouter-start-rats-nest.snap.svg b/tests/shared/__snapshots__/autorouter-start-rats-nest.snap.svg new file mode 100644 index 000000000..628e8392c --- /dev/null +++ b/tests/shared/__snapshots__/autorouter-start-rats-nest.snap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/shared/autorouter-diagnostics.test.ts b/tests/shared/autorouter-diagnostics.test.ts index 3d8d17664..0b45a67bf 100644 --- a/tests/shared/autorouter-diagnostics.test.ts +++ b/tests/shared/autorouter-diagnostics.test.ts @@ -3,7 +3,6 @@ import { EventEmitter } from "node:events" import fs from "node:fs" import os from "node:os" import path from "node:path" -<<<<<<< HEAD import type { CircuitJson } from "circuit-json" import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" import { @@ -14,6 +13,7 @@ import { parseAutorouterTimeout, } from "lib/shared/autorouter-diagnostics" import { convertSvgToPngBuffer } from "lib/shared/convert-svg-to-png" +import "bun-match-svg" class FakeRootCircuit extends EventEmitter { dbToArrayCallCount = 0 @@ -155,6 +155,11 @@ describe("autorouter diagnostics", () => { source_net_id: "source_net_0", name: "N1", }, + { + type: "source_net", + source_net_id: "source_net_1", + name: "N2", + }, { type: "source_port", source_port_id: "source_port_a", @@ -165,12 +170,22 @@ describe("autorouter diagnostics", () => { source_port_id: "source_port_b", name: "B", }, + { + type: "source_port", + source_port_id: "source_port_c", + name: "C", + }, + { + type: "source_port", + source_port_id: "source_port_d", + name: "D", + }, { type: "pcb_port", pcb_port_id: "pcb_port_a", source_port_id: "source_port_a", x: -2, - y: 0, + y: -2, layers: ["top"], }, { @@ -178,7 +193,23 @@ describe("autorouter diagnostics", () => { pcb_port_id: "pcb_port_b", source_port_id: "source_port_b", x: 2, - y: 0, + y: 2, + layers: ["top"], + }, + { + type: "pcb_port", + pcb_port_id: "pcb_port_c", + source_port_id: "source_port_c", + x: -2, + y: 2, + layers: ["top"], + }, + { + type: "pcb_port", + pcb_port_id: "pcb_port_d", + source_port_id: "source_port_d", + x: 2, + y: -2, layers: ["top"], }, { @@ -186,7 +217,7 @@ describe("autorouter diagnostics", () => { pcb_plated_hole_id: "pcb_plated_hole_a", pcb_port_id: "pcb_port_a", x: -2, - y: 0, + y: -2, shape: "circle", outer_diameter: 1, hole_diameter: 0.5, @@ -197,7 +228,29 @@ describe("autorouter diagnostics", () => { pcb_plated_hole_id: "pcb_plated_hole_b", pcb_port_id: "pcb_port_b", x: 2, - y: 0, + y: 2, + shape: "circle", + outer_diameter: 1, + hole_diameter: 0.5, + layers: ["top", "bottom"], + }, + { + type: "pcb_plated_hole", + pcb_plated_hole_id: "pcb_plated_hole_c", + pcb_port_id: "pcb_port_c", + x: -2, + y: 2, + shape: "circle", + outer_diameter: 1, + hole_diameter: 0.5, + layers: ["top", "bottom"], + }, + { + type: "pcb_plated_hole", + pcb_plated_hole_id: "pcb_plated_hole_d", + pcb_port_id: "pcb_port_d", + x: 2, + y: -2, shape: "circle", outer_diameter: 1, hole_diameter: 0.5, @@ -209,6 +262,12 @@ describe("autorouter diagnostics", () => { connected_source_port_ids: ["source_port_a", "source_port_b"], connected_source_net_ids: ["source_net_0"], }, + { + type: "source_trace", + source_trace_id: "source_trace_1", + connected_source_port_ids: ["source_port_c", "source_port_d"], + connected_source_net_ids: ["source_net_1"], + }, ] const root = new FakeRootCircuit(circuitJson) const diagnostics = new AutorouterDiagnostics({ @@ -309,6 +368,11 @@ describe("autorouter diagnostics", () => { ) expect(startPng).toEqual(Buffer.from(expectedPngWithRatsNest)) expect(startPng).not.toEqual(Buffer.from(expectedPngWithoutRatsNest)) + expect( + convertCircuitJsonToPcbSvg(circuitJson as any, { + shouldDrawRatsNest: true, + }), + ).toMatchSvgSnapshot(import.meta.path, "autorouter-start-rats-nest") expect( fs.readFileSync(path.join(debugDir, "phase-0-routed.png")), ).not.toEqual( From 4073cb659130d21c762ee6dd5d8756d6a27dfb17 Mon Sep 17 00:00:00 2001 From: seveibar Date: Wed, 5 Aug 2026 17:56:25 -0700 Subject: [PATCH 3/3] test: type autorouter rats nest fixture --- tests/shared/autorouter-diagnostics.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/shared/autorouter-diagnostics.test.ts b/tests/shared/autorouter-diagnostics.test.ts index 0b45a67bf..3ffc6ba73 100644 --- a/tests/shared/autorouter-diagnostics.test.ts +++ b/tests/shared/autorouter-diagnostics.test.ts @@ -140,7 +140,7 @@ describe("autorouter diagnostics", () => { test("logs phase start/end and writes SRJ and PNG artifacts", async () => { const debugDir = makeTempDir() const logs: string[] = [] - const circuitJson = [ + const circuitJson: CircuitJson = [ { type: "pcb_board", pcb_board_id: "board_0", @@ -149,16 +149,19 @@ describe("autorouter diagnostics", () => { height: 10, thickness: 1.4, num_layers: 2, + material: "fr4", }, { type: "source_net", source_net_id: "source_net_0", name: "N1", + member_source_group_ids: [], }, { type: "source_net", source_net_id: "source_net_1", name: "N2", + member_source_group_ids: [], }, { type: "source_port",