@tscircuit/schematic-trace-solver 0.0.200 → 0.0.202
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 +105 -7
- 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/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts +6 -2
- package/lib/solvers/SameNetJunctionAlignmentSolver/shortenSameSideRailToLabelAnchor.ts +62 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +1 -0
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260916T054012Z/__snapshots__/bug-report-20260916T054012Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260916T054012Z/bug-report-20260916T054012Z.test.ts +5 -1
- 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-trace-endpoints-stop-at-component-boundary.input.json +82 -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/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
|
@@ -12776,7 +12776,8 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
|
|
|
12776
12776
|
traces: this.traces,
|
|
12777
12777
|
netLabelPlacements: this.netLabelPlacements,
|
|
12778
12778
|
fixedNetLabelPlacements: this.params.fixedNetLabelPlacements,
|
|
12779
|
-
useRenderedLabelBounds: this.params.useRenderedLabelBounds
|
|
12779
|
+
useRenderedLabelBounds: this.params.useRenderedLabelBounds,
|
|
12780
|
+
preserveNonInlineLabelsAgainstInlineEligibleCollisions: this.params.preserveNonInlineLabelsAgainstInlineEligibleCollisions
|
|
12780
12781
|
};
|
|
12781
12782
|
}
|
|
12782
12783
|
getOutput() {
|
|
@@ -12790,6 +12791,36 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
|
|
|
12790
12791
|
collisionKey(a, b) {
|
|
12791
12792
|
return [a.globalConnNetId, b.globalConnNetId].sort().join("::");
|
|
12792
12793
|
}
|
|
12794
|
+
isSingleTerminalInlineEligible(label) {
|
|
12795
|
+
if (!label.netId) return false;
|
|
12796
|
+
return [
|
|
12797
|
+
...this.inputProblem.netConnections,
|
|
12798
|
+
...this.inputProblem.directConnections
|
|
12799
|
+
].some(
|
|
12800
|
+
(connection) => connection.allowInlineNetLabel && connection.pinIds.length === 1 && connection.netId === label.netId && label.pinIds.every((pinId) => connection.pinIds.includes(pinId))
|
|
12801
|
+
);
|
|
12802
|
+
}
|
|
12803
|
+
hasSatisfiedSingleOrientation(label) {
|
|
12804
|
+
if (!label.netId) return false;
|
|
12805
|
+
const orientations = this.inputProblem.availableNetLabelOrientations[label.netId];
|
|
12806
|
+
return orientations?.length === 1 && orientations[0] === label.orientation;
|
|
12807
|
+
}
|
|
12808
|
+
isAlignedWithHostTrace(label) {
|
|
12809
|
+
const orientationAxis = label.orientation.startsWith("x") ? "x" : "y";
|
|
12810
|
+
return label.mspConnectionPairIds.some((mspPairId) => {
|
|
12811
|
+
const trace = this.traceMap[mspPairId];
|
|
12812
|
+
return trace?.tracePath.slice(1).some((end, index) => {
|
|
12813
|
+
const start = trace.tracePath[index];
|
|
12814
|
+
if (!tracePathContainsPoint([start, end], label.anchorPoint))
|
|
12815
|
+
return false;
|
|
12816
|
+
const segmentAxis = Math.abs(start.x - end.x) < SEGMENT_PARALLEL_EPS ? "y" : "x";
|
|
12817
|
+
return segmentAxis === orientationAxis;
|
|
12818
|
+
});
|
|
12819
|
+
});
|
|
12820
|
+
}
|
|
12821
|
+
isProtectedAgainstTemporaryInlineCollision(label) {
|
|
12822
|
+
return !this.isSingleTerminalInlineEligible(label) && this.hasSatisfiedSingleOrientation(label) && this.isAlignedWithHostTrace(label);
|
|
12823
|
+
}
|
|
12793
12824
|
findNextCollidingPair() {
|
|
12794
12825
|
const labels = [
|
|
12795
12826
|
...this.outputNetLabelPlacements,
|
|
@@ -12962,6 +12993,18 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
|
|
|
12962
12993
|
this.labelsToTry = [pair[1], pair[0]].filter(
|
|
12963
12994
|
(label) => !(this.params.fixedNetLabelPlacements ?? []).includes(label)
|
|
12964
12995
|
);
|
|
12996
|
+
if (this.params.preserveNonInlineLabelsAgainstInlineEligibleCollisions && pair.some((label) => this.isSingleTerminalInlineEligible(label)) && pair.some(
|
|
12997
|
+
(label) => this.isProtectedAgainstTemporaryInlineCollision(label)
|
|
12998
|
+
)) {
|
|
12999
|
+
this.labelsToTry = this.labelsToTry.filter(
|
|
13000
|
+
(label) => this.isSingleTerminalInlineEligible(label) || !this.isProtectedAgainstTemporaryInlineCollision(label)
|
|
13001
|
+
);
|
|
13002
|
+
}
|
|
13003
|
+
if (this.labelsToTry.length === 0) {
|
|
13004
|
+
this.skippedCollisionKeys.add(this.collisionKey(pair[0], pair[1]));
|
|
13005
|
+
this.clearActiveSearch();
|
|
13006
|
+
return;
|
|
13007
|
+
}
|
|
12965
13008
|
this.beginSearchForLabel(this.labelsToTry.shift());
|
|
12966
13009
|
return;
|
|
12967
13010
|
}
|
|
@@ -15159,6 +15202,38 @@ var placeGroundRailLabelsAtOuterEnd = ({
|
|
|
15159
15202
|
return output;
|
|
15160
15203
|
};
|
|
15161
15204
|
|
|
15205
|
+
// lib/solvers/SameNetJunctionAlignmentSolver/shortenSameSideRailToLabelAnchor.ts
|
|
15206
|
+
var MAX_LOCAL_RAIL_SHIFT = 0.5;
|
|
15207
|
+
var MIN_ESCAPE_LENGTH = 1;
|
|
15208
|
+
var shortenSameSideRailToLabelAnchor = ({
|
|
15209
|
+
traces,
|
|
15210
|
+
netLabelPlacements
|
|
15211
|
+
}) => traces.map((trace) => {
|
|
15212
|
+
const path = trace.tracePath;
|
|
15213
|
+
const firstPin = path[0];
|
|
15214
|
+
const secondPin = path.at(-1);
|
|
15215
|
+
if (!firstPin || !secondPin || !nearlyEqual(firstPin.x, secondPin.x)) {
|
|
15216
|
+
return trace;
|
|
15217
|
+
}
|
|
15218
|
+
const railIndex = path.findIndex(
|
|
15219
|
+
(point, index) => index > 0 && index < path.length - 2 && isHorizontal2(path[index - 1], point) && isVertical2(point, path[index + 1]) && isHorizontal2(path[index + 1], path[index + 2])
|
|
15220
|
+
);
|
|
15221
|
+
if (railIndex < 0) return trace;
|
|
15222
|
+
const firstRail = path[railIndex];
|
|
15223
|
+
const secondRail = path[railIndex + 1];
|
|
15224
|
+
const escapeLength = Math.abs(firstRail.x - firstPin.x);
|
|
15225
|
+
const label = netLabelPlacements.find(
|
|
15226
|
+
({ anchorPoint, orientation, mspConnectionPairIds }) => mspConnectionPairIds.includes(trace.mspPairId) && (orientation === "y-" && anchorPoint.y < Math.min(firstPin.y, secondPin.y) || orientation === "y+" && anchorPoint.y > Math.max(firstPin.y, secondPin.y)) && anchorPoint.x >= Math.min(firstPin.x, firstRail.x) && anchorPoint.x <= Math.max(firstPin.x, firstRail.x) && Math.abs(anchorPoint.x - firstPin.x) < escapeLength && Math.abs(anchorPoint.x - firstRail.x) <= MAX_LOCAL_RAIL_SHIFT && escapeLength >= MIN_ESCAPE_LENGTH
|
|
15227
|
+
);
|
|
15228
|
+
if (!label) return trace;
|
|
15229
|
+
return {
|
|
15230
|
+
...trace,
|
|
15231
|
+
tracePath: path.map(
|
|
15232
|
+
(point, index) => index === railIndex || index === railIndex + 1 ? { ...point, x: label.anchorPoint.x } : point
|
|
15233
|
+
)
|
|
15234
|
+
};
|
|
15235
|
+
});
|
|
15236
|
+
|
|
15162
15237
|
// lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts
|
|
15163
15238
|
var SameNetJunctionAlignmentSolver = class extends BaseSolver {
|
|
15164
15239
|
input;
|
|
@@ -15172,10 +15247,13 @@ var SameNetJunctionAlignmentSolver = class extends BaseSolver {
|
|
|
15172
15247
|
}
|
|
15173
15248
|
_step() {
|
|
15174
15249
|
const alignment = alignSameNetJunctions(this.input);
|
|
15175
|
-
this.outputTraces =
|
|
15250
|
+
this.outputTraces = shortenSameSideRailToLabelAnchor({
|
|
15251
|
+
traces: alignment.traces,
|
|
15252
|
+
netLabelPlacements: alignment.netLabelPlacements
|
|
15253
|
+
});
|
|
15176
15254
|
this.outputNetLabelPlacements = placeGroundRailLabelsAtOuterEnd({
|
|
15177
15255
|
inputProblem: this.input.inputProblem,
|
|
15178
|
-
traces:
|
|
15256
|
+
traces: this.outputTraces,
|
|
15179
15257
|
netLabelPlacements: alignment.netLabelPlacements
|
|
15180
15258
|
});
|
|
15181
15259
|
this.stats.alignedJunctionCount = alignment.alignedJunctionCount;
|
|
@@ -15593,6 +15671,11 @@ function* getLocalTraceLabelShifts(inputProblem, output, pendingInlinePlacements
|
|
|
15593
15671
|
minY: label.center.y - label.height / 2,
|
|
15594
15672
|
maxY: label.center.y + label.height / 2
|
|
15595
15673
|
}));
|
|
15674
|
+
const pendingReplacementBounds = output.netLabelPlacements.filter(
|
|
15675
|
+
(label) => pendingInlinePlacements.some(
|
|
15676
|
+
(placement) => placement.globalConnNetId === label.globalConnNetId && placement.pinIds.length === label.pinIds.length && placement.pinIds.every((pinId) => label.pinIds.includes(pinId))
|
|
15677
|
+
)
|
|
15678
|
+
).map(getAnchoredNetLabelRenderedBounds);
|
|
15596
15679
|
const pinPositions = [
|
|
15597
15680
|
...inputProblem.chips.flatMap((chip) => chip.pins),
|
|
15598
15681
|
...output.traces.flatMap((trace) => trace.pins)
|
|
@@ -15604,13 +15687,26 @@ function* getLocalTraceLabelShifts(inputProblem, output, pendingInlinePlacements
|
|
|
15604
15687
|
const axis = Math.abs(a.x - b.x) < EPS16 ? "x" : "y";
|
|
15605
15688
|
const along = axis === "x" ? "y" : "x";
|
|
15606
15689
|
if (Math.abs(a[axis] - b[axis]) > EPS16 || same(a, b)) continue;
|
|
15607
|
-
const
|
|
15690
|
+
const candidateObstacles = [
|
|
15608
15691
|
...output.netLabelPlacements.filter((label) => label.globalConnNetId !== trace.globalConnNetId).map(getAnchoredNetLabelRenderedBounds),
|
|
15609
15692
|
...inlineBounds,
|
|
15610
15693
|
...terminalBounds.filter(
|
|
15611
15694
|
({ label }) => !trace.pinIds.some((pinId) => label.pinIds.includes(pinId))
|
|
15612
15695
|
).map(({ bounds }) => bounds)
|
|
15613
|
-
]
|
|
15696
|
+
];
|
|
15697
|
+
const attachedLabelBounds = output.netLabelPlacements.filter(
|
|
15698
|
+
(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)
|
|
15699
|
+
).map(getAnchoredNetLabelRenderedBounds);
|
|
15700
|
+
const blocking = [
|
|
15701
|
+
...candidateObstacles.filter(
|
|
15702
|
+
(bounds) => segmentIntersectsRect(a, b, bounds)
|
|
15703
|
+
),
|
|
15704
|
+
...pendingReplacementBounds.filter(
|
|
15705
|
+
(bounds) => attachedLabelBounds.some(
|
|
15706
|
+
(labelBounds) => boundsOverlap(labelBounds, bounds)
|
|
15707
|
+
)
|
|
15708
|
+
)
|
|
15709
|
+
];
|
|
15614
15710
|
if (!blocking.length) continue;
|
|
15615
15711
|
let low = Math.min(a[along], b[along]);
|
|
15616
15712
|
let high = Math.max(a[along], b[along]);
|
|
@@ -17524,7 +17620,8 @@ var InlineNetLabelSolver = class _InlineNetLabelSolver extends BaseSolver {
|
|
|
17524
17620
|
netLabelPlacements: retainedNetLabelPlacements,
|
|
17525
17621
|
fixedNetLabelPlacements: fixedLabels,
|
|
17526
17622
|
netLabelConnectorTraceIds: this.netLabelConnectorTraceIds,
|
|
17527
|
-
useRenderedLabelBounds: activeInlinePlacements.length > 0
|
|
17623
|
+
useRenderedLabelBounds: activeInlinePlacements.length > 0,
|
|
17624
|
+
preserveNonInlineLabelsAgainstInlineEligibleCollisions: true
|
|
17528
17625
|
});
|
|
17529
17626
|
if (activeInlinePlacements.length > 0) labelCollisionSolver.solve();
|
|
17530
17627
|
const pushed = pushAnchoredNetLabelsAwayFromInlineLabels({
|
|
@@ -18975,7 +19072,8 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
18975
19072
|
{
|
|
18976
19073
|
inputProblem: instance.inputProblem,
|
|
18977
19074
|
traces: simplificationOutput.traces,
|
|
18978
|
-
netLabelPlacements: simplificationOutput.netLabelPlacements
|
|
19075
|
+
netLabelPlacements: simplificationOutput.netLabelPlacements,
|
|
19076
|
+
preserveNonInlineLabelsAgainstInlineEligibleCollisions: true
|
|
18979
19077
|
}
|
|
18980
19078
|
];
|
|
18981
19079
|
}
|
|
@@ -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
|
}
|
|
@@ -8,6 +8,7 @@ import type { InputProblem } from "lib/types/InputProblem"
|
|
|
8
8
|
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
9
9
|
import { alignSameNetJunctions } from "./alignSameNetJunctions"
|
|
10
10
|
import { placeGroundRailLabelsAtOuterEnd } from "./placeGroundRailLabelsAtOuterEnd"
|
|
11
|
+
import { shortenSameSideRailToLabelAnchor } from "./shortenSameSideRailToLabelAnchor"
|
|
11
12
|
|
|
12
13
|
interface SameNetJunctionAlignmentSolverInput {
|
|
13
14
|
inputProblem: InputProblem
|
|
@@ -31,10 +32,13 @@ export class SameNetJunctionAlignmentSolver extends BaseSolver {
|
|
|
31
32
|
|
|
32
33
|
override _step() {
|
|
33
34
|
const alignment = alignSameNetJunctions(this.input)
|
|
34
|
-
this.outputTraces =
|
|
35
|
+
this.outputTraces = shortenSameSideRailToLabelAnchor({
|
|
36
|
+
traces: alignment.traces,
|
|
37
|
+
netLabelPlacements: alignment.netLabelPlacements,
|
|
38
|
+
})
|
|
35
39
|
this.outputNetLabelPlacements = placeGroundRailLabelsAtOuterEnd({
|
|
36
40
|
inputProblem: this.input.inputProblem,
|
|
37
|
-
traces:
|
|
41
|
+
traces: this.outputTraces,
|
|
38
42
|
netLabelPlacements: alignment.netLabelPlacements,
|
|
39
43
|
})
|
|
40
44
|
this.stats.alignedJunctionCount = alignment.alignedJunctionCount
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
2
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
3
|
+
import {
|
|
4
|
+
isHorizontal,
|
|
5
|
+
isVertical,
|
|
6
|
+
nearlyEqual,
|
|
7
|
+
} from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
|
|
8
|
+
|
|
9
|
+
const MAX_LOCAL_RAIL_SHIFT = 0.5
|
|
10
|
+
const MIN_ESCAPE_LENGTH = 1
|
|
11
|
+
|
|
12
|
+
export const shortenSameSideRailToLabelAnchor = ({
|
|
13
|
+
traces,
|
|
14
|
+
netLabelPlacements,
|
|
15
|
+
}: {
|
|
16
|
+
traces: SolvedTracePath[]
|
|
17
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
18
|
+
}) =>
|
|
19
|
+
traces.map((trace) => {
|
|
20
|
+
const path = trace.tracePath
|
|
21
|
+
const firstPin = path[0]
|
|
22
|
+
const secondPin = path.at(-1)
|
|
23
|
+
if (!firstPin || !secondPin || !nearlyEqual(firstPin.x, secondPin.x)) {
|
|
24
|
+
return trace
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const railIndex = path.findIndex(
|
|
28
|
+
(point, index) =>
|
|
29
|
+
index > 0 &&
|
|
30
|
+
index < path.length - 2 &&
|
|
31
|
+
isHorizontal(path[index - 1]!, point) &&
|
|
32
|
+
isVertical(point, path[index + 1]!) &&
|
|
33
|
+
isHorizontal(path[index + 1]!, path[index + 2]!),
|
|
34
|
+
)
|
|
35
|
+
if (railIndex < 0) return trace
|
|
36
|
+
const firstRail = path[railIndex]!
|
|
37
|
+
const secondRail = path[railIndex + 1]!
|
|
38
|
+
const escapeLength = Math.abs(firstRail.x - firstPin.x)
|
|
39
|
+
const label = netLabelPlacements.find(
|
|
40
|
+
({ anchorPoint, orientation, mspConnectionPairIds }) =>
|
|
41
|
+
mspConnectionPairIds.includes(trace.mspPairId) &&
|
|
42
|
+
((orientation === "y-" &&
|
|
43
|
+
anchorPoint.y < Math.min(firstPin.y, secondPin.y)) ||
|
|
44
|
+
(orientation === "y+" &&
|
|
45
|
+
anchorPoint.y > Math.max(firstPin.y, secondPin.y))) &&
|
|
46
|
+
anchorPoint.x >= Math.min(firstPin.x, firstRail.x) &&
|
|
47
|
+
anchorPoint.x <= Math.max(firstPin.x, firstRail.x) &&
|
|
48
|
+
Math.abs(anchorPoint.x - firstPin.x) < escapeLength &&
|
|
49
|
+
Math.abs(anchorPoint.x - firstRail.x) <= MAX_LOCAL_RAIL_SHIFT &&
|
|
50
|
+
escapeLength >= MIN_ESCAPE_LENGTH,
|
|
51
|
+
)
|
|
52
|
+
if (!label) return trace
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
...trace,
|
|
56
|
+
tracePath: path.map((point, index) =>
|
|
57
|
+
index === railIndex || index === railIndex + 1
|
|
58
|
+
? { ...point, x: label.anchorPoint.x }
|
|
59
|
+
: point,
|
|
60
|
+
),
|
|
61
|
+
}
|
|
62
|
+
})
|
|
@@ -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
|
},
|