@tscircuit/capacity-autorouter 0.0.40 → 0.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +15 -5
- package/dist/index.js +585 -29
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -3567,6 +3567,540 @@ var TwoCrossingRoutesHighDensitySolver = class extends BaseSolver {
|
|
|
3567
3567
|
}
|
|
3568
3568
|
};
|
|
3569
3569
|
|
|
3570
|
+
// lib/utils/findClosestPointToABCWithinBounds.ts
|
|
3571
|
+
function findClosestPointToABCWithinBounds(A, B, C, radius, bounds) {
|
|
3572
|
+
const avgPoint = {
|
|
3573
|
+
x: (A.x + B.x + C.x) / 3,
|
|
3574
|
+
y: (A.y + B.y + C.y) / 3
|
|
3575
|
+
};
|
|
3576
|
+
const distance5 = (p1, p2) => {
|
|
3577
|
+
return Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
|
|
3578
|
+
};
|
|
3579
|
+
const isValidPoint = (point) => {
|
|
3580
|
+
const distToA = distance5(point, A);
|
|
3581
|
+
const distToB = distance5(point, B);
|
|
3582
|
+
const distToC = distance5(point, C);
|
|
3583
|
+
const withinBounds = point.x >= bounds.minX && point.x <= bounds.maxX && point.y >= bounds.minY && point.y <= bounds.maxY;
|
|
3584
|
+
return distToA >= radius && distToB >= radius && distToC >= radius && withinBounds;
|
|
3585
|
+
};
|
|
3586
|
+
const isOnBoundary = (point) => {
|
|
3587
|
+
const epsilon = 1e-6;
|
|
3588
|
+
return Math.abs(point.x - bounds.minX) < epsilon || Math.abs(point.x - bounds.maxX) < epsilon || Math.abs(point.y - bounds.minY) < epsilon || Math.abs(point.y - bounds.maxY) < epsilon;
|
|
3589
|
+
};
|
|
3590
|
+
if (isValidPoint(avgPoint)) {
|
|
3591
|
+
return avgPoint;
|
|
3592
|
+
}
|
|
3593
|
+
const pointOnCircle = (center, constraint, r) => {
|
|
3594
|
+
const vx = center.x - constraint.x;
|
|
3595
|
+
const vy = center.y - constraint.y;
|
|
3596
|
+
const dist = Math.sqrt(vx * vx + vy * vy);
|
|
3597
|
+
if (dist < 1e-10) {
|
|
3598
|
+
return { x: constraint.x + r, y: constraint.y };
|
|
3599
|
+
}
|
|
3600
|
+
return {
|
|
3601
|
+
x: constraint.x + vx / dist * r,
|
|
3602
|
+
y: constraint.y + vy / dist * r
|
|
3603
|
+
};
|
|
3604
|
+
};
|
|
3605
|
+
const findCircleIntersections = (c1, c2, r) => {
|
|
3606
|
+
const dx = c2.x - c1.x;
|
|
3607
|
+
const dy = c2.y - c1.y;
|
|
3608
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
3609
|
+
if (dist > 2 * r - 1e-10 || dist < 1e-10) {
|
|
3610
|
+
return [];
|
|
3611
|
+
}
|
|
3612
|
+
const a = dist * dist / (2 * dist);
|
|
3613
|
+
const h = Math.sqrt(Math.max(0, r * r - a * a));
|
|
3614
|
+
const midX = c1.x + dx * a / dist;
|
|
3615
|
+
const midY = c1.y + dy * a / dist;
|
|
3616
|
+
const intersection1 = {
|
|
3617
|
+
x: midX + h * dy / dist,
|
|
3618
|
+
y: midY - h * dx / dist
|
|
3619
|
+
};
|
|
3620
|
+
const intersection2 = {
|
|
3621
|
+
x: midX - h * dy / dist,
|
|
3622
|
+
y: midY + h * dx / dist
|
|
3623
|
+
};
|
|
3624
|
+
const result = [];
|
|
3625
|
+
const epsilon = 1e-6;
|
|
3626
|
+
if (Math.abs(distance5(intersection1, c1) - r) < epsilon && Math.abs(distance5(intersection1, c2) - r) < epsilon) {
|
|
3627
|
+
result.push(intersection1);
|
|
3628
|
+
}
|
|
3629
|
+
if (Math.abs(distance5(intersection2, c1) - r) < epsilon && Math.abs(distance5(intersection2, c2) - r) < epsilon) {
|
|
3630
|
+
result.push(intersection2);
|
|
3631
|
+
}
|
|
3632
|
+
return result;
|
|
3633
|
+
};
|
|
3634
|
+
const candidateA = pointOnCircle(avgPoint, A, radius);
|
|
3635
|
+
const candidateB = pointOnCircle(avgPoint, B, radius);
|
|
3636
|
+
const candidateC = pointOnCircle(avgPoint, C, radius);
|
|
3637
|
+
const intersectionsAB = findCircleIntersections(A, B, radius);
|
|
3638
|
+
const intersectionsBC = findCircleIntersections(B, C, radius);
|
|
3639
|
+
const intersectionsCA = findCircleIntersections(C, A, radius);
|
|
3640
|
+
const allCandidates = [
|
|
3641
|
+
candidateA,
|
|
3642
|
+
candidateB,
|
|
3643
|
+
candidateC,
|
|
3644
|
+
...intersectionsAB,
|
|
3645
|
+
...intersectionsBC,
|
|
3646
|
+
...intersectionsCA
|
|
3647
|
+
];
|
|
3648
|
+
const validCandidates = allCandidates.filter(isValidPoint);
|
|
3649
|
+
if (validCandidates.length > 0) {
|
|
3650
|
+
const interiorCandidates = validCandidates.filter((p) => !isOnBoundary(p));
|
|
3651
|
+
if (interiorCandidates.length > 0) {
|
|
3652
|
+
interiorCandidates.sort(
|
|
3653
|
+
(a, b) => distance5(a, avgPoint) - distance5(b, avgPoint)
|
|
3654
|
+
);
|
|
3655
|
+
return interiorCandidates[0];
|
|
3656
|
+
}
|
|
3657
|
+
}
|
|
3658
|
+
const gridStep = 5;
|
|
3659
|
+
let bestPoint = null;
|
|
3660
|
+
let bestDistance = Infinity;
|
|
3661
|
+
for (let x = bounds.minX + 1; x < bounds.maxX; x += gridStep) {
|
|
3662
|
+
for (let y = bounds.minY + 1; y < bounds.maxY; y += gridStep) {
|
|
3663
|
+
const point = { x, y };
|
|
3664
|
+
if (isValidPoint(point)) {
|
|
3665
|
+
const dist = distance5(point, avgPoint);
|
|
3666
|
+
if (dist < bestDistance) {
|
|
3667
|
+
bestDistance = dist;
|
|
3668
|
+
bestPoint = point;
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
if (bestPoint !== null) {
|
|
3674
|
+
return bestPoint;
|
|
3675
|
+
}
|
|
3676
|
+
const numSamples = 100;
|
|
3677
|
+
const boundaryPoints = [];
|
|
3678
|
+
for (let i = 0; i <= numSamples; i++) {
|
|
3679
|
+
const t = i / numSamples;
|
|
3680
|
+
boundaryPoints.push({
|
|
3681
|
+
x: bounds.minX + t * (bounds.maxX - bounds.minX),
|
|
3682
|
+
y: bounds.minY
|
|
3683
|
+
});
|
|
3684
|
+
boundaryPoints.push({
|
|
3685
|
+
x: bounds.maxX,
|
|
3686
|
+
y: bounds.minY + t * (bounds.maxY - bounds.minY)
|
|
3687
|
+
});
|
|
3688
|
+
boundaryPoints.push({
|
|
3689
|
+
x: bounds.maxX - t * (bounds.maxX - bounds.minX),
|
|
3690
|
+
y: bounds.maxY
|
|
3691
|
+
});
|
|
3692
|
+
boundaryPoints.push({
|
|
3693
|
+
x: bounds.minX,
|
|
3694
|
+
y: bounds.maxY - t * (bounds.maxY - bounds.minY)
|
|
3695
|
+
});
|
|
3696
|
+
}
|
|
3697
|
+
const validBoundaryPoints = boundaryPoints.filter(isValidPoint);
|
|
3698
|
+
if (validBoundaryPoints.length > 0) {
|
|
3699
|
+
validBoundaryPoints.sort(
|
|
3700
|
+
(a, b) => distance5(a, avgPoint) - distance5(b, avgPoint)
|
|
3701
|
+
);
|
|
3702
|
+
return validBoundaryPoints[0];
|
|
3703
|
+
}
|
|
3704
|
+
let minViolation = Infinity;
|
|
3705
|
+
let leastBadPoint = { x: bounds.minX, y: bounds.minY };
|
|
3706
|
+
for (const point of [...allCandidates, ...boundaryPoints]) {
|
|
3707
|
+
if (point.x >= bounds.minX && point.x <= bounds.maxX && point.y >= bounds.minY && point.y <= bounds.maxY) {
|
|
3708
|
+
const violationA = Math.max(0, radius - distance5(point, A));
|
|
3709
|
+
const violationB = Math.max(0, radius - distance5(point, B));
|
|
3710
|
+
const violationC = Math.max(0, radius - distance5(point, C));
|
|
3711
|
+
const totalViolation = violationA + violationB + violationC;
|
|
3712
|
+
if (totalViolation < minViolation) {
|
|
3713
|
+
minViolation = totalViolation;
|
|
3714
|
+
leastBadPoint = point;
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
return leastBadPoint;
|
|
3719
|
+
}
|
|
3720
|
+
|
|
3721
|
+
// lib/utils/findPointToGetAroundCircle.ts
|
|
3722
|
+
function findPointToGetAroundCircle(A, C, Q) {
|
|
3723
|
+
const B = computeTangentPoint(C, A, Q.center, Q.radius);
|
|
3724
|
+
const D = computeTangentPoint(A, C, Q.center, Q.radius);
|
|
3725
|
+
const E = computeIntersection(
|
|
3726
|
+
{ x: C.x, y: C.y },
|
|
3727
|
+
{ x: B.x, y: B.y },
|
|
3728
|
+
{ x: A.x, y: A.y },
|
|
3729
|
+
{ x: D.x, y: D.y }
|
|
3730
|
+
);
|
|
3731
|
+
return { B, D, E };
|
|
3732
|
+
}
|
|
3733
|
+
function computeTangentPoint(observationPoint, referencePoint, circleCenter, radius) {
|
|
3734
|
+
const CQ = [
|
|
3735
|
+
circleCenter.x - observationPoint.x,
|
|
3736
|
+
circleCenter.y - observationPoint.y
|
|
3737
|
+
];
|
|
3738
|
+
const CQLength = Math.sqrt(CQ[0] * CQ[0] + CQ[1] * CQ[1]);
|
|
3739
|
+
if (CQLength < radius) {
|
|
3740
|
+
return observationPoint;
|
|
3741
|
+
}
|
|
3742
|
+
const CR = [
|
|
3743
|
+
referencePoint.x - observationPoint.x,
|
|
3744
|
+
referencePoint.y - observationPoint.y
|
|
3745
|
+
];
|
|
3746
|
+
const d = Math.sqrt(CQLength * CQLength - radius * radius);
|
|
3747
|
+
const CQUnit = [CQ[0] / CQLength, CQ[1] / CQLength];
|
|
3748
|
+
const perp1 = [-CQUnit[1], CQUnit[0]];
|
|
3749
|
+
const perp2 = [CQUnit[1], -CQUnit[0]];
|
|
3750
|
+
const dot1 = CR[0] * perp1[0] + CR[1] * perp1[1];
|
|
3751
|
+
const dot2 = CR[0] * perp2[0] + CR[1] * perp2[1];
|
|
3752
|
+
const perp = dot1 > dot2 ? perp1 : perp2;
|
|
3753
|
+
const sinTheta = radius / CQLength;
|
|
3754
|
+
const cosTheta = d / CQLength;
|
|
3755
|
+
const unitToTangent = [
|
|
3756
|
+
CQUnit[0] * cosTheta + perp[0] * sinTheta,
|
|
3757
|
+
CQUnit[1] * cosTheta + perp[1] * sinTheta
|
|
3758
|
+
];
|
|
3759
|
+
return {
|
|
3760
|
+
x: observationPoint.x + d * unitToTangent[0],
|
|
3761
|
+
y: observationPoint.y + d * unitToTangent[1]
|
|
3762
|
+
};
|
|
3763
|
+
}
|
|
3764
|
+
function computeIntersection(p1, p2, p3, p4) {
|
|
3765
|
+
const a1 = p2.y - p1.y;
|
|
3766
|
+
const b1 = p1.x - p2.x;
|
|
3767
|
+
const c1 = a1 * p1.x + b1 * p1.y;
|
|
3768
|
+
const a2 = p4.y - p3.y;
|
|
3769
|
+
const b2 = p3.x - p4.x;
|
|
3770
|
+
const c2 = a2 * p3.x + b2 * p3.y;
|
|
3771
|
+
const det = a1 * b2 - a2 * b1;
|
|
3772
|
+
if (Math.abs(det) < 1e-8) {
|
|
3773
|
+
return {
|
|
3774
|
+
x: (p1.x + p3.x) / 2,
|
|
3775
|
+
y: (p1.y + p3.y) / 2
|
|
3776
|
+
};
|
|
3777
|
+
}
|
|
3778
|
+
const x = (b2 * c1 - b1 * c2) / det;
|
|
3779
|
+
const y = (a1 * c2 - a2 * c1) / det;
|
|
3780
|
+
return { x, y };
|
|
3781
|
+
}
|
|
3782
|
+
|
|
3783
|
+
// lib/solvers/HighDensitySolver/TwoRouteHighDensitySolver/SingleTransitionCrossingRouteSolver.ts
|
|
3784
|
+
var SingleTransitionCrossingRouteSolver = class extends BaseSolver {
|
|
3785
|
+
// Input parameters
|
|
3786
|
+
nodeWithPortPoints;
|
|
3787
|
+
routes;
|
|
3788
|
+
// Configuration parameters
|
|
3789
|
+
viaDiameter;
|
|
3790
|
+
traceThickness;
|
|
3791
|
+
obstacleMargin;
|
|
3792
|
+
layerCount = 2;
|
|
3793
|
+
debugViaPositions;
|
|
3794
|
+
// Solution state
|
|
3795
|
+
solvedRoutes = [];
|
|
3796
|
+
// Bounds
|
|
3797
|
+
bounds;
|
|
3798
|
+
constructor(params) {
|
|
3799
|
+
super();
|
|
3800
|
+
this.nodeWithPortPoints = params.nodeWithPortPoints;
|
|
3801
|
+
this.viaDiameter = params?.viaDiameter ?? 0.6;
|
|
3802
|
+
this.traceThickness = params?.traceThickness ?? 0.15;
|
|
3803
|
+
this.obstacleMargin = params?.obstacleMargin ?? 0.1;
|
|
3804
|
+
this.layerCount = params?.layerCount ?? 2;
|
|
3805
|
+
this.debugViaPositions = [];
|
|
3806
|
+
this.routes = this.extractRoutesFromNode();
|
|
3807
|
+
this.bounds = this.calculateBounds();
|
|
3808
|
+
if (this.routes.length !== 2) {
|
|
3809
|
+
this.failed = true;
|
|
3810
|
+
return;
|
|
3811
|
+
}
|
|
3812
|
+
const [routeA, routeB] = this.routes;
|
|
3813
|
+
const routeAHasTransition = routeA.A.z !== routeA.B.z;
|
|
3814
|
+
const routeBHasTransition = routeB.A.z !== routeB.B.z;
|
|
3815
|
+
if (routeAHasTransition && routeBHasTransition || !routeAHasTransition && !routeBHasTransition) {
|
|
3816
|
+
this.failed = true;
|
|
3817
|
+
return;
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
/**
|
|
3821
|
+
* Extract routes that need to be connected from the node data
|
|
3822
|
+
*/
|
|
3823
|
+
extractRoutesFromNode() {
|
|
3824
|
+
const routes = [];
|
|
3825
|
+
const connectedPorts = this.nodeWithPortPoints.portPoints;
|
|
3826
|
+
const connectionGroups = /* @__PURE__ */ new Map();
|
|
3827
|
+
for (const connectedPort of connectedPorts) {
|
|
3828
|
+
const { connectionName } = connectedPort;
|
|
3829
|
+
if (!connectionGroups.has(connectionName)) {
|
|
3830
|
+
connectionGroups.set(connectionName, []);
|
|
3831
|
+
}
|
|
3832
|
+
connectionGroups.get(connectionName)?.push(connectedPort);
|
|
3833
|
+
}
|
|
3834
|
+
for (const [connectionName, points] of connectionGroups.entries()) {
|
|
3835
|
+
if (points.length === 2) {
|
|
3836
|
+
routes.push({
|
|
3837
|
+
A: { ...points[0], z: points[0].z ?? 0 },
|
|
3838
|
+
B: { ...points[1], z: points[1].z ?? 0 },
|
|
3839
|
+
connectionName
|
|
3840
|
+
});
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
return routes;
|
|
3844
|
+
}
|
|
3845
|
+
/**
|
|
3846
|
+
* Calculate the bounding box of the node
|
|
3847
|
+
*/
|
|
3848
|
+
calculateBounds() {
|
|
3849
|
+
return {
|
|
3850
|
+
minX: this.nodeWithPortPoints.center.x - this.nodeWithPortPoints.width / 2,
|
|
3851
|
+
maxX: this.nodeWithPortPoints.center.x + this.nodeWithPortPoints.width / 2,
|
|
3852
|
+
minY: this.nodeWithPortPoints.center.y - this.nodeWithPortPoints.height / 2,
|
|
3853
|
+
maxY: this.nodeWithPortPoints.center.y + this.nodeWithPortPoints.height / 2
|
|
3854
|
+
};
|
|
3855
|
+
}
|
|
3856
|
+
/**
|
|
3857
|
+
* Check if two routes are crossing
|
|
3858
|
+
*/
|
|
3859
|
+
doRoutesCross(routeA, routeB) {
|
|
3860
|
+
return doSegmentsIntersect(routeA.A, routeA.B, routeB.A, routeB.B);
|
|
3861
|
+
}
|
|
3862
|
+
calculateViaPosition(transitionRoute, flatRoute) {
|
|
3863
|
+
const flatRouteZ = flatRoute.A.z;
|
|
3864
|
+
const ntrP1 = transitionRoute.A.z !== flatRouteZ ? transitionRoute.A : transitionRoute.B;
|
|
3865
|
+
const marginFromBorderWithTrace = this.obstacleMargin * 2 + this.viaDiameter / 2 + this.traceThickness;
|
|
3866
|
+
const marginFromBorderWithoutTrace = this.obstacleMargin + this.viaDiameter / 2;
|
|
3867
|
+
return findClosestPointToABCWithinBounds(
|
|
3868
|
+
flatRoute.A,
|
|
3869
|
+
flatRoute.B,
|
|
3870
|
+
ntrP1,
|
|
3871
|
+
marginFromBorderWithTrace,
|
|
3872
|
+
{
|
|
3873
|
+
minX: this.bounds.minX + marginFromBorderWithoutTrace,
|
|
3874
|
+
minY: this.bounds.minY + marginFromBorderWithoutTrace,
|
|
3875
|
+
maxX: this.bounds.maxX - marginFromBorderWithTrace,
|
|
3876
|
+
maxY: this.bounds.maxY - marginFromBorderWithTrace
|
|
3877
|
+
}
|
|
3878
|
+
);
|
|
3879
|
+
}
|
|
3880
|
+
/**
|
|
3881
|
+
* Create a single transition route with properly placed via
|
|
3882
|
+
*/
|
|
3883
|
+
createTransitionRoute(start, end, via, connectionName) {
|
|
3884
|
+
const route = [
|
|
3885
|
+
{ x: start.x, y: start.y, z: start.z ?? 0 },
|
|
3886
|
+
{ x: via.x, y: via.y, z: start.z ?? 0 },
|
|
3887
|
+
{ x: via.x, y: via.y, z: end.z ?? 0 },
|
|
3888
|
+
{ x: end.x, y: end.y, z: end.z ?? 0 }
|
|
3889
|
+
];
|
|
3890
|
+
return {
|
|
3891
|
+
connectionName,
|
|
3892
|
+
route,
|
|
3893
|
+
traceThickness: this.traceThickness,
|
|
3894
|
+
viaDiameter: this.viaDiameter,
|
|
3895
|
+
vias: [via]
|
|
3896
|
+
};
|
|
3897
|
+
}
|
|
3898
|
+
/**
|
|
3899
|
+
* Create the non-transition route
|
|
3900
|
+
*/
|
|
3901
|
+
createFlatRoute(flatStart, flatEnd, via, otherRouteStart, otherRouteEnd, flatRouteConnectionName) {
|
|
3902
|
+
const ntrP1 = otherRouteStart.z !== flatStart.z ? otherRouteStart : otherRouteEnd;
|
|
3903
|
+
const middle = (a, b) => {
|
|
3904
|
+
return {
|
|
3905
|
+
x: (a.x + b.x) / 2,
|
|
3906
|
+
y: (a.y + b.y) / 2
|
|
3907
|
+
};
|
|
3908
|
+
};
|
|
3909
|
+
const middleWithMargin = (a, aMargin, b, bMargin) => {
|
|
3910
|
+
const dx = b.x - a.x;
|
|
3911
|
+
const dy = b.y - a.y;
|
|
3912
|
+
const effectiveA = {
|
|
3913
|
+
x: a.x + dx * aMargin,
|
|
3914
|
+
y: a.y + dy * aMargin
|
|
3915
|
+
};
|
|
3916
|
+
const effectiveB = {
|
|
3917
|
+
x: b.x - dx * bMargin,
|
|
3918
|
+
y: b.y - dy * bMargin
|
|
3919
|
+
};
|
|
3920
|
+
return middle(effectiveA, effectiveB);
|
|
3921
|
+
};
|
|
3922
|
+
const traceBounds = {
|
|
3923
|
+
maxX: this.bounds.maxX - this.obstacleMargin - this.traceThickness / 2,
|
|
3924
|
+
maxY: this.bounds.maxY - this.obstacleMargin - this.traceThickness / 2,
|
|
3925
|
+
minX: this.bounds.minX + this.obstacleMargin + this.traceThickness / 2,
|
|
3926
|
+
minY: this.bounds.minY + this.obstacleMargin + this.traceThickness / 2
|
|
3927
|
+
};
|
|
3928
|
+
const minDistFromViaToTrace = this.viaDiameter / 2 + this.traceThickness / 2 + this.obstacleMargin;
|
|
3929
|
+
const p2 = middleWithMargin(
|
|
3930
|
+
via,
|
|
3931
|
+
this.viaDiameter,
|
|
3932
|
+
otherRouteStart.z !== flatStart.z ? otherRouteStart : otherRouteEnd,
|
|
3933
|
+
this.traceThickness
|
|
3934
|
+
);
|
|
3935
|
+
const p1 = findPointToGetAroundCircle(flatStart, p2, {
|
|
3936
|
+
center: { x: via.x, y: via.y },
|
|
3937
|
+
radius: minDistFromViaToTrace
|
|
3938
|
+
}).E;
|
|
3939
|
+
const p3 = findPointToGetAroundCircle(p2, flatEnd, {
|
|
3940
|
+
center: { x: via.x, y: via.y },
|
|
3941
|
+
radius: minDistFromViaToTrace
|
|
3942
|
+
}).E;
|
|
3943
|
+
const p1IsNeeded = pointToSegmentDistance(via, flatStart, p2) < minDistFromViaToTrace;
|
|
3944
|
+
const p3IsNeeded = pointToSegmentDistance(via, p2, flatEnd) < minDistFromViaToTrace;
|
|
3945
|
+
return {
|
|
3946
|
+
connectionName: flatRouteConnectionName,
|
|
3947
|
+
route: [
|
|
3948
|
+
{ x: flatStart.x, y: flatStart.y, z: flatStart.z ?? 0 },
|
|
3949
|
+
...p1IsNeeded ? [{ x: p1.x, y: p1.y, z: flatStart.z ?? 0 }] : [],
|
|
3950
|
+
{ x: p2.x, y: p2.y, z: flatStart.z ?? 0 },
|
|
3951
|
+
...p3IsNeeded ? [{ x: p3.x, y: p3.y, z: flatStart.z ?? 0 }] : [],
|
|
3952
|
+
{ x: flatEnd.x, y: flatEnd.y, z: flatEnd.z ?? 0 }
|
|
3953
|
+
],
|
|
3954
|
+
traceThickness: this.traceThickness,
|
|
3955
|
+
viaDiameter: this.viaDiameter,
|
|
3956
|
+
vias: []
|
|
3957
|
+
};
|
|
3958
|
+
}
|
|
3959
|
+
/**
|
|
3960
|
+
* Try to solve with one route having a transition and the other staying flat
|
|
3961
|
+
*/
|
|
3962
|
+
trySolve() {
|
|
3963
|
+
const [routeA, routeB] = this.routes;
|
|
3964
|
+
const routeAHasTransition = routeA.A.z !== routeA.B.z;
|
|
3965
|
+
const transitionRoute = routeAHasTransition ? routeA : routeB;
|
|
3966
|
+
const flatRoute = routeAHasTransition ? routeB : routeA;
|
|
3967
|
+
const viaPosition = this.calculateViaPosition(transitionRoute, flatRoute);
|
|
3968
|
+
if (viaPosition) {
|
|
3969
|
+
this.debugViaPositions.push({ via: viaPosition });
|
|
3970
|
+
} else {
|
|
3971
|
+
return false;
|
|
3972
|
+
}
|
|
3973
|
+
const transitionRouteSolution = this.createTransitionRoute(
|
|
3974
|
+
transitionRoute.A,
|
|
3975
|
+
transitionRoute.B,
|
|
3976
|
+
viaPosition,
|
|
3977
|
+
transitionRoute.connectionName
|
|
3978
|
+
);
|
|
3979
|
+
const flatRouteSolution = this.createFlatRoute(
|
|
3980
|
+
flatRoute.A,
|
|
3981
|
+
flatRoute.B,
|
|
3982
|
+
viaPosition,
|
|
3983
|
+
transitionRoute.A,
|
|
3984
|
+
transitionRoute.B,
|
|
3985
|
+
flatRoute.connectionName
|
|
3986
|
+
);
|
|
3987
|
+
this.solvedRoutes.push(transitionRouteSolution, flatRouteSolution);
|
|
3988
|
+
return true;
|
|
3989
|
+
}
|
|
3990
|
+
/**
|
|
3991
|
+
* Main step method that attempts to solve the routes
|
|
3992
|
+
*/
|
|
3993
|
+
_step() {
|
|
3994
|
+
if (!this.doRoutesCross(this.routes[0], this.routes[1])) {
|
|
3995
|
+
this.failed = true;
|
|
3996
|
+
this.error = "Can only solve routes that have a single transition crossing";
|
|
3997
|
+
return;
|
|
3998
|
+
}
|
|
3999
|
+
if (this.trySolve()) {
|
|
4000
|
+
this.solved = true;
|
|
4001
|
+
return;
|
|
4002
|
+
}
|
|
4003
|
+
this.failed = true;
|
|
4004
|
+
}
|
|
4005
|
+
/**
|
|
4006
|
+
* Visualization for debugging
|
|
4007
|
+
*/
|
|
4008
|
+
visualize() {
|
|
4009
|
+
const graphics = {
|
|
4010
|
+
lines: [],
|
|
4011
|
+
points: [],
|
|
4012
|
+
rects: [],
|
|
4013
|
+
circles: []
|
|
4014
|
+
};
|
|
4015
|
+
graphics.rects.push({
|
|
4016
|
+
center: {
|
|
4017
|
+
x: (this.bounds.minX + this.bounds.maxX) / 2,
|
|
4018
|
+
y: (this.bounds.minY + this.bounds.maxY) / 2
|
|
4019
|
+
},
|
|
4020
|
+
width: this.bounds.maxX - this.bounds.minX,
|
|
4021
|
+
height: this.bounds.maxY - this.bounds.minY,
|
|
4022
|
+
stroke: "rgba(0, 0, 0, 0.5)",
|
|
4023
|
+
fill: "rgba(240, 240, 240, 0.1)",
|
|
4024
|
+
label: "PCB Bounds"
|
|
4025
|
+
});
|
|
4026
|
+
for (const route of this.routes) {
|
|
4027
|
+
graphics.points.push({
|
|
4028
|
+
x: route.A.x,
|
|
4029
|
+
y: route.A.y,
|
|
4030
|
+
label: `${route.connectionName} start (z=${route.A.z})`,
|
|
4031
|
+
color: "orange"
|
|
4032
|
+
});
|
|
4033
|
+
graphics.points.push({
|
|
4034
|
+
x: route.B.x,
|
|
4035
|
+
y: route.B.y,
|
|
4036
|
+
label: `${route.connectionName} end (z=${route.B.z})`,
|
|
4037
|
+
color: "orange"
|
|
4038
|
+
});
|
|
4039
|
+
graphics.lines.push({
|
|
4040
|
+
points: [route.A, route.B],
|
|
4041
|
+
strokeColor: "rgba(255, 0, 0, 0.5)",
|
|
4042
|
+
label: `${route.connectionName} direct`
|
|
4043
|
+
});
|
|
4044
|
+
}
|
|
4045
|
+
for (let i = 0; i < this.debugViaPositions.length; i++) {
|
|
4046
|
+
const { via } = this.debugViaPositions[i];
|
|
4047
|
+
graphics.circles.push({
|
|
4048
|
+
center: via,
|
|
4049
|
+
radius: this.viaDiameter / 2,
|
|
4050
|
+
fill: "rgba(255, 165, 0, 0.7)",
|
|
4051
|
+
stroke: "rgba(0, 0, 0, 0.5)",
|
|
4052
|
+
label: `Computed Via (attempt ${i + 1})`
|
|
4053
|
+
});
|
|
4054
|
+
const safetyMargin = this.viaDiameter / 2 + this.obstacleMargin;
|
|
4055
|
+
graphics.circles.push({
|
|
4056
|
+
center: via,
|
|
4057
|
+
radius: safetyMargin,
|
|
4058
|
+
stroke: "rgba(255, 165, 0, 0.7)",
|
|
4059
|
+
fill: "rgba(0, 0, 0, 0)",
|
|
4060
|
+
label: "Safety Margin"
|
|
4061
|
+
});
|
|
4062
|
+
}
|
|
4063
|
+
for (let si = 0; si < this.solvedRoutes.length; si++) {
|
|
4064
|
+
const route = this.solvedRoutes[si];
|
|
4065
|
+
const routeColor = si % 2 === 0 ? "rgba(0, 255, 0, 0.75)" : "rgba(255, 0, 255, 0.75)";
|
|
4066
|
+
for (let i = 0; i < route.route.length - 1; i++) {
|
|
4067
|
+
const pointA = route.route[i];
|
|
4068
|
+
const pointB = route.route[i + 1];
|
|
4069
|
+
graphics.lines.push({
|
|
4070
|
+
points: [pointA, pointB],
|
|
4071
|
+
strokeColor: routeColor,
|
|
4072
|
+
strokeDash: pointA.z !== route.route[0].z ? [0.2, 0.2] : void 0,
|
|
4073
|
+
strokeWidth: route.traceThickness,
|
|
4074
|
+
label: `${route.connectionName} z=${pointA.z}`
|
|
4075
|
+
});
|
|
4076
|
+
}
|
|
4077
|
+
for (const via of route.vias) {
|
|
4078
|
+
graphics.circles.push({
|
|
4079
|
+
center: via,
|
|
4080
|
+
radius: this.viaDiameter / 2,
|
|
4081
|
+
fill: "rgba(0, 0, 255, 0.8)",
|
|
4082
|
+
stroke: "black",
|
|
4083
|
+
label: "Solved Via"
|
|
4084
|
+
});
|
|
4085
|
+
graphics.circles.push({
|
|
4086
|
+
center: via,
|
|
4087
|
+
radius: this.viaDiameter / 2 + this.obstacleMargin,
|
|
4088
|
+
fill: "rgba(0, 0, 255, 0.3)",
|
|
4089
|
+
stroke: "black",
|
|
4090
|
+
label: "Via Margin"
|
|
4091
|
+
});
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
return graphics;
|
|
4095
|
+
}
|
|
4096
|
+
/**
|
|
4097
|
+
* Get the solved routes
|
|
4098
|
+
*/
|
|
4099
|
+
getSolvedRoutes() {
|
|
4100
|
+
return this.solvedRoutes;
|
|
4101
|
+
}
|
|
4102
|
+
};
|
|
4103
|
+
|
|
3570
4104
|
// lib/solvers/HyperHighDensitySolver/HyperSingleIntraNodeSolver.ts
|
|
3571
4105
|
var HyperSingleIntraNodeSolver = class extends HyperParameterSupervisorSolver {
|
|
3572
4106
|
constructorParams;
|
|
@@ -3582,7 +4116,7 @@ var HyperSingleIntraNodeSolver = class extends HyperParameterSupervisorSolver {
|
|
|
3582
4116
|
}
|
|
3583
4117
|
getCombinationDefs() {
|
|
3584
4118
|
return [
|
|
3585
|
-
["
|
|
4119
|
+
["closedFormTwoTrace"],
|
|
3586
4120
|
["majorCombinations", "orderings6", "cellSizeFactor"],
|
|
3587
4121
|
["noVias"],
|
|
3588
4122
|
["orderings50"],
|
|
@@ -3673,10 +4207,13 @@ var HyperSingleIntraNodeSolver = class extends HyperParameterSupervisorSolver {
|
|
|
3673
4207
|
}))
|
|
3674
4208
|
},
|
|
3675
4209
|
{
|
|
3676
|
-
name: "
|
|
4210
|
+
name: "closedFormTwoTrace",
|
|
3677
4211
|
possibleValues: [
|
|
3678
4212
|
{
|
|
3679
4213
|
CLOSED_FORM_TWO_TRACE_SAME_LAYER: true
|
|
4214
|
+
},
|
|
4215
|
+
{
|
|
4216
|
+
CLOSED_FORM_TWO_TRACE_TRANSITION_CROSSING: true
|
|
3680
4217
|
}
|
|
3681
4218
|
]
|
|
3682
4219
|
}
|
|
@@ -3694,6 +4231,11 @@ var HyperSingleIntraNodeSolver = class extends HyperParameterSupervisorSolver {
|
|
|
3694
4231
|
nodeWithPortPoints: this.nodeWithPortPoints
|
|
3695
4232
|
});
|
|
3696
4233
|
}
|
|
4234
|
+
if (hyperParameters.CLOSED_FORM_TWO_TRACE_TRANSITION_CROSSING) {
|
|
4235
|
+
return new SingleTransitionCrossingRouteSolver({
|
|
4236
|
+
nodeWithPortPoints: this.nodeWithPortPoints
|
|
4237
|
+
});
|
|
4238
|
+
}
|
|
3697
4239
|
return new IntraNodeRouteSolver({
|
|
3698
4240
|
...this.constructorParams,
|
|
3699
4241
|
hyperParameters
|
|
@@ -4120,13 +4662,13 @@ function buildMinimumSpanningTree(points) {
|
|
|
4120
4662
|
if (point.x === neighbor.x && point.y === neighbor.y) {
|
|
4121
4663
|
continue;
|
|
4122
4664
|
}
|
|
4123
|
-
const
|
|
4665
|
+
const distance5 = Math.sqrt(
|
|
4124
4666
|
(point.x - neighbor.x) ** 2 + (point.y - neighbor.y) ** 2
|
|
4125
4667
|
);
|
|
4126
4668
|
edges.push({
|
|
4127
4669
|
from: point,
|
|
4128
4670
|
to: neighbor,
|
|
4129
|
-
weight:
|
|
4671
|
+
weight: distance5
|
|
4130
4672
|
});
|
|
4131
4673
|
}
|
|
4132
4674
|
}
|
|
@@ -5387,6 +5929,7 @@ ${isMutable ? "MUTABLE" : "IMMUTABLE"}`,
|
|
|
5387
5929
|
for (const connectedPointId of segmentPoint.directlyConnectedSegmentPointIds) {
|
|
5388
5930
|
if (segmentPointId < connectedPointId) {
|
|
5389
5931
|
const connectedPoint = modifiedSegmentPoints.get(connectedPointId);
|
|
5932
|
+
if (!connectedPoint) continue;
|
|
5390
5933
|
const sameLayer = segmentPoint.z === connectedPoint.z;
|
|
5391
5934
|
const commonLayer = segmentPoint.z;
|
|
5392
5935
|
let strokeDash;
|
|
@@ -5946,11 +6489,11 @@ var CapacityPathingSolver = class extends BaseSolver {
|
|
|
5946
6489
|
let closestNode = this.nodes[0];
|
|
5947
6490
|
let minDistance = Number.MAX_VALUE;
|
|
5948
6491
|
for (const node of nodesWithTargets) {
|
|
5949
|
-
const
|
|
6492
|
+
const distance5 = Math.sqrt(
|
|
5950
6493
|
(node.center.x - point.x) ** 2 + (node.center.y - point.y) ** 2
|
|
5951
6494
|
);
|
|
5952
|
-
if (
|
|
5953
|
-
minDistance =
|
|
6495
|
+
if (distance5 < minDistance) {
|
|
6496
|
+
minDistance = distance5;
|
|
5954
6497
|
closestNode = node;
|
|
5955
6498
|
}
|
|
5956
6499
|
}
|
|
@@ -5968,9 +6511,16 @@ var CapacityPathingSolver = class extends BaseSolver {
|
|
|
5968
6511
|
connectionsWithNodes.push({
|
|
5969
6512
|
connection,
|
|
5970
6513
|
nodes: nodesForConnection,
|
|
5971
|
-
pathFound: false
|
|
6514
|
+
pathFound: false,
|
|
6515
|
+
straightLineDistance: distance(
|
|
6516
|
+
nodesForConnection[0].center,
|
|
6517
|
+
nodesForConnection[nodesForConnection.length - 1].center
|
|
6518
|
+
)
|
|
5972
6519
|
});
|
|
5973
6520
|
}
|
|
6521
|
+
connectionsWithNodes.sort(
|
|
6522
|
+
(a, b) => a.straightLineDistance - b.straightLineDistance
|
|
6523
|
+
);
|
|
5974
6524
|
return { connectionsWithNodes, connectionNameToGoalNodeIds };
|
|
5975
6525
|
}
|
|
5976
6526
|
currentConnectionIndex = 0;
|
|
@@ -6991,23 +7541,23 @@ function pointToSegmentDistance4(P, Q1, Q2) {
|
|
|
6991
7541
|
const w = { x: P.x - Q1.x, y: P.y - Q1.y };
|
|
6992
7542
|
const c1 = dotProduct(w, v);
|
|
6993
7543
|
if (c1 <= 0) {
|
|
6994
|
-
return
|
|
7544
|
+
return distance4(P, Q1);
|
|
6995
7545
|
}
|
|
6996
7546
|
const c2 = dotProduct(v, v);
|
|
6997
7547
|
if (c2 <= c1) {
|
|
6998
|
-
return
|
|
7548
|
+
return distance4(P, Q2);
|
|
6999
7549
|
}
|
|
7000
7550
|
const b = c1 / c2;
|
|
7001
7551
|
const Pb = {
|
|
7002
7552
|
x: Q1.x + b * v.x,
|
|
7003
7553
|
y: Q1.y + b * v.y
|
|
7004
7554
|
};
|
|
7005
|
-
return
|
|
7555
|
+
return distance4(P, Pb);
|
|
7006
7556
|
}
|
|
7007
7557
|
function dotProduct(v1, v2) {
|
|
7008
7558
|
return v1.x * v2.x + v1.y * v2.y;
|
|
7009
7559
|
}
|
|
7010
|
-
function
|
|
7560
|
+
function distance4(p1, p2) {
|
|
7011
7561
|
const dx = p2.x - p1.x;
|
|
7012
7562
|
const dy = p2.y - p1.y;
|
|
7013
7563
|
return Math.sqrt(dx * dx + dy * dy);
|
|
@@ -7225,15 +7775,15 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
|
|
|
7225
7775
|
return p1.x === p2.x && p1.y === p2.y && p1.z === p2.z;
|
|
7226
7776
|
}
|
|
7227
7777
|
// Get point at a specific distance along the path
|
|
7228
|
-
getPointAtDistance(
|
|
7229
|
-
|
|
7778
|
+
getPointAtDistance(distance5) {
|
|
7779
|
+
distance5 = Math.max(0, Math.min(distance5, this.totalPathLength));
|
|
7230
7780
|
const segment = this.pathSegments.find(
|
|
7231
|
-
(seg) =>
|
|
7781
|
+
(seg) => distance5 >= seg.startDistance && distance5 <= seg.endDistance
|
|
7232
7782
|
);
|
|
7233
7783
|
if (!segment) {
|
|
7234
7784
|
return this.inputRoute.route[this.inputRoute.route.length - 1];
|
|
7235
7785
|
}
|
|
7236
|
-
const factor = (
|
|
7786
|
+
const factor = (distance5 - segment.startDistance) / segment.length;
|
|
7237
7787
|
return {
|
|
7238
7788
|
x: segment.start.x + factor * (segment.end.x - segment.start.x),
|
|
7239
7789
|
y: segment.start.y + factor * (segment.end.y - segment.start.y),
|
|
@@ -7242,17 +7792,17 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
|
|
|
7242
7792
|
};
|
|
7243
7793
|
}
|
|
7244
7794
|
// Find nearest index in the original route for a given distance
|
|
7245
|
-
getNearestIndexForDistance(
|
|
7246
|
-
if (
|
|
7247
|
-
if (
|
|
7795
|
+
getNearestIndexForDistance(distance5) {
|
|
7796
|
+
if (distance5 <= 0) return 0;
|
|
7797
|
+
if (distance5 >= this.totalPathLength)
|
|
7248
7798
|
return this.inputRoute.route.length - 1;
|
|
7249
7799
|
const segmentIndex = this.pathSegments.findIndex(
|
|
7250
|
-
(seg) =>
|
|
7800
|
+
(seg) => distance5 >= seg.startDistance && distance5 <= seg.endDistance
|
|
7251
7801
|
);
|
|
7252
7802
|
if (segmentIndex === -1) return 0;
|
|
7253
7803
|
const segment = this.pathSegments[segmentIndex];
|
|
7254
7804
|
const midDistance = (segment.startDistance + segment.endDistance) / 2;
|
|
7255
|
-
return
|
|
7805
|
+
return distance5 > midDistance ? segmentIndex + 1 : segmentIndex;
|
|
7256
7806
|
}
|
|
7257
7807
|
// Check if a path segment is valid
|
|
7258
7808
|
isValidPathSegment(start, end) {
|
|
@@ -7354,10 +7904,10 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
|
|
|
7354
7904
|
}
|
|
7355
7905
|
this.currentStepSize = this.maxStepSize;
|
|
7356
7906
|
}
|
|
7357
|
-
moveHead(
|
|
7358
|
-
this.lastHeadMoveDistance =
|
|
7907
|
+
moveHead(distance5) {
|
|
7908
|
+
this.lastHeadMoveDistance = distance5;
|
|
7359
7909
|
this.headDistanceAlongPath = Math.min(
|
|
7360
|
-
this.headDistanceAlongPath +
|
|
7910
|
+
this.headDistanceAlongPath + distance5,
|
|
7361
7911
|
this.totalPathLength
|
|
7362
7912
|
);
|
|
7363
7913
|
}
|
|
@@ -7495,9 +8045,9 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
|
|
|
7495
8045
|
color: "red",
|
|
7496
8046
|
label: ["Tentative Head", `z: ${tentativeHead.z}`].join("\n")
|
|
7497
8047
|
});
|
|
7498
|
-
let
|
|
7499
|
-
while (
|
|
7500
|
-
const point = this.getPointAtDistance(
|
|
8048
|
+
let distance5 = 0;
|
|
8049
|
+
while (distance5 < this.totalPathLength) {
|
|
8050
|
+
const point = this.getPointAtDistance(distance5);
|
|
7501
8051
|
graphics.circles.push({
|
|
7502
8052
|
center: {
|
|
7503
8053
|
x: point.x,
|
|
@@ -7506,7 +8056,7 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
|
|
|
7506
8056
|
radius: 0.05,
|
|
7507
8057
|
fill: "rgba(100, 100, 100, 0.5)"
|
|
7508
8058
|
});
|
|
7509
|
-
|
|
8059
|
+
distance5 += this.totalPathLength / 20;
|
|
7510
8060
|
}
|
|
7511
8061
|
if (this.lastValidPath && this.lastValidPath.length > 1) {
|
|
7512
8062
|
for (let i = 0; i < this.lastValidPath.length - 1; i++) {
|
|
@@ -7840,7 +8390,7 @@ function definePipelineStep(solverName, solverClass, getConstructorParams, opts
|
|
|
7840
8390
|
onSolved: opts.onSolved
|
|
7841
8391
|
};
|
|
7842
8392
|
}
|
|
7843
|
-
var
|
|
8393
|
+
var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
7844
8394
|
constructor(srj, opts = {}) {
|
|
7845
8395
|
super();
|
|
7846
8396
|
this.srj = srj;
|
|
@@ -8072,6 +8622,11 @@ var CapacityMeshSolver = class extends BaseSolver {
|
|
|
8072
8622
|
this.timeSpentOnPhase[pipelineStepDef.solverName] = 0;
|
|
8073
8623
|
this.startTimeOfPhase[pipelineStepDef.solverName] = performance.now();
|
|
8074
8624
|
}
|
|
8625
|
+
solveUntilPhase(phase) {
|
|
8626
|
+
while (this.getCurrentPhase() !== phase) {
|
|
8627
|
+
this.step();
|
|
8628
|
+
}
|
|
8629
|
+
}
|
|
8075
8630
|
getCurrentPhase() {
|
|
8076
8631
|
return this.pipelineDef[this.currentPipelineStepIndex]?.solverName ?? "none";
|
|
8077
8632
|
}
|
|
@@ -8242,6 +8797,7 @@ var CapacityMeshSolver = class extends BaseSolver {
|
|
|
8242
8797
|
};
|
|
8243
8798
|
}
|
|
8244
8799
|
};
|
|
8800
|
+
var CapacityMeshSolver = AutoroutingPipelineSolver;
|
|
8245
8801
|
export {
|
|
8246
8802
|
CapacityMeshSolver,
|
|
8247
8803
|
calculateOptimalCapacityDepth,
|