@tscircuit/schematic-trace-solver 0.0.50 → 0.0.52
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 +120 -3
- package/dist/index.js +1149 -51
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +30 -0
- package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/TraceAnchoredNetLabelOverlapSolver.ts +331 -0
- package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/candidates.ts +429 -0
- package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/geometry.ts +250 -0
- package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/types.ts +47 -0
- package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/visualize.ts +125 -0
- package/lib/solvers/VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver.ts +268 -0
- package/lib/solvers/VccNetLabelCornerPlacementSolver/geometry.ts +59 -0
- package/lib/solvers/VccNetLabelCornerPlacementSolver/types.ts +37 -0
- package/lib/solvers/VccNetLabelCornerPlacementSolver/visualize.ts +114 -0
- package/package.json +1 -1
- package/tests/examples/__snapshots__/example12.snap.svg +2 -2
- package/tests/examples/__snapshots__/example14.snap.svg +68 -68
- package/tests/examples/__snapshots__/example17.snap.svg +2 -2
- package/tests/examples/__snapshots__/example30.snap.svg +59 -59
- package/tests/examples/__snapshots__/example31.snap.svg +12 -12
|
@@ -22,6 +22,8 @@ import { MergedNetLabelObstacleSolver } from "../TraceLabelOverlapAvoidanceSolve
|
|
|
22
22
|
import { TraceCleanupSolver } from "../TraceCleanupSolver/TraceCleanupSolver"
|
|
23
23
|
import { Example28Solver } from "../Example28Solver/Example28Solver"
|
|
24
24
|
import { AvailableNetOrientationSolver } from "../AvailableNetOrientationSolver/AvailableNetOrientationSolver"
|
|
25
|
+
import { VccNetLabelCornerPlacementSolver } from "../VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver"
|
|
26
|
+
import { TraceAnchoredNetLabelOverlapSolver } from "../TraceAnchoredNetLabelOverlapSolver/TraceAnchoredNetLabelOverlapSolver"
|
|
25
27
|
|
|
26
28
|
type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
|
|
27
29
|
solverName: string
|
|
@@ -73,6 +75,8 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
73
75
|
traceCleanupSolver?: TraceCleanupSolver
|
|
74
76
|
example28Solver?: Example28Solver
|
|
75
77
|
availableNetOrientationSolver?: AvailableNetOrientationSolver
|
|
78
|
+
vccNetLabelCornerPlacementSolver?: VccNetLabelCornerPlacementSolver
|
|
79
|
+
traceAnchoredNetLabelOverlapSolver?: TraceAnchoredNetLabelOverlapSolver
|
|
76
80
|
|
|
77
81
|
startTimeOfPhase: Record<string, number>
|
|
78
82
|
endTimeOfPhase: Record<string, number>
|
|
@@ -254,6 +258,32 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
254
258
|
},
|
|
255
259
|
],
|
|
256
260
|
),
|
|
261
|
+
definePipelineStep(
|
|
262
|
+
"vccNetLabelCornerPlacementSolver",
|
|
263
|
+
VccNetLabelCornerPlacementSolver,
|
|
264
|
+
(instance) => {
|
|
265
|
+
return [
|
|
266
|
+
{
|
|
267
|
+
inputProblem: instance.inputProblem,
|
|
268
|
+
traces: instance.availableNetOrientationSolver!.traces,
|
|
269
|
+
netLabelPlacements:
|
|
270
|
+
instance.availableNetOrientationSolver!.outputNetLabelPlacements,
|
|
271
|
+
},
|
|
272
|
+
]
|
|
273
|
+
},
|
|
274
|
+
),
|
|
275
|
+
definePipelineStep(
|
|
276
|
+
"traceAnchoredNetLabelOverlapSolver",
|
|
277
|
+
TraceAnchoredNetLabelOverlapSolver,
|
|
278
|
+
(instance) => [
|
|
279
|
+
{
|
|
280
|
+
inputProblem: instance.inputProblem,
|
|
281
|
+
traces: instance.availableNetOrientationSolver!.traces,
|
|
282
|
+
netLabelPlacements:
|
|
283
|
+
instance.vccNetLabelCornerPlacementSolver!.outputNetLabelPlacements,
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
),
|
|
257
287
|
]
|
|
258
288
|
|
|
259
289
|
constructor(inputProblem: InputProblem) {
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
3
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
4
|
+
import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
5
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
6
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
7
|
+
import { generateCandidatesAlongTrace } from "./candidates"
|
|
8
|
+
import {
|
|
9
|
+
getLabelBounds,
|
|
10
|
+
getTraceLocationsForPoint,
|
|
11
|
+
rectsOverlap,
|
|
12
|
+
traceCrossesBoundsInterior,
|
|
13
|
+
} from "./geometry"
|
|
14
|
+
import type {
|
|
15
|
+
Bounds,
|
|
16
|
+
CandidateStatus,
|
|
17
|
+
LabelCandidate,
|
|
18
|
+
LabelOverlap,
|
|
19
|
+
TraceAnchoredNetLabelOverlapSolverParams,
|
|
20
|
+
} from "./types"
|
|
21
|
+
import { visualizeTraceAnchoredNetLabelOverlapSolver } from "./visualize"
|
|
22
|
+
|
|
23
|
+
type ActiveOverlapSearch = {
|
|
24
|
+
overlap: LabelOverlap
|
|
25
|
+
queuedLabelIndices: number[]
|
|
26
|
+
labelIndex: number | null
|
|
27
|
+
candidates: LabelCandidate[]
|
|
28
|
+
candidateIndex: number
|
|
29
|
+
candidateResults: LabelCandidate[]
|
|
30
|
+
completed: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class TraceAnchoredNetLabelOverlapSolver extends BaseSolver {
|
|
34
|
+
inputProblem: InputProblem
|
|
35
|
+
traces: SolvedTracePath[]
|
|
36
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
37
|
+
|
|
38
|
+
outputNetLabelPlacements: NetLabelPlacement[]
|
|
39
|
+
currentOverlap: LabelOverlap | null = null
|
|
40
|
+
currentCandidateResults: LabelCandidate[] = []
|
|
41
|
+
|
|
42
|
+
private activeSearch: ActiveOverlapSearch | null = null
|
|
43
|
+
private skippedOverlapKeys = new Set<string>()
|
|
44
|
+
|
|
45
|
+
constructor(params: TraceAnchoredNetLabelOverlapSolverParams) {
|
|
46
|
+
super()
|
|
47
|
+
this.inputProblem = params.inputProblem
|
|
48
|
+
this.traces = params.traces
|
|
49
|
+
this.netLabelPlacements = params.netLabelPlacements
|
|
50
|
+
this.outputNetLabelPlacements = [...params.netLabelPlacements]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override getConstructorParams(): ConstructorParameters<
|
|
54
|
+
typeof TraceAnchoredNetLabelOverlapSolver
|
|
55
|
+
>[0] {
|
|
56
|
+
return {
|
|
57
|
+
inputProblem: this.inputProblem,
|
|
58
|
+
traces: this.traces,
|
|
59
|
+
netLabelPlacements: this.netLabelPlacements,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
override _step() {
|
|
64
|
+
if (this.activeSearch?.completed) this.clearActiveSearch()
|
|
65
|
+
|
|
66
|
+
const search = this.getNextSearchWithCandidates()
|
|
67
|
+
if (!search) {
|
|
68
|
+
this.finish()
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.evaluateNextCandidate(search)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getOutput() {
|
|
76
|
+
return {
|
|
77
|
+
netLabelPlacements: this.outputNetLabelPlacements,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private findNextOverlap() {
|
|
82
|
+
for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
|
|
83
|
+
for (let j = i + 1; j < this.outputNetLabelPlacements.length; j++) {
|
|
84
|
+
if (!this.isLabelEligible(i) && !this.isLabelEligible(j)) continue
|
|
85
|
+
|
|
86
|
+
const overlap = {
|
|
87
|
+
firstLabelIndex: i,
|
|
88
|
+
secondLabelIndex: j,
|
|
89
|
+
}
|
|
90
|
+
if (this.skippedOverlapKeys.has(this.getOverlapKey(overlap))) continue
|
|
91
|
+
if (this.labelsOverlap(overlap)) return overlap
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private isLabelEligible(labelIndex: number) {
|
|
99
|
+
return this.getTraceLocationsForLabel(labelIndex).length > 0
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private labelsOverlap(overlap: LabelOverlap) {
|
|
103
|
+
const first = this.outputNetLabelPlacements[overlap.firstLabelIndex]
|
|
104
|
+
const second = this.outputNetLabelPlacements[overlap.secondLabelIndex]
|
|
105
|
+
if (!first || !second) return false
|
|
106
|
+
|
|
107
|
+
return rectsOverlap(getLabelBounds(first), getLabelBounds(second))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private startNextOverlapSearch() {
|
|
111
|
+
const overlap = this.findNextOverlap()
|
|
112
|
+
if (!overlap) return null
|
|
113
|
+
|
|
114
|
+
const search: ActiveOverlapSearch = {
|
|
115
|
+
overlap,
|
|
116
|
+
queuedLabelIndices: this.getMoveLabelIndices(overlap),
|
|
117
|
+
labelIndex: null,
|
|
118
|
+
candidates: [],
|
|
119
|
+
candidateIndex: 0,
|
|
120
|
+
candidateResults: [],
|
|
121
|
+
completed: false,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
this.setActiveSearch(search)
|
|
125
|
+
return search
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private getMoveLabelIndices(overlap: LabelOverlap) {
|
|
129
|
+
const labelIndices: number[] = []
|
|
130
|
+
|
|
131
|
+
if (this.isLabelEligible(overlap.secondLabelIndex)) {
|
|
132
|
+
labelIndices.push(overlap.secondLabelIndex)
|
|
133
|
+
}
|
|
134
|
+
if (this.isLabelEligible(overlap.firstLabelIndex)) {
|
|
135
|
+
labelIndices.push(overlap.firstLabelIndex)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return labelIndices
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private getNextSearchWithCandidates() {
|
|
142
|
+
while (true) {
|
|
143
|
+
const search = this.activeSearch ?? this.startNextOverlapSearch()
|
|
144
|
+
if (!search) return null
|
|
145
|
+
if (this.activateNextLabelSearch(search)) return search
|
|
146
|
+
|
|
147
|
+
this.skipSearch(search)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private activateNextLabelSearch(search: ActiveOverlapSearch) {
|
|
152
|
+
if (
|
|
153
|
+
search.labelIndex !== null &&
|
|
154
|
+
search.candidateIndex < search.candidates.length
|
|
155
|
+
) {
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
while (search.queuedLabelIndices.length > 0) {
|
|
160
|
+
const labelIndex = search.queuedLabelIndices.shift()!
|
|
161
|
+
const label = this.outputNetLabelPlacements[labelIndex]
|
|
162
|
+
if (!label) continue
|
|
163
|
+
|
|
164
|
+
search.labelIndex = labelIndex
|
|
165
|
+
search.candidates = this.getCandidatesForLabel(label)
|
|
166
|
+
search.candidateIndex = 0
|
|
167
|
+
if (search.candidates.length > 0) return true
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
search.labelIndex = null
|
|
171
|
+
search.candidates = []
|
|
172
|
+
search.candidateIndex = 0
|
|
173
|
+
return false
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private skipSearch(search: ActiveOverlapSearch) {
|
|
177
|
+
this.skippedOverlapKeys.add(this.getOverlapKey(search.overlap))
|
|
178
|
+
this.clearActiveSearch()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private evaluateNextCandidate(search: ActiveOverlapSearch) {
|
|
182
|
+
const labelIndex = search.labelIndex
|
|
183
|
+
if (labelIndex === null) return
|
|
184
|
+
|
|
185
|
+
const label = this.outputNetLabelPlacements[labelIndex]
|
|
186
|
+
const candidate = search.candidates[search.candidateIndex]
|
|
187
|
+
if (!label || !candidate) return
|
|
188
|
+
|
|
189
|
+
search.candidateIndex += 1
|
|
190
|
+
|
|
191
|
+
const evaluatedCandidate = {
|
|
192
|
+
...candidate,
|
|
193
|
+
status: this.getCandidateStatus(candidate, labelIndex),
|
|
194
|
+
}
|
|
195
|
+
search.candidateResults.push(evaluatedCandidate)
|
|
196
|
+
|
|
197
|
+
if (evaluatedCandidate.status !== "valid") return
|
|
198
|
+
|
|
199
|
+
evaluatedCandidate.selected = true
|
|
200
|
+
this.applyCandidate(labelIndex, label, evaluatedCandidate)
|
|
201
|
+
search.completed = true
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private getCandidatesForLabel(label: NetLabelPlacement) {
|
|
205
|
+
const candidates: LabelCandidate[] = []
|
|
206
|
+
|
|
207
|
+
for (const traceLocation of getTraceLocationsForPoint(
|
|
208
|
+
label.anchorPoint,
|
|
209
|
+
this.traces,
|
|
210
|
+
).filter(({ trace }) => this.isTraceAssociatedWithLabel(trace, label))) {
|
|
211
|
+
candidates.push(
|
|
212
|
+
...generateCandidatesAlongTrace({
|
|
213
|
+
inputProblem: this.inputProblem,
|
|
214
|
+
label,
|
|
215
|
+
traceLocation,
|
|
216
|
+
}),
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return candidates.sort(
|
|
221
|
+
(a, b) =>
|
|
222
|
+
a.distanceFromOriginal - b.distanceFromOriginal ||
|
|
223
|
+
a.pathDistance - b.pathDistance,
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private getCandidateStatus(
|
|
228
|
+
candidate: LabelCandidate,
|
|
229
|
+
labelIndex: number,
|
|
230
|
+
): CandidateStatus {
|
|
231
|
+
const bounds = getLabelBounds(candidate)
|
|
232
|
+
if (this.intersectsAnyChip(bounds)) return "chip-collision"
|
|
233
|
+
if (traceCrossesBoundsInterior(bounds, this.traces))
|
|
234
|
+
return "trace-collision"
|
|
235
|
+
if (this.intersectsAnyOtherLabel(bounds, labelIndex)) {
|
|
236
|
+
return "netlabel-collision"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return "valid"
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private applyCandidate(
|
|
243
|
+
labelIndex: number,
|
|
244
|
+
label: NetLabelPlacement,
|
|
245
|
+
candidate: LabelCandidate,
|
|
246
|
+
) {
|
|
247
|
+
this.outputNetLabelPlacements[labelIndex] = {
|
|
248
|
+
...label,
|
|
249
|
+
anchorPoint: candidate.anchorPoint,
|
|
250
|
+
center: candidate.center,
|
|
251
|
+
width: candidate.width,
|
|
252
|
+
height: candidate.height,
|
|
253
|
+
orientation: candidate.orientation,
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private intersectsAnyChip(bounds: Bounds) {
|
|
258
|
+
return this.inputProblem.chips.some((chip) =>
|
|
259
|
+
rectsOverlap(bounds, getRectBounds(chip.center, chip.width, chip.height)),
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private intersectsAnyOtherLabel(bounds: Bounds, labelIndex: number) {
|
|
264
|
+
return this.outputNetLabelPlacements.some((label, index) => {
|
|
265
|
+
if (index === labelIndex) return false
|
|
266
|
+
return rectsOverlap(bounds, getLabelBounds(label))
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private getTraceLocationsForLabel(labelIndex: number) {
|
|
271
|
+
const label = this.outputNetLabelPlacements[labelIndex]
|
|
272
|
+
if (!label) return []
|
|
273
|
+
|
|
274
|
+
return getTraceLocationsForPoint(label.anchorPoint, this.traces).filter(
|
|
275
|
+
({ trace }) => this.isTraceAssociatedWithLabel(trace, label),
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private isTraceAssociatedWithLabel(
|
|
280
|
+
trace: SolvedTracePath,
|
|
281
|
+
label: NetLabelPlacement,
|
|
282
|
+
) {
|
|
283
|
+
if (label.mspConnectionPairIds.includes(trace.mspPairId)) return true
|
|
284
|
+
if (label.netId !== undefined && label.netId === trace.userNetId) {
|
|
285
|
+
return true
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return label.globalConnNetId === trace.globalConnNetId
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private getOverlapKey(overlap: LabelOverlap) {
|
|
292
|
+
const first = this.outputNetLabelPlacements[overlap.firstLabelIndex]
|
|
293
|
+
const second = this.outputNetLabelPlacements[overlap.secondLabelIndex]
|
|
294
|
+
return [
|
|
295
|
+
overlap.firstLabelIndex,
|
|
296
|
+
overlap.secondLabelIndex,
|
|
297
|
+
first?.anchorPoint.x,
|
|
298
|
+
first?.anchorPoint.y,
|
|
299
|
+
second?.anchorPoint.x,
|
|
300
|
+
second?.anchorPoint.y,
|
|
301
|
+
].join(":")
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private finish() {
|
|
305
|
+
this.clearActiveSearch()
|
|
306
|
+
this.solved = true
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private setActiveSearch(search: ActiveOverlapSearch) {
|
|
310
|
+
this.activeSearch = search
|
|
311
|
+
this.currentOverlap = search.overlap
|
|
312
|
+
this.currentCandidateResults = search.candidateResults
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private clearActiveSearch() {
|
|
316
|
+
this.activeSearch = null
|
|
317
|
+
this.currentOverlap = null
|
|
318
|
+
this.currentCandidateResults = []
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
override visualize(): GraphicsObject {
|
|
322
|
+
return visualizeTraceAnchoredNetLabelOverlapSolver({
|
|
323
|
+
inputProblem: this.inputProblem,
|
|
324
|
+
traces: this.traces,
|
|
325
|
+
outputNetLabelPlacements: this.outputNetLabelPlacements,
|
|
326
|
+
currentOverlap: this.currentOverlap,
|
|
327
|
+
currentCandidateResults: this.currentCandidateResults,
|
|
328
|
+
solved: this.solved,
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
}
|