From 43032752c144c0f7d65f1b06b010ac0bc95b6b47 Mon Sep 17 00:00:00 2001 From: anzal787 Date: Mon, 19 Jan 2026 06:09:35 -0800 Subject: [PATCH] Refactor TraceCleanupSolver for improved readability Refactor Trace CleanupSolver to improve code clarity and maintainability. Consolidate imports, streamline trace processing, and enhance visualization logic. --- .../TraceCleanupSolver/TraceCleanupSolver.ts | 384 ++++++++++-------- 1 file changed, 210 insertions(+), 174 deletions(-) diff --git a/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts b/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts index e9bac7c..b83e608 100644 --- a/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +++ b/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts @@ -1,180 +1,216 @@ -import type { InputProblem } from "lib/types/InputProblem" -import type { GraphicsObject, Line } from "graphics-debug" -import { minimizeTurnsWithFilteredLabels } from "./minimizeTurnsWithFilteredLabels" -import { balanceZShapes } from "./balanceZShapes" -import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver" -import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver" -import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem" -import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver" - -/** - * Defines the input structure for the TraceCleanupSolver. - */ + +import type { InputProblem } from "lib/types/InputProblem"; +import type { GraphicsObject, Line } from "graphics-debug"; +import { minimizeTurnsWithFilteredLabels } from "./minimizeTurnsWithFilteredLabels"; +import { balanceZShapes } from "./balanceZShapes"; +import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"; +import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"; +import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"; +import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"; +import { UntangleTraceSubsolver } from "./sub-solver/UntangleTraceSubsolver"; +import { is4PointRectangle } from "./is4PointRectangle"; + interface TraceCleanupSolverInput { - inputProblem: InputProblem - allTraces: SolvedTracePath[] - allLabelPlacements: NetLabelPlacement[] - mergedLabelNetIdMap: Record> - paddingBuffer: number + inputProblem: InputProblem; + allTraces: SolvedTracePath[]; + allLabelPlacements: NetLabelPlacement[]; + mergedLabelNetIdMap: Record>; + paddingBuffer: number; } -import { UntangleTraceSubsolver } from "./sub-solver/UntangleTraceSubsolver" -import { is4PointRectangle } from "./is4PointRectangle" - -/** - * Represents the different stages or steps within the trace cleanup pipeline. - */ type PipelineStep = - | "minimizing_turns" - | "balancing_l_shapes" - | "untangling_traces" - -/** - * The TraceCleanupSolver is responsible for improving the aesthetics and readability of schematic traces. - * It operates in a multi-step pipeline: - * 1. **Untangling Traces**: It first attempts to untangle any overlapping or highly convoluted traces using a sub-solver. - * 2. **Minimizing Turns**: After untangling, it iterates through each trace to minimize the number of turns, simplifying their paths. - * 3. **Balancing L-Shapes**: Finally, it balances L-shaped trace segments to create more visually appealing and consistent layouts. - * The solver processes traces one by one, applying these cleanup steps sequentially to refine the overall trace layout. - */ + | "minimizing_turns" + | "balancing_l_shapes" + | "untangling_traces"; + export class TraceCleanupSolver extends BaseSolver { - private input: TraceCleanupSolverInput - private outputTraces: SolvedTracePath[] - private traceIdQueue: string[] - private tracesMap: Map - private pipelineStep: PipelineStep = "untangling_traces" - private activeTraceId: string | null = null // New property - override activeSubSolver: BaseSolver | null = null - - constructor(solverInput: TraceCleanupSolverInput) { - super() - this.input = solverInput - this.outputTraces = [...solverInput.allTraces] - this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t])) - this.traceIdQueue = Array.from( - solverInput.allTraces.map((e) => e.mspPairId), - ) - } - - override _step() { - if (this.activeSubSolver) { - this.activeSubSolver.step() - if (this.activeSubSolver.solved) { - const output = ( - this.activeSubSolver as UntangleTraceSubsolver - ).getOutput() - this.outputTraces = output.traces - this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t])) - this.activeSubSolver = null - this.pipelineStep = "minimizing_turns" - } else if (this.activeSubSolver.failed) { - this.activeSubSolver = null - this.pipelineStep = "minimizing_turns" - } - return - } - - switch (this.pipelineStep) { - case "untangling_traces": - this._runUntangleTracesStep() - break - case "minimizing_turns": - this._runMinimizeTurnsStep() - break - case "balancing_l_shapes": - this._runBalanceLShapesStep() - break - } - } - - private _runUntangleTracesStep() { - this.activeSubSolver = new UntangleTraceSubsolver({ - ...this.input, - allTraces: Array.from(this.tracesMap.values()), - }) - } - - private _runMinimizeTurnsStep() { - if (this.traceIdQueue.length === 0) { - this.pipelineStep = "balancing_l_shapes" - this.traceIdQueue = Array.from( - this.input.allTraces.map((e) => e.mspPairId), - ) - return - } - - this._processTrace("minimizing_turns") - } - - private _runBalanceLShapesStep() { - if (this.traceIdQueue.length === 0) { - this.solved = true - return - } - - this._processTrace("balancing_l_shapes") - } - - private _processTrace(step: "minimizing_turns" | "balancing_l_shapes") { - const targetMspConnectionPairId = this.traceIdQueue.shift()! - this.activeTraceId = targetMspConnectionPairId - const originalTrace = this.tracesMap.get(targetMspConnectionPairId)! - - if (is4PointRectangle(originalTrace.tracePath)) { - return - } - - const allTraces = Array.from(this.tracesMap.values()) - - let updatedTrace: SolvedTracePath - - if (step === "minimizing_turns") { - updatedTrace = minimizeTurnsWithFilteredLabels({ - ...this.input, - targetMspConnectionPairId, - traces: allTraces, - }) - } else { - updatedTrace = balanceZShapes({ - ...this.input, - targetMspConnectionPairId, - traces: allTraces, - }) - } - - this.tracesMap.set(targetMspConnectionPairId, updatedTrace) - this.outputTraces = Array.from(this.tracesMap.values()) - } - - getOutput() { - return { - traces: this.outputTraces, - } - } - - override visualize(): GraphicsObject { - if (this.activeSubSolver) { - return this.activeSubSolver.visualize() - } - - const graphics = visualizeInputProblem(this.input.inputProblem, { - chipAlpha: 0.1, - connectionAlpha: 0.1, - }) - - if (!graphics.lines) graphics.lines = [] - if (!graphics.points) graphics.points = [] - if (!graphics.rects) graphics.rects = [] - if (!graphics.circles) graphics.circles = [] - if (!graphics.texts) graphics.texts = [] - - for (const trace of this.outputTraces) { - const line: Line = { - points: trace.tracePath.map((p) => ({ x: p.x, y: p.y })), - strokeColor: trace.mspPairId === this.activeTraceId ? "red" : "blue", // Highlight active trace - } - graphics.lines!.push(line) - } - return graphics - } + private input: TraceCleanupSolverInput; + private outputTraces: SolvedTracePath[]; + private traceIdQueue: string[]; + private tracesMap: Map; + private pipelineStep: PipelineStep = "untangling_traces"; + private activeTraceId: string | null = null; + override activeSubSolver: BaseSolver | null = null; + + constructor(solverInput: TraceCleanupSolverInput) { + super(); + this.input = solverInput; + this.outputTraces = [...solverInput.allTraces]; + this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t])); + this.traceIdQueue = solverInput.allTraces.map((e) => e.mspPairId); + } + + override _step() { + if (this.activeSubSolver) { + this.activeSubSolver.step(); + + if (this.activeSubSolver.solved) { + const output = ( + this.activeSubSolver as UntangleTraceSubsolver + ).getOutput(); + this.outputTraces = output.traces; + this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t])); + this.activeSubSolver = null; + this.pipelineStep = "minimizing_turns"; + } else if (this.activeSubSolver.failed) { + this.activeSubSolver = null; + this.pipelineStep = "minimizing_turns"; + } + return; + } + + switch (this.pipelineStep) { + case "untangling_traces": + this._runUntangleTracesStep(); + break; + case "minimizing_turns": + this._runMinimizeTurnsStep(); + break; + case "balancing_l_shapes": + this._runBalanceLShapesStep(); + break; + } + } + + private _runUntangleTracesStep() { + this.activeSubSolver = new UntangleTraceSubsolver({ + ...this.input, + allTraces: Array.from(this.tracesMap.values()), + }); + } + + private _runMinimizeTurnsStep() { + if (this.traceIdQueue.length === 0) { + this.pipelineStep = "balancing_l_shapes"; + this.traceIdQueue = this.input.allTraces.map((e) => e.mspPairId); + return; + } + this._processTrace("minimizing_turns"); + } + + private _runBalanceLShapesStep() { + if (this.traceIdQueue.length === 0) { + this._mergeNearbySameNetTraces(); + this.solved = true; + return; + } + this._processTrace("balancing_l_shapes"); + } + + private _processTrace(step: "minimizing_turns" | "balancing_l_shapes") { + const targetMspConnectionPairId = this.traceIdQueue.shift()!; + this.activeTraceId = targetMspConnectionPairId; + const originalTrace = this.tracesMap.get(targetMspConnectionPairId)!; + + if (is4PointRectangle(originalTrace.tracePath)) return; + + const allTraces = Array.from(this.tracesMap.values()); + let updatedTrace: SolvedTracePath; + + if (step === "minimizing_turns") { + updatedTrace = minimizeTurnsWithFilteredLabels({ + ...this.input, + targetMspConnectionPairId, + traces: allTraces, + }); + } else { + updatedTrace = balanceZShapes({ + ...this.input, + targetMspConnectionPairId, + traces: allTraces, + }); + } + + this.tracesMap.set(targetMspConnectionPairId, updatedTrace); + this.outputTraces = Array.from(this.tracesMap.values()); + } + + private _mergeNearbySameNetTraces() { + const threshold = 5; + const traces = [...this.outputTraces]; + const merged: SolvedTracePath[] = []; + const used = new Set(); + + const dist = (a: { x: number; y: number }, b: { x: number; y: number }) => + Math.hypot(a.x - b.x, a.y - b.y); + + for (let i = 0; i < traces.length; i++) { + if (used.has(i)) continue; + let base = { ...traces[i] }; + + for (let j = 0; j < traces.length; j++) { + if (i === j || used.has(j)) continue; + const other = traces[j]; + + const sameNet = + base.mspConnectionPairIds.some((id) => + other.mspConnectionPairIds.includes(id), + ) || + Object.values(this.input.mergedLabelNetIdMap).some( + (netSet) => + base.mspConnectionPairIds.some((id) => netSet.has(id)) && + other.mspConnectionPairIds.some((id) => netSet.has(id)), + ); + + if (!sameNet) continue; + + const aEnd = base.tracePath.at(-1); + const bStart = other.tracePath[0]; + const aStart = base.tracePath[0]; + const bEnd = other.tracePath.at(-1); + + if (!aEnd || !bStart || !aStart || !bEnd) continue; + + if (dist(aEnd, bStart) < threshold) { + base.tracePath = [...base.tracePath, ...other.tracePath]; + base.mspConnectionPairIds = Array.from( + new Set([ + ...base.mspConnectionPairIds, + ...other.mspConnectionPairIds, + ]), + ); + used.add(j); + } else if (dist(aStart, bEnd) < threshold) { + base.tracePath = [...other.tracePath, ...base.tracePath]; + base.mspConnectionPairIds = Array.from( + new Set([ + ...base.mspConnectionPairIds, + ...other.mspConnectionPairIds, + ]), + ); + used.add(j); + } + } + + used.add(i); + merged.push(base); + } + + this.outputTraces = merged; + this.tracesMap = new Map(merged.map((t) => [t.mspPairId, t])); + } + + getOutput() { + return { traces: this.outputTraces }; + } + + override visualize(): GraphicsObject { + if (this.activeSubSolver) return this.activeSubSolver.visualize(); + + const graphics = visualizeInputProblem(this.input.inputProblem, { + chipAlpha: 0.1, + connectionAlpha: 0.1, + }); + + if (!graphics.lines) graphics.lines = []; + + for (const trace of this.outputTraces) { + const line: Line = { + points: trace.tracePath.map((p) => ({ x: p.x, y: p.y })), + strokeColor: trace.mspPairId === this.activeTraceId ? "red" : "blue", + }; + graphics.lines.push(line); + } + return graphics; + } }