@tscircuit/schematic-trace-solver 0.0.6 → 0.0.8
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 +0 -5
- package/dist/index.js +451 -338
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver.ts +199 -465
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver_visualize.ts +89 -0
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/anchors.ts +11 -0
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions.ts +45 -0
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry.ts +48 -0
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/host.ts +64 -0
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/solvePortOnlyPin.ts +177 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +4 -0
- package/lib/solvers/SchematicTracePipelineSolver/expandChipsToFitPins.ts +41 -0
- package/package.json +1 -1
- package/site/examples/example01-basic.page.tsx +2 -2
|
@@ -3,17 +3,27 @@ import type {
|
|
|
3
3
|
NetLabelPlacement,
|
|
4
4
|
OverlappingSameNetTraceGroup,
|
|
5
5
|
} from "../NetLabelPlacementSolver"
|
|
6
|
-
import type {
|
|
6
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
7
7
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
8
8
|
import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
9
9
|
import type { FacingDirection } from "lib/utils/dir"
|
|
10
10
|
import type { GraphicsObject } from "graphics-debug"
|
|
11
11
|
import { ChipObstacleSpatialIndex } from "lib/data-structures/ChipObstacleSpatialIndex"
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
import {
|
|
13
|
+
getDimsForOrientation,
|
|
14
|
+
getCenterFromAnchor,
|
|
15
|
+
getRectBounds,
|
|
16
|
+
} from "./geometry"
|
|
17
|
+
import { rectIntersectsAnyTrace } from "./collisions"
|
|
18
|
+
import { chooseHostTraceForGroup } from "./host"
|
|
19
|
+
import { anchorsForSegment } from "./anchors"
|
|
20
|
+
import { solveNetLabelPlacementForPortOnlyPin } from "./solvePortOnlyPin"
|
|
21
|
+
import { visualizeSingleNetLabelPlacementSolver } from "./SingleNetLabelPlacementSolver_visualize"
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
NET_LABEL_HORIZONTAL_WIDTH,
|
|
25
|
+
NET_LABEL_HORIZONTAL_HEIGHT,
|
|
26
|
+
} from "./geometry"
|
|
17
27
|
// NOTE: net labels, when in the y+/y- orientation, are rotated and therefore
|
|
18
28
|
// the width/height are swapped
|
|
19
29
|
|
|
@@ -77,95 +87,6 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
77
87
|
new ChipObstacleSpatialIndex(params.inputProblem.chips)
|
|
78
88
|
}
|
|
79
89
|
|
|
80
|
-
private getDimsForOrientation(orientation: FacingDirection) {
|
|
81
|
-
if (orientation === "y+" || orientation === "y-") {
|
|
82
|
-
return {
|
|
83
|
-
width: NET_LABEL_HORIZONTAL_HEIGHT,
|
|
84
|
-
height: NET_LABEL_HORIZONTAL_WIDTH,
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return {
|
|
88
|
-
width: NET_LABEL_HORIZONTAL_WIDTH,
|
|
89
|
-
height: NET_LABEL_HORIZONTAL_HEIGHT,
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
private getCenterFromAnchor(
|
|
94
|
-
anchor: { x: number; y: number },
|
|
95
|
-
orientation: FacingDirection,
|
|
96
|
-
width: number,
|
|
97
|
-
height: number,
|
|
98
|
-
) {
|
|
99
|
-
switch (orientation) {
|
|
100
|
-
case "x+":
|
|
101
|
-
return { x: anchor.x + width / 2, y: anchor.y }
|
|
102
|
-
case "x-":
|
|
103
|
-
return { x: anchor.x - width / 2, y: anchor.y }
|
|
104
|
-
case "y+":
|
|
105
|
-
return { x: anchor.x, y: anchor.y + height / 2 }
|
|
106
|
-
case "y-":
|
|
107
|
-
return { x: anchor.x, y: anchor.y - height / 2 }
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
private getRectBounds(
|
|
112
|
-
center: { x: number; y: number },
|
|
113
|
-
w: number,
|
|
114
|
-
h: number,
|
|
115
|
-
) {
|
|
116
|
-
return {
|
|
117
|
-
minX: center.x - w / 2,
|
|
118
|
-
minY: center.y - h / 2,
|
|
119
|
-
maxX: center.x + w / 2,
|
|
120
|
-
maxY: center.y + h / 2,
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
private segmentIntersectsRect(
|
|
125
|
-
p1: { x: number; y: number },
|
|
126
|
-
p2: { x: number; y: number },
|
|
127
|
-
rect: { minX: number; minY: number; maxX: number; maxY: number },
|
|
128
|
-
EPS = 1e-9,
|
|
129
|
-
): boolean {
|
|
130
|
-
const isVert = Math.abs(p1.x - p2.x) < EPS
|
|
131
|
-
const isHorz = Math.abs(p1.y - p2.y) < EPS
|
|
132
|
-
if (!isVert && !isHorz) return false
|
|
133
|
-
|
|
134
|
-
if (isVert) {
|
|
135
|
-
const x = p1.x
|
|
136
|
-
if (x < rect.minX - EPS || x > rect.maxX + EPS) return false
|
|
137
|
-
const segMinY = Math.min(p1.y, p2.y)
|
|
138
|
-
const segMaxY = Math.max(p1.y, p2.y)
|
|
139
|
-
const overlap =
|
|
140
|
-
Math.min(segMaxY, rect.maxY) - Math.max(segMinY, rect.minY)
|
|
141
|
-
return overlap > EPS
|
|
142
|
-
} else {
|
|
143
|
-
const y = p1.y
|
|
144
|
-
if (y < rect.minY - EPS || y > rect.maxY + EPS) return false
|
|
145
|
-
const segMinX = Math.min(p1.x, p2.x)
|
|
146
|
-
const segMaxX = Math.max(p1.x, p2.x)
|
|
147
|
-
const overlap =
|
|
148
|
-
Math.min(segMaxX, rect.maxX) - Math.max(segMinX, rect.minX)
|
|
149
|
-
return overlap > EPS
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
private rectIntersectsAnyTrace(
|
|
154
|
-
bounds: { minX: number; minY: number; maxX: number; maxY: number },
|
|
155
|
-
hostPathId: MspConnectionPairId,
|
|
156
|
-
hostSegIndex: number,
|
|
157
|
-
): boolean {
|
|
158
|
-
for (const [pairId, solved] of Object.entries(this.inputTraceMap)) {
|
|
159
|
-
const pts = solved.tracePath
|
|
160
|
-
for (let i = 0; i < pts.length - 1; i++) {
|
|
161
|
-
if (pairId === hostPathId && i === hostSegIndex) continue
|
|
162
|
-
if (this.segmentIntersectsRect(pts[i]!, pts[i + 1]!, bounds))
|
|
163
|
-
return true
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return false
|
|
167
|
-
}
|
|
168
|
-
|
|
169
90
|
override _step() {
|
|
170
91
|
if (this.netLabelPlacement) {
|
|
171
92
|
this.solved = true
|
|
@@ -174,162 +95,33 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
174
95
|
|
|
175
96
|
// Handle port-only island (no traces) by placing a label at the port
|
|
176
97
|
if (this.overlappingSameNetTraceGroup.portOnlyPinId) {
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if (!pin) {
|
|
188
|
-
this.failed = true
|
|
189
|
-
this.error = `Port-only pin not found: ${pinId}`
|
|
190
|
-
return
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const orientations =
|
|
194
|
-
this.availableOrientations.length > 0
|
|
195
|
-
? this.availableOrientations
|
|
196
|
-
: (["x+", "x-", "y+", "y-"] as FacingDirection[])
|
|
197
|
-
|
|
198
|
-
const anchor = { x: pin.x, y: pin.y }
|
|
199
|
-
const outwardOf = (o: FacingDirection) =>
|
|
200
|
-
o === "x+"
|
|
201
|
-
? { x: 1, y: 0 }
|
|
202
|
-
: o === "x-"
|
|
203
|
-
? { x: -1, y: 0 }
|
|
204
|
-
: o === "y+"
|
|
205
|
-
? { x: 0, y: 1 }
|
|
206
|
-
: { x: 0, y: -1 }
|
|
207
|
-
|
|
208
|
-
for (const orientation of orientations) {
|
|
209
|
-
const { width, height } = this.getDimsForOrientation(orientation)
|
|
210
|
-
// Place label fully outside the chip: shift center slightly outward
|
|
211
|
-
const baseCenter = this.getCenterFromAnchor(
|
|
212
|
-
anchor,
|
|
213
|
-
orientation,
|
|
214
|
-
width,
|
|
215
|
-
height,
|
|
216
|
-
)
|
|
217
|
-
const outward = outwardOf(orientation)
|
|
218
|
-
const offset = 1e-3
|
|
219
|
-
const center = {
|
|
220
|
-
x: baseCenter.x + outward.x * offset,
|
|
221
|
-
y: baseCenter.y + outward.y * offset,
|
|
222
|
-
}
|
|
223
|
-
const bounds = this.getRectBounds(center, width, height)
|
|
224
|
-
|
|
225
|
-
// Chip collision check
|
|
226
|
-
const chips = this.chipObstacleSpatialIndex.getChipsInBounds(bounds)
|
|
227
|
-
if (chips.length > 0) {
|
|
228
|
-
this.testedCandidates.push({
|
|
229
|
-
center,
|
|
230
|
-
width,
|
|
231
|
-
height,
|
|
232
|
-
bounds,
|
|
233
|
-
anchor,
|
|
234
|
-
orientation,
|
|
235
|
-
status: "chip-collision",
|
|
236
|
-
hostSegIndex: -1,
|
|
237
|
-
})
|
|
238
|
-
continue
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Trace collision check
|
|
242
|
-
if (
|
|
243
|
-
this.rectIntersectsAnyTrace(bounds, "" as MspConnectionPairId, -1)
|
|
244
|
-
) {
|
|
245
|
-
this.testedCandidates.push({
|
|
246
|
-
center,
|
|
247
|
-
width,
|
|
248
|
-
height,
|
|
249
|
-
bounds,
|
|
250
|
-
anchor,
|
|
251
|
-
orientation,
|
|
252
|
-
status: "trace-collision",
|
|
253
|
-
hostSegIndex: -1,
|
|
254
|
-
})
|
|
255
|
-
continue
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Found a valid placement
|
|
259
|
-
this.testedCandidates.push({
|
|
260
|
-
center,
|
|
261
|
-
width,
|
|
262
|
-
height,
|
|
263
|
-
bounds,
|
|
264
|
-
anchor,
|
|
265
|
-
orientation,
|
|
266
|
-
status: "ok",
|
|
267
|
-
hostSegIndex: -1,
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
this.netLabelPlacement = {
|
|
271
|
-
globalConnNetId: this.overlappingSameNetTraceGroup.globalConnNetId,
|
|
272
|
-
dcConnNetId: undefined,
|
|
273
|
-
orientation,
|
|
274
|
-
anchorPoint: anchor,
|
|
275
|
-
width,
|
|
276
|
-
height,
|
|
277
|
-
center,
|
|
278
|
-
}
|
|
98
|
+
const res = solveNetLabelPlacementForPortOnlyPin({
|
|
99
|
+
inputProblem: this.inputProblem,
|
|
100
|
+
inputTraceMap: this.inputTraceMap,
|
|
101
|
+
chipObstacleSpatialIndex: this.chipObstacleSpatialIndex,
|
|
102
|
+
overlappingSameNetTraceGroup: this.overlappingSameNetTraceGroup,
|
|
103
|
+
availableOrientations: this.availableOrientations,
|
|
104
|
+
})
|
|
105
|
+
this.testedCandidates.push(...res.testedCandidates)
|
|
106
|
+
if (res.placement) {
|
|
107
|
+
this.netLabelPlacement = res.placement
|
|
279
108
|
this.solved = true
|
|
280
109
|
return
|
|
281
110
|
}
|
|
282
|
-
|
|
283
111
|
this.failed = true
|
|
284
|
-
this.error =
|
|
112
|
+
this.error =
|
|
113
|
+
res.error ?? "Could not place net label at port without collisions"
|
|
285
114
|
return
|
|
286
115
|
}
|
|
287
116
|
|
|
288
117
|
// Prefer starting from the trace connected to the "largest" chip (most pins)
|
|
289
118
|
const groupId = this.overlappingSameNetTraceGroup.globalConnNetId
|
|
290
|
-
|
|
291
|
-
this.inputProblem
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
)
|
|
296
|
-
const chipIdsInGroup = new Set<string>()
|
|
297
|
-
for (const t of groupTraces) {
|
|
298
|
-
chipIdsInGroup.add(t.pins[0].chipId)
|
|
299
|
-
chipIdsInGroup.add(t.pins[1].chipId)
|
|
300
|
-
}
|
|
301
|
-
let largestChipId: string | null = null
|
|
302
|
-
let largestPinCount = -1
|
|
303
|
-
for (const id of chipIdsInGroup) {
|
|
304
|
-
const chip = chipsById[id]
|
|
305
|
-
const count = chip?.pins?.length ?? 0
|
|
306
|
-
if (count > largestPinCount) {
|
|
307
|
-
largestPinCount = count
|
|
308
|
-
largestChipId = id
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
const lengthOf = (path: SolvedTracePath) => {
|
|
312
|
-
let sum = 0
|
|
313
|
-
const pts = path.tracePath
|
|
314
|
-
for (let i = 0; i < pts.length - 1; i++) {
|
|
315
|
-
sum +=
|
|
316
|
-
Math.abs(pts[i + 1]!.x - pts[i]!.x) +
|
|
317
|
-
Math.abs(pts[i + 1]!.y - pts[i]!.y)
|
|
318
|
-
}
|
|
319
|
-
return sum
|
|
320
|
-
}
|
|
321
|
-
const hostCandidates =
|
|
322
|
-
largestChipId == null
|
|
323
|
-
? []
|
|
324
|
-
: groupTraces.filter(
|
|
325
|
-
(t) =>
|
|
326
|
-
t.pins[0].chipId === largestChipId ||
|
|
327
|
-
t.pins[1].chipId === largestChipId,
|
|
328
|
-
)
|
|
329
|
-
let host =
|
|
330
|
-
hostCandidates.length > 0
|
|
331
|
-
? hostCandidates.reduce((a, b) => (lengthOf(a) >= lengthOf(b) ? a : b))
|
|
332
|
-
: this.overlappingSameNetTraceGroup.overlappingTraces
|
|
119
|
+
let host = chooseHostTraceForGroup({
|
|
120
|
+
inputProblem: this.inputProblem,
|
|
121
|
+
inputTraceMap: this.inputTraceMap,
|
|
122
|
+
globalConnNetId: groupId,
|
|
123
|
+
fallbackTrace: this.overlappingSameNetTraceGroup.overlappingTraces,
|
|
124
|
+
})
|
|
333
125
|
|
|
334
126
|
if (!host) {
|
|
335
127
|
this.failed = true
|
|
@@ -338,100 +130,138 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
338
130
|
}
|
|
339
131
|
|
|
340
132
|
// Ensure we traverse the host path starting at the segment attached to the largest chip's pin
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
pts = pts.slice().reverse()
|
|
352
|
-
}
|
|
353
|
-
}
|
|
133
|
+
const tracesToScanBase = Object.values(this.inputTraceMap).filter(
|
|
134
|
+
(t) => t.globalConnNetId === groupId,
|
|
135
|
+
)
|
|
136
|
+
const tracesToScan =
|
|
137
|
+
this.availableOrientations.length === 1
|
|
138
|
+
? [
|
|
139
|
+
host,
|
|
140
|
+
...tracesToScanBase.filter((t) => t.mspPairId !== host!.mspPairId),
|
|
141
|
+
]
|
|
142
|
+
: [host]
|
|
354
143
|
|
|
355
144
|
const orientations =
|
|
356
145
|
this.availableOrientations.length > 0
|
|
357
146
|
? this.availableOrientations
|
|
358
147
|
: (["x+", "x-", "y+", "y-"] as FacingDirection[])
|
|
359
148
|
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
149
|
+
const singleOrientationMode = this.availableOrientations.length === 1
|
|
150
|
+
|
|
151
|
+
// For axis-aligned comparisons (furthest-point selection)
|
|
152
|
+
const scoreFor = (
|
|
153
|
+
orientation: FacingDirection,
|
|
154
|
+
anchor: { x: number; y: number },
|
|
364
155
|
) => {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
156
|
+
switch (orientation) {
|
|
157
|
+
case "y+":
|
|
158
|
+
return anchor.y
|
|
159
|
+
case "y-":
|
|
160
|
+
return -anchor.y
|
|
161
|
+
case "x+":
|
|
162
|
+
return anchor.x
|
|
163
|
+
case "x-":
|
|
164
|
+
return -anchor.x
|
|
165
|
+
}
|
|
371
166
|
}
|
|
167
|
+
let bestCandidate: {
|
|
168
|
+
anchor: { x: number; y: number }
|
|
169
|
+
orientation: FacingDirection
|
|
170
|
+
width: number
|
|
171
|
+
height: number
|
|
172
|
+
center: { x: number; y: number }
|
|
173
|
+
hostSegIndex: number
|
|
174
|
+
dcConnNetId: string
|
|
175
|
+
} | null = null
|
|
176
|
+
let bestScore = -Infinity
|
|
372
177
|
|
|
373
|
-
|
|
374
|
-
const a = pts[si]!
|
|
375
|
-
const b = pts[si + 1]!
|
|
376
|
-
const isH = Math.abs(a.y - b.y) < EPS
|
|
377
|
-
const isV = Math.abs(a.x - b.x) < EPS
|
|
378
|
-
if (!isH && !isV) continue
|
|
379
|
-
|
|
380
|
-
// Only consider orientations perpendicular to the segment to avoid
|
|
381
|
-
// self-overlap with the host segment.
|
|
382
|
-
const segmentAllowed: FacingDirection[] = isH
|
|
383
|
-
? (["y+", "y-"] as FacingDirection[])
|
|
384
|
-
: (["x+", "x-"] as FacingDirection[])
|
|
385
|
-
const candidateOrients = orientations.filter((o) =>
|
|
386
|
-
segmentAllowed.includes(o),
|
|
387
|
-
)
|
|
388
|
-
if (candidateOrients.length === 0) continue
|
|
389
|
-
|
|
390
|
-
const anchors = anchorsForSegment(a, b)
|
|
391
|
-
for (const anchor of anchors) {
|
|
392
|
-
for (const orientation of candidateOrients) {
|
|
393
|
-
const { width, height } = this.getDimsForOrientation(orientation)
|
|
394
|
-
const center = this.getCenterFromAnchor(
|
|
395
|
-
anchor,
|
|
396
|
-
orientation,
|
|
397
|
-
width,
|
|
398
|
-
height,
|
|
399
|
-
)
|
|
178
|
+
const EPS = 1e-6
|
|
400
179
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
180
|
+
for (const curr of tracesToScan) {
|
|
181
|
+
const pts = curr.tracePath.slice()
|
|
182
|
+
for (let si = 0; si < pts.length - 1; si++) {
|
|
183
|
+
const a = pts[si]!
|
|
184
|
+
const b = pts[si + 1]!
|
|
185
|
+
const isH = Math.abs(a.y - b.y) < EPS
|
|
186
|
+
const isV = Math.abs(a.x - b.x) < EPS
|
|
187
|
+
if (!isH && !isV) continue
|
|
188
|
+
|
|
189
|
+
// Only consider orientations perpendicular to the segment to avoid
|
|
190
|
+
// self-overlap with the host segment.
|
|
191
|
+
const segmentAllowed: FacingDirection[] = isH
|
|
192
|
+
? (["y+", "y-"] as FacingDirection[])
|
|
193
|
+
: (["x+", "x-"] as FacingDirection[])
|
|
194
|
+
const candidateOrients = orientations.filter((o) =>
|
|
195
|
+
segmentAllowed.includes(o),
|
|
196
|
+
)
|
|
197
|
+
if (candidateOrients.length === 0) continue
|
|
416
198
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
width,
|
|
423
|
-
height,
|
|
424
|
-
bounds,
|
|
199
|
+
const anchors = anchorsForSegment(a, b)
|
|
200
|
+
for (const anchor of anchors) {
|
|
201
|
+
for (const orientation of candidateOrients) {
|
|
202
|
+
const { width, height } = getDimsForOrientation(orientation)
|
|
203
|
+
const center = getCenterFromAnchor(
|
|
425
204
|
anchor,
|
|
426
205
|
orientation,
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
206
|
+
width,
|
|
207
|
+
height,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
// Small outward offset to avoid counting the touching trace as a collision
|
|
211
|
+
const outward =
|
|
212
|
+
orientation === "x+"
|
|
213
|
+
? { x: 1, y: 0 }
|
|
214
|
+
: orientation === "x-"
|
|
215
|
+
? { x: -1, y: 0 }
|
|
216
|
+
: orientation === "y+"
|
|
217
|
+
? { x: 0, y: 1 }
|
|
218
|
+
: { x: 0, y: -1 }
|
|
219
|
+
const offset = 1e-4
|
|
220
|
+
const testCenter = {
|
|
221
|
+
x: center.x + outward.x * offset,
|
|
222
|
+
y: center.y + outward.y * offset,
|
|
223
|
+
}
|
|
224
|
+
const bounds = getRectBounds(testCenter, width, height)
|
|
225
|
+
|
|
226
|
+
// Chip collision check
|
|
227
|
+
const chips = this.chipObstacleSpatialIndex.getChipsInBounds(bounds)
|
|
228
|
+
if (chips.length > 0) {
|
|
229
|
+
this.testedCandidates.push({
|
|
230
|
+
center: testCenter,
|
|
231
|
+
width,
|
|
232
|
+
height,
|
|
233
|
+
bounds,
|
|
234
|
+
anchor,
|
|
235
|
+
orientation,
|
|
236
|
+
status: "chip-collision",
|
|
237
|
+
hostSegIndex: si,
|
|
238
|
+
})
|
|
239
|
+
continue
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Trace collision check (ignore the host segment)
|
|
243
|
+
if (
|
|
244
|
+
rectIntersectsAnyTrace(
|
|
245
|
+
bounds,
|
|
246
|
+
this.inputTraceMap,
|
|
247
|
+
curr.mspPairId,
|
|
248
|
+
si,
|
|
249
|
+
)
|
|
250
|
+
) {
|
|
251
|
+
this.testedCandidates.push({
|
|
252
|
+
center: testCenter,
|
|
253
|
+
width,
|
|
254
|
+
height,
|
|
255
|
+
bounds,
|
|
256
|
+
anchor,
|
|
257
|
+
orientation,
|
|
258
|
+
status: "trace-collision",
|
|
259
|
+
hostSegIndex: si,
|
|
260
|
+
})
|
|
261
|
+
continue
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Found a valid placement
|
|
435
265
|
this.testedCandidates.push({
|
|
436
266
|
center: testCenter,
|
|
437
267
|
width,
|
|
@@ -439,160 +269,64 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
439
269
|
bounds,
|
|
440
270
|
anchor,
|
|
441
271
|
orientation,
|
|
442
|
-
status: "
|
|
272
|
+
status: "ok",
|
|
443
273
|
hostSegIndex: si,
|
|
444
274
|
})
|
|
445
|
-
continue
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// Found a valid placement
|
|
449
|
-
this.testedCandidates.push({
|
|
450
|
-
center: testCenter,
|
|
451
|
-
width,
|
|
452
|
-
height,
|
|
453
|
-
bounds,
|
|
454
|
-
anchor,
|
|
455
|
-
orientation,
|
|
456
|
-
status: "ok",
|
|
457
|
-
hostSegIndex: si,
|
|
458
|
-
})
|
|
459
275
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
276
|
+
if (singleOrientationMode) {
|
|
277
|
+
const s = scoreFor(orientation, anchor)
|
|
278
|
+
if (s > bestScore + 1e-9) {
|
|
279
|
+
bestScore = s
|
|
280
|
+
bestCandidate = {
|
|
281
|
+
anchor,
|
|
282
|
+
orientation,
|
|
283
|
+
width,
|
|
284
|
+
height,
|
|
285
|
+
center,
|
|
286
|
+
hostSegIndex: si,
|
|
287
|
+
dcConnNetId: curr.dcConnNetId,
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Continue traversing to prioritize the furthest valid point
|
|
291
|
+
continue
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
this.netLabelPlacement = {
|
|
295
|
+
globalConnNetId:
|
|
296
|
+
this.overlappingSameNetTraceGroup.globalConnNetId,
|
|
297
|
+
dcConnNetId: curr.dcConnNetId,
|
|
298
|
+
orientation,
|
|
299
|
+
anchorPoint: anchor,
|
|
300
|
+
width,
|
|
301
|
+
height,
|
|
302
|
+
center,
|
|
303
|
+
}
|
|
304
|
+
this.solved = true
|
|
305
|
+
return
|
|
468
306
|
}
|
|
469
|
-
this.solved = true
|
|
470
|
-
return
|
|
471
307
|
}
|
|
472
308
|
}
|
|
473
309
|
}
|
|
474
310
|
|
|
311
|
+
if (singleOrientationMode && bestCandidate) {
|
|
312
|
+
this.netLabelPlacement = {
|
|
313
|
+
globalConnNetId: this.overlappingSameNetTraceGroup.globalConnNetId,
|
|
314
|
+
dcConnNetId: bestCandidate.dcConnNetId,
|
|
315
|
+
orientation: bestCandidate.orientation,
|
|
316
|
+
anchorPoint: bestCandidate.anchor,
|
|
317
|
+
width: bestCandidate.width,
|
|
318
|
+
height: bestCandidate.height,
|
|
319
|
+
center: bestCandidate.center,
|
|
320
|
+
}
|
|
321
|
+
this.solved = true
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
|
|
475
325
|
this.failed = true
|
|
476
326
|
this.error = "Could not place net label without collisions"
|
|
477
327
|
}
|
|
478
328
|
|
|
479
329
|
override visualize(): GraphicsObject {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
// Visualize the entire trace group for this net id
|
|
483
|
-
const groupId = this.overlappingSameNetTraceGroup.globalConnNetId
|
|
484
|
-
// Choose host as in _step: the trace that touches the largest chip in the group
|
|
485
|
-
const chipsById: Record<string, InputChip> = Object.fromEntries(
|
|
486
|
-
this.inputProblem.chips.map((c) => [c.chipId, c]),
|
|
487
|
-
)
|
|
488
|
-
const groupTraces = Object.values(this.inputTraceMap).filter(
|
|
489
|
-
(t) => t.globalConnNetId === groupId,
|
|
490
|
-
)
|
|
491
|
-
const chipIdsInGroup = new Set<string>()
|
|
492
|
-
for (const t of groupTraces) {
|
|
493
|
-
chipIdsInGroup.add(t.pins[0].chipId)
|
|
494
|
-
chipIdsInGroup.add(t.pins[1].chipId)
|
|
495
|
-
}
|
|
496
|
-
let largestChipId: string | null = null
|
|
497
|
-
let largestPinCount = -1
|
|
498
|
-
for (const id of chipIdsInGroup) {
|
|
499
|
-
const chip = chipsById[id]
|
|
500
|
-
const count = chip?.pins?.length ?? 0
|
|
501
|
-
if (count > largestPinCount) {
|
|
502
|
-
largestPinCount = count
|
|
503
|
-
largestChipId = id
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
const lengthOf = (path: SolvedTracePath) => {
|
|
507
|
-
let sum = 0
|
|
508
|
-
const pts = path.tracePath
|
|
509
|
-
for (let i = 0; i < pts.length - 1; i++) {
|
|
510
|
-
sum +=
|
|
511
|
-
Math.abs(pts[i + 1]!.x - pts[i]!.x) +
|
|
512
|
-
Math.abs(pts[i + 1]!.y - pts[i]!.y)
|
|
513
|
-
}
|
|
514
|
-
return sum
|
|
515
|
-
}
|
|
516
|
-
const hostCandidates =
|
|
517
|
-
largestChipId == null
|
|
518
|
-
? []
|
|
519
|
-
: groupTraces.filter(
|
|
520
|
-
(t) =>
|
|
521
|
-
t.pins[0].chipId === largestChipId ||
|
|
522
|
-
t.pins[1].chipId === largestChipId,
|
|
523
|
-
)
|
|
524
|
-
const host =
|
|
525
|
-
hostCandidates.length > 0
|
|
526
|
-
? hostCandidates.reduce((a, b) => (lengthOf(a) >= lengthOf(b) ? a : b))
|
|
527
|
-
: this.overlappingSameNetTraceGroup.overlappingTraces
|
|
528
|
-
const groupStroke = getColorFromString(groupId, 0.9)
|
|
529
|
-
const groupFill = getColorFromString(groupId, 0.5)
|
|
530
|
-
|
|
531
|
-
for (const trace of Object.values(this.inputTraceMap)) {
|
|
532
|
-
if (trace.globalConnNetId !== groupId) continue
|
|
533
|
-
const isHost = host ? trace.mspPairId === host.mspPairId : false
|
|
534
|
-
graphics.lines!.push({
|
|
535
|
-
points: trace.tracePath,
|
|
536
|
-
strokeColor: isHost ? groupStroke : groupFill,
|
|
537
|
-
strokeWidth: isHost ? 0.006 : 0.003,
|
|
538
|
-
strokeDash: isHost ? undefined : "4 2",
|
|
539
|
-
} as any)
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// Visualize all tested candidate rectangles with reason coloring
|
|
543
|
-
for (const c of this.testedCandidates) {
|
|
544
|
-
const fill =
|
|
545
|
-
c.status === "ok"
|
|
546
|
-
? "rgba(0, 180, 0, 0.25)"
|
|
547
|
-
: c.status === "chip-collision"
|
|
548
|
-
? "rgba(220, 0, 0, 0.25)"
|
|
549
|
-
: c.status === "trace-collision"
|
|
550
|
-
? "rgba(220, 140, 0, 0.25)"
|
|
551
|
-
: "rgba(120, 120, 120, 0.15)"
|
|
552
|
-
const stroke =
|
|
553
|
-
c.status === "ok"
|
|
554
|
-
? "green"
|
|
555
|
-
: c.status === "chip-collision"
|
|
556
|
-
? "red"
|
|
557
|
-
: c.status === "trace-collision"
|
|
558
|
-
? "orange"
|
|
559
|
-
: "gray"
|
|
560
|
-
|
|
561
|
-
graphics.rects!.push({
|
|
562
|
-
center: {
|
|
563
|
-
x: (c.bounds.minX + c.bounds.maxX) / 2,
|
|
564
|
-
y: (c.bounds.minY + c.bounds.maxY) / 2,
|
|
565
|
-
},
|
|
566
|
-
width: c.width,
|
|
567
|
-
height: c.height,
|
|
568
|
-
fill,
|
|
569
|
-
strokeColor: stroke,
|
|
570
|
-
} as any)
|
|
571
|
-
|
|
572
|
-
graphics.points!.push({
|
|
573
|
-
x: c.anchor.x,
|
|
574
|
-
y: c.anchor.y,
|
|
575
|
-
color: stroke,
|
|
576
|
-
} as any)
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// Visualize the final accepted label (if any)
|
|
580
|
-
if (this.netLabelPlacement) {
|
|
581
|
-
const p = this.netLabelPlacement
|
|
582
|
-
graphics.rects!.push({
|
|
583
|
-
center: p.center,
|
|
584
|
-
width: p.width,
|
|
585
|
-
height: p.height,
|
|
586
|
-
fill: "rgba(0, 128, 255, 0.35)",
|
|
587
|
-
strokeColor: "blue",
|
|
588
|
-
} as any)
|
|
589
|
-
graphics.points!.push({
|
|
590
|
-
x: p.anchorPoint.x,
|
|
591
|
-
y: p.anchorPoint.y,
|
|
592
|
-
color: "blue",
|
|
593
|
-
} as any)
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
return graphics
|
|
330
|
+
return visualizeSingleNetLabelPlacementSolver(this)
|
|
597
331
|
}
|
|
598
332
|
}
|