@tscircuit/capacity-autorouter 0.0.31 → 0.0.33

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
@@ -1403,6 +1403,7 @@ var CapacityMeshNodeSolver2_NodeUnderObstacle = class extends CapacityMeshNodeSo
1403
1403
  this.opts = opts;
1404
1404
  }
1405
1405
  VIA_DIAMETER = 0.6;
1406
+ OBSTACLE_MARGIN = 0.1;
1406
1407
  isNodeCompletelyOutsideBounds(node) {
1407
1408
  return node.center.x + node.width / 2 < this.srj.bounds.minX || node.center.x - node.width / 2 > this.srj.bounds.maxX || node.center.y + node.height / 2 < this.srj.bounds.minY || node.center.y - node.height / 2 > this.srj.bounds.maxY;
1408
1409
  }
@@ -1507,7 +1508,7 @@ var CapacityMeshNodeSolver2_NodeUnderObstacle = class extends CapacityMeshNodeSo
1507
1508
  const unfinishedNewNodes = [];
1508
1509
  for (const childNode of childNodes) {
1509
1510
  const shouldBeXYSubdivided = this.shouldNodeBeXYSubdivided(childNode);
1510
- const shouldBeZSubdivided = childNode.availableZ.length > 1 && !shouldBeXYSubdivided && (childNode._containsObstacle || childNode.width < this.VIA_DIAMETER);
1511
+ const shouldBeZSubdivided = childNode.availableZ.length > 1 && !shouldBeXYSubdivided && (childNode._containsObstacle || childNode.width < this.VIA_DIAMETER + this.OBSTACLE_MARGIN);
1511
1512
  if (shouldBeXYSubdivided) {
1512
1513
  unfinishedNewNodes.push(childNode);
1513
1514
  } else if (!shouldBeXYSubdivided && !childNode._containsObstacle && !shouldBeZSubdivided) {
@@ -2782,6 +2783,687 @@ var HyperParameterSupervisorSolver = class extends BaseSolver {
2782
2783
  }
2783
2784
  };
2784
2785
 
2786
+ // lib/solvers/HighDensitySolver/TwoRouteHighDensitySolver/findCircleLineIntersections.ts
2787
+ var findCircleLineIntersections = (circle, line) => {
2788
+ const cx = circle.x;
2789
+ const cy = circle.y;
2790
+ const r = circle.r;
2791
+ const x1 = line.p1.x;
2792
+ const y1 = line.p1.y;
2793
+ const x2 = line.p2.x;
2794
+ const y2 = line.p2.y;
2795
+ if (Math.abs(x2 - x1) < 1e-3) {
2796
+ const x = x1;
2797
+ const a = r * r - (x - cx) ** 2;
2798
+ if (a < 0) return [];
2799
+ if (Math.abs(a) < 1e-3) {
2800
+ const y = cy;
2801
+ if (y >= Math.min(y1, y2) && y <= Math.max(y1, y2)) {
2802
+ return [{ x, y }];
2803
+ }
2804
+ return [];
2805
+ }
2806
+ const y_12 = cy + Math.sqrt(a);
2807
+ const y_22 = cy - Math.sqrt(a);
2808
+ const points2 = [];
2809
+ if (y_12 >= Math.min(y1, y2) && y_12 <= Math.max(y1, y2)) {
2810
+ points2.push({ x, y: y_12 });
2811
+ }
2812
+ if (y_22 >= Math.min(y1, y2) && y_22 <= Math.max(y1, y2)) {
2813
+ points2.push({ x, y: y_22 });
2814
+ }
2815
+ return points2;
2816
+ }
2817
+ const m = (y2 - y1) / (x2 - x1);
2818
+ const b = y1 - m * x1;
2819
+ const A = 1 + m * m;
2820
+ const B = 2 * (m * b - m * cy - cx);
2821
+ const C = cx * cx + (b - cy) * (b - cy) - r * r;
2822
+ const discriminant = B * B - 4 * A * C;
2823
+ if (discriminant < 0) return [];
2824
+ if (Math.abs(discriminant) < 1e-3) {
2825
+ const x = -B / (2 * A);
2826
+ const y = m * x + b;
2827
+ if (x >= Math.min(x1, x2) && x <= Math.max(x1, x2) && y >= Math.min(y1, y2) && y <= Math.max(y1, y2)) {
2828
+ return [{ x, y }];
2829
+ }
2830
+ return [];
2831
+ }
2832
+ const x_1 = (-B + Math.sqrt(discriminant)) / (2 * A);
2833
+ const x_2 = (-B - Math.sqrt(discriminant)) / (2 * A);
2834
+ const y_1 = m * x_1 + b;
2835
+ const y_2 = m * x_2 + b;
2836
+ const points = [];
2837
+ if (x_1 >= Math.min(x1, x2) && x_1 <= Math.max(x1, x2) && y_1 >= Math.min(y1, y2) && y_1 <= Math.max(y1, y2)) {
2838
+ points.push({ x: x_1, y: y_1 });
2839
+ }
2840
+ if (x_2 >= Math.min(x1, x2) && x_2 <= Math.max(x1, x2) && y_2 >= Math.min(y1, y2) && y_2 <= Math.max(y1, y2)) {
2841
+ points.push({ x: x_2, y: y_2 });
2842
+ }
2843
+ return points;
2844
+ };
2845
+
2846
+ // lib/solvers/HighDensitySolver/TwoRouteHighDensitySolver/TwoCrossingRoutesHighDensitySolver.ts
2847
+ var TwoCrossingRoutesHighDensitySolver = class extends BaseSolver {
2848
+ // Input parameters
2849
+ nodeWithPortPoints;
2850
+ routes;
2851
+ // Configuration parameters
2852
+ viaDiameter;
2853
+ traceThickness;
2854
+ obstacleMargin;
2855
+ layerCount = 2;
2856
+ debugViaPositions;
2857
+ // Solution state
2858
+ solvedRoutes = [];
2859
+ // Bounds
2860
+ bounds;
2861
+ constructor(params) {
2862
+ super();
2863
+ this.nodeWithPortPoints = params.nodeWithPortPoints;
2864
+ this.viaDiameter = params?.viaDiameter ?? 0.6;
2865
+ this.traceThickness = params?.traceThickness ?? 0.15;
2866
+ this.obstacleMargin = params?.obstacleMargin ?? 0.1;
2867
+ this.layerCount = params?.layerCount ?? 2;
2868
+ this.debugViaPositions = [];
2869
+ this.routes = this.extractRoutesFromNode();
2870
+ this.bounds = this.calculateBounds();
2871
+ if (this.routes.length !== 2) {
2872
+ this.failed = true;
2873
+ return;
2874
+ }
2875
+ const [routeA, routeB] = this.routes;
2876
+ const routeAStartsAndEndsOnSameLayer = routeA.startPort.z === routeA.endPort.z;
2877
+ if (!routeAStartsAndEndsOnSameLayer) {
2878
+ this.failed = true;
2879
+ return;
2880
+ }
2881
+ const routeBStartsAndEndsOnSameLayer = routeB.startPort.z === routeB.endPort.z;
2882
+ if (!routeBStartsAndEndsOnSameLayer) {
2883
+ this.failed = true;
2884
+ return;
2885
+ }
2886
+ const routesAreSameLayer = routeA.startPort.z === routeB.startPort.z;
2887
+ if (!routesAreSameLayer) {
2888
+ this.failed = true;
2889
+ return;
2890
+ }
2891
+ }
2892
+ /**
2893
+ * Extract routes that need to be connected from the node data
2894
+ */
2895
+ extractRoutesFromNode() {
2896
+ const routes = [];
2897
+ const connectedPorts = this.nodeWithPortPoints.portPoints;
2898
+ const connectionGroups = /* @__PURE__ */ new Map();
2899
+ for (const connectedPort of connectedPorts) {
2900
+ const { connectionName } = connectedPort;
2901
+ if (!connectionGroups.has(connectionName)) {
2902
+ connectionGroups.set(connectionName, []);
2903
+ }
2904
+ connectionGroups.get(connectionName)?.push(connectedPort);
2905
+ }
2906
+ for (const [connectionName, points] of connectionGroups.entries()) {
2907
+ if (points.length === 2) {
2908
+ routes.push({
2909
+ startPort: { ...points[0], z: points[0].z ?? 0 },
2910
+ endPort: { ...points[1], z: points[1].z ?? 0 },
2911
+ connectionName
2912
+ });
2913
+ }
2914
+ }
2915
+ return routes;
2916
+ }
2917
+ /**
2918
+ * Calculate the bounding box of the node
2919
+ */
2920
+ calculateBounds() {
2921
+ return {
2922
+ minX: this.nodeWithPortPoints.center.x - this.nodeWithPortPoints.width / 2,
2923
+ maxX: this.nodeWithPortPoints.center.x + this.nodeWithPortPoints.width / 2,
2924
+ minY: this.nodeWithPortPoints.center.y - this.nodeWithPortPoints.height / 2,
2925
+ maxY: this.nodeWithPortPoints.center.y + this.nodeWithPortPoints.height / 2
2926
+ };
2927
+ }
2928
+ /**
2929
+ * Check if two routes are crossing
2930
+ */
2931
+ doRoutesCross(routeA, routeB) {
2932
+ return doSegmentsIntersect(
2933
+ routeA.startPort,
2934
+ routeA.endPort,
2935
+ routeB.startPort,
2936
+ routeB.endPort
2937
+ );
2938
+ }
2939
+ calculateViaPositions(routeA, routeB) {
2940
+ const outerBox = {
2941
+ width: this.bounds.maxX - this.bounds.minX,
2942
+ height: this.bounds.maxY - this.bounds.minY,
2943
+ x: this.bounds.minX,
2944
+ y: this.bounds.minY
2945
+ };
2946
+ const innerBox = {
2947
+ width: outerBox.width - 2 * this.obstacleMargin - this.viaDiameter,
2948
+ height: outerBox.height - 2 * this.obstacleMargin - this.viaDiameter,
2949
+ x: outerBox.x + this.obstacleMargin + this.viaDiameter / 2,
2950
+ y: outerBox.y + this.obstacleMargin + this.viaDiameter / 2
2951
+ };
2952
+ const K1 = this.viaDiameter + this.obstacleMargin;
2953
+ const pointA = routeB.startPort;
2954
+ const pointB = routeB.endPort;
2955
+ const corners = [
2956
+ { x: innerBox.x, y: innerBox.y },
2957
+ // Top-left (0)
2958
+ { x: innerBox.x + innerBox.width, y: innerBox.y },
2959
+ // Top-right (1)
2960
+ { x: innerBox.x + innerBox.width, y: innerBox.y + innerBox.height },
2961
+ // Bottom-right (2)
2962
+ { x: innerBox.x, y: innerBox.y + innerBox.height }
2963
+ // Bottom-left (3)
2964
+ ];
2965
+ const distanceBetween = (p1, p2) => {
2966
+ return distance(p1, p2);
2967
+ };
2968
+ const candidatePoints = [];
2969
+ corners.forEach((corner, index) => {
2970
+ if (distanceBetween(corner, pointA) >= K1 && distanceBetween(corner, pointB) >= K1) {
2971
+ candidatePoints.push({ ...corner, type: "corner", index });
2972
+ }
2973
+ });
2974
+ const edges = [
2975
+ { p1: corners[0], p2: corners[1] },
2976
+ // top
2977
+ { p1: corners[1], p2: corners[2] },
2978
+ // right
2979
+ { p1: corners[2], p2: corners[3] },
2980
+ // bottom
2981
+ { p1: corners[3], p2: corners[0] }
2982
+ // left
2983
+ ];
2984
+ [pointA, pointB].forEach((circleCenter, circleIndex) => {
2985
+ edges.forEach((edge, edgeIndex) => {
2986
+ const intersections = findCircleLineIntersections(
2987
+ { ...circleCenter, r: K1 },
2988
+ edge
2989
+ );
2990
+ intersections.forEach((point) => {
2991
+ const otherCircle = circleIndex === 0 ? pointB : pointA;
2992
+ if (distanceBetween(point, otherCircle) >= K1) {
2993
+ candidatePoints.push({
2994
+ ...point,
2995
+ type: "intersection",
2996
+ circle: circleIndex,
2997
+ edge: edgeIndex
2998
+ });
2999
+ }
3000
+ });
3001
+ });
3002
+ });
3003
+ if (candidatePoints.length < 2) {
3004
+ const relaxedK1 = K1 * 0.8;
3005
+ corners.forEach((corner, index) => {
3006
+ if (distanceBetween(corner, pointA) >= relaxedK1 && distanceBetween(corner, pointB) >= relaxedK1 && !candidatePoints.some((p) => p.x === corner.x && p.y === corner.y)) {
3007
+ candidatePoints.push({ ...corner, type: "relaxed_corner", index });
3008
+ }
3009
+ });
3010
+ if (candidatePoints.length < 2) {
3011
+ const sortedCorners = [...corners].sort((a, b) => {
3012
+ const aMinDist = Math.min(
3013
+ distanceBetween(a, pointA),
3014
+ distanceBetween(a, pointB)
3015
+ );
3016
+ const bMinDist = Math.min(
3017
+ distanceBetween(b, pointA),
3018
+ distanceBetween(b, pointB)
3019
+ );
3020
+ return bMinDist - aMinDist;
3021
+ });
3022
+ for (const corner of sortedCorners) {
3023
+ if (!candidatePoints.some((p) => p.x === corner.x && p.y === corner.y)) {
3024
+ candidatePoints.push({ ...corner, type: "forced_corner" });
3025
+ if (candidatePoints.length >= 2) break;
3026
+ }
3027
+ }
3028
+ }
3029
+ }
3030
+ if (candidatePoints.length < 2) {
3031
+ return null;
3032
+ }
3033
+ let maxDist = 0;
3034
+ let optimalPair = [
3035
+ candidatePoints[0],
3036
+ candidatePoints[candidatePoints.length > 1 ? 1 : 0]
3037
+ ];
3038
+ for (let i = 0; i < candidatePoints.length; i++) {
3039
+ for (let j = i + 1; j < candidatePoints.length; j++) {
3040
+ const dist = distanceBetween(candidatePoints[i], candidatePoints[j]);
3041
+ if (dist > maxDist) {
3042
+ maxDist = dist;
3043
+ optimalPair = [candidatePoints[i], candidatePoints[j]];
3044
+ }
3045
+ }
3046
+ }
3047
+ let via1 = { x: optimalPair[0].x, y: optimalPair[0].y };
3048
+ let via2 = { x: optimalPair[1].x, y: optimalPair[1].y };
3049
+ const via1DistToStart = distance(via1, routeA.startPort);
3050
+ const via2DistToStart = distance(via2, routeA.startPort);
3051
+ if (via2DistToStart < via1DistToStart) {
3052
+ ;
3053
+ [via1, via2] = [via2, via1];
3054
+ }
3055
+ return {
3056
+ via1,
3057
+ via2
3058
+ };
3059
+ }
3060
+ /**
3061
+ * Create a route with properly placed vias
3062
+ */
3063
+ createRoute(start, end, via1, via2, connectionName) {
3064
+ const middleZ = start.z === 0 ? 1 : 0;
3065
+ const route = [
3066
+ { x: start.x, y: start.y, z: start.z ?? 0 },
3067
+ { x: via1.x, y: via1.y, z: start.z ?? 0 },
3068
+ { x: via1.x, y: via1.y, z: middleZ },
3069
+ // Via transition to layer 1
3070
+ { x: via2.x, y: via2.y, z: middleZ },
3071
+ // Stay on layer 1
3072
+ { x: via2.x, y: via2.y, z: end.z ?? 0 },
3073
+ // Via transition back
3074
+ { x: end.x, y: end.y, z: end.z ?? 0 }
3075
+ ];
3076
+ return {
3077
+ connectionName,
3078
+ route,
3079
+ traceThickness: this.traceThickness,
3080
+ viaDiameter: this.viaDiameter,
3081
+ vias: [via1, via2]
3082
+ };
3083
+ }
3084
+ /**
3085
+ * Try to solve with routeA going over and routeB staying on layer 0
3086
+ */
3087
+ trySolveAOverB(routeA, routeB) {
3088
+ const viaPositions = this.calculateViaPositions(routeA, routeB);
3089
+ if (viaPositions) {
3090
+ this.debugViaPositions.push(viaPositions);
3091
+ } else {
3092
+ return false;
3093
+ }
3094
+ const { via1, via2 } = this.optimizeViaPositions(viaPositions);
3095
+ const routeASolution = this.createRoute(
3096
+ routeA.startPort,
3097
+ routeA.endPort,
3098
+ via1,
3099
+ via2,
3100
+ routeA.connectionName
3101
+ );
3102
+ const midSegmentStart = { x: via1.x, y: via1.y, z: 1 };
3103
+ const midSegmentEnd = { x: via2.x, y: via2.y, z: 1 };
3104
+ const orthogonalPoints = this.calculateShortestOrthogonalRoutePoints(
3105
+ routeB.startPort,
3106
+ routeB.endPort,
3107
+ midSegmentStart,
3108
+ midSegmentEnd,
3109
+ routeA.startPort,
3110
+ routeA.endPort
3111
+ ) ?? this.calculateConservativeOrthogonalRoutePoints(
3112
+ routeB.startPort,
3113
+ routeB.endPort,
3114
+ midSegmentStart,
3115
+ midSegmentEnd,
3116
+ routeA.startPort,
3117
+ routeA.endPort
3118
+ );
3119
+ const routeBSolution = {
3120
+ connectionName: routeB.connectionName,
3121
+ route: orthogonalPoints,
3122
+ traceThickness: this.traceThickness,
3123
+ viaDiameter: this.viaDiameter,
3124
+ vias: []
3125
+ };
3126
+ this.solvedRoutes.push(routeASolution, routeBSolution);
3127
+ return true;
3128
+ }
3129
+ optimizeViaPositions(viaPositions) {
3130
+ const { via1, via2 } = viaPositions;
3131
+ const minRequiredDistance = (this.viaDiameter + this.traceThickness + this.obstacleMargin) * 2;
3132
+ const currentDistance = distance(via1, via2);
3133
+ if (currentDistance <= minRequiredDistance) {
3134
+ return viaPositions;
3135
+ }
3136
+ const dirX = via2.x - via1.x;
3137
+ const dirY = via2.y - via1.y;
3138
+ const dirLength = Math.sqrt(dirX * dirX + dirY * dirY);
3139
+ const normDirX = dirX / dirLength;
3140
+ const normDirY = dirY / dirLength;
3141
+ const midpointX = (via1.x + via2.x) / 2;
3142
+ const midpointY = (via1.y + via2.y) / 2;
3143
+ const moveDistance = (currentDistance - minRequiredDistance) / 2;
3144
+ const newVia1 = {
3145
+ x: via1.x + normDirX * moveDistance,
3146
+ y: via1.y + normDirY * moveDistance
3147
+ };
3148
+ const newVia2 = {
3149
+ x: via2.x - normDirX * moveDistance,
3150
+ y: via2.y - normDirY * moveDistance
3151
+ };
3152
+ return {
3153
+ via1: newVia1,
3154
+ via2: newVia2
3155
+ };
3156
+ }
3157
+ /**
3158
+ * Calculate the orthogonal route points for the second route
3159
+ */
3160
+ calculateConservativeOrthogonalRoutePoints(start, end, via1, via2, otherRouteStart, otherRouteEnd) {
3161
+ const outerBox = {
3162
+ width: this.bounds.maxX - this.bounds.minX,
3163
+ height: this.bounds.maxY - this.bounds.minY,
3164
+ x: this.bounds.minX,
3165
+ y: this.bounds.minY
3166
+ };
3167
+ const innerEdgeBox = {
3168
+ width: outerBox.width - 2 * this.obstacleMargin - this.traceThickness,
3169
+ height: outerBox.height - 2 * this.obstacleMargin - this.traceThickness,
3170
+ x: outerBox.x + this.obstacleMargin + this.traceThickness / 2,
3171
+ y: outerBox.y + this.obstacleMargin + this.traceThickness / 2
3172
+ };
3173
+ const midSegmentDX = via2.x - via1.x;
3174
+ const midSegmentDY = via2.y - via1.y;
3175
+ const orthDX = -midSegmentDY;
3176
+ const orthDY = midSegmentDX;
3177
+ const orthLength = Math.sqrt(orthDX * orthDX + orthDY * orthDY);
3178
+ const normOrthDX = orthDX / orthLength;
3179
+ const normOrthDY = orthDY / orthLength;
3180
+ const midpointX = (via1.x + via2.x) / 2;
3181
+ const midpointY = (via1.y + via2.y) / 2;
3182
+ const calculateIntersections = () => {
3183
+ const intersections2 = [];
3184
+ const leftT = (innerEdgeBox.x - midpointX) / normOrthDX;
3185
+ const leftY = midpointY + leftT * normOrthDY;
3186
+ if (leftY >= innerEdgeBox.y && leftY <= innerEdgeBox.y + innerEdgeBox.height) {
3187
+ intersections2.push({ x: innerEdgeBox.x, y: leftY });
3188
+ }
3189
+ const rightT = (innerEdgeBox.x + innerEdgeBox.width - midpointX) / normOrthDX;
3190
+ const rightY = midpointY + rightT * normOrthDY;
3191
+ if (rightY >= innerEdgeBox.y && rightY <= innerEdgeBox.y + innerEdgeBox.height) {
3192
+ intersections2.push({
3193
+ x: innerEdgeBox.x + innerEdgeBox.width,
3194
+ y: rightY
3195
+ });
3196
+ }
3197
+ const topT = (innerEdgeBox.y - midpointY) / normOrthDY;
3198
+ const topX = midpointX + topT * normOrthDX;
3199
+ if (topX >= innerEdgeBox.x && topX <= innerEdgeBox.x + innerEdgeBox.width) {
3200
+ intersections2.push({ x: topX, y: innerEdgeBox.y });
3201
+ }
3202
+ const bottomT = (innerEdgeBox.y + innerEdgeBox.height - midpointY) / normOrthDY;
3203
+ const bottomX = midpointX + bottomT * normOrthDX;
3204
+ if (bottomX >= innerEdgeBox.x && bottomX <= innerEdgeBox.x + innerEdgeBox.width) {
3205
+ intersections2.push({
3206
+ x: bottomX,
3207
+ y: innerEdgeBox.y + innerEdgeBox.height
3208
+ });
3209
+ }
3210
+ return intersections2;
3211
+ };
3212
+ const intersections = calculateIntersections();
3213
+ if (intersections.length < 2) {
3214
+ return [
3215
+ { x: start.x, y: start.y, z: start.z ?? 0 },
3216
+ { x: end.x, y: end.y, z: end.z ?? 0 }
3217
+ ];
3218
+ }
3219
+ const sortedIntersections = [...intersections].sort((a, b) => {
3220
+ const distA = distance(a, start);
3221
+ const distB = distance(b, start);
3222
+ return distA - distB;
3223
+ });
3224
+ let middlePoint1 = sortedIntersections[0];
3225
+ let middlePoint2 = sortedIntersections[intersections.length - 1];
3226
+ if (doSegmentsIntersect(start, middlePoint1, otherRouteStart, via1) || doSegmentsIntersect(end, middlePoint2, otherRouteEnd, via2)) {
3227
+ ;
3228
+ [middlePoint1, middlePoint2] = [middlePoint2, middlePoint1];
3229
+ }
3230
+ return [
3231
+ { x: start.x, y: start.y, z: start.z ?? 0 },
3232
+ { x: middlePoint1.x, y: middlePoint1.y, z: start.z ?? 0 },
3233
+ { x: middlePoint2.x, y: middlePoint2.y, z: start.z ?? 0 },
3234
+ { x: end.x, y: end.y, z: end.z ?? 0 }
3235
+ ];
3236
+ }
3237
+ calculateShortestOrthogonalRoutePoints(start, end, via1, via2, otherRouteStart, otherRouteEnd) {
3238
+ const midSegmentCenter = {
3239
+ x: (via1.x + via2.x) / 2,
3240
+ y: (via1.y + via2.y) / 2
3241
+ };
3242
+ const midSegmentDirection = {
3243
+ x: via2.x - via1.x,
3244
+ y: via2.y - via1.y
3245
+ };
3246
+ const midSegmentLength = distance(via1, via2);
3247
+ const normOrthDX = midSegmentDirection.y / midSegmentLength;
3248
+ const normOrthDY = midSegmentDirection.x / midSegmentLength;
3249
+ let orthogonalPoint1 = {
3250
+ x: midSegmentCenter.x + midSegmentLength / 2 * normOrthDY,
3251
+ y: midSegmentCenter.y - midSegmentLength / 2 * normOrthDX
3252
+ };
3253
+ let orthogonalPoint2 = {
3254
+ x: midSegmentCenter.x - midSegmentLength / 2 * normOrthDY,
3255
+ y: midSegmentCenter.y + midSegmentLength / 2 * normOrthDX
3256
+ };
3257
+ if (distance(orthogonalPoint2, start) < distance(orthogonalPoint1, start)) {
3258
+ ;
3259
+ [orthogonalPoint1, orthogonalPoint2] = [
3260
+ orthogonalPoint2,
3261
+ orthogonalPoint1
3262
+ ];
3263
+ }
3264
+ if (doSegmentsIntersect(start, orthogonalPoint1, otherRouteStart, via1) || doSegmentsIntersect(end, orthogonalPoint2, otherRouteEnd, via2)) {
3265
+ ;
3266
+ [orthogonalPoint1, orthogonalPoint2] = [
3267
+ orthogonalPoint2,
3268
+ orthogonalPoint1
3269
+ ];
3270
+ }
3271
+ if (doSegmentsIntersect(start, orthogonalPoint2, otherRouteStart, via1) || doSegmentsIntersect(end, orthogonalPoint1, otherRouteEnd, via2)) {
3272
+ return null;
3273
+ }
3274
+ return [
3275
+ { x: start.x, y: start.y, z: start.z ?? 0 },
3276
+ { x: orthogonalPoint1.x, y: orthogonalPoint1.y, z: start.z ?? 0 },
3277
+ { x: orthogonalPoint2.x, y: orthogonalPoint2.y, z: start.z ?? 0 },
3278
+ { x: end.x, y: end.y, z: end.z ?? 0 }
3279
+ ];
3280
+ }
3281
+ /**
3282
+ * Main step method that attempts to solve the two crossing routes
3283
+ */
3284
+ _step() {
3285
+ if (this.routes.length !== 2) {
3286
+ this.failed = true;
3287
+ return;
3288
+ }
3289
+ const [routeA, routeB] = this.routes;
3290
+ if (!this.doRoutesCross(routeA, routeB)) {
3291
+ const routeASolution = {
3292
+ connectionName: routeA.connectionName,
3293
+ route: [
3294
+ {
3295
+ x: routeA.startPort.x,
3296
+ y: routeA.startPort.y,
3297
+ z: routeA.startPort.z ?? 0
3298
+ },
3299
+ {
3300
+ x: routeA.endPort.x,
3301
+ y: routeA.endPort.y,
3302
+ z: routeA.endPort.z ?? 0
3303
+ }
3304
+ ],
3305
+ traceThickness: this.traceThickness,
3306
+ viaDiameter: this.viaDiameter,
3307
+ vias: []
3308
+ };
3309
+ const routeBSolution = {
3310
+ connectionName: routeB.connectionName,
3311
+ route: [
3312
+ {
3313
+ x: routeB.startPort.x,
3314
+ y: routeB.startPort.y,
3315
+ z: routeB.startPort.z ?? 0
3316
+ },
3317
+ {
3318
+ x: routeB.endPort.x,
3319
+ y: routeB.endPort.y,
3320
+ z: routeB.endPort.z ?? 0
3321
+ }
3322
+ ],
3323
+ traceThickness: this.traceThickness,
3324
+ viaDiameter: this.viaDiameter,
3325
+ vias: []
3326
+ };
3327
+ this.solvedRoutes.push(routeASolution, routeBSolution);
3328
+ this.solved = true;
3329
+ return;
3330
+ }
3331
+ if (this.trySolveAOverB(routeA, routeB)) {
3332
+ this.solved = true;
3333
+ return;
3334
+ }
3335
+ if (this.trySolveAOverB(routeB, routeA)) {
3336
+ this.solved = true;
3337
+ return;
3338
+ }
3339
+ this.failed = true;
3340
+ }
3341
+ /**
3342
+ * Visualization for debugging
3343
+ */
3344
+ visualize() {
3345
+ const graphics = {
3346
+ lines: [],
3347
+ points: [],
3348
+ rects: [],
3349
+ circles: []
3350
+ };
3351
+ graphics.rects.push({
3352
+ center: {
3353
+ x: (this.bounds.minX + this.bounds.maxX) / 2,
3354
+ y: (this.bounds.minY + this.bounds.maxY) / 2
3355
+ },
3356
+ width: this.bounds.maxX - this.bounds.minX,
3357
+ height: this.bounds.maxY - this.bounds.minY,
3358
+ stroke: "rgba(0, 0, 0, 0.5)",
3359
+ fill: "rgba(240, 240, 240, 0.1)",
3360
+ label: "PCB Bounds"
3361
+ });
3362
+ for (const route of this.routes) {
3363
+ graphics.points.push({
3364
+ x: route.startPort.x,
3365
+ y: route.startPort.y,
3366
+ label: `${route.connectionName} start`,
3367
+ color: "orange"
3368
+ });
3369
+ graphics.points.push({
3370
+ x: route.endPort.x,
3371
+ y: route.endPort.y,
3372
+ label: `${route.connectionName} end`,
3373
+ color: "orange"
3374
+ });
3375
+ graphics.lines.push({
3376
+ points: [route.startPort, route.endPort],
3377
+ strokeColor: "rgba(255, 0, 0, 0.5)",
3378
+ label: `${route.connectionName} direct`
3379
+ });
3380
+ }
3381
+ for (let i = 0; i < this.debugViaPositions.length; i++) {
3382
+ const { via1, via2 } = this.debugViaPositions[i];
3383
+ const colors = ["rgba(255, 165, 0, 0.7)", "rgba(128, 0, 128, 0.7)"];
3384
+ const color = colors[i % colors.length];
3385
+ graphics.circles.push({
3386
+ center: via1,
3387
+ radius: this.viaDiameter / 2,
3388
+ fill: color,
3389
+ stroke: "rgba(0, 0, 0, 0.5)",
3390
+ label: `Computed Via A (attempt ${i + 1})`
3391
+ });
3392
+ graphics.circles.push({
3393
+ center: via2,
3394
+ radius: this.viaDiameter / 2,
3395
+ fill: color,
3396
+ stroke: "rgba(0, 0, 0, 0.5)",
3397
+ label: `Computed Via B (attempt ${i + 1})`
3398
+ });
3399
+ const safetyMargin = this.viaDiameter / 2 + this.obstacleMargin;
3400
+ graphics.circles.push({
3401
+ center: via1,
3402
+ radius: safetyMargin,
3403
+ stroke: color,
3404
+ fill: "rgba(0, 0, 0, 0)",
3405
+ label: "Safety Margin"
3406
+ });
3407
+ graphics.circles.push({
3408
+ center: via2,
3409
+ radius: safetyMargin,
3410
+ stroke: color,
3411
+ fill: "rgba(0, 0, 0, 0)",
3412
+ label: "Safety Margin"
3413
+ });
3414
+ graphics.lines.push({
3415
+ points: [
3416
+ this.routes[i % 2].startPort,
3417
+ via1,
3418
+ via2,
3419
+ this.routes[i % 2].endPort
3420
+ ],
3421
+ strokeColor: `${color.substring(0, color.lastIndexOf(","))}, 0.3)`,
3422
+ strokeDash: [5, 5],
3423
+ label: `Potential Route (attempt ${i + 1})`
3424
+ });
3425
+ }
3426
+ for (let si = 0; si < this.solvedRoutes.length; si++) {
3427
+ const route = this.solvedRoutes[si];
3428
+ const routeColor = si % 2 === 0 ? "rgba(0, 255, 0, 0.75)" : "rgba(255, 0, 255, 0.75)";
3429
+ for (let i = 0; i < route.route.length - 1; i++) {
3430
+ const pointA = route.route[i];
3431
+ const pointB = route.route[i + 1];
3432
+ graphics.lines.push({
3433
+ points: [pointA, pointB],
3434
+ strokeColor: routeColor,
3435
+ strokeDash: pointA.z === 1 ? [0.2, 0.2] : void 0,
3436
+ strokeWidth: route.traceThickness,
3437
+ label: `${route.connectionName} z=${pointA.z}`
3438
+ });
3439
+ }
3440
+ for (const via of route.vias) {
3441
+ graphics.circles.push({
3442
+ center: via,
3443
+ radius: this.viaDiameter / 2,
3444
+ fill: "rgba(0, 0, 255, 0.8)",
3445
+ stroke: "black",
3446
+ label: "Solved Via"
3447
+ });
3448
+ graphics.circles.push({
3449
+ center: via,
3450
+ radius: this.viaDiameter / 2 + this.obstacleMargin,
3451
+ fill: "rgba(0, 0, 255, 0.3)",
3452
+ stroke: "black",
3453
+ label: "Via Margin"
3454
+ });
3455
+ }
3456
+ }
3457
+ return graphics;
3458
+ }
3459
+ /**
3460
+ * Get the solved routes
3461
+ */
3462
+ getSolvedRoutes() {
3463
+ return this.solvedRoutes;
3464
+ }
3465
+ };
3466
+
2785
3467
  // lib/solvers/HyperHighDensitySolver/HyperSingleIntraNodeSolver.ts
2786
3468
  var HyperSingleIntraNodeSolver = class extends HyperParameterSupervisorSolver {
2787
3469
  constructorParams;
@@ -2797,6 +3479,7 @@ var HyperSingleIntraNodeSolver = class extends HyperParameterSupervisorSolver {
2797
3479
  }
2798
3480
  getCombinationDefs() {
2799
3481
  return [
3482
+ ["closedFormTwoTraceSameLayer"],
2800
3483
  ["majorCombinations", "orderings6", "cellSizeFactor"],
2801
3484
  ["noVias"],
2802
3485
  ["orderings50"],
@@ -2885,6 +3568,14 @@ var HyperSingleIntraNodeSolver = class extends HyperParameterSupervisorSolver {
2885
3568
  possibleValues: Array.from({ length: 50 }, (_, i) => ({
2886
3569
  SHUFFLE_SEED: 100 + i
2887
3570
  }))
3571
+ },
3572
+ {
3573
+ name: "closedFormTwoTraceSameLayer",
3574
+ possibleValues: [
3575
+ {
3576
+ CLOSED_FORM_TWO_TRACE_SAME_LAYER: true
3577
+ }
3578
+ ]
2888
3579
  }
2889
3580
  ];
2890
3581
  }
@@ -2895,6 +3586,11 @@ var HyperSingleIntraNodeSolver = class extends HyperParameterSupervisorSolver {
2895
3586
  return 1 - (solver.progress || 0);
2896
3587
  }
2897
3588
  generateSolver(hyperParameters) {
3589
+ if (hyperParameters.CLOSED_FORM_TWO_TRACE_SAME_LAYER) {
3590
+ return new TwoCrossingRoutesHighDensitySolver({
3591
+ nodeWithPortPoints: this.nodeWithPortPoints
3592
+ });
3593
+ }
2898
3594
  return new IntraNodeRouteSolver({
2899
3595
  ...this.constructorParams,
2900
3596
  hyperParameters
@@ -4462,9 +5158,6 @@ var UnravelSectionSolver = class extends BaseSolver {
4462
5158
  );
4463
5159
  if (!neighbor) continue;
4464
5160
  neighbors.push(neighbor);
4465
- this.queuedOrExploredCandidatePointModificationHashes.add(
4466
- neighbor.candidateHash
4467
- );
4468
5161
  }
4469
5162
  return neighbors;
4470
5163
  }
@@ -6143,13 +6836,13 @@ function minimumDistanceBetweenSegments(A1, A2, B1, B2) {
6143
6836
  if (segmentsIntersect(A1, A2, B1, B2)) {
6144
6837
  return 0;
6145
6838
  }
6146
- const distA1 = pointToSegmentDistance3(A1, B1, B2);
6147
- const distA2 = pointToSegmentDistance3(A2, B1, B2);
6148
- const distB1 = pointToSegmentDistance3(B1, A1, A2);
6149
- const distB2 = pointToSegmentDistance3(B2, A1, A2);
6839
+ const distA1 = pointToSegmentDistance4(A1, B1, B2);
6840
+ const distA2 = pointToSegmentDistance4(A2, B1, B2);
6841
+ const distB1 = pointToSegmentDistance4(B1, A1, A2);
6842
+ const distB2 = pointToSegmentDistance4(B2, A1, A2);
6150
6843
  return Math.min(distA1, distA2, distB1, distB2);
6151
6844
  }
6152
- function pointToSegmentDistance3(P, Q1, Q2) {
6845
+ function pointToSegmentDistance4(P, Q1, Q2) {
6153
6846
  const v = { x: Q2.x - Q1.x, y: Q2.y - Q1.y };
6154
6847
  const w = { x: P.x - Q1.x, y: P.y - Q1.y };
6155
6848
  const c1 = dotProduct(w, v);