@tscircuit/capacity-autorouter 0.0.32 → 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.d.ts +83 -1
- package/dist/index.js +702 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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 =
|
|
6147
|
-
const distA2 =
|
|
6148
|
-
const distB1 =
|
|
6149
|
-
const distB2 =
|
|
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
|
|
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);
|