@tscircuit/schematic-trace-solver 0.0.92 → 0.0.94
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 +7 -1
- package/dist/index.js +89 -22
- package/lib/solvers/Example28Solver/doesPathRunAlongChipBoundary.ts +18 -0
- package/lib/solvers/Example28Solver/reroute.ts +99 -16
- package/lib/solvers/Example28Solver/types.ts +7 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +15 -9
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +5 -5
- package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +4 -6
- package/tests/examples/__snapshots__/example03.snap.svg +44 -44
- package/tests/examples/__snapshots__/example32.snap.svg +3 -3
- package/tests/examples/__snapshots__/example42.snap.svg +1 -1
- package/tests/repros/__snapshots__/repro-missing-trace-netlabel.snap.svg +18 -20
- package/tests/repros/__snapshots__/repro-netlabel-overlap-trace.snap.svg +3 -3
- package/tests/repros/__snapshots__/repro-rectifier-trace-overlap.snap.svg +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -194,7 +194,6 @@ declare class SchematicTraceSingleLineSolver2 extends BaseSolver {
|
|
|
194
194
|
private axisOfSegment;
|
|
195
195
|
private pathLength;
|
|
196
196
|
private getPinBandPenalty;
|
|
197
|
-
private pathCost;
|
|
198
197
|
private isSegmentOutsidePinBand;
|
|
199
198
|
_step(): void;
|
|
200
199
|
visualize(): GraphicsObject;
|
|
@@ -650,6 +649,13 @@ type TracePathScore = {
|
|
|
650
649
|
labelIntersections: number;
|
|
651
650
|
labelHugDistance: number;
|
|
652
651
|
traceIntersections: number;
|
|
652
|
+
/**
|
|
653
|
+
* Total length the path runs along a chip boundary. A soft quality penalty
|
|
654
|
+
* (traces hugging a chip edge look bad) that is only preferred against once
|
|
655
|
+
* the more important factors above are equal, so a boundary-hugging route is
|
|
656
|
+
* still chosen over one that actually overlaps a label or another trace.
|
|
657
|
+
*/
|
|
658
|
+
chipBoundaryOverlap: number;
|
|
653
659
|
pathLength: number;
|
|
654
660
|
};
|
|
655
661
|
type RerouteCandidateResult = {
|
package/dist/index.js
CHANGED
|
@@ -1112,9 +1112,6 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
|
|
|
1112
1112
|
}
|
|
1113
1113
|
return penalty;
|
|
1114
1114
|
}
|
|
1115
|
-
pathCost(path) {
|
|
1116
|
-
return this.pathLength(path) + this.getPinBandPenalty(path);
|
|
1117
|
-
}
|
|
1118
1115
|
isSegmentOutsidePinBand(a, b) {
|
|
1119
1116
|
if (isHorizontal(a, b)) {
|
|
1120
1117
|
return a.y <= this.aabb.minY || a.y >= this.aabb.maxY;
|
|
@@ -1210,8 +1207,12 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
|
|
|
1210
1207
|
this.visited.add(key);
|
|
1211
1208
|
const nextSet = new Set(collisionRects);
|
|
1212
1209
|
nextSet.add(rect);
|
|
1213
|
-
|
|
1214
|
-
|
|
1210
|
+
newStates.push({
|
|
1211
|
+
path: newPath,
|
|
1212
|
+
collisionRects: nextSet,
|
|
1213
|
+
length: this.pathLength(newPath),
|
|
1214
|
+
pinBandPenalty: this.getPinBandPenalty(newPath)
|
|
1215
|
+
});
|
|
1215
1216
|
};
|
|
1216
1217
|
for (const coord of candidates) {
|
|
1217
1218
|
addShiftedCandidate(segIndex, axis, coord);
|
|
@@ -1243,7 +1244,9 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
|
|
|
1243
1244
|
addShiftedCandidate(adjacentSegIndex, adjacentAxis, coord);
|
|
1244
1245
|
}
|
|
1245
1246
|
}
|
|
1246
|
-
newStates.sort(
|
|
1247
|
+
newStates.sort(
|
|
1248
|
+
(a2, b2) => a2.length - b2.length || a2.pinBandPenalty - b2.pinBandPenalty
|
|
1249
|
+
);
|
|
1247
1250
|
for (const st of newStates) {
|
|
1248
1251
|
this.queue.push({ path: st.path, collisionRects: st.collisionRects });
|
|
1249
1252
|
}
|
|
@@ -5627,6 +5630,19 @@ var doesPathRunAlongChipBoundary = (path, chipObstacles) => {
|
|
|
5627
5630
|
}
|
|
5628
5631
|
return false;
|
|
5629
5632
|
};
|
|
5633
|
+
var getChipBoundaryOverlap = (path, chipObstacles) => {
|
|
5634
|
+
let total = 0;
|
|
5635
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
5636
|
+
const start = path[i];
|
|
5637
|
+
const end = path[i + 1];
|
|
5638
|
+
for (const obstacle of chipObstacles) {
|
|
5639
|
+
if (!segmentRunsAlongRectBoundary(start, end, obstacle)) continue;
|
|
5640
|
+
const overlap = getSegmentOverlapWithRectSpan(start, end, obstacle);
|
|
5641
|
+
if (overlap > EPS7) total += overlap;
|
|
5642
|
+
}
|
|
5643
|
+
}
|
|
5644
|
+
return total;
|
|
5645
|
+
};
|
|
5630
5646
|
var getSegmentOverlapWithRectSpan = (start, end, rect) => {
|
|
5631
5647
|
const isVertical4 = Math.abs(start.x - end.x) < EPS7;
|
|
5632
5648
|
if (isVertical4) {
|
|
@@ -5637,6 +5653,30 @@ var getSegmentOverlapWithRectSpan = (start, end, rect) => {
|
|
|
5637
5653
|
|
|
5638
5654
|
// lib/solvers/Example28Solver/reroute.ts
|
|
5639
5655
|
var LABEL_CLEARANCE = 0.1;
|
|
5656
|
+
var MAX_CORRIDOR_SHIFTS = 16;
|
|
5657
|
+
var corridorHasOtherNetTrace = ({
|
|
5658
|
+
x,
|
|
5659
|
+
lowY,
|
|
5660
|
+
highY,
|
|
5661
|
+
outputTraces,
|
|
5662
|
+
netId
|
|
5663
|
+
}) => {
|
|
5664
|
+
for (const other of outputTraces) {
|
|
5665
|
+
if (other.globalConnNetId === netId) continue;
|
|
5666
|
+
const path = other.tracePath;
|
|
5667
|
+
for (let i = 0; i + 1 < path.length; i++) {
|
|
5668
|
+
const start = path[i];
|
|
5669
|
+
const end = path[i + 1];
|
|
5670
|
+
if (Math.abs(start.x - end.x) >= 1e-9) continue;
|
|
5671
|
+
if (Math.abs(start.x - x) >= LABEL_CLEARANCE / 2) continue;
|
|
5672
|
+
const segLowY = Math.min(start.y, end.y);
|
|
5673
|
+
const segHighY = Math.max(start.y, end.y);
|
|
5674
|
+
if (Math.min(highY, segHighY) - Math.max(lowY, segLowY) > 1e-6)
|
|
5675
|
+
return true;
|
|
5676
|
+
}
|
|
5677
|
+
}
|
|
5678
|
+
return false;
|
|
5679
|
+
};
|
|
5640
5680
|
var findBestReroutePath = ({
|
|
5641
5681
|
trace,
|
|
5642
5682
|
obstacleLabel,
|
|
@@ -5658,7 +5698,8 @@ var findBestReroutePath = ({
|
|
|
5658
5698
|
obstacleLabel,
|
|
5659
5699
|
outputTraces,
|
|
5660
5700
|
outputNetLabelPlacements,
|
|
5661
|
-
candidateResults
|
|
5701
|
+
candidateResults,
|
|
5702
|
+
chipObstacles
|
|
5662
5703
|
});
|
|
5663
5704
|
markSelectedCandidate(candidateResults, bestPath);
|
|
5664
5705
|
return { bestPath, candidateResults };
|
|
@@ -5674,8 +5715,11 @@ var generateRerouteCandidateResults = ({
|
|
|
5674
5715
|
const seen = /* @__PURE__ */ new Set();
|
|
5675
5716
|
const candidateResults = [];
|
|
5676
5717
|
const horizontalSegmentPushCandidate = generateHorizontalSegmentPushCandidate(
|
|
5677
|
-
|
|
5678
|
-
|
|
5718
|
+
{
|
|
5719
|
+
trace,
|
|
5720
|
+
label,
|
|
5721
|
+
outputTraces
|
|
5722
|
+
}
|
|
5679
5723
|
);
|
|
5680
5724
|
if (horizontalSegmentPushCandidate) {
|
|
5681
5725
|
candidateResults.push(
|
|
@@ -5745,14 +5789,6 @@ var createCandidateResult = ({
|
|
|
5745
5789
|
selected: false
|
|
5746
5790
|
};
|
|
5747
5791
|
}
|
|
5748
|
-
if (doesPathRunAlongChipBoundary(path, chipObstacles)) {
|
|
5749
|
-
return {
|
|
5750
|
-
path,
|
|
5751
|
-
status: "chip-collision",
|
|
5752
|
-
usesHorizontalSegmentPush,
|
|
5753
|
-
selected: false
|
|
5754
|
-
};
|
|
5755
|
-
}
|
|
5756
5792
|
return {
|
|
5757
5793
|
path,
|
|
5758
5794
|
score: scoreTracePath({
|
|
@@ -5760,7 +5796,8 @@ var createCandidateResult = ({
|
|
|
5760
5796
|
tracePath: path,
|
|
5761
5797
|
obstacleLabel,
|
|
5762
5798
|
outputTraces,
|
|
5763
|
-
outputNetLabelPlacements
|
|
5799
|
+
outputNetLabelPlacements,
|
|
5800
|
+
chipObstacles
|
|
5764
5801
|
}),
|
|
5765
5802
|
status: "valid",
|
|
5766
5803
|
usesHorizontalSegmentPush,
|
|
@@ -5850,7 +5887,8 @@ var selectBestReroutePath = ({
|
|
|
5850
5887
|
obstacleLabel,
|
|
5851
5888
|
outputTraces,
|
|
5852
5889
|
outputNetLabelPlacements,
|
|
5853
|
-
candidateResults
|
|
5890
|
+
candidateResults,
|
|
5891
|
+
chipObstacles
|
|
5854
5892
|
}) => {
|
|
5855
5893
|
for (const candidate of candidateResults) {
|
|
5856
5894
|
if (!candidate.usesHorizontalSegmentPush) continue;
|
|
@@ -5864,7 +5902,8 @@ var selectBestReroutePath = ({
|
|
|
5864
5902
|
tracePath: trace.tracePath,
|
|
5865
5903
|
obstacleLabel,
|
|
5866
5904
|
outputTraces,
|
|
5867
|
-
outputNetLabelPlacements
|
|
5905
|
+
outputNetLabelPlacements,
|
|
5906
|
+
chipObstacles
|
|
5868
5907
|
});
|
|
5869
5908
|
for (const candidate of candidateResults) {
|
|
5870
5909
|
if (candidate.status !== "valid" || !candidate.score) continue;
|
|
@@ -5874,7 +5913,11 @@ var selectBestReroutePath = ({
|
|
|
5874
5913
|
}
|
|
5875
5914
|
return bestPath;
|
|
5876
5915
|
};
|
|
5877
|
-
var generateHorizontalSegmentPushCandidate = (
|
|
5916
|
+
var generateHorizontalSegmentPushCandidate = ({
|
|
5917
|
+
trace,
|
|
5918
|
+
label,
|
|
5919
|
+
outputTraces
|
|
5920
|
+
}) => {
|
|
5878
5921
|
const labelDirection = dir(label.orientation);
|
|
5879
5922
|
if (labelDirection.x === 0) return null;
|
|
5880
5923
|
const path = trace.tracePath;
|
|
@@ -5899,6 +5942,20 @@ var generateHorizontalSegmentPushCandidate = (trace, label) => {
|
|
|
5899
5942
|
if (labelDirection.x > 0) {
|
|
5900
5943
|
segmentPushX = bounds.maxX + LABEL_CLEARANCE;
|
|
5901
5944
|
}
|
|
5945
|
+
const corridorStep = labelDirection.x > 0 ? LABEL_CLEARANCE : -LABEL_CLEARANCE;
|
|
5946
|
+
const verticalLowY = Math.min(verticalStart.y, verticalEnd.y);
|
|
5947
|
+
const verticalHighY = Math.max(verticalStart.y, verticalEnd.y);
|
|
5948
|
+
for (let shift = 0; shift < MAX_CORRIDOR_SHIFTS; shift++) {
|
|
5949
|
+
const occupied = corridorHasOtherNetTrace({
|
|
5950
|
+
x: segmentPushX,
|
|
5951
|
+
lowY: verticalLowY,
|
|
5952
|
+
highY: verticalHighY,
|
|
5953
|
+
outputTraces,
|
|
5954
|
+
netId: trace.globalConnNetId
|
|
5955
|
+
});
|
|
5956
|
+
if (!occupied) break;
|
|
5957
|
+
segmentPushX += corridorStep;
|
|
5958
|
+
}
|
|
5902
5959
|
const segmentPushStartY = getClearedHorizontalY({
|
|
5903
5960
|
start: previousAnchor,
|
|
5904
5961
|
end: { x: segmentPushX, y: verticalStart.y },
|
|
@@ -5950,7 +6007,8 @@ var scoreTracePath = ({
|
|
|
5950
6007
|
tracePath,
|
|
5951
6008
|
obstacleLabel,
|
|
5952
6009
|
outputTraces,
|
|
5953
|
-
outputNetLabelPlacements
|
|
6010
|
+
outputNetLabelPlacements,
|
|
6011
|
+
chipObstacles
|
|
5954
6012
|
}) => {
|
|
5955
6013
|
const candidateTrace = { ...trace, tracePath };
|
|
5956
6014
|
return {
|
|
@@ -5960,6 +6018,7 @@ var scoreTracePath = ({
|
|
|
5960
6018
|
}).length,
|
|
5961
6019
|
labelHugDistance: getLabelHugDistance(tracePath, obstacleLabel),
|
|
5962
6020
|
traceIntersections: countTraceIntersections(candidateTrace, outputTraces),
|
|
6021
|
+
chipBoundaryOverlap: getChipBoundaryOverlap(tracePath, chipObstacles),
|
|
5963
6022
|
pathLength: getPathLength(tracePath)
|
|
5964
6023
|
};
|
|
5965
6024
|
};
|
|
@@ -5972,6 +6031,14 @@ var countTraceIntersections = (trace, outputTraces) => {
|
|
|
5972
6031
|
return count;
|
|
5973
6032
|
};
|
|
5974
6033
|
var isBetterScore = (score, bestScore) => {
|
|
6034
|
+
const scoreHasLabelOverlap = score.labelIntersections > 0;
|
|
6035
|
+
const bestHasLabelOverlap = bestScore.labelIntersections > 0;
|
|
6036
|
+
if (scoreHasLabelOverlap !== bestHasLabelOverlap) {
|
|
6037
|
+
return !scoreHasLabelOverlap;
|
|
6038
|
+
}
|
|
6039
|
+
if (score.chipBoundaryOverlap !== bestScore.chipBoundaryOverlap) {
|
|
6040
|
+
return score.chipBoundaryOverlap < bestScore.chipBoundaryOverlap;
|
|
6041
|
+
}
|
|
5975
6042
|
if (score.labelIntersections !== bestScore.labelIntersections) {
|
|
5976
6043
|
return score.labelIntersections < bestScore.labelIntersections;
|
|
5977
6044
|
}
|
|
@@ -20,6 +20,24 @@ export const doesPathRunAlongChipBoundary = (
|
|
|
20
20
|
return false
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** Total length of the path that runs along (overlapping) a chip boundary. */
|
|
24
|
+
export const getChipBoundaryOverlap = (
|
|
25
|
+
path: Point[],
|
|
26
|
+
chipObstacles: ChipObstacle[],
|
|
27
|
+
) => {
|
|
28
|
+
let total = 0
|
|
29
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
30
|
+
const start = path[i]!
|
|
31
|
+
const end = path[i + 1]!
|
|
32
|
+
for (const obstacle of chipObstacles) {
|
|
33
|
+
if (!segmentRunsAlongRectBoundary(start, end, obstacle)) continue
|
|
34
|
+
const overlap = getSegmentOverlapWithRectSpan(start, end, obstacle)
|
|
35
|
+
if (overlap > EPS) total += overlap
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return total
|
|
39
|
+
}
|
|
40
|
+
|
|
23
41
|
const getSegmentOverlapWithRectSpan = (
|
|
24
42
|
start: Point,
|
|
25
43
|
end: Point,
|
|
@@ -8,13 +8,13 @@ import { detectTraceLabelOverlap } from "lib/solvers/TraceLabelOverlapAvoidanceS
|
|
|
8
8
|
import { generateRerouteCandidates } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace"
|
|
9
9
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
10
10
|
import { dir } from "lib/utils/dir"
|
|
11
|
+
import { getChipBoundaryOverlap } from "./doesPathRunAlongChipBoundary"
|
|
11
12
|
import {
|
|
12
13
|
countPathIntersections,
|
|
13
14
|
getPathKey,
|
|
14
15
|
getPathLength,
|
|
15
16
|
isPathCollidingWithChipInterior,
|
|
16
17
|
} from "./geometry"
|
|
17
|
-
import { doesPathRunAlongChipBoundary } from "./doesPathRunAlongChipBoundary"
|
|
18
18
|
import type {
|
|
19
19
|
ChipObstacle,
|
|
20
20
|
RerouteCandidateResult,
|
|
@@ -22,6 +22,43 @@ import type {
|
|
|
22
22
|
} from "./types"
|
|
23
23
|
|
|
24
24
|
const LABEL_CLEARANCE = 0.1
|
|
25
|
+
// How many LABEL_CLEARANCE-wide corridors to try before giving up on finding a
|
|
26
|
+
// free one for a pushed segment.
|
|
27
|
+
const MAX_CORRIDOR_SHIFTS = 16
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Whether a vertical trace segment of a different net already runs along the
|
|
31
|
+
* corridor at `x`, overlapping the [lowY, highY] span (within a corridor width).
|
|
32
|
+
*/
|
|
33
|
+
const corridorHasOtherNetTrace = ({
|
|
34
|
+
x,
|
|
35
|
+
lowY,
|
|
36
|
+
highY,
|
|
37
|
+
outputTraces,
|
|
38
|
+
netId,
|
|
39
|
+
}: {
|
|
40
|
+
x: number
|
|
41
|
+
lowY: number
|
|
42
|
+
highY: number
|
|
43
|
+
outputTraces: SolvedTracePath[]
|
|
44
|
+
netId: string
|
|
45
|
+
}): boolean => {
|
|
46
|
+
for (const other of outputTraces) {
|
|
47
|
+
if (other.globalConnNetId === netId) continue
|
|
48
|
+
const path = other.tracePath
|
|
49
|
+
for (let i = 0; i + 1 < path.length; i++) {
|
|
50
|
+
const start = path[i]!
|
|
51
|
+
const end = path[i + 1]!
|
|
52
|
+
if (Math.abs(start.x - end.x) >= 1e-9) continue
|
|
53
|
+
if (Math.abs(start.x - x) >= LABEL_CLEARANCE / 2) continue
|
|
54
|
+
const segLowY = Math.min(start.y, end.y)
|
|
55
|
+
const segHighY = Math.max(start.y, end.y)
|
|
56
|
+
if (Math.min(highY, segHighY) - Math.max(lowY, segLowY) > 1e-6)
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
25
62
|
|
|
26
63
|
export const findBestReroutePath = ({
|
|
27
64
|
trace,
|
|
@@ -52,6 +89,7 @@ export const findBestReroutePath = ({
|
|
|
52
89
|
outputTraces,
|
|
53
90
|
outputNetLabelPlacements,
|
|
54
91
|
candidateResults,
|
|
92
|
+
chipObstacles,
|
|
55
93
|
})
|
|
56
94
|
|
|
57
95
|
markSelectedCandidate(candidateResults, bestPath)
|
|
@@ -77,8 +115,11 @@ export const generateRerouteCandidateResults = ({
|
|
|
77
115
|
const seen = new Set<string>()
|
|
78
116
|
const candidateResults: RerouteCandidateResult[] = []
|
|
79
117
|
const horizontalSegmentPushCandidate = generateHorizontalSegmentPushCandidate(
|
|
80
|
-
|
|
81
|
-
|
|
118
|
+
{
|
|
119
|
+
trace,
|
|
120
|
+
label,
|
|
121
|
+
outputTraces,
|
|
122
|
+
},
|
|
82
123
|
)
|
|
83
124
|
|
|
84
125
|
if (horizontalSegmentPushCandidate) {
|
|
@@ -166,15 +207,11 @@ const createCandidateResult = ({
|
|
|
166
207
|
}
|
|
167
208
|
}
|
|
168
209
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
selected: false,
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
210
|
+
// Note: a path that merely runs along a chip boundary is NOT rejected here.
|
|
211
|
+
// It is a valid route, just a lower-quality one — scoreTracePath penalizes it
|
|
212
|
+
// via chipBoundaryOverlap so it loses only to routes that are otherwise as
|
|
213
|
+
// good. Hard-rejecting it discarded the best available route when every
|
|
214
|
+
// alternative overlapped a label or another trace.
|
|
178
215
|
return {
|
|
179
216
|
path,
|
|
180
217
|
score: scoreTracePath({
|
|
@@ -183,6 +220,7 @@ const createCandidateResult = ({
|
|
|
183
220
|
obstacleLabel,
|
|
184
221
|
outputTraces,
|
|
185
222
|
outputNetLabelPlacements,
|
|
223
|
+
chipObstacles,
|
|
186
224
|
}),
|
|
187
225
|
status: "valid",
|
|
188
226
|
usesHorizontalSegmentPush,
|
|
@@ -293,12 +331,14 @@ const selectBestReroutePath = ({
|
|
|
293
331
|
outputTraces,
|
|
294
332
|
outputNetLabelPlacements,
|
|
295
333
|
candidateResults,
|
|
334
|
+
chipObstacles,
|
|
296
335
|
}: {
|
|
297
336
|
trace: SolvedTracePath
|
|
298
337
|
obstacleLabel: NetLabelPlacement
|
|
299
338
|
outputTraces: SolvedTracePath[]
|
|
300
339
|
outputNetLabelPlacements: NetLabelPlacement[]
|
|
301
340
|
candidateResults: RerouteCandidateResult[]
|
|
341
|
+
chipObstacles: ChipObstacle[]
|
|
302
342
|
}) => {
|
|
303
343
|
for (const candidate of candidateResults) {
|
|
304
344
|
if (!candidate.usesHorizontalSegmentPush) continue
|
|
@@ -314,6 +354,7 @@ const selectBestReroutePath = ({
|
|
|
314
354
|
obstacleLabel,
|
|
315
355
|
outputTraces,
|
|
316
356
|
outputNetLabelPlacements,
|
|
357
|
+
chipObstacles,
|
|
317
358
|
})
|
|
318
359
|
|
|
319
360
|
for (const candidate of candidateResults) {
|
|
@@ -327,10 +368,15 @@ const selectBestReroutePath = ({
|
|
|
327
368
|
return bestPath
|
|
328
369
|
}
|
|
329
370
|
|
|
330
|
-
const generateHorizontalSegmentPushCandidate = (
|
|
331
|
-
trace
|
|
332
|
-
label
|
|
333
|
-
|
|
371
|
+
const generateHorizontalSegmentPushCandidate = ({
|
|
372
|
+
trace,
|
|
373
|
+
label,
|
|
374
|
+
outputTraces,
|
|
375
|
+
}: {
|
|
376
|
+
trace: SolvedTracePath
|
|
377
|
+
label: NetLabelPlacement
|
|
378
|
+
outputTraces: SolvedTracePath[]
|
|
379
|
+
}): Point[] | null => {
|
|
334
380
|
const labelDirection = dir(label.orientation)
|
|
335
381
|
if (labelDirection.x === 0) return null
|
|
336
382
|
|
|
@@ -366,6 +412,25 @@ const generateHorizontalSegmentPushCandidate = (
|
|
|
366
412
|
if (labelDirection.x > 0) {
|
|
367
413
|
segmentPushX = bounds.maxX + LABEL_CLEARANCE
|
|
368
414
|
}
|
|
415
|
+
|
|
416
|
+
// Two traces escaping the same label block would otherwise be pushed to the
|
|
417
|
+
// identical corridor and stack on one line. Slide the corridor further out
|
|
418
|
+
// until it no longer coincides with a different-net trace already routed
|
|
419
|
+
// there, giving each escaping trace its own parallel corridor.
|
|
420
|
+
const corridorStep = labelDirection.x > 0 ? LABEL_CLEARANCE : -LABEL_CLEARANCE
|
|
421
|
+
const verticalLowY = Math.min(verticalStart.y, verticalEnd.y)
|
|
422
|
+
const verticalHighY = Math.max(verticalStart.y, verticalEnd.y)
|
|
423
|
+
for (let shift = 0; shift < MAX_CORRIDOR_SHIFTS; shift++) {
|
|
424
|
+
const occupied = corridorHasOtherNetTrace({
|
|
425
|
+
x: segmentPushX,
|
|
426
|
+
lowY: verticalLowY,
|
|
427
|
+
highY: verticalHighY,
|
|
428
|
+
outputTraces,
|
|
429
|
+
netId: trace.globalConnNetId,
|
|
430
|
+
})
|
|
431
|
+
if (!occupied) break
|
|
432
|
+
segmentPushX += corridorStep
|
|
433
|
+
}
|
|
369
434
|
const segmentPushStartY = getClearedHorizontalY({
|
|
370
435
|
start: previousAnchor,
|
|
371
436
|
end: { x: segmentPushX, y: verticalStart.y },
|
|
@@ -437,12 +502,14 @@ const scoreTracePath = ({
|
|
|
437
502
|
obstacleLabel,
|
|
438
503
|
outputTraces,
|
|
439
504
|
outputNetLabelPlacements,
|
|
505
|
+
chipObstacles,
|
|
440
506
|
}: {
|
|
441
507
|
trace: SolvedTracePath
|
|
442
508
|
tracePath: Point[]
|
|
443
509
|
obstacleLabel: NetLabelPlacement
|
|
444
510
|
outputTraces: SolvedTracePath[]
|
|
445
511
|
outputNetLabelPlacements: NetLabelPlacement[]
|
|
512
|
+
chipObstacles: ChipObstacle[]
|
|
446
513
|
}): TracePathScore => {
|
|
447
514
|
const candidateTrace = { ...trace, tracePath }
|
|
448
515
|
return {
|
|
@@ -452,6 +519,7 @@ const scoreTracePath = ({
|
|
|
452
519
|
}).length,
|
|
453
520
|
labelHugDistance: getLabelHugDistance(tracePath, obstacleLabel),
|
|
454
521
|
traceIntersections: countTraceIntersections(candidateTrace, outputTraces),
|
|
522
|
+
chipBoundaryOverlap: getChipBoundaryOverlap(tracePath, chipObstacles),
|
|
455
523
|
pathLength: getPathLength(tracePath),
|
|
456
524
|
}
|
|
457
525
|
}
|
|
@@ -469,6 +537,21 @@ const countTraceIntersections = (
|
|
|
469
537
|
}
|
|
470
538
|
|
|
471
539
|
const isBetterScore = (score: TracePathScore, bestScore: TracePathScore) => {
|
|
540
|
+
// A route with no label overlap is always preferred over one with any. This
|
|
541
|
+
// is separate from the full overlap count below so that a *clean* route wins
|
|
542
|
+
// even if it hugs a chip boundary, while a route that must overlap a label is
|
|
543
|
+
// still steered away from also hugging a boundary.
|
|
544
|
+
const scoreHasLabelOverlap = score.labelIntersections > 0
|
|
545
|
+
const bestHasLabelOverlap = bestScore.labelIntersections > 0
|
|
546
|
+
if (scoreHasLabelOverlap !== bestHasLabelOverlap) {
|
|
547
|
+
// Reached only when exactly one route overlaps a label; the clean one wins.
|
|
548
|
+
return !scoreHasLabelOverlap
|
|
549
|
+
}
|
|
550
|
+
// Boundary hugging is only accepted as the price of a clean route; once a
|
|
551
|
+
// label overlap is unavoidable, avoid hugging a boundary on top of it.
|
|
552
|
+
if (score.chipBoundaryOverlap !== bestScore.chipBoundaryOverlap) {
|
|
553
|
+
return score.chipBoundaryOverlap < bestScore.chipBoundaryOverlap
|
|
554
|
+
}
|
|
472
555
|
if (score.labelIntersections !== bestScore.labelIntersections) {
|
|
473
556
|
return score.labelIntersections < bestScore.labelIntersections
|
|
474
557
|
}
|
|
@@ -15,6 +15,13 @@ export type TracePathScore = {
|
|
|
15
15
|
labelIntersections: number
|
|
16
16
|
labelHugDistance: number
|
|
17
17
|
traceIntersections: number
|
|
18
|
+
/**
|
|
19
|
+
* Total length the path runs along a chip boundary. A soft quality penalty
|
|
20
|
+
* (traces hugging a chip edge look bad) that is only preferred against once
|
|
21
|
+
* the more important factors above are equal, so a boundary-hugging route is
|
|
22
|
+
* still chosen over one that actually overlaps a label or another trace.
|
|
23
|
+
*/
|
|
24
|
+
chipBoundaryOverlap: number
|
|
18
25
|
pathLength: number
|
|
19
26
|
}
|
|
20
27
|
|
|
@@ -239,10 +239,6 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
|
|
|
239
239
|
return penalty
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
private pathCost(path: Point[]): number {
|
|
243
|
-
return this.pathLength(path) + this.getPinBandPenalty(path)
|
|
244
|
-
}
|
|
245
|
-
|
|
246
242
|
private isSegmentOutsidePinBand(a: Point, b: Point): boolean {
|
|
247
243
|
if (isHorizontal(a, b)) {
|
|
248
244
|
return a.y <= this.aabb.minY || a.y >= this.aabb.maxY
|
|
@@ -362,11 +358,15 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
|
|
|
362
358
|
candidates.push(...mids)
|
|
363
359
|
}
|
|
364
360
|
|
|
365
|
-
// Generate new shifted paths
|
|
361
|
+
// Generate new shifted paths. Order by path length first, then prefer paths
|
|
362
|
+
// that stay out of the pin band. The pin-band preference is a tiebreaker
|
|
363
|
+
// only: it must never make the solver pick a longer route (a flat additive
|
|
364
|
+
// penalty would, discarding a shorter valid route in favor of a detour).
|
|
366
365
|
const newStates: Array<{
|
|
367
366
|
path: Point[]
|
|
368
367
|
collisionRects: Set<ObstacleRect>
|
|
369
|
-
|
|
368
|
+
length: number
|
|
369
|
+
pinBandPenalty: number
|
|
370
370
|
}> = []
|
|
371
371
|
|
|
372
372
|
const addShiftedCandidate = (
|
|
@@ -386,8 +386,12 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
|
|
|
386
386
|
this.visited.add(key)
|
|
387
387
|
const nextSet = new Set(collisionRects)
|
|
388
388
|
nextSet.add(rect)
|
|
389
|
-
|
|
390
|
-
|
|
389
|
+
newStates.push({
|
|
390
|
+
path: newPath,
|
|
391
|
+
collisionRects: nextSet,
|
|
392
|
+
length: this.pathLength(newPath),
|
|
393
|
+
pinBandPenalty: this.getPinBandPenalty(newPath),
|
|
394
|
+
})
|
|
391
395
|
}
|
|
392
396
|
|
|
393
397
|
for (const coord of candidates) {
|
|
@@ -430,7 +434,9 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
|
|
|
430
434
|
}
|
|
431
435
|
}
|
|
432
436
|
|
|
433
|
-
newStates.sort(
|
|
437
|
+
newStates.sort(
|
|
438
|
+
(a, b) => a.length - b.length || a.pinBandPenalty - b.pinBandPenalty,
|
|
439
|
+
)
|
|
434
440
|
for (const st of newStates) {
|
|
435
441
|
this.queue.push({ path: st.path, collisionRects: st.collisionRects })
|
|
436
442
|
}
|