@tscircuit/schematic-trace-solver 0.0.46 → 0.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +18 -16
- package/dist/index.js +228 -122
- package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +2 -0
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver_visualize.ts +12 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +89 -61
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +16 -5
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +27 -16
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/doesTraceStartOrEndInLabel.ts +58 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +13 -1
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts +110 -60
- package/package.json +1 -1
- package/site/examples/example27.page.tsx +4 -0
- package/site/examples/example28.page.tsx +61 -0
- package/tests/assets/example27.json +168 -0
- package/tests/examples/__snapshots__/example03.snap.svg +67 -67
- package/tests/examples/__snapshots__/example16.snap.svg +3 -3
- package/tests/examples/__snapshots__/example28.snap.svg +23 -189
- package/tests/examples/__snapshots__/example30.snap.svg +258 -0
- package/tests/examples/example28.test.ts +2 -3
- package/tests/examples/example30.test.ts +12 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +9 -6
|
@@ -47,6 +47,14 @@ export function visualizeSingleNetLabelPlacementSolver(
|
|
|
47
47
|
: c.status === "trace-collision"
|
|
48
48
|
? "orange"
|
|
49
49
|
: "gray"
|
|
50
|
+
const candidateLabel =
|
|
51
|
+
c.status === "ok"
|
|
52
|
+
? "status: ok(valid net label candidate)"
|
|
53
|
+
: c.status === "chip-collision"
|
|
54
|
+
? "status: chip-collision"
|
|
55
|
+
: c.status === "trace-collision"
|
|
56
|
+
? "status: trace-collision"
|
|
57
|
+
: "status: parallel-to-segment"
|
|
50
58
|
|
|
51
59
|
graphics.rects!.push({
|
|
52
60
|
center: {
|
|
@@ -57,12 +65,14 @@ export function visualizeSingleNetLabelPlacementSolver(
|
|
|
57
65
|
height: c.height,
|
|
58
66
|
fill,
|
|
59
67
|
strokeColor: stroke,
|
|
68
|
+
label: `${candidateLabel}\norientation: ${c.orientation}`,
|
|
60
69
|
} as any)
|
|
61
70
|
|
|
62
71
|
graphics.points!.push({
|
|
63
72
|
x: c.anchor.x,
|
|
64
73
|
y: c.anchor.y,
|
|
65
74
|
color: stroke,
|
|
75
|
+
label: `anchor\norientation: ${c.orientation}`,
|
|
66
76
|
} as any)
|
|
67
77
|
}
|
|
68
78
|
|
|
@@ -75,11 +85,13 @@ export function visualizeSingleNetLabelPlacementSolver(
|
|
|
75
85
|
height: p.height,
|
|
76
86
|
fill: "rgba(0, 128, 255, 0.35)",
|
|
77
87
|
strokeColor: "blue",
|
|
88
|
+
label: `netId: ${p.netId}\nglobalConnNetId: ${p.globalConnNetId}`,
|
|
78
89
|
} as any)
|
|
79
90
|
graphics.points!.push({
|
|
80
91
|
x: p.anchorPoint.x,
|
|
81
92
|
y: p.anchorPoint.y,
|
|
82
93
|
color: "blue",
|
|
94
|
+
label: `anchor\norientation: ${p.orientation}`,
|
|
83
95
|
} as any)
|
|
84
96
|
}
|
|
85
97
|
|
|
@@ -7,6 +7,7 @@ import type { InputProblem } from "../../types/InputProblem"
|
|
|
7
7
|
import { MergedNetLabelObstacleSolver } from "./sub-solvers/LabelMergingSolver/LabelMergingSolver"
|
|
8
8
|
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
9
9
|
import { OverlapAvoidanceStepSolver } from "./sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver"
|
|
10
|
+
import { detectTraceLabelOverlap } from "./detectTraceLabelOverlap"
|
|
10
11
|
|
|
11
12
|
interface TraceLabelOverlapAvoidanceSolverInput {
|
|
12
13
|
inputProblem: InputProblem
|
|
@@ -15,87 +16,110 @@ interface TraceLabelOverlapAvoidanceSolverInput {
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
|
-
*
|
|
19
|
+
* Resolves overlaps between schematic traces and net labels using a two-phase,
|
|
20
|
+
* "fire-and-forget" dispatching strategy.
|
|
19
21
|
*
|
|
20
|
-
* This solver
|
|
21
|
-
* 1. **MergedNetLabelObstacleSolver**: This solver first merges labels that are
|
|
22
|
-
* close to each other to form larger "obstacle" groups. This simplifies the
|
|
23
|
-
* problem by reducing the number of individual obstacles the traces need to avoid.
|
|
24
|
-
* 2. **OverlapAvoidanceStepSolver**: This solver then takes the output of the merging
|
|
25
|
-
* step and iteratively attempts to reroute traces to avoid the merged label obstacles.
|
|
26
|
-
* It handles one overlap at a time, making it a step-by-step process.
|
|
22
|
+
* This solver operates in two distinct phases:
|
|
27
23
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
24
|
+
* 1. **Dispatch Phase**: Iterates through traces, identifying clean ones and dispatching
|
|
25
|
+
* colliding ones to dedicated `OverlapAvoidanceStepSolver` instances.
|
|
30
26
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
27
|
+
* 2. **Execution Phase**: Steps through all dispatched sub-solvers until they complete.
|
|
28
|
+
*
|
|
29
|
+
* The final output combines clean traces with results from sub-solvers. A final
|
|
30
|
+
* `MergedNetLabelObstacleSolver` ensures pipeline compatibility.
|
|
33
31
|
*/
|
|
34
32
|
export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
|
|
35
33
|
inputProblem: InputProblem
|
|
36
|
-
traces: SolvedTracePath[]
|
|
37
34
|
netLabelPlacements: NetLabelPlacement[]
|
|
38
35
|
|
|
39
|
-
|
|
36
|
+
unprocessedTraces: SolvedTracePath[] = []
|
|
37
|
+
cleanTraces: SolvedTracePath[] = []
|
|
38
|
+
subSolvers: OverlapAvoidanceStepSolver[] = []
|
|
39
|
+
private phase: "searching_for_overlaps" | "fixing_overlaps" =
|
|
40
|
+
"searching_for_overlaps"
|
|
41
|
+
detourCounts: Map<string, number> = new Map()
|
|
42
|
+
|
|
40
43
|
labelMergingSolver?: MergedNetLabelObstacleSolver
|
|
41
|
-
overlapAvoidanceSolver?: OverlapAvoidanceStepSolver
|
|
42
|
-
pipelineStepIndex = 0
|
|
43
44
|
|
|
44
45
|
constructor(solverInput: TraceLabelOverlapAvoidanceSolverInput) {
|
|
45
46
|
super()
|
|
46
47
|
this.inputProblem = solverInput.inputProblem
|
|
47
|
-
this.
|
|
48
|
+
this.unprocessedTraces = [...solverInput.traces]
|
|
48
49
|
this.netLabelPlacements = solverInput.netLabelPlacements
|
|
50
|
+
this.cleanTraces = []
|
|
51
|
+
this.subSolvers = []
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
override _step() {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
this.
|
|
58
|
-
|
|
59
|
-
} else if (this.activeSubSolver.failed) {
|
|
60
|
-
this.failed = true // If any sub-solver fails, the whole thing fails
|
|
61
|
-
this.activeSubSolver = null
|
|
55
|
+
if (this.phase === "searching_for_overlaps") {
|
|
56
|
+
if (this.unprocessedTraces.length === 0) {
|
|
57
|
+
console.log(
|
|
58
|
+
`Dispatch phase complete. Created ${this.subSolvers.length} sub-solvers.`,
|
|
59
|
+
)
|
|
60
|
+
this.phase = "fixing_overlaps"
|
|
61
|
+
return
|
|
62
62
|
}
|
|
63
|
-
return // Return to allow the sub-solver to run
|
|
64
|
-
}
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
const currentTargetTrace = this.unprocessedTraces.shift()!
|
|
65
|
+
|
|
66
|
+
const localOverlaps = detectTraceLabelOverlap({
|
|
67
|
+
traces: [currentTargetTrace],
|
|
68
|
+
netLabels: this.netLabelPlacements,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
if (localOverlaps.length === 0) {
|
|
72
|
+
this.cleanTraces.push(currentTargetTrace)
|
|
73
|
+
} else {
|
|
74
|
+
// Dispatch a new sub-solver for this dirty trace
|
|
75
|
+
const collidingLabels = localOverlaps.map((o) => o.label)
|
|
76
|
+
const labelMerger = new MergedNetLabelObstacleSolver({
|
|
77
|
+
netLabelPlacements: collidingLabels,
|
|
71
78
|
inputProblem: this.inputProblem,
|
|
72
|
-
traces:
|
|
79
|
+
traces: [currentTargetTrace],
|
|
73
80
|
})
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
labelMerger.solve()
|
|
82
|
+
const mergingOutput = labelMerger.getOutput()
|
|
76
83
|
|
|
77
|
-
|
|
78
|
-
this.overlapAvoidanceSolver = new OverlapAvoidanceStepSolver({
|
|
84
|
+
const subSolver = new OverlapAvoidanceStepSolver({
|
|
79
85
|
inputProblem: this.inputProblem,
|
|
80
|
-
traces:
|
|
81
|
-
initialNetLabelPlacements: this.netLabelPlacements,
|
|
82
|
-
mergedNetLabelPlacements:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
this.labelMergingSolver!.getOutput().mergedLabelNetIdMap,
|
|
86
|
+
traces: [currentTargetTrace],
|
|
87
|
+
initialNetLabelPlacements: this.netLabelPlacements,
|
|
88
|
+
mergedNetLabelPlacements: mergingOutput.netLabelPlacements,
|
|
89
|
+
mergedLabelNetIdMap: mergingOutput.mergedLabelNetIdMap,
|
|
90
|
+
detourCounts: this.detourCounts,
|
|
86
91
|
})
|
|
87
|
-
this.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
this.subSolvers.push(subSolver)
|
|
93
|
+
}
|
|
94
|
+
} else if (this.phase === "fixing_overlaps") {
|
|
95
|
+
if (this.subSolvers.every((s) => s.solved || s.failed)) {
|
|
96
|
+
console.log("All sub-solvers finished.")
|
|
97
|
+
// Final merge for pipeline compatibility
|
|
98
|
+
if (!this.labelMergingSolver) {
|
|
99
|
+
this.labelMergingSolver = new MergedNetLabelObstacleSolver({
|
|
100
|
+
netLabelPlacements: this.netLabelPlacements,
|
|
101
|
+
inputProblem: this.inputProblem,
|
|
102
|
+
traces: this.getOutput().traces,
|
|
103
|
+
})
|
|
104
|
+
this.labelMergingSolver.solve()
|
|
105
|
+
}
|
|
91
106
|
this.solved = true
|
|
92
|
-
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Step through all active sub-solvers
|
|
111
|
+
for (const solver of this.subSolvers) {
|
|
112
|
+
if (!solver.solved && !solver.failed) {
|
|
113
|
+
solver.step()
|
|
114
|
+
}
|
|
115
|
+
}
|
|
93
116
|
}
|
|
94
117
|
}
|
|
95
118
|
|
|
96
119
|
getOutput() {
|
|
120
|
+
const solvedTraces = this.subSolvers.flatMap((s) => s.getOutput().allTraces)
|
|
97
121
|
return {
|
|
98
|
-
traces: this.
|
|
122
|
+
traces: [...this.cleanTraces, ...solvedTraces],
|
|
99
123
|
netLabelPlacements:
|
|
100
124
|
this.labelMergingSolver?.getOutput().netLabelPlacements ??
|
|
101
125
|
this.netLabelPlacements,
|
|
@@ -103,26 +127,30 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
|
|
|
103
127
|
}
|
|
104
128
|
|
|
105
129
|
override visualize(): GraphicsObject {
|
|
106
|
-
if (this.activeSubSolver) {
|
|
107
|
-
return this.activeSubSolver.visualize()
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// When no sub-solver is active, show the current state of the pipeline
|
|
111
130
|
const graphics = visualizeInputProblem(this.inputProblem)
|
|
112
131
|
if (!graphics.lines) graphics.lines = []
|
|
113
132
|
if (!graphics.rects) graphics.rects = []
|
|
114
133
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
for (const trace of output.traces) {
|
|
134
|
+
// Show clean traces in purple
|
|
135
|
+
for (const trace of this.cleanTraces) {
|
|
118
136
|
graphics.lines!.push({
|
|
119
137
|
points: trace.tracePath,
|
|
120
138
|
strokeColor: "purple",
|
|
121
139
|
})
|
|
122
140
|
}
|
|
123
141
|
|
|
124
|
-
|
|
125
|
-
|
|
142
|
+
// Delegate visualization to sub-solvers
|
|
143
|
+
for (const solver of this.subSolvers) {
|
|
144
|
+
const solverGraphics = solver.visualize()
|
|
145
|
+
graphics.lines!.push(...(solverGraphics.lines ?? []))
|
|
146
|
+
graphics.rects!.push(...(solverGraphics.rects ?? []))
|
|
147
|
+
// graphics.texts!.push(...(solverGraphics.texts ?? []))
|
|
148
|
+
graphics.points!.push(...(solverGraphics.points ?? []))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Also show original labels
|
|
152
|
+
for (const label of this.netLabelPlacements) {
|
|
153
|
+
const color = getColorFromString(label.globalConnNetId, 0.3) // Make fill opaque
|
|
126
154
|
graphics.rects!.push({
|
|
127
155
|
center: label.center,
|
|
128
156
|
width: label.width,
|
|
@@ -8,6 +8,18 @@ import { generateSnipAndReconnectCandidates } from "./trySnipAndReconnect"
|
|
|
8
8
|
import { generateFourPointDetourCandidates } from "./tryFourPointDetour"
|
|
9
9
|
import { simplifyPath } from "../TraceCleanupSolver/simplifyPath"
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Generates a list of candidate rerouted paths for a given trace that is
|
|
13
|
+
* colliding with a net label.
|
|
14
|
+
*
|
|
15
|
+
* This function employs multiple strategies to propose alternative paths:
|
|
16
|
+
* 1. **Four-Point Detour:** Creates a rectangular detour around the label.
|
|
17
|
+
* 2. **Snip and Reconnect:** Attempts to remove the colliding segment and
|
|
18
|
+
* reconnect the trace around the obstacle.
|
|
19
|
+
*
|
|
20
|
+
* The candidates are generated with increasing padding based on `detourCount`
|
|
21
|
+
* to explore progressively wider detours.
|
|
22
|
+
*/
|
|
11
23
|
export const generateRerouteCandidates = ({
|
|
12
24
|
trace,
|
|
13
25
|
label,
|
|
@@ -26,13 +38,12 @@ export const generateRerouteCandidates = ({
|
|
|
26
38
|
return [initialTrace.tracePath]
|
|
27
39
|
}
|
|
28
40
|
|
|
29
|
-
const labelPadding = paddingBuffer
|
|
30
41
|
const labelBoundsRaw = getRectBounds(label.center, label.width, label.height)
|
|
31
42
|
const labelBounds = {
|
|
32
|
-
minX: labelBoundsRaw.minX
|
|
33
|
-
minY: labelBoundsRaw.minY
|
|
34
|
-
maxX: labelBoundsRaw.maxX
|
|
35
|
-
maxY: labelBoundsRaw.maxY
|
|
43
|
+
minX: labelBoundsRaw.minX,
|
|
44
|
+
minY: labelBoundsRaw.minY,
|
|
45
|
+
maxX: labelBoundsRaw.maxX,
|
|
46
|
+
maxY: labelBoundsRaw.maxY,
|
|
36
47
|
chipId: `netlabel-${label.netId}`,
|
|
37
48
|
}
|
|
38
49
|
|
|
@@ -6,7 +6,7 @@ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/
|
|
|
6
6
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
7
7
|
import { detectTraceLabelOverlap } from "../../detectTraceLabelOverlap"
|
|
8
8
|
import { SingleOverlapSolver } from "../SingleOverlapSolver/SingleOverlapSolver"
|
|
9
|
-
import {
|
|
9
|
+
import { doesTraceStartOrEndInLabel } from "./doesTraceStartOrEndInLabel"
|
|
10
10
|
import { visualizeDecomposition } from "./visualizeDecomposition"
|
|
11
11
|
|
|
12
12
|
type Overlap = ReturnType<typeof detectTraceLabelOverlap>[0]
|
|
@@ -18,6 +18,7 @@ export interface OverlapCollectionSolverInput {
|
|
|
18
18
|
initialNetLabelPlacements: NetLabelPlacement[]
|
|
19
19
|
mergedNetLabelPlacements: NetLabelPlacement[]
|
|
20
20
|
mergedLabelNetIdMap: Record<string, Set<string>>
|
|
21
|
+
detourCounts: Map<string, number>
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -33,8 +34,8 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
33
34
|
allTraces: SolvedTracePath[]
|
|
34
35
|
modifiedTraces: SolvedTracePath[] = []
|
|
35
36
|
|
|
36
|
-
private detourCountByLabel: Record<string, number> = {}
|
|
37
37
|
private readonly PADDING_BUFFER = 0.1
|
|
38
|
+
private detourCounts: Map<string, number> = new Map()
|
|
38
39
|
|
|
39
40
|
public override activeSubSolver: SingleOverlapSolver | null = null
|
|
40
41
|
private overlapQueue: Overlap[] = []
|
|
@@ -50,6 +51,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
50
51
|
this.mergedNetLabelPlacements = solverInput.mergedNetLabelPlacements
|
|
51
52
|
this.mergedLabelNetIdMap = solverInput.mergedLabelNetIdMap
|
|
52
53
|
this.allTraces = [...solverInput.traces]
|
|
54
|
+
this.detourCounts = solverInput.detourCounts
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
override _step() {
|
|
@@ -111,7 +113,6 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
111
113
|
(t) => t.mspPairId === nextOverlap.trace.mspPairId,
|
|
112
114
|
)!
|
|
113
115
|
const labelToAvoid = nextOverlap.label
|
|
114
|
-
const traceStartPoint = traceToFix.tracePath[0]
|
|
115
116
|
|
|
116
117
|
const originalNetIds =
|
|
117
118
|
this.mergedLabelNetIdMap[labelToAvoid.globalConnNetId]
|
|
@@ -136,15 +137,19 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
if (actualOverlapLabel) {
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
this.
|
|
140
|
+
const detourCount =
|
|
141
|
+
this.detourCounts.get(actualOverlapLabel.globalConnNetId) ?? 0
|
|
142
|
+
this.detourCounts.set(
|
|
143
|
+
actualOverlapLabel.globalConnNetId,
|
|
144
|
+
detourCount + 1,
|
|
145
|
+
)
|
|
146
|
+
|
|
142
147
|
this.activeSubSolver = new SingleOverlapSolver({
|
|
143
148
|
trace: traceToFix,
|
|
144
149
|
label: actualOverlapLabel,
|
|
145
150
|
problem: this.inputProblem,
|
|
146
151
|
paddingBuffer: this.PADDING_BUFFER,
|
|
147
|
-
detourCount,
|
|
152
|
+
detourCount: detourCount,
|
|
148
153
|
})
|
|
149
154
|
} else {
|
|
150
155
|
const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`
|
|
@@ -155,7 +160,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
155
160
|
|
|
156
161
|
if (
|
|
157
162
|
originalNetIds &&
|
|
158
|
-
|
|
163
|
+
doesTraceStartOrEndInLabel({ trace: traceToFix, label: labelToAvoid })
|
|
159
164
|
) {
|
|
160
165
|
const childLabels = this.initialNetLabelPlacements.filter((l) =>
|
|
161
166
|
originalNetIds.has(l.globalConnNetId),
|
|
@@ -175,15 +180,18 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
175
180
|
}
|
|
176
181
|
|
|
177
182
|
if (actualOverlapLabel) {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
this.
|
|
183
|
+
const detourCount =
|
|
184
|
+
this.detourCounts.get(actualOverlapLabel.globalConnNetId) ?? 0
|
|
185
|
+
this.detourCounts.set(
|
|
186
|
+
actualOverlapLabel.globalConnNetId,
|
|
187
|
+
detourCount + 1,
|
|
188
|
+
)
|
|
181
189
|
this.activeSubSolver = new SingleOverlapSolver({
|
|
182
190
|
trace: traceToFix,
|
|
183
191
|
label: actualOverlapLabel,
|
|
184
192
|
problem: this.inputProblem,
|
|
185
193
|
paddingBuffer: this.PADDING_BUFFER,
|
|
186
|
-
detourCount,
|
|
194
|
+
detourCount: detourCount,
|
|
187
195
|
})
|
|
188
196
|
} else {
|
|
189
197
|
const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`
|
|
@@ -192,15 +200,17 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
192
200
|
return
|
|
193
201
|
}
|
|
194
202
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
203
|
+
// STRATEGY 3: Real collision between different nets.
|
|
204
|
+
// We must reroute around the entire merged label.
|
|
205
|
+
const detourCount =
|
|
206
|
+
this.detourCounts.get(labelToAvoid.globalConnNetId) ?? 0
|
|
207
|
+
this.detourCounts.set(labelToAvoid.globalConnNetId, detourCount + 1)
|
|
198
208
|
this.activeSubSolver = new SingleOverlapSolver({
|
|
199
209
|
trace: traceToFix,
|
|
200
210
|
label: labelToAvoid,
|
|
201
211
|
problem: this.inputProblem,
|
|
202
212
|
paddingBuffer: this.PADDING_BUFFER,
|
|
203
|
-
detourCount,
|
|
213
|
+
detourCount: detourCount,
|
|
204
214
|
})
|
|
205
215
|
}
|
|
206
216
|
}
|
|
@@ -209,6 +219,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
209
219
|
return {
|
|
210
220
|
allTraces: this.allTraces,
|
|
211
221
|
modifiedTraces: this.modifiedTraces,
|
|
222
|
+
detourCounts: this.detourCounts,
|
|
212
223
|
}
|
|
213
224
|
}
|
|
214
225
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
2
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
3
|
+
import { detectTraceLabelOverlap } from "../../detectTraceLabelOverlap"
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
trace: SolvedTracePath
|
|
7
|
+
label: NetLabelPlacement
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Checks if the first or last segment of a given trace overlaps with a specified net label.
|
|
12
|
+
* This is useful for determining if a trace begins or ends within an obstacle.
|
|
13
|
+
*/
|
|
14
|
+
export const doesTraceStartOrEndInLabel = ({ trace, label }: Props) => {
|
|
15
|
+
if (trace.tracePath.length < 2) {
|
|
16
|
+
return false // Not a valid trace with segments
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Create a mini-trace for the first segment
|
|
20
|
+
const firstSegmentTrace: SolvedTracePath = {
|
|
21
|
+
...trace,
|
|
22
|
+
tracePath: [trace.tracePath[0], trace.tracePath[1]],
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Check for overlap with the first segment
|
|
26
|
+
const firstSegmentOverlap = detectTraceLabelOverlap({
|
|
27
|
+
traces: [firstSegmentTrace],
|
|
28
|
+
netLabels: [label],
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
if (firstSegmentOverlap.length > 0) {
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// If the trace has more than one segment, check the last one
|
|
36
|
+
if (trace.tracePath.length > 2) {
|
|
37
|
+
const lastPoint = trace.tracePath[trace.tracePath.length - 1]
|
|
38
|
+
const secondToLastPoint = trace.tracePath[trace.tracePath.length - 2]
|
|
39
|
+
|
|
40
|
+
// Create a mini-trace for the last segment
|
|
41
|
+
const lastSegmentTrace: SolvedTracePath = {
|
|
42
|
+
...trace,
|
|
43
|
+
tracePath: [secondToLastPoint, lastPoint],
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check for overlap with the last segment
|
|
47
|
+
const lastSegmentOverlap = detectTraceLabelOverlap({
|
|
48
|
+
traces: [lastSegmentTrace],
|
|
49
|
+
netLabels: [label],
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
if (lastSegmentOverlap.length > 0) {
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
@@ -18,6 +18,8 @@ interface SingleOverlapSolverInput {
|
|
|
18
18
|
detourCount: number
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
const MAX_TRIES = 5
|
|
22
|
+
|
|
21
23
|
/**
|
|
22
24
|
* This solver attempts to find a valid rerouting for a single trace that is
|
|
23
25
|
* overlapping with a net label. It tries various candidate paths until it
|
|
@@ -30,6 +32,7 @@ export class SingleOverlapSolver extends BaseSolver {
|
|
|
30
32
|
problem: InputProblem
|
|
31
33
|
obstacles: ReturnType<typeof getObstacleRects>
|
|
32
34
|
label: NetLabelPlacement
|
|
35
|
+
_tried: number = 0
|
|
33
36
|
|
|
34
37
|
constructor(solverInput: SingleOverlapSolverInput) {
|
|
35
38
|
super()
|
|
@@ -37,8 +40,14 @@ export class SingleOverlapSolver extends BaseSolver {
|
|
|
37
40
|
this.problem = solverInput.problem
|
|
38
41
|
this.label = solverInput.label
|
|
39
42
|
|
|
43
|
+
// Calculate an effective padding for this specific run based on the detourCount.
|
|
44
|
+
const effectivePadding =
|
|
45
|
+
solverInput.paddingBuffer +
|
|
46
|
+
solverInput.detourCount * solverInput.paddingBuffer
|
|
47
|
+
|
|
40
48
|
const candidates = generateRerouteCandidates({
|
|
41
49
|
...solverInput,
|
|
50
|
+
paddingBuffer: effectivePadding, // Use the calculated, larger padding
|
|
42
51
|
})
|
|
43
52
|
|
|
44
53
|
const getPathLength = (pts: Point[]) => {
|
|
@@ -58,11 +67,13 @@ export class SingleOverlapSolver extends BaseSolver {
|
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
override _step() {
|
|
61
|
-
|
|
70
|
+
// Failure conditions: no more candidates or exceeded max tries
|
|
71
|
+
if (this.queuedCandidatePaths.length === 0 || this._tried >= MAX_TRIES) {
|
|
62
72
|
this.failed = true
|
|
63
73
|
return
|
|
64
74
|
}
|
|
65
75
|
|
|
76
|
+
this._tried++
|
|
66
77
|
const nextCandidatePath = this.queuedCandidatePaths.shift()!
|
|
67
78
|
const simplifiedPath = simplifyPath(nextCandidatePath)
|
|
68
79
|
|
|
@@ -70,6 +81,7 @@ export class SingleOverlapSolver extends BaseSolver {
|
|
|
70
81
|
this.solvedTracePath = simplifiedPath
|
|
71
82
|
this.solved = true
|
|
72
83
|
}
|
|
84
|
+
// If the path collides, we simply do nothing and let the next step try another candidate.
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
override visualize(): GraphicsObject {
|