From ba32fd6cea57e2c4f260e27332b48e3e469a315f Mon Sep 17 00:00:00 2001 From: nailoo Date: Mon, 8 Sep 2025 11:38:29 +0530 Subject: [PATCH 1/3] feat: shift traces away from net labels --- .../NetLabelTraceShiftSolver.ts | 150 ++++++++++++++++++ .../SchematicTracePipelineSolver.ts | 16 ++ .../NetLabelTraceShiftSolver_basic.test.ts | 79 +++++++++ 3 files changed, 245 insertions(+) create mode 100644 lib/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver.ts create mode 100644 tests/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver_basic.test.ts diff --git a/lib/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver.ts b/lib/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver.ts new file mode 100644 index 0000000..073f714 --- /dev/null +++ b/lib/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver.ts @@ -0,0 +1,150 @@ +import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver" +import type { InputProblem } from "lib/types/InputProblem" +import type { + MspConnectionPairId, + MspConnectionPair, +} from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver" +import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver" +import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver" +import { generateElbowVariants } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/generateElbowVariants" +import type { Guideline } from "lib/solvers/GuidelinesSolver/GuidelinesSolver" +import type { Point } from "@tscircuit/math-utils" +import { segmentIntersectsRect } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions" + +const EPS = 1e-9 + +export class NetLabelTraceShiftSolver extends BaseSolver { + inputProblem: InputProblem + inputTraceMap: Record + netLabelPlacements: NetLabelPlacement[] + correctedTraceMap: Record + + constructor(params: { + inputProblem: InputProblem + inputTraceMap: Record + netLabelPlacements: NetLabelPlacement[] + }) { + super() + this.inputProblem = params.inputProblem + this.inputTraceMap = params.inputTraceMap + this.netLabelPlacements = params.netLabelPlacements + this.correctedTraceMap = structuredClone(params.inputTraceMap) + } + + override getConstructorParams(): ConstructorParameters[0] { + return { + inputProblem: this.inputProblem, + inputTraceMap: this.inputTraceMap, + netLabelPlacements: this.netLabelPlacements, + } + } + + private findCollision( + rect: { minX: number; minY: number; maxX: number; maxY: number }, + ignorePairIds: Set, + ): { mspPairId: MspConnectionPairId; segIndex: number } | null { + for (const [pairId, solved] of Object.entries(this.correctedTraceMap)) { + if (ignorePairIds.has(pairId)) continue + const pts = solved.tracePath + for (let i = 0; i < pts.length - 1; i++) { + if (segmentIntersectsRect(pts[i]!, pts[i + 1]!, rect, EPS)) { + return { mspPairId: pairId, segIndex: i } + } + } + } + return null + } + + private variantIntersectsRect( + pts: Point[], + rect: { minX: number; minY: number; maxX: number; maxY: number }, + ): boolean { + for (let i = 0; i < pts.length - 1; i++) { + if (segmentIntersectsRect(pts[i]!, pts[i + 1]!, rect, EPS)) return true + } + return false + } + + override _step() { + for (const label of this.netLabelPlacements) { + const rect = { + minX: label.center.x - label.width / 2, + maxX: label.center.x + label.width / 2, + minY: label.center.y - label.height / 2, + maxY: label.center.y + label.height / 2, + } + + const ignorePairIds = new Set( + label.mspConnectionPairIds, + ) + + let collision = this.findCollision(rect, ignorePairIds) + let guard = 0 + while (collision && guard++ < 10) { + const path = this.correctedTraceMap[collision.mspPairId] + if (!path) break + const pts = path.tracePath + const segIndex = collision.segIndex + if (segIndex <= 0 || segIndex >= pts.length - 2) break + + const baseElbow = pts.slice(segIndex - 1, segIndex + 3) + + const isVert = Math.abs(baseElbow[1]!.x - baseElbow[2]!.x) < EPS + const guidelines: Guideline[] = [] + const margin = 0.2 + if (isVert) { + guidelines.push({ + orientation: "vertical", + x: rect.minX - margin, + y: undefined, + }) + guidelines.push({ + orientation: "vertical", + x: rect.maxX + margin, + y: undefined, + }) + } else { + guidelines.push({ + orientation: "horizontal", + y: rect.minY - margin, + x: undefined, + }) + guidelines.push({ + orientation: "horizontal", + y: rect.maxY + margin, + x: undefined, + }) + } + + const { elbowVariants } = generateElbowVariants({ + baseElbow, + guidelines, + }) + + let replaced = false + for (const variant of elbowVariants.slice(1)) { + if (this.variantIntersectsRect(variant, rect)) continue + const newPath = [ + ...pts.slice(0, segIndex - 1), + ...variant, + ...pts.slice(segIndex + 3), + ] + this.correctedTraceMap[collision.mspPairId] = { + ...(path as MspConnectionPair), + tracePath: newPath, + mspConnectionPairIds: path.mspConnectionPairIds, + pinIds: path.pinIds, + } as SolvedTracePath + replaced = true + break + } + + if (!replaced) break + collision = this.findCollision(rect, ignorePairIds) + } + } + + this.solved = true + } +} + diff --git a/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts b/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts index 57138de..ba49ce2 100644 --- a/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +++ b/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts @@ -10,6 +10,7 @@ import { MspConnectionPairSolver } from "../MspConnectionPairSolver/MspConnectio import { SchematicTraceLinesSolver } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver" import { TraceOverlapShiftSolver } from "../TraceOverlapShiftSolver/TraceOverlapShiftSolver" import { NetLabelPlacementSolver } from "../NetLabelPlacementSolver/NetLabelPlacementSolver" +import { NetLabelTraceShiftSolver } from "../NetLabelTraceShiftSolver/NetLabelTraceShiftSolver" import { visualizeInputProblem } from "./visualizeInputProblem" import { GuidelinesSolver } from "../GuidelinesSolver/GuidelinesSolver" import { getInputChipBounds } from "../GuidelinesSolver/getInputChipBounds" @@ -52,6 +53,7 @@ export class SchematicTracePipelineSolver extends BaseSolver { schematicTraceLinesSolver?: SchematicTraceLinesSolver traceOverlapShiftSolver?: TraceOverlapShiftSolver netLabelPlacementSolver?: NetLabelPlacementSolver + netLabelTraceShiftSolver?: NetLabelTraceShiftSolver startTimeOfPhase: Record endTimeOfPhase: Record @@ -134,6 +136,20 @@ export class SchematicTracePipelineSolver extends BaseSolver { }, }, ), + definePipelineStep( + "netLabelTraceShiftSolver", + NetLabelTraceShiftSolver, + () => [ + { + inputProblem: this.inputProblem, + inputTraceMap: this.netLabelPlacementSolver!.inputTraceMap, + netLabelPlacements: this.netLabelPlacementSolver!.netLabelPlacements, + }, + ], + { + onSolved: (_solver) => {}, + }, + ), ] constructor(inputProblem: InputProblem) { diff --git a/tests/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver_basic.test.ts b/tests/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver_basic.test.ts new file mode 100644 index 0000000..cb5e1bb --- /dev/null +++ b/tests/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver_basic.test.ts @@ -0,0 +1,79 @@ +import { test, expect } from "bun:test" +import type { InputProblem } from "lib/types/InputProblem" +import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver" +import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver" +import { NetLabelTraceShiftSolver } from "lib/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver" +import { segmentIntersectsRect } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions" + +const inputProblem: InputProblem = { + chips: [], + directConnections: [], + netConnections: [], + availableNetLabelOrientations: {}, +} + +test("NetLabelTraceShiftSolver moves trace away from net label", () => { + const trace: SolvedTracePath = { + mspPairId: "t1", + dcConnNetId: "d1", + globalConnNetId: "g1", + pins: [ + { pinId: "p1", chipId: "c1", x: 0, y: 0 }, + { pinId: "p2", chipId: "c2", x: 5, y: 2 }, + ], + tracePath: [ + { x: 0, y: 0 }, + { x: 0, y: 1 }, + { x: 3, y: 1 }, + { x: 3, y: 2 }, + { x: 5, y: 2 }, + ], + mspConnectionPairIds: ["t1"], + pinIds: ["p1", "p2"], + } + + const label: NetLabelPlacement = { + globalConnNetId: "g2", + dcConnNetId: undefined, + netId: undefined, + mspConnectionPairIds: [], + pinIds: [], + orientation: "x+", + anchorPoint: { x: 0, y: 0 }, + width: 1, + height: 0.5, + center: { x: 1.5, y: 1 }, + } + + const solver = new NetLabelTraceShiftSolver({ + inputProblem, + inputTraceMap: { t1: trace }, + netLabelPlacements: [label], + }) + + solver.solve() + + const newPath = solver.correctedTraceMap["t1"].tracePath + const rect = { + minX: label.center.x - label.width / 2, + maxX: label.center.x + label.width / 2, + minY: label.center.y - label.height / 2, + maxY: label.center.y + label.height / 2, + } + + // Ensure the horizontal segment moved outside the label bounds + expect( + newPath[1].y < rect.minY || newPath[1].y > rect.maxY, + ).toBe(true) + + // Ensure the final path no longer intersects the label rectangle + let intersects = false + for (let i = 0; i < newPath.length - 1; i++) { + if (segmentIntersectsRect(newPath[i]!, newPath[i + 1]!, rect)) { + intersects = true + break + } + } + expect(intersects).toBe(false) +}) + From 536ad44bfaa2eac73fe6a72fe66075e55a8a0098 Mon Sep 17 00:00:00 2001 From: nailoo Date: Mon, 8 Sep 2025 11:40:57 +0530 Subject: [PATCH 2/3] Delee test --- .../NetLabelTraceShiftSolver_basic.test.ts | 79 ------------------- 1 file changed, 79 deletions(-) delete mode 100644 tests/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver_basic.test.ts diff --git a/tests/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver_basic.test.ts b/tests/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver_basic.test.ts deleted file mode 100644 index cb5e1bb..0000000 --- a/tests/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver_basic.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { test, expect } from "bun:test" -import type { InputProblem } from "lib/types/InputProblem" -import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver" -import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver" -import { NetLabelTraceShiftSolver } from "lib/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver" -import { segmentIntersectsRect } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions" - -const inputProblem: InputProblem = { - chips: [], - directConnections: [], - netConnections: [], - availableNetLabelOrientations: {}, -} - -test("NetLabelTraceShiftSolver moves trace away from net label", () => { - const trace: SolvedTracePath = { - mspPairId: "t1", - dcConnNetId: "d1", - globalConnNetId: "g1", - pins: [ - { pinId: "p1", chipId: "c1", x: 0, y: 0 }, - { pinId: "p2", chipId: "c2", x: 5, y: 2 }, - ], - tracePath: [ - { x: 0, y: 0 }, - { x: 0, y: 1 }, - { x: 3, y: 1 }, - { x: 3, y: 2 }, - { x: 5, y: 2 }, - ], - mspConnectionPairIds: ["t1"], - pinIds: ["p1", "p2"], - } - - const label: NetLabelPlacement = { - globalConnNetId: "g2", - dcConnNetId: undefined, - netId: undefined, - mspConnectionPairIds: [], - pinIds: [], - orientation: "x+", - anchorPoint: { x: 0, y: 0 }, - width: 1, - height: 0.5, - center: { x: 1.5, y: 1 }, - } - - const solver = new NetLabelTraceShiftSolver({ - inputProblem, - inputTraceMap: { t1: trace }, - netLabelPlacements: [label], - }) - - solver.solve() - - const newPath = solver.correctedTraceMap["t1"].tracePath - const rect = { - minX: label.center.x - label.width / 2, - maxX: label.center.x + label.width / 2, - minY: label.center.y - label.height / 2, - maxY: label.center.y + label.height / 2, - } - - // Ensure the horizontal segment moved outside the label bounds - expect( - newPath[1].y < rect.minY || newPath[1].y > rect.maxY, - ).toBe(true) - - // Ensure the final path no longer intersects the label rectangle - let intersects = false - for (let i = 0; i < newPath.length - 1; i++) { - if (segmentIntersectsRect(newPath[i]!, newPath[i + 1]!, rect)) { - intersects = true - break - } - } - expect(intersects).toBe(false) -}) - From 7c8f3f466fa8ab670d04290e063075658750863a Mon Sep 17 00:00:00 2001 From: nailoo Date: Mon, 8 Sep 2025 13:58:20 +0530 Subject: [PATCH 3/3] fix trace --- .../NetLabelTraceShiftSolver.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver.ts b/lib/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver.ts index 073f714..8263e66 100644 --- a/lib/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver.ts +++ b/lib/solvers/NetLabelTraceShiftSolver/NetLabelTraceShiftSolver.ts @@ -116,10 +116,22 @@ export class NetLabelTraceShiftSolver extends BaseSolver { }) } - const { elbowVariants } = generateElbowVariants({ - baseElbow, - guidelines, + const isOrthogonal = baseElbow.every((p, i) => { + if (i === 0) return true + const prev = baseElbow[i - 1]! + return Math.abs(p.x - prev.x) < EPS || Math.abs(p.y - prev.y) < EPS }) + if (!isOrthogonal) break + + let elbowVariants: Point[][] + try { + elbowVariants = generateElbowVariants({ + baseElbow, + guidelines, + }).elbowVariants + } catch { + break + } let replaced = false for (const variant of elbowVariants.slice(1)) { @@ -147,4 +159,3 @@ export class NetLabelTraceShiftSolver extends BaseSolver { this.solved = true } } -