@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.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
- ["closedFormTwoTraceSameLayer"],
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: "closedFormTwoTraceSameLayer",
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 distance4 = Math.sqrt(
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: distance4
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 distance4 = Math.sqrt(
6492
+ const distance5 = Math.sqrt(
5950
6493
  (node.center.x - point.x) ** 2 + (node.center.y - point.y) ** 2
5951
6494
  );
5952
- if (distance4 < minDistance) {
5953
- minDistance = distance4;
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 distance3(P, Q1);
7544
+ return distance4(P, Q1);
7002
7545
  }
7003
7546
  const c2 = dotProduct(v, v);
7004
7547
  if (c2 <= c1) {
7005
- return distance3(P, Q2);
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 distance3(P, Pb);
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 distance3(p1, p2) {
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(distance4) {
7236
- distance4 = Math.max(0, Math.min(distance4, this.totalPathLength));
7778
+ getPointAtDistance(distance5) {
7779
+ distance5 = Math.max(0, Math.min(distance5, this.totalPathLength));
7237
7780
  const segment = this.pathSegments.find(
7238
- (seg) => distance4 >= seg.startDistance && distance4 <= seg.endDistance
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 = (distance4 - segment.startDistance) / segment.length;
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(distance4) {
7253
- if (distance4 <= 0) return 0;
7254
- if (distance4 >= this.totalPathLength)
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) => distance4 >= seg.startDistance && distance4 <= seg.endDistance
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 distance4 > midDistance ? segmentIndex + 1 : segmentIndex;
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(distance4) {
7365
- this.lastHeadMoveDistance = distance4;
7907
+ moveHead(distance5) {
7908
+ this.lastHeadMoveDistance = distance5;
7366
7909
  this.headDistanceAlongPath = Math.min(
7367
- this.headDistanceAlongPath + distance4,
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 distance4 = 0;
7506
- while (distance4 < this.totalPathLength) {
7507
- const point = this.getPointAtDistance(distance4);
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
- distance4 += this.totalPathLength / 20;
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++) {