@tscircuit/schematic-trace-solver 0.0.121 → 0.0.122
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 +10 -3
- package/dist/index.js +196 -28
- package/lib/solvers/RailNetLabelCornerPlacementSolver/RailNetLabelCornerPlacementSolver.ts +223 -21
- package/lib/solvers/RailNetLabelCornerPlacementSolver/types.ts +3 -3
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +34 -9
- package/package.json +1 -1
- package/site/bug-reports/bug-report-20260804T095800Z.page.tsx +4 -0
- package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +4 -4
- package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +5 -5
- package/tests/bug-reports/bug-report-20260717T042845Z/__snapshots__/bug-report-20260717T042845Z.snap.svg +12 -12
- package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260804T095800Z/__snapshots__/bug-report-20260804T095800Z.snap.svg +135 -0
- package/tests/bug-reports/bug-report-20260804T095800Z/bug-report-20260804T095800Z.json +634 -0
- package/tests/bug-reports/bug-report-20260804T095800Z/bug-report-20260804T095800Z.test.ts +12 -0
- package/tests/examples/__snapshots__/example12.snap.svg +2 -2
- package/tests/examples/__snapshots__/example13.snap.svg +4 -4
- package/tests/examples/__snapshots__/example14.snap.svg +36 -36
- package/tests/examples/__snapshots__/example31.snap.svg +6 -6
- package/tests/examples/__snapshots__/example43.snap.svg +2 -2
- package/tests/examples/__snapshots__/example44.snap.svg +2 -2
- package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +2 -2
- package/tests/repros/__snapshots__/bugreport-001-gnd-overlap.snap.svg +26 -26
- package/tests/repros/__snapshots__/netlabel-connector-through-rail-label.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-atmega328p-fault-pullup.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-atmega328p-missing-gnd-netlabel.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-missing-trace-netlabel.snap.svg +18 -18
- package/tests/repros/__snapshots__/repro-netlabel-overlap-trace.snap.svg +3 -3
- package/tests/repros/__snapshots__/repro-nrf52810-clock-routing.snap.svg +82 -0
- package/tests/repros/assets/repro-nrf52810-clock-routing.input.json +234 -0
- package/tests/repros/repro-bq25895-cross-net-trace-overlap.test.ts +13 -0
- package/tests/repros/repro-missing-trace-netlabel.test.ts +12 -0
- package/tests/repros/repro-netlabel-overlap-trace.test.ts +38 -0
- package/tests/repros/repro-nrf52810-clock-routing.test.ts +28 -0
package/dist/index.d.ts
CHANGED
|
@@ -828,11 +828,11 @@ type TraceCornerCandidate = {
|
|
|
828
828
|
traceId: string;
|
|
829
829
|
distance: number;
|
|
830
830
|
/**
|
|
831
|
-
* True when the corner
|
|
832
|
-
*
|
|
833
|
-
* a clean stub off that pin rather than floating over a mid-trace bend.
|
|
831
|
+
* True when the corner is on a vertical rail that connects directly to one
|
|
832
|
+
* of the trace's endpoint pins, rather than on an arbitrary middle bend.
|
|
834
833
|
*/
|
|
835
834
|
pinAligned: boolean;
|
|
835
|
+
reroutedTracePath?: Point[];
|
|
836
836
|
};
|
|
837
837
|
type CornerCandidateStatus = "valid" | "chip-collision" | "text-collision" | "trace-collision" | "netlabel-collision";
|
|
838
838
|
type EvaluatedCornerCandidate = TraceCornerCandidate & {
|
|
@@ -859,6 +859,7 @@ declare class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
859
859
|
getConstructorParams(): ConstructorParameters<typeof RailNetLabelCornerPlacementSolver>[0];
|
|
860
860
|
_step(): void;
|
|
861
861
|
getOutput(): {
|
|
862
|
+
traces: SolvedTracePath[];
|
|
862
863
|
netLabelPlacements: NetLabelPlacement[];
|
|
863
864
|
};
|
|
864
865
|
private getProcessableLabelIndices;
|
|
@@ -873,6 +874,12 @@ declare class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
873
874
|
private intersectsAnyChip;
|
|
874
875
|
private intersectsAnyOtherNetLabel;
|
|
875
876
|
private getCornerCandidatesForLabel;
|
|
877
|
+
private isConfiguredRailLabel;
|
|
878
|
+
private pointsEqual;
|
|
879
|
+
private getVerticalSegmentPointIndices;
|
|
880
|
+
private isPinAlignedCorner;
|
|
881
|
+
private getClearanceShiftedCornerCandidates;
|
|
882
|
+
private isReroutedTraceClear;
|
|
876
883
|
private getTraceLinesForLabel;
|
|
877
884
|
private isTraceForLabel;
|
|
878
885
|
visualize(): GraphicsObject;
|
package/dist/index.js
CHANGED
|
@@ -9052,6 +9052,7 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
|
|
|
9052
9052
|
}
|
|
9053
9053
|
getOutput() {
|
|
9054
9054
|
return {
|
|
9055
|
+
traces: this.traces,
|
|
9055
9056
|
netLabelPlacements: this.outputNetLabelPlacements
|
|
9056
9057
|
};
|
|
9057
9058
|
}
|
|
@@ -9104,15 +9105,25 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
|
|
|
9104
9105
|
center,
|
|
9105
9106
|
width: label.width,
|
|
9106
9107
|
height: label.height,
|
|
9107
|
-
status: this.getCandidateStatus(bounds, labelIndex),
|
|
9108
|
+
status: this.getCandidateStatus(bounds, labelIndex, candidate),
|
|
9108
9109
|
selected: false
|
|
9109
9110
|
};
|
|
9110
9111
|
}
|
|
9111
|
-
getCandidateStatus(bounds, labelIndex) {
|
|
9112
|
+
getCandidateStatus(bounds, labelIndex, candidate) {
|
|
9112
9113
|
if (this.intersectsAnyChip(bounds)) return "chip-collision";
|
|
9113
9114
|
if (rectIntersectsAnyTextBox(bounds, this.inputProblem))
|
|
9114
9115
|
return "text-collision";
|
|
9115
|
-
|
|
9116
|
+
const candidateTraceMap = candidate.reroutedTracePath ? {
|
|
9117
|
+
...this.traceMap,
|
|
9118
|
+
[candidate.traceId]: {
|
|
9119
|
+
...this.traceMap[candidate.traceId],
|
|
9120
|
+
tracePath: candidate.reroutedTracePath
|
|
9121
|
+
}
|
|
9122
|
+
} : this.traceMap;
|
|
9123
|
+
if (traceCrossesBoundsInterior(bounds, candidateTraceMap)) {
|
|
9124
|
+
return "trace-collision";
|
|
9125
|
+
}
|
|
9126
|
+
if (candidate.reroutedTracePath && !this.isReroutedTraceClear(candidate, labelIndex)) {
|
|
9116
9127
|
return "trace-collision";
|
|
9117
9128
|
}
|
|
9118
9129
|
if (this.intersectsAnyOtherNetLabel(bounds, labelIndex)) {
|
|
@@ -9121,6 +9132,23 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
|
|
|
9121
9132
|
return "valid";
|
|
9122
9133
|
}
|
|
9123
9134
|
applyCandidate(labelIndex, label, candidate) {
|
|
9135
|
+
if (candidate.reroutedTracePath) {
|
|
9136
|
+
const originalTrace = this.traceMap[candidate.traceId];
|
|
9137
|
+
const reroutedTrace = {
|
|
9138
|
+
...originalTrace,
|
|
9139
|
+
tracePath: candidate.reroutedTracePath
|
|
9140
|
+
};
|
|
9141
|
+
this.traces = this.traces.map(
|
|
9142
|
+
(trace) => trace.mspPairId === candidate.traceId ? reroutedTrace : trace
|
|
9143
|
+
);
|
|
9144
|
+
this.traceMap[candidate.traceId] = reroutedTrace;
|
|
9145
|
+
this.outputNetLabelPlacements = moveAttachedLabelsToReroutedTrace({
|
|
9146
|
+
trace: originalTrace,
|
|
9147
|
+
originalTracePath: originalTrace.tracePath,
|
|
9148
|
+
reroutedTracePath: candidate.reroutedTracePath,
|
|
9149
|
+
netLabelPlacements: this.outputNetLabelPlacements
|
|
9150
|
+
});
|
|
9151
|
+
}
|
|
9124
9152
|
this.outputNetLabelPlacements[labelIndex] = {
|
|
9125
9153
|
...label,
|
|
9126
9154
|
anchorPoint: candidate.anchorPoint,
|
|
@@ -9146,30 +9174,152 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
|
|
|
9146
9174
|
});
|
|
9147
9175
|
}
|
|
9148
9176
|
getCornerCandidatesForLabel(label) {
|
|
9149
|
-
const
|
|
9150
|
-
const
|
|
9151
|
-
const
|
|
9152
|
-
|
|
9177
|
+
const anchorAlignedCandidates = [];
|
|
9178
|
+
const railAlignedCandidates = [];
|
|
9179
|
+
const labelTraces = this.getTraceLinesForLabel(label);
|
|
9180
|
+
const allowRailAlignedFallback = this.isConfiguredRailLabel(label) && !labelTraces.some(
|
|
9181
|
+
(trace) => getTraceCorners(trace.tracePath).some(
|
|
9182
|
+
(corner) => this.pointsEqual(corner, label.anchorPoint)
|
|
9183
|
+
)
|
|
9184
|
+
);
|
|
9185
|
+
for (const trace of labelTraces) {
|
|
9153
9186
|
const path = trace.tracePath;
|
|
9154
9187
|
const pins = [path[0], path[path.length - 1]];
|
|
9155
9188
|
for (const anchorPoint of getTraceCorners(path)) {
|
|
9156
|
-
const
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
return Math.abs(pin.y - anchorPoint.y) < EPS6;
|
|
9162
|
-
});
|
|
9189
|
+
const cornerIndex = path.indexOf(anchorPoint);
|
|
9190
|
+
const anchorAlignedToPin = pins.some(
|
|
9191
|
+
(pin) => Math.abs(pin.x - anchorPoint.x) <= EPS6
|
|
9192
|
+
);
|
|
9193
|
+
const pinAligned = anchorAlignedToPin || allowRailAlignedFallback && this.isPinAlignedCorner(path, cornerIndex);
|
|
9163
9194
|
if (!pinAligned) continue;
|
|
9164
|
-
candidates
|
|
9195
|
+
const candidates = anchorAlignedToPin ? anchorAlignedCandidates : railAlignedCandidates;
|
|
9196
|
+
candidates.push(
|
|
9197
|
+
{
|
|
9198
|
+
anchorPoint,
|
|
9199
|
+
traceId: trace.mspPairId,
|
|
9200
|
+
distance: getDistance(anchorPoint, label.anchorPoint),
|
|
9201
|
+
pinAligned
|
|
9202
|
+
},
|
|
9203
|
+
...this.getClearanceShiftedCornerCandidates({
|
|
9204
|
+
label,
|
|
9205
|
+
trace,
|
|
9206
|
+
cornerIndex,
|
|
9207
|
+
pinAligned
|
|
9208
|
+
})
|
|
9209
|
+
);
|
|
9210
|
+
}
|
|
9211
|
+
}
|
|
9212
|
+
return [
|
|
9213
|
+
...anchorAlignedCandidates.sort((a, b) => a.distance - b.distance),
|
|
9214
|
+
...railAlignedCandidates.sort((a, b) => a.distance - b.distance)
|
|
9215
|
+
];
|
|
9216
|
+
}
|
|
9217
|
+
isConfiguredRailLabel(label) {
|
|
9218
|
+
if (!label.netId) return false;
|
|
9219
|
+
return this.inputProblem.availableNetLabelOrientations[label.netId]?.some(
|
|
9220
|
+
(orientation) => orientation === "y+" || orientation === "y-"
|
|
9221
|
+
);
|
|
9222
|
+
}
|
|
9223
|
+
pointsEqual(a, b) {
|
|
9224
|
+
return Math.abs(a.x - b.x) <= EPS6 && Math.abs(a.y - b.y) <= EPS6;
|
|
9225
|
+
}
|
|
9226
|
+
getVerticalSegmentPointIndices(path, cornerIndex) {
|
|
9227
|
+
const corner = path[cornerIndex];
|
|
9228
|
+
const previousIsVertical = Math.abs(path[cornerIndex - 1].x - corner.x) <= EPS6;
|
|
9229
|
+
const nextIsVertical = Math.abs(path[cornerIndex + 1].x - corner.x) <= EPS6;
|
|
9230
|
+
if (previousIsVertical === nextIsVertical) return null;
|
|
9231
|
+
const indices = previousIsVertical ? [cornerIndex - 1, cornerIndex] : [cornerIndex, cornerIndex + 1];
|
|
9232
|
+
return indices;
|
|
9233
|
+
}
|
|
9234
|
+
isPinAlignedCorner(path, cornerIndex) {
|
|
9235
|
+
const verticalPointIndices = this.getVerticalSegmentPointIndices(
|
|
9236
|
+
path,
|
|
9237
|
+
cornerIndex
|
|
9238
|
+
);
|
|
9239
|
+
if (!verticalPointIndices) return false;
|
|
9240
|
+
const [startIndex, endIndex] = verticalPointIndices;
|
|
9241
|
+
return startIndex <= 1 || endIndex >= path.length - 2;
|
|
9242
|
+
}
|
|
9243
|
+
getClearanceShiftedCornerCandidates({
|
|
9244
|
+
label,
|
|
9245
|
+
trace,
|
|
9246
|
+
cornerIndex,
|
|
9247
|
+
pinAligned
|
|
9248
|
+
}) {
|
|
9249
|
+
if (label.orientation !== "y+" && label.orientation !== "y-") return [];
|
|
9250
|
+
const path = trace.tracePath;
|
|
9251
|
+
const corner = path[cornerIndex];
|
|
9252
|
+
const verticalPointIndices = this.getVerticalSegmentPointIndices(
|
|
9253
|
+
path,
|
|
9254
|
+
cornerIndex
|
|
9255
|
+
);
|
|
9256
|
+
if (!verticalPointIndices || verticalPointIndices.includes(0) || verticalPointIndices.includes(path.length - 1)) {
|
|
9257
|
+
return [];
|
|
9258
|
+
}
|
|
9259
|
+
const center = getCenterFromAnchor(
|
|
9260
|
+
corner,
|
|
9261
|
+
label.orientation,
|
|
9262
|
+
label.width,
|
|
9263
|
+
label.height
|
|
9264
|
+
);
|
|
9265
|
+
const labelBounds = getRectBounds(center, label.width, label.height);
|
|
9266
|
+
const collidingChipBounds = this.inputProblem.chips.map((chip) => getRectBounds(chip.center, chip.width, chip.height)).filter((chipBounds) => rectsOverlap(labelBounds, chipBounds));
|
|
9267
|
+
const shiftedCoordinates = /* @__PURE__ */ new Set();
|
|
9268
|
+
for (const chipBounds of collidingChipBounds) {
|
|
9269
|
+
shiftedCoordinates.add(chipBounds.minX - label.width / 2);
|
|
9270
|
+
shiftedCoordinates.add(chipBounds.maxX + label.width / 2);
|
|
9271
|
+
}
|
|
9272
|
+
return [...shiftedCoordinates].flatMap((x) => {
|
|
9273
|
+
if (Math.abs(x - corner.x) <= EPS6) return [];
|
|
9274
|
+
const reroutedTracePath = simplifyPath(
|
|
9275
|
+
path.map(
|
|
9276
|
+
(point, pointIndex) => verticalPointIndices.includes(pointIndex) ? { ...point, x } : point
|
|
9277
|
+
)
|
|
9278
|
+
);
|
|
9279
|
+
const anchorPoint = { x, y: corner.y };
|
|
9280
|
+
const remainsCorner = getTraceCorners(reroutedTracePath).some(
|
|
9281
|
+
(point) => this.pointsEqual(point, anchorPoint)
|
|
9282
|
+
);
|
|
9283
|
+
if (!remainsCorner) return [];
|
|
9284
|
+
return [
|
|
9285
|
+
{
|
|
9165
9286
|
anchorPoint,
|
|
9166
9287
|
traceId: trace.mspPairId,
|
|
9167
9288
|
distance: getDistance(anchorPoint, label.anchorPoint),
|
|
9168
|
-
pinAligned
|
|
9169
|
-
|
|
9170
|
-
|
|
9289
|
+
pinAligned,
|
|
9290
|
+
reroutedTracePath
|
|
9291
|
+
}
|
|
9292
|
+
];
|
|
9293
|
+
});
|
|
9294
|
+
}
|
|
9295
|
+
isReroutedTraceClear(candidate, labelIndex) {
|
|
9296
|
+
const reroutedTracePath = candidate.reroutedTracePath;
|
|
9297
|
+
if (isPathCollidingWithObstacles(
|
|
9298
|
+
reroutedTracePath,
|
|
9299
|
+
getObstacleRects(this.inputProblem)
|
|
9300
|
+
)) {
|
|
9301
|
+
return false;
|
|
9171
9302
|
}
|
|
9172
|
-
|
|
9303
|
+
const candidateTrace = this.traceMap[candidate.traceId];
|
|
9304
|
+
const otherNetTraces = this.traces.filter(
|
|
9305
|
+
(trace) => trace.mspPairId !== candidate.traceId && trace.globalConnNetId !== candidateTrace.globalConnNetId
|
|
9306
|
+
);
|
|
9307
|
+
if (doesPathOverlapTraceStrokes(reroutedTracePath, otherNetTraces)) {
|
|
9308
|
+
return false;
|
|
9309
|
+
}
|
|
9310
|
+
const reroutedTraceMap = {
|
|
9311
|
+
[candidate.traceId]: {
|
|
9312
|
+
...candidateTrace,
|
|
9313
|
+
tracePath: reroutedTracePath
|
|
9314
|
+
}
|
|
9315
|
+
};
|
|
9316
|
+
return this.outputNetLabelPlacements.every((label, index) => {
|
|
9317
|
+
if (index === labelIndex) return true;
|
|
9318
|
+
return !traceCrossesBoundsInterior(
|
|
9319
|
+
getRectBounds(label.center, label.width, label.height),
|
|
9320
|
+
reroutedTraceMap
|
|
9321
|
+
);
|
|
9322
|
+
});
|
|
9173
9323
|
}
|
|
9174
9324
|
getTraceLinesForLabel(label) {
|
|
9175
9325
|
return this.traces.filter(
|
|
@@ -11484,9 +11634,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
11484
11634
|
(instance) => [
|
|
11485
11635
|
{
|
|
11486
11636
|
inputProblem: instance.inputProblem,
|
|
11487
|
-
traces:
|
|
11488
|
-
instance.postLabelTraceOverlapShiftSolver.correctedTraceMap
|
|
11489
|
-
),
|
|
11637
|
+
traces: instance.railNetLabelCornerPlacementSolver.getOutput().traces,
|
|
11490
11638
|
netLabelPlacements: instance.railNetLabelCornerPlacementSolver.outputNetLabelPlacements
|
|
11491
11639
|
}
|
|
11492
11640
|
]
|
|
@@ -11497,9 +11645,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
11497
11645
|
(instance) => [
|
|
11498
11646
|
{
|
|
11499
11647
|
inputProblem: instance.inputProblem,
|
|
11500
|
-
traces:
|
|
11501
|
-
instance.postLabelTraceOverlapShiftSolver.correctedTraceMap
|
|
11502
|
-
),
|
|
11648
|
+
traces: instance.railNetLabelCornerPlacementSolver.getOutput().traces,
|
|
11503
11649
|
netLabelPlacements: instance.traceAnchoredNetLabelOverlapSolver.outputNetLabelPlacements
|
|
11504
11650
|
}
|
|
11505
11651
|
]
|
|
@@ -11529,12 +11675,34 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
11529
11675
|
"netLabelTraceCollisionSolver",
|
|
11530
11676
|
NetLabelTraceCollisionSolver,
|
|
11531
11677
|
(instance) => {
|
|
11532
|
-
const
|
|
11678
|
+
const previousOutput = instance.preAlignmentNetLabelTraceCollisionSolver.getOutput();
|
|
11679
|
+
const alignmentOutput = instance.traceCleanupSolver2.getOutput();
|
|
11680
|
+
const previousTraceMap = new Map(
|
|
11681
|
+
previousOutput.traces.map((trace) => [trace.mspPairId, trace])
|
|
11682
|
+
);
|
|
11683
|
+
let netLabelPlacements = previousOutput.netLabelPlacements;
|
|
11684
|
+
for (const trace of alignmentOutput.traces) {
|
|
11685
|
+
const previousTrace = previousTraceMap.get(trace.mspPairId);
|
|
11686
|
+
if (!previousTrace || previousTrace.tracePath === trace.tracePath) {
|
|
11687
|
+
continue;
|
|
11688
|
+
}
|
|
11689
|
+
const currentLabels = netLabelPlacements;
|
|
11690
|
+
const movedLabels = moveAttachedLabelsToReroutedTrace({
|
|
11691
|
+
trace: previousTrace,
|
|
11692
|
+
originalTracePath: previousTrace.tracePath,
|
|
11693
|
+
reroutedTracePath: trace.tracePath,
|
|
11694
|
+
netLabelPlacements: currentLabels
|
|
11695
|
+
});
|
|
11696
|
+
netLabelPlacements = movedLabels.map((movedLabel, labelIndex) => {
|
|
11697
|
+
const label = currentLabels[labelIndex];
|
|
11698
|
+
return label.orientation === "y+" || label.orientation === "y-" ? movedLabel : label;
|
|
11699
|
+
});
|
|
11700
|
+
}
|
|
11533
11701
|
return [
|
|
11534
11702
|
{
|
|
11535
11703
|
inputProblem: instance.inputProblem,
|
|
11536
|
-
traces:
|
|
11537
|
-
netLabelPlacements
|
|
11704
|
+
traces: alignmentOutput.traces,
|
|
11705
|
+
netLabelPlacements
|
|
11538
11706
|
}
|
|
11539
11707
|
];
|
|
11540
11708
|
}
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
3
|
import { traceCrossesBoundsInterior } from "lib/solvers/AvailableNetOrientationSolver/geometry"
|
|
3
4
|
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
5
|
+
import { moveAttachedLabelsToReroutedTrace } from "lib/solvers/Example28Solver/labelMovement"
|
|
4
6
|
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
5
7
|
import {
|
|
6
8
|
getCenterFromAnchor,
|
|
7
9
|
getRectBounds,
|
|
8
10
|
} from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
9
11
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
12
|
+
import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
13
|
+
import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
14
|
+
import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
|
|
10
15
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
16
|
+
import { doesPathOverlapTraceStrokes } from "lib/utils/doesPathCoincideWithTraces"
|
|
11
17
|
import {
|
|
12
18
|
EPS,
|
|
13
19
|
getDistance,
|
|
@@ -95,6 +101,7 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
95
101
|
|
|
96
102
|
getOutput() {
|
|
97
103
|
return {
|
|
104
|
+
traces: this.traces,
|
|
98
105
|
netLabelPlacements: this.outputNetLabelPlacements,
|
|
99
106
|
}
|
|
100
107
|
}
|
|
@@ -163,7 +170,7 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
163
170
|
center,
|
|
164
171
|
width: label.width,
|
|
165
172
|
height: label.height,
|
|
166
|
-
status: this.getCandidateStatus(bounds, labelIndex),
|
|
173
|
+
status: this.getCandidateStatus(bounds, labelIndex, candidate),
|
|
167
174
|
selected: false,
|
|
168
175
|
}
|
|
169
176
|
}
|
|
@@ -171,11 +178,27 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
171
178
|
private getCandidateStatus(
|
|
172
179
|
bounds: Bounds,
|
|
173
180
|
labelIndex: number,
|
|
181
|
+
candidate: TraceCornerCandidate,
|
|
174
182
|
): CornerCandidateStatus {
|
|
175
183
|
if (this.intersectsAnyChip(bounds)) return "chip-collision"
|
|
176
184
|
if (rectIntersectsAnyTextBox(bounds, this.inputProblem))
|
|
177
185
|
return "text-collision"
|
|
178
|
-
|
|
186
|
+
const candidateTraceMap = candidate.reroutedTracePath
|
|
187
|
+
? {
|
|
188
|
+
...this.traceMap,
|
|
189
|
+
[candidate.traceId]: {
|
|
190
|
+
...this.traceMap[candidate.traceId]!,
|
|
191
|
+
tracePath: candidate.reroutedTracePath,
|
|
192
|
+
},
|
|
193
|
+
}
|
|
194
|
+
: this.traceMap
|
|
195
|
+
if (traceCrossesBoundsInterior(bounds, candidateTraceMap)) {
|
|
196
|
+
return "trace-collision"
|
|
197
|
+
}
|
|
198
|
+
if (
|
|
199
|
+
candidate.reroutedTracePath &&
|
|
200
|
+
!this.isReroutedTraceClear(candidate, labelIndex)
|
|
201
|
+
) {
|
|
179
202
|
return "trace-collision"
|
|
180
203
|
}
|
|
181
204
|
if (this.intersectsAnyOtherNetLabel(bounds, labelIndex)) {
|
|
@@ -190,6 +213,24 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
190
213
|
label: NetLabelPlacement,
|
|
191
214
|
candidate: EvaluatedCornerCandidate,
|
|
192
215
|
) {
|
|
216
|
+
if (candidate.reroutedTracePath) {
|
|
217
|
+
const originalTrace = this.traceMap[candidate.traceId]!
|
|
218
|
+
const reroutedTrace = {
|
|
219
|
+
...originalTrace,
|
|
220
|
+
tracePath: candidate.reroutedTracePath,
|
|
221
|
+
}
|
|
222
|
+
this.traces = this.traces.map((trace) =>
|
|
223
|
+
trace.mspPairId === candidate.traceId ? reroutedTrace : trace,
|
|
224
|
+
)
|
|
225
|
+
this.traceMap[candidate.traceId] = reroutedTrace
|
|
226
|
+
this.outputNetLabelPlacements = moveAttachedLabelsToReroutedTrace({
|
|
227
|
+
trace: originalTrace,
|
|
228
|
+
originalTracePath: originalTrace.tracePath,
|
|
229
|
+
reroutedTracePath: candidate.reroutedTracePath,
|
|
230
|
+
netLabelPlacements: this.outputNetLabelPlacements,
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
|
|
193
234
|
this.outputNetLabelPlacements[labelIndex] = {
|
|
194
235
|
...label,
|
|
195
236
|
anchorPoint: candidate.anchorPoint,
|
|
@@ -225,36 +266,197 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
225
266
|
}
|
|
226
267
|
|
|
227
268
|
private getCornerCandidatesForLabel(label: NetLabelPlacement) {
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
const
|
|
269
|
+
const anchorAlignedCandidates: TraceCornerCandidate[] = []
|
|
270
|
+
const railAlignedCandidates: TraceCornerCandidate[] = []
|
|
271
|
+
const labelTraces = this.getTraceLinesForLabel(label)
|
|
272
|
+
const allowRailAlignedFallback =
|
|
273
|
+
this.isConfiguredRailLabel(label) &&
|
|
274
|
+
!labelTraces.some((trace) =>
|
|
275
|
+
getTraceCorners(trace.tracePath).some((corner) =>
|
|
276
|
+
this.pointsEqual(corner, label.anchorPoint),
|
|
277
|
+
),
|
|
278
|
+
)
|
|
231
279
|
|
|
232
|
-
for (const trace of
|
|
280
|
+
for (const trace of labelTraces) {
|
|
233
281
|
const path = trace.tracePath
|
|
234
282
|
const pins = [path[0]!, path[path.length - 1]!]
|
|
235
283
|
for (const anchorPoint of getTraceCorners(path)) {
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
// stub off a pin). Non-aligned corners are no better than where the
|
|
245
|
-
// label already is, so they aren't offered as candidates — this keeps
|
|
246
|
-
// already-well-placed labels untouched.
|
|
284
|
+
const cornerIndex = path.indexOf(anchorPoint)
|
|
285
|
+
const anchorAlignedToPin = pins.some(
|
|
286
|
+
(pin) => Math.abs(pin.x - anchorPoint.x) <= EPS,
|
|
287
|
+
)
|
|
288
|
+
const pinAligned =
|
|
289
|
+
anchorAlignedToPin ||
|
|
290
|
+
(allowRailAlignedFallback &&
|
|
291
|
+
this.isPinAlignedCorner(path, cornerIndex))
|
|
247
292
|
if (!pinAligned) continue
|
|
248
|
-
|
|
293
|
+
|
|
294
|
+
const candidates = anchorAlignedToPin
|
|
295
|
+
? anchorAlignedCandidates
|
|
296
|
+
: railAlignedCandidates
|
|
297
|
+
candidates.push(
|
|
298
|
+
{
|
|
299
|
+
anchorPoint,
|
|
300
|
+
traceId: trace.mspPairId,
|
|
301
|
+
distance: getDistance(anchorPoint, label.anchorPoint),
|
|
302
|
+
pinAligned,
|
|
303
|
+
},
|
|
304
|
+
...this.getClearanceShiftedCornerCandidates({
|
|
305
|
+
label,
|
|
306
|
+
trace,
|
|
307
|
+
cornerIndex,
|
|
308
|
+
pinAligned,
|
|
309
|
+
}),
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return [
|
|
315
|
+
...anchorAlignedCandidates.sort((a, b) => a.distance - b.distance),
|
|
316
|
+
...railAlignedCandidates.sort((a, b) => a.distance - b.distance),
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private isConfiguredRailLabel(label: NetLabelPlacement) {
|
|
321
|
+
if (!label.netId) return false
|
|
322
|
+
return this.inputProblem.availableNetLabelOrientations[label.netId]?.some(
|
|
323
|
+
(orientation) => orientation === "y+" || orientation === "y-",
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private pointsEqual(a: Point, b: Point) {
|
|
328
|
+
return Math.abs(a.x - b.x) <= EPS && Math.abs(a.y - b.y) <= EPS
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private getVerticalSegmentPointIndices(path: Point[], cornerIndex: number) {
|
|
332
|
+
const corner = path[cornerIndex]!
|
|
333
|
+
const previousIsVertical =
|
|
334
|
+
Math.abs(path[cornerIndex - 1]!.x - corner.x) <= EPS
|
|
335
|
+
const nextIsVertical = Math.abs(path[cornerIndex + 1]!.x - corner.x) <= EPS
|
|
336
|
+
if (previousIsVertical === nextIsVertical) return null
|
|
337
|
+
|
|
338
|
+
const indices = previousIsVertical
|
|
339
|
+
? [cornerIndex - 1, cornerIndex]
|
|
340
|
+
: [cornerIndex, cornerIndex + 1]
|
|
341
|
+
return indices
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private isPinAlignedCorner(path: Point[], cornerIndex: number) {
|
|
345
|
+
const verticalPointIndices = this.getVerticalSegmentPointIndices(
|
|
346
|
+
path,
|
|
347
|
+
cornerIndex,
|
|
348
|
+
)
|
|
349
|
+
if (!verticalPointIndices) return false
|
|
350
|
+
|
|
351
|
+
const [startIndex, endIndex] = verticalPointIndices
|
|
352
|
+
return startIndex! <= 1 || endIndex! >= path.length - 2
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private getClearanceShiftedCornerCandidates({
|
|
356
|
+
label,
|
|
357
|
+
trace,
|
|
358
|
+
cornerIndex,
|
|
359
|
+
pinAligned,
|
|
360
|
+
}: {
|
|
361
|
+
label: NetLabelPlacement
|
|
362
|
+
trace: SolvedTracePath
|
|
363
|
+
cornerIndex: number
|
|
364
|
+
pinAligned: boolean
|
|
365
|
+
}): TraceCornerCandidate[] {
|
|
366
|
+
if (label.orientation !== "y+" && label.orientation !== "y-") return []
|
|
367
|
+
|
|
368
|
+
const path = trace.tracePath
|
|
369
|
+
const corner = path[cornerIndex]!
|
|
370
|
+
const verticalPointIndices = this.getVerticalSegmentPointIndices(
|
|
371
|
+
path,
|
|
372
|
+
cornerIndex,
|
|
373
|
+
)
|
|
374
|
+
if (
|
|
375
|
+
!verticalPointIndices ||
|
|
376
|
+
verticalPointIndices.includes(0) ||
|
|
377
|
+
verticalPointIndices.includes(path.length - 1)
|
|
378
|
+
) {
|
|
379
|
+
return []
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const center = getCenterFromAnchor(
|
|
383
|
+
corner,
|
|
384
|
+
label.orientation,
|
|
385
|
+
label.width,
|
|
386
|
+
label.height,
|
|
387
|
+
)
|
|
388
|
+
const labelBounds = getRectBounds(center, label.width, label.height)
|
|
389
|
+
const collidingChipBounds = this.inputProblem.chips
|
|
390
|
+
.map((chip) => getRectBounds(chip.center, chip.width, chip.height))
|
|
391
|
+
.filter((chipBounds) => rectsOverlap(labelBounds, chipBounds))
|
|
392
|
+
const shiftedCoordinates = new Set<number>()
|
|
393
|
+
for (const chipBounds of collidingChipBounds) {
|
|
394
|
+
shiftedCoordinates.add(chipBounds.minX - label.width / 2)
|
|
395
|
+
shiftedCoordinates.add(chipBounds.maxX + label.width / 2)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return [...shiftedCoordinates].flatMap((x) => {
|
|
399
|
+
if (Math.abs(x - corner.x) <= EPS) return []
|
|
400
|
+
const reroutedTracePath = simplifyPath(
|
|
401
|
+
path.map((point, pointIndex) =>
|
|
402
|
+
verticalPointIndices.includes(pointIndex) ? { ...point, x } : point,
|
|
403
|
+
),
|
|
404
|
+
)
|
|
405
|
+
const anchorPoint = { x, y: corner.y }
|
|
406
|
+
const remainsCorner = getTraceCorners(reroutedTracePath).some((point) =>
|
|
407
|
+
this.pointsEqual(point, anchorPoint),
|
|
408
|
+
)
|
|
409
|
+
if (!remainsCorner) return []
|
|
410
|
+
|
|
411
|
+
return [
|
|
412
|
+
{
|
|
249
413
|
anchorPoint,
|
|
250
414
|
traceId: trace.mspPairId,
|
|
251
415
|
distance: getDistance(anchorPoint, label.anchorPoint),
|
|
252
416
|
pinAligned,
|
|
253
|
-
|
|
254
|
-
|
|
417
|
+
reroutedTracePath,
|
|
418
|
+
},
|
|
419
|
+
]
|
|
420
|
+
})
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private isReroutedTraceClear(
|
|
424
|
+
candidate: TraceCornerCandidate,
|
|
425
|
+
labelIndex: number,
|
|
426
|
+
) {
|
|
427
|
+
const reroutedTracePath = candidate.reroutedTracePath!
|
|
428
|
+
if (
|
|
429
|
+
isPathCollidingWithObstacles(
|
|
430
|
+
reroutedTracePath,
|
|
431
|
+
getObstacleRects(this.inputProblem),
|
|
432
|
+
)
|
|
433
|
+
) {
|
|
434
|
+
return false
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const candidateTrace = this.traceMap[candidate.traceId]!
|
|
438
|
+
const otherNetTraces = this.traces.filter(
|
|
439
|
+
(trace) =>
|
|
440
|
+
trace.mspPairId !== candidate.traceId &&
|
|
441
|
+
trace.globalConnNetId !== candidateTrace.globalConnNetId,
|
|
442
|
+
)
|
|
443
|
+
if (doesPathOverlapTraceStrokes(reroutedTracePath, otherNetTraces)) {
|
|
444
|
+
return false
|
|
255
445
|
}
|
|
256
446
|
|
|
257
|
-
|
|
447
|
+
const reroutedTraceMap = {
|
|
448
|
+
[candidate.traceId]: {
|
|
449
|
+
...candidateTrace,
|
|
450
|
+
tracePath: reroutedTracePath,
|
|
451
|
+
},
|
|
452
|
+
}
|
|
453
|
+
return this.outputNetLabelPlacements.every((label, index) => {
|
|
454
|
+
if (index === labelIndex) return true
|
|
455
|
+
return !traceCrossesBoundsInterior(
|
|
456
|
+
getRectBounds(label.center, label.width, label.height),
|
|
457
|
+
reroutedTraceMap,
|
|
458
|
+
)
|
|
459
|
+
})
|
|
258
460
|
}
|
|
259
461
|
|
|
260
462
|
private getTraceLinesForLabel(label: NetLabelPlacement) {
|
|
@@ -21,11 +21,11 @@ export type TraceCornerCandidate = {
|
|
|
21
21
|
traceId: string
|
|
22
22
|
distance: number
|
|
23
23
|
/**
|
|
24
|
-
* True when the corner
|
|
25
|
-
*
|
|
26
|
-
* a clean stub off that pin rather than floating over a mid-trace bend.
|
|
24
|
+
* True when the corner is on a vertical rail that connects directly to one
|
|
25
|
+
* of the trace's endpoint pins, rather than on an arbitrary middle bend.
|
|
27
26
|
*/
|
|
28
27
|
pinAligned: boolean
|
|
28
|
+
reroutedTracePath?: Point[]
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export type CornerCandidateStatus =
|