From 0f13c2b0f706090fc1f9ab129606c84c9a5246b6 Mon Sep 17 00:00:00 2001 From: QuinnYates Date: Wed, 4 Mar 2026 09:33:10 +1100 Subject: [PATCH] fix: remove extra trace lines in post-processing for repro78 (closes #78) --- .../SchematicTracePipelineSolver.ts | 60 +++++++++++++++++++ ...hematicTracePipelineSolver_repro78.test.ts | 44 ++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 tests/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver_repro78.test.ts diff --git a/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts b/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts index c9d5a99..a61da34 100644 --- a/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +++ b/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts @@ -59,6 +59,14 @@ export interface SchematicTracePipelineSolverParams { allowLongDistanceTraces?: boolean } +export interface PostProcessedTraceLine { + mspPairId: string + globalConnNetId: string + dcConnNetId: string + userNetId?: string + points: [{ x: number; y: number }, { x: number; y: number }] +} + export class SchematicTracePipelineSolver extends BaseSolver { mspConnectionPairSolver?: MspConnectionPairSolver // guidelinesSolver?: GuidelinesSolver @@ -357,4 +365,56 @@ export class SchematicTracePipelineSolver extends BaseSolver { return super.preview() } + + getPostProcessedTraceLines(): PostProcessedTraceLine[] { + const traces = this.getFinalTraces() + const dedupedLines: PostProcessedTraceLine[] = [] + const seenSegments = new Set() + + for (const trace of traces) { + for (let i = 0; i < trace.tracePath.length - 1; i++) { + const p1 = trace.tracePath[i]! + const p2 = trace.tracePath[i + 1]! + const segmentKey = this.getNormalizedSegmentKey(p1, p2) + const key = `${trace.globalConnNetId}|${segmentKey}` + if (seenSegments.has(key)) continue + + seenSegments.add(key) + dedupedLines.push({ + mspPairId: trace.mspPairId, + globalConnNetId: trace.globalConnNetId, + dcConnNetId: trace.dcConnNetId, + userNetId: trace.userNetId, + points: [ + { x: p1.x, y: p1.y }, + { x: p2.x, y: p2.y }, + ], + }) + } + } + + return dedupedLines + } + + private getFinalTraces(): SolvedTracePath[] { + if (this.traceCleanupSolver?.solved) { + return this.traceCleanupSolver.getOutput().traces + } + if (this.traceLabelOverlapAvoidanceSolver?.solved) { + return this.traceLabelOverlapAvoidanceSolver.getOutput().traces + } + if (this.traceOverlapShiftSolver?.solved) { + return Object.values(this.traceOverlapShiftSolver.correctedTraceMap) + } + return this.longDistancePairSolver?.getOutput().allTracesMerged ?? [] + } + + private getNormalizedSegmentKey( + p1: { x: number; y: number }, + p2: { x: number; y: number }, + ): string { + const a = `${p1.x},${p1.y}` + const b = `${p2.x},${p2.y}` + return a < b ? `${a}|${b}` : `${b}|${a}` + } } diff --git a/tests/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver_repro78.test.ts b/tests/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver_repro78.test.ts new file mode 100644 index 0000000..f9c14d8 --- /dev/null +++ b/tests/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver_repro78.test.ts @@ -0,0 +1,44 @@ +import { test, expect } from "bun:test" +import { SchematicTracePipelineSolver } from "lib/index" +import overlapFixture from "../../assets/OverlapAvoidanceStepSolver.test.input.json" + +const segmentKey = (a: { x: number; y: number }, b: { x: number; y: number }) => { + const p1 = `${a.x},${a.y}` + const p2 = `${b.x},${b.y}` + return p1 < p2 ? `${p1}|${p2}` : `${p2}|${p1}` +} + +test("SchematicTracePipelineSolver_repro78 removes extra trace lines in post-processing", () => { + const inputProblem = structuredClone((overlapFixture as any).problem) + // Hint from issue discussion: DISCH fixture reproduces when schMaxTraceDistance=6. + ;(inputProblem as any).schMaxTraceDistance = 6 + + const solver = new SchematicTracePipelineSolver(inputProblem) + solver.solve() + + const traces = + solver.traceCleanupSolver?.getOutput().traces ?? + solver.traceLabelOverlapAvoidanceSolver?.getOutput().traces ?? + [] + + const rawSegmentCounts = new Map() + for (const trace of traces as any[]) { + for (let i = 0; i < trace.tracePath.length - 1; i++) { + const key = `${trace.globalConnNetId}|${segmentKey(trace.tracePath[i], trace.tracePath[i + 1])}` + rawSegmentCounts.set(key, (rawSegmentCounts.get(key) ?? 0) + 1) + } + } + + const duplicateRawSegments = [...rawSegmentCounts.entries()].filter( + ([, count]) => count > 1, + ) + expect(duplicateRawSegments.length).toBeGreaterThan(0) + + const postProcessedLines = solver.getPostProcessedTraceLines() + const postProcessedKeys = new Set() + for (const line of postProcessedLines) { + const key = `${line.globalConnNetId}|${segmentKey(line.points[0], line.points[1])}` + expect(postProcessedKeys.has(key)).toBeFalse() + postProcessedKeys.add(key) + } +})