@tscircuit/schematic-trace-solver 0.0.40 → 0.0.42
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 +155 -9
- package/dist/index.js +998 -479
- package/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts +238 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +61 -26
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +94 -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/sub-solvers/TraceCleanupSolver/TraceCleanupSolver.ts +132 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{balanceLShapes.ts → sub-solvers/TraceCleanupSolver/balanceLShapes.ts} +12 -10
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{hasCollisions.ts → sub-solvers/TraceCleanupSolver/hasCollisions.ts} +1 -1
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{hasCollisionsWithLabels.ts → sub-solvers/TraceCleanupSolver/hasCollisionsWithLabels.ts} +1 -1
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{minimizeTurnsWithFilteredLabels.ts → sub-solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts} +6 -6
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{simplifyPath.ts → sub-solvers/TraceCleanupSolver/simplifyPath.ts} +1 -1
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{turnMinimization.ts → sub-solvers/TraceCleanupSolver/turnMinimization.ts} +0 -2
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts +104 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts +24 -154
- package/lib/utils/does-trace-overlap-with-existing-traces.ts +33 -0
- package/package.json +1 -1
- package/site/examples/example25.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/example25.json +96 -0
- package/tests/examples/__snapshots__/example03.snap.svg +67 -76
- package/tests/examples/__snapshots__/example10.snap.svg +24 -30
- package/tests/examples/__snapshots__/example13.snap.svg +81 -84
- package/tests/examples/__snapshots__/example15.snap.svg +5 -8
- package/tests/examples/__snapshots__/example21.snap.svg +68 -74
- package/tests/examples/__snapshots__/example25.snap.svg +3 -3
- package/tests/examples/__snapshots__/example26.snap.svg +3 -3
- package/tests/examples/__snapshots__/example28.snap.svg +273 -0
- package/tests/examples/example28.test.ts +13 -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/TraceCleanupSolver.test.ts +19 -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/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/TraceCleanupSolver.snap.svg +119 -0
- /package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{countTurns.ts → sub-solvers/TraceCleanupSolver/countTurns.ts} +0 -0
- /package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{tryConnectPoints.ts → sub-solvers/TraceCleanupSolver/tryConnectPoints.ts} +0 -0
|
@@ -1,16 +1,13 @@
|
|
|
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"
|
|
8
|
+
import { TraceCleanupSolver } from "./sub-solvers/TraceCleanupSolver/TraceCleanupSolver"
|
|
9
9
|
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
10
|
-
import
|
|
11
|
-
import { minimizeTurnsWithFilteredLabels } from "./minimizeTurnsWithFilteredLabels"
|
|
12
|
-
import { balanceLShapes } from "./balanceLShapes"
|
|
13
|
-
import { NetLabelPlacementSolver } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
10
|
+
import { OverlapAvoidanceStepSolver } from "./sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver"
|
|
14
11
|
|
|
15
12
|
interface TraceLabelOverlapAvoidanceSolverInput {
|
|
16
13
|
inputProblem: InputProblem
|
|
@@ -18,222 +15,127 @@ interface TraceLabelOverlapAvoidanceSolverInput {
|
|
|
18
15
|
netLabelPlacements: NetLabelPlacement[]
|
|
19
16
|
}
|
|
20
17
|
|
|
18
|
+
/**
|
|
19
|
+
* This solver is a pipeline that runs a series of sub-solvers to resolve
|
|
20
|
+
* trace-label overlaps and clean up the resulting traces.
|
|
21
|
+
*/
|
|
21
22
|
export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
inputProblem: InputProblem
|
|
24
|
+
traces: SolvedTracePath[]
|
|
25
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
26
|
+
|
|
27
|
+
// sub-solver instances
|
|
28
|
+
labelMergingSolver?: MergedNetLabelObstacleSolver
|
|
29
|
+
overlapAvoidanceSolver?: OverlapAvoidanceStepSolver
|
|
30
|
+
traceCleanupSolver?: TraceCleanupSolver
|
|
31
|
+
pipelineStepIndex = 0
|
|
30
32
|
|
|
31
33
|
constructor(solverInput: TraceLabelOverlapAvoidanceSolverInput) {
|
|
32
34
|
super()
|
|
33
|
-
this.
|
|
35
|
+
this.inputProblem = solverInput.inputProblem
|
|
34
36
|
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
|
|
37
|
+
this.netLabelPlacements = solverInput.netLabelPlacements
|
|
99
38
|
}
|
|
100
39
|
|
|
101
40
|
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)
|
|
41
|
+
// If a sub-solver is active, step it and check for completion.
|
|
42
|
+
if (this.activeSubSolver) {
|
|
43
|
+
this.activeSubSolver.step()
|
|
44
|
+
|
|
45
|
+
if (this.activeSubSolver.solved) {
|
|
46
|
+
this.activeSubSolver = null
|
|
47
|
+
this.pipelineStepIndex++
|
|
48
|
+
} else if (this.activeSubSolver.failed) {
|
|
49
|
+
this.failed = true // If any sub-solver fails, the whole thing fails
|
|
50
|
+
this.activeSubSolver = null
|
|
128
51
|
}
|
|
129
|
-
return
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
if (unfriendlyOverlaps.length === 0) {
|
|
133
|
-
this.solved = true
|
|
134
|
-
return
|
|
52
|
+
return // Return to allow the sub-solver to run
|
|
135
53
|
}
|
|
136
54
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
55
|
+
// If no sub-solver is active, create the next one in the pipeline.
|
|
56
|
+
switch (this.pipelineStepIndex) {
|
|
57
|
+
case 0:
|
|
58
|
+
this.labelMergingSolver = new MergedNetLabelObstacleSolver({
|
|
59
|
+
netLabelPlacements: this.netLabelPlacements,
|
|
60
|
+
inputProblem: this.inputProblem,
|
|
61
|
+
traces: this.traces,
|
|
62
|
+
})
|
|
63
|
+
this.activeSubSolver = this.labelMergingSolver
|
|
64
|
+
break
|
|
65
|
+
|
|
66
|
+
case 1:
|
|
67
|
+
this.overlapAvoidanceSolver = new OverlapAvoidanceStepSolver({
|
|
68
|
+
inputProblem: this.inputProblem,
|
|
69
|
+
traces: this.traces,
|
|
70
|
+
netLabelPlacements:
|
|
71
|
+
this.labelMergingSolver!.getOutput().netLabelPlacements,
|
|
72
|
+
mergedLabelNetIdMap:
|
|
73
|
+
this.labelMergingSolver!.getOutput().mergedLabelNetIdMap,
|
|
74
|
+
})
|
|
75
|
+
this.activeSubSolver = this.overlapAvoidanceSolver
|
|
76
|
+
break
|
|
77
|
+
|
|
78
|
+
case 2:
|
|
79
|
+
this.traceCleanupSolver = new TraceCleanupSolver({
|
|
80
|
+
inputProblem: this.inputProblem,
|
|
81
|
+
allTraces: this.overlapAvoidanceSolver!.getOutput().allTraces,
|
|
82
|
+
targetTraceIds: new Set(
|
|
83
|
+
this.overlapAvoidanceSolver!.getOutput().modifiedTraces.map(
|
|
84
|
+
(t) => t.mspPairId,
|
|
85
|
+
),
|
|
86
|
+
),
|
|
87
|
+
allLabelPlacements:
|
|
88
|
+
this.labelMergingSolver!.getOutput().netLabelPlacements,
|
|
89
|
+
mergedLabelNetIdMap:
|
|
90
|
+
this.labelMergingSolver!.getOutput().mergedLabelNetIdMap,
|
|
91
|
+
paddingBuffer: 0.01,
|
|
92
|
+
})
|
|
93
|
+
this.activeSubSolver = this.traceCleanupSolver
|
|
94
|
+
break
|
|
95
|
+
|
|
96
|
+
default:
|
|
97
|
+
this.solved = true
|
|
98
|
+
break
|
|
140
99
|
}
|
|
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
100
|
}
|
|
203
101
|
|
|
204
102
|
getOutput() {
|
|
205
103
|
return {
|
|
206
|
-
traces: this.
|
|
207
|
-
netLabelPlacements:
|
|
104
|
+
traces: this.traceCleanupSolver?.getOutput().traces ?? this.traces,
|
|
105
|
+
netLabelPlacements:
|
|
106
|
+
this.labelMergingSolver?.getOutput().netLabelPlacements ??
|
|
107
|
+
this.netLabelPlacements,
|
|
208
108
|
}
|
|
209
109
|
}
|
|
210
110
|
|
|
211
111
|
override visualize(): GraphicsObject {
|
|
212
|
-
|
|
112
|
+
if (this.activeSubSolver) {
|
|
113
|
+
return this.activeSubSolver.visualize()
|
|
114
|
+
}
|
|
213
115
|
|
|
116
|
+
// When no sub-solver is active, show the current state of the pipeline
|
|
117
|
+
const graphics = visualizeInputProblem(this.inputProblem)
|
|
214
118
|
if (!graphics.lines) graphics.lines = []
|
|
215
|
-
if (!graphics.circles) graphics.circles = []
|
|
216
|
-
if (!graphics.texts) graphics.texts = []
|
|
217
119
|
if (!graphics.rects) graphics.rects = []
|
|
218
120
|
|
|
219
|
-
|
|
121
|
+
const output = this.getOutput()
|
|
122
|
+
|
|
123
|
+
for (const trace of output.traces) {
|
|
220
124
|
graphics.lines!.push({
|
|
221
125
|
points: trace.tracePath,
|
|
222
126
|
strokeColor: "purple",
|
|
223
127
|
})
|
|
224
128
|
}
|
|
225
129
|
|
|
226
|
-
for (const
|
|
130
|
+
for (const label of output.netLabelPlacements) {
|
|
131
|
+
const color = getColorFromString(label.globalConnNetId, 0.3)
|
|
227
132
|
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),
|
|
133
|
+
center: label.center,
|
|
134
|
+
width: label.width,
|
|
135
|
+
height: label.height,
|
|
136
|
+
fill: color,
|
|
137
|
+
stroke: color.replace("0.3", "1"),
|
|
138
|
+
label: label.globalConnNetId,
|
|
237
139
|
})
|
|
238
140
|
}
|
|
239
141
|
|
|
@@ -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 { simplifyPath } from "./simplifyPath"
|
|
7
|
+
import { generateSnipAndReconnectCandidates } from "./trySnipAndReconnect"
|
|
8
|
+
import { simplifyPath } from "./sub-solvers/TraceCleanupSolver/simplifyPath"
|
|
9
|
+
import { generateFourPointDetourCandidates } from "./tryFourPointDetour"
|
|
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
|
+
}
|