@tscircuit/schematic-trace-solver 0.0.147 → 0.0.149
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 +3 -1
- package/dist/index.js +196 -44
- package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +101 -2
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +57 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/UntangleTraceSubsolver.ts +17 -1
- package/lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts +52 -0
- package/package.json +1 -1
- package/site/examples/example51.page.tsx +4 -0
- package/tests/assets/example51.json +5102 -0
- package/tests/examples/__snapshots__/example51.snap.svg +367 -0
- package/tests/examples/example51.test.ts +12 -0
- package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +3 -3
- package/tests/repros/__snapshots__/repro-core-555-power-cross-net-overlap.snap.svg +59 -0
- package/tests/repros/__snapshots__/repro-core-tb67s579ftg-inline-label-routing.snap.svg +235 -0
- package/tests/repros/assets/repro-core-tb67s579ftg-inline-label-routing.input.json +900 -0
- package/tests/repros/repro-core-555-power-cross-net-overlap.test.ts +136 -0
- package/tests/repros/repro-core-tb67s579ftg-inline-label-routing.test.ts +40 -0
- package/tests/solvers/InlineNetLabelSolver/__snapshots__/terminal-stub-trace-clearance.snap.svg +53 -0
- package/tests/solvers/InlineNetLabelSolver/terminal-stub-trace-clearance.test.ts +102 -0
package/dist/index.d.ts
CHANGED
|
@@ -1186,6 +1186,8 @@ declare const DEFAULT_INLINE_NET_LABEL_HEIGHT = 0.18;
|
|
|
1186
1186
|
* Gap between the trace and the near edge of the inline label text.
|
|
1187
1187
|
*/
|
|
1188
1188
|
declare const INLINE_NET_LABEL_TRACE_MARGIN = 0.05;
|
|
1189
|
+
/** Clearance between a generated terminal stub and an unrelated trace. */
|
|
1190
|
+
declare const INLINE_NET_LABEL_STUB_TRACE_CLEARANCE = 0.05;
|
|
1189
1191
|
/**
|
|
1190
1192
|
* Largest perpendicular deviation a route may have and still be labeled as one
|
|
1191
1193
|
* span. A route that is straight except for elbow jogs up to this size reads
|
|
@@ -1361,4 +1363,4 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
1361
1363
|
preview(): GraphicsObject;
|
|
1362
1364
|
}
|
|
1363
1365
|
|
|
1364
|
-
export { type ChipId, DEFAULT_INLINE_NET_LABEL_HEIGHT, INLINE_NET_LABEL_MAX_SPAN_JOG, INLINE_NET_LABEL_TRACE_MARGIN, type InlineNetLabelPlacement, InlineNetLabelSolver, type InputChip, type InputDirectConnection, type InputNetConnection, type InputPin, type InputProblem, type NetId, type NetLabelPlacement, type PinId, SchematicTracePipelineSolver, SchematicTraceSingleLineSolver2, type SectionId, type TextBoxes, estimateInlineNetLabelWidth };
|
|
1366
|
+
export { type ChipId, DEFAULT_INLINE_NET_LABEL_HEIGHT, INLINE_NET_LABEL_MAX_SPAN_JOG, INLINE_NET_LABEL_STUB_TRACE_CLEARANCE, INLINE_NET_LABEL_TRACE_MARGIN, type InlineNetLabelPlacement, InlineNetLabelSolver, type InputChip, type InputDirectConnection, type InputNetConnection, type InputPin, type InputProblem, type NetId, type NetLabelPlacement, type PinId, SchematicTracePipelineSolver, SchematicTraceSingleLineSolver2, type SectionId, type TextBoxes, estimateInlineNetLabelWidth };
|
package/dist/index.js
CHANGED
|
@@ -6352,6 +6352,51 @@ var generateLShapeRerouteCandidates = ({
|
|
|
6352
6352
|
}
|
|
6353
6353
|
return [[i1_padded, c2, i2_padded]];
|
|
6354
6354
|
};
|
|
6355
|
+
var generatePerimeterCornerTraceDetours = ({
|
|
6356
|
+
trace,
|
|
6357
|
+
chipBounds,
|
|
6358
|
+
clearance
|
|
6359
|
+
}) => {
|
|
6360
|
+
if (trace.tracePath.length < 4 || chipBounds.length === 0) return [];
|
|
6361
|
+
const start = trace.tracePath[0];
|
|
6362
|
+
const firstEscape = trace.tracePath[1];
|
|
6363
|
+
const lastEscape = trace.tracePath.at(-2);
|
|
6364
|
+
const end = trace.tracePath.at(-1);
|
|
6365
|
+
const minX = Math.min(...chipBounds.map((bounds) => bounds.minX)) - clearance;
|
|
6366
|
+
const maxX = Math.max(...chipBounds.map((bounds) => bounds.maxX)) + clearance;
|
|
6367
|
+
const minY = Math.min(...chipBounds.map((bounds) => bounds.minY)) - clearance;
|
|
6368
|
+
const maxY = Math.max(...chipBounds.map((bounds) => bounds.maxY)) + clearance;
|
|
6369
|
+
const candidates = [];
|
|
6370
|
+
for (const channelX of [minX, maxX]) {
|
|
6371
|
+
for (const channelY of [minY, maxY]) {
|
|
6372
|
+
candidates.push({
|
|
6373
|
+
traceId: trace.mspPairId,
|
|
6374
|
+
path: simplifyPath([
|
|
6375
|
+
start,
|
|
6376
|
+
firstEscape,
|
|
6377
|
+
{ x: firstEscape.x, y: channelY },
|
|
6378
|
+
{ x: channelX, y: channelY },
|
|
6379
|
+
{ x: channelX, y: lastEscape.y },
|
|
6380
|
+
lastEscape,
|
|
6381
|
+
end
|
|
6382
|
+
])
|
|
6383
|
+
});
|
|
6384
|
+
candidates.push({
|
|
6385
|
+
traceId: trace.mspPairId,
|
|
6386
|
+
path: simplifyPath([
|
|
6387
|
+
start,
|
|
6388
|
+
firstEscape,
|
|
6389
|
+
{ x: channelX, y: firstEscape.y },
|
|
6390
|
+
{ x: channelX, y: channelY },
|
|
6391
|
+
{ x: lastEscape.x, y: channelY },
|
|
6392
|
+
lastEscape,
|
|
6393
|
+
end
|
|
6394
|
+
])
|
|
6395
|
+
});
|
|
6396
|
+
}
|
|
6397
|
+
}
|
|
6398
|
+
return candidates;
|
|
6399
|
+
};
|
|
6355
6400
|
var generatePerpendicularTraceDetours = ({
|
|
6356
6401
|
trace,
|
|
6357
6402
|
segmentIndex,
|
|
@@ -6663,7 +6708,8 @@ var UntangleTraceSubsolver = class extends BaseSolver {
|
|
|
6663
6708
|
const otherTrace = traces[secondIndex];
|
|
6664
6709
|
if (trace.globalConnNetId === otherTrace.globalConnNetId) continue;
|
|
6665
6710
|
const isInitialBundleCrossing = this._isTraceBundle(trace, otherTrace);
|
|
6666
|
-
|
|
6711
|
+
const isEligibleInitialCrossing = this.input.eligibleTraceIds?.has(trace.mspPairId) === true || this.input.eligibleTraceIds?.has(otherTrace.mspPairId) === true;
|
|
6712
|
+
if (!isInitialBundleCrossing && !isEligibleInitialCrossing && !this.reroutedTraceIds.has(trace.mspPairId) && !this.reroutedTraceIds.has(otherTrace.mspPairId)) {
|
|
6667
6713
|
continue;
|
|
6668
6714
|
}
|
|
6669
6715
|
const crossings = findPerpendicularPathCrossings(
|
|
@@ -6706,6 +6752,16 @@ var UntangleTraceSubsolver = class extends BaseSolver {
|
|
|
6706
6752
|
obstacleEnd: crossing.trace.tracePath[crossing.segmentIndex + 1],
|
|
6707
6753
|
chipBounds,
|
|
6708
6754
|
clearance: this.input.paddingBuffer
|
|
6755
|
+
}),
|
|
6756
|
+
...generatePerimeterCornerTraceDetours({
|
|
6757
|
+
trace: crossing.trace,
|
|
6758
|
+
chipBounds,
|
|
6759
|
+
clearance: this.input.paddingBuffer
|
|
6760
|
+
}),
|
|
6761
|
+
...generatePerimeterCornerTraceDetours({
|
|
6762
|
+
trace: crossing.otherTrace,
|
|
6763
|
+
chipBounds,
|
|
6764
|
+
clearance: this.input.paddingBuffer
|
|
6709
6765
|
})
|
|
6710
6766
|
];
|
|
6711
6767
|
const chipObstacles = getObstacleRects(this.input.inputProblem).filter(
|
|
@@ -12622,47 +12678,6 @@ var TraceElbowTransitionSimplificationSolver = class extends BaseSolver {
|
|
|
12622
12678
|
}
|
|
12623
12679
|
};
|
|
12624
12680
|
|
|
12625
|
-
// lib/solvers/InlineNetLabelSolver/getAxisAlignedSegments.ts
|
|
12626
|
-
var getAxisAlignedSegments = (path, epsilon = 1e-6) => {
|
|
12627
|
-
const segments = [];
|
|
12628
|
-
let runStart = null;
|
|
12629
|
-
let runAxis = null;
|
|
12630
|
-
let runSign = 0;
|
|
12631
|
-
const closeRun = (runEnd) => {
|
|
12632
|
-
if (runStart && runAxis) {
|
|
12633
|
-
const length = runAxis === "x" ? Math.abs(runEnd.x - runStart.x) : Math.abs(runEnd.y - runStart.y);
|
|
12634
|
-
if (length > epsilon) {
|
|
12635
|
-
segments.push({ start: runStart, end: runEnd, axis: runAxis, length });
|
|
12636
|
-
}
|
|
12637
|
-
}
|
|
12638
|
-
runStart = null;
|
|
12639
|
-
runAxis = null;
|
|
12640
|
-
runSign = 0;
|
|
12641
|
-
};
|
|
12642
|
-
for (let i = 0; i < path.length - 1; i++) {
|
|
12643
|
-
const a = path[i];
|
|
12644
|
-
const b = path[i + 1];
|
|
12645
|
-
const dx = b.x - a.x;
|
|
12646
|
-
const dy = b.y - a.y;
|
|
12647
|
-
if (Math.abs(dx) <= epsilon && Math.abs(dy) <= epsilon) continue;
|
|
12648
|
-
const axis = Math.abs(dy) <= epsilon ? "x" : Math.abs(dx) <= epsilon ? "y" : null;
|
|
12649
|
-
if (!axis) {
|
|
12650
|
-
closeRun(a);
|
|
12651
|
-
continue;
|
|
12652
|
-
}
|
|
12653
|
-
const sign = Math.sign(axis === "x" ? dx : dy);
|
|
12654
|
-
if (runStart && runAxis === axis && runSign === sign) continue;
|
|
12655
|
-
closeRun(a);
|
|
12656
|
-
runStart = a;
|
|
12657
|
-
runAxis = axis;
|
|
12658
|
-
runSign = sign;
|
|
12659
|
-
}
|
|
12660
|
-
if (path.length >= 2) {
|
|
12661
|
-
closeRun(path[path.length - 1]);
|
|
12662
|
-
}
|
|
12663
|
-
return segments.sort((a, b) => b.length - a.length);
|
|
12664
|
-
};
|
|
12665
|
-
|
|
12666
12681
|
// lib/solvers/InlineNetLabelSolver/alignPortOnlyInlineNetLabelStubs.ts
|
|
12667
12682
|
var getStubDirection = (path) => {
|
|
12668
12683
|
const [start, end] = path;
|
|
@@ -12811,6 +12826,47 @@ var alignPortOnlyInlineNetLabelStubs = ({
|
|
|
12811
12826
|
return alignedPlacements;
|
|
12812
12827
|
};
|
|
12813
12828
|
|
|
12829
|
+
// lib/solvers/InlineNetLabelSolver/getAxisAlignedSegments.ts
|
|
12830
|
+
var getAxisAlignedSegments = (path, epsilon = 1e-6) => {
|
|
12831
|
+
const segments = [];
|
|
12832
|
+
let runStart = null;
|
|
12833
|
+
let runAxis = null;
|
|
12834
|
+
let runSign = 0;
|
|
12835
|
+
const closeRun = (runEnd) => {
|
|
12836
|
+
if (runStart && runAxis) {
|
|
12837
|
+
const length = runAxis === "x" ? Math.abs(runEnd.x - runStart.x) : Math.abs(runEnd.y - runStart.y);
|
|
12838
|
+
if (length > epsilon) {
|
|
12839
|
+
segments.push({ start: runStart, end: runEnd, axis: runAxis, length });
|
|
12840
|
+
}
|
|
12841
|
+
}
|
|
12842
|
+
runStart = null;
|
|
12843
|
+
runAxis = null;
|
|
12844
|
+
runSign = 0;
|
|
12845
|
+
};
|
|
12846
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
12847
|
+
const a = path[i];
|
|
12848
|
+
const b = path[i + 1];
|
|
12849
|
+
const dx = b.x - a.x;
|
|
12850
|
+
const dy = b.y - a.y;
|
|
12851
|
+
if (Math.abs(dx) <= epsilon && Math.abs(dy) <= epsilon) continue;
|
|
12852
|
+
const axis = Math.abs(dy) <= epsilon ? "x" : Math.abs(dx) <= epsilon ? "y" : null;
|
|
12853
|
+
if (!axis) {
|
|
12854
|
+
closeRun(a);
|
|
12855
|
+
continue;
|
|
12856
|
+
}
|
|
12857
|
+
const sign = Math.sign(axis === "x" ? dx : dy);
|
|
12858
|
+
if (runStart && runAxis === axis && runSign === sign) continue;
|
|
12859
|
+
closeRun(a);
|
|
12860
|
+
runStart = a;
|
|
12861
|
+
runAxis = axis;
|
|
12862
|
+
runSign = sign;
|
|
12863
|
+
}
|
|
12864
|
+
if (path.length >= 2) {
|
|
12865
|
+
closeRun(path[path.length - 1]);
|
|
12866
|
+
}
|
|
12867
|
+
return segments.sort((a, b) => b.length - a.length);
|
|
12868
|
+
};
|
|
12869
|
+
|
|
12814
12870
|
// lib/solvers/InlineNetLabelSolver/pushAnchoredNetLabelsAwayFromInlineLabels.ts
|
|
12815
12871
|
var LABEL_CLEARANCE2 = 0.05;
|
|
12816
12872
|
var POINT_EPSILON = 1e-6;
|
|
@@ -13173,6 +13229,7 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
13173
13229
|
// lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts
|
|
13174
13230
|
var DEFAULT_INLINE_NET_LABEL_HEIGHT = 0.18;
|
|
13175
13231
|
var INLINE_NET_LABEL_TRACE_MARGIN = 0.05;
|
|
13232
|
+
var INLINE_NET_LABEL_STUB_TRACE_CLEARANCE = 0.05;
|
|
13176
13233
|
var INLINE_NET_LABEL_MAX_SPAN_JOG = 0.4;
|
|
13177
13234
|
var getPinPairKey2 = (pinIds) => [...pinIds].sort().join("::");
|
|
13178
13235
|
var InlineNetLabelSolver = class extends BaseSolver {
|
|
@@ -13273,7 +13330,15 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
13273
13330
|
const stubLength = Math.max(width + 0.2, 0.6);
|
|
13274
13331
|
const start = inputPin ? { x: inputPin.x, y: inputPin.y } : anchoredPlacement.anchorPoint;
|
|
13275
13332
|
const direction = inputPin && inputChip ? inputPin._facingDirection ?? getPinDirection(inputPin, inputChip) : anchoredPlacement.orientation;
|
|
13276
|
-
const
|
|
13333
|
+
const intendedEnd = direction === "x+" ? { x: start.x + stubLength, y: start.y } : direction === "x-" ? { x: start.x - stubLength, y: start.y } : direction === "y+" ? { x: start.x, y: start.y + stubLength } : { x: start.x, y: start.y - stubLength };
|
|
13334
|
+
const end = getCollisionLimitedTerminalStubEnd({
|
|
13335
|
+
start,
|
|
13336
|
+
intendedEnd,
|
|
13337
|
+
minimumLength: width + 2 * INLINE_NET_LABEL_TRACE_MARGIN,
|
|
13338
|
+
traces: this.traces,
|
|
13339
|
+
ownGlobalConnNetId: anchoredPlacement.globalConnNetId
|
|
13340
|
+
});
|
|
13341
|
+
if (!end) return null;
|
|
13277
13342
|
const axis = direction === "x+" || direction === "x-" ? "x" : "y";
|
|
13278
13343
|
const side = axis === "x" ? "y+" : "x-";
|
|
13279
13344
|
const anchorPoint = {
|
|
@@ -13625,6 +13690,55 @@ var doesPathIntersectBounds2 = (path, bounds) => {
|
|
|
13625
13690
|
}
|
|
13626
13691
|
return false;
|
|
13627
13692
|
};
|
|
13693
|
+
var GEOMETRY_EPSILON = 1e-6;
|
|
13694
|
+
var getCollisionLimitedTerminalStubEnd = ({
|
|
13695
|
+
start,
|
|
13696
|
+
intendedEnd,
|
|
13697
|
+
minimumLength,
|
|
13698
|
+
traces,
|
|
13699
|
+
ownGlobalConnNetId
|
|
13700
|
+
}) => {
|
|
13701
|
+
const isHorizontal4 = Math.abs(intendedEnd.x - start.x) >= Math.abs(intendedEnd.y - start.y);
|
|
13702
|
+
const alongAxis = isHorizontal4 ? "x" : "y";
|
|
13703
|
+
const acrossAxis = isHorizontal4 ? "y" : "x";
|
|
13704
|
+
const direction = Math.sign(intendedEnd[alongAxis] - start[alongAxis]) || 1;
|
|
13705
|
+
const intendedLength = Math.abs(intendedEnd[alongAxis] - start[alongAxis]);
|
|
13706
|
+
let availableLength = intendedLength;
|
|
13707
|
+
for (const trace of traces) {
|
|
13708
|
+
if (trace.globalConnNetId === ownGlobalConnNetId) continue;
|
|
13709
|
+
for (let segmentIndex = 0; segmentIndex < trace.tracePath.length - 1; segmentIndex++) {
|
|
13710
|
+
const segmentStart = trace.tracePath[segmentIndex];
|
|
13711
|
+
const segmentEnd = trace.tracePath[segmentIndex + 1];
|
|
13712
|
+
const startAlong = (segmentStart[alongAxis] - start[alongAxis]) * direction;
|
|
13713
|
+
const endAlong = (segmentEnd[alongAxis] - start[alongAxis]) * direction;
|
|
13714
|
+
const startAcross = segmentStart[acrossAxis] - start[acrossAxis];
|
|
13715
|
+
const endAcross = segmentEnd[acrossAxis] - start[acrossAxis];
|
|
13716
|
+
const minAlong = Math.min(startAlong, endAlong);
|
|
13717
|
+
const maxAlong = Math.max(startAlong, endAlong);
|
|
13718
|
+
const minAcross = Math.min(startAcross, endAcross);
|
|
13719
|
+
const maxAcross = Math.max(startAcross, endAcross);
|
|
13720
|
+
let collisionDistance;
|
|
13721
|
+
const isCollinear = Math.abs(startAcross) <= GEOMETRY_EPSILON && Math.abs(endAcross) <= GEOMETRY_EPSILON;
|
|
13722
|
+
if (isCollinear && maxAlong >= -GEOMETRY_EPSILON && minAlong <= intendedLength + GEOMETRY_EPSILON) {
|
|
13723
|
+
collisionDistance = Math.max(0, minAlong);
|
|
13724
|
+
} else {
|
|
13725
|
+
const crossesStubAxis = minAcross <= GEOMETRY_EPSILON && maxAcross >= -GEOMETRY_EPSILON;
|
|
13726
|
+
const perpendicularAlong = (startAlong + endAlong) / 2;
|
|
13727
|
+
if (crossesStubAxis && Math.abs(startAlong - endAlong) <= GEOMETRY_EPSILON && perpendicularAlong >= -GEOMETRY_EPSILON && perpendicularAlong <= intendedLength + GEOMETRY_EPSILON) {
|
|
13728
|
+
collisionDistance = Math.max(0, perpendicularAlong);
|
|
13729
|
+
}
|
|
13730
|
+
}
|
|
13731
|
+
if (collisionDistance !== void 0) {
|
|
13732
|
+
availableLength = Math.min(
|
|
13733
|
+
availableLength,
|
|
13734
|
+
collisionDistance - INLINE_NET_LABEL_STUB_TRACE_CLEARANCE
|
|
13735
|
+
);
|
|
13736
|
+
}
|
|
13737
|
+
}
|
|
13738
|
+
}
|
|
13739
|
+
if (availableLength + GEOMETRY_EPSILON < minimumLength) return null;
|
|
13740
|
+
return isHorizontal4 ? { x: start.x + direction * availableLength, y: start.y } : { x: start.x, y: start.y + direction * availableLength };
|
|
13741
|
+
};
|
|
13628
13742
|
|
|
13629
13743
|
// lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
|
|
13630
13744
|
function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
|
|
@@ -13803,6 +13917,42 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
13803
13917
|
definePipelineStep("traceCleanupSolver", TraceCleanupSolver, (instance) => {
|
|
13804
13918
|
const prevSolverOutput = instance.traceElbowTransitionSimplificationSolver.getOutput();
|
|
13805
13919
|
const traces = prevSolverOutput.traces;
|
|
13920
|
+
const overlapShiftSolver = instance.traceOverlapShiftSolver;
|
|
13921
|
+
const inlineLabelNetIds = new Set(
|
|
13922
|
+
[
|
|
13923
|
+
...instance.inputProblem.directConnections,
|
|
13924
|
+
...instance.inputProblem.netConnections
|
|
13925
|
+
].filter((connection) => connection.allowInlineNetLabel).map((connection) => connection.netId)
|
|
13926
|
+
);
|
|
13927
|
+
const traceIdsInNewCrossingsAfterOverlapShift = /* @__PURE__ */ new Set();
|
|
13928
|
+
for (let traceIndex = 0; traceIndex < overlapShiftSolver.inputTracePaths.length; traceIndex++) {
|
|
13929
|
+
const inputTrace = overlapShiftSolver.inputTracePaths[traceIndex];
|
|
13930
|
+
const shiftedTrace = overlapShiftSolver.correctedTraceMap[inputTrace.mspPairId];
|
|
13931
|
+
for (let otherTraceIndex = traceIndex + 1; otherTraceIndex < overlapShiftSolver.inputTracePaths.length; otherTraceIndex++) {
|
|
13932
|
+
const otherInputTrace = overlapShiftSolver.inputTracePaths[otherTraceIndex];
|
|
13933
|
+
if (inputTrace.globalConnNetId === otherInputTrace.globalConnNetId) {
|
|
13934
|
+
continue;
|
|
13935
|
+
}
|
|
13936
|
+
if (!inlineLabelNetIds.has(inputTrace.userNetId ?? "") && !inlineLabelNetIds.has(otherInputTrace.userNetId ?? "")) {
|
|
13937
|
+
continue;
|
|
13938
|
+
}
|
|
13939
|
+
const otherShiftedTrace = overlapShiftSolver.correctedTraceMap[otherInputTrace.mspPairId];
|
|
13940
|
+
const crossingCountBeforeShift = findPerpendicularPathCrossings(
|
|
13941
|
+
inputTrace.tracePath,
|
|
13942
|
+
otherInputTrace.tracePath
|
|
13943
|
+
).length;
|
|
13944
|
+
const crossingCountAfterShift = findPerpendicularPathCrossings(
|
|
13945
|
+
shiftedTrace.tracePath,
|
|
13946
|
+
otherShiftedTrace.tracePath
|
|
13947
|
+
).length;
|
|
13948
|
+
if (crossingCountAfterShift > crossingCountBeforeShift) {
|
|
13949
|
+
traceIdsInNewCrossingsAfterOverlapShift.add(inputTrace.mspPairId);
|
|
13950
|
+
traceIdsInNewCrossingsAfterOverlapShift.add(
|
|
13951
|
+
otherInputTrace.mspPairId
|
|
13952
|
+
);
|
|
13953
|
+
}
|
|
13954
|
+
}
|
|
13955
|
+
}
|
|
13806
13956
|
const labelMergingOutput = instance.traceLabelOverlapAvoidanceSolver.labelMergingSolver.getOutput();
|
|
13807
13957
|
return [
|
|
13808
13958
|
{
|
|
@@ -13810,7 +13960,8 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
13810
13960
|
allTraces: traces,
|
|
13811
13961
|
allLabelPlacements: labelMergingOutput.netLabelPlacements,
|
|
13812
13962
|
mergedLabelNetIdMap: labelMergingOutput.mergedLabelNetIdMap,
|
|
13813
|
-
paddingBuffer: 0.1
|
|
13963
|
+
paddingBuffer: 0.1,
|
|
13964
|
+
eligibleTraceIds: traceIdsInNewCrossingsAfterOverlapShift
|
|
13814
13965
|
}
|
|
13815
13966
|
];
|
|
13816
13967
|
}),
|
|
@@ -14153,6 +14304,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
14153
14304
|
export {
|
|
14154
14305
|
DEFAULT_INLINE_NET_LABEL_HEIGHT,
|
|
14155
14306
|
INLINE_NET_LABEL_MAX_SPAN_JOG,
|
|
14307
|
+
INLINE_NET_LABEL_STUB_TRACE_CLEARANCE,
|
|
14156
14308
|
INLINE_NET_LABEL_TRACE_MARGIN,
|
|
14157
14309
|
InlineNetLabelSolver,
|
|
14158
14310
|
SchematicTracePipelineSolver,
|
|
@@ -13,11 +13,11 @@ import type {
|
|
|
13
13
|
} from "lib/types/InputProblem"
|
|
14
14
|
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
15
15
|
import { boundsOverlap, getTextBoxBounds } from "lib/utils/textBoxBounds"
|
|
16
|
+
import { alignPortOnlyInlineNetLabelStubs } from "./alignPortOnlyInlineNetLabelStubs"
|
|
16
17
|
import {
|
|
17
18
|
type AxisAlignedSegment,
|
|
18
19
|
getAxisAlignedSegments,
|
|
19
20
|
} from "./getAxisAlignedSegments"
|
|
20
|
-
import { alignPortOnlyInlineNetLabelStubs } from "./alignPortOnlyInlineNetLabelStubs"
|
|
21
21
|
import { pushAnchoredNetLabelsAwayFromInlineLabels } from "./pushAnchoredNetLabelsAwayFromInlineLabels"
|
|
22
22
|
|
|
23
23
|
export const DEFAULT_INLINE_NET_LABEL_HEIGHT = 0.18
|
|
@@ -27,6 +27,9 @@ export const DEFAULT_INLINE_NET_LABEL_HEIGHT = 0.18
|
|
|
27
27
|
*/
|
|
28
28
|
export const INLINE_NET_LABEL_TRACE_MARGIN = 0.05
|
|
29
29
|
|
|
30
|
+
/** Clearance between a generated terminal stub and an unrelated trace. */
|
|
31
|
+
export const INLINE_NET_LABEL_STUB_TRACE_CLEARANCE = 0.05
|
|
32
|
+
|
|
30
33
|
/**
|
|
31
34
|
* Largest perpendicular deviation a route may have and still be labeled as one
|
|
32
35
|
* span. A route that is straight except for elbow jogs up to this size reads
|
|
@@ -248,7 +251,7 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
248
251
|
inputPin && inputChip
|
|
249
252
|
? (inputPin._facingDirection ?? getPinDirection(inputPin, inputChip))
|
|
250
253
|
: anchoredPlacement.orientation
|
|
251
|
-
const
|
|
254
|
+
const intendedEnd: Point =
|
|
252
255
|
direction === "x+"
|
|
253
256
|
? { x: start.x + stubLength, y: start.y }
|
|
254
257
|
: direction === "x-"
|
|
@@ -257,6 +260,15 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
257
260
|
? { x: start.x, y: start.y + stubLength }
|
|
258
261
|
: { x: start.x, y: start.y - stubLength }
|
|
259
262
|
|
|
263
|
+
const end = getCollisionLimitedTerminalStubEnd({
|
|
264
|
+
start,
|
|
265
|
+
intendedEnd,
|
|
266
|
+
minimumLength: width + 2 * INLINE_NET_LABEL_TRACE_MARGIN,
|
|
267
|
+
traces: this.traces,
|
|
268
|
+
ownGlobalConnNetId: anchoredPlacement.globalConnNetId,
|
|
269
|
+
})
|
|
270
|
+
if (!end) return null
|
|
271
|
+
|
|
260
272
|
const axis: InlineNetLabelPlacement["axis"] =
|
|
261
273
|
direction === "x+" || direction === "x-" ? "x" : "y"
|
|
262
274
|
const side: InlineNetLabelPlacement["side"] = axis === "x" ? "y+" : "x-"
|
|
@@ -777,3 +789,90 @@ const doesPathIntersectBounds = (path: Point[], bounds: Bounds): boolean => {
|
|
|
777
789
|
}
|
|
778
790
|
return false
|
|
779
791
|
}
|
|
792
|
+
|
|
793
|
+
const GEOMETRY_EPSILON = 1e-6
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Shortens an axis-aligned terminal stub before the first unrelated trace.
|
|
797
|
+
* Coordinates are schematic-world millimetres with +x right and +y up.
|
|
798
|
+
*/
|
|
799
|
+
const getCollisionLimitedTerminalStubEnd = ({
|
|
800
|
+
start,
|
|
801
|
+
intendedEnd,
|
|
802
|
+
minimumLength,
|
|
803
|
+
traces,
|
|
804
|
+
ownGlobalConnNetId,
|
|
805
|
+
}: {
|
|
806
|
+
start: Point
|
|
807
|
+
intendedEnd: Point
|
|
808
|
+
minimumLength: number
|
|
809
|
+
traces: SolvedTracePath[]
|
|
810
|
+
ownGlobalConnNetId: string
|
|
811
|
+
}): Point | null => {
|
|
812
|
+
const isHorizontal =
|
|
813
|
+
Math.abs(intendedEnd.x - start.x) >= Math.abs(intendedEnd.y - start.y)
|
|
814
|
+
const alongAxis = isHorizontal ? "x" : "y"
|
|
815
|
+
const acrossAxis = isHorizontal ? "y" : "x"
|
|
816
|
+
const direction = Math.sign(intendedEnd[alongAxis] - start[alongAxis]) || 1
|
|
817
|
+
const intendedLength = Math.abs(intendedEnd[alongAxis] - start[alongAxis])
|
|
818
|
+
let availableLength = intendedLength
|
|
819
|
+
|
|
820
|
+
for (const trace of traces) {
|
|
821
|
+
if (trace.globalConnNetId === ownGlobalConnNetId) continue
|
|
822
|
+
|
|
823
|
+
for (
|
|
824
|
+
let segmentIndex = 0;
|
|
825
|
+
segmentIndex < trace.tracePath.length - 1;
|
|
826
|
+
segmentIndex++
|
|
827
|
+
) {
|
|
828
|
+
const segmentStart = trace.tracePath[segmentIndex]!
|
|
829
|
+
const segmentEnd = trace.tracePath[segmentIndex + 1]!
|
|
830
|
+
const startAlong =
|
|
831
|
+
(segmentStart[alongAxis] - start[alongAxis]) * direction
|
|
832
|
+
const endAlong = (segmentEnd[alongAxis] - start[alongAxis]) * direction
|
|
833
|
+
const startAcross = segmentStart[acrossAxis] - start[acrossAxis]
|
|
834
|
+
const endAcross = segmentEnd[acrossAxis] - start[acrossAxis]
|
|
835
|
+
const minAlong = Math.min(startAlong, endAlong)
|
|
836
|
+
const maxAlong = Math.max(startAlong, endAlong)
|
|
837
|
+
const minAcross = Math.min(startAcross, endAcross)
|
|
838
|
+
const maxAcross = Math.max(startAcross, endAcross)
|
|
839
|
+
|
|
840
|
+
let collisionDistance: number | undefined
|
|
841
|
+
const isCollinear =
|
|
842
|
+
Math.abs(startAcross) <= GEOMETRY_EPSILON &&
|
|
843
|
+
Math.abs(endAcross) <= GEOMETRY_EPSILON
|
|
844
|
+
if (
|
|
845
|
+
isCollinear &&
|
|
846
|
+
maxAlong >= -GEOMETRY_EPSILON &&
|
|
847
|
+
minAlong <= intendedLength + GEOMETRY_EPSILON
|
|
848
|
+
) {
|
|
849
|
+
collisionDistance = Math.max(0, minAlong)
|
|
850
|
+
} else {
|
|
851
|
+
const crossesStubAxis =
|
|
852
|
+
minAcross <= GEOMETRY_EPSILON && maxAcross >= -GEOMETRY_EPSILON
|
|
853
|
+
const perpendicularAlong = (startAlong + endAlong) / 2
|
|
854
|
+
if (
|
|
855
|
+
crossesStubAxis &&
|
|
856
|
+
Math.abs(startAlong - endAlong) <= GEOMETRY_EPSILON &&
|
|
857
|
+
perpendicularAlong >= -GEOMETRY_EPSILON &&
|
|
858
|
+
perpendicularAlong <= intendedLength + GEOMETRY_EPSILON
|
|
859
|
+
) {
|
|
860
|
+
collisionDistance = Math.max(0, perpendicularAlong)
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (collisionDistance !== undefined) {
|
|
865
|
+
availableLength = Math.min(
|
|
866
|
+
availableLength,
|
|
867
|
+
collisionDistance - INLINE_NET_LABEL_STUB_TRACE_CLEARANCE,
|
|
868
|
+
)
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
if (availableLength + GEOMETRY_EPSILON < minimumLength) return null
|
|
874
|
+
|
|
875
|
+
return isHorizontal
|
|
876
|
+
? { x: start.x + direction * availableLength, y: start.y }
|
|
877
|
+
: { x: start.x, y: start.y + direction * availableLength }
|
|
878
|
+
}
|
|
@@ -35,6 +35,7 @@ import { UnroutedTraceRecoverySolver } from "../UnroutedTraceRecoverySolver/Unro
|
|
|
35
35
|
import { SameNetJunctionAlignmentSolver } from "../SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver"
|
|
36
36
|
import { TraceElbowTransitionSimplificationSolver } from "../TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver"
|
|
37
37
|
import { InlineNetLabelSolver } from "../InlineNetLabelSolver/InlineNetLabelSolver"
|
|
38
|
+
import { findPerpendicularPathCrossings } from "../TraceCleanupSolver/sub-solver/findIntersectionsWithObstacles"
|
|
38
39
|
|
|
39
40
|
type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
|
|
40
41
|
solverName: string
|
|
@@ -259,6 +260,61 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
259
260
|
const prevSolverOutput =
|
|
260
261
|
instance.traceElbowTransitionSimplificationSolver!.getOutput()
|
|
261
262
|
const traces = prevSolverOutput.traces
|
|
263
|
+
const overlapShiftSolver = instance.traceOverlapShiftSolver!
|
|
264
|
+
const inlineLabelNetIds = new Set(
|
|
265
|
+
[
|
|
266
|
+
...instance.inputProblem.directConnections,
|
|
267
|
+
...instance.inputProblem.netConnections,
|
|
268
|
+
]
|
|
269
|
+
.filter((connection) => connection.allowInlineNetLabel)
|
|
270
|
+
.map((connection) => connection.netId),
|
|
271
|
+
)
|
|
272
|
+
// An overlap shift can separate collinear inline-label traces by creating
|
|
273
|
+
// a new perpendicular crossing. Limit the extra untangle work to those
|
|
274
|
+
// newly introduced crossings so established routes remain stable.
|
|
275
|
+
const traceIdsInNewCrossingsAfterOverlapShift = new Set<string>()
|
|
276
|
+
for (
|
|
277
|
+
let traceIndex = 0;
|
|
278
|
+
traceIndex < overlapShiftSolver.inputTracePaths.length;
|
|
279
|
+
traceIndex++
|
|
280
|
+
) {
|
|
281
|
+
const inputTrace = overlapShiftSolver.inputTracePaths[traceIndex]!
|
|
282
|
+
const shiftedTrace =
|
|
283
|
+
overlapShiftSolver.correctedTraceMap[inputTrace.mspPairId]!
|
|
284
|
+
for (
|
|
285
|
+
let otherTraceIndex = traceIndex + 1;
|
|
286
|
+
otherTraceIndex < overlapShiftSolver.inputTracePaths.length;
|
|
287
|
+
otherTraceIndex++
|
|
288
|
+
) {
|
|
289
|
+
const otherInputTrace =
|
|
290
|
+
overlapShiftSolver.inputTracePaths[otherTraceIndex]!
|
|
291
|
+
if (inputTrace.globalConnNetId === otherInputTrace.globalConnNetId) {
|
|
292
|
+
continue
|
|
293
|
+
}
|
|
294
|
+
if (
|
|
295
|
+
!inlineLabelNetIds.has(inputTrace.userNetId ?? "") &&
|
|
296
|
+
!inlineLabelNetIds.has(otherInputTrace.userNetId ?? "")
|
|
297
|
+
) {
|
|
298
|
+
continue
|
|
299
|
+
}
|
|
300
|
+
const otherShiftedTrace =
|
|
301
|
+
overlapShiftSolver.correctedTraceMap[otherInputTrace.mspPairId]!
|
|
302
|
+
const crossingCountBeforeShift = findPerpendicularPathCrossings(
|
|
303
|
+
inputTrace.tracePath,
|
|
304
|
+
otherInputTrace.tracePath,
|
|
305
|
+
).length
|
|
306
|
+
const crossingCountAfterShift = findPerpendicularPathCrossings(
|
|
307
|
+
shiftedTrace.tracePath,
|
|
308
|
+
otherShiftedTrace.tracePath,
|
|
309
|
+
).length
|
|
310
|
+
if (crossingCountAfterShift > crossingCountBeforeShift) {
|
|
311
|
+
traceIdsInNewCrossingsAfterOverlapShift.add(inputTrace.mspPairId)
|
|
312
|
+
traceIdsInNewCrossingsAfterOverlapShift.add(
|
|
313
|
+
otherInputTrace.mspPairId,
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
262
318
|
|
|
263
319
|
const labelMergingOutput =
|
|
264
320
|
instance.traceLabelOverlapAvoidanceSolver!.labelMergingSolver!.getOutput()
|
|
@@ -270,6 +326,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
270
326
|
allLabelPlacements: labelMergingOutput.netLabelPlacements,
|
|
271
327
|
mergedLabelNetIdMap: labelMergingOutput.mergedLabelNetIdMap,
|
|
272
328
|
paddingBuffer: 0.1,
|
|
329
|
+
eligibleTraceIds: traceIdsInNewCrossingsAfterOverlapShift,
|
|
273
330
|
},
|
|
274
331
|
]
|
|
275
332
|
}),
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "./findIntersectionsWithObstacles"
|
|
13
13
|
import {
|
|
14
14
|
generateLShapeRerouteCandidates,
|
|
15
|
+
generatePerimeterCornerTraceDetours,
|
|
15
16
|
generatePerpendicularTraceDetours,
|
|
16
17
|
} from "./generateLShapeRerouteCandidates"
|
|
17
18
|
import { isPathColliding, type CollisionInfo } from "./isPathColliding"
|
|
@@ -53,6 +54,7 @@ export interface UntangleTraceSubsolverInput {
|
|
|
53
54
|
allLabelPlacements: NetLabelPlacement[]
|
|
54
55
|
mergedLabelNetIdMap: Record<string, Set<string>>
|
|
55
56
|
paddingBuffer: number
|
|
57
|
+
eligibleTraceIds?: ReadonlySet<string>
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
/**
|
|
@@ -127,7 +129,7 @@ export class UntangleTraceSubsolver extends BaseSolver {
|
|
|
127
129
|
|
|
128
130
|
if (this.processingCrossings) {
|
|
129
131
|
// The L-shape pass below only reacts when both arms intersect obstacles.
|
|
130
|
-
// Resolve strict
|
|
132
|
+
// Resolve eligible strict cross-net crossings before entering it.
|
|
131
133
|
const crossing = this._findCrossing()
|
|
132
134
|
if (crossing) {
|
|
133
135
|
if (!this._resolveCrossing(crossing)) {
|
|
@@ -207,8 +209,12 @@ export class UntangleTraceSubsolver extends BaseSolver {
|
|
|
207
209
|
const otherTrace = traces[secondIndex]!
|
|
208
210
|
if (trace.globalConnNetId === otherTrace.globalConnNetId) continue
|
|
209
211
|
const isInitialBundleCrossing = this._isTraceBundle(trace, otherTrace)
|
|
212
|
+
const isEligibleInitialCrossing =
|
|
213
|
+
this.input.eligibleTraceIds?.has(trace.mspPairId) === true ||
|
|
214
|
+
this.input.eligibleTraceIds?.has(otherTrace.mspPairId) === true
|
|
210
215
|
if (
|
|
211
216
|
!isInitialBundleCrossing &&
|
|
217
|
+
!isEligibleInitialCrossing &&
|
|
212
218
|
!this.reroutedTraceIds.has(trace.mspPairId) &&
|
|
213
219
|
!this.reroutedTraceIds.has(otherTrace.mspPairId)
|
|
214
220
|
) {
|
|
@@ -259,6 +265,16 @@ export class UntangleTraceSubsolver extends BaseSolver {
|
|
|
259
265
|
chipBounds,
|
|
260
266
|
clearance: this.input.paddingBuffer,
|
|
261
267
|
}),
|
|
268
|
+
...generatePerimeterCornerTraceDetours({
|
|
269
|
+
trace: crossing.trace,
|
|
270
|
+
chipBounds,
|
|
271
|
+
clearance: this.input.paddingBuffer,
|
|
272
|
+
}),
|
|
273
|
+
...generatePerimeterCornerTraceDetours({
|
|
274
|
+
trace: crossing.otherTrace,
|
|
275
|
+
chipBounds,
|
|
276
|
+
clearance: this.input.paddingBuffer,
|
|
277
|
+
}),
|
|
262
278
|
]
|
|
263
279
|
const chipObstacles = getObstacleRects(this.input.inputProblem).filter(
|
|
264
280
|
(obstacle) => obstacle.kind === "chip",
|
|
@@ -122,6 +122,58 @@ export interface TraceDetourCandidate {
|
|
|
122
122
|
path: Point[]
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
export const generatePerimeterCornerTraceDetours = ({
|
|
126
|
+
trace,
|
|
127
|
+
chipBounds,
|
|
128
|
+
clearance,
|
|
129
|
+
}: Omit<
|
|
130
|
+
PerpendicularTraceDetourInput,
|
|
131
|
+
"segmentIndex" | "obstacleStart" | "obstacleEnd"
|
|
132
|
+
>): TraceDetourCandidate[] => {
|
|
133
|
+
if (trace.tracePath.length < 4 || chipBounds.length === 0) return []
|
|
134
|
+
|
|
135
|
+
const start = trace.tracePath[0]!
|
|
136
|
+
const firstEscape = trace.tracePath[1]!
|
|
137
|
+
const lastEscape = trace.tracePath.at(-2)!
|
|
138
|
+
const end = trace.tracePath.at(-1)!
|
|
139
|
+
const minX = Math.min(...chipBounds.map((bounds) => bounds.minX)) - clearance
|
|
140
|
+
const maxX = Math.max(...chipBounds.map((bounds) => bounds.maxX)) + clearance
|
|
141
|
+
const minY = Math.min(...chipBounds.map((bounds) => bounds.minY)) - clearance
|
|
142
|
+
const maxY = Math.max(...chipBounds.map((bounds) => bounds.maxY)) + clearance
|
|
143
|
+
const candidates: TraceDetourCandidate[] = []
|
|
144
|
+
|
|
145
|
+
for (const channelX of [minX, maxX]) {
|
|
146
|
+
for (const channelY of [minY, maxY]) {
|
|
147
|
+
candidates.push({
|
|
148
|
+
traceId: trace.mspPairId,
|
|
149
|
+
path: simplifyPath([
|
|
150
|
+
start,
|
|
151
|
+
firstEscape,
|
|
152
|
+
{ x: firstEscape.x, y: channelY },
|
|
153
|
+
{ x: channelX, y: channelY },
|
|
154
|
+
{ x: channelX, y: lastEscape.y },
|
|
155
|
+
lastEscape,
|
|
156
|
+
end,
|
|
157
|
+
]),
|
|
158
|
+
})
|
|
159
|
+
candidates.push({
|
|
160
|
+
traceId: trace.mspPairId,
|
|
161
|
+
path: simplifyPath([
|
|
162
|
+
start,
|
|
163
|
+
firstEscape,
|
|
164
|
+
{ x: channelX, y: firstEscape.y },
|
|
165
|
+
{ x: channelX, y: channelY },
|
|
166
|
+
{ x: lastEscape.x, y: channelY },
|
|
167
|
+
lastEscape,
|
|
168
|
+
end,
|
|
169
|
+
]),
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return candidates
|
|
175
|
+
}
|
|
176
|
+
|
|
125
177
|
export const generatePerpendicularTraceDetours = ({
|
|
126
178
|
trace,
|
|
127
179
|
segmentIndex,
|
package/package.json
CHANGED