From ee45edf39382bd27cccc5c6b5c558f04358f8149 Mon Sep 17 00:00:00 2001 From: Severin Ibarluzea Date: Mon, 10 Nov 2025 18:08:37 -0800 Subject: [PATCH 1/2] Prevent label overlap solver from looping --- .../SingleOverlapSolver.ts | 43 ++++- ...hematicTracePipelineSolver_repro04.test.ts | 180 ++++++++++++++++++ 2 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 tests/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver_repro04.test.ts diff --git a/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts b/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts index 8c07fdb..d72d7f7 100644 --- a/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +++ b/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts @@ -4,11 +4,18 @@ import type { NetLabelPlacement } from "../../../NetLabelPlacementSolver/NetLabe import type { InputProblem } from "lib/types/InputProblem" import type { GraphicsObject } from "graphics-debug" import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver" -import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions" -import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect" +import { + isPathCollidingWithObstacles, + segmentIntersectsRect, +} from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions" +import { + getObstacleRects, + type ChipWithBounds, +} from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect" import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem" import { generateRerouteCandidates } from "../../rerouteCollidingTrace" import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath" +import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry" interface SingleOverlapSolverInput { trace: SolvedTracePath @@ -30,12 +37,15 @@ export class SingleOverlapSolver extends BaseSolver { problem: InputProblem obstacles: ReturnType label: NetLabelPlacement + paddingBuffer: number + labelBounds: ChipWithBounds constructor(solverInput: SingleOverlapSolverInput) { super() this.initialTrace = solverInput.trace this.problem = solverInput.problem this.label = solverInput.label + this.paddingBuffer = solverInput.paddingBuffer const candidates = generateRerouteCandidates({ ...solverInput, @@ -55,6 +65,19 @@ export class SingleOverlapSolver extends BaseSolver { (a, b) => getPathLength(a) - getPathLength(b), ) this.obstacles = getObstacleRects(this.problem) + + const labelBounds = getRectBounds( + this.label.center, + this.label.width, + this.label.height, + ) + this.labelBounds = { + chipId: `netlabel-${this.label.netId}`, + minX: labelBounds.minX - this.paddingBuffer, + minY: labelBounds.minY - this.paddingBuffer, + maxX: labelBounds.maxX + this.paddingBuffer, + maxY: labelBounds.maxY + this.paddingBuffer, + } } override _step() { @@ -66,12 +89,26 @@ export class SingleOverlapSolver extends BaseSolver { const nextCandidatePath = this.queuedCandidatePaths.shift()! const simplifiedPath = simplifyPath(nextCandidatePath) - if (!isPathCollidingWithObstacles(simplifiedPath, this.obstacles)) { + if ( + !isPathCollidingWithObstacles(simplifiedPath, this.obstacles) && + !this.doesPathOverlapLabel(simplifiedPath) + ) { this.solvedTracePath = simplifiedPath this.solved = true } } + private doesPathOverlapLabel(path: Point[]): boolean { + for (let i = 0; i < path.length - 1; i++) { + const start = path[i]! + const end = path[i + 1]! + if (segmentIntersectsRect(start, end, this.labelBounds)) { + return true + } + } + return false + } + override visualize(): GraphicsObject { const graphics = visualizeInputProblem(this.problem, { chipAlpha: 0.1, diff --git a/tests/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver_repro04.test.ts b/tests/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver_repro04.test.ts new file mode 100644 index 0000000..b03082b --- /dev/null +++ b/tests/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver_repro04.test.ts @@ -0,0 +1,180 @@ +import { test, expect } from "bun:test" +import { SchematicTracePipelineSolver } from "lib/index" +import type { InputProblem } from "lib/types/InputProblem" + +const inputProblem: InputProblem = { + chips: [ + { + chipId: "schematic_component_0", + center: { + x: 0, + y: 0, + }, + width: 0.4, + height: 2.5999999999999996, + pins: [ + { + pinId: "P.1", + x: -0.6000000000000001, + y: 1.0999999999999999, + }, + { + pinId: "P.2", + x: -0.6000000000000001, + y: 0.8999999999999999, + }, + { + pinId: "P.3", + x: -0.6000000000000001, + y: 0.6999999999999998, + }, + { + pinId: "P.4", + x: -0.6000000000000001, + y: 0.4999999999999998, + }, + { + pinId: "P.5", + x: -0.6000000000000001, + y: 0.2999999999999998, + }, + { + pinId: "P.6", + x: -0.6000000000000001, + y: 0.09999999999999987, + }, + { + pinId: "P.7", + x: -0.6000000000000001, + y: -0.10000000000000009, + }, + { + pinId: "P.8", + x: -0.6000000000000001, + y: -0.30000000000000004, + }, + { + pinId: "P.9", + x: -0.6000000000000001, + y: -0.5, + }, + { + pinId: "P.10", + x: -0.6000000000000001, + y: -0.7, + }, + { + pinId: "P.11", + x: -0.6000000000000001, + y: -0.8999999999999999, + }, + { + pinId: "P.12", + x: -0.6000000000000001, + y: -1.0999999999999999, + }, + { + pinId: "P.13", + x: 0.6000000000000001, + y: -1.0999999999999999, + }, + { + pinId: "P.14", + x: 0.6000000000000001, + y: -0.8999999999999999, + }, + { + pinId: "P.15", + x: 0.6000000000000001, + y: -0.6999999999999998, + }, + { + pinId: "P.16", + x: 0.6000000000000001, + y: -0.4999999999999998, + }, + { + pinId: "P.17", + x: 0.6000000000000001, + y: -0.2999999999999998, + }, + { + pinId: "P.18", + x: 0.6000000000000001, + y: -0.09999999999999987, + }, + { + pinId: "P.19", + x: 0.6000000000000001, + y: 0.10000000000000009, + }, + { + pinId: "P.20", + x: 0.6000000000000001, + y: 0.30000000000000004, + }, + { + pinId: "P.21", + x: 0.6000000000000001, + y: 0.5, + }, + { + pinId: "P.22", + x: 0.6000000000000001, + y: 0.7, + }, + { + pinId: "P.23", + x: 0.6000000000000001, + y: 0.8999999999999999, + }, + { + pinId: "P.24", + x: 0.6000000000000001, + y: 1.0999999999999999, + }, + ], + }, + ], + directConnections: [ + { + pinIds: ["P.1", "P.18"], + netId: "P.pin1 to P.pin18", + }, + { + pinIds: ["P.2", "P.17"], + netId: "P.pin2 to P.pin17", + }, + { + pinIds: ["P.3", "P.16"], + netId: "P.pin3 to P.pin16", + }, + { + pinIds: ["P.4", "P.15"], + netId: "P.pin4 to P.pin15", + }, + { + pinIds: ["P.5", "P.14"], + netId: "P.pin5 to P.pin14", + }, + { + pinIds: ["P.6", "P.13"], + netId: "P.pin6 to P.pin13", + }, + { + pinIds: ["P.7", "P.19"], + netId: "P.pin7 to P.pin19", + }, + ], + netConnections: [], + availableNetLabelOrientations: {}, + maxMspPairDistance: 2.4, +} + +test("SchematicTracePipelineSolver_repro04 resolves castellated pinout without infinite loop", () => { + const solver = new SchematicTracePipelineSolver(inputProblem) + solver.solve() + + expect(solver.solved).toBe(true) + expect(solver.iterations).toBeLessThan(1000) +}) From f1b18f1d2608b4ace4c5e2d4ab9e2f25c955e92e Mon Sep 17 00:00:00 2001 From: Severin Ibarluzea Date: Mon, 10 Nov 2025 18:25:07 -0800 Subject: [PATCH 2/2] Set bun version to 1.3.1 in workflow --- .github/workflows/bun-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bun-test.yml b/.github/workflows/bun-test.yml index a6300fe..d9e5022 100644 --- a/.github/workflows/bun-test.yml +++ b/.github/workflows/bun-test.yml @@ -19,7 +19,7 @@ jobs: - name: Setup bun uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: 1.3.1 - name: Install dependencies run: bun install