@tscircuit/schematic-trace-solver 0.0.201 → 0.0.203
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 +104 -6
- package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +1 -0
- package/lib/solvers/InlineNetLabelSolver/getLocalTraceLabelShifts.ts +37 -2
- package/lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts +66 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +1 -0
- package/lib/solvers/TraceCleanupSolver/getInteriorShortcutPaths.ts +41 -0
- package/lib/solvers/TraceCleanupSolver/rerouteGeneratedNetLabelConnectorCrossings.ts +7 -0
- package/package.json +1 -1
- package/tests/repros/__snapshots__/repro-am3352-06-boot.snap.svg +448 -0
- package/tests/repros/__snapshots__/repro-mouse-switch-ground-unnecessary-jog.snap.svg +15 -22
- package/tests/repros/__snapshots__/repro-trace-endpoints-stop-at-component-boundary.snap.svg +94 -0
- package/tests/repros/assets/repro-am3352-06-boot.input.json +1875 -0
- package/tests/repros/assets/repro-trace-endpoints-stop-at-component-boundary.input.json +82 -0
- package/tests/repros/repro-am3352-06-boot.test.ts +43 -0
- package/tests/repros/repro-mouse-switch-ground-unnecessary-jog.test.ts +15 -0
- package/tests/repros/repro-trace-endpoints-stop-at-component-boundary.test.ts +21 -0
- package/tests/solvers/TraceCleanupSolver/reroute-generated-net-label-connector-crossings.test.ts +49 -0
package/dist/index.d.ts
CHANGED
|
@@ -1149,6 +1149,8 @@ type Candidate = {
|
|
|
1149
1149
|
status: CandidateStatus | null;
|
|
1150
1150
|
};
|
|
1151
1151
|
interface NetLabelNetLabelCollisionSolverParams {
|
|
1152
|
+
/** Move temporary port-only inline labels before moving their neighbors. */
|
|
1153
|
+
preserveNonInlineLabelsAgainstInlineEligibleCollisions?: boolean;
|
|
1152
1154
|
/** Use the final horizontal tag envelope after inline conversion. */
|
|
1153
1155
|
useRenderedLabelBounds?: boolean;
|
|
1154
1156
|
/** Already placed labels that are obstacles, but must not be moved. */
|
|
@@ -1181,6 +1183,10 @@ declare class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
1181
1183
|
};
|
|
1182
1184
|
private labelBounds;
|
|
1183
1185
|
private collisionKey;
|
|
1186
|
+
private isSingleTerminalInlineEligible;
|
|
1187
|
+
private hasSatisfiedSingleOrientation;
|
|
1188
|
+
private isAlignedWithHostTrace;
|
|
1189
|
+
private isProtectedAgainstTemporaryInlineCollision;
|
|
1184
1190
|
private findNextCollidingPair;
|
|
1185
1191
|
private netLabelWidthOf;
|
|
1186
1192
|
private netLabelHeightOf;
|
package/dist/index.js
CHANGED
|
@@ -7810,6 +7810,36 @@ var is4PointRectangle = (path) => {
|
|
|
7810
7810
|
return isHVHC || isVHVC;
|
|
7811
7811
|
};
|
|
7812
7812
|
|
|
7813
|
+
// lib/solvers/TraceCleanupSolver/getInteriorShortcutPaths.ts
|
|
7814
|
+
var getInteriorShortcutPaths = (path) => {
|
|
7815
|
+
const candidates = [path];
|
|
7816
|
+
for (let startIndex = 1; startIndex < path.length - 3; startIndex++) {
|
|
7817
|
+
for (let endIndex = startIndex + 2; endIndex < path.length - 1; endIndex++) {
|
|
7818
|
+
for (const connection of tryConnectPoints(
|
|
7819
|
+
path[startIndex],
|
|
7820
|
+
path[endIndex]
|
|
7821
|
+
)) {
|
|
7822
|
+
const candidate = simplifyPath([
|
|
7823
|
+
...path.slice(0, startIndex),
|
|
7824
|
+
...connection,
|
|
7825
|
+
...path.slice(endIndex + 1)
|
|
7826
|
+
]);
|
|
7827
|
+
const preservesTerminalDirections = [
|
|
7828
|
+
[0, 1],
|
|
7829
|
+
[-1, -2]
|
|
7830
|
+
].every(([endIndex2, neighborIndex]) => {
|
|
7831
|
+
const endpoint = path.at(endIndex2);
|
|
7832
|
+
const neighbor = path.at(neighborIndex);
|
|
7833
|
+
const candidateNeighbor = candidate.at(neighborIndex);
|
|
7834
|
+
return (neighbor.x - endpoint.x) * (candidateNeighbor.x - endpoint.x) + (neighbor.y - endpoint.y) * (candidateNeighbor.y - endpoint.y) > 0;
|
|
7835
|
+
});
|
|
7836
|
+
if (preservesTerminalDirections) candidates.push(candidate);
|
|
7837
|
+
}
|
|
7838
|
+
}
|
|
7839
|
+
}
|
|
7840
|
+
return candidates;
|
|
7841
|
+
};
|
|
7842
|
+
|
|
7813
7843
|
// lib/solvers/TraceCleanupSolver/rerouteGeneratedNetLabelConnectorCrossings.ts
|
|
7814
7844
|
var EPS13 = 1e-6;
|
|
7815
7845
|
var getConnectorObstacleBounds = (connector, segmentIndex, labels) => {
|
|
@@ -7902,7 +7932,12 @@ var rerouteGeneratedNetLabelConnectorCrossings = ({
|
|
|
7902
7932
|
obstacleBounds,
|
|
7903
7933
|
chipBounds: [],
|
|
7904
7934
|
clearance
|
|
7905
|
-
}).
|
|
7935
|
+
}).flatMap(
|
|
7936
|
+
(candidate) => getInteriorShortcutPaths(candidate.path).map((path) => ({
|
|
7937
|
+
...candidate,
|
|
7938
|
+
path
|
|
7939
|
+
}))
|
|
7940
|
+
).filter(
|
|
7906
7941
|
(candidate) => getPathLength4(candidate.path) <= getPathLength4(trace.tracePath) + EPS13 && countTurns(candidate.path) <= countTurns(trace.tracePath) + 2 && !isPathCollidingWithObstacles(
|
|
7907
7942
|
candidate.path,
|
|
7908
7943
|
componentAndTextObstacles
|
|
@@ -12776,7 +12811,8 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
|
|
|
12776
12811
|
traces: this.traces,
|
|
12777
12812
|
netLabelPlacements: this.netLabelPlacements,
|
|
12778
12813
|
fixedNetLabelPlacements: this.params.fixedNetLabelPlacements,
|
|
12779
|
-
useRenderedLabelBounds: this.params.useRenderedLabelBounds
|
|
12814
|
+
useRenderedLabelBounds: this.params.useRenderedLabelBounds,
|
|
12815
|
+
preserveNonInlineLabelsAgainstInlineEligibleCollisions: this.params.preserveNonInlineLabelsAgainstInlineEligibleCollisions
|
|
12780
12816
|
};
|
|
12781
12817
|
}
|
|
12782
12818
|
getOutput() {
|
|
@@ -12790,6 +12826,36 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
|
|
|
12790
12826
|
collisionKey(a, b) {
|
|
12791
12827
|
return [a.globalConnNetId, b.globalConnNetId].sort().join("::");
|
|
12792
12828
|
}
|
|
12829
|
+
isSingleTerminalInlineEligible(label) {
|
|
12830
|
+
if (!label.netId) return false;
|
|
12831
|
+
return [
|
|
12832
|
+
...this.inputProblem.netConnections,
|
|
12833
|
+
...this.inputProblem.directConnections
|
|
12834
|
+
].some(
|
|
12835
|
+
(connection) => connection.allowInlineNetLabel && connection.pinIds.length === 1 && connection.netId === label.netId && label.pinIds.every((pinId) => connection.pinIds.includes(pinId))
|
|
12836
|
+
);
|
|
12837
|
+
}
|
|
12838
|
+
hasSatisfiedSingleOrientation(label) {
|
|
12839
|
+
if (!label.netId) return false;
|
|
12840
|
+
const orientations = this.inputProblem.availableNetLabelOrientations[label.netId];
|
|
12841
|
+
return orientations?.length === 1 && orientations[0] === label.orientation;
|
|
12842
|
+
}
|
|
12843
|
+
isAlignedWithHostTrace(label) {
|
|
12844
|
+
const orientationAxis = label.orientation.startsWith("x") ? "x" : "y";
|
|
12845
|
+
return label.mspConnectionPairIds.some((mspPairId) => {
|
|
12846
|
+
const trace = this.traceMap[mspPairId];
|
|
12847
|
+
return trace?.tracePath.slice(1).some((end, index) => {
|
|
12848
|
+
const start = trace.tracePath[index];
|
|
12849
|
+
if (!tracePathContainsPoint([start, end], label.anchorPoint))
|
|
12850
|
+
return false;
|
|
12851
|
+
const segmentAxis = Math.abs(start.x - end.x) < SEGMENT_PARALLEL_EPS ? "y" : "x";
|
|
12852
|
+
return segmentAxis === orientationAxis;
|
|
12853
|
+
});
|
|
12854
|
+
});
|
|
12855
|
+
}
|
|
12856
|
+
isProtectedAgainstTemporaryInlineCollision(label) {
|
|
12857
|
+
return !this.isSingleTerminalInlineEligible(label) && this.hasSatisfiedSingleOrientation(label) && this.isAlignedWithHostTrace(label);
|
|
12858
|
+
}
|
|
12793
12859
|
findNextCollidingPair() {
|
|
12794
12860
|
const labels = [
|
|
12795
12861
|
...this.outputNetLabelPlacements,
|
|
@@ -12962,6 +13028,18 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
|
|
|
12962
13028
|
this.labelsToTry = [pair[1], pair[0]].filter(
|
|
12963
13029
|
(label) => !(this.params.fixedNetLabelPlacements ?? []).includes(label)
|
|
12964
13030
|
);
|
|
13031
|
+
if (this.params.preserveNonInlineLabelsAgainstInlineEligibleCollisions && pair.some((label) => this.isSingleTerminalInlineEligible(label)) && pair.some(
|
|
13032
|
+
(label) => this.isProtectedAgainstTemporaryInlineCollision(label)
|
|
13033
|
+
)) {
|
|
13034
|
+
this.labelsToTry = this.labelsToTry.filter(
|
|
13035
|
+
(label) => this.isSingleTerminalInlineEligible(label) || !this.isProtectedAgainstTemporaryInlineCollision(label)
|
|
13036
|
+
);
|
|
13037
|
+
}
|
|
13038
|
+
if (this.labelsToTry.length === 0) {
|
|
13039
|
+
this.skippedCollisionKeys.add(this.collisionKey(pair[0], pair[1]));
|
|
13040
|
+
this.clearActiveSearch();
|
|
13041
|
+
return;
|
|
13042
|
+
}
|
|
12965
13043
|
this.beginSearchForLabel(this.labelsToTry.shift());
|
|
12966
13044
|
return;
|
|
12967
13045
|
}
|
|
@@ -15628,6 +15706,11 @@ function* getLocalTraceLabelShifts(inputProblem, output, pendingInlinePlacements
|
|
|
15628
15706
|
minY: label.center.y - label.height / 2,
|
|
15629
15707
|
maxY: label.center.y + label.height / 2
|
|
15630
15708
|
}));
|
|
15709
|
+
const pendingReplacementBounds = output.netLabelPlacements.filter(
|
|
15710
|
+
(label) => pendingInlinePlacements.some(
|
|
15711
|
+
(placement) => placement.globalConnNetId === label.globalConnNetId && placement.pinIds.length === label.pinIds.length && placement.pinIds.every((pinId) => label.pinIds.includes(pinId))
|
|
15712
|
+
)
|
|
15713
|
+
).map(getAnchoredNetLabelRenderedBounds);
|
|
15631
15714
|
const pinPositions = [
|
|
15632
15715
|
...inputProblem.chips.flatMap((chip) => chip.pins),
|
|
15633
15716
|
...output.traces.flatMap((trace) => trace.pins)
|
|
@@ -15639,13 +15722,26 @@ function* getLocalTraceLabelShifts(inputProblem, output, pendingInlinePlacements
|
|
|
15639
15722
|
const axis = Math.abs(a.x - b.x) < EPS16 ? "x" : "y";
|
|
15640
15723
|
const along = axis === "x" ? "y" : "x";
|
|
15641
15724
|
if (Math.abs(a[axis] - b[axis]) > EPS16 || same(a, b)) continue;
|
|
15642
|
-
const
|
|
15725
|
+
const candidateObstacles = [
|
|
15643
15726
|
...output.netLabelPlacements.filter((label) => label.globalConnNetId !== trace.globalConnNetId).map(getAnchoredNetLabelRenderedBounds),
|
|
15644
15727
|
...inlineBounds,
|
|
15645
15728
|
...terminalBounds.filter(
|
|
15646
15729
|
({ label }) => !trace.pinIds.some((pinId) => label.pinIds.includes(pinId))
|
|
15647
15730
|
).map(({ bounds }) => bounds)
|
|
15648
|
-
]
|
|
15731
|
+
];
|
|
15732
|
+
const attachedLabelBounds = output.netLabelPlacements.filter(
|
|
15733
|
+
(label) => label.globalConnNetId === trace.globalConnNetId && label.netId != null && inputProblem.availableNetLabelOrientations[label.netId]?.length === 1 && inputProblem.availableNetLabelOrientations[label.netId]?.[0] === label.orientation && (axis === "x" ? label.orientation === "y+" || label.orientation === "y-" : label.orientation === "x+" || label.orientation === "x-") && tracePathContainsPoint([a, b], label.anchorPoint)
|
|
15734
|
+
).map(getAnchoredNetLabelRenderedBounds);
|
|
15735
|
+
const blocking = [
|
|
15736
|
+
...candidateObstacles.filter(
|
|
15737
|
+
(bounds) => segmentIntersectsRect(a, b, bounds)
|
|
15738
|
+
),
|
|
15739
|
+
...pendingReplacementBounds.filter(
|
|
15740
|
+
(bounds) => attachedLabelBounds.some(
|
|
15741
|
+
(labelBounds) => boundsOverlap(labelBounds, bounds)
|
|
15742
|
+
)
|
|
15743
|
+
)
|
|
15744
|
+
];
|
|
15649
15745
|
if (!blocking.length) continue;
|
|
15650
15746
|
let low = Math.min(a[along], b[along]);
|
|
15651
15747
|
let high = Math.max(a[along], b[along]);
|
|
@@ -17559,7 +17655,8 @@ var InlineNetLabelSolver = class _InlineNetLabelSolver extends BaseSolver {
|
|
|
17559
17655
|
netLabelPlacements: retainedNetLabelPlacements,
|
|
17560
17656
|
fixedNetLabelPlacements: fixedLabels,
|
|
17561
17657
|
netLabelConnectorTraceIds: this.netLabelConnectorTraceIds,
|
|
17562
|
-
useRenderedLabelBounds: activeInlinePlacements.length > 0
|
|
17658
|
+
useRenderedLabelBounds: activeInlinePlacements.length > 0,
|
|
17659
|
+
preserveNonInlineLabelsAgainstInlineEligibleCollisions: true
|
|
17563
17660
|
});
|
|
17564
17661
|
if (activeInlinePlacements.length > 0) labelCollisionSolver.solve();
|
|
17565
17662
|
const pushed = pushAnchoredNetLabelsAwayFromInlineLabels({
|
|
@@ -19010,7 +19107,8 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
19010
19107
|
{
|
|
19011
19108
|
inputProblem: instance.inputProblem,
|
|
19012
19109
|
traces: simplificationOutput.traces,
|
|
19013
|
-
netLabelPlacements: simplificationOutput.netLabelPlacements
|
|
19110
|
+
netLabelPlacements: simplificationOutput.netLabelPlacements,
|
|
19111
|
+
preserveNonInlineLabelsAgainstInlineEligibleCollisions: true
|
|
19014
19112
|
}
|
|
19015
19113
|
];
|
|
19016
19114
|
}
|
|
@@ -1195,6 +1195,7 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
1195
1195
|
fixedNetLabelPlacements: fixedLabels,
|
|
1196
1196
|
netLabelConnectorTraceIds: this.netLabelConnectorTraceIds,
|
|
1197
1197
|
useRenderedLabelBounds: activeInlinePlacements.length > 0,
|
|
1198
|
+
preserveNonInlineLabelsAgainstInlineEligibleCollisions: true,
|
|
1198
1199
|
})
|
|
1199
1200
|
if (activeInlinePlacements.length > 0) labelCollisionSolver.solve()
|
|
1200
1201
|
const pushed = pushAnchoredNetLabelsAwayFromInlineLabels({
|
|
@@ -75,6 +75,16 @@ export function* getLocalTraceLabelShifts(
|
|
|
75
75
|
minY: label.center.y - label.height / 2,
|
|
76
76
|
maxY: label.center.y + label.height / 2,
|
|
77
77
|
}))
|
|
78
|
+
const pendingReplacementBounds = output.netLabelPlacements
|
|
79
|
+
.filter((label) =>
|
|
80
|
+
pendingInlinePlacements.some(
|
|
81
|
+
(placement) =>
|
|
82
|
+
placement.globalConnNetId === label.globalConnNetId &&
|
|
83
|
+
placement.pinIds.length === label.pinIds.length &&
|
|
84
|
+
placement.pinIds.every((pinId) => label.pinIds.includes(pinId)),
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
.map(getAnchoredNetLabelRenderedBounds)
|
|
78
88
|
const pinPositions = [
|
|
79
89
|
...inputProblem.chips.flatMap((chip) => chip.pins),
|
|
80
90
|
...output.traces.flatMap((trace) => trace.pins),
|
|
@@ -87,7 +97,7 @@ export function* getLocalTraceLabelShifts(
|
|
|
87
97
|
const axis = Math.abs(a.x - b.x) < EPS ? "x" : "y"
|
|
88
98
|
const along = axis === "x" ? "y" : "x"
|
|
89
99
|
if (Math.abs(a[axis] - b[axis]) > EPS || same(a, b)) continue
|
|
90
|
-
const
|
|
100
|
+
const candidateObstacles = [
|
|
91
101
|
...output.netLabelPlacements
|
|
92
102
|
.filter((label) => label.globalConnNetId !== trace.globalConnNetId)
|
|
93
103
|
.map(getAnchoredNetLabelRenderedBounds),
|
|
@@ -98,7 +108,32 @@ export function* getLocalTraceLabelShifts(
|
|
|
98
108
|
!trace.pinIds.some((pinId) => label.pinIds.includes(pinId)),
|
|
99
109
|
)
|
|
100
110
|
.map(({ bounds }) => bounds),
|
|
101
|
-
]
|
|
111
|
+
]
|
|
112
|
+
const attachedLabelBounds = output.netLabelPlacements
|
|
113
|
+
.filter(
|
|
114
|
+
(label) =>
|
|
115
|
+
label.globalConnNetId === trace.globalConnNetId &&
|
|
116
|
+
label.netId != null &&
|
|
117
|
+
inputProblem.availableNetLabelOrientations[label.netId]?.length ===
|
|
118
|
+
1 &&
|
|
119
|
+
inputProblem.availableNetLabelOrientations[label.netId]?.[0] ===
|
|
120
|
+
label.orientation &&
|
|
121
|
+
(axis === "x"
|
|
122
|
+
? label.orientation === "y+" || label.orientation === "y-"
|
|
123
|
+
: label.orientation === "x+" || label.orientation === "x-") &&
|
|
124
|
+
tracePathContainsPoint([a, b], label.anchorPoint),
|
|
125
|
+
)
|
|
126
|
+
.map(getAnchoredNetLabelRenderedBounds)
|
|
127
|
+
const blocking = [
|
|
128
|
+
...candidateObstacles.filter((bounds) =>
|
|
129
|
+
segmentIntersectsRect(a, b, bounds),
|
|
130
|
+
),
|
|
131
|
+
...pendingReplacementBounds.filter((bounds) =>
|
|
132
|
+
attachedLabelBounds.some((labelBounds) =>
|
|
133
|
+
boundsOverlap(labelBounds, bounds),
|
|
134
|
+
),
|
|
135
|
+
),
|
|
136
|
+
]
|
|
102
137
|
if (!blocking.length) continue
|
|
103
138
|
// Cleanup can split one rail across several trace records, including a
|
|
104
139
|
// branch that starts at a junction instead of at its pin. Shift the
|
|
@@ -93,6 +93,8 @@ function sampleAnchorsAlongSegment(
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
export interface NetLabelNetLabelCollisionSolverParams {
|
|
96
|
+
/** Move temporary port-only inline labels before moving their neighbors. */
|
|
97
|
+
preserveNonInlineLabelsAgainstInlineEligibleCollisions?: boolean
|
|
96
98
|
/** Use the final horizontal tag envelope after inline conversion. */
|
|
97
99
|
useRenderedLabelBounds?: boolean
|
|
98
100
|
/** Already placed labels that are obstacles, but must not be moved. */
|
|
@@ -148,6 +150,8 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
148
150
|
netLabelPlacements: this.netLabelPlacements,
|
|
149
151
|
fixedNetLabelPlacements: this.params.fixedNetLabelPlacements,
|
|
150
152
|
useRenderedLabelBounds: this.params.useRenderedLabelBounds,
|
|
153
|
+
preserveNonInlineLabelsAgainstInlineEligibleCollisions:
|
|
154
|
+
this.params.preserveNonInlineLabelsAgainstInlineEligibleCollisions,
|
|
151
155
|
}
|
|
152
156
|
}
|
|
153
157
|
|
|
@@ -168,6 +172,50 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
168
172
|
return [a.globalConnNetId, b.globalConnNetId].sort().join("::")
|
|
169
173
|
}
|
|
170
174
|
|
|
175
|
+
private isSingleTerminalInlineEligible(label: NetLabelPlacement) {
|
|
176
|
+
if (!label.netId) return false
|
|
177
|
+
return [
|
|
178
|
+
...this.inputProblem.netConnections,
|
|
179
|
+
...this.inputProblem.directConnections,
|
|
180
|
+
].some(
|
|
181
|
+
(connection) =>
|
|
182
|
+
connection.allowInlineNetLabel &&
|
|
183
|
+
connection.pinIds.length === 1 &&
|
|
184
|
+
connection.netId === label.netId &&
|
|
185
|
+
label.pinIds.every((pinId) => connection.pinIds.includes(pinId)),
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private hasSatisfiedSingleOrientation(label: NetLabelPlacement) {
|
|
190
|
+
if (!label.netId) return false
|
|
191
|
+
const orientations =
|
|
192
|
+
this.inputProblem.availableNetLabelOrientations[label.netId]
|
|
193
|
+
return orientations?.length === 1 && orientations[0] === label.orientation
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private isAlignedWithHostTrace(label: NetLabelPlacement) {
|
|
197
|
+
const orientationAxis = label.orientation.startsWith("x") ? "x" : "y"
|
|
198
|
+
return label.mspConnectionPairIds.some((mspPairId) => {
|
|
199
|
+
const trace = this.traceMap[mspPairId]
|
|
200
|
+
return trace?.tracePath.slice(1).some((end, index) => {
|
|
201
|
+
const start = trace.tracePath[index]!
|
|
202
|
+
if (!tracePathContainsPoint([start, end], label.anchorPoint))
|
|
203
|
+
return false
|
|
204
|
+
const segmentAxis =
|
|
205
|
+
Math.abs(start.x - end.x) < SEGMENT_PARALLEL_EPS ? "y" : "x"
|
|
206
|
+
return segmentAxis === orientationAxis
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private isProtectedAgainstTemporaryInlineCollision(label: NetLabelPlacement) {
|
|
212
|
+
return (
|
|
213
|
+
!this.isSingleTerminalInlineEligible(label) &&
|
|
214
|
+
this.hasSatisfiedSingleOrientation(label) &&
|
|
215
|
+
this.isAlignedWithHostTrace(label)
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
171
219
|
private findNextCollidingPair():
|
|
172
220
|
| [NetLabelPlacement, NetLabelPlacement]
|
|
173
221
|
| null {
|
|
@@ -394,6 +442,24 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
|
|
|
394
442
|
this.labelsToTry = [pair[1], pair[0]].filter(
|
|
395
443
|
(label) => !(this.params.fixedNetLabelPlacements ?? []).includes(label),
|
|
396
444
|
)
|
|
445
|
+
if (
|
|
446
|
+
this.params.preserveNonInlineLabelsAgainstInlineEligibleCollisions &&
|
|
447
|
+
pair.some((label) => this.isSingleTerminalInlineEligible(label)) &&
|
|
448
|
+
pair.some((label) =>
|
|
449
|
+
this.isProtectedAgainstTemporaryInlineCollision(label),
|
|
450
|
+
)
|
|
451
|
+
) {
|
|
452
|
+
this.labelsToTry = this.labelsToTry.filter(
|
|
453
|
+
(label) =>
|
|
454
|
+
this.isSingleTerminalInlineEligible(label) ||
|
|
455
|
+
!this.isProtectedAgainstTemporaryInlineCollision(label),
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
if (this.labelsToTry.length === 0) {
|
|
459
|
+
this.skippedCollisionKeys.add(this.collisionKey(pair[0], pair[1]))
|
|
460
|
+
this.clearActiveSearch()
|
|
461
|
+
return
|
|
462
|
+
}
|
|
397
463
|
this.beginSearchForLabel(this.labelsToTry.shift()!)
|
|
398
464
|
return
|
|
399
465
|
}
|
|
@@ -559,6 +559,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
559
559
|
inputProblem: instance.inputProblem,
|
|
560
560
|
traces: simplificationOutput.traces,
|
|
561
561
|
netLabelPlacements: simplificationOutput.netLabelPlacements,
|
|
562
|
+
preserveNonInlineLabelsAgainstInlineEligibleCollisions: true,
|
|
562
563
|
},
|
|
563
564
|
]
|
|
564
565
|
},
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import { simplifyPath } from "./simplifyPath"
|
|
3
|
+
import { tryConnectPoints } from "./tryConnectPoints"
|
|
4
|
+
|
|
5
|
+
export const getInteriorShortcutPaths = (path: Point[]): Point[][] => {
|
|
6
|
+
const candidates: Point[][] = [path]
|
|
7
|
+
// Reconnect interior points to remove old loops left behind by a connector detour.
|
|
8
|
+
for (let startIndex = 1; startIndex < path.length - 3; startIndex++) {
|
|
9
|
+
for (
|
|
10
|
+
let endIndex = startIndex + 2;
|
|
11
|
+
endIndex < path.length - 1;
|
|
12
|
+
endIndex++
|
|
13
|
+
) {
|
|
14
|
+
for (const connection of tryConnectPoints(
|
|
15
|
+
path[startIndex]!,
|
|
16
|
+
path[endIndex]!,
|
|
17
|
+
)) {
|
|
18
|
+
const candidate = simplifyPath([
|
|
19
|
+
...path.slice(0, startIndex),
|
|
20
|
+
...connection,
|
|
21
|
+
...path.slice(endIndex + 1),
|
|
22
|
+
])
|
|
23
|
+
const preservesTerminalDirections = [
|
|
24
|
+
[0, 1],
|
|
25
|
+
[-1, -2],
|
|
26
|
+
].every(([endIndex, neighborIndex]) => {
|
|
27
|
+
const endpoint = path.at(endIndex!)!
|
|
28
|
+
const neighbor = path.at(neighborIndex!)!
|
|
29
|
+
const candidateNeighbor = candidate.at(neighborIndex!)!
|
|
30
|
+
return (
|
|
31
|
+
(neighbor.x - endpoint.x) * (candidateNeighbor.x - endpoint.x) +
|
|
32
|
+
(neighbor.y - endpoint.y) * (candidateNeighbor.y - endpoint.y) >
|
|
33
|
+
0
|
|
34
|
+
)
|
|
35
|
+
})
|
|
36
|
+
if (preservesTerminalDirections) candidates.push(candidate)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return candidates
|
|
41
|
+
}
|
|
@@ -9,6 +9,7 @@ import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/Schemati
|
|
|
9
9
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
10
10
|
import { doesPathCoincideWithTraces } from "lib/utils/doesPathCoincideWithTraces"
|
|
11
11
|
import { countTurns } from "./countTurns"
|
|
12
|
+
import { getInteriorShortcutPaths } from "./getInteriorShortcutPaths"
|
|
12
13
|
import { hasCollisionsWithLabels } from "./hasCollisionsWithLabels"
|
|
13
14
|
import { findPerpendicularPathCrossings } from "./sub-solver/findIntersectionsWithObstacles"
|
|
14
15
|
import { generatePerpendicularTraceDetours } from "./sub-solver/generateLShapeRerouteCandidates"
|
|
@@ -150,6 +151,12 @@ export const rerouteGeneratedNetLabelConnectorCrossings = ({
|
|
|
150
151
|
chipBounds: [],
|
|
151
152
|
clearance,
|
|
152
153
|
})
|
|
154
|
+
.flatMap((candidate) =>
|
|
155
|
+
getInteriorShortcutPaths(candidate.path).map((path) => ({
|
|
156
|
+
...candidate,
|
|
157
|
+
path,
|
|
158
|
+
})),
|
|
159
|
+
)
|
|
153
160
|
.filter(
|
|
154
161
|
(candidate) =>
|
|
155
162
|
getPathLength(candidate.path) <=
|