@tscircuit/schematic-trace-solver 0.0.61 → 0.0.63
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 +71 -5
- package/dist/index.js +421 -16
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +16 -0
- package/lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts +449 -0
- package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +24 -0
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver.ts +7 -1
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry.ts +9 -4
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/solvePortOnlyPin.ts +3 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +15 -0
- package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/candidates.ts +19 -0
- package/lib/types/InputProblem.ts +1 -0
- package/package.json +1 -1
- package/site/examples/example39.page.tsx +4 -0
- package/site/examples/example40.page.tsx +4 -0
- package/site/examples/example41.page.tsx +4 -0
- package/tests/assets/example39.json +71 -0
- package/tests/assets/example40.json +157 -0
- package/tests/assets/example41.json +82 -0
- package/tests/examples/__snapshots__/example39.snap.svg +107 -0
- package/tests/examples/__snapshots__/example40.snap.svg +231 -0
- package/tests/examples/__snapshots__/example41.snap.svg +119 -0
- package/tests/examples/example39.test.ts +12 -0
- package/tests/examples/example40.test.ts +12 -0
- package/tests/examples/example41.test.ts +12 -0
|
@@ -0,0 +1,449 @@
|
|
|
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 type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
|
+
import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
6
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
7
|
+
import type { FacingDirection } from "lib/utils/dir"
|
|
8
|
+
import {
|
|
9
|
+
getDimsForOrientation,
|
|
10
|
+
getCenterFromAnchor,
|
|
11
|
+
getRectBounds,
|
|
12
|
+
} from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
13
|
+
import { rectIntersectsAnyTrace } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions"
|
|
14
|
+
import { ChipObstacleSpatialIndex } from "lib/data-structures/ChipObstacleSpatialIndex"
|
|
15
|
+
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
16
|
+
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
17
|
+
|
|
18
|
+
type CandidateStatus =
|
|
19
|
+
| "ok"
|
|
20
|
+
| "chip-collision"
|
|
21
|
+
| "trace-collision"
|
|
22
|
+
| "label-collision"
|
|
23
|
+
|
|
24
|
+
const ANCHOR_TRACE_CLEARANCE = 1e-4
|
|
25
|
+
const SEGMENT_PARALLEL_EPS = 1e-6
|
|
26
|
+
const CANDIDATE_STEP = 0.1
|
|
27
|
+
|
|
28
|
+
const OUTWARD_DIR: Record<FacingDirection, { x: number; y: number }> = {
|
|
29
|
+
"x+": { x: 1, y: 0 },
|
|
30
|
+
"x-": { x: -1, y: 0 },
|
|
31
|
+
"y+": { x: 0, y: 1 },
|
|
32
|
+
"y-": { x: 0, y: -1 },
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const CANDIDATE_STATUS_COLOR: Record<CandidateStatus, string> = {
|
|
36
|
+
ok: "green",
|
|
37
|
+
"label-collision": "orange",
|
|
38
|
+
"trace-collision": "darkorange",
|
|
39
|
+
"chip-collision": "red",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const CANDIDATE_STATUS_FILL: Record<CandidateStatus, string> = {
|
|
43
|
+
ok: "rgba(0, 200, 0, 0.25)",
|
|
44
|
+
"label-collision": "rgba(255, 160, 0, 0.2)",
|
|
45
|
+
"trace-collision": "rgba(200, 80, 0, 0.2)",
|
|
46
|
+
"chip-collision": "rgba(220, 0, 0, 0.15)",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type Candidate = {
|
|
50
|
+
orientation: FacingDirection
|
|
51
|
+
anchor: { x: number; y: number }
|
|
52
|
+
center: { x: number; y: number }
|
|
53
|
+
width: number
|
|
54
|
+
height: number
|
|
55
|
+
bounds: { minX: number; minY: number; maxX: number; maxY: number }
|
|
56
|
+
hostPairId?: MspConnectionPairId
|
|
57
|
+
hostSegIndex?: number
|
|
58
|
+
status: CandidateStatus | null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function boundsOverlap(
|
|
62
|
+
a: { minX: number; minY: number; maxX: number; maxY: number },
|
|
63
|
+
b: { minX: number; minY: number; maxX: number; maxY: number },
|
|
64
|
+
): boolean {
|
|
65
|
+
return (
|
|
66
|
+
a.minX < b.maxX - 1e-9 &&
|
|
67
|
+
a.maxX > b.minX + 1e-9 &&
|
|
68
|
+
a.minY < b.maxY - 1e-9 &&
|
|
69
|
+
a.maxY > b.minY + 1e-9
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function sampleAnchorsAlongSegment(
|
|
74
|
+
a: { x: number; y: number },
|
|
75
|
+
b: { x: number; y: number },
|
|
76
|
+
): Array<{ x: number; y: number }> {
|
|
77
|
+
const dx = b.x - a.x
|
|
78
|
+
const dy = b.y - a.y
|
|
79
|
+
const len = Math.sqrt(dx * dx + dy * dy)
|
|
80
|
+
const steps = Math.max(1, Math.round(len / CANDIDATE_STEP))
|
|
81
|
+
const anchors: Array<{ x: number; y: number }> = []
|
|
82
|
+
for (let k = 0; k <= steps; k++) {
|
|
83
|
+
const t = k / steps
|
|
84
|
+
anchors.push({ x: a.x + t * dx, y: a.y + t * dy })
|
|
85
|
+
}
|
|
86
|
+
return anchors
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface NetLabelNetLabelCollisionSolverParams {
|
|
90
|
+
inputProblem: InputProblem
|
|
91
|
+
traces: SolvedTracePath[]
|
|
92
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
96
|
+
inputProblem: InputProblem
|
|
97
|
+
traces: SolvedTracePath[]
|
|
98
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
99
|
+
|
|
100
|
+
outputNetLabelPlacements: NetLabelPlacement[]
|
|
101
|
+
|
|
102
|
+
currentCollision: [NetLabelPlacement, NetLabelPlacement] | null = null
|
|
103
|
+
currentLabelToMove: NetLabelPlacement | null = null
|
|
104
|
+
candidateResults: Candidate[] = []
|
|
105
|
+
|
|
106
|
+
private chipIndex: ChipObstacleSpatialIndex
|
|
107
|
+
private traceMap: Record<MspConnectionPairId, SolvedTracePath>
|
|
108
|
+
private skippedCollisionKeys = new Set<string>()
|
|
109
|
+
|
|
110
|
+
private labelsToTry: NetLabelPlacement[] = []
|
|
111
|
+
private candidateQueue: Candidate[] = []
|
|
112
|
+
private candidateIndex = 0
|
|
113
|
+
|
|
114
|
+
constructor(params: NetLabelNetLabelCollisionSolverParams) {
|
|
115
|
+
super()
|
|
116
|
+
this.inputProblem = params.inputProblem
|
|
117
|
+
this.traces = params.traces
|
|
118
|
+
this.netLabelPlacements = params.netLabelPlacements
|
|
119
|
+
this.outputNetLabelPlacements = [...params.netLabelPlacements]
|
|
120
|
+
this.chipIndex =
|
|
121
|
+
params.inputProblem._chipObstacleSpatialIndex ??
|
|
122
|
+
new ChipObstacleSpatialIndex(params.inputProblem.chips)
|
|
123
|
+
this.traceMap = Object.fromEntries(
|
|
124
|
+
params.traces.map((t) => [t.mspPairId, t]),
|
|
125
|
+
) as Record<MspConnectionPairId, SolvedTracePath>
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
override getConstructorParams(): ConstructorParameters<
|
|
129
|
+
typeof NetLabelNetLabelCollisionSolver
|
|
130
|
+
>[0] {
|
|
131
|
+
return {
|
|
132
|
+
inputProblem: this.inputProblem,
|
|
133
|
+
traces: this.traces,
|
|
134
|
+
netLabelPlacements: this.netLabelPlacements,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getOutput() {
|
|
139
|
+
return { netLabelPlacements: this.outputNetLabelPlacements }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private labelBounds(label: NetLabelPlacement) {
|
|
143
|
+
return getRectBounds(label.center, label.width, label.height)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private collisionKey(a: NetLabelPlacement, b: NetLabelPlacement) {
|
|
147
|
+
return [a.globalConnNetId, b.globalConnNetId].sort().join("::")
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private findNextCollidingPair():
|
|
151
|
+
| [NetLabelPlacement, NetLabelPlacement]
|
|
152
|
+
| null {
|
|
153
|
+
const labels = this.outputNetLabelPlacements
|
|
154
|
+
for (let i = 0; i < labels.length; i++) {
|
|
155
|
+
for (let j = i + 1; j < labels.length; j++) {
|
|
156
|
+
const a = labels[i]!
|
|
157
|
+
const b = labels[j]!
|
|
158
|
+
if (a.globalConnNetId === b.globalConnNetId) continue
|
|
159
|
+
if (this.skippedCollisionKeys.has(this.collisionKey(a, b))) continue
|
|
160
|
+
if (boundsOverlap(this.labelBounds(a), this.labelBounds(b)))
|
|
161
|
+
return [a, b]
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private netLabelWidthOf(label: NetLabelPlacement): number | undefined {
|
|
168
|
+
if (label.orientation === "x+" || label.orientation === "x-")
|
|
169
|
+
return label.width
|
|
170
|
+
return label.height
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private netLabelHeightOf(label: NetLabelPlacement): number | undefined {
|
|
174
|
+
if (label.orientation === "x+" || label.orientation === "x-")
|
|
175
|
+
return label.height
|
|
176
|
+
return label.width
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private buildCandidatesForLabel(label: NetLabelPlacement): Candidate[] {
|
|
180
|
+
const netLabelWidth = this.netLabelWidthOf(label)
|
|
181
|
+
const netLabelHeight = this.netLabelHeightOf(label)
|
|
182
|
+
const candidates: Candidate[] = []
|
|
183
|
+
|
|
184
|
+
const buildCandidate = (
|
|
185
|
+
orientation: FacingDirection,
|
|
186
|
+
anchor: { x: number; y: number },
|
|
187
|
+
hostPairId?: MspConnectionPairId,
|
|
188
|
+
hostSegIndex?: number,
|
|
189
|
+
): Candidate => {
|
|
190
|
+
const { width, height } = getDimsForOrientation({
|
|
191
|
+
orientation,
|
|
192
|
+
netLabelWidth,
|
|
193
|
+
netLabelHeight,
|
|
194
|
+
})
|
|
195
|
+
const baseCenter = getCenterFromAnchor(anchor, orientation, width, height)
|
|
196
|
+
const outwardDir = OUTWARD_DIR[orientation]
|
|
197
|
+
const center = {
|
|
198
|
+
x: baseCenter.x + outwardDir.x * ANCHOR_TRACE_CLEARANCE,
|
|
199
|
+
y: baseCenter.y + outwardDir.y * ANCHOR_TRACE_CLEARANCE,
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
orientation,
|
|
203
|
+
anchor,
|
|
204
|
+
center,
|
|
205
|
+
width,
|
|
206
|
+
height,
|
|
207
|
+
bounds: getRectBounds(center, width, height),
|
|
208
|
+
hostPairId,
|
|
209
|
+
hostSegIndex,
|
|
210
|
+
status: null,
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const isPortOnly = label.mspConnectionPairIds.length === 0
|
|
215
|
+
|
|
216
|
+
if (isPortOnly) {
|
|
217
|
+
const allOrientations: FacingDirection[] = ["x+", "x-", "y+", "y-"]
|
|
218
|
+
const orderedOrientations = [
|
|
219
|
+
label.orientation,
|
|
220
|
+
...allOrientations.filter((o) => o !== label.orientation),
|
|
221
|
+
]
|
|
222
|
+
for (const orientation of orderedOrientations) {
|
|
223
|
+
candidates.push(buildCandidate(orientation, label.anchorPoint))
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
for (const mspPairId of label.mspConnectionPairIds) {
|
|
227
|
+
const trace = this.traceMap[mspPairId]
|
|
228
|
+
if (!trace) continue
|
|
229
|
+
const pts = trace.tracePath
|
|
230
|
+
for (let si = 0; si < pts.length - 1; si++) {
|
|
231
|
+
const segStart = pts[si]!
|
|
232
|
+
const segEnd = pts[si + 1]!
|
|
233
|
+
const isHorizontal =
|
|
234
|
+
Math.abs(segStart.y - segEnd.y) < SEGMENT_PARALLEL_EPS
|
|
235
|
+
const isVertical =
|
|
236
|
+
Math.abs(segStart.x - segEnd.x) < SEGMENT_PARALLEL_EPS
|
|
237
|
+
if (!isHorizontal && !isVertical) continue
|
|
238
|
+
let perpendicularOrientations: FacingDirection[]
|
|
239
|
+
if (isHorizontal) {
|
|
240
|
+
perpendicularOrientations = ["y+", "y-"]
|
|
241
|
+
} else {
|
|
242
|
+
perpendicularOrientations = ["x+", "x-"]
|
|
243
|
+
}
|
|
244
|
+
for (const anchor of sampleAnchorsAlongSegment(segStart, segEnd)) {
|
|
245
|
+
for (const orientation of perpendicularOrientations) {
|
|
246
|
+
candidates.push(
|
|
247
|
+
buildCandidate(
|
|
248
|
+
orientation,
|
|
249
|
+
anchor,
|
|
250
|
+
mspPairId as MspConnectionPairId,
|
|
251
|
+
si,
|
|
252
|
+
),
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return candidates
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private checkCandidate(
|
|
264
|
+
candidate: Candidate,
|
|
265
|
+
movingLabelNetId: string,
|
|
266
|
+
obstacleLabels: NetLabelPlacement[],
|
|
267
|
+
): CandidateStatus {
|
|
268
|
+
const { bounds, hostPairId, hostSegIndex } = candidate
|
|
269
|
+
|
|
270
|
+
if (this.chipIndex.getChipsInBounds(bounds).length > 0)
|
|
271
|
+
return "chip-collision"
|
|
272
|
+
|
|
273
|
+
if (
|
|
274
|
+
rectIntersectsAnyTrace(bounds, this.traceMap, hostPairId, hostSegIndex)
|
|
275
|
+
.hasIntersection
|
|
276
|
+
) {
|
|
277
|
+
return "trace-collision"
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
for (const obstacle of obstacleLabels) {
|
|
281
|
+
if (obstacle.globalConnNetId === movingLabelNetId) continue
|
|
282
|
+
if (boundsOverlap(bounds, this.labelBounds(obstacle)))
|
|
283
|
+
return "label-collision"
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return "ok"
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private beginSearchForLabel(label: NetLabelPlacement) {
|
|
290
|
+
this.currentLabelToMove = label
|
|
291
|
+
this.candidateQueue = this.buildCandidatesForLabel(label)
|
|
292
|
+
this.candidateIndex = 0
|
|
293
|
+
this.candidateResults = []
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private clearActiveSearch() {
|
|
297
|
+
this.currentCollision = null
|
|
298
|
+
this.currentLabelToMove = null
|
|
299
|
+
this.labelsToTry = []
|
|
300
|
+
this.candidateQueue = []
|
|
301
|
+
this.candidateIndex = 0
|
|
302
|
+
this.candidateResults = []
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
override _step() {
|
|
306
|
+
if (!this.currentCollision) {
|
|
307
|
+
const pair = this.findNextCollidingPair()
|
|
308
|
+
if (!pair) {
|
|
309
|
+
this.solved = true
|
|
310
|
+
return
|
|
311
|
+
}
|
|
312
|
+
this.currentCollision = pair
|
|
313
|
+
this.labelsToTry = [pair[1], pair[0]]
|
|
314
|
+
this.beginSearchForLabel(this.labelsToTry.shift()!)
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (this.candidateIndex >= this.candidateQueue.length) {
|
|
319
|
+
if (this.labelsToTry.length > 0) {
|
|
320
|
+
this.beginSearchForLabel(this.labelsToTry.shift()!)
|
|
321
|
+
} else {
|
|
322
|
+
this.skippedCollisionKeys.add(
|
|
323
|
+
this.collisionKey(this.currentCollision[0], this.currentCollision[1]),
|
|
324
|
+
)
|
|
325
|
+
this.clearActiveSearch()
|
|
326
|
+
}
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const candidate = this.candidateQueue[this.candidateIndex++]!
|
|
331
|
+
const [labelA, labelB] = this.currentCollision
|
|
332
|
+
let fixedLabel: NetLabelPlacement
|
|
333
|
+
if (this.currentLabelToMove === labelB) {
|
|
334
|
+
fixedLabel = labelA
|
|
335
|
+
} else {
|
|
336
|
+
fixedLabel = labelB
|
|
337
|
+
}
|
|
338
|
+
const obstacleLabels = [
|
|
339
|
+
...this.outputNetLabelPlacements.filter(
|
|
340
|
+
(l) => l !== labelA && l !== labelB,
|
|
341
|
+
),
|
|
342
|
+
fixedLabel,
|
|
343
|
+
]
|
|
344
|
+
|
|
345
|
+
const status = this.checkCandidate(
|
|
346
|
+
candidate,
|
|
347
|
+
this.currentLabelToMove!.globalConnNetId,
|
|
348
|
+
obstacleLabels,
|
|
349
|
+
)
|
|
350
|
+
candidate.status = status
|
|
351
|
+
this.candidateResults.push({ ...candidate })
|
|
352
|
+
|
|
353
|
+
if (status === "ok") {
|
|
354
|
+
const idx = this.outputNetLabelPlacements.indexOf(
|
|
355
|
+
this.currentLabelToMove!,
|
|
356
|
+
)
|
|
357
|
+
if (idx !== -1) {
|
|
358
|
+
this.outputNetLabelPlacements[idx] = {
|
|
359
|
+
...this.currentLabelToMove!,
|
|
360
|
+
orientation: candidate.orientation,
|
|
361
|
+
anchorPoint: candidate.anchor,
|
|
362
|
+
width: candidate.width,
|
|
363
|
+
height: candidate.height,
|
|
364
|
+
center: candidate.center,
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
this.clearActiveSearch()
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
override visualize(): GraphicsObject {
|
|
372
|
+
const graphics = visualizeInputProblem(this.inputProblem)
|
|
373
|
+
if (!graphics.lines) graphics.lines = []
|
|
374
|
+
if (!graphics.rects) graphics.rects = []
|
|
375
|
+
if (!graphics.points) graphics.points = []
|
|
376
|
+
|
|
377
|
+
for (const trace of this.traces) {
|
|
378
|
+
graphics.lines.push({
|
|
379
|
+
points: trace.tracePath,
|
|
380
|
+
strokeColor: "purple",
|
|
381
|
+
} as any)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
for (const label of this.outputNetLabelPlacements) {
|
|
385
|
+
const isInActiveCollision =
|
|
386
|
+
this.currentCollision != null &&
|
|
387
|
+
(label === this.currentCollision[0] ||
|
|
388
|
+
label === this.currentCollision[1])
|
|
389
|
+
|
|
390
|
+
let labelFill: string
|
|
391
|
+
let labelStroke: string
|
|
392
|
+
let labelText: string
|
|
393
|
+
let pointColor: string
|
|
394
|
+
if (isInActiveCollision) {
|
|
395
|
+
labelFill = "rgba(255, 0, 0, 0.2)"
|
|
396
|
+
labelStroke = "red"
|
|
397
|
+
labelText = `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}\n⚠ COLLIDING`
|
|
398
|
+
pointColor = "red"
|
|
399
|
+
} else {
|
|
400
|
+
labelFill = getColorFromString(label.globalConnNetId, 0.35)
|
|
401
|
+
labelStroke = getColorFromString(label.globalConnNetId, 0.9)
|
|
402
|
+
labelText = `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`
|
|
403
|
+
pointColor = getColorFromString(label.globalConnNetId, 0.9)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
graphics.rects.push({
|
|
407
|
+
center: label.center,
|
|
408
|
+
width: label.width,
|
|
409
|
+
height: label.height,
|
|
410
|
+
fill: labelFill,
|
|
411
|
+
strokeColor: labelStroke,
|
|
412
|
+
label: labelText,
|
|
413
|
+
} as any)
|
|
414
|
+
graphics.points.push({
|
|
415
|
+
x: label.anchorPoint.x,
|
|
416
|
+
y: label.anchorPoint.y,
|
|
417
|
+
color: pointColor,
|
|
418
|
+
label: `anchorPoint\norientation: ${label.orientation}`,
|
|
419
|
+
} as any)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const movingNetId = this.currentLabelToMove
|
|
423
|
+
? this.currentLabelToMove.netId
|
|
424
|
+
: "?"
|
|
425
|
+
for (const c of this.candidateResults) {
|
|
426
|
+
const statusColor = CANDIDATE_STATUS_COLOR[c.status!]
|
|
427
|
+
const statusFill = CANDIDATE_STATUS_FILL[c.status!]
|
|
428
|
+
let strokeDash: string | undefined
|
|
429
|
+
if (c.status !== "ok") strokeDash = "4 2"
|
|
430
|
+
graphics.rects.push({
|
|
431
|
+
center: c.center,
|
|
432
|
+
width: c.width,
|
|
433
|
+
height: c.height,
|
|
434
|
+
fill: statusFill,
|
|
435
|
+
strokeColor: statusColor,
|
|
436
|
+
strokeDash,
|
|
437
|
+
label: `candidate: ${c.status}\norientation: ${c.orientation}\nmoving: ${movingNetId}`,
|
|
438
|
+
} as any)
|
|
439
|
+
graphics.points.push({
|
|
440
|
+
x: c.anchor.x,
|
|
441
|
+
y: c.anchor.y,
|
|
442
|
+
color: statusColor,
|
|
443
|
+
label: `candidate anchor\n${c.status}`,
|
|
444
|
+
} as any)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return graphics
|
|
448
|
+
}
|
|
449
|
+
}
|
|
@@ -280,6 +280,26 @@ export class NetLabelPlacementSolver extends BaseSolver {
|
|
|
280
280
|
)?.netLabelWidth
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
private getNetLabelHeightForGroup(
|
|
284
|
+
group: OverlappingSameNetTraceGroup,
|
|
285
|
+
): number | undefined {
|
|
286
|
+
if (group.netId) {
|
|
287
|
+
const ncHeight = this.inputProblem.netConnections.find(
|
|
288
|
+
(nc) => nc.netId === group.netId,
|
|
289
|
+
)?.netLabelHeight
|
|
290
|
+
if (ncHeight !== undefined) return ncHeight
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const pinIds = group.overlappingTraces?.pins.map((p) => p.pinId) ?? []
|
|
294
|
+
if (group.portOnlyPinId) {
|
|
295
|
+
pinIds.push(group.portOnlyPinId)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return this.inputProblem.netConnections.find((nc) =>
|
|
299
|
+
nc.pinIds.some((pid) => pinIds.includes(pid)),
|
|
300
|
+
)?.netLabelHeight
|
|
301
|
+
}
|
|
302
|
+
|
|
283
303
|
override _step() {
|
|
284
304
|
if (this.activeSubSolver?.solved) {
|
|
285
305
|
this.netLabelPlacements.push(this.activeSubSolver.netLabelPlacement!)
|
|
@@ -304,12 +324,14 @@ export class NetLabelPlacementSolver extends BaseSolver {
|
|
|
304
324
|
) {
|
|
305
325
|
this.triedAnyOrientationFallbackForCurrentGroup = true
|
|
306
326
|
const netLabelWidth = this.getNetLabelWidthForGroup(this.currentGroup)
|
|
327
|
+
const netLabelHeight = this.getNetLabelHeightForGroup(this.currentGroup)
|
|
307
328
|
this.activeSubSolver = new SingleNetLabelPlacementSolver({
|
|
308
329
|
inputProblem: this.inputProblem,
|
|
309
330
|
inputTraceMap: this.inputTraceMap,
|
|
310
331
|
overlappingSameNetTraceGroup: this.currentGroup,
|
|
311
332
|
availableOrientations: fullOrients,
|
|
312
333
|
netLabelWidth,
|
|
334
|
+
netLabelHeight,
|
|
313
335
|
})
|
|
314
336
|
return
|
|
315
337
|
}
|
|
@@ -340,6 +362,7 @@ export class NetLabelPlacementSolver extends BaseSolver {
|
|
|
340
362
|
this.triedAnyOrientationFallbackForCurrentGroup = false
|
|
341
363
|
|
|
342
364
|
const netLabelWidth = this.getNetLabelWidthForGroup(this.currentGroup)
|
|
365
|
+
const netLabelHeight = this.getNetLabelHeightForGroup(this.currentGroup)
|
|
343
366
|
|
|
344
367
|
this.activeSubSolver = new SingleNetLabelPlacementSolver({
|
|
345
368
|
inputProblem: this.inputProblem,
|
|
@@ -349,6 +372,7 @@ export class NetLabelPlacementSolver extends BaseSolver {
|
|
|
349
372
|
netId
|
|
350
373
|
] ?? ["x+", "x-", "y+", "y-"],
|
|
351
374
|
netLabelWidth,
|
|
375
|
+
netLabelHeight,
|
|
352
376
|
})
|
|
353
377
|
}
|
|
354
378
|
|
|
@@ -58,8 +58,9 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
58
58
|
|
|
59
59
|
chipObstacleSpatialIndex: ChipObstacleSpatialIndex
|
|
60
60
|
|
|
61
|
-
// Optional override for the width of the net label (per netId)
|
|
61
|
+
// Optional override for the width/height of the net label (per netId)
|
|
62
62
|
netLabelWidth?: number
|
|
63
|
+
netLabelHeight?: number
|
|
63
64
|
|
|
64
65
|
netLabelPlacement: NetLabelPlacement | null = null
|
|
65
66
|
testedCandidates: Array<{
|
|
@@ -79,6 +80,7 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
79
80
|
overlappingSameNetTraceGroup: OverlappingSameNetTraceGroup
|
|
80
81
|
availableOrientations: FacingDirection[]
|
|
81
82
|
netLabelWidth?: number
|
|
83
|
+
netLabelHeight?: number
|
|
82
84
|
}) {
|
|
83
85
|
super()
|
|
84
86
|
this.inputProblem = params.inputProblem
|
|
@@ -86,6 +88,7 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
86
88
|
this.overlappingSameNetTraceGroup = params.overlappingSameNetTraceGroup
|
|
87
89
|
this.availableOrientations = params.availableOrientations
|
|
88
90
|
this.netLabelWidth = params.netLabelWidth
|
|
91
|
+
this.netLabelHeight = params.netLabelHeight
|
|
89
92
|
|
|
90
93
|
this.chipObstacleSpatialIndex =
|
|
91
94
|
params.inputProblem._chipObstacleSpatialIndex ??
|
|
@@ -101,6 +104,7 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
101
104
|
overlappingSameNetTraceGroup: this.overlappingSameNetTraceGroup,
|
|
102
105
|
availableOrientations: this.availableOrientations,
|
|
103
106
|
netLabelWidth: this.netLabelWidth,
|
|
107
|
+
netLabelHeight: this.netLabelHeight,
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
|
|
@@ -119,6 +123,7 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
119
123
|
overlappingSameNetTraceGroup: this.overlappingSameNetTraceGroup,
|
|
120
124
|
availableOrientations: this.availableOrientations,
|
|
121
125
|
netLabelWidth: this.netLabelWidth,
|
|
126
|
+
netLabelHeight: this.netLabelHeight,
|
|
122
127
|
})
|
|
123
128
|
this.testedCandidates.push(...res.testedCandidates)
|
|
124
129
|
if (res.placement) {
|
|
@@ -230,6 +235,7 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
230
235
|
const { width, height } = getDimsForOrientation({
|
|
231
236
|
orientation,
|
|
232
237
|
netLabelWidth: this.netLabelWidth,
|
|
238
|
+
netLabelHeight: this.netLabelHeight,
|
|
233
239
|
})
|
|
234
240
|
const center = getCenterFromAnchor(
|
|
235
241
|
anchor,
|
|
@@ -6,23 +6,28 @@ export const NET_LABEL_HORIZONTAL_HEIGHT = 0.2
|
|
|
6
6
|
export function getDimsForOrientation(params: {
|
|
7
7
|
orientation: FacingDirection
|
|
8
8
|
netLabelWidth?: number
|
|
9
|
+
netLabelHeight?: number
|
|
9
10
|
}) {
|
|
10
|
-
const { orientation, netLabelWidth } = params
|
|
11
|
+
const { orientation, netLabelWidth, netLabelHeight } = params
|
|
11
12
|
const horizWidth =
|
|
12
13
|
typeof netLabelWidth === "number"
|
|
13
14
|
? netLabelWidth
|
|
14
15
|
: NET_LABEL_HORIZONTAL_WIDTH
|
|
16
|
+
const horizHeight =
|
|
17
|
+
typeof netLabelHeight === "number"
|
|
18
|
+
? netLabelHeight
|
|
19
|
+
: NET_LABEL_HORIZONTAL_HEIGHT
|
|
15
20
|
|
|
16
21
|
if (orientation === "y+" || orientation === "y-") {
|
|
17
22
|
return {
|
|
18
|
-
// Rotated,
|
|
19
|
-
width:
|
|
23
|
+
// Rotated: horizontal length = netLabelHeight, vertical length = netLabelWidth
|
|
24
|
+
width: horizHeight,
|
|
20
25
|
height: horizWidth,
|
|
21
26
|
}
|
|
22
27
|
}
|
|
23
28
|
return {
|
|
24
29
|
width: horizWidth,
|
|
25
|
-
height:
|
|
30
|
+
height: horizHeight,
|
|
26
31
|
}
|
|
27
32
|
}
|
|
28
33
|
|
package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/solvePortOnlyPin.ts
CHANGED
|
@@ -22,6 +22,7 @@ export function solveNetLabelPlacementForPortOnlyPin(params: {
|
|
|
22
22
|
overlappingSameNetTraceGroup: OverlappingSameNetTraceGroup
|
|
23
23
|
availableOrientations: FacingDirection[]
|
|
24
24
|
netLabelWidth?: number
|
|
25
|
+
netLabelHeight?: number
|
|
25
26
|
}): {
|
|
26
27
|
placement: NetLabelPlacement | null
|
|
27
28
|
testedCandidates: Array<{
|
|
@@ -43,6 +44,7 @@ export function solveNetLabelPlacementForPortOnlyPin(params: {
|
|
|
43
44
|
overlappingSameNetTraceGroup,
|
|
44
45
|
availableOrientations,
|
|
45
46
|
netLabelWidth,
|
|
47
|
+
netLabelHeight,
|
|
46
48
|
} = params
|
|
47
49
|
|
|
48
50
|
const pinId = overlappingSameNetTraceGroup.portOnlyPinId
|
|
@@ -103,6 +105,7 @@ export function solveNetLabelPlacementForPortOnlyPin(params: {
|
|
|
103
105
|
const { width, height } = getDimsForOrientation({
|
|
104
106
|
orientation,
|
|
105
107
|
netLabelWidth,
|
|
108
|
+
netLabelHeight,
|
|
106
109
|
})
|
|
107
110
|
// Place label fully outside the chip: shift center slightly outward
|
|
108
111
|
const baseCenter = getCenterFromAnchor(anchor, orientation, width, height)
|
|
@@ -26,6 +26,7 @@ import { AvailableNetOrientationSolver } from "../AvailableNetOrientationSolver/
|
|
|
26
26
|
import { VccNetLabelCornerPlacementSolver } from "../VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver"
|
|
27
27
|
import { TraceAnchoredNetLabelOverlapSolver } from "../TraceAnchoredNetLabelOverlapSolver/TraceAnchoredNetLabelOverlapSolver"
|
|
28
28
|
import { NetLabelTraceCollisionSolver } from "../NetLabelTraceCollisionSolver/NetLabelTraceCollisionSolver"
|
|
29
|
+
import { NetLabelNetLabelCollisionSolver } from "../NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver"
|
|
29
30
|
|
|
30
31
|
type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
|
|
31
32
|
solverName: string
|
|
@@ -80,6 +81,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
80
81
|
vccNetLabelCornerPlacementSolver?: VccNetLabelCornerPlacementSolver
|
|
81
82
|
traceAnchoredNetLabelOverlapSolver?: TraceAnchoredNetLabelOverlapSolver
|
|
82
83
|
netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver
|
|
84
|
+
netLabelNetLabelCollisionSolver?: NetLabelNetLabelCollisionSolver
|
|
83
85
|
|
|
84
86
|
startTimeOfPhase: Record<string, number>
|
|
85
87
|
endTimeOfPhase: Record<string, number>
|
|
@@ -300,6 +302,19 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
300
302
|
},
|
|
301
303
|
],
|
|
302
304
|
),
|
|
305
|
+
definePipelineStep(
|
|
306
|
+
"netLabelNetLabelCollisionSolver",
|
|
307
|
+
NetLabelNetLabelCollisionSolver,
|
|
308
|
+
(instance) => [
|
|
309
|
+
{
|
|
310
|
+
inputProblem: instance.inputProblem,
|
|
311
|
+
traces: instance.netLabelTraceCollisionSolver!.getOutput().traces,
|
|
312
|
+
netLabelPlacements:
|
|
313
|
+
instance.netLabelTraceCollisionSolver!.getOutput()
|
|
314
|
+
.netLabelPlacements,
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
),
|
|
303
318
|
]
|
|
304
319
|
|
|
305
320
|
constructor(inputProblem: InputProblem) {
|