@tscircuit/capacity-autorouter 0.0.41 → 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 +6 -2
- package/dist/index.js +570 -27
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
}
|
|
@@ -6998,23 +7541,23 @@ function pointToSegmentDistance4(P, Q1, Q2) {
|
|
|
6998
7541
|
const w = { x: P.x - Q1.x, y: P.y - Q1.y };
|
|
6999
7542
|
const c1 = dotProduct(w, v);
|
|
7000
7543
|
if (c1 <= 0) {
|
|
7001
|
-
return
|
|
7544
|
+
return distance4(P, Q1);
|
|
7002
7545
|
}
|
|
7003
7546
|
const c2 = dotProduct(v, v);
|
|
7004
7547
|
if (c2 <= c1) {
|
|
7005
|
-
return
|
|
7548
|
+
return distance4(P, Q2);
|
|
7006
7549
|
}
|
|
7007
7550
|
const b = c1 / c2;
|
|
7008
7551
|
const Pb = {
|
|
7009
7552
|
x: Q1.x + b * v.x,
|
|
7010
7553
|
y: Q1.y + b * v.y
|
|
7011
7554
|
};
|
|
7012
|
-
return
|
|
7555
|
+
return distance4(P, Pb);
|
|
7013
7556
|
}
|
|
7014
7557
|
function dotProduct(v1, v2) {
|
|
7015
7558
|
return v1.x * v2.x + v1.y * v2.y;
|
|
7016
7559
|
}
|
|
7017
|
-
function
|
|
7560
|
+
function distance4(p1, p2) {
|
|
7018
7561
|
const dx = p2.x - p1.x;
|
|
7019
7562
|
const dy = p2.y - p1.y;
|
|
7020
7563
|
return Math.sqrt(dx * dx + dy * dy);
|
|
@@ -7232,15 +7775,15 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
|
|
|
7232
7775
|
return p1.x === p2.x && p1.y === p2.y && p1.z === p2.z;
|
|
7233
7776
|
}
|
|
7234
7777
|
// Get point at a specific distance along the path
|
|
7235
|
-
getPointAtDistance(
|
|
7236
|
-
|
|
7778
|
+
getPointAtDistance(distance5) {
|
|
7779
|
+
distance5 = Math.max(0, Math.min(distance5, this.totalPathLength));
|
|
7237
7780
|
const segment = this.pathSegments.find(
|
|
7238
|
-
(seg) =>
|
|
7781
|
+
(seg) => distance5 >= seg.startDistance && distance5 <= seg.endDistance
|
|
7239
7782
|
);
|
|
7240
7783
|
if (!segment) {
|
|
7241
7784
|
return this.inputRoute.route[this.inputRoute.route.length - 1];
|
|
7242
7785
|
}
|
|
7243
|
-
const factor = (
|
|
7786
|
+
const factor = (distance5 - segment.startDistance) / segment.length;
|
|
7244
7787
|
return {
|
|
7245
7788
|
x: segment.start.x + factor * (segment.end.x - segment.start.x),
|
|
7246
7789
|
y: segment.start.y + factor * (segment.end.y - segment.start.y),
|
|
@@ -7249,17 +7792,17 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
|
|
|
7249
7792
|
};
|
|
7250
7793
|
}
|
|
7251
7794
|
// Find nearest index in the original route for a given distance
|
|
7252
|
-
getNearestIndexForDistance(
|
|
7253
|
-
if (
|
|
7254
|
-
if (
|
|
7795
|
+
getNearestIndexForDistance(distance5) {
|
|
7796
|
+
if (distance5 <= 0) return 0;
|
|
7797
|
+
if (distance5 >= this.totalPathLength)
|
|
7255
7798
|
return this.inputRoute.route.length - 1;
|
|
7256
7799
|
const segmentIndex = this.pathSegments.findIndex(
|
|
7257
|
-
(seg) =>
|
|
7800
|
+
(seg) => distance5 >= seg.startDistance && distance5 <= seg.endDistance
|
|
7258
7801
|
);
|
|
7259
7802
|
if (segmentIndex === -1) return 0;
|
|
7260
7803
|
const segment = this.pathSegments[segmentIndex];
|
|
7261
7804
|
const midDistance = (segment.startDistance + segment.endDistance) / 2;
|
|
7262
|
-
return
|
|
7805
|
+
return distance5 > midDistance ? segmentIndex + 1 : segmentIndex;
|
|
7263
7806
|
}
|
|
7264
7807
|
// Check if a path segment is valid
|
|
7265
7808
|
isValidPathSegment(start, end) {
|
|
@@ -7361,10 +7904,10 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
|
|
|
7361
7904
|
}
|
|
7362
7905
|
this.currentStepSize = this.maxStepSize;
|
|
7363
7906
|
}
|
|
7364
|
-
moveHead(
|
|
7365
|
-
this.lastHeadMoveDistance =
|
|
7907
|
+
moveHead(distance5) {
|
|
7908
|
+
this.lastHeadMoveDistance = distance5;
|
|
7366
7909
|
this.headDistanceAlongPath = Math.min(
|
|
7367
|
-
this.headDistanceAlongPath +
|
|
7910
|
+
this.headDistanceAlongPath + distance5,
|
|
7368
7911
|
this.totalPathLength
|
|
7369
7912
|
);
|
|
7370
7913
|
}
|
|
@@ -7502,9 +8045,9 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
|
|
|
7502
8045
|
color: "red",
|
|
7503
8046
|
label: ["Tentative Head", `z: ${tentativeHead.z}`].join("\n")
|
|
7504
8047
|
});
|
|
7505
|
-
let
|
|
7506
|
-
while (
|
|
7507
|
-
const point = this.getPointAtDistance(
|
|
8048
|
+
let distance5 = 0;
|
|
8049
|
+
while (distance5 < this.totalPathLength) {
|
|
8050
|
+
const point = this.getPointAtDistance(distance5);
|
|
7508
8051
|
graphics.circles.push({
|
|
7509
8052
|
center: {
|
|
7510
8053
|
x: point.x,
|
|
@@ -7513,7 +8056,7 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
|
|
|
7513
8056
|
radius: 0.05,
|
|
7514
8057
|
fill: "rgba(100, 100, 100, 0.5)"
|
|
7515
8058
|
});
|
|
7516
|
-
|
|
8059
|
+
distance5 += this.totalPathLength / 20;
|
|
7517
8060
|
}
|
|
7518
8061
|
if (this.lastValidPath && this.lastValidPath.length > 1) {
|
|
7519
8062
|
for (let i = 0; i < this.lastValidPath.length - 1; i++) {
|