@tscircuit/core 0.0.1651 → 0.0.1652
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.js +1407 -261
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -17548,108 +17548,6 @@ function buildOutputSimpleRouteJson(params) {
|
|
|
17548
17548
|
};
|
|
17549
17549
|
}
|
|
17550
17550
|
|
|
17551
|
-
// node_modules/@tscircuit/fanout-solver/lib/geometry.ts
|
|
17552
|
-
var EPSILON = 1e-9;
|
|
17553
|
-
function obstacleIsCircular(obstacle) {
|
|
17554
|
-
return obstacle.shape === "circle";
|
|
17555
|
-
}
|
|
17556
|
-
function distance9(a, b) {
|
|
17557
|
-
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
17558
|
-
}
|
|
17559
|
-
function distancePointToSegment(point6, start, end) {
|
|
17560
|
-
const dx = end.x - start.x;
|
|
17561
|
-
const dy = end.y - start.y;
|
|
17562
|
-
const lengthSquared = dx * dx + dy * dy;
|
|
17563
|
-
const rawT = lengthSquared < EPSILON ? 0 : ((point6.x - start.x) * dx + (point6.y - start.y) * dy) / lengthSquared;
|
|
17564
|
-
const t = Math.max(0, Math.min(1, rawT));
|
|
17565
|
-
return Math.hypot(point6.x - (start.x + t * dx), point6.y - (start.y + t * dy));
|
|
17566
|
-
}
|
|
17567
|
-
function cross(origin, a, b) {
|
|
17568
|
-
return (a.x - origin.x) * (b.y - origin.y) - (a.y - origin.y) * (b.x - origin.x);
|
|
17569
|
-
}
|
|
17570
|
-
function segmentsProperlyCross(a, b, c, d) {
|
|
17571
|
-
const d1 = cross(c, d, a);
|
|
17572
|
-
const d2 = cross(c, d, b);
|
|
17573
|
-
const d3 = cross(a, b, c);
|
|
17574
|
-
const d4 = cross(a, b, d);
|
|
17575
|
-
return (d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0);
|
|
17576
|
-
}
|
|
17577
|
-
function distanceSegmentToSegment(firstStart, firstEnd, secondStart, secondEnd) {
|
|
17578
|
-
if (segmentsProperlyCross(firstStart, firstEnd, secondStart, secondEnd)) {
|
|
17579
|
-
return 0;
|
|
17580
|
-
}
|
|
17581
|
-
return Math.min(
|
|
17582
|
-
distancePointToSegment(firstStart, secondStart, secondEnd),
|
|
17583
|
-
distancePointToSegment(firstEnd, secondStart, secondEnd),
|
|
17584
|
-
distancePointToSegment(secondStart, firstStart, firstEnd),
|
|
17585
|
-
distancePointToSegment(secondEnd, firstStart, firstEnd)
|
|
17586
|
-
);
|
|
17587
|
-
}
|
|
17588
|
-
function pointIsInsideObstacle(point6, obstacle, tolerance = EPSILON) {
|
|
17589
|
-
if (obstacleIsCircular(obstacle)) {
|
|
17590
|
-
return distance9(point6, obstacle.center) <= obstacle.width / 2 + tolerance;
|
|
17591
|
-
}
|
|
17592
|
-
return Math.abs(point6.x - obstacle.center.x) <= obstacle.width / 2 + tolerance && Math.abs(point6.y - obstacle.center.y) <= obstacle.height / 2 + tolerance;
|
|
17593
|
-
}
|
|
17594
|
-
function distancePointToObstacle(point6, obstacle) {
|
|
17595
|
-
if (obstacleIsCircular(obstacle)) {
|
|
17596
|
-
return Math.max(0, distance9(point6, obstacle.center) - obstacle.width / 2);
|
|
17597
|
-
}
|
|
17598
|
-
const dx = Math.max(
|
|
17599
|
-
Math.abs(point6.x - obstacle.center.x) - obstacle.width / 2,
|
|
17600
|
-
0
|
|
17601
|
-
);
|
|
17602
|
-
const dy = Math.max(
|
|
17603
|
-
Math.abs(point6.y - obstacle.center.y) - obstacle.height / 2,
|
|
17604
|
-
0
|
|
17605
|
-
);
|
|
17606
|
-
return Math.hypot(dx, dy);
|
|
17607
|
-
}
|
|
17608
|
-
function distanceSegmentToObstacle(segment2, obstacle) {
|
|
17609
|
-
if (obstacleIsCircular(obstacle)) {
|
|
17610
|
-
return Math.max(
|
|
17611
|
-
0,
|
|
17612
|
-
distancePointToSegment(obstacle.center, segment2.start, segment2.end) - obstacle.width / 2
|
|
17613
|
-
);
|
|
17614
|
-
}
|
|
17615
|
-
if (pointIsInsideObstacle(segment2.start, obstacle) || pointIsInsideObstacle(segment2.end, obstacle)) {
|
|
17616
|
-
return 0;
|
|
17617
|
-
}
|
|
17618
|
-
const minX = obstacle.center.x - obstacle.width / 2;
|
|
17619
|
-
const maxX = obstacle.center.x + obstacle.width / 2;
|
|
17620
|
-
const minY = obstacle.center.y - obstacle.height / 2;
|
|
17621
|
-
const maxY = obstacle.center.y + obstacle.height / 2;
|
|
17622
|
-
const corners = [
|
|
17623
|
-
{ x: minX, y: minY },
|
|
17624
|
-
{ x: maxX, y: minY },
|
|
17625
|
-
{ x: maxX, y: maxY },
|
|
17626
|
-
{ x: minX, y: maxY }
|
|
17627
|
-
];
|
|
17628
|
-
let minimumDistance = Number.POSITIVE_INFINITY;
|
|
17629
|
-
for (let index = 0; index < corners.length; index++) {
|
|
17630
|
-
minimumDistance = Math.min(
|
|
17631
|
-
minimumDistance,
|
|
17632
|
-
distanceSegmentToSegment(
|
|
17633
|
-
segment2.start,
|
|
17634
|
-
segment2.end,
|
|
17635
|
-
corners[index],
|
|
17636
|
-
corners[(index + 1) % corners.length]
|
|
17637
|
-
)
|
|
17638
|
-
);
|
|
17639
|
-
}
|
|
17640
|
-
return minimumDistance;
|
|
17641
|
-
}
|
|
17642
|
-
function segmentsAreClear(first, second, clearance) {
|
|
17643
|
-
if (first.layer !== second.layer) return true;
|
|
17644
|
-
const requiredDistance = (first.width + second.width) / 2 + clearance;
|
|
17645
|
-
return distanceSegmentToSegment(
|
|
17646
|
-
first.start,
|
|
17647
|
-
first.end,
|
|
17648
|
-
second.start,
|
|
17649
|
-
second.end
|
|
17650
|
-
) >= requiredDistance - EPSILON;
|
|
17651
|
-
}
|
|
17652
|
-
|
|
17653
17551
|
// node_modules/@tscircuit/fanout-solver/lib/layer-colors.ts
|
|
17654
17552
|
var COPPER_LAYER_COLORS = [
|
|
17655
17553
|
"#ef4444",
|
|
@@ -17703,7 +17601,7 @@ function getLayerSpan(fromLayer, toLayer, layerNames) {
|
|
|
17703
17601
|
return layerNames.slice(firstIndex, lastIndex + 1);
|
|
17704
17602
|
}
|
|
17705
17603
|
function generateLayerAssignments(params) {
|
|
17706
|
-
const { busIds, layers, maxAssignments } = params;
|
|
17604
|
+
const { busIds, layers, layersByBusId, maxAssignments } = params;
|
|
17707
17605
|
if (layers.length === 0) {
|
|
17708
17606
|
throw new Error("FanoutSolver: no escape layers are available");
|
|
17709
17607
|
}
|
|
@@ -17712,7 +17610,21 @@ function generateLayerAssignments(params) {
|
|
|
17712
17610
|
`FanoutSolver: maxLayerCombinations must be positive, received ${maxAssignments}`
|
|
17713
17611
|
);
|
|
17714
17612
|
}
|
|
17715
|
-
const
|
|
17613
|
+
const availableLayersByBus = busIds.map(
|
|
17614
|
+
(busId) => layersByBusId?.[busId] ?? layers
|
|
17615
|
+
);
|
|
17616
|
+
const busWithoutLayersIndex = availableLayersByBus.findIndex(
|
|
17617
|
+
(availableLayers) => availableLayers.length === 0
|
|
17618
|
+
);
|
|
17619
|
+
if (busWithoutLayersIndex >= 0) {
|
|
17620
|
+
throw new Error(
|
|
17621
|
+
`FanoutSolver: no escape layers are available for bus "${busIds[busWithoutLayersIndex]}"`
|
|
17622
|
+
);
|
|
17623
|
+
}
|
|
17624
|
+
const rawCombinationCount = availableLayersByBus.reduce(
|
|
17625
|
+
(count, availableLayers) => count * availableLayers.length,
|
|
17626
|
+
1
|
|
17627
|
+
);
|
|
17716
17628
|
const combinationCount = Math.min(
|
|
17717
17629
|
maxAssignments,
|
|
17718
17630
|
Number.isFinite(rawCombinationCount) ? rawCombinationCount : maxAssignments
|
|
@@ -17722,7 +17634,7 @@ function generateLayerAssignments(params) {
|
|
|
17722
17634
|
function addAssignment(layerIndexes) {
|
|
17723
17635
|
const assignment = {};
|
|
17724
17636
|
for (let busIndex = 0; busIndex < busIds.length; busIndex++) {
|
|
17725
|
-
assignment[busIds[busIndex]] =
|
|
17637
|
+
assignment[busIds[busIndex]] = availableLayersByBus[busIndex][layerIndexes[busIndex]];
|
|
17726
17638
|
}
|
|
17727
17639
|
const key = JSON.stringify(assignment);
|
|
17728
17640
|
if (seenAssignments.has(key)) return;
|
|
@@ -17734,9 +17646,10 @@ function generateLayerAssignments(params) {
|
|
|
17734
17646
|
const layerIndexes = [];
|
|
17735
17647
|
let remaining = ordinal;
|
|
17736
17648
|
for (let busIndex = 0; busIndex < busIds.length; busIndex++) {
|
|
17737
|
-
const
|
|
17738
|
-
|
|
17739
|
-
|
|
17649
|
+
const layerCount = availableLayersByBus[busIndex].length;
|
|
17650
|
+
const digit = remaining % layerCount;
|
|
17651
|
+
remaining = Math.floor(remaining / layerCount);
|
|
17652
|
+
layerIndexes.push((digit + busIndex) % layerCount);
|
|
17740
17653
|
}
|
|
17741
17654
|
addAssignment(layerIndexes);
|
|
17742
17655
|
}
|
|
@@ -17749,33 +17662,148 @@ function generateLayerAssignments(params) {
|
|
|
17749
17662
|
return (mixed ^ mixed >>> 15) >>> 0;
|
|
17750
17663
|
}
|
|
17751
17664
|
const balancedLayerIndexes = busIds.map(
|
|
17752
|
-
(_, busIndex) => busIndex %
|
|
17665
|
+
(_, busIndex) => busIndex % availableLayersByBus[busIndex].length
|
|
17753
17666
|
);
|
|
17754
17667
|
addAssignment(balancedLayerIndexes);
|
|
17755
|
-
|
|
17668
|
+
const maximumAvailableLayerCount = Math.max(
|
|
17669
|
+
...availableLayersByBus.map((availableLayers) => availableLayers.length)
|
|
17670
|
+
);
|
|
17671
|
+
for (let globalShift = 1; globalShift < maximumAvailableLayerCount && assignments.length < combinationCount; globalShift++) {
|
|
17756
17672
|
addAssignment(
|
|
17757
17673
|
balancedLayerIndexes.map(
|
|
17758
|
-
(layerIndex) => (layerIndex + globalShift) %
|
|
17674
|
+
(layerIndex, busIndex) => (layerIndex + globalShift) % availableLayersByBus[busIndex].length
|
|
17759
17675
|
)
|
|
17760
17676
|
);
|
|
17761
17677
|
}
|
|
17762
17678
|
for (let busIndex = 0; busIndex < busIds.length && assignments.length < combinationCount; busIndex++) {
|
|
17763
|
-
for (let shift = 1; shift <
|
|
17679
|
+
for (let shift = 1; shift < availableLayersByBus[busIndex].length && assignments.length < combinationCount; shift++) {
|
|
17764
17680
|
const layerIndexes = [...balancedLayerIndexes];
|
|
17765
|
-
layerIndexes[busIndex] = (balancedLayerIndexes[busIndex] + shift) %
|
|
17681
|
+
layerIndexes[busIndex] = (balancedLayerIndexes[busIndex] + shift) % availableLayersByBus[busIndex].length;
|
|
17766
17682
|
addAssignment(layerIndexes);
|
|
17767
17683
|
}
|
|
17768
17684
|
}
|
|
17769
17685
|
for (let seed = 1; assignments.length < combinationCount && seed < combinationCount * 20; seed++) {
|
|
17770
17686
|
addAssignment(
|
|
17771
17687
|
busIds.map(
|
|
17772
|
-
(_, busIndex) => mix32(seed * 2654435761 + busIndex * 2246822507) %
|
|
17688
|
+
(_, busIndex) => mix32(seed * 2654435761 + busIndex * 2246822507) % availableLayersByBus[busIndex].length
|
|
17773
17689
|
)
|
|
17774
17690
|
);
|
|
17775
17691
|
}
|
|
17776
17692
|
return assignments;
|
|
17777
17693
|
}
|
|
17778
17694
|
|
|
17695
|
+
// node_modules/@tscircuit/fanout-solver/lib/geometry.ts
|
|
17696
|
+
var EPSILON = 1e-9;
|
|
17697
|
+
function obstacleIsCircular(obstacle) {
|
|
17698
|
+
return obstacle.shape === "circle";
|
|
17699
|
+
}
|
|
17700
|
+
function toObstacleLocalPoint(point6, obstacle) {
|
|
17701
|
+
const rotationRadians = -(obstacle.ccwRotationDegrees ?? 0) * Math.PI / 180;
|
|
17702
|
+
const dx = point6.x - obstacle.center.x;
|
|
17703
|
+
const dy = point6.y - obstacle.center.y;
|
|
17704
|
+
return {
|
|
17705
|
+
x: dx * Math.cos(rotationRadians) - dy * Math.sin(rotationRadians),
|
|
17706
|
+
y: dx * Math.sin(rotationRadians) + dy * Math.cos(rotationRadians)
|
|
17707
|
+
};
|
|
17708
|
+
}
|
|
17709
|
+
function distance9(a, b) {
|
|
17710
|
+
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
17711
|
+
}
|
|
17712
|
+
function distancePointToSegment(point6, start, end) {
|
|
17713
|
+
const dx = end.x - start.x;
|
|
17714
|
+
const dy = end.y - start.y;
|
|
17715
|
+
const lengthSquared = dx * dx + dy * dy;
|
|
17716
|
+
const rawT = lengthSquared < EPSILON ? 0 : ((point6.x - start.x) * dx + (point6.y - start.y) * dy) / lengthSquared;
|
|
17717
|
+
const t = Math.max(0, Math.min(1, rawT));
|
|
17718
|
+
return Math.hypot(point6.x - (start.x + t * dx), point6.y - (start.y + t * dy));
|
|
17719
|
+
}
|
|
17720
|
+
function cross(origin, a, b) {
|
|
17721
|
+
return (a.x - origin.x) * (b.y - origin.y) - (a.y - origin.y) * (b.x - origin.x);
|
|
17722
|
+
}
|
|
17723
|
+
function segmentsProperlyCross(a, b, c, d) {
|
|
17724
|
+
const d1 = cross(c, d, a);
|
|
17725
|
+
const d2 = cross(c, d, b);
|
|
17726
|
+
const d3 = cross(a, b, c);
|
|
17727
|
+
const d4 = cross(a, b, d);
|
|
17728
|
+
return (d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0);
|
|
17729
|
+
}
|
|
17730
|
+
function distanceSegmentToSegment(firstStart, firstEnd, secondStart, secondEnd) {
|
|
17731
|
+
if (segmentsProperlyCross(firstStart, firstEnd, secondStart, secondEnd)) {
|
|
17732
|
+
return 0;
|
|
17733
|
+
}
|
|
17734
|
+
return Math.min(
|
|
17735
|
+
distancePointToSegment(firstStart, secondStart, secondEnd),
|
|
17736
|
+
distancePointToSegment(firstEnd, secondStart, secondEnd),
|
|
17737
|
+
distancePointToSegment(secondStart, firstStart, firstEnd),
|
|
17738
|
+
distancePointToSegment(secondEnd, firstStart, firstEnd)
|
|
17739
|
+
);
|
|
17740
|
+
}
|
|
17741
|
+
function pointIsInsideObstacle(point6, obstacle, tolerance = EPSILON) {
|
|
17742
|
+
if (obstacleIsCircular(obstacle)) {
|
|
17743
|
+
return distance9(point6, obstacle.center) <= obstacle.width / 2 + tolerance;
|
|
17744
|
+
}
|
|
17745
|
+
const localPoint = toObstacleLocalPoint(point6, obstacle);
|
|
17746
|
+
return Math.abs(localPoint.x) <= obstacle.width / 2 + tolerance && Math.abs(localPoint.y) <= obstacle.height / 2 + tolerance;
|
|
17747
|
+
}
|
|
17748
|
+
function distancePointToObstacle(point6, obstacle) {
|
|
17749
|
+
if (obstacleIsCircular(obstacle)) {
|
|
17750
|
+
return Math.max(0, distance9(point6, obstacle.center) - obstacle.width / 2);
|
|
17751
|
+
}
|
|
17752
|
+
const localPoint = toObstacleLocalPoint(point6, obstacle);
|
|
17753
|
+
const dx = Math.max(Math.abs(localPoint.x) - obstacle.width / 2, 0);
|
|
17754
|
+
const dy = Math.max(Math.abs(localPoint.y) - obstacle.height / 2, 0);
|
|
17755
|
+
return Math.hypot(dx, dy);
|
|
17756
|
+
}
|
|
17757
|
+
function distanceSegmentToObstacle(segment2, obstacle) {
|
|
17758
|
+
if (obstacleIsCircular(obstacle)) {
|
|
17759
|
+
return Math.max(
|
|
17760
|
+
0,
|
|
17761
|
+
distancePointToSegment(obstacle.center, segment2.start, segment2.end) - obstacle.width / 2
|
|
17762
|
+
);
|
|
17763
|
+
}
|
|
17764
|
+
const localStart = toObstacleLocalPoint(segment2.start, obstacle);
|
|
17765
|
+
const localEnd = toObstacleLocalPoint(segment2.end, obstacle);
|
|
17766
|
+
if (Math.abs(localStart.x) <= obstacle.width / 2 + EPSILON && Math.abs(localStart.y) <= obstacle.height / 2 + EPSILON) {
|
|
17767
|
+
return 0;
|
|
17768
|
+
}
|
|
17769
|
+
if (Math.abs(localEnd.x) <= obstacle.width / 2 + EPSILON && Math.abs(localEnd.y) <= obstacle.height / 2 + EPSILON) {
|
|
17770
|
+
return 0;
|
|
17771
|
+
}
|
|
17772
|
+
const minX = -obstacle.width / 2;
|
|
17773
|
+
const maxX = obstacle.width / 2;
|
|
17774
|
+
const minY = -obstacle.height / 2;
|
|
17775
|
+
const maxY = obstacle.height / 2;
|
|
17776
|
+
const corners = [
|
|
17777
|
+
{ x: minX, y: minY },
|
|
17778
|
+
{ x: maxX, y: minY },
|
|
17779
|
+
{ x: maxX, y: maxY },
|
|
17780
|
+
{ x: minX, y: maxY }
|
|
17781
|
+
];
|
|
17782
|
+
let minimumDistance = Number.POSITIVE_INFINITY;
|
|
17783
|
+
for (let index = 0; index < corners.length; index++) {
|
|
17784
|
+
minimumDistance = Math.min(
|
|
17785
|
+
minimumDistance,
|
|
17786
|
+
distanceSegmentToSegment(
|
|
17787
|
+
localStart,
|
|
17788
|
+
localEnd,
|
|
17789
|
+
corners[index],
|
|
17790
|
+
corners[(index + 1) % corners.length]
|
|
17791
|
+
)
|
|
17792
|
+
);
|
|
17793
|
+
}
|
|
17794
|
+
return minimumDistance;
|
|
17795
|
+
}
|
|
17796
|
+
function segmentsAreClear(first, second, clearance) {
|
|
17797
|
+
if (first.layer !== second.layer) return true;
|
|
17798
|
+
const requiredDistance = (first.width + second.width) / 2 + clearance;
|
|
17799
|
+
return distanceSegmentToSegment(
|
|
17800
|
+
first.start,
|
|
17801
|
+
first.end,
|
|
17802
|
+
second.start,
|
|
17803
|
+
second.end
|
|
17804
|
+
) >= requiredDistance - EPSILON;
|
|
17805
|
+
}
|
|
17806
|
+
|
|
17779
17807
|
// node_modules/@tscircuit/fanout-solver/lib/prepare-buses.ts
|
|
17780
17808
|
var FANOUT_BORDER_TARGETS = /* @__PURE__ */ new Set([
|
|
17781
17809
|
"left",
|
|
@@ -18525,6 +18553,122 @@ function prepareFanoutBuses(srj, options) {
|
|
|
18525
18553
|
return buses;
|
|
18526
18554
|
}
|
|
18527
18555
|
|
|
18556
|
+
// node_modules/@tscircuit/fanout-solver/lib/net-identity.ts
|
|
18557
|
+
var identityCache = /* @__PURE__ */ new WeakMap();
|
|
18558
|
+
function getConnectionNetKey(connection) {
|
|
18559
|
+
return connection.netConnectionName ?? connection.rootConnectionName ?? connection.name;
|
|
18560
|
+
}
|
|
18561
|
+
function addTokenNet(tokenNetKeys, token, netKey) {
|
|
18562
|
+
if (!token) return false;
|
|
18563
|
+
const keys = tokenNetKeys.get(token) ?? /* @__PURE__ */ new Set();
|
|
18564
|
+
const sizeBefore = keys.size;
|
|
18565
|
+
keys.add(netKey);
|
|
18566
|
+
tokenNetKeys.set(token, keys);
|
|
18567
|
+
return keys.size !== sizeBefore;
|
|
18568
|
+
}
|
|
18569
|
+
function getKnownNetKeys(tokenNetKeys, tokens) {
|
|
18570
|
+
const keys = /* @__PURE__ */ new Set();
|
|
18571
|
+
for (const token of tokens) {
|
|
18572
|
+
for (const key of tokenNetKeys.get(token) ?? []) keys.add(key);
|
|
18573
|
+
}
|
|
18574
|
+
return keys;
|
|
18575
|
+
}
|
|
18576
|
+
function createElectricalNetIdentity(srj) {
|
|
18577
|
+
const connectionNetKeys = /* @__PURE__ */ new Map();
|
|
18578
|
+
const tokenNetKeys = /* @__PURE__ */ new Map();
|
|
18579
|
+
for (const connection of srj.connections) {
|
|
18580
|
+
const netKey = getConnectionNetKey(connection);
|
|
18581
|
+
connectionNetKeys.set(connection.name, netKey);
|
|
18582
|
+
addTokenNet(tokenNetKeys, connection.name, netKey);
|
|
18583
|
+
addTokenNet(tokenNetKeys, connection.rootConnectionName, netKey);
|
|
18584
|
+
addTokenNet(tokenNetKeys, connection.netConnectionName, netKey);
|
|
18585
|
+
for (const point6 of connection.pointsToConnect) {
|
|
18586
|
+
addTokenNet(tokenNetKeys, point6.pointId, netKey);
|
|
18587
|
+
addTokenNet(tokenNetKeys, point6.pcb_port_id, netKey);
|
|
18588
|
+
}
|
|
18589
|
+
}
|
|
18590
|
+
for (const trace of srj.traces ?? []) {
|
|
18591
|
+
const netKey = trace.connection_name ? connectionNetKeys.get(trace.connection_name) : void 0;
|
|
18592
|
+
if (!netKey) continue;
|
|
18593
|
+
addTokenNet(tokenNetKeys, trace.pcb_trace_id, netKey);
|
|
18594
|
+
for (const token of trace.connectsTo ?? []) {
|
|
18595
|
+
addTokenNet(tokenNetKeys, token, netKey);
|
|
18596
|
+
}
|
|
18597
|
+
}
|
|
18598
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
18599
|
+
let changed = false;
|
|
18600
|
+
for (const obstacle of srj.obstacles) {
|
|
18601
|
+
const netKeys = getKnownNetKeys(tokenNetKeys, obstacle.connectedTo);
|
|
18602
|
+
if (netKeys.size !== 1) continue;
|
|
18603
|
+
const netKey = [...netKeys][0];
|
|
18604
|
+
for (const token of obstacle.connectedTo) {
|
|
18605
|
+
changed = addTokenNet(tokenNetKeys, token, netKey) || changed;
|
|
18606
|
+
}
|
|
18607
|
+
}
|
|
18608
|
+
if (!changed) break;
|
|
18609
|
+
}
|
|
18610
|
+
const parentByNetKey = /* @__PURE__ */ new Map();
|
|
18611
|
+
const findRoot = (netKey) => {
|
|
18612
|
+
const parent = parentByNetKey.get(netKey) ?? netKey;
|
|
18613
|
+
parentByNetKey.set(netKey, parent);
|
|
18614
|
+
if (parent === netKey) return netKey;
|
|
18615
|
+
const root = findRoot(parent);
|
|
18616
|
+
parentByNetKey.set(netKey, root);
|
|
18617
|
+
return root;
|
|
18618
|
+
};
|
|
18619
|
+
const union = (first, second) => {
|
|
18620
|
+
const firstRoot = findRoot(first);
|
|
18621
|
+
const secondRoot = findRoot(second);
|
|
18622
|
+
if (firstRoot !== secondRoot) parentByNetKey.set(secondRoot, firstRoot);
|
|
18623
|
+
};
|
|
18624
|
+
for (const netKeys of tokenNetKeys.values()) {
|
|
18625
|
+
const [firstNetKey, ...otherNetKeys] = [...netKeys];
|
|
18626
|
+
if (!firstNetKey) continue;
|
|
18627
|
+
for (const otherNetKey of otherNetKeys) union(firstNetKey, otherNetKey);
|
|
18628
|
+
}
|
|
18629
|
+
for (const connectedTokens of [
|
|
18630
|
+
...srj.obstacles.map((obstacle) => obstacle.connectedTo),
|
|
18631
|
+
...(srj.traces ?? []).map((trace) => trace.connectsTo ?? [])
|
|
18632
|
+
]) {
|
|
18633
|
+
const [firstNetKey, ...otherNetKeys] = [
|
|
18634
|
+
...getKnownNetKeys(tokenNetKeys, connectedTokens)
|
|
18635
|
+
];
|
|
18636
|
+
if (!firstNetKey) continue;
|
|
18637
|
+
for (const otherNetKey of otherNetKeys) union(firstNetKey, otherNetKey);
|
|
18638
|
+
}
|
|
18639
|
+
for (const [connectionName, netKey] of connectionNetKeys) {
|
|
18640
|
+
connectionNetKeys.set(connectionName, findRoot(netKey));
|
|
18641
|
+
}
|
|
18642
|
+
for (const [token, netKeys] of tokenNetKeys) {
|
|
18643
|
+
tokenNetKeys.set(
|
|
18644
|
+
token,
|
|
18645
|
+
new Set([...netKeys].map((netKey) => findRoot(netKey)))
|
|
18646
|
+
);
|
|
18647
|
+
}
|
|
18648
|
+
return { connectionNetKeys, tokenNetKeys };
|
|
18649
|
+
}
|
|
18650
|
+
function getElectricalNetIdentity(srj) {
|
|
18651
|
+
const cached = identityCache.get(srj);
|
|
18652
|
+
if (cached) return cached;
|
|
18653
|
+
const identity9 = createElectricalNetIdentity(srj);
|
|
18654
|
+
identityCache.set(srj, identity9);
|
|
18655
|
+
return identity9;
|
|
18656
|
+
}
|
|
18657
|
+
function connectionsShareElectricalNet(srj, firstConnectionName, secondConnectionName) {
|
|
18658
|
+
const identity9 = getElectricalNetIdentity(srj);
|
|
18659
|
+
const firstNet = identity9.connectionNetKeys.get(firstConnectionName);
|
|
18660
|
+
const secondNet = identity9.connectionNetKeys.get(secondConnectionName);
|
|
18661
|
+
return firstNet !== void 0 && firstNet === secondNet;
|
|
18662
|
+
}
|
|
18663
|
+
function obstacleSharesElectricalNet(srj, obstacle, connectionName) {
|
|
18664
|
+
const identity9 = getElectricalNetIdentity(srj);
|
|
18665
|
+
const connectionNet = identity9.connectionNetKeys.get(connectionName);
|
|
18666
|
+
if (!connectionNet) return false;
|
|
18667
|
+
return obstacle.connectedTo.some(
|
|
18668
|
+
(token) => identity9.tokenNetKeys.get(token)?.has(connectionNet)
|
|
18669
|
+
);
|
|
18670
|
+
}
|
|
18671
|
+
|
|
18528
18672
|
// node_modules/@tscircuit/fanout-solver/lib/route-bus.ts
|
|
18529
18673
|
function isHorizontal(direction) {
|
|
18530
18674
|
return direction === "left" || direction === "right";
|
|
@@ -18935,10 +19079,21 @@ function buildPlan(params) {
|
|
|
18935
19079
|
};
|
|
18936
19080
|
}
|
|
18937
19081
|
function segmentIsClearOfObstacles(params) {
|
|
18938
|
-
const {
|
|
19082
|
+
const {
|
|
19083
|
+
segment: segment2,
|
|
19084
|
+
plan,
|
|
19085
|
+
segmentIndex,
|
|
19086
|
+
srj,
|
|
19087
|
+
allowSameNetMerges,
|
|
19088
|
+
obstacles,
|
|
19089
|
+
clearance
|
|
19090
|
+
} = params;
|
|
18939
19091
|
for (const obstacle of obstacles) {
|
|
18940
19092
|
if (!obstacle.layers.includes(segment2.layer)) continue;
|
|
18941
19093
|
if (obstacle.connectedTo.includes(plan.connectionName)) continue;
|
|
19094
|
+
if (allowSameNetMerges && obstacleSharesElectricalNet(srj, obstacle, plan.connectionName)) {
|
|
19095
|
+
continue;
|
|
19096
|
+
}
|
|
18942
19097
|
if (segmentIndex === 0 && obstacle === plan.sourceObstacle && segment2.layer === plan.sourceLayer) {
|
|
18943
19098
|
continue;
|
|
18944
19099
|
}
|
|
@@ -18948,8 +19103,8 @@ function segmentIsClearOfObstacles(params) {
|
|
|
18948
19103
|
}
|
|
18949
19104
|
return true;
|
|
18950
19105
|
}
|
|
18951
|
-
function
|
|
18952
|
-
const { plan,
|
|
19106
|
+
function planIsStaticallyClear(params) {
|
|
19107
|
+
const { plan, srj, sharedBoundary, clearance, allowSameNetMerges } = params;
|
|
18953
19108
|
const routableBounds = getRoutableBounds(srj.bounds, sharedBoundary);
|
|
18954
19109
|
if (!pointIsInsideBounds(plan.exitPoint, routableBounds) || plan.segments.some(
|
|
18955
19110
|
(segment2) => !pointIsInsideBounds(segment2.start, routableBounds) || !pointIsInsideBounds(segment2.end, routableBounds)
|
|
@@ -18961,6 +19116,8 @@ function planIsClear(params) {
|
|
|
18961
19116
|
segment: plan.segments[index],
|
|
18962
19117
|
plan,
|
|
18963
19118
|
segmentIndex: index,
|
|
19119
|
+
srj,
|
|
19120
|
+
allowSameNetMerges,
|
|
18964
19121
|
obstacles: srj.obstacles,
|
|
18965
19122
|
clearance
|
|
18966
19123
|
})) {
|
|
@@ -18972,21 +19129,55 @@ function planIsClear(params) {
|
|
|
18972
19129
|
if (!obstacle.layers.some((layer) => plan.via.spanLayers.includes(layer))) {
|
|
18973
19130
|
continue;
|
|
18974
19131
|
}
|
|
19132
|
+
if (allowSameNetMerges && obstacleSharesElectricalNet(srj, obstacle, plan.connectionName)) {
|
|
19133
|
+
continue;
|
|
19134
|
+
}
|
|
18975
19135
|
if (distancePointToObstacle(plan.via.center, obstacle) < plan.via.diameter / 2 + clearance - 1e-9) {
|
|
18976
19136
|
return false;
|
|
18977
19137
|
}
|
|
18978
19138
|
}
|
|
18979
19139
|
}
|
|
19140
|
+
return true;
|
|
19141
|
+
}
|
|
19142
|
+
function planIsClearOfPlans(params) {
|
|
19143
|
+
const {
|
|
19144
|
+
plan,
|
|
19145
|
+
otherPlans,
|
|
19146
|
+
srj,
|
|
19147
|
+
allowSameNetMerges,
|
|
19148
|
+
clearance,
|
|
19149
|
+
blockingBusCounts
|
|
19150
|
+
} = params;
|
|
18980
19151
|
for (const otherPlan of otherPlans) {
|
|
19152
|
+
if (allowSameNetMerges && connectionsShareElectricalNet(
|
|
19153
|
+
srj,
|
|
19154
|
+
plan.connectionName,
|
|
19155
|
+
otherPlan.connectionName
|
|
19156
|
+
)) {
|
|
19157
|
+
continue;
|
|
19158
|
+
}
|
|
19159
|
+
const plansShareSourcePort = plan.sourcePoint.pcb_port_id && plan.sourcePoint.pcb_port_id === otherPlan.sourcePoint.pcb_port_id || plan.sourcePoint.pointId && plan.sourcePoint.pointId === otherPlan.sourcePoint.pointId;
|
|
19160
|
+
if (plansShareSourcePort) continue;
|
|
19161
|
+
const recordBlocker = () => {
|
|
19162
|
+
if (otherPlan.busId === plan.busId) return;
|
|
19163
|
+
blockingBusCounts?.set(
|
|
19164
|
+
otherPlan.busId,
|
|
19165
|
+
(blockingBusCounts.get(otherPlan.busId) ?? 0) + 1
|
|
19166
|
+
);
|
|
19167
|
+
};
|
|
18981
19168
|
for (const segment2 of plan.segments) {
|
|
18982
19169
|
for (const otherSegment of otherPlan.segments) {
|
|
18983
|
-
if (!segmentsAreClear(segment2, otherSegment, clearance))
|
|
19170
|
+
if (!segmentsAreClear(segment2, otherSegment, clearance)) {
|
|
19171
|
+
recordBlocker();
|
|
19172
|
+
return false;
|
|
19173
|
+
}
|
|
18984
19174
|
}
|
|
18985
19175
|
if (otherPlan.via?.spanLayers.includes(segment2.layer) && distancePointToSegment(
|
|
18986
19176
|
otherPlan.via.center,
|
|
18987
19177
|
segment2.start,
|
|
18988
19178
|
segment2.end
|
|
18989
19179
|
) < otherPlan.via.diameter / 2 + segment2.width / 2 + clearance - 1e-9) {
|
|
19180
|
+
recordBlocker();
|
|
18990
19181
|
return false;
|
|
18991
19182
|
}
|
|
18992
19183
|
}
|
|
@@ -18997,18 +19188,52 @@ function planIsClear(params) {
|
|
|
18997
19188
|
otherSegment.start,
|
|
18998
19189
|
otherSegment.end
|
|
18999
19190
|
) < plan.via.diameter / 2 + otherSegment.width / 2 + clearance - 1e-9) {
|
|
19191
|
+
recordBlocker();
|
|
19000
19192
|
return false;
|
|
19001
19193
|
}
|
|
19002
19194
|
}
|
|
19003
19195
|
if (otherPlan.via && plan.via.spanLayers.some(
|
|
19004
19196
|
(layer) => otherPlan.via.spanLayers.includes(layer)
|
|
19005
19197
|
) && distance9(plan.via.center, otherPlan.via.center) < (plan.via.diameter + otherPlan.via.diameter) / 2 + clearance - 1e-9) {
|
|
19198
|
+
recordBlocker();
|
|
19006
19199
|
return false;
|
|
19007
19200
|
}
|
|
19008
19201
|
}
|
|
19009
19202
|
}
|
|
19010
19203
|
return true;
|
|
19011
19204
|
}
|
|
19205
|
+
function planIsClear(params) {
|
|
19206
|
+
const {
|
|
19207
|
+
plan,
|
|
19208
|
+
otherPlans,
|
|
19209
|
+
staticClearanceCache,
|
|
19210
|
+
blockingBusCounts,
|
|
19211
|
+
cacheKey,
|
|
19212
|
+
srj,
|
|
19213
|
+
sharedBoundary,
|
|
19214
|
+
clearance,
|
|
19215
|
+
allowSameNetMerges
|
|
19216
|
+
} = params;
|
|
19217
|
+
let staticallyClear = staticClearanceCache?.get(cacheKey);
|
|
19218
|
+
if (staticallyClear === void 0) {
|
|
19219
|
+
staticallyClear = planIsStaticallyClear({
|
|
19220
|
+
plan,
|
|
19221
|
+
srj,
|
|
19222
|
+
sharedBoundary,
|
|
19223
|
+
clearance,
|
|
19224
|
+
allowSameNetMerges
|
|
19225
|
+
});
|
|
19226
|
+
staticClearanceCache?.set(cacheKey, staticallyClear);
|
|
19227
|
+
}
|
|
19228
|
+
return staticallyClear && planIsClearOfPlans({
|
|
19229
|
+
plan,
|
|
19230
|
+
otherPlans,
|
|
19231
|
+
srj,
|
|
19232
|
+
allowSameNetMerges,
|
|
19233
|
+
clearance,
|
|
19234
|
+
blockingBusCounts
|
|
19235
|
+
});
|
|
19236
|
+
}
|
|
19012
19237
|
function routePlaneTerminatedBus(params) {
|
|
19013
19238
|
const {
|
|
19014
19239
|
srj,
|
|
@@ -19019,7 +19244,10 @@ function routePlaneTerminatedBus(params) {
|
|
|
19019
19244
|
traceWidth,
|
|
19020
19245
|
viaDiameter,
|
|
19021
19246
|
viaHoleDiameter,
|
|
19022
|
-
clearance
|
|
19247
|
+
clearance,
|
|
19248
|
+
staticClearanceCache,
|
|
19249
|
+
blockingBusCounts,
|
|
19250
|
+
allowSameNetMerges = false
|
|
19023
19251
|
} = params;
|
|
19024
19252
|
const sourceObstacle = bus.connections[0]?.sourceObstacle;
|
|
19025
19253
|
if (!sourceObstacle || bus.termination.type !== "plane") return null;
|
|
@@ -19056,9 +19284,13 @@ function routePlaneTerminatedBus(params) {
|
|
|
19056
19284
|
if (!planIsClear({
|
|
19057
19285
|
plan,
|
|
19058
19286
|
otherPlans: [...acceptedPlans, ...candidatePlans],
|
|
19287
|
+
staticClearanceCache,
|
|
19288
|
+
blockingBusCounts,
|
|
19289
|
+
cacheKey: `plane:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${viaHandedness}`,
|
|
19059
19290
|
srj,
|
|
19060
19291
|
sharedBoundary: bus.sharedBoundary,
|
|
19061
|
-
clearance
|
|
19292
|
+
clearance,
|
|
19293
|
+
allowSameNetMerges
|
|
19062
19294
|
})) {
|
|
19063
19295
|
orderIsClear = false;
|
|
19064
19296
|
break;
|
|
@@ -19070,7 +19302,7 @@ function routePlaneTerminatedBus(params) {
|
|
|
19070
19302
|
}
|
|
19071
19303
|
return null;
|
|
19072
19304
|
}
|
|
19073
|
-
function
|
|
19305
|
+
function routeBusAlternatives(params, maxAlternatives = 1) {
|
|
19074
19306
|
const {
|
|
19075
19307
|
srj,
|
|
19076
19308
|
bus,
|
|
@@ -19081,14 +19313,23 @@ function routeBus(params) {
|
|
|
19081
19313
|
viaDiameter,
|
|
19082
19314
|
viaHoleDiameter,
|
|
19083
19315
|
clearance,
|
|
19084
|
-
compactBusTracks
|
|
19316
|
+
compactBusTracks,
|
|
19317
|
+
staticClearanceCache,
|
|
19318
|
+
blockingBusCounts,
|
|
19319
|
+
allowSameNetMerges = false
|
|
19085
19320
|
} = params;
|
|
19086
|
-
if (
|
|
19087
|
-
|
|
19088
|
-
|
|
19321
|
+
if (!Number.isInteger(maxAlternatives) || maxAlternatives < 1) {
|
|
19322
|
+
throw new Error(
|
|
19323
|
+
`FanoutSolver: maxAlternatives must be a positive integer, received ${maxAlternatives}`
|
|
19324
|
+
);
|
|
19325
|
+
}
|
|
19326
|
+
if (bus.termination.type === "plane") {
|
|
19327
|
+
const plan = routePlaneTerminatedBus(params);
|
|
19328
|
+
return plan ? [plan] : [];
|
|
19329
|
+
}
|
|
19089
19330
|
const exitAxis = getExitAxis(bus);
|
|
19090
19331
|
const sourceObstacle = bus.connections[0]?.sourceObstacle;
|
|
19091
|
-
if (!sourceObstacle) return [];
|
|
19332
|
+
if (!sourceObstacle) return [[]];
|
|
19092
19333
|
const directionalPadSize = isHorizontal(bus.direction) ? sourceObstacle.width : sourceObstacle.height;
|
|
19093
19334
|
const sourceLayer = bus.connections[0].sourceLayer;
|
|
19094
19335
|
const targetUsesVia = targetLayer !== sourceLayer;
|
|
@@ -19096,68 +19337,94 @@ function routeBus(params) {
|
|
|
19096
19337
|
const pairChannelFitsVia = getDirectionalPitch(bus) / 2 - directionalPadSize / 2 >= viaDiameter / 2 + clearance - 1e-9;
|
|
19097
19338
|
const interstitialEscape = targetUsesVia && !outwardEdgeBus && !pairChannelFitsVia;
|
|
19098
19339
|
const viaHandednesses = targetUsesVia ? pairChannelFitsVia || outwardEdgeBus ? [0] : [1, -1] : [0];
|
|
19340
|
+
const alternatives = [];
|
|
19341
|
+
const seenAlternativeKeys = /* @__PURE__ */ new Set();
|
|
19342
|
+
const addAlternative = (plans) => {
|
|
19343
|
+
const key = plans.map(
|
|
19344
|
+
(plan) => `${plan.connectionIndex}:${plan.targetLayer}:${plan.exitPoint.x}:${plan.exitPoint.y}:${plan.segments.map((segment2) => `${segment2.start.x},${segment2.start.y},${segment2.end.x},${segment2.end.y},${segment2.layer}`).join(";")}`
|
|
19345
|
+
).join("|");
|
|
19346
|
+
if (seenAlternativeKeys.has(key)) return;
|
|
19347
|
+
seenAlternativeKeys.add(key);
|
|
19348
|
+
alternatives.push(plans);
|
|
19349
|
+
};
|
|
19350
|
+
const searchConnectionOrder = (connectionOrder, viaHandedness, connectionIndex, candidatePlans) => {
|
|
19351
|
+
if (alternatives.length >= maxAlternatives) return;
|
|
19352
|
+
if (connectionIndex >= connectionOrder.length) {
|
|
19353
|
+
addAlternative(candidatePlans);
|
|
19354
|
+
return;
|
|
19355
|
+
}
|
|
19356
|
+
const preparedConnection = connectionOrder[connectionIndex];
|
|
19357
|
+
const connectionRank = getConnectionRank(bus, preparedConnection);
|
|
19358
|
+
const trackCandidates = getTrackCandidates({
|
|
19359
|
+
bus,
|
|
19360
|
+
connection: preparedConnection,
|
|
19361
|
+
preferredTrack: getPreferredTrack({
|
|
19362
|
+
bus,
|
|
19363
|
+
connection: preparedConnection,
|
|
19364
|
+
targetUsesVia,
|
|
19365
|
+
interstitialEscape,
|
|
19366
|
+
compactBusTracks,
|
|
19367
|
+
traceWidth,
|
|
19368
|
+
viaDiameter,
|
|
19369
|
+
clearance
|
|
19370
|
+
}),
|
|
19371
|
+
traceWidth,
|
|
19372
|
+
clearance
|
|
19373
|
+
});
|
|
19374
|
+
for (let trackIndex = 0; trackIndex < trackCandidates.length; trackIndex++) {
|
|
19375
|
+
const track = trackCandidates[trackIndex];
|
|
19376
|
+
const plan = buildPlan({
|
|
19377
|
+
preparedConnection,
|
|
19378
|
+
bus,
|
|
19379
|
+
targetLayer,
|
|
19380
|
+
track: track.value,
|
|
19381
|
+
exitAxis,
|
|
19382
|
+
layerNames,
|
|
19383
|
+
traceWidth,
|
|
19384
|
+
viaDiameter,
|
|
19385
|
+
viaHoleDiameter,
|
|
19386
|
+
viaHandedness,
|
|
19387
|
+
interstitialEscape,
|
|
19388
|
+
spreadLaneIndex: Math.min(
|
|
19389
|
+
connectionRank,
|
|
19390
|
+
bus.connections.length - connectionRank - 1
|
|
19391
|
+
),
|
|
19392
|
+
clearance,
|
|
19393
|
+
terminateAtVia: false
|
|
19394
|
+
});
|
|
19395
|
+
if (!planIsClear({
|
|
19396
|
+
plan,
|
|
19397
|
+
otherPlans: [...acceptedPlans, ...candidatePlans],
|
|
19398
|
+
staticClearanceCache,
|
|
19399
|
+
blockingBusCounts,
|
|
19400
|
+
cacheKey: `boundary:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${viaHandedness}:${trackIndex}`,
|
|
19401
|
+
srj,
|
|
19402
|
+
sharedBoundary: bus.sharedBoundary,
|
|
19403
|
+
clearance,
|
|
19404
|
+
allowSameNetMerges
|
|
19405
|
+
})) {
|
|
19406
|
+
continue;
|
|
19407
|
+
}
|
|
19408
|
+
searchConnectionOrder(
|
|
19409
|
+
connectionOrder,
|
|
19410
|
+
viaHandedness,
|
|
19411
|
+
connectionIndex + 1,
|
|
19412
|
+
[...candidatePlans, plan]
|
|
19413
|
+
);
|
|
19414
|
+
if (alternatives.length >= maxAlternatives) return;
|
|
19415
|
+
if (maxAlternatives === 1) return;
|
|
19416
|
+
}
|
|
19417
|
+
};
|
|
19099
19418
|
for (const viaHandedness of viaHandednesses) {
|
|
19100
19419
|
for (const connectionOrder of getConnectionOrders(bus)) {
|
|
19101
|
-
|
|
19102
|
-
|
|
19103
|
-
for (const preparedConnection of connectionOrder) {
|
|
19104
|
-
let acceptedPlan = null;
|
|
19105
|
-
for (const track of getTrackCandidates({
|
|
19106
|
-
bus,
|
|
19107
|
-
connection: preparedConnection,
|
|
19108
|
-
preferredTrack: getPreferredTrack({
|
|
19109
|
-
bus,
|
|
19110
|
-
connection: preparedConnection,
|
|
19111
|
-
targetUsesVia,
|
|
19112
|
-
interstitialEscape,
|
|
19113
|
-
compactBusTracks,
|
|
19114
|
-
traceWidth,
|
|
19115
|
-
viaDiameter,
|
|
19116
|
-
clearance
|
|
19117
|
-
}),
|
|
19118
|
-
traceWidth,
|
|
19119
|
-
clearance
|
|
19120
|
-
})) {
|
|
19121
|
-
const plan = buildPlan({
|
|
19122
|
-
preparedConnection,
|
|
19123
|
-
bus,
|
|
19124
|
-
targetLayer,
|
|
19125
|
-
track: track.value,
|
|
19126
|
-
exitAxis,
|
|
19127
|
-
layerNames,
|
|
19128
|
-
traceWidth,
|
|
19129
|
-
viaDiameter,
|
|
19130
|
-
viaHoleDiameter,
|
|
19131
|
-
viaHandedness,
|
|
19132
|
-
interstitialEscape,
|
|
19133
|
-
spreadLaneIndex: Math.min(
|
|
19134
|
-
getConnectionRank(bus, preparedConnection),
|
|
19135
|
-
bus.connections.length - getConnectionRank(bus, preparedConnection) - 1
|
|
19136
|
-
),
|
|
19137
|
-
clearance,
|
|
19138
|
-
terminateAtVia: false
|
|
19139
|
-
});
|
|
19140
|
-
if (planIsClear({
|
|
19141
|
-
plan,
|
|
19142
|
-
otherPlans: [...acceptedPlans, ...candidatePlans],
|
|
19143
|
-
srj,
|
|
19144
|
-
sharedBoundary: bus.sharedBoundary,
|
|
19145
|
-
clearance
|
|
19146
|
-
})) {
|
|
19147
|
-
acceptedPlan = plan;
|
|
19148
|
-
break;
|
|
19149
|
-
}
|
|
19150
|
-
}
|
|
19151
|
-
if (!acceptedPlan) {
|
|
19152
|
-
orderIsClear = false;
|
|
19153
|
-
break;
|
|
19154
|
-
}
|
|
19155
|
-
candidatePlans.push(acceptedPlan);
|
|
19156
|
-
}
|
|
19157
|
-
if (orderIsClear) return candidatePlans;
|
|
19420
|
+
searchConnectionOrder(connectionOrder, viaHandedness, 0, []);
|
|
19421
|
+
if (alternatives.length >= maxAlternatives) return alternatives;
|
|
19158
19422
|
}
|
|
19159
19423
|
}
|
|
19160
|
-
return
|
|
19424
|
+
return alternatives;
|
|
19425
|
+
}
|
|
19426
|
+
function routeBus(params) {
|
|
19427
|
+
return routeBusAlternatives(params, 1)[0] ?? null;
|
|
19161
19428
|
}
|
|
19162
19429
|
|
|
19163
19430
|
// node_modules/@tscircuit/fanout-solver/lib/route-single-layer-adaptive-exits.ts
|
|
@@ -20978,6 +21245,539 @@ function routeSingleLayerWithPushAndShove(params) {
|
|
|
20978
21245
|
return paths.map(buildPlan3);
|
|
20979
21246
|
}
|
|
20980
21247
|
|
|
21248
|
+
// node_modules/@tscircuit/fanout-solver/lib/validate-fanout-solution.ts
|
|
21249
|
+
var EPSILON3 = 1e-6;
|
|
21250
|
+
function pointsMatch(first, second) {
|
|
21251
|
+
return distance9(first, second) <= EPSILON3;
|
|
21252
|
+
}
|
|
21253
|
+
function getPointLayers2(point6) {
|
|
21254
|
+
return "layer" in point6 ? [point6.layer] : point6.layers;
|
|
21255
|
+
}
|
|
21256
|
+
function connectionPointsMatch(first, second) {
|
|
21257
|
+
return pointsMatch(first, second) && getPointLayers2(first).join("\0") === getPointLayers2(second).join("\0") && first.pointId === second.pointId && first.pcb_port_id === second.pcb_port_id;
|
|
21258
|
+
}
|
|
21259
|
+
function pointIsOnBoundary(point6, boundary) {
|
|
21260
|
+
const inside = point6.x >= boundary.minX - EPSILON3 && point6.x <= boundary.maxX + EPSILON3 && point6.y >= boundary.minY - EPSILON3 && point6.y <= boundary.maxY + EPSILON3;
|
|
21261
|
+
const onEdge = Math.abs(point6.x - boundary.minX) <= EPSILON3 || Math.abs(point6.x - boundary.maxX) <= EPSILON3 || Math.abs(point6.y - boundary.minY) <= EPSILON3 || Math.abs(point6.y - boundary.maxY) <= EPSILON3;
|
|
21262
|
+
return inside && onEdge;
|
|
21263
|
+
}
|
|
21264
|
+
function pointIsInsideBounds2(point6, bounds) {
|
|
21265
|
+
return point6.x >= bounds.minX - EPSILON3 && point6.x <= bounds.maxX + EPSILON3 && point6.y >= bounds.minY - EPSILON3 && point6.y <= bounds.maxY + EPSILON3;
|
|
21266
|
+
}
|
|
21267
|
+
function addIssue(issues, code, message, plan, otherConnectionName) {
|
|
21268
|
+
issues.push({
|
|
21269
|
+
code,
|
|
21270
|
+
message,
|
|
21271
|
+
...plan ? {
|
|
21272
|
+
connectionName: plan.connectionName,
|
|
21273
|
+
busId: plan.busId
|
|
21274
|
+
} : {},
|
|
21275
|
+
...otherConnectionName ? { otherConnectionName } : {}
|
|
21276
|
+
});
|
|
21277
|
+
}
|
|
21278
|
+
function extractTraceSegments(params) {
|
|
21279
|
+
const { trace, plan, issues } = params;
|
|
21280
|
+
const segments = [];
|
|
21281
|
+
let previousWire;
|
|
21282
|
+
let pendingVia;
|
|
21283
|
+
for (const routePoint of trace.route) {
|
|
21284
|
+
if (routePoint.route_type === "via") {
|
|
21285
|
+
if (!previousWire || !pointsMatch(previousWire, routePoint) || previousWire.layer !== routePoint.from_layer) {
|
|
21286
|
+
addIssue(
|
|
21287
|
+
issues,
|
|
21288
|
+
"disconnected-trace",
|
|
21289
|
+
`Trace ${trace.pcb_trace_id} reaches a via without a matching ${routePoint.from_layer} wire endpoint`,
|
|
21290
|
+
plan
|
|
21291
|
+
);
|
|
21292
|
+
}
|
|
21293
|
+
pendingVia = routePoint;
|
|
21294
|
+
continue;
|
|
21295
|
+
}
|
|
21296
|
+
if (routePoint.route_type !== "wire") {
|
|
21297
|
+
addIssue(
|
|
21298
|
+
issues,
|
|
21299
|
+
"unsupported-route-point",
|
|
21300
|
+
`Trace ${trace.pcb_trace_id} contains unsupported ${routePoint.route_type} geometry`,
|
|
21301
|
+
plan
|
|
21302
|
+
);
|
|
21303
|
+
continue;
|
|
21304
|
+
}
|
|
21305
|
+
if (pendingVia) {
|
|
21306
|
+
if (!pointsMatch(routePoint, pendingVia) || routePoint.layer !== pendingVia.to_layer) {
|
|
21307
|
+
addIssue(
|
|
21308
|
+
issues,
|
|
21309
|
+
"disconnected-trace",
|
|
21310
|
+
`Trace ${trace.pcb_trace_id} does not continue from its via on ${pendingVia.to_layer}`,
|
|
21311
|
+
plan
|
|
21312
|
+
);
|
|
21313
|
+
}
|
|
21314
|
+
previousWire = routePoint;
|
|
21315
|
+
pendingVia = void 0;
|
|
21316
|
+
continue;
|
|
21317
|
+
}
|
|
21318
|
+
if (previousWire) {
|
|
21319
|
+
if (previousWire.layer !== routePoint.layer) {
|
|
21320
|
+
addIssue(
|
|
21321
|
+
issues,
|
|
21322
|
+
"disconnected-trace",
|
|
21323
|
+
`Trace ${trace.pcb_trace_id} changes from ${previousWire.layer} to ${routePoint.layer} without a via`,
|
|
21324
|
+
plan
|
|
21325
|
+
);
|
|
21326
|
+
} else if (!pointsMatch(previousWire, routePoint)) {
|
|
21327
|
+
segments.push({
|
|
21328
|
+
start: { x: previousWire.x, y: previousWire.y },
|
|
21329
|
+
end: { x: routePoint.x, y: routePoint.y },
|
|
21330
|
+
width: routePoint.width,
|
|
21331
|
+
layer: routePoint.layer
|
|
21332
|
+
});
|
|
21333
|
+
}
|
|
21334
|
+
}
|
|
21335
|
+
previousWire = routePoint;
|
|
21336
|
+
}
|
|
21337
|
+
if (pendingVia) {
|
|
21338
|
+
addIssue(
|
|
21339
|
+
issues,
|
|
21340
|
+
"disconnected-trace",
|
|
21341
|
+
`Trace ${trace.pcb_trace_id} ends at a via without a wire on ${pendingVia.to_layer}`,
|
|
21342
|
+
plan
|
|
21343
|
+
);
|
|
21344
|
+
}
|
|
21345
|
+
return segments;
|
|
21346
|
+
}
|
|
21347
|
+
function validatePlanStructure(params) {
|
|
21348
|
+
const { plan, preparedBus, inputSrj, outputSrj, sharedBoundary, issues } = params;
|
|
21349
|
+
const inputConnection = inputSrj.connections[plan.connectionIndex];
|
|
21350
|
+
if (!inputConnection || inputConnection.name !== plan.connectionName) {
|
|
21351
|
+
addIssue(
|
|
21352
|
+
issues,
|
|
21353
|
+
"connection-mismatch",
|
|
21354
|
+
`Plan index ${plan.connectionIndex} does not identify connection ${plan.connectionName}`,
|
|
21355
|
+
plan
|
|
21356
|
+
);
|
|
21357
|
+
return;
|
|
21358
|
+
}
|
|
21359
|
+
const preparedConnection = preparedBus?.connections.find(
|
|
21360
|
+
(connection) => connection.connectionIndex === plan.connectionIndex
|
|
21361
|
+
);
|
|
21362
|
+
if (!preparedConnection || preparedConnection.sourcePointIndex !== plan.sourcePointIndex || !connectionPointsMatch(preparedConnection.sourcePoint, plan.sourcePoint) || preparedConnection.sourceObstacle.obstacleId !== plan.sourceObstacle.obstacleId) {
|
|
21363
|
+
addIssue(
|
|
21364
|
+
issues,
|
|
21365
|
+
"source-mismatch",
|
|
21366
|
+
`Plan ${plan.connectionName} does not start at its prepared component endpoint`,
|
|
21367
|
+
plan
|
|
21368
|
+
);
|
|
21369
|
+
}
|
|
21370
|
+
if (preparedBus?.termination.type !== plan.termination.type) {
|
|
21371
|
+
addIssue(
|
|
21372
|
+
issues,
|
|
21373
|
+
"termination-mismatch",
|
|
21374
|
+
`Plan ${plan.connectionName} does not use its bus termination`,
|
|
21375
|
+
plan
|
|
21376
|
+
);
|
|
21377
|
+
}
|
|
21378
|
+
if (plan.segments.length === 0 || plan.length <= EPSILON3) {
|
|
21379
|
+
addIssue(
|
|
21380
|
+
issues,
|
|
21381
|
+
"not-broken-out",
|
|
21382
|
+
`Plan ${plan.connectionName} has no non-zero escape geometry`,
|
|
21383
|
+
plan
|
|
21384
|
+
);
|
|
21385
|
+
} else {
|
|
21386
|
+
const routableBounds = {
|
|
21387
|
+
minX: Math.min(inputSrj.bounds.minX, sharedBoundary.minX),
|
|
21388
|
+
maxX: Math.max(inputSrj.bounds.maxX, sharedBoundary.maxX),
|
|
21389
|
+
minY: Math.min(inputSrj.bounds.minY, sharedBoundary.minY),
|
|
21390
|
+
maxY: Math.max(inputSrj.bounds.maxY, sharedBoundary.maxY)
|
|
21391
|
+
};
|
|
21392
|
+
if (plan.segments.some(
|
|
21393
|
+
(segment2) => !pointIsInsideBounds2(segment2.start, routableBounds) || !pointIsInsideBounds2(segment2.end, routableBounds)
|
|
21394
|
+
)) {
|
|
21395
|
+
addIssue(
|
|
21396
|
+
issues,
|
|
21397
|
+
"outside-routing-bounds",
|
|
21398
|
+
`Plan ${plan.connectionName} leaves the routable SRJ/shared-boundary area`,
|
|
21399
|
+
plan
|
|
21400
|
+
);
|
|
21401
|
+
}
|
|
21402
|
+
if (!pointsMatch(plan.segments[0].start, plan.sourcePoint)) {
|
|
21403
|
+
addIssue(
|
|
21404
|
+
issues,
|
|
21405
|
+
"disconnected-trace",
|
|
21406
|
+
`Plan ${plan.connectionName} does not start at its source pad`,
|
|
21407
|
+
plan
|
|
21408
|
+
);
|
|
21409
|
+
}
|
|
21410
|
+
if (!pointsMatch(plan.segments.at(-1).end, plan.exitPoint)) {
|
|
21411
|
+
addIssue(
|
|
21412
|
+
issues,
|
|
21413
|
+
"disconnected-trace",
|
|
21414
|
+
`Plan ${plan.connectionName} does not end at its declared exit`,
|
|
21415
|
+
plan
|
|
21416
|
+
);
|
|
21417
|
+
}
|
|
21418
|
+
for (let index = 1; index < plan.segments.length; index++) {
|
|
21419
|
+
const previous = plan.segments[index - 1];
|
|
21420
|
+
const current = plan.segments[index];
|
|
21421
|
+
if (!pointsMatch(previous.end, current.start)) {
|
|
21422
|
+
addIssue(
|
|
21423
|
+
issues,
|
|
21424
|
+
"disconnected-trace",
|
|
21425
|
+
`Plan ${plan.connectionName} has a gap between route segments`,
|
|
21426
|
+
plan
|
|
21427
|
+
);
|
|
21428
|
+
}
|
|
21429
|
+
if (previous.layer !== current.layer && (!plan.via || !pointsMatch(previous.end, plan.via.center) || !plan.via.spanLayers.includes(previous.layer) || !plan.via.spanLayers.includes(current.layer))) {
|
|
21430
|
+
addIssue(
|
|
21431
|
+
issues,
|
|
21432
|
+
"disconnected-trace",
|
|
21433
|
+
`Plan ${plan.connectionName} changes layers without a connecting via`,
|
|
21434
|
+
plan
|
|
21435
|
+
);
|
|
21436
|
+
}
|
|
21437
|
+
}
|
|
21438
|
+
}
|
|
21439
|
+
const traceSegments = extractTraceSegments({
|
|
21440
|
+
trace: plan.trace,
|
|
21441
|
+
plan,
|
|
21442
|
+
issues
|
|
21443
|
+
});
|
|
21444
|
+
if (traceSegments.length !== plan.segments.length || traceSegments.some((segment2, index) => {
|
|
21445
|
+
const declared = plan.segments[index];
|
|
21446
|
+
return !declared || segment2.layer !== declared.layer || Math.abs(segment2.width - declared.width) > EPSILON3 || !pointsMatch(segment2.start, declared.start) || !pointsMatch(segment2.end, declared.end);
|
|
21447
|
+
})) {
|
|
21448
|
+
addIssue(
|
|
21449
|
+
issues,
|
|
21450
|
+
"trace-plan-mismatch",
|
|
21451
|
+
`Trace ${plan.trace.pcb_trace_id} does not encode its declared route segments`,
|
|
21452
|
+
plan
|
|
21453
|
+
);
|
|
21454
|
+
}
|
|
21455
|
+
const firstRoutePoint = plan.trace.route.find(
|
|
21456
|
+
(routePoint) => "x" in routePoint && "y" in routePoint
|
|
21457
|
+
);
|
|
21458
|
+
const lastRoutePoint = [...plan.trace.route].reverse().find(
|
|
21459
|
+
(routePoint) => "x" in routePoint && "y" in routePoint
|
|
21460
|
+
);
|
|
21461
|
+
if (!firstRoutePoint || !lastRoutePoint || !pointsMatch(firstRoutePoint, plan.sourcePoint) || !pointsMatch(lastRoutePoint, plan.exitPoint)) {
|
|
21462
|
+
addIssue(
|
|
21463
|
+
issues,
|
|
21464
|
+
"disconnected-trace",
|
|
21465
|
+
`Trace ${plan.trace.pcb_trace_id} does not span its source and exit`,
|
|
21466
|
+
plan
|
|
21467
|
+
);
|
|
21468
|
+
}
|
|
21469
|
+
const outputConnection = outputSrj.connections.find(
|
|
21470
|
+
(connection) => connection.name === plan.connectionName
|
|
21471
|
+
);
|
|
21472
|
+
if (plan.termination.type === "boundary") {
|
|
21473
|
+
if (!outputConnection) {
|
|
21474
|
+
addIssue(
|
|
21475
|
+
issues,
|
|
21476
|
+
"output-connection-missing",
|
|
21477
|
+
`Boundary connection ${plan.connectionName} was removed from the output`,
|
|
21478
|
+
plan
|
|
21479
|
+
);
|
|
21480
|
+
} else {
|
|
21481
|
+
const outputSource = outputConnection.pointsToConnect[plan.sourcePointIndex];
|
|
21482
|
+
if (!outputSource || !pointsMatch(outputSource, plan.exitPoint) || !("layer" in outputSource) || outputSource.layer !== plan.targetLayer) {
|
|
21483
|
+
addIssue(
|
|
21484
|
+
issues,
|
|
21485
|
+
"output-exit-mismatch",
|
|
21486
|
+
`Output connection ${plan.connectionName} is not attached to its fanout exit`,
|
|
21487
|
+
plan
|
|
21488
|
+
);
|
|
21489
|
+
}
|
|
21490
|
+
for (let index = 0; index < inputConnection.pointsToConnect.length; index++) {
|
|
21491
|
+
if (index === plan.sourcePointIndex) continue;
|
|
21492
|
+
const inputPoint = inputConnection.pointsToConnect[index];
|
|
21493
|
+
const outputPoint = outputConnection.pointsToConnect[index];
|
|
21494
|
+
if (!inputPoint || !outputPoint || !connectionPointsMatch(inputPoint, outputPoint)) {
|
|
21495
|
+
addIssue(
|
|
21496
|
+
issues,
|
|
21497
|
+
"downstream-endpoint-lost",
|
|
21498
|
+
`Output connection ${plan.connectionName} did not retain downstream endpoint ${index}`,
|
|
21499
|
+
plan
|
|
21500
|
+
);
|
|
21501
|
+
}
|
|
21502
|
+
}
|
|
21503
|
+
}
|
|
21504
|
+
} else if (outputConnection) {
|
|
21505
|
+
addIssue(
|
|
21506
|
+
issues,
|
|
21507
|
+
"plane-connection-retained",
|
|
21508
|
+
`Plane-terminated connection ${plan.connectionName} remains in the output`,
|
|
21509
|
+
plan
|
|
21510
|
+
);
|
|
21511
|
+
}
|
|
21512
|
+
}
|
|
21513
|
+
function plansHaveConnectedCopper(first, second) {
|
|
21514
|
+
for (const firstSegment of first.segments) {
|
|
21515
|
+
for (const secondSegment of second.segments) {
|
|
21516
|
+
if (firstSegment.layer === secondSegment.layer && distanceSegmentToSegment(
|
|
21517
|
+
firstSegment.start,
|
|
21518
|
+
firstSegment.end,
|
|
21519
|
+
secondSegment.start,
|
|
21520
|
+
secondSegment.end
|
|
21521
|
+
) <= (firstSegment.width + secondSegment.width) / 2 + EPSILON3) {
|
|
21522
|
+
return true;
|
|
21523
|
+
}
|
|
21524
|
+
}
|
|
21525
|
+
if (second.via?.spanLayers.includes(firstSegment.layer) && distancePointToSegment(
|
|
21526
|
+
second.via.center,
|
|
21527
|
+
firstSegment.start,
|
|
21528
|
+
firstSegment.end
|
|
21529
|
+
) <= second.via.diameter / 2 + firstSegment.width / 2 + EPSILON3) {
|
|
21530
|
+
return true;
|
|
21531
|
+
}
|
|
21532
|
+
}
|
|
21533
|
+
if (first.via) {
|
|
21534
|
+
for (const secondSegment of second.segments) {
|
|
21535
|
+
if (first.via.spanLayers.includes(secondSegment.layer) && distancePointToSegment(
|
|
21536
|
+
first.via.center,
|
|
21537
|
+
secondSegment.start,
|
|
21538
|
+
secondSegment.end
|
|
21539
|
+
) <= first.via.diameter / 2 + secondSegment.width / 2 + EPSILON3) {
|
|
21540
|
+
return true;
|
|
21541
|
+
}
|
|
21542
|
+
}
|
|
21543
|
+
if (second.via && first.via.spanLayers.some(
|
|
21544
|
+
(layer) => second.via.spanLayers.includes(layer)
|
|
21545
|
+
) && distance9(first.via.center, second.via.center) <= (first.via.diameter + second.via.diameter) / 2 + EPSILON3) {
|
|
21546
|
+
return true;
|
|
21547
|
+
}
|
|
21548
|
+
}
|
|
21549
|
+
return false;
|
|
21550
|
+
}
|
|
21551
|
+
function validateBreakoutConnectivity(params) {
|
|
21552
|
+
const { plans, inputSrj, sharedBoundary, issues } = params;
|
|
21553
|
+
const connectedPlans = /* @__PURE__ */ new Set();
|
|
21554
|
+
const neighboringPlans = /* @__PURE__ */ new Map();
|
|
21555
|
+
for (const plan of plans) neighboringPlans.set(plan, []);
|
|
21556
|
+
for (let firstIndex = 0; firstIndex < plans.length; firstIndex++) {
|
|
21557
|
+
const first = plans[firstIndex];
|
|
21558
|
+
if (first.termination.type === "plane" ? Boolean(first.via) : first.segments.some(
|
|
21559
|
+
(segment2) => pointIsOnBoundary(segment2.start, sharedBoundary) || pointIsOnBoundary(segment2.end, sharedBoundary)
|
|
21560
|
+
)) {
|
|
21561
|
+
connectedPlans.add(first);
|
|
21562
|
+
}
|
|
21563
|
+
for (let secondIndex = firstIndex + 1; secondIndex < plans.length; secondIndex++) {
|
|
21564
|
+
const second = plans[secondIndex];
|
|
21565
|
+
if (!connectionsShareElectricalNet(
|
|
21566
|
+
inputSrj,
|
|
21567
|
+
first.connectionName,
|
|
21568
|
+
second.connectionName
|
|
21569
|
+
) || !plansHaveConnectedCopper(first, second)) {
|
|
21570
|
+
continue;
|
|
21571
|
+
}
|
|
21572
|
+
neighboringPlans.get(first).push(second);
|
|
21573
|
+
neighboringPlans.get(second).push(first);
|
|
21574
|
+
}
|
|
21575
|
+
}
|
|
21576
|
+
const queue = [...connectedPlans];
|
|
21577
|
+
while (queue.length > 0) {
|
|
21578
|
+
const plan = queue.shift();
|
|
21579
|
+
for (const neighbor of neighboringPlans.get(plan) ?? []) {
|
|
21580
|
+
if (connectedPlans.has(neighbor)) continue;
|
|
21581
|
+
connectedPlans.add(neighbor);
|
|
21582
|
+
queue.push(neighbor);
|
|
21583
|
+
}
|
|
21584
|
+
}
|
|
21585
|
+
for (const plan of plans) {
|
|
21586
|
+
if (connectedPlans.has(plan)) continue;
|
|
21587
|
+
addIssue(
|
|
21588
|
+
issues,
|
|
21589
|
+
"not-broken-out",
|
|
21590
|
+
plan.termination.type === "boundary" ? `Connection ${plan.connectionName} has no continuous same-net copper path to the shared boundary` : `Plane connection ${plan.connectionName} has no terminating via`,
|
|
21591
|
+
plan
|
|
21592
|
+
);
|
|
21593
|
+
}
|
|
21594
|
+
return connectedPlans;
|
|
21595
|
+
}
|
|
21596
|
+
function validateClearances(params) {
|
|
21597
|
+
const { plans, inputSrj, clearance, issues } = params;
|
|
21598
|
+
for (const plan of plans) {
|
|
21599
|
+
for (let segmentIndex = 0; segmentIndex < plan.segments.length; segmentIndex++) {
|
|
21600
|
+
const segment2 = plan.segments[segmentIndex];
|
|
21601
|
+
for (const obstacle of inputSrj.obstacles) {
|
|
21602
|
+
if (!obstacle.layers.includes(segment2.layer)) continue;
|
|
21603
|
+
if (obstacleSharesElectricalNet(inputSrj, obstacle, plan.connectionName)) {
|
|
21604
|
+
continue;
|
|
21605
|
+
}
|
|
21606
|
+
if (segmentIndex === 0 && obstacle.obstacleId === plan.sourceObstacle.obstacleId && segment2.layer === plan.sourceLayer) {
|
|
21607
|
+
continue;
|
|
21608
|
+
}
|
|
21609
|
+
const actual = distanceSegmentToObstacle(segment2, obstacle);
|
|
21610
|
+
const required = segment2.width / 2 + clearance;
|
|
21611
|
+
if (actual < required - 1e-9) {
|
|
21612
|
+
addIssue(
|
|
21613
|
+
issues,
|
|
21614
|
+
"obstacle-clearance",
|
|
21615
|
+
`Trace ${plan.connectionName} on ${segment2.layer} is ${actual.toFixed(4)}mm from different-net obstacle ${obstacle.obstacleId}; ${required.toFixed(4)}mm is required`,
|
|
21616
|
+
plan
|
|
21617
|
+
);
|
|
21618
|
+
}
|
|
21619
|
+
}
|
|
21620
|
+
}
|
|
21621
|
+
if (plan.via) {
|
|
21622
|
+
for (const obstacle of inputSrj.obstacles) {
|
|
21623
|
+
if (!obstacle.layers.some(
|
|
21624
|
+
(layer) => plan.via.spanLayers.includes(layer)
|
|
21625
|
+
) || obstacleSharesElectricalNet(inputSrj, obstacle, plan.connectionName)) {
|
|
21626
|
+
continue;
|
|
21627
|
+
}
|
|
21628
|
+
const actual = distancePointToObstacle(plan.via.center, obstacle);
|
|
21629
|
+
const required = plan.via.diameter / 2 + clearance;
|
|
21630
|
+
if (actual < required - 1e-9) {
|
|
21631
|
+
addIssue(
|
|
21632
|
+
issues,
|
|
21633
|
+
"via-obstacle-clearance",
|
|
21634
|
+
`Via ${plan.connectionName} is ${actual.toFixed(4)}mm from different-net obstacle ${obstacle.obstacleId} on its layer span; ${required.toFixed(4)}mm is required`,
|
|
21635
|
+
plan
|
|
21636
|
+
);
|
|
21637
|
+
}
|
|
21638
|
+
}
|
|
21639
|
+
}
|
|
21640
|
+
}
|
|
21641
|
+
for (let firstIndex = 0; firstIndex < plans.length; firstIndex++) {
|
|
21642
|
+
const first = plans[firstIndex];
|
|
21643
|
+
for (let secondIndex = firstIndex + 1; secondIndex < plans.length; secondIndex++) {
|
|
21644
|
+
const second = plans[secondIndex];
|
|
21645
|
+
if (connectionsShareElectricalNet(
|
|
21646
|
+
inputSrj,
|
|
21647
|
+
first.connectionName,
|
|
21648
|
+
second.connectionName
|
|
21649
|
+
)) {
|
|
21650
|
+
continue;
|
|
21651
|
+
}
|
|
21652
|
+
for (const firstSegment of first.segments) {
|
|
21653
|
+
for (const secondSegment of second.segments) {
|
|
21654
|
+
if (!segmentsAreClear(firstSegment, secondSegment, clearance)) {
|
|
21655
|
+
addIssue(
|
|
21656
|
+
issues,
|
|
21657
|
+
"different-net-trace-clearance",
|
|
21658
|
+
`Different-net traces ${first.connectionName} and ${second.connectionName} intersect or violate clearance on ${firstSegment.layer}`,
|
|
21659
|
+
first,
|
|
21660
|
+
second.connectionName
|
|
21661
|
+
);
|
|
21662
|
+
}
|
|
21663
|
+
}
|
|
21664
|
+
if (second.via?.spanLayers.includes(firstSegment.layer) && distancePointToSegment(
|
|
21665
|
+
second.via.center,
|
|
21666
|
+
firstSegment.start,
|
|
21667
|
+
firstSegment.end
|
|
21668
|
+
) < second.via.diameter / 2 + firstSegment.width / 2 + clearance - 1e-9) {
|
|
21669
|
+
addIssue(
|
|
21670
|
+
issues,
|
|
21671
|
+
"different-net-trace-via-clearance",
|
|
21672
|
+
`Trace ${first.connectionName} violates via clearance to ${second.connectionName} on ${firstSegment.layer}`,
|
|
21673
|
+
first,
|
|
21674
|
+
second.connectionName
|
|
21675
|
+
);
|
|
21676
|
+
}
|
|
21677
|
+
}
|
|
21678
|
+
if (first.via) {
|
|
21679
|
+
for (const secondSegment of second.segments) {
|
|
21680
|
+
if (first.via.spanLayers.includes(secondSegment.layer) && distancePointToSegment(
|
|
21681
|
+
first.via.center,
|
|
21682
|
+
secondSegment.start,
|
|
21683
|
+
secondSegment.end
|
|
21684
|
+
) < first.via.diameter / 2 + secondSegment.width / 2 + clearance - 1e-9) {
|
|
21685
|
+
addIssue(
|
|
21686
|
+
issues,
|
|
21687
|
+
"different-net-trace-via-clearance",
|
|
21688
|
+
`Via ${first.connectionName} violates trace clearance to ${second.connectionName} on ${secondSegment.layer}`,
|
|
21689
|
+
first,
|
|
21690
|
+
second.connectionName
|
|
21691
|
+
);
|
|
21692
|
+
}
|
|
21693
|
+
}
|
|
21694
|
+
if (second.via && first.via.spanLayers.some(
|
|
21695
|
+
(layer) => second.via.spanLayers.includes(layer)
|
|
21696
|
+
) && distance9(first.via.center, second.via.center) < (first.via.diameter + second.via.diameter) / 2 + clearance - 1e-9) {
|
|
21697
|
+
addIssue(
|
|
21698
|
+
issues,
|
|
21699
|
+
"different-net-via-clearance",
|
|
21700
|
+
`Vias ${first.connectionName} and ${second.connectionName} violate clearance on an overlapping layer span`,
|
|
21701
|
+
first,
|
|
21702
|
+
second.connectionName
|
|
21703
|
+
);
|
|
21704
|
+
}
|
|
21705
|
+
}
|
|
21706
|
+
}
|
|
21707
|
+
}
|
|
21708
|
+
}
|
|
21709
|
+
function validateFanoutSolution(params) {
|
|
21710
|
+
const {
|
|
21711
|
+
inputSrj,
|
|
21712
|
+
outputSrj,
|
|
21713
|
+
plans,
|
|
21714
|
+
preparedBuses,
|
|
21715
|
+
sharedBoundary,
|
|
21716
|
+
clearance
|
|
21717
|
+
} = params;
|
|
21718
|
+
const issues = [];
|
|
21719
|
+
const plansByConnection = /* @__PURE__ */ new Map();
|
|
21720
|
+
const preparedBusById = new Map(preparedBuses.map((bus) => [bus.busId, bus]));
|
|
21721
|
+
for (const plan of plans) {
|
|
21722
|
+
const connectionPlans = plansByConnection.get(plan.connectionName) ?? [];
|
|
21723
|
+
connectionPlans.push(plan);
|
|
21724
|
+
plansByConnection.set(plan.connectionName, connectionPlans);
|
|
21725
|
+
}
|
|
21726
|
+
for (const connection of inputSrj.connections) {
|
|
21727
|
+
const connectionPlans = plansByConnection.get(connection.name) ?? [];
|
|
21728
|
+
if (connectionPlans.length === 0) {
|
|
21729
|
+
addIssue(
|
|
21730
|
+
issues,
|
|
21731
|
+
"missing-plan",
|
|
21732
|
+
`Connection ${connection.name} has no fanout plan`
|
|
21733
|
+
);
|
|
21734
|
+
} else if (connectionPlans.length > 1) {
|
|
21735
|
+
addIssue(
|
|
21736
|
+
issues,
|
|
21737
|
+
"duplicate-plan",
|
|
21738
|
+
`Connection ${connection.name} has ${connectionPlans.length} fanout plans`,
|
|
21739
|
+
connectionPlans[0]
|
|
21740
|
+
);
|
|
21741
|
+
}
|
|
21742
|
+
}
|
|
21743
|
+
for (const plan of plans) {
|
|
21744
|
+
if (!inputSrj.connections.some(
|
|
21745
|
+
(connection) => connection.name === plan.connectionName
|
|
21746
|
+
)) {
|
|
21747
|
+
addIssue(
|
|
21748
|
+
issues,
|
|
21749
|
+
"unknown-plan",
|
|
21750
|
+
`Plan ${plan.connectionName} is not an input connection`,
|
|
21751
|
+
plan
|
|
21752
|
+
);
|
|
21753
|
+
continue;
|
|
21754
|
+
}
|
|
21755
|
+
validatePlanStructure({
|
|
21756
|
+
plan,
|
|
21757
|
+
preparedBus: preparedBusById.get(plan.busId),
|
|
21758
|
+
inputSrj,
|
|
21759
|
+
outputSrj,
|
|
21760
|
+
sharedBoundary,
|
|
21761
|
+
issues
|
|
21762
|
+
});
|
|
21763
|
+
}
|
|
21764
|
+
const connectedPlans = validateBreakoutConnectivity({
|
|
21765
|
+
plans,
|
|
21766
|
+
inputSrj,
|
|
21767
|
+
sharedBoundary,
|
|
21768
|
+
issues
|
|
21769
|
+
});
|
|
21770
|
+
validateClearances({ plans, inputSrj, clearance, issues });
|
|
21771
|
+
return {
|
|
21772
|
+
valid: issues.length === 0,
|
|
21773
|
+
checkedConnectionCount: inputSrj.connections.length,
|
|
21774
|
+
brokenOutConnectionCount: new Set(
|
|
21775
|
+
[...connectedPlans].map((plan) => plan.connectionName)
|
|
21776
|
+
).size,
|
|
21777
|
+
issues
|
|
21778
|
+
};
|
|
21779
|
+
}
|
|
21780
|
+
|
|
20981
21781
|
// node_modules/@tscircuit/fanout-solver/lib/fanout-solver.ts
|
|
20982
21782
|
function resolvePositiveNumber(label, value) {
|
|
20983
21783
|
if (!Number.isFinite(value) || value <= 0) {
|
|
@@ -21033,6 +21833,7 @@ function resolveConfig(srj, options) {
|
|
|
21033
21833
|
viaHoleDiameter,
|
|
21034
21834
|
clearance,
|
|
21035
21835
|
compactBusTracks: options.compactBusTracks ?? false,
|
|
21836
|
+
allowSameNetMerges: options.allowSameNetMerges ?? false,
|
|
21036
21837
|
singleLayerPushAndShove: options.singleLayerPushAndShove ?? false,
|
|
21037
21838
|
singleLayerAdaptiveExits: options.singleLayerAdaptiveExits ?? false,
|
|
21038
21839
|
borderDistribution,
|
|
@@ -21093,44 +21894,8 @@ function getBusDepthInRows(bus) {
|
|
|
21093
21894
|
Math.abs(averageSource - outwardCoordinate) / directionalPitch
|
|
21094
21895
|
);
|
|
21095
21896
|
}
|
|
21096
|
-
function sourceLayerEscapeIsBlocked(params) {
|
|
21097
|
-
const { bus, srj, traceWidth, clearance } = params;
|
|
21098
|
-
for (const connection of bus.connections) {
|
|
21099
|
-
const source = {
|
|
21100
|
-
x: connection.sourcePoint.x,
|
|
21101
|
-
y: connection.sourcePoint.y
|
|
21102
|
-
};
|
|
21103
|
-
const boundaryPoint = (() => {
|
|
21104
|
-
switch (bus.direction) {
|
|
21105
|
-
case "left":
|
|
21106
|
-
return { x: bus.sharedBoundary.minX, y: source.y };
|
|
21107
|
-
case "right":
|
|
21108
|
-
return { x: bus.sharedBoundary.maxX, y: source.y };
|
|
21109
|
-
case "up":
|
|
21110
|
-
return { x: source.x, y: bus.sharedBoundary.maxY };
|
|
21111
|
-
case "down":
|
|
21112
|
-
return { x: source.x, y: bus.sharedBoundary.minY };
|
|
21113
|
-
}
|
|
21114
|
-
})();
|
|
21115
|
-
const directEscapeSegment = {
|
|
21116
|
-
start: source,
|
|
21117
|
-
end: boundaryPoint,
|
|
21118
|
-
width: traceWidth,
|
|
21119
|
-
layer: connection.sourceLayer
|
|
21120
|
-
};
|
|
21121
|
-
for (const obstacle of srj.obstacles) {
|
|
21122
|
-
if (obstacle === connection.sourceObstacle || !obstacle.layers.includes(connection.sourceLayer)) {
|
|
21123
|
-
continue;
|
|
21124
|
-
}
|
|
21125
|
-
if (distanceSegmentToObstacle(directEscapeSegment, obstacle) < traceWidth / 2 + clearance - 1e-9) {
|
|
21126
|
-
return true;
|
|
21127
|
-
}
|
|
21128
|
-
}
|
|
21129
|
-
}
|
|
21130
|
-
return false;
|
|
21131
|
-
}
|
|
21132
21897
|
function createPreferredLayerAssignment(params) {
|
|
21133
|
-
const { buses, escapeLayers,
|
|
21898
|
+
const { buses, escapeLayers, escapeLayersByBusId } = params;
|
|
21134
21899
|
const assignment = {};
|
|
21135
21900
|
const directionsByComponent = /* @__PURE__ */ new Map();
|
|
21136
21901
|
let nextViaLayerIndex = 0;
|
|
@@ -21148,13 +21913,11 @@ function createPreferredLayerAssignment(params) {
|
|
|
21148
21913
|
assignment[bus.busId] = bus.termination.layer;
|
|
21149
21914
|
continue;
|
|
21150
21915
|
}
|
|
21151
|
-
const
|
|
21152
|
-
|
|
21153
|
-
|
|
21154
|
-
|
|
21155
|
-
|
|
21156
|
-
clearance
|
|
21157
|
-
})) {
|
|
21916
|
+
const routableEscapeLayers = escapeLayersByBusId[bus.busId] ?? escapeLayers;
|
|
21917
|
+
const viaLayers = routableEscapeLayers.filter(
|
|
21918
|
+
(layer) => layer !== sourceLayer
|
|
21919
|
+
);
|
|
21920
|
+
if (routableEscapeLayers.includes(sourceLayer) && busIsOnOutwardComponentEdge2(bus)) {
|
|
21158
21921
|
assignment[bus.busId] = sourceLayer;
|
|
21159
21922
|
} else if (viaLayers.length > 0) {
|
|
21160
21923
|
const componentDirections = directionsByComponent.get(bus.componentId);
|
|
@@ -21182,6 +21945,26 @@ function prioritizeLayerAssignment(params) {
|
|
|
21182
21945
|
)
|
|
21183
21946
|
].slice(0, maxAssignments);
|
|
21184
21947
|
}
|
|
21948
|
+
function getCandidateEscapeLayersForBus(params) {
|
|
21949
|
+
const { bus, srj, config, staticClearanceCache } = params;
|
|
21950
|
+
const individuallyRoutableLayers = config.escapeLayers.filter(
|
|
21951
|
+
(targetLayer) => routeBus({
|
|
21952
|
+
srj,
|
|
21953
|
+
bus,
|
|
21954
|
+
targetLayer,
|
|
21955
|
+
acceptedPlans: [],
|
|
21956
|
+
layerNames: config.layerNames,
|
|
21957
|
+
traceWidth: config.traceWidth,
|
|
21958
|
+
viaDiameter: config.viaDiameter,
|
|
21959
|
+
viaHoleDiameter: config.viaHoleDiameter,
|
|
21960
|
+
clearance: config.clearance,
|
|
21961
|
+
compactBusTracks: config.compactBusTracks,
|
|
21962
|
+
allowSameNetMerges: config.allowSameNetMerges,
|
|
21963
|
+
staticClearanceCache
|
|
21964
|
+
}) !== null
|
|
21965
|
+
);
|
|
21966
|
+
return individuallyRoutableLayers.length > 0 ? individuallyRoutableLayers : config.escapeLayers;
|
|
21967
|
+
}
|
|
21185
21968
|
var FanoutSolver = class extends BaseSolver {
|
|
21186
21969
|
constructor(inputSrj, options = {}) {
|
|
21187
21970
|
super();
|
|
@@ -21211,9 +21994,27 @@ var FanoutSolver = class extends BaseSolver {
|
|
|
21211
21994
|
(bus) => bus.termination.type === "plane" ? [[bus.busId, bus.termination.layer]] : []
|
|
21212
21995
|
)
|
|
21213
21996
|
);
|
|
21997
|
+
const escapeLayersByBusId = Object.fromEntries(
|
|
21998
|
+
this.preparedBuses.flatMap((bus) => {
|
|
21999
|
+
if (bus.termination.type === "plane") return [];
|
|
22000
|
+
return [
|
|
22001
|
+
[
|
|
22002
|
+
bus.busId,
|
|
22003
|
+
getCandidateEscapeLayersForBus({
|
|
22004
|
+
bus,
|
|
22005
|
+
srj: inputSrj,
|
|
22006
|
+
config: this.config,
|
|
22007
|
+
staticClearanceCache: this.routeStaticClearanceCache
|
|
22008
|
+
})
|
|
22009
|
+
]
|
|
22010
|
+
];
|
|
22011
|
+
})
|
|
22012
|
+
);
|
|
22013
|
+
this.escapeLayersByBusId = escapeLayersByBusId;
|
|
21214
22014
|
const generatedAssignments = generateLayerAssignments({
|
|
21215
22015
|
busIds: boundaryBusIds,
|
|
21216
22016
|
layers: this.config.escapeLayers,
|
|
22017
|
+
layersByBusId: escapeLayersByBusId,
|
|
21217
22018
|
maxAssignments: this.config.maxLayerCombinations
|
|
21218
22019
|
}).map((assignment) => ({
|
|
21219
22020
|
...assignment,
|
|
@@ -21223,14 +22024,12 @@ var FanoutSolver = class extends BaseSolver {
|
|
|
21223
22024
|
preferredAssignment: createPreferredLayerAssignment({
|
|
21224
22025
|
buses: this.preparedBuses,
|
|
21225
22026
|
escapeLayers: this.config.escapeLayers,
|
|
21226
|
-
|
|
21227
|
-
traceWidth: this.config.traceWidth,
|
|
21228
|
-
clearance: this.config.clearance
|
|
22027
|
+
escapeLayersByBusId
|
|
21229
22028
|
}),
|
|
21230
22029
|
generatedAssignments,
|
|
21231
22030
|
maxAssignments: this.config.maxLayerCombinations
|
|
21232
22031
|
});
|
|
21233
|
-
this.MAX_ITERATIONS = this.
|
|
22032
|
+
this.MAX_ITERATIONS = this.config.maxLayerCombinations + 2;
|
|
21234
22033
|
}
|
|
21235
22034
|
inputSrj;
|
|
21236
22035
|
options;
|
|
@@ -21238,14 +22037,48 @@ var FanoutSolver = class extends BaseSolver {
|
|
|
21238
22037
|
attempts = [];
|
|
21239
22038
|
layerAssignments;
|
|
21240
22039
|
config;
|
|
22040
|
+
escapeLayersByBusId;
|
|
22041
|
+
evaluatedAssignmentKeys = /* @__PURE__ */ new Set();
|
|
22042
|
+
queuedAssignmentKeys = /* @__PURE__ */ new Set();
|
|
22043
|
+
assignmentRepairDepthByKey = /* @__PURE__ */ new Map();
|
|
22044
|
+
pendingRepairAssignments = [];
|
|
22045
|
+
routeStaticClearanceCache = /* @__PURE__ */ new Map();
|
|
22046
|
+
routingPrefixCache = /* @__PURE__ */ new Map();
|
|
22047
|
+
groupedBeamEvaluated = false;
|
|
21241
22048
|
nextAssignmentIndex = 0;
|
|
22049
|
+
nextGeneratedAssignmentIndex = 0;
|
|
21242
22050
|
bestAttempt = null;
|
|
21243
22051
|
getSolverName() {
|
|
21244
22052
|
return "FanoutSolver";
|
|
21245
22053
|
}
|
|
21246
|
-
|
|
21247
|
-
|
|
21248
|
-
const
|
|
22054
|
+
getValidationBoundary() {
|
|
22055
|
+
if (this.options.sharedBoundary) return this.options.sharedBoundary;
|
|
22056
|
+
const firstBoundary = this.preparedBuses[0]?.sharedBoundary;
|
|
22057
|
+
if (!firstBoundary) return this.inputSrj.bounds;
|
|
22058
|
+
return this.preparedBuses.slice(1).reduce(
|
|
22059
|
+
(boundary, bus) => ({
|
|
22060
|
+
minX: Math.min(boundary.minX, bus.sharedBoundary.minX),
|
|
22061
|
+
maxX: Math.max(boundary.maxX, bus.sharedBoundary.maxX),
|
|
22062
|
+
minY: Math.min(boundary.minY, bus.sharedBoundary.minY),
|
|
22063
|
+
maxY: Math.max(boundary.maxY, bus.sharedBoundary.maxY)
|
|
22064
|
+
}),
|
|
22065
|
+
{ ...firstBoundary }
|
|
22066
|
+
);
|
|
22067
|
+
}
|
|
22068
|
+
validateCompletePlans(plans, outputSrj) {
|
|
22069
|
+
return validateFanoutSolution({
|
|
22070
|
+
inputSrj: this.inputSrj,
|
|
22071
|
+
outputSrj,
|
|
22072
|
+
plans,
|
|
22073
|
+
preparedBuses: this.preparedBuses,
|
|
22074
|
+
sharedBoundary: this.getValidationBoundary(),
|
|
22075
|
+
clearance: this.config.clearance
|
|
22076
|
+
});
|
|
22077
|
+
}
|
|
22078
|
+
evaluateAssignmentWithStrategy(assignmentIndex, busLayerAssignments, routingStrategy) {
|
|
22079
|
+
let plans = [];
|
|
22080
|
+
let failedBusIds = [];
|
|
22081
|
+
let blockingBusCounts = /* @__PURE__ */ new Map();
|
|
21249
22082
|
const isSingleLayerFanout = this.config.escapeLayers.length === 1;
|
|
21250
22083
|
if (isSingleLayerFanout && this.config.singleLayerPushAndShove) {
|
|
21251
22084
|
const singleLayerParams = {
|
|
@@ -21268,8 +22101,11 @@ var FanoutSolver = class extends BaseSolver {
|
|
|
21268
22101
|
}
|
|
21269
22102
|
}
|
|
21270
22103
|
const busesInRoutingOrder = [...this.preparedBuses].sort(
|
|
21271
|
-
(a, b) => Number(a.termination.type === "plane") - Number(b.termination.type === "plane") ||
|
|
22104
|
+
(a, b) => Number(a.termination.type === "plane") - Number(b.termination.type === "plane") || (routingStrategy === "group-by-layer" ? (busLayerAssignments[a.busId] ?? "").localeCompare(
|
|
22105
|
+
busLayerAssignments[b.busId] ?? ""
|
|
22106
|
+
) : 0) || b.componentObstacles.length - a.componentObstacles.length || (isSingleLayerFanout ? getBusDistanceToBoundary(b) - getBusDistanceToBoundary(a) : b.connections.length - a.connections.length || (routingStrategy === "deep-first" ? getBusDistanceToBoundary(b) - getBusDistanceToBoundary(a) : getBusDistanceToBoundary(a) - getBusDistanceToBoundary(b)))
|
|
21272
22107
|
);
|
|
22108
|
+
let routingPrefixKey = `${routingStrategy}|`;
|
|
21273
22109
|
for (const bus of isSingleLayerFanout && this.config.singleLayerPushAndShove ? [] : busesInRoutingOrder) {
|
|
21274
22110
|
const targetLayer = busLayerAssignments[bus.busId];
|
|
21275
22111
|
if (!targetLayer) {
|
|
@@ -21277,6 +22113,15 @@ var FanoutSolver = class extends BaseSolver {
|
|
|
21277
22113
|
`FanoutSolver: assignment ${assignmentIndex} has no layer for bus "${bus.busId}"`
|
|
21278
22114
|
);
|
|
21279
22115
|
}
|
|
22116
|
+
routingPrefixKey += `${bus.busId.length}:${bus.busId};${targetLayer.length}:${targetLayer};`;
|
|
22117
|
+
const cachedPrefix = this.routingPrefixCache.get(routingPrefixKey);
|
|
22118
|
+
if (cachedPrefix) {
|
|
22119
|
+
plans = [...cachedPrefix.plans];
|
|
22120
|
+
failedBusIds = [...cachedPrefix.failedBusIds];
|
|
22121
|
+
blockingBusCounts = new Map(cachedPrefix.blockingBusCounts);
|
|
22122
|
+
continue;
|
|
22123
|
+
}
|
|
22124
|
+
const currentBusBlockingCounts = /* @__PURE__ */ new Map();
|
|
21280
22125
|
const busPlans = routeBus({
|
|
21281
22126
|
srj: this.inputSrj,
|
|
21282
22127
|
bus,
|
|
@@ -21287,13 +22132,45 @@ var FanoutSolver = class extends BaseSolver {
|
|
|
21287
22132
|
viaDiameter: this.config.viaDiameter,
|
|
21288
22133
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
21289
22134
|
clearance: this.config.clearance,
|
|
21290
|
-
compactBusTracks: this.config.compactBusTracks
|
|
22135
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
22136
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
22137
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
22138
|
+
blockingBusCounts: currentBusBlockingCounts
|
|
21291
22139
|
});
|
|
21292
22140
|
if (!busPlans) {
|
|
21293
22141
|
failedBusIds.push(bus.busId);
|
|
21294
|
-
|
|
22142
|
+
for (const [blockingBusId, count] of currentBusBlockingCounts) {
|
|
22143
|
+
blockingBusCounts.set(
|
|
22144
|
+
blockingBusId,
|
|
22145
|
+
(blockingBusCounts.get(blockingBusId) ?? 0) + count
|
|
22146
|
+
);
|
|
22147
|
+
}
|
|
22148
|
+
} else {
|
|
22149
|
+
plans.push(...busPlans);
|
|
21295
22150
|
}
|
|
21296
|
-
|
|
22151
|
+
this.routingPrefixCache.set(routingPrefixKey, {
|
|
22152
|
+
plans: [...plans],
|
|
22153
|
+
failedBusIds: [...failedBusIds],
|
|
22154
|
+
blockingBusCounts: new Map(blockingBusCounts)
|
|
22155
|
+
});
|
|
22156
|
+
}
|
|
22157
|
+
let validationIssues;
|
|
22158
|
+
let outputSrj = buildOutputSimpleRouteJson({
|
|
22159
|
+
inputSrj: this.inputSrj,
|
|
22160
|
+
plans,
|
|
22161
|
+
layerNames: this.config.layerNames
|
|
22162
|
+
});
|
|
22163
|
+
const validation = plans.length === this.inputSrj.connections.length ? this.validateCompletePlans(plans, outputSrj) : null;
|
|
22164
|
+
if (validation && !validation.valid) {
|
|
22165
|
+
validationIssues = validation.issues;
|
|
22166
|
+
plans = [];
|
|
22167
|
+
failedBusIds = this.preparedBuses.map((bus) => bus.busId);
|
|
22168
|
+
blockingBusCounts.clear();
|
|
22169
|
+
outputSrj = buildOutputSimpleRouteJson({
|
|
22170
|
+
inputSrj: this.inputSrj,
|
|
22171
|
+
plans,
|
|
22172
|
+
layerNames: this.config.layerNames
|
|
22173
|
+
});
|
|
21297
22174
|
}
|
|
21298
22175
|
const routedBusCount = this.preparedBuses.length - failedBusIds.length;
|
|
21299
22176
|
const routeLength = plans.reduce((total, plan) => total + plan.length, 0);
|
|
@@ -21305,20 +22182,271 @@ var FanoutSolver = class extends BaseSolver {
|
|
|
21305
22182
|
routedBusCount,
|
|
21306
22183
|
routedConnectionCount: plans.length,
|
|
21307
22184
|
failedBusIds,
|
|
21308
|
-
score
|
|
22185
|
+
score,
|
|
22186
|
+
...validationIssues ? { validationIssues } : {}
|
|
21309
22187
|
};
|
|
21310
22188
|
return {
|
|
21311
22189
|
summary,
|
|
21312
22190
|
plans,
|
|
21313
|
-
|
|
21314
|
-
|
|
21315
|
-
plans,
|
|
21316
|
-
layerNames: this.config.layerNames
|
|
21317
|
-
})
|
|
22191
|
+
blockingBusIds: [...blockingBusCounts.entries()].toSorted(([, firstCount], [, secondCount]) => secondCount - firstCount).map(([busId]) => busId),
|
|
22192
|
+
outputSrj
|
|
21318
22193
|
};
|
|
21319
22194
|
}
|
|
22195
|
+
evaluateAssignment(assignmentIndex, busLayerAssignments) {
|
|
22196
|
+
let bestAttempt = this.evaluateAssignmentWithStrategy(
|
|
22197
|
+
assignmentIndex,
|
|
22198
|
+
busLayerAssignments,
|
|
22199
|
+
"default"
|
|
22200
|
+
);
|
|
22201
|
+
if (bestAttempt.summary.routedConnectionCount === this.inputSrj.connections.length) {
|
|
22202
|
+
return bestAttempt;
|
|
22203
|
+
}
|
|
22204
|
+
for (const routingStrategy of ["group-by-layer", "deep-first"]) {
|
|
22205
|
+
const attempt = this.evaluateAssignmentWithStrategy(
|
|
22206
|
+
assignmentIndex,
|
|
22207
|
+
busLayerAssignments,
|
|
22208
|
+
routingStrategy
|
|
22209
|
+
);
|
|
22210
|
+
if (attempt.summary.score < bestAttempt.summary.score) {
|
|
22211
|
+
bestAttempt = attempt;
|
|
22212
|
+
}
|
|
22213
|
+
if (bestAttempt.summary.routedConnectionCount === this.inputSrj.connections.length) {
|
|
22214
|
+
return bestAttempt;
|
|
22215
|
+
}
|
|
22216
|
+
}
|
|
22217
|
+
return bestAttempt;
|
|
22218
|
+
}
|
|
22219
|
+
/**
|
|
22220
|
+
* Search layer assignments and track alternatives together. The regular
|
|
22221
|
+
* assignment loop commits to one route per bus before the next bus is
|
|
22222
|
+
* considered, so a locally-valid track can still starve a later bus. A
|
|
22223
|
+
* bounded beam keeps several grouped-layer route prefixes alive. It also
|
|
22224
|
+
* evaluates multi-connection buses atomically, so one promising route for a
|
|
22225
|
+
* power or signal lane cannot starve a later bus before the solver explores
|
|
22226
|
+
* an alternate layer/track combination.
|
|
22227
|
+
*/
|
|
22228
|
+
evaluateGroupedBeam(assignmentIndex, groupByDirection = false) {
|
|
22229
|
+
if (this.config.escapeLayers.length < 2) return null;
|
|
22230
|
+
if (this.preparedBuses.length > 56) return null;
|
|
22231
|
+
const totalConnections = this.inputSrj.connections.length;
|
|
22232
|
+
if (totalConnections > 64) return null;
|
|
22233
|
+
if (new Set(this.preparedBuses.map((bus) => bus.componentId)).size !== 1) {
|
|
22234
|
+
return null;
|
|
22235
|
+
}
|
|
22236
|
+
const busesInSearchOrder = [...this.preparedBuses].sort((a, b) => {
|
|
22237
|
+
const aLayerCount = a.termination.type === "plane" ? 1 : this.escapeLayersByBusId[a.busId]?.length ?? this.config.escapeLayers.length;
|
|
22238
|
+
const bLayerCount = b.termination.type === "plane" ? 1 : this.escapeLayersByBusId[b.busId]?.length ?? this.config.escapeLayers.length;
|
|
22239
|
+
return Number(a.termination.type === "plane") - Number(b.termination.type === "plane") || (groupByDirection ? a.direction.localeCompare(b.direction) : 0) || aLayerCount - bLayerCount || b.componentObstacles.length - a.componentObstacles.length || b.connections.length - a.connections.length || getBusDepthInRows(b) - getBusDepthInRows(a) || a.busId.localeCompare(b.busId);
|
|
22240
|
+
});
|
|
22241
|
+
const isSmallProblem = totalConnections <= 24;
|
|
22242
|
+
const hasMultiConnectionBus = this.preparedBuses.some(
|
|
22243
|
+
(bus) => bus.connections.length > 1
|
|
22244
|
+
);
|
|
22245
|
+
const beamWidth = isSmallProblem ? 48 : totalConnections <= 32 ? 24 : 12;
|
|
22246
|
+
const alternativesPerLayer = isSmallProblem && !hasMultiConnectionBus ? 4 : 1;
|
|
22247
|
+
let states = [{ assignment: {}, plans: [] }];
|
|
22248
|
+
const getStateScore = (state) => {
|
|
22249
|
+
const routeLength = state.plans.reduce(
|
|
22250
|
+
(total, plan) => total + plan.length,
|
|
22251
|
+
0
|
|
22252
|
+
);
|
|
22253
|
+
const viaCount = state.plans.filter((plan) => plan.via).length;
|
|
22254
|
+
return routeLength + viaCount * 0.1 + assignmentLoadPenalty(state.assignment) * 0.01;
|
|
22255
|
+
};
|
|
22256
|
+
for (const bus of busesInSearchOrder) {
|
|
22257
|
+
const nextStates = [];
|
|
22258
|
+
for (const state of states) {
|
|
22259
|
+
const candidateLayers = bus.termination.type === "plane" ? [bus.termination.layer] : this.escapeLayersByBusId[bus.busId] ?? this.config.escapeLayers;
|
|
22260
|
+
const layerLoads = /* @__PURE__ */ new Map();
|
|
22261
|
+
for (const layer of Object.values(state.assignment)) {
|
|
22262
|
+
layerLoads.set(layer, (layerLoads.get(layer) ?? 0) + 1);
|
|
22263
|
+
}
|
|
22264
|
+
const sourceLayer = bus.connections[0]?.sourceLayer;
|
|
22265
|
+
const orderedLayers = candidateLayers.toSorted(
|
|
22266
|
+
(first, second) => (layerLoads.get(first) ?? 0) - (layerLoads.get(second) ?? 0) || Number(first === sourceLayer) - Number(second === sourceLayer) || first.localeCompare(second)
|
|
22267
|
+
);
|
|
22268
|
+
for (const targetLayer of orderedLayers) {
|
|
22269
|
+
const busAlternatives = routeBusAlternatives(
|
|
22270
|
+
{
|
|
22271
|
+
srj: this.inputSrj,
|
|
22272
|
+
bus,
|
|
22273
|
+
targetLayer,
|
|
22274
|
+
acceptedPlans: state.plans,
|
|
22275
|
+
layerNames: this.config.layerNames,
|
|
22276
|
+
traceWidth: this.config.traceWidth,
|
|
22277
|
+
viaDiameter: this.config.viaDiameter,
|
|
22278
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
22279
|
+
clearance: this.config.clearance,
|
|
22280
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
22281
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
22282
|
+
staticClearanceCache: this.routeStaticClearanceCache
|
|
22283
|
+
},
|
|
22284
|
+
alternativesPerLayer
|
|
22285
|
+
);
|
|
22286
|
+
for (const busPlans of busAlternatives) {
|
|
22287
|
+
nextStates.push({
|
|
22288
|
+
assignment: {
|
|
22289
|
+
...state.assignment,
|
|
22290
|
+
[bus.busId]: targetLayer
|
|
22291
|
+
},
|
|
22292
|
+
plans: [...state.plans, ...busPlans]
|
|
22293
|
+
});
|
|
22294
|
+
}
|
|
22295
|
+
}
|
|
22296
|
+
}
|
|
22297
|
+
if (nextStates.length === 0) return null;
|
|
22298
|
+
nextStates.sort((first, second) => {
|
|
22299
|
+
const scoreDifference = getStateScore(first) - getStateScore(second);
|
|
22300
|
+
if (Math.abs(scoreDifference) > 1e-9) return scoreDifference;
|
|
22301
|
+
return JSON.stringify(first.assignment).localeCompare(
|
|
22302
|
+
JSON.stringify(second.assignment)
|
|
22303
|
+
);
|
|
22304
|
+
});
|
|
22305
|
+
const statesByAssignment = /* @__PURE__ */ new Map();
|
|
22306
|
+
states = [];
|
|
22307
|
+
for (const state of nextStates) {
|
|
22308
|
+
const key = JSON.stringify(state.assignment);
|
|
22309
|
+
const sameAssignmentCount = statesByAssignment.get(key) ?? 0;
|
|
22310
|
+
if (sameAssignmentCount >= 2) continue;
|
|
22311
|
+
statesByAssignment.set(key, sameAssignmentCount + 1);
|
|
22312
|
+
states.push(state);
|
|
22313
|
+
if (states.length >= beamWidth) break;
|
|
22314
|
+
}
|
|
22315
|
+
}
|
|
22316
|
+
const bestState = states[0];
|
|
22317
|
+
if (!bestState) return null;
|
|
22318
|
+
const outputSrj = buildOutputSimpleRouteJson({
|
|
22319
|
+
inputSrj: this.inputSrj,
|
|
22320
|
+
plans: bestState.plans,
|
|
22321
|
+
layerNames: this.config.layerNames
|
|
22322
|
+
});
|
|
22323
|
+
if (bestState.plans.length === this.inputSrj.connections.length && !this.validateCompletePlans(bestState.plans, outputSrj).valid) {
|
|
22324
|
+
return null;
|
|
22325
|
+
}
|
|
22326
|
+
const score = bestState.plans.length === this.inputSrj.connections.length ? bestState.plans.reduce((total, plan) => total + plan.length, 0) + bestState.plans.filter((plan) => plan.via).length * 0.1 + assignmentLoadPenalty(bestState.assignment) * 0.01 : Number.POSITIVE_INFINITY;
|
|
22327
|
+
if (!Number.isFinite(score)) return null;
|
|
22328
|
+
const summary = {
|
|
22329
|
+
assignmentIndex,
|
|
22330
|
+
busLayerAssignments: bestState.assignment,
|
|
22331
|
+
routedBusCount: this.preparedBuses.length,
|
|
22332
|
+
routedConnectionCount: bestState.plans.length,
|
|
22333
|
+
failedBusIds: [],
|
|
22334
|
+
score
|
|
22335
|
+
};
|
|
22336
|
+
return {
|
|
22337
|
+
summary,
|
|
22338
|
+
plans: bestState.plans,
|
|
22339
|
+
blockingBusIds: [],
|
|
22340
|
+
outputSrj
|
|
22341
|
+
};
|
|
22342
|
+
}
|
|
22343
|
+
prioritizeFailedBusRepairs(assignment, failedBusIds, blockingBusIds) {
|
|
22344
|
+
const assignmentKey = JSON.stringify(assignment);
|
|
22345
|
+
const repairDepth = this.assignmentRepairDepthByKey.get(assignmentKey) ?? 0;
|
|
22346
|
+
if (repairDepth >= 2) return;
|
|
22347
|
+
const maximumRepairs = 8;
|
|
22348
|
+
const repairs = [];
|
|
22349
|
+
const repairKeys = /* @__PURE__ */ new Set();
|
|
22350
|
+
const addRepair = (repair) => {
|
|
22351
|
+
const key = JSON.stringify(repair);
|
|
22352
|
+
if (repairKeys.has(key) || this.evaluatedAssignmentKeys.has(key) || this.queuedAssignmentKeys.has(key)) {
|
|
22353
|
+
return;
|
|
22354
|
+
}
|
|
22355
|
+
repairKeys.add(key);
|
|
22356
|
+
this.queuedAssignmentKeys.add(key);
|
|
22357
|
+
this.assignmentRepairDepthByKey.set(key, repairDepth + 1);
|
|
22358
|
+
repairs.push(repair);
|
|
22359
|
+
};
|
|
22360
|
+
const repairBusIds = [];
|
|
22361
|
+
for (let index = 0; index < Math.max(failedBusIds.length, blockingBusIds.length); index++) {
|
|
22362
|
+
const failedBusId = failedBusIds[index];
|
|
22363
|
+
const blockingBusId = blockingBusIds[index];
|
|
22364
|
+
if (failedBusId && !repairBusIds.includes(failedBusId)) {
|
|
22365
|
+
repairBusIds.push(failedBusId);
|
|
22366
|
+
}
|
|
22367
|
+
if (blockingBusId && !repairBusIds.includes(blockingBusId)) {
|
|
22368
|
+
repairBusIds.push(blockingBusId);
|
|
22369
|
+
}
|
|
22370
|
+
}
|
|
22371
|
+
for (const failedBusId of failedBusIds) {
|
|
22372
|
+
const failedLayer = assignment[failedBusId];
|
|
22373
|
+
const failedCandidateLayers = this.escapeLayersByBusId[failedBusId];
|
|
22374
|
+
if (!failedLayer || !failedCandidateLayers) continue;
|
|
22375
|
+
for (const blockingBusId of blockingBusIds.slice(0, 4)) {
|
|
22376
|
+
const blockingLayer = assignment[blockingBusId];
|
|
22377
|
+
const blockingCandidateLayers = this.escapeLayersByBusId[blockingBusId];
|
|
22378
|
+
if (!blockingLayer || !blockingCandidateLayers || !failedCandidateLayers.includes(blockingLayer) || !blockingCandidateLayers.includes(failedLayer)) {
|
|
22379
|
+
continue;
|
|
22380
|
+
}
|
|
22381
|
+
addRepair({
|
|
22382
|
+
...assignment,
|
|
22383
|
+
[failedBusId]: blockingLayer,
|
|
22384
|
+
[blockingBusId]: failedLayer
|
|
22385
|
+
});
|
|
22386
|
+
if (repairs.length >= maximumRepairs) break;
|
|
22387
|
+
}
|
|
22388
|
+
if (repairs.length >= maximumRepairs) break;
|
|
22389
|
+
}
|
|
22390
|
+
for (const busId of repairBusIds) {
|
|
22391
|
+
const currentLayer = assignment[busId];
|
|
22392
|
+
const candidateLayers = this.escapeLayersByBusId[busId];
|
|
22393
|
+
if (!currentLayer || !candidateLayers) continue;
|
|
22394
|
+
const currentLayerIndex = candidateLayers.indexOf(currentLayer);
|
|
22395
|
+
for (let shift = 1; shift < candidateLayers.length; shift++) {
|
|
22396
|
+
const candidateLayer = candidateLayers[(Math.max(currentLayerIndex, 0) + shift) % candidateLayers.length];
|
|
22397
|
+
if (candidateLayer === currentLayer) continue;
|
|
22398
|
+
addRepair({ ...assignment, [busId]: candidateLayer });
|
|
22399
|
+
if (repairs.length >= maximumRepairs) break;
|
|
22400
|
+
}
|
|
22401
|
+
if (repairs.length >= maximumRepairs) break;
|
|
22402
|
+
}
|
|
22403
|
+
this.pendingRepairAssignments.push(...repairs);
|
|
22404
|
+
}
|
|
21320
22405
|
_step() {
|
|
21321
|
-
|
|
22406
|
+
if (!this.groupedBeamEvaluated) {
|
|
22407
|
+
this.groupedBeamEvaluated = true;
|
|
22408
|
+
let beamAttempt = this.evaluateGroupedBeam(-1);
|
|
22409
|
+
if (!beamAttempt) {
|
|
22410
|
+
beamAttempt = this.evaluateGroupedBeam(-1, true);
|
|
22411
|
+
}
|
|
22412
|
+
if (beamAttempt) {
|
|
22413
|
+
this.attempts.push(beamAttempt.summary);
|
|
22414
|
+
this.bestAttempt = beamAttempt;
|
|
22415
|
+
this.stats = {
|
|
22416
|
+
assignment: 0,
|
|
22417
|
+
assignmentCount: this.config.maxLayerCombinations,
|
|
22418
|
+
routedBuses: `${beamAttempt.summary.routedBusCount}/${this.preparedBuses.length}`,
|
|
22419
|
+
routedConnections: `${beamAttempt.summary.routedConnectionCount}/${this.inputSrj.connections.length}`,
|
|
22420
|
+
failedBuses: "none",
|
|
22421
|
+
bestScore: beamAttempt.summary.score
|
|
22422
|
+
};
|
|
22423
|
+
this.solved = true;
|
|
22424
|
+
return;
|
|
22425
|
+
}
|
|
22426
|
+
}
|
|
22427
|
+
let assignment;
|
|
22428
|
+
while (!assignment && this.nextAssignmentIndex < this.config.maxLayerCombinations) {
|
|
22429
|
+
const preferGeneratedAssignment = this.nextAssignmentIndex % 3 === 0;
|
|
22430
|
+
let candidate;
|
|
22431
|
+
let candidateCameFromRepairQueue = false;
|
|
22432
|
+
if (preferGeneratedAssignment) {
|
|
22433
|
+
candidate = this.layerAssignments[this.nextGeneratedAssignmentIndex++];
|
|
22434
|
+
} else {
|
|
22435
|
+
candidate = this.pendingRepairAssignments.pop();
|
|
22436
|
+
candidateCameFromRepairQueue = candidate !== void 0;
|
|
22437
|
+
}
|
|
22438
|
+
if (!candidate) {
|
|
22439
|
+
candidate = preferGeneratedAssignment ? this.pendingRepairAssignments.pop() : this.layerAssignments[this.nextGeneratedAssignmentIndex++];
|
|
22440
|
+
candidateCameFromRepairQueue = preferGeneratedAssignment && candidate !== void 0;
|
|
22441
|
+
}
|
|
22442
|
+
if (!candidate) break;
|
|
22443
|
+
const candidateKey = JSON.stringify(candidate);
|
|
22444
|
+
if (candidateCameFromRepairQueue) {
|
|
22445
|
+
this.queuedAssignmentKeys.delete(candidateKey);
|
|
22446
|
+
}
|
|
22447
|
+
if (this.evaluatedAssignmentKeys.has(candidateKey)) continue;
|
|
22448
|
+
assignment = candidate;
|
|
22449
|
+
}
|
|
21322
22450
|
if (!assignment) {
|
|
21323
22451
|
if (this.bestAttempt && this.bestAttempt.summary.routedConnectionCount === this.inputSrj.connections.length) {
|
|
21324
22452
|
this.solved = true;
|
|
@@ -21333,13 +22461,21 @@ var FanoutSolver = class extends BaseSolver {
|
|
|
21333
22461
|
assignment
|
|
21334
22462
|
);
|
|
21335
22463
|
this.nextAssignmentIndex++;
|
|
22464
|
+
this.evaluatedAssignmentKeys.add(JSON.stringify(assignment));
|
|
22465
|
+
if (!this.bestAttempt || attempt.summary.routedConnectionCount >= this.bestAttempt.summary.routedConnectionCount) {
|
|
22466
|
+
this.prioritizeFailedBusRepairs(
|
|
22467
|
+
assignment,
|
|
22468
|
+
attempt.summary.failedBusIds,
|
|
22469
|
+
attempt.blockingBusIds
|
|
22470
|
+
);
|
|
22471
|
+
}
|
|
21336
22472
|
this.attempts.push(attempt.summary);
|
|
21337
22473
|
if (!this.bestAttempt || attempt.summary.score < this.bestAttempt.summary.score) {
|
|
21338
22474
|
this.bestAttempt = attempt;
|
|
21339
22475
|
}
|
|
21340
22476
|
this.stats = {
|
|
21341
22477
|
assignment: attempt.summary.assignmentIndex + 1,
|
|
21342
|
-
assignmentCount: this.
|
|
22478
|
+
assignmentCount: this.config.maxLayerCombinations,
|
|
21343
22479
|
routedBuses: `${attempt.summary.routedBusCount}/${this.preparedBuses.length}`,
|
|
21344
22480
|
routedConnections: `${attempt.summary.routedConnectionCount}/${this.inputSrj.connections.length}`,
|
|
21345
22481
|
failedBuses: attempt.summary.failedBusIds.join(", ") || "none",
|
|
@@ -21351,7 +22487,7 @@ var FanoutSolver = class extends BaseSolver {
|
|
|
21351
22487
|
}
|
|
21352
22488
|
computeProgress() {
|
|
21353
22489
|
if (this.solved || this.failed) return 1;
|
|
21354
|
-
return this.nextAssignmentIndex / this.
|
|
22490
|
+
return this.nextAssignmentIndex / this.config.maxLayerCombinations;
|
|
21355
22491
|
}
|
|
21356
22492
|
getConstructorParams() {
|
|
21357
22493
|
return [this.inputSrj, this.options];
|
|
@@ -21362,6 +22498,15 @@ var FanoutSolver = class extends BaseSolver {
|
|
|
21362
22498
|
"FanoutSolver: getOutput() called before a complete fanout was solved"
|
|
21363
22499
|
);
|
|
21364
22500
|
}
|
|
22501
|
+
const validation = this.validateCompletePlans(
|
|
22502
|
+
this.bestAttempt.plans,
|
|
22503
|
+
this.bestAttempt.outputSrj
|
|
22504
|
+
);
|
|
22505
|
+
if (!validation.valid) {
|
|
22506
|
+
throw new Error(
|
|
22507
|
+
`FanoutSolver: completed output failed validation: ${validation.issues[0]?.message ?? "unknown validation error"}`
|
|
22508
|
+
);
|
|
22509
|
+
}
|
|
21365
22510
|
return {
|
|
21366
22511
|
simpleRouteJson: this.bestAttempt.outputSrj,
|
|
21367
22512
|
fanoutTraces: this.bestAttempt.plans.map((plan) => plan.trace),
|
|
@@ -21379,7 +22524,8 @@ var FanoutSolver = class extends BaseSolver {
|
|
|
21379
22524
|
busDirections: Object.fromEntries(
|
|
21380
22525
|
this.preparedBuses.map((bus) => [bus.busId, bus.direction])
|
|
21381
22526
|
),
|
|
21382
|
-
attempts: [...this.attempts]
|
|
22527
|
+
attempts: [...this.attempts],
|
|
22528
|
+
validation
|
|
21383
22529
|
};
|
|
21384
22530
|
}
|
|
21385
22531
|
getOutputSimpleRouteJson() {
|
|
@@ -31458,7 +32604,7 @@ function Group_getRoutingPhasePlans(group) {
|
|
|
31458
32604
|
var package_default = {
|
|
31459
32605
|
name: "@tscircuit/core",
|
|
31460
32606
|
type: "module",
|
|
31461
|
-
version: "0.0.
|
|
32607
|
+
version: "0.0.1651",
|
|
31462
32608
|
types: "dist/index.d.ts",
|
|
31463
32609
|
main: "dist/index.js",
|
|
31464
32610
|
module: "dist/index.js",
|
|
@@ -31498,7 +32644,7 @@ var package_default = {
|
|
|
31498
32644
|
"@tscircuit/common": "^0.0.20",
|
|
31499
32645
|
"@tscircuit/copper-pour-solver": "0.0.44",
|
|
31500
32646
|
"@tscircuit/create-fdm-enclosure": "0.0.3",
|
|
31501
|
-
"@tscircuit/fanout-solver": "0.0.
|
|
32647
|
+
"@tscircuit/fanout-solver": "0.0.20",
|
|
31502
32648
|
"@tscircuit/footprinter": "^0.0.409",
|
|
31503
32649
|
"@tscircuit/image-utils": "^0.0.8",
|
|
31504
32650
|
"@tscircuit/infer-cable-insertion-point": "^0.0.3",
|
|
@@ -31755,7 +32901,7 @@ function Group_applyDrcTolerancesToSimpleRouteJson(simpleRouteJson, drcTolerance
|
|
|
31755
32901
|
|
|
31756
32902
|
// lib/components/primitive-components/Group/Group_syncFanoutExitsWithGlobalConnections.ts
|
|
31757
32903
|
var POINT_MATCH_TOLERANCE = 1e-6;
|
|
31758
|
-
var
|
|
32904
|
+
var pointsMatch2 = (first, second) => Math.abs(first.x - second.x) <= POINT_MATCH_TOLERANCE && Math.abs(first.y - second.y) <= POINT_MATCH_TOLERANCE && first.layer === second.layer;
|
|
31759
32905
|
var removeCompletedFanoutConnections = (simpleRouteJson, routingPcbGroupId) => {
|
|
31760
32906
|
const connections = simpleRouteJson.connections.filter(
|
|
31761
32907
|
(connection) => connection.routingPcbGroupId !== routingPcbGroupId
|
|
@@ -31827,14 +32973,14 @@ function Group_syncFanoutExitsWithGlobalConnections({
|
|
|
31827
32973
|
const globalConnection = baseConnections.find(
|
|
31828
32974
|
(connection) => connection.routingPcbGroupId !== routingPcbGroupId && connection.source_trace_id === inputConnection.source_trace_id && connection.pointsToConnect.some(
|
|
31829
32975
|
(globalPoint) => inputConnection.pointsToConnect.some(
|
|
31830
|
-
(inputPoint) =>
|
|
32976
|
+
(inputPoint) => pointsMatch2(globalPoint, inputPoint)
|
|
31831
32977
|
)
|
|
31832
32978
|
)
|
|
31833
32979
|
);
|
|
31834
32980
|
if (!globalConnection) continue;
|
|
31835
32981
|
const previousGlobalPointIndex = globalConnection.pointsToConnect.findIndex(
|
|
31836
32982
|
(globalPoint) => inputConnection.pointsToConnect.some(
|
|
31837
|
-
(inputPoint) =>
|
|
32983
|
+
(inputPoint) => pointsMatch2(globalPoint, inputPoint)
|
|
31838
32984
|
)
|
|
31839
32985
|
);
|
|
31840
32986
|
if (previousGlobalPointIndex < 0) continue;
|
|
@@ -31842,7 +32988,7 @@ function Group_syncFanoutExitsWithGlobalConnections({
|
|
|
31842
32988
|
const changedPointIndex = outputConnection.pointsToConnect.findIndex(
|
|
31843
32989
|
(outputPoint, pointIndex) => {
|
|
31844
32990
|
const inputPoint = inputConnection.pointsToConnect[pointIndex];
|
|
31845
|
-
return inputPoint !== void 0 && !
|
|
32991
|
+
return inputPoint !== void 0 && !pointsMatch2(outputPoint, inputPoint);
|
|
31846
32992
|
}
|
|
31847
32993
|
);
|
|
31848
32994
|
if (changedPointIndex < 0) continue;
|
|
@@ -40195,15 +41341,15 @@ import {
|
|
|
40195
41341
|
} from "@tscircuit/copper-pour-solver";
|
|
40196
41342
|
|
|
40197
41343
|
// lib/components/primitive-components/CopperPour/utils/mark-trace-segments-inside-copper-pour.ts
|
|
40198
|
-
var
|
|
41344
|
+
var EPSILON4 = 1e-9;
|
|
40199
41345
|
var isWireRoutePoint = (routePoint) => routePoint.route_type === "wire";
|
|
40200
41346
|
var isPointOnSegment = (p, a, b) => {
|
|
40201
41347
|
const cross3 = (p.y - a.y) * (b.x - a.x) - (p.x - a.x) * (b.y - a.y);
|
|
40202
|
-
if (Math.abs(cross3) >
|
|
41348
|
+
if (Math.abs(cross3) > EPSILON4) return false;
|
|
40203
41349
|
const dot = (p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y);
|
|
40204
|
-
if (dot < -
|
|
41350
|
+
if (dot < -EPSILON4) return false;
|
|
40205
41351
|
const squaredLength = (b.x - a.x) ** 2 + (b.y - a.y) ** 2;
|
|
40206
|
-
if (dot - squaredLength >
|
|
41352
|
+
if (dot - squaredLength > EPSILON4) return false;
|
|
40207
41353
|
return true;
|
|
40208
41354
|
};
|
|
40209
41355
|
var isPointInRing = (point6, ring) => {
|
|
@@ -40227,7 +41373,7 @@ var isPointInRectPour = (p, pour) => {
|
|
|
40227
41373
|
const dy = p.y - center.y;
|
|
40228
41374
|
const localX = dx * cosR - dy * sinR;
|
|
40229
41375
|
const localY = dx * sinR + dy * cosR;
|
|
40230
|
-
return Math.abs(localX) <= width / 2 +
|
|
41376
|
+
return Math.abs(localX) <= width / 2 + EPSILON4 && Math.abs(localY) <= height / 2 + EPSILON4;
|
|
40231
41377
|
};
|
|
40232
41378
|
var isPointInBrepPour = (p, pour) => {
|
|
40233
41379
|
const outerRing = pour.brep_shape.outer_ring.vertices.map((v) => ({
|
|
@@ -40261,7 +41407,7 @@ var isSegmentFullyInsideCopperPour = (start, end, pour) => {
|
|
|
40261
41407
|
const dx = end.x - start.x;
|
|
40262
41408
|
const dy = end.y - start.y;
|
|
40263
41409
|
const length7 = Math.hypot(dx, dy);
|
|
40264
|
-
if (length7 <=
|
|
41410
|
+
if (length7 <= EPSILON4) return false;
|
|
40265
41411
|
const samples = [0, 0.25, 0.5, 0.75, 1];
|
|
40266
41412
|
return samples.every(
|
|
40267
41413
|
(t) => isPointInCopperPour(
|