Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ interface TraceCleanupSolverInput {

import { UntangleTraceSubsolver } from "./sub-solver/UntangleTraceSubsolver"
import { is4PointRectangle } from "./is4PointRectangle"
import { mergeSameNetTraceSegments } from "./mergeSameNetTraceSegments"

/**
* Represents the different stages or steps within the trace cleanup pipeline.
*/
type PipelineStep =
| "merging_same_net_trace_segments"
| "minimizing_turns"
| "balancing_l_shapes"
| "untangling_traces"
Expand Down Expand Up @@ -66,10 +68,10 @@ export class TraceCleanupSolver extends BaseSolver {
this.outputTraces = output.traces
this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]))
this.activeSubSolver = null
this.pipelineStep = "minimizing_turns"
this.pipelineStep = "merging_same_net_trace_segments"
} else if (this.activeSubSolver.failed) {
this.activeSubSolver = null
this.pipelineStep = "minimizing_turns"
this.pipelineStep = "merging_same_net_trace_segments"
}
return
}
Expand All @@ -78,6 +80,9 @@ export class TraceCleanupSolver extends BaseSolver {
case "untangling_traces":
this._runUntangleTracesStep()
break
case "merging_same_net_trace_segments":
this._runMergeSameNetTraceSegmentsStep()
break
case "minimizing_turns":
this._runMinimizeTurnsStep()
break
Expand All @@ -94,6 +99,14 @@ export class TraceCleanupSolver extends BaseSolver {
})
}

private _runMergeSameNetTraceSegmentsStep() {
this.outputTraces = mergeSameNetTraceSegments(this.outputTraces, {
tolerance: Math.max(this.input.paddingBuffer * 4, 0.12),
})
this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]))
this.pipelineStep = "minimizing_turns"
}

private _runMinimizeTurnsStep() {
if (this.traceIdQueue.length === 0) {
this.pipelineStep = "balancing_l_shapes"
Expand Down
144 changes: 144 additions & 0 deletions lib/solvers/TraceCleanupSolver/mergeSameNetTraceSegments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import type { Point } from "graphics-debug"
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
import {
isHorizontal,
isVertical,
} from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"

type SegmentOrientation = "horizontal" | "vertical"

interface TraceSegment {
traceIndex: number
pointIndex: number
orientation: SegmentOrientation
coord: number
min: number
max: number
netKey: string
}

const getTraceNetKey = (trace: SolvedTracePath) =>
trace.userNetId ?? trace.globalConnNetId ?? trace.dcConnNetId

const rangesOverlap = (
aMin: number,
aMax: number,
bMin: number,
bMax: number,
) => Math.min(aMax, bMax) >= Math.max(aMin, bMin)

const getInteriorOrthogonalSegments = (
traces: SolvedTracePath[],
): TraceSegment[] => {
const segments: TraceSegment[] = []

traces.forEach((trace, traceIndex) => {
const netKey = getTraceNetKey(trace)
if (!netKey) return

for (
let pointIndex = 1;
pointIndex < trace.tracePath.length - 2;
pointIndex++
) {
const p1 = trace.tracePath[pointIndex]!
const p2 = trace.tracePath[pointIndex + 1]!

if (isHorizontal(p1, p2)) {
segments.push({
traceIndex,
pointIndex,
orientation: "horizontal",
coord: p1.y,
min: Math.min(p1.x, p2.x),
max: Math.max(p1.x, p2.x),
netKey,
})
} else if (isVertical(p1, p2)) {
segments.push({
traceIndex,
pointIndex,
orientation: "vertical",
coord: p1.x,
min: Math.min(p1.y, p2.y),
max: Math.max(p1.y, p2.y),
netKey,
})
}
}
})

return segments
}

const alignSegment = (
tracePath: Point[],
segment: TraceSegment,
coord: number,
) => {
const p1 = tracePath[segment.pointIndex]!
const p2 = tracePath[segment.pointIndex + 1]!
const nextP1 = Object.create(Object.getPrototypeOf(p1))
Object.defineProperties(nextP1, Object.getOwnPropertyDescriptors(p1))
const nextP2 = Object.create(Object.getPrototypeOf(p2))
Object.defineProperties(nextP2, Object.getOwnPropertyDescriptors(p2))

if (segment.orientation === "horizontal") {
nextP1.y = coord
nextP2.y = coord
} else {
nextP1.x = coord
nextP2.x = coord
}

tracePath[segment.pointIndex] = nextP1
tracePath[segment.pointIndex + 1] = nextP2
}

export const mergeSameNetTraceSegments = (
traces: SolvedTracePath[],
{ tolerance = 0.12 }: { tolerance?: number } = {},
): SolvedTracePath[] => {
const nextTraces = traces.slice()
const mutableTraceIndexes = new Set<number>()

const getMutableTracePath = (traceIndex: number) => {
if (!mutableTraceIndexes.has(traceIndex)) {
const trace = traces[traceIndex]!
nextTraces[traceIndex] = {
...trace,
tracePath: trace.tracePath.slice(),
}
mutableTraceIndexes.add(traceIndex)
}

return nextTraces[traceIndex]!.tracePath
}

const segments = getInteriorOrthogonalSegments(traces)

for (let i = 0; i < segments.length; i++) {
const anchor = segments[i]!

for (let j = i + 1; j < segments.length; j++) {
const candidate = segments[j]!
if (anchor.netKey !== candidate.netKey) continue
if (anchor.orientation !== candidate.orientation) continue
if (Math.abs(anchor.coord - candidate.coord) > tolerance) continue
if (
!rangesOverlap(anchor.min, anchor.max, candidate.min, candidate.max)
) {
continue
}

alignSegment(
getMutableTracePath(candidate.traceIndex),
candidate,
anchor.coord,
)
candidate.coord = anchor.coord
}
}

return nextTraces
}
20 changes: 7 additions & 13 deletions tests/examples/__snapshots__/example01.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 10 additions & 18 deletions tests/examples/__snapshots__/example02.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading