diff --git a/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts b/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts index e9bac7ca3..74560ab13 100644 --- a/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +++ b/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts @@ -20,6 +20,7 @@ interface TraceCleanupSolverInput { import { UntangleTraceSubsolver } from "./sub-solver/UntangleTraceSubsolver" import { is4PointRectangle } from "./is4PointRectangle" +import { mergeCloseSameNetTraceSegments } from "./mergeCloseSameNetTraceSegments" /** * Represents the different stages or steps within the trace cleanup pipeline. @@ -27,6 +28,7 @@ import { is4PointRectangle } from "./is4PointRectangle" type PipelineStep = | "minimizing_turns" | "balancing_l_shapes" + | "merging_close_same_net_segments" | "untangling_traces" /** @@ -84,6 +86,9 @@ export class TraceCleanupSolver extends BaseSolver { case "balancing_l_shapes": this._runBalanceLShapesStep() break + case "merging_close_same_net_segments": + this._runMergeCloseSameNetSegmentsStep() + break } } @@ -108,13 +113,19 @@ export class TraceCleanupSolver extends BaseSolver { private _runBalanceLShapesStep() { if (this.traceIdQueue.length === 0) { - this.solved = true + this.pipelineStep = "merging_close_same_net_segments" return } this._processTrace("balancing_l_shapes") } + private _runMergeCloseSameNetSegmentsStep() { + this.outputTraces = mergeCloseSameNetTraceSegments(this.outputTraces) + this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t])) + this.solved = true + } + private _processTrace(step: "minimizing_turns" | "balancing_l_shapes") { const targetMspConnectionPairId = this.traceIdQueue.shift()! this.activeTraceId = targetMspConnectionPairId diff --git a/lib/solvers/TraceCleanupSolver/mergeCloseSameNetTraceSegments.ts b/lib/solvers/TraceCleanupSolver/mergeCloseSameNetTraceSegments.ts new file mode 100644 index 000000000..20e48f5e8 --- /dev/null +++ b/lib/solvers/TraceCleanupSolver/mergeCloseSameNetTraceSegments.ts @@ -0,0 +1,138 @@ +import type { Point } from "graphics-debug" +import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver" +import { simplifyPath } from "./simplifyPath" + +const EPS = 1e-6 +const DEFAULT_MERGE_DISTANCE = 0.12 + +type Segment = { + traceIndex: number + segmentIndex: number + p1: Point + p2: Point + orientation: "horizontal" | "vertical" + fixedCoord: number + minRange: number + maxRange: number +} + +const getSegment = ( + trace: SolvedTracePath, + traceIndex: number, + segmentIndex: number, +): Segment | null => { + const p1 = trace.tracePath[segmentIndex]! + const p2 = trace.tracePath[segmentIndex + 1]! + + if (Math.abs(p1.y - p2.y) < EPS) { + return { + traceIndex, + segmentIndex, + p1, + p2, + orientation: "horizontal", + fixedCoord: p1.y, + minRange: Math.min(p1.x, p2.x), + maxRange: Math.max(p1.x, p2.x), + } + } + + if (Math.abs(p1.x - p2.x) < EPS) { + return { + traceIndex, + segmentIndex, + p1, + p2, + orientation: "vertical", + fixedCoord: p1.x, + minRange: Math.min(p1.y, p2.y), + maxRange: Math.max(p1.y, p2.y), + } + } + + return null +} + +const rangesOverlap = (a: Segment, b: Segment) => + Math.min(a.maxRange, b.maxRange) - Math.max(a.minRange, b.minRange) > EPS + +const sameNet = (a: SolvedTracePath, b: SolvedTracePath) => + a.globalConnNetId === b.globalConnNetId + +const setSegmentFixedCoord = ( + path: Point[], + segmentIndex: number, + orientation: Segment["orientation"], + coord: number, +) => { + if (orientation === "horizontal") { + path[segmentIndex] = { ...path[segmentIndex]!, y: coord } + path[segmentIndex + 1] = { ...path[segmentIndex + 1]!, y: coord } + } else { + path[segmentIndex] = { ...path[segmentIndex]!, x: coord } + path[segmentIndex + 1] = { ...path[segmentIndex + 1]!, x: coord } + } +} + +const isInternalSegment = (trace: SolvedTracePath, segmentIndex: number) => + segmentIndex > 0 && segmentIndex < trace.tracePath.length - 2 + +export const mergeCloseSameNetTraceSegments = ( + traces: SolvedTracePath[], + mergeDistance = DEFAULT_MERGE_DISTANCE, +): SolvedTracePath[] => { + const outputTraces = traces.map((trace) => ({ + ...trace, + tracePath: trace.tracePath.map((point) => ({ ...point })), + })) + + for (let traceAIndex = 0; traceAIndex < outputTraces.length; traceAIndex++) { + const traceA = outputTraces[traceAIndex]! + + for ( + let segAIndex = 0; + segAIndex < traceA.tracePath.length - 1; + segAIndex++ + ) { + const segmentA = getSegment(traceA, traceAIndex, segAIndex) + if (!segmentA) continue + + for ( + let traceBIndex = traceAIndex + 1; + traceBIndex < outputTraces.length; + traceBIndex++ + ) { + const traceB = outputTraces[traceBIndex]! + if (!sameNet(traceA, traceB)) continue + + for ( + let segBIndex = 0; + segBIndex < traceB.tracePath.length - 1; + segBIndex++ + ) { + if (!isInternalSegment(traceB, segBIndex)) continue + + const segmentB = getSegment(traceB, traceBIndex, segBIndex) + if (!segmentB) continue + if (segmentA.orientation !== segmentB.orientation) continue + if (!rangesOverlap(segmentA, segmentB)) continue + + const distance = Math.abs(segmentA.fixedCoord - segmentB.fixedCoord) + if (distance < EPS || distance > mergeDistance) continue + + setSegmentFixedCoord( + traceB.tracePath, + segmentB.segmentIndex, + segmentB.orientation, + segmentA.fixedCoord, + ) + } + } + } + } + + return outputTraces.map((trace) => ({ + ...trace, + tracePath: simplifyPath(trace.tracePath), + })) +} diff --git a/tests/solvers/TraceCleanupSolver/mergeCloseSameNetTraceSegments.test.ts b/tests/solvers/TraceCleanupSolver/mergeCloseSameNetTraceSegments.test.ts new file mode 100644 index 000000000..70296c1ca --- /dev/null +++ b/tests/solvers/TraceCleanupSolver/mergeCloseSameNetTraceSegments.test.ts @@ -0,0 +1,65 @@ +import { expect, test } from "bun:test" +import { mergeCloseSameNetTraceSegments } from "lib/solvers/TraceCleanupSolver/mergeCloseSameNetTraceSegments" +import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver" + +const makeTrace = ( + mspPairId: string, + globalConnNetId: string, + tracePath: SolvedTracePath["tracePath"], +): SolvedTracePath => + ({ + mspPairId, + globalConnNetId, + tracePath, + mspConnectionPairIds: [mspPairId], + pinIds: [], + pins: [], + }) as unknown as SolvedTracePath + +test("merges close same-net parallel trace segments", () => { + const traces = [ + makeTrace("a", "net1", [ + { x: 0, y: 0 }, + { x: 3, y: 0 }, + ]), + makeTrace("b", "net1", [ + { x: 1, y: -1 }, + { x: 1, y: 0.08 }, + { x: 4, y: 0.08 }, + { x: 4, y: 1 }, + ]), + ] + + const result = mergeCloseSameNetTraceSegments(traces) + + expect(result[1]!.tracePath).toEqual([ + { x: 1, y: -1 }, + { x: 1, y: 0 }, + { x: 4, y: 0 }, + { x: 4, y: 1 }, + ]) +}) + +test("does not merge close parallel trace segments from different nets", () => { + const traces = [ + makeTrace("a", "net1", [ + { x: 0, y: 0 }, + { x: 3, y: 0 }, + ]), + makeTrace("b", "net2", [ + { x: 1, y: -1 }, + { x: 1, y: 0.08 }, + { x: 4, y: 0.08 }, + { x: 4, y: 1 }, + ]), + ] + + const result = mergeCloseSameNetTraceSegments(traces) + + expect(result[1]!.tracePath).toEqual([ + { x: 1, y: -1 }, + { x: 1, y: 0.08 }, + { x: 4, y: 0.08 }, + { x: 4, y: 1 }, + ]) +})