From 066c8aa7f9a3f4db6b31ce88778b93b38acce273 Mon Sep 17 00:00:00 2001 From: seveibar Date: Mon, 27 Jul 2026 19:24:14 -0700 Subject: [PATCH 1/2] Handle connected copper in fanout clearance --- README.md | 23 +++++- datasets/dataset06.ts | 2 +- docs/images/dataset06/sample001.png | Bin 0 -> 66205 bytes lib/route-bus.ts | 1 + scripts/render-dataset-pngs.ts | 42 ++++++++--- tests/connected-copper-obstacles.test.ts | 78 +++++++++++++++++++++ tests/dataset06-clad1-rp2040-repro.test.ts | 8 +-- 7 files changed, 138 insertions(+), 16 deletions(-) create mode 100644 docs/images/dataset06/sample001.png create mode 100644 tests/connected-copper-obstacles.test.ts diff --git a/README.md b/README.md index 59fc3af..3a829e0 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ and treats each bus-layer decision atomically. - Chamfers orthogonal routing corners into 45° segments before validating and emitting the fanout. - Verifies pad, via, trace, and already-routed fanout clearance. +- Treats an obstacle whose `connectedTo` list contains the connection name as + electrically connected copper rather than a foreign keepout. - Emits supplied fanout traces, via obstacles, and moved breakout endpoints in a new `SimpleRouteJson`. The returned problem is ready for a downstream autorouter to finish. @@ -296,6 +298,22 @@ and clearance values as the JLCPCB regressions. | ----------- | ------------ | ----: | -----------------: | ---------------: | -----: | | `sample001` | FCBGA1088L | 1088 | 589 | 499 | 6 | +## Dataset 06 + +`datasets/dataset06.ts` preserves the exact 132-connection, 265-obstacle, +single-layer mixed-footprint input from the clad1 RP2040 board. The checked-in +snapshot is intentionally labeled with its routed count: + +![clad1 RP2040 fanout reproduction](docs/images/dataset06/sample001.png) + +Same-connection copper awareness improves the deterministic result from 35 to +37 routed connections. The remaining repro is not presented as solved: it +contains escapes that must first move away from their requested boundary before +turning around the RP2040 pad field and large exposed pad. Completing it +requires a non-monotone single-layer search. Treating repeated terminals of one +electrical net as a mergeable multi-terminal tree would also remove artificial +same-net congestion. + ## Development ```sh @@ -312,7 +330,10 @@ connection, routing, and layer-assignment metrics. `bun run start` opens the datasets in the standard tscircuit solver debugger. `bun run render:dataset` writes `graphics-debug` PNGs under one subdirectory per dataset, with a red shared boundary, gray component courtyards, and green fanout-exit -markers. +markers. Pass an output directory and dataset id to render one dataset, for +example `bun scripts/render-dataset-pngs.ts docs/images dataset06`. Failed +regressions render their best partial attempt with a visible `INCOMPLETE` +label. ## Scope diff --git a/datasets/dataset06.ts b/datasets/dataset06.ts index b6127c8..7c81266 100644 --- a/datasets/dataset06.ts +++ b/datasets/dataset06.ts @@ -20,7 +20,7 @@ export const fanoutDataset06: FanoutDatasetSample[] = [ id: "sample001", name: "clad1 RP2040 shared-boundary reproduction", description: - "Exact serialized single-layer fanout input from the clad1 RP2040 assembly. The current solver routes 35 of 132 connections through the shared component-area boundary.", + "Exact serialized single-layer fanout input from the clad1 RP2040 assembly. Same-connection copper awareness raises the partial route from 35 to 37 of 132 connections; completing the fixture still requires non-monotone escapes and electrically connected multi-terminal merging.", footprintCount: componentIds.size, footprinterStrings: ["clad1 RP2040 assembly (serialized SRJ)"], simpleRouteJson: fixture.simpleRouteJson, diff --git a/docs/images/dataset06/sample001.png b/docs/images/dataset06/sample001.png new file mode 100644 index 0000000000000000000000000000000000000000..98f5a923622c5d62c20e571df8ffe8b7535c8bfd GIT binary patch literal 66205 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 connection.pointsToConnect.flatMap((point) => @@ -96,9 +112,12 @@ function formatFootprinterStrings(strings: string[]): string { } const outputDirectory = process.argv[2] ?? "verification-pngs" +const requestedDatasetId = process.argv[3] await mkdir(outputDirectory, { recursive: true }) -for (const dataset of fanoutDatasets) { +for (const dataset of fanoutDatasets.filter( + (candidate) => !requestedDatasetId || candidate.id === requestedDatasetId, +)) { const datasetDirectory = `${outputDirectory}/${dataset.id}` await mkdir(datasetDirectory, { recursive: true }) @@ -108,23 +127,26 @@ for (const dataset of fanoutDatasets) { sample.solverOptions, ) solver.solve() - if (solver.failed) { - throw new Error( - `${dataset.id}/${sample.id} failed: ${solver.error ?? "unknown solver error"}`, - ) - } - const output = solver.getOutput() + const visualizedSrj = solver.solved + ? solver.getOutput().simpleRouteJson + : sample.simpleRouteJson const componentBounds = getComponentBounds(sample.componentBounds) + const routeStatus = solver.failed + ? ` · incomplete: ${solver.attempts[0]?.routedConnectionCount ?? 0}/${sample.simpleRouteJson.connections.length}` + : "" const graphics = mergeGraphics( { ...solver.visualize(), - title: `${dataset.id}/${sample.id}: ${sample.name} · ${formatFootprinterStrings(sample.footprinterStrings)}`, + title: `${dataset.id}/${sample.id}: ${sample.name} · ${formatFootprinterStrings(sample.footprinterStrings)}${routeStatus}`, }, createVerificationOverlay( - output.simpleRouteJson, + visualizedSrj, componentBounds, sample.sharedBoundary, + solver.failed + ? `INCOMPLETE ${solver.attempts[0]?.routedConnectionCount ?? 0}/${sample.simpleRouteJson.connections.length}` + : undefined, ), ) const png = await getPngBufferFromGraphicsObject(graphics, { @@ -135,7 +157,7 @@ for (const dataset of fanoutDatasets) { pngWidth: 1400, viewbox: getVerificationViewbox( sample.sharedBoundary, - output.simpleRouteJson.bounds, + visualizedSrj.bounds, ), }) const outputPath = `${datasetDirectory}/${sample.id}.png` diff --git a/tests/connected-copper-obstacles.test.ts b/tests/connected-copper-obstacles.test.ts new file mode 100644 index 0000000..9b35749 --- /dev/null +++ b/tests/connected-copper-obstacles.test.ts @@ -0,0 +1,78 @@ +import { expect, test } from "bun:test" +import type { SimpleRouteJson } from "@tscircuit/capacity-autorouter" +import { FanoutSolver } from "lib/fanout-solver" +import type { FanoutBusSpec } from "lib/types" + +test("a fanout trace may cross copper connected to the same connection", () => { + const simpleRouteJson: SimpleRouteJson = { + layerCount: 1, + minTraceWidth: 0.1, + nominalTraceWidth: 0.1, + minViaPadDiameter: 0.25, + minViaHoleDiameter: 0.15, + minTraceToPadEdgeClearance: 0.1, + minViaEdgeToPadEdgeClearance: 0.1, + defaultObstacleMargin: 0.1, + bounds: { minX: -1, maxX: 4, minY: -0.3, maxY: 0.3 }, + obstacles: [ + { + obstacleId: "source-pad", + componentId: "source-component", + type: "rect", + center: { x: 0, y: 0 }, + width: 0.2, + height: 0.2, + layers: ["top"], + connectedTo: ["SIGNAL", "source-pad"], + }, + { + obstacleId: "connected-copper", + componentId: "connected-component", + type: "rect", + center: { x: 1, y: 0 }, + width: 0.8, + height: 0.4, + layers: ["top"], + connectedTo: ["SIGNAL"], + }, + ], + connections: [ + { + name: "SIGNAL", + pointsToConnect: [ + { + x: 0, + y: 0, + layer: "top", + pointId: "source-pad", + pcb_port_id: "source-pad", + }, + { x: 4, y: 0, layer: "top" }, + ], + }, + ], + buses: [ + { + busId: "signal-bus", + connectionNames: ["SIGNAL"], + sourceComponentId: "source-component", + direction: "right", + }, + ] as FanoutBusSpec[], + } + + const solver = new FanoutSolver(simpleRouteJson, { + sharedBoundary: { minX: -0.5, maxX: 3, minY: -0.3, maxY: 0.3 }, + escapeLayers: ["top"], + }) + solver.solve() + + expect(solver.failed).toBe(false) + expect(solver.getOutput().fanoutTraces).toHaveLength(1) + expect(solver.getOutput().fanoutTraces[0]!.route.at(-1)).toMatchObject({ + route_type: "wire", + x: 3, + y: 0, + layer: "top", + }) +}) diff --git a/tests/dataset06-clad1-rp2040-repro.test.ts b/tests/dataset06-clad1-rp2040-repro.test.ts index 1cabbb7..695408b 100644 --- a/tests/dataset06-clad1-rp2040-repro.test.ts +++ b/tests/dataset06-clad1-rp2040-repro.test.ts @@ -2,7 +2,7 @@ import { expect, test } from "bun:test" import { FanoutSolver } from "lib/fanout-solver" import { fanoutDataset06 } from "../datasets/dataset06" -test("dataset06 reproduces the incomplete clad1 RP2040 shared-boundary fanout", () => { +test("dataset06 records the remaining clad1 RP2040 fanout gap", () => { const sample = fanoutDataset06[0]! expect(sample.simpleRouteJson.layerCount).toBe(1) @@ -22,11 +22,11 @@ test("dataset06 reproduces the incomplete clad1 RP2040 shared-boundary fanout", expect(solver.failed).toBe(true) expect(solver.error).toBe( - "FanoutSolver: best layer assignment routed 35/132 connections", + "FanoutSolver: best layer assignment routed 37/132 connections", ) expect(solver.attempts).toHaveLength(1) expect(solver.attempts[0]).toMatchObject({ - routedConnectionCount: 35, - routedBusCount: 35, + routedConnectionCount: 37, + routedBusCount: 37, }) }, 20_000) From 59a1ea11c59150d6b86fbca4ed738b9af99d693b Mon Sep 17 00:00:00 2001 From: seveibar Date: Mon, 27 Jul 2026 21:02:33 -0700 Subject: [PATCH 2/2] Allow clad1 regression more time in CI --- tests/dataset06-clad1-rp2040-repro.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dataset06-clad1-rp2040-repro.test.ts b/tests/dataset06-clad1-rp2040-repro.test.ts index 695408b..c1a47da 100644 --- a/tests/dataset06-clad1-rp2040-repro.test.ts +++ b/tests/dataset06-clad1-rp2040-repro.test.ts @@ -29,4 +29,4 @@ test("dataset06 records the remaining clad1 RP2040 fanout gap", () => { routedConnectionCount: 37, routedBusCount: 37, }) -}, 20_000) +}, 60_000)