@tscircuit/schematic-trace-solver 0.0.41 → 0.0.43
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 +118 -8
- package/dist/index.js +1067 -748
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +48 -16
- package/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +133 -0
- package/lib/solvers/TraceCleanupSolver/balanceZShapes.ts +200 -0
- package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/hasCollisions.ts +1 -1
- package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/hasCollisionsWithLabels.ts +1 -1
- package/lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +76 -0
- package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/simplifyPath.ts +1 -1
- package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/turnMinimization.ts +0 -2
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +74 -192
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +13 -24
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts +205 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +144 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +117 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts +104 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts +24 -154
- package/package.json +1 -1
- package/site/examples/example26.page.tsx +4 -0
- package/tests/assets/1.input.json +379 -0
- package/tests/assets/2.input.json +330 -0
- package/tests/assets/3.input.json +388 -0
- package/tests/assets/MergedNetLabelObstacles.test.input.json +154 -0
- package/tests/assets/OverlapAvoidanceStepSolver.test.input.json +682 -0
- package/tests/assets/SingleOverlapSolver.test.input.json +170 -0
- package/tests/assets/TraceCleanupSolver.test.input.json +216 -0
- package/tests/assets/TraceLabelOverlapAvoidanceSolver.test.input.json +424 -0
- package/tests/assets/example26.json +1206 -0
- package/tests/examples/__snapshots__/example02.snap.svg +1 -1
- package/tests/examples/__snapshots__/example09.snap.svg +34 -34
- package/tests/examples/__snapshots__/example11.snap.svg +13 -13
- package/tests/examples/__snapshots__/example12.snap.svg +3 -3
- package/tests/examples/__snapshots__/example13.snap.svg +12 -12
- package/tests/examples/__snapshots__/example16.snap.svg +4 -4
- package/tests/examples/__snapshots__/example19.snap.svg +9 -9
- package/tests/examples/__snapshots__/example28.snap.svg +3 -3
- package/tests/examples/__snapshots__/example29.snap.svg +1208 -0
- package/tests/examples/example29.test.ts +13 -0
- package/tests/solvers/TraceCleanupSolver/TraceCleanupSolver.test.ts +19 -0
- package/tests/solvers/TraceCleanupSolver/__snapshots__/TraceCleanupSolver.snap.svg +119 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.test.ts +18 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +243 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView01.snap.svg +223 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView02.snap.svg +205 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView03.snap.svg +184 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView01.test.ts +29 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView02.test.ts +27 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView03.test.ts +39 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/MergedNetLabelObstacles.test.ts +14 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/OverlapAvoidanceStepSolver.test.ts +20 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/SingleOverlapSolver.test.ts +10 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/MergedNetLabelObstacles.snap.svg +140 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg +216 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/SingleOverlapSolver.snap.svg +125 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/balanceLShapes.ts +0 -207
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/minimizeTurnsWithFilteredLabels.ts +0 -66
- /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/countTurns.ts +0 -0
- /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/tryConnectPoints.ts +0 -0
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import { BaseSolver } from "../BaseSolver/BaseSolver"
|
|
2
|
-
import { detectTraceLabelOverlap } from "./detectTraceLabelOverlap"
|
|
3
|
-
import { rerouteCollidingTrace } from "./rerouteCollidingTrace"
|
|
4
2
|
import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
3
|
import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
6
4
|
import type { GraphicsObject } from "graphics-debug"
|
|
7
5
|
import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
|
|
8
|
-
import {
|
|
6
|
+
import type { InputProblem } from "../../types/InputProblem"
|
|
7
|
+
import { MergedNetLabelObstacleSolver } from "./sub-solvers/LabelMergingSolver/LabelMergingSolver"
|
|
9
8
|
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
10
|
-
import
|
|
11
|
-
import { minimizeTurnsWithFilteredLabels } from "./minimizeTurnsWithFilteredLabels"
|
|
12
|
-
import { balanceLShapes } from "./balanceLShapes"
|
|
13
|
-
import { NetLabelPlacementSolver } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
9
|
+
import { OverlapAvoidanceStepSolver } from "./sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver"
|
|
14
10
|
|
|
15
11
|
interface TraceLabelOverlapAvoidanceSolverInput {
|
|
16
12
|
inputProblem: InputProblem
|
|
@@ -18,222 +14,108 @@ interface TraceLabelOverlapAvoidanceSolverInput {
|
|
|
18
14
|
netLabelPlacements: NetLabelPlacement[]
|
|
19
15
|
}
|
|
20
16
|
|
|
17
|
+
/**
|
|
18
|
+
* This solver is a pipeline that runs a series of sub-solvers to resolve
|
|
19
|
+
* trace-label overlaps and clean up the resulting traces.
|
|
20
|
+
*/
|
|
21
21
|
export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
inputProblem: InputProblem
|
|
23
|
+
traces: SolvedTracePath[]
|
|
24
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
25
|
+
|
|
26
|
+
// sub-solver instances
|
|
27
|
+
labelMergingSolver?: MergedNetLabelObstacleSolver
|
|
28
|
+
overlapAvoidanceSolver?: OverlapAvoidanceStepSolver
|
|
29
|
+
pipelineStepIndex = 0
|
|
30
30
|
|
|
31
31
|
constructor(solverInput: TraceLabelOverlapAvoidanceSolverInput) {
|
|
32
32
|
super()
|
|
33
|
-
this.
|
|
33
|
+
this.inputProblem = solverInput.inputProblem
|
|
34
34
|
this.traces = solverInput.traces
|
|
35
|
-
this.
|
|
36
|
-
this.mergedLabelNetIdMap = {}
|
|
37
|
-
this.detourCountByLabel = {}
|
|
38
|
-
|
|
39
|
-
const originalLabels = solverInput.netLabelPlacements
|
|
40
|
-
this.netLabelPlacements = originalLabels
|
|
41
|
-
if (!originalLabels || originalLabels.length === 0) {
|
|
42
|
-
this.netTempLabelPlacements = []
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const labelGroups: Record<string, NetLabelPlacement[]> = {}
|
|
47
|
-
|
|
48
|
-
for (const p of originalLabels) {
|
|
49
|
-
if (p.pinIds.length === 0) continue
|
|
50
|
-
const chipId = p.pinIds[0].split(".")[0]
|
|
51
|
-
if (!chipId) continue
|
|
52
|
-
const key = `${chipId}-${p.orientation}`
|
|
53
|
-
if (!(key in labelGroups)) {
|
|
54
|
-
labelGroups[key] = []
|
|
55
|
-
}
|
|
56
|
-
labelGroups[key]!.push(p)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const finalPlacements: NetLabelPlacement[] = []
|
|
60
|
-
for (const [key, group] of Object.entries(labelGroups)) {
|
|
61
|
-
if (group.length <= 1) {
|
|
62
|
-
finalPlacements.push(...group)
|
|
63
|
-
continue
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
let minX = Infinity,
|
|
67
|
-
minY = Infinity,
|
|
68
|
-
maxX = -Infinity,
|
|
69
|
-
maxY = -Infinity
|
|
70
|
-
for (const p of group) {
|
|
71
|
-
const bounds = getRectBounds(p.center, p.width, p.height)
|
|
72
|
-
minX = Math.min(minX, bounds.minX)
|
|
73
|
-
minY = Math.min(minY, bounds.minY)
|
|
74
|
-
maxX = Math.max(maxX, bounds.maxX)
|
|
75
|
-
maxY = Math.max(maxY, bounds.maxY)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const newWidth = maxX - minX
|
|
79
|
-
const newHeight = maxY - minY
|
|
80
|
-
const template = group[0]!
|
|
81
|
-
const syntheticId = `merged-group-${key}`
|
|
82
|
-
const originalNetIds = new Set(group.map((p) => p.globalConnNetId))
|
|
83
|
-
this.mergedLabelNetIdMap[syntheticId] = originalNetIds
|
|
84
|
-
|
|
85
|
-
finalPlacements.push({
|
|
86
|
-
...template,
|
|
87
|
-
globalConnNetId: syntheticId,
|
|
88
|
-
width: newWidth,
|
|
89
|
-
height: newHeight,
|
|
90
|
-
center: { x: minX + newWidth / 2, y: minY + newHeight / 2 },
|
|
91
|
-
pinIds: [...new Set(group.flatMap((p) => p.pinIds))],
|
|
92
|
-
mspConnectionPairIds: [
|
|
93
|
-
...new Set(group.flatMap((p) => p.mspConnectionPairIds)),
|
|
94
|
-
],
|
|
95
|
-
})
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
this.netTempLabelPlacements = finalPlacements
|
|
35
|
+
this.netLabelPlacements = solverInput.netLabelPlacements
|
|
99
36
|
}
|
|
100
37
|
|
|
101
38
|
override _step() {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this.
|
|
105
|
-
|
|
106
|
-
this.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
this.detourCountByLabel = {}
|
|
113
|
-
|
|
114
|
-
const overlaps = detectTraceLabelOverlap(
|
|
115
|
-
this.traces,
|
|
116
|
-
this.netTempLabelPlacements,
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
if (overlaps.length === 0) {
|
|
120
|
-
this.solved = true
|
|
121
|
-
return
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const unfriendlyOverlaps = overlaps.filter((o) => {
|
|
125
|
-
const originalNetIds = this.mergedLabelNetIdMap[o.label.globalConnNetId]
|
|
126
|
-
if (originalNetIds) {
|
|
127
|
-
return !originalNetIds.has(o.trace.globalConnNetId)
|
|
39
|
+
// If a sub-solver is active, step it and check for completion.
|
|
40
|
+
if (this.activeSubSolver) {
|
|
41
|
+
this.activeSubSolver.step()
|
|
42
|
+
|
|
43
|
+
if (this.activeSubSolver.solved) {
|
|
44
|
+
this.activeSubSolver = null
|
|
45
|
+
this.pipelineStepIndex++
|
|
46
|
+
} else if (this.activeSubSolver.failed) {
|
|
47
|
+
this.failed = true // If any sub-solver fails, the whole thing fails
|
|
48
|
+
this.activeSubSolver = null
|
|
128
49
|
}
|
|
129
|
-
return
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
if (unfriendlyOverlaps.length === 0) {
|
|
133
|
-
this.solved = true
|
|
134
|
-
return
|
|
50
|
+
return // Return to allow the sub-solver to run
|
|
135
51
|
}
|
|
136
52
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
53
|
+
// If no sub-solver is active, create the next one in the pipeline.
|
|
54
|
+
switch (this.pipelineStepIndex) {
|
|
55
|
+
case 0:
|
|
56
|
+
this.labelMergingSolver = new MergedNetLabelObstacleSolver({
|
|
57
|
+
netLabelPlacements: this.netLabelPlacements,
|
|
58
|
+
inputProblem: this.inputProblem,
|
|
59
|
+
traces: this.traces,
|
|
60
|
+
})
|
|
61
|
+
this.activeSubSolver = this.labelMergingSolver
|
|
62
|
+
break
|
|
63
|
+
|
|
64
|
+
case 1:
|
|
65
|
+
this.overlapAvoidanceSolver = new OverlapAvoidanceStepSolver({
|
|
66
|
+
inputProblem: this.inputProblem,
|
|
67
|
+
traces: this.traces,
|
|
68
|
+
netLabelPlacements:
|
|
69
|
+
this.labelMergingSolver!.getOutput().netLabelPlacements,
|
|
70
|
+
mergedLabelNetIdMap:
|
|
71
|
+
this.labelMergingSolver!.getOutput().mergedLabelNetIdMap,
|
|
72
|
+
})
|
|
73
|
+
this.activeSubSolver = this.overlapAvoidanceSolver
|
|
74
|
+
break
|
|
75
|
+
|
|
76
|
+
default:
|
|
77
|
+
this.solved = true
|
|
78
|
+
break
|
|
140
79
|
}
|
|
141
|
-
|
|
142
|
-
const processedTraceIds = new Set<string>()
|
|
143
|
-
|
|
144
|
-
for (const overlap of unfriendlyOverlaps) {
|
|
145
|
-
if (processedTraceIds.has(overlap.trace.mspPairId)) {
|
|
146
|
-
continue
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const currentTraceState = updatedTracesMap[overlap.trace.mspPairId]!
|
|
150
|
-
const labelId = overlap.label.globalConnNetId
|
|
151
|
-
const detourCount = this.detourCountByLabel[labelId] || 0
|
|
152
|
-
|
|
153
|
-
const newTrace = rerouteCollidingTrace({
|
|
154
|
-
trace: currentTraceState,
|
|
155
|
-
label: overlap.label,
|
|
156
|
-
problem: this.problem,
|
|
157
|
-
paddingBuffer: this.PADDING_BUFFER,
|
|
158
|
-
detourCount,
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
if (newTrace.tracePath !== currentTraceState.tracePath) {
|
|
162
|
-
this.detourCountByLabel[labelId] = detourCount + 1
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
updatedTracesMap[currentTraceState.mspPairId] = newTrace
|
|
166
|
-
processedTraceIds.add(currentTraceState.mspPairId)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
this.updatedTraces = Object.values(updatedTracesMap)
|
|
170
|
-
|
|
171
|
-
const minimizedTraces = minimizeTurnsWithFilteredLabels({
|
|
172
|
-
traces: this.updatedTraces,
|
|
173
|
-
problem: this.problem,
|
|
174
|
-
allLabelPlacements: this.netTempLabelPlacements, // Use temp labels which include merged ones
|
|
175
|
-
mergedLabelNetIdMap: this.mergedLabelNetIdMap,
|
|
176
|
-
paddingBuffer: this.PADDING_BUFFER,
|
|
177
|
-
})
|
|
178
|
-
if (minimizedTraces) {
|
|
179
|
-
this.updatedTraces = minimizedTraces
|
|
180
|
-
}
|
|
181
|
-
const balancedTraces = balanceLShapes({
|
|
182
|
-
traces: this.updatedTraces,
|
|
183
|
-
problem: this.problem,
|
|
184
|
-
allLabelPlacements: this.netLabelPlacements,
|
|
185
|
-
})
|
|
186
|
-
if (balancedTraces) {
|
|
187
|
-
this.updatedTraces = balancedTraces
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const finalLabelPlacementSolver = new NetLabelPlacementSolver({
|
|
191
|
-
inputProblem: this.problem,
|
|
192
|
-
inputTraceMap: Object.fromEntries(
|
|
193
|
-
this.updatedTraces.map((trace) => [trace.mspPairId, trace]),
|
|
194
|
-
),
|
|
195
|
-
})
|
|
196
|
-
finalLabelPlacementSolver.solve()
|
|
197
|
-
if (!finalLabelPlacementSolver.failed) {
|
|
198
|
-
this.netLabelPlacements = finalLabelPlacementSolver.netLabelPlacements
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
this.solved = true
|
|
202
80
|
}
|
|
203
81
|
|
|
204
82
|
getOutput() {
|
|
205
83
|
return {
|
|
206
|
-
traces: this.
|
|
207
|
-
netLabelPlacements:
|
|
84
|
+
traces: this.overlapAvoidanceSolver?.getOutput().allTraces ?? this.traces,
|
|
85
|
+
netLabelPlacements:
|
|
86
|
+
this.labelMergingSolver?.getOutput().netLabelPlacements ??
|
|
87
|
+
this.netLabelPlacements,
|
|
208
88
|
}
|
|
209
89
|
}
|
|
210
90
|
|
|
211
91
|
override visualize(): GraphicsObject {
|
|
212
|
-
|
|
92
|
+
if (this.activeSubSolver) {
|
|
93
|
+
return this.activeSubSolver.visualize()
|
|
94
|
+
}
|
|
213
95
|
|
|
96
|
+
// When no sub-solver is active, show the current state of the pipeline
|
|
97
|
+
const graphics = visualizeInputProblem(this.inputProblem)
|
|
214
98
|
if (!graphics.lines) graphics.lines = []
|
|
215
|
-
if (!graphics.circles) graphics.circles = []
|
|
216
|
-
if (!graphics.texts) graphics.texts = []
|
|
217
99
|
if (!graphics.rects) graphics.rects = []
|
|
218
100
|
|
|
219
|
-
|
|
101
|
+
const output = this.getOutput()
|
|
102
|
+
|
|
103
|
+
for (const trace of output.traces) {
|
|
220
104
|
graphics.lines!.push({
|
|
221
105
|
points: trace.tracePath,
|
|
222
106
|
strokeColor: "purple",
|
|
223
107
|
})
|
|
224
108
|
}
|
|
225
109
|
|
|
226
|
-
for (const
|
|
110
|
+
for (const label of output.netLabelPlacements) {
|
|
111
|
+
const color = getColorFromString(label.globalConnNetId, 0.3)
|
|
227
112
|
graphics.rects!.push({
|
|
228
|
-
center:
|
|
229
|
-
width:
|
|
230
|
-
height:
|
|
231
|
-
fill:
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
x: p.anchorPoint.x,
|
|
235
|
-
y: p.anchorPoint.y,
|
|
236
|
-
color: getColorFromString(p.globalConnNetId, 0.9),
|
|
113
|
+
center: label.center,
|
|
114
|
+
width: label.width,
|
|
115
|
+
height: label.height,
|
|
116
|
+
fill: color,
|
|
117
|
+
stroke: color.replace("0.3", "1"),
|
|
118
|
+
label: label.globalConnNetId,
|
|
237
119
|
})
|
|
238
120
|
}
|
|
239
121
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
1
2
|
import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
2
3
|
import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
3
4
|
import { getRectBounds } from "../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
4
|
-
import { getObstacleRects } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
5
5
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
6
6
|
import { findTraceViolationZone } from "./violation"
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { generateSnipAndReconnectCandidates } from "./trySnipAndReconnect"
|
|
8
|
+
import { generateFourPointDetourCandidates } from "./tryFourPointDetour"
|
|
9
|
+
import { simplifyPath } from "../TraceCleanupSolver/simplifyPath"
|
|
9
10
|
|
|
10
|
-
export const
|
|
11
|
+
export const generateRerouteCandidates = ({
|
|
11
12
|
trace,
|
|
12
13
|
label,
|
|
13
|
-
problem,
|
|
14
14
|
paddingBuffer,
|
|
15
15
|
detourCount,
|
|
16
16
|
}: {
|
|
@@ -19,14 +19,13 @@ export const rerouteCollidingTrace = ({
|
|
|
19
19
|
problem: InputProblem
|
|
20
20
|
paddingBuffer: number
|
|
21
21
|
detourCount: number
|
|
22
|
-
}):
|
|
22
|
+
}): Point[][] => {
|
|
23
23
|
const initialTrace = { ...trace, tracePath: simplifyPath(trace.tracePath) }
|
|
24
24
|
|
|
25
25
|
if (trace.globalConnNetId === label.globalConnNetId) {
|
|
26
|
-
return initialTrace
|
|
26
|
+
return [initialTrace.tracePath]
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const obstacles = getObstacleRects(problem)
|
|
30
29
|
const labelPadding = paddingBuffer
|
|
31
30
|
const labelBoundsRaw = getRectBounds(label.center, label.width, label.height)
|
|
32
31
|
const labelBounds = {
|
|
@@ -37,37 +36,27 @@ export const rerouteCollidingTrace = ({
|
|
|
37
36
|
chipId: `netlabel-${label.netId}`,
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
const
|
|
39
|
+
const fourPointCandidates = generateFourPointDetourCandidates({
|
|
41
40
|
initialTrace,
|
|
42
41
|
label,
|
|
43
42
|
labelBounds,
|
|
44
|
-
obstacles,
|
|
45
43
|
paddingBuffer,
|
|
46
44
|
detourCount,
|
|
47
45
|
})
|
|
48
|
-
|
|
49
|
-
initialTrace.tracePath = fourPointResult.tracePath
|
|
50
|
-
}
|
|
46
|
+
|
|
51
47
|
const { firstInsideIndex, lastInsideIndex } = findTraceViolationZone(
|
|
52
48
|
initialTrace.tracePath,
|
|
53
49
|
labelBounds,
|
|
54
50
|
)
|
|
55
51
|
|
|
56
|
-
const
|
|
52
|
+
const snipReconnectCandidates = generateSnipAndReconnectCandidates({
|
|
57
53
|
initialTrace,
|
|
58
54
|
firstInsideIndex,
|
|
59
55
|
lastInsideIndex,
|
|
60
56
|
labelBounds,
|
|
61
|
-
|
|
57
|
+
paddingBuffer,
|
|
58
|
+
detourCount,
|
|
62
59
|
})
|
|
63
60
|
|
|
64
|
-
|
|
65
|
-
return snipReconnectResult
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (fourPointResult) {
|
|
69
|
-
return fourPointResult
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return initialTrace
|
|
61
|
+
return [...fourPointCandidates, ...snipReconnectCandidates]
|
|
73
62
|
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import type { SolvedTracePath } from "../../../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
2
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
3
|
+
import type { NetLabelPlacement } from "../../../NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
4
|
+
import { getRectBounds } from "../../../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
5
|
+
import type { GraphicsObject, Line } from "graphics-debug"
|
|
6
|
+
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
7
|
+
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
8
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
9
|
+
|
|
10
|
+
interface LabelMergingSolverInput {
|
|
11
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
12
|
+
inputProblem: InputProblem
|
|
13
|
+
traces: SolvedTracePath[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface LabelMergingSolverOutput {
|
|
17
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
18
|
+
mergedLabelNetIdMap: Record<string, Set<string>>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Merges multiple net labels into a single, larger label if they are on the
|
|
23
|
+
* same side of the same chip, reducing schematic clutter.
|
|
24
|
+
*/
|
|
25
|
+
export class MergedNetLabelObstacleSolver extends BaseSolver {
|
|
26
|
+
private input: LabelMergingSolverInput
|
|
27
|
+
private output!: LabelMergingSolverOutput
|
|
28
|
+
private inputProblem: InputProblem
|
|
29
|
+
private traces: SolvedTracePath[]
|
|
30
|
+
|
|
31
|
+
constructor(solverInput: LabelMergingSolverInput) {
|
|
32
|
+
// console.log(JSON.stringify(solverInput));
|
|
33
|
+
|
|
34
|
+
super()
|
|
35
|
+
this.input = solverInput
|
|
36
|
+
this.inputProblem = solverInput.inputProblem
|
|
37
|
+
this.traces = solverInput.traces
|
|
38
|
+
|
|
39
|
+
// Initialize output to a default state to allow visualization before the first step
|
|
40
|
+
this.output = {
|
|
41
|
+
netLabelPlacements: solverInput.netLabelPlacements,
|
|
42
|
+
mergedLabelNetIdMap: {},
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
override _step() {
|
|
47
|
+
const originalLabels = this.input.netLabelPlacements
|
|
48
|
+
const mergedLabelNetIdMap: Record<string, Set<string>> = {}
|
|
49
|
+
|
|
50
|
+
if (!originalLabels || originalLabels.length === 0) {
|
|
51
|
+
this.output = {
|
|
52
|
+
netLabelPlacements: [],
|
|
53
|
+
mergedLabelNetIdMap: {},
|
|
54
|
+
}
|
|
55
|
+
this.solved = true
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const labelGroups: Record<string, NetLabelPlacement[]> = {}
|
|
60
|
+
|
|
61
|
+
for (const p of originalLabels) {
|
|
62
|
+
if (p.pinIds.length === 0) continue
|
|
63
|
+
const chipId = p.pinIds[0].split(".")[0]
|
|
64
|
+
if (!chipId) continue
|
|
65
|
+
const key = `${chipId}-${p.orientation}`
|
|
66
|
+
if (!(key in labelGroups)) {
|
|
67
|
+
labelGroups[key] = []
|
|
68
|
+
}
|
|
69
|
+
labelGroups[key]!.push(p)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const finalPlacements: NetLabelPlacement[] = []
|
|
73
|
+
for (const [key, group] of Object.entries(labelGroups)) {
|
|
74
|
+
if (group.length <= 1) {
|
|
75
|
+
finalPlacements.push(...group)
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let minX = Infinity
|
|
80
|
+
let minY = Infinity
|
|
81
|
+
let maxX = -Infinity
|
|
82
|
+
let maxY = -Infinity
|
|
83
|
+
for (const p of group) {
|
|
84
|
+
const bounds = getRectBounds(p.center, p.width, p.height)
|
|
85
|
+
minX = Math.min(minX, bounds.minX)
|
|
86
|
+
minY = Math.min(minY, bounds.minY)
|
|
87
|
+
maxX = Math.max(maxX, bounds.maxX)
|
|
88
|
+
maxY = Math.max(maxY, bounds.maxY)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const newWidth = maxX - minX
|
|
92
|
+
const newHeight = maxY - minY
|
|
93
|
+
const template = group[0]!
|
|
94
|
+
const syntheticId = `merged-group-${key}`
|
|
95
|
+
const originalNetIds = new Set(group.map((p) => p.globalConnNetId))
|
|
96
|
+
mergedLabelNetIdMap[syntheticId] = originalNetIds
|
|
97
|
+
|
|
98
|
+
finalPlacements.push({
|
|
99
|
+
...template,
|
|
100
|
+
globalConnNetId: syntheticId,
|
|
101
|
+
width: newWidth,
|
|
102
|
+
height: newHeight,
|
|
103
|
+
center: { x: minX + newWidth / 2, y: minY + newHeight / 2 },
|
|
104
|
+
pinIds: [...new Set(group.flatMap((p) => p.pinIds))],
|
|
105
|
+
mspConnectionPairIds: [
|
|
106
|
+
...new Set(group.flatMap((p) => p.mspConnectionPairIds)),
|
|
107
|
+
],
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.output = {
|
|
112
|
+
netLabelPlacements: finalPlacements,
|
|
113
|
+
mergedLabelNetIdMap,
|
|
114
|
+
}
|
|
115
|
+
this.solved = true
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
getOutput(): LabelMergingSolverOutput {
|
|
119
|
+
return this.output
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
override visualize(): GraphicsObject {
|
|
123
|
+
const graphics = visualizeInputProblem(this.inputProblem, {
|
|
124
|
+
chipAlpha: 0.1,
|
|
125
|
+
connectionAlpha: 0.1,
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
if (!graphics.rects) graphics.rects = []
|
|
129
|
+
if (!graphics.lines) graphics.lines = []
|
|
130
|
+
if (!graphics.points) graphics.points = []
|
|
131
|
+
if (!graphics.texts) graphics.texts = []
|
|
132
|
+
|
|
133
|
+
const originalLabelsById = new Map<string, NetLabelPlacement>()
|
|
134
|
+
for (const label of this.input.netLabelPlacements) {
|
|
135
|
+
originalLabelsById.set(label.globalConnNetId, label)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const trace of this.traces) {
|
|
139
|
+
const line: Line = {
|
|
140
|
+
points: trace.tracePath.map((p) => ({ x: p.x, y: p.y })),
|
|
141
|
+
strokeColor: "blue",
|
|
142
|
+
}
|
|
143
|
+
graphics.lines!.push(line)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const finalLabel of this.output.netLabelPlacements) {
|
|
147
|
+
const isMerged = finalLabel.globalConnNetId.startsWith("merged-group-")
|
|
148
|
+
const color = getColorFromString(finalLabel.globalConnNetId)
|
|
149
|
+
|
|
150
|
+
if (isMerged) {
|
|
151
|
+
// Draw the new merged label
|
|
152
|
+
graphics.rects.push({
|
|
153
|
+
center: finalLabel.center,
|
|
154
|
+
width: finalLabel.width,
|
|
155
|
+
height: finalLabel.height,
|
|
156
|
+
fill: color.replace(/, 1\)/, ", 0.2)"), // semi-transparent
|
|
157
|
+
stroke: color,
|
|
158
|
+
label: finalLabel.globalConnNetId,
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
const originalNetIds =
|
|
162
|
+
this.output.mergedLabelNetIdMap[finalLabel.globalConnNetId]
|
|
163
|
+
if (originalNetIds) {
|
|
164
|
+
for (const originalNetId of originalNetIds) {
|
|
165
|
+
const originalLabel = originalLabelsById.get(originalNetId)
|
|
166
|
+
if (originalLabel) {
|
|
167
|
+
// Draw the original label as a dashed box
|
|
168
|
+
const bounds = getRectBounds(
|
|
169
|
+
originalLabel.center,
|
|
170
|
+
originalLabel.width,
|
|
171
|
+
originalLabel.height,
|
|
172
|
+
)
|
|
173
|
+
const p1 = { x: bounds.minX, y: bounds.minY }
|
|
174
|
+
const p2 = { x: bounds.maxX, y: bounds.minY }
|
|
175
|
+
const p3 = { x: bounds.maxX, y: bounds.maxY }
|
|
176
|
+
const p4 = { x: bounds.minX, y: bounds.maxY }
|
|
177
|
+
graphics.lines.push({
|
|
178
|
+
points: [p1, p2, p3, p4, p1],
|
|
179
|
+
strokeColor: color,
|
|
180
|
+
strokeDash: "4 4",
|
|
181
|
+
})
|
|
182
|
+
// Draw line from original to new center
|
|
183
|
+
graphics.lines.push({
|
|
184
|
+
points: [originalLabel.center, finalLabel.center],
|
|
185
|
+
strokeColor: color,
|
|
186
|
+
strokeDash: "2 2",
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
// Draw un-merged labels
|
|
193
|
+
graphics.rects.push({
|
|
194
|
+
center: finalLabel.center,
|
|
195
|
+
width: finalLabel.width,
|
|
196
|
+
height: finalLabel.height,
|
|
197
|
+
stroke: color,
|
|
198
|
+
label: finalLabel.globalConnNetId,
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return graphics
|
|
204
|
+
}
|
|
205
|
+
}
|