@tscircuit/schematic-trace-solver 0.0.130 → 0.0.132
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 +6 -0
- package/dist/index.js +217 -13
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +290 -12
- package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +9 -0
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +4 -4
- package/tests/bug-reports/bug-report-20260811T075142Z/__snapshots__/bug-report-20260811T075142Z.snap.svg +32 -32
- package/tests/bug-reports/bug-report-20260811T075142Z/bug-report-20260811T075142Z.test.ts +119 -0
- package/tests/repros/__snapshots__/repro-board-589-regulator-section.snap.svg +69 -0
- package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +467 -0
- package/tests/repros/assets/repro-board-589-regulator-section.input.json +270 -0
- package/tests/repros/assets/repro-usb-power-vbus-label-detour.input.json +2382 -0
- package/tests/repros/repro-board-589-regulator-section.test.ts +33 -0
- package/tests/repros/repro-usb-power-vbus-label-detour.test.ts +16 -0
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
11
11
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
12
12
|
import type {
|
|
13
|
+
ChipId,
|
|
13
14
|
InputNetConnection,
|
|
14
15
|
InputPin,
|
|
15
16
|
InputProblem,
|
|
@@ -54,6 +55,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
54
55
|
currentLabelIndex: number | null = null
|
|
55
56
|
currentLabel: NetLabelPlacement | null = null
|
|
56
57
|
currentCandidateResults: EvaluatedCandidate[] = []
|
|
58
|
+
private crowdedPortOnlyLabelIndices = new Set<number>()
|
|
57
59
|
|
|
58
60
|
blockedStandaloneLabelIndex: number | null = null
|
|
59
61
|
blockedStandaloneLabelTargetAnchorX: number | null = null
|
|
@@ -76,6 +78,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
76
78
|
params.inputProblem._chipObstacleSpatialIndex ??
|
|
77
79
|
new ChipObstacleSpatialIndex(params.inputProblem.chips)
|
|
78
80
|
this.maxSearchDistance = getMaxSearchDistance(params.inputProblem)
|
|
81
|
+
this.crowdedPortOnlyLabelIndices = this.getCrowdedPortOnlyLabelIndices()
|
|
79
82
|
this.queuedLabelIndices = this.getProcessableLabelIndices()
|
|
80
83
|
this.setCurrentLabel(this.queuedLabelIndices[0] ?? null)
|
|
81
84
|
}
|
|
@@ -111,10 +114,11 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
111
114
|
const indices: number[] = []
|
|
112
115
|
|
|
113
116
|
for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
|
|
114
|
-
if (this.shouldProcessLabel(this.outputNetLabelPlacements[i]
|
|
117
|
+
if (this.shouldProcessLabel(this.outputNetLabelPlacements[i]!, i)) {
|
|
115
118
|
indices.push(i)
|
|
116
119
|
}
|
|
117
120
|
}
|
|
121
|
+
this.sortCrowdedTopBankByPin(indices)
|
|
118
122
|
|
|
119
123
|
const blockedStandaloneLabelIndex = indices.find((index) => {
|
|
120
124
|
const label = this.outputNetLabelPlacements[index]!
|
|
@@ -184,9 +188,98 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
184
188
|
return indices
|
|
185
189
|
}
|
|
186
190
|
|
|
187
|
-
private
|
|
191
|
+
private sortCrowdedTopBankByPin(indices: number[]) {
|
|
192
|
+
const slotsByChip = new Map<ChipId, number[]>()
|
|
193
|
+
|
|
194
|
+
for (let slot = 0; slot < indices.length; slot++) {
|
|
195
|
+
const labelIndex = indices[slot]!
|
|
196
|
+
if (!this.crowdedPortOnlyLabelIndices.has(labelIndex)) continue
|
|
197
|
+
const label = this.outputNetLabelPlacements[labelIndex]!
|
|
198
|
+
if (this.getChipSideForPoint(label.anchorPoint) !== "top") continue
|
|
199
|
+
const pin = this.pinMap[label.pinIds[0]!]
|
|
200
|
+
if (!pin) continue
|
|
201
|
+
const slots = slotsByChip.get(pin.chipId) ?? []
|
|
202
|
+
slots.push(slot)
|
|
203
|
+
slotsByChip.set(pin.chipId, slots)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
for (const slots of slotsByChip.values()) {
|
|
207
|
+
const labelIndices = slots
|
|
208
|
+
.map((slot) => indices[slot]!)
|
|
209
|
+
.sort((a, b) => {
|
|
210
|
+
const aPin = this.pinMap[this.outputNetLabelPlacements[a]!.pinIds[0]!]
|
|
211
|
+
const bPin = this.pinMap[this.outputNetLabelPlacements[b]!.pinIds[0]!]
|
|
212
|
+
return aPin!.x - bPin!.x
|
|
213
|
+
})
|
|
214
|
+
for (let position = 0; position < slots.length; position++) {
|
|
215
|
+
indices[slots[position]!] = labelIndices[position]!
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private shouldProcessLabel(label: NetLabelPlacement, labelIndex: number) {
|
|
188
221
|
const orientations = this.getAvailableOrientations(label)
|
|
189
|
-
|
|
222
|
+
if (orientations.length === 0) return false
|
|
223
|
+
if (!orientations.includes(label.orientation)) return true
|
|
224
|
+
if (!this.crowdedPortOnlyLabelIndices.has(labelIndex)) return false
|
|
225
|
+
|
|
226
|
+
const bounds = getRectBounds(label.center, label.width, label.height)
|
|
227
|
+
return this.outputNetLabelPlacements.some((otherLabel, otherIndex) => {
|
|
228
|
+
if (otherIndex === labelIndex) return false
|
|
229
|
+
if (otherLabel.globalConnNetId === label.globalConnNetId) return false
|
|
230
|
+
return rectsOverlap(
|
|
231
|
+
bounds,
|
|
232
|
+
getRectBounds(otherLabel.center, otherLabel.width, otherLabel.height),
|
|
233
|
+
)
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private getCrowdedPortOnlyLabelIndices() {
|
|
238
|
+
const labelIndicesByChipAndSide = new Map<ChipId, Map<ChipSide, number[]>>()
|
|
239
|
+
for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
|
|
240
|
+
const label = this.outputNetLabelPlacements[i]!
|
|
241
|
+
if (!this.isPortOnlyLabel(label)) continue
|
|
242
|
+
if (!this.isStandaloneSinglePinNetLabel(label)) continue
|
|
243
|
+
if (this.getAvailableOrientations(label).length === 0) continue
|
|
244
|
+
const pin = this.pinMap[label.pinIds[0]!]
|
|
245
|
+
if (!pin) continue
|
|
246
|
+
const chipSide = this.getChipSideForPoint(label.anchorPoint)
|
|
247
|
+
if (!chipSide) continue
|
|
248
|
+
const labelIndicesBySide =
|
|
249
|
+
labelIndicesByChipAndSide.get(pin.chipId) ??
|
|
250
|
+
new Map<ChipSide, number[]>()
|
|
251
|
+
const labelIndices = labelIndicesBySide.get(chipSide) ?? []
|
|
252
|
+
labelIndices.push(i)
|
|
253
|
+
labelIndicesBySide.set(chipSide, labelIndices)
|
|
254
|
+
labelIndicesByChipAndSide.set(pin.chipId, labelIndicesBySide)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const crowdedIndices = new Set<number>()
|
|
258
|
+
for (const labelIndicesBySide of labelIndicesByChipAndSide.values()) {
|
|
259
|
+
for (const labelIndices of labelIndicesBySide.values()) {
|
|
260
|
+
const hasWrongOrientation = labelIndices.some((index) => {
|
|
261
|
+
const label = this.outputNetLabelPlacements[index]!
|
|
262
|
+
return !this.getAvailableOrientations(label).includes(
|
|
263
|
+
label.orientation,
|
|
264
|
+
)
|
|
265
|
+
})
|
|
266
|
+
const hasOverlap = labelIndices.some((index, position) => {
|
|
267
|
+
const label = this.outputNetLabelPlacements[index]!
|
|
268
|
+
const bounds = getRectBounds(label.center, label.width, label.height)
|
|
269
|
+
return labelIndices.slice(position + 1).some((otherIndex) => {
|
|
270
|
+
const other = this.outputNetLabelPlacements[otherIndex]!
|
|
271
|
+
return rectsOverlap(
|
|
272
|
+
bounds,
|
|
273
|
+
getRectBounds(other.center, other.width, other.height),
|
|
274
|
+
)
|
|
275
|
+
})
|
|
276
|
+
})
|
|
277
|
+
if (!hasWrongOrientation || !hasOverlap) continue
|
|
278
|
+
for (const index of labelIndices) crowdedIndices.add(index)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return crowdedIndices
|
|
190
283
|
}
|
|
191
284
|
|
|
192
285
|
private processLabel(labelIndex: number) {
|
|
@@ -219,7 +312,11 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
219
312
|
) {
|
|
220
313
|
if (candidate.phase === "trace-anchor") return
|
|
221
314
|
|
|
222
|
-
const tracePath = this.getCandidateConnectorTrace(
|
|
315
|
+
const tracePath = this.getCandidateConnectorTrace(
|
|
316
|
+
label,
|
|
317
|
+
candidate,
|
|
318
|
+
labelIndex,
|
|
319
|
+
)
|
|
223
320
|
if (tracePath.length < 2) return
|
|
224
321
|
|
|
225
322
|
const mspPairId = `available-net-orientation-${labelIndex}-${label.netId ?? label.globalConnNetId}`
|
|
@@ -254,6 +351,21 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
254
351
|
private findCorrectedCandidate(label: NetLabelPlacement, labelIndex: number) {
|
|
255
352
|
const orientations = this.getAvailableOrientations(label)
|
|
256
353
|
const requiredOrientation = orientations[0]!
|
|
354
|
+
|
|
355
|
+
if (
|
|
356
|
+
this.crowdedPortOnlyLabelIndices.has(labelIndex) &&
|
|
357
|
+
this.getChipSideForPoint(label.anchorPoint) === "top" &&
|
|
358
|
+
isYOrientation(requiredOrientation)
|
|
359
|
+
) {
|
|
360
|
+
const topFanoutCandidate =
|
|
361
|
+
this.findValidCrowdedTopVerticalFanoutCandidate(
|
|
362
|
+
label,
|
|
363
|
+
labelIndex,
|
|
364
|
+
requiredOrientation,
|
|
365
|
+
)
|
|
366
|
+
if (topFanoutCandidate) return topFanoutCandidate
|
|
367
|
+
}
|
|
368
|
+
|
|
257
369
|
const netConnection = this.inputProblem.netConnections.find(
|
|
258
370
|
(connection) => connection.netId === label.netId,
|
|
259
371
|
)
|
|
@@ -334,6 +446,117 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
334
446
|
)
|
|
335
447
|
}
|
|
336
448
|
|
|
449
|
+
private findValidCrowdedTopVerticalFanoutCandidate(
|
|
450
|
+
label: NetLabelPlacement,
|
|
451
|
+
labelIndex: number,
|
|
452
|
+
orientation: "y+" | "y-",
|
|
453
|
+
) {
|
|
454
|
+
const pin = this.pinMap[label.pinIds[0]!]
|
|
455
|
+
const chip = pin
|
|
456
|
+
? this.chipObstacleSpatialIndex.chips.find(
|
|
457
|
+
(candidate) => candidate.chipId === pin.chipId,
|
|
458
|
+
)
|
|
459
|
+
: undefined
|
|
460
|
+
if (!chip) return null
|
|
461
|
+
|
|
462
|
+
const { width, height } = getDimsForOrientation({
|
|
463
|
+
orientation,
|
|
464
|
+
netLabelWidth: this.getNetLabelWidth(label),
|
|
465
|
+
netLabelHeight: this.getNetLabelHeight(label),
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
const nextPinToRight = this.outputNetLabelPlacements
|
|
469
|
+
.map((otherLabel, index) => ({
|
|
470
|
+
index,
|
|
471
|
+
pin: this.pinMap[otherLabel.pinIds[0]!],
|
|
472
|
+
}))
|
|
473
|
+
.filter(
|
|
474
|
+
({ index, pin: otherPin }) =>
|
|
475
|
+
this.crowdedPortOnlyLabelIndices.has(index) &&
|
|
476
|
+
otherPin?.chipId === pin.chipId &&
|
|
477
|
+
otherPin.x > pin.x + EPS,
|
|
478
|
+
)
|
|
479
|
+
.sort((a, b) => a.pin!.x - b.pin!.x)[0]?.pin
|
|
480
|
+
|
|
481
|
+
// A down-facing label uses the terminal right-hand escape. Interior
|
|
482
|
+
// down-facing labels fall back to the general search instead of crossing
|
|
483
|
+
// the remaining pin corridors.
|
|
484
|
+
if (orientation === "y-" && nextPinToRight) return null
|
|
485
|
+
|
|
486
|
+
const anchorX =
|
|
487
|
+
orientation === "y-"
|
|
488
|
+
? Math.max(
|
|
489
|
+
label.anchorPoint.x + LABEL_SEARCH_STEP,
|
|
490
|
+
chip.bounds.maxX + width / 2 + LABEL_TRACE_CLEARANCE,
|
|
491
|
+
)
|
|
492
|
+
: Math.min(
|
|
493
|
+
label.anchorPoint.x,
|
|
494
|
+
(nextPinToRight?.x ?? chip.bounds.maxX) -
|
|
495
|
+
LABEL_SEARCH_STEP -
|
|
496
|
+
width / 2,
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
let highestPlacedLabelY = chip.bounds.maxY
|
|
500
|
+
let lowestUpFacingLabelY = Number.POSITIVE_INFINITY
|
|
501
|
+
let highestUpFacingLabelY = Number.NEGATIVE_INFINITY
|
|
502
|
+
for (let index = 0; index < this.outputNetLabelPlacements.length; index++) {
|
|
503
|
+
if (index === labelIndex) continue
|
|
504
|
+
if (!this.crowdedPortOnlyLabelIndices.has(index)) continue
|
|
505
|
+
if (this.queuedLabelIndices.includes(index)) continue
|
|
506
|
+
const placedLabel = this.outputNetLabelPlacements[index]!
|
|
507
|
+
const placedBounds = getRectBounds(
|
|
508
|
+
placedLabel.center,
|
|
509
|
+
placedLabel.width,
|
|
510
|
+
placedLabel.height,
|
|
511
|
+
)
|
|
512
|
+
highestPlacedLabelY = Math.max(highestPlacedLabelY, placedBounds.maxY)
|
|
513
|
+
if (placedLabel.orientation !== "y+") continue
|
|
514
|
+
lowestUpFacingLabelY = Math.min(lowestUpFacingLabelY, placedBounds.minY)
|
|
515
|
+
highestUpFacingLabelY = Math.max(highestUpFacingLabelY, placedBounds.maxY)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const stackedAnchorY =
|
|
519
|
+
highestPlacedLabelY +
|
|
520
|
+
LABEL_SEARCH_STEP +
|
|
521
|
+
(orientation === "y-" ? height : 0)
|
|
522
|
+
const hasUpFacingStack =
|
|
523
|
+
Number.isFinite(lowestUpFacingLabelY) &&
|
|
524
|
+
Number.isFinite(highestUpFacingLabelY)
|
|
525
|
+
const centeredAnchorY = hasUpFacingStack
|
|
526
|
+
? (lowestUpFacingLabelY + highestUpFacingLabelY) / 2
|
|
527
|
+
: stackedAnchorY
|
|
528
|
+
const anchorYs =
|
|
529
|
+
orientation === "y-"
|
|
530
|
+
? [centeredAnchorY, stackedAnchorY]
|
|
531
|
+
: [stackedAnchorY]
|
|
532
|
+
|
|
533
|
+
for (const anchorY of anchorYs) {
|
|
534
|
+
if (anchorY - label.anchorPoint.y > this.maxSearchDistance + EPS) {
|
|
535
|
+
continue
|
|
536
|
+
}
|
|
537
|
+
const candidate = this.createCandidate(
|
|
538
|
+
label,
|
|
539
|
+
{ x: anchorX, y: anchorY },
|
|
540
|
+
orientation,
|
|
541
|
+
)
|
|
542
|
+
const result = this.evaluateCandidate(
|
|
543
|
+
candidate,
|
|
544
|
+
label,
|
|
545
|
+
labelIndex,
|
|
546
|
+
"lateral-shift",
|
|
547
|
+
anchorY - label.anchorPoint.y,
|
|
548
|
+
anchorX - label.anchorPoint.x,
|
|
549
|
+
)
|
|
550
|
+
this.currentCandidateResults.push(result)
|
|
551
|
+
if (result.status !== "valid") continue
|
|
552
|
+
|
|
553
|
+
result.selected = true
|
|
554
|
+
return result
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return null
|
|
558
|
+
}
|
|
559
|
+
|
|
337
560
|
private hasTraceContinuingInOrientation(
|
|
338
561
|
label: NetLabelPlacement,
|
|
339
562
|
orientation: "y+" | "y-",
|
|
@@ -703,12 +926,35 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
703
926
|
EvaluatedCandidate,
|
|
704
927
|
"anchorPoint" | "connectorSource" | "orientation" | "phase"
|
|
705
928
|
>,
|
|
929
|
+
labelIndex: number,
|
|
706
930
|
) {
|
|
931
|
+
if (
|
|
932
|
+
this.crowdedPortOnlyLabelIndices.has(labelIndex) &&
|
|
933
|
+
this.getChipSideForPoint(label.anchorPoint) === "top" &&
|
|
934
|
+
isYOrientation(candidate.orientation)
|
|
935
|
+
) {
|
|
936
|
+
// Top-edge vertical labels use the target row as their bend row. This
|
|
937
|
+
// makes every connector leave the pin upward first, instead of creating
|
|
938
|
+
// a low horizontal bus that the overlap solver later pushes below the
|
|
939
|
+
// component. It also gives a y- ground label the desired up-then-right
|
|
940
|
+
// connector while preserving its required facing direction.
|
|
941
|
+
return simplifyOrthogonalPath([
|
|
942
|
+
label.anchorPoint,
|
|
943
|
+
{ x: label.anchorPoint.x, y: candidate.anchorPoint.y },
|
|
944
|
+
candidate.anchorPoint,
|
|
945
|
+
])
|
|
946
|
+
}
|
|
947
|
+
|
|
707
948
|
if (candidate.phase === "lateral-shift") {
|
|
708
|
-
const
|
|
949
|
+
const chipOutwardDir = this.crowdedPortOnlyLabelIndices.has(labelIndex)
|
|
950
|
+
? this.getChipOutwardDirection(label.anchorPoint)
|
|
951
|
+
: (() => {
|
|
952
|
+
const orientDir = dir(candidate.orientation)
|
|
953
|
+
return { x: -orientDir.x, y: -orientDir.y }
|
|
954
|
+
})()
|
|
709
955
|
const kickedSource = {
|
|
710
|
-
x: label.anchorPoint.x
|
|
711
|
-
y: label.anchorPoint.y
|
|
956
|
+
x: label.anchorPoint.x + chipOutwardDir.x * LABEL_SEARCH_STEP,
|
|
957
|
+
y: label.anchorPoint.y + chipOutwardDir.y * LABEL_SEARCH_STEP,
|
|
712
958
|
}
|
|
713
959
|
return simplifyOrthogonalPath([
|
|
714
960
|
label.anchorPoint,
|
|
@@ -1007,11 +1253,15 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1007
1253
|
if (nonChipBoundsStatus !== "valid") return nonChipBoundsStatus
|
|
1008
1254
|
}
|
|
1009
1255
|
|
|
1010
|
-
const connectorTrace = this.getCandidateConnectorTrace(
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1256
|
+
const connectorTrace = this.getCandidateConnectorTrace(
|
|
1257
|
+
label,
|
|
1258
|
+
{
|
|
1259
|
+
anchorPoint: candidate.anchorPoint,
|
|
1260
|
+
orientation: candidate.orientation,
|
|
1261
|
+
phase,
|
|
1262
|
+
},
|
|
1263
|
+
labelIndex,
|
|
1264
|
+
)
|
|
1015
1265
|
|
|
1016
1266
|
for (const chip of this.chipObstacleSpatialIndex.chips) {
|
|
1017
1267
|
if (tracePathCrossesAnyBounds(connectorTrace, chip.bounds)) {
|
|
@@ -1021,6 +1271,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1021
1271
|
|
|
1022
1272
|
for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
|
|
1023
1273
|
if (i === labelIndex) continue
|
|
1274
|
+
if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue
|
|
1024
1275
|
const otherLabel = this.outputNetLabelPlacements[i]!
|
|
1025
1276
|
if (
|
|
1026
1277
|
tracePathIntersectsBounds(
|
|
@@ -1227,9 +1478,21 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1227
1478
|
return label.mspConnectionPairIds.length === 0
|
|
1228
1479
|
}
|
|
1229
1480
|
|
|
1481
|
+
private shouldIgnorePendingCrowdedLabel(
|
|
1482
|
+
labelIndex: number,
|
|
1483
|
+
otherLabelIndex: number,
|
|
1484
|
+
) {
|
|
1485
|
+
return (
|
|
1486
|
+
this.crowdedPortOnlyLabelIndices.has(labelIndex) &&
|
|
1487
|
+
this.crowdedPortOnlyLabelIndices.has(otherLabelIndex) &&
|
|
1488
|
+
this.queuedLabelIndices.includes(otherLabelIndex)
|
|
1489
|
+
)
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1230
1492
|
private intersectsAnyOtherNetLabel(bounds: Bounds, labelIndex: number) {
|
|
1231
1493
|
for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
|
|
1232
1494
|
if (i === labelIndex) continue
|
|
1495
|
+
if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue
|
|
1233
1496
|
const label = this.outputNetLabelPlacements[i]!
|
|
1234
1497
|
const otherBounds = getRectBounds(label.center, label.width, label.height)
|
|
1235
1498
|
if (
|
|
@@ -1312,6 +1575,21 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1312
1575
|
return { x: 0, y: 0 }
|
|
1313
1576
|
}
|
|
1314
1577
|
|
|
1578
|
+
private getChipOutwardDirection(point: Point) {
|
|
1579
|
+
switch (this.getChipSideForPoint(point)) {
|
|
1580
|
+
case "left":
|
|
1581
|
+
return { x: -1, y: 0 }
|
|
1582
|
+
case "right":
|
|
1583
|
+
return { x: 1, y: 0 }
|
|
1584
|
+
case "bottom":
|
|
1585
|
+
return { x: 0, y: -1 }
|
|
1586
|
+
case "top":
|
|
1587
|
+
return { x: 0, y: 1 }
|
|
1588
|
+
default:
|
|
1589
|
+
return { x: 0, y: 0 }
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1315
1593
|
private getContainingChipSide(point: Point) {
|
|
1316
1594
|
let nearestSide: ChipSide | null = null
|
|
1317
1595
|
let nearestDistance = Number.POSITIVE_INFINITY
|
|
@@ -4,6 +4,7 @@ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/Sche
|
|
|
4
4
|
import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
5
5
|
import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
6
6
|
import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
|
|
7
|
+
import { preservesLabelAnchors } from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/preservesLabelAnchors"
|
|
7
8
|
import {
|
|
8
9
|
getVisibleTraceLength,
|
|
9
10
|
getVisibleTraceSegmentCount,
|
|
@@ -164,6 +165,14 @@ const candidateIsClear = ({
|
|
|
164
165
|
return false
|
|
165
166
|
}
|
|
166
167
|
|
|
168
|
+
const candidateTraces = traces.map((trace) => {
|
|
169
|
+
if (trace.mspPairId === originalTrace.mspPairId) return candidateTrace
|
|
170
|
+
return trace
|
|
171
|
+
})
|
|
172
|
+
if (!preservesLabelAnchors(netLabelPlacements, traces, candidateTraces)) {
|
|
173
|
+
return false
|
|
174
|
+
}
|
|
175
|
+
|
|
167
176
|
const otherNetLabelPlacements = netLabelPlacements.filter(
|
|
168
177
|
(label) => label.globalConnNetId !== candidateTrace.globalConnNetId,
|
|
169
178
|
)
|