@tscircuit/capacity-autorouter 0.0.27 → 0.0.28

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
@@ -1434,6 +1434,7 @@ var CapacityMeshNodeSolver2_NodeUnderObstacle = class extends CapacityMeshNodeSo
1434
1434
  this.srj = srj;
1435
1435
  this.opts = opts;
1436
1436
  }
1437
+ VIA_DIAMETER = 0.6;
1437
1438
  isNodeCompletelyOutsideBounds(node) {
1438
1439
  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;
1439
1440
  }
@@ -1538,10 +1539,10 @@ var CapacityMeshNodeSolver2_NodeUnderObstacle = class extends CapacityMeshNodeSo
1538
1539
  const unfinishedNewNodes = [];
1539
1540
  for (const childNode of childNodes) {
1540
1541
  const shouldBeXYSubdivided = this.shouldNodeBeXYSubdivided(childNode);
1541
- const shouldBeZSubdivided = childNode.availableZ.length > 1 && !shouldBeXYSubdivided && childNode._containsObstacle;
1542
+ const shouldBeZSubdivided = childNode.availableZ.length > 1 && !shouldBeXYSubdivided && (childNode._containsObstacle || childNode.width < this.VIA_DIAMETER);
1542
1543
  if (shouldBeXYSubdivided) {
1543
1544
  unfinishedNewNodes.push(childNode);
1544
- } else if (!shouldBeXYSubdivided && !childNode._containsObstacle) {
1545
+ } else if (!shouldBeXYSubdivided && !childNode._containsObstacle && !shouldBeZSubdivided) {
1545
1546
  finishedNewNodes.push(childNode);
1546
1547
  } else if (!shouldBeXYSubdivided && childNode._containsTarget) {
1547
1548
  if (shouldBeZSubdivided) {
@@ -3127,6 +3128,9 @@ var getConnectivityMapFromSimpleRouteJson = (srj) => {
3127
3128
  }
3128
3129
  }
3129
3130
  }
3131
+ for (const obstacle of srj.obstacles) {
3132
+ connMap.addConnections([obstacle.connectedTo]);
3133
+ }
3130
3134
  return connMap;
3131
3135
  };
3132
3136
 
@@ -3332,13 +3336,13 @@ function buildMinimumSpanningTree(points) {
3332
3336
  if (point.x === neighbor.x && point.y === neighbor.y) {
3333
3337
  continue;
3334
3338
  }
3335
- const distance3 = Math.sqrt(
3339
+ const distance4 = Math.sqrt(
3336
3340
  (point.x - neighbor.x) ** 2 + (point.y - neighbor.y) ** 2
3337
3341
  );
3338
3342
  edges.push({
3339
3343
  from: point,
3340
3344
  to: neighbor,
3341
- weight: distance3
3345
+ weight: distance4
3342
3346
  });
3343
3347
  }
3344
3348
  }
@@ -5105,11 +5109,11 @@ var CapacityPathingSolver = class extends BaseSolver {
5105
5109
  let closestNode = this.nodes[0];
5106
5110
  let minDistance = Number.MAX_VALUE;
5107
5111
  for (const node of nodesWithTargets) {
5108
- const distance3 = Math.sqrt(
5112
+ const distance4 = Math.sqrt(
5109
5113
  (node.center.x - point.x) ** 2 + (node.center.y - point.y) ** 2
5110
5114
  );
5111
- if (distance3 < minDistance) {
5112
- minDistance = distance3;
5115
+ if (distance4 < minDistance) {
5116
+ minDistance = distance4;
5113
5117
  closestNode = node;
5114
5118
  }
5115
5119
  }
@@ -5827,6 +5831,694 @@ ${node.width}x${node.height}
5827
5831
  }
5828
5832
  };
5829
5833
 
5834
+ // lib/solvers/SimplifiedPathSolver/SingleSimplifiedPathSolver.ts
5835
+ var SingleSimplifiedPathSolver = class extends BaseSolver {
5836
+ newRoute;
5837
+ newVias;
5838
+ headIndex = 0;
5839
+ tailIndex = 0;
5840
+ inputRoute;
5841
+ otherHdRoutes;
5842
+ obstacles;
5843
+ connMap;
5844
+ colorMap;
5845
+ constructor(params) {
5846
+ super();
5847
+ this.inputRoute = params.inputRoute;
5848
+ this.otherHdRoutes = params.otherHdRoutes;
5849
+ this.obstacles = params.obstacles;
5850
+ this.connMap = params.connMap;
5851
+ this.colorMap = params.colorMap;
5852
+ this.newRoute = [];
5853
+ this.newVias = [];
5854
+ }
5855
+ get simplifiedRoute() {
5856
+ return {
5857
+ connectionName: this.inputRoute.connectionName,
5858
+ traceThickness: this.inputRoute.traceThickness,
5859
+ viaDiameter: this.inputRoute.viaDiameter,
5860
+ route: this.newRoute,
5861
+ vias: this.newVias
5862
+ };
5863
+ }
5864
+ isValidPath(pointsInRoute) {
5865
+ throw new Error("Not implemented");
5866
+ }
5867
+ _step() {
5868
+ throw new Error("Not implemented");
5869
+ }
5870
+ getVisualsForNewRouteAndObstacles() {
5871
+ const graphics = {
5872
+ lines: [],
5873
+ points: [],
5874
+ circles: [],
5875
+ rects: [],
5876
+ coordinateSystem: "cartesian",
5877
+ title: "Simplified Path Solver"
5878
+ };
5879
+ for (let i = 0; i < this.inputRoute.route.length - 1; i++) {
5880
+ graphics.lines.push({
5881
+ points: [
5882
+ { x: this.inputRoute.route[i].x, y: this.inputRoute.route[i].y },
5883
+ {
5884
+ x: this.inputRoute.route[i + 1].x,
5885
+ y: this.inputRoute.route[i + 1].y
5886
+ }
5887
+ ],
5888
+ strokeColor: "rgba(255, 0, 0, 0.8)",
5889
+ strokeDash: this.inputRoute.route[i].z === 1 ? "5, 5" : void 0,
5890
+ layer: this.inputRoute.route[i].z.toString()
5891
+ });
5892
+ }
5893
+ for (let i = 0; i < this.newRoute.length; i++) {
5894
+ if (i < this.newRoute.length - 1) {
5895
+ graphics.lines.push({
5896
+ points: [
5897
+ { x: this.newRoute[i].x, y: this.newRoute[i].y },
5898
+ { x: this.newRoute[i + 1].x, y: this.newRoute[i + 1].y }
5899
+ ],
5900
+ strokeWidth: 0.15,
5901
+ strokeColor: "rgba(0, 255, 0, 0.8)",
5902
+ strokeDash: this.newRoute[i].z === 1 ? [0.4, 0.4] : void 0,
5903
+ layer: this.newRoute[i].z.toString()
5904
+ });
5905
+ }
5906
+ graphics.points.push({
5907
+ x: this.newRoute[i].x,
5908
+ y: this.newRoute[i].y,
5909
+ color: "rgba(0, 255, 0, 0.8)",
5910
+ label: `z: ${this.newRoute[i].z}`,
5911
+ layer: this.newRoute[i].z.toString()
5912
+ });
5913
+ }
5914
+ for (const via of this.newVias) {
5915
+ graphics.circles.push({
5916
+ center: via,
5917
+ radius: this.inputRoute.viaDiameter / 2,
5918
+ fill: "rgba(0, 0, 255, 0.5)"
5919
+ });
5920
+ }
5921
+ for (const obstacle of this.obstacles) {
5922
+ graphics.rects.push({
5923
+ center: obstacle.center,
5924
+ width: obstacle.width,
5925
+ height: obstacle.height,
5926
+ fill: obstacle.layers?.includes("top") ? "rgba(255, 0, 0, 0.3)" : obstacle.layers?.includes("bottom") ? "rgba(0, 0, 255, 0.3)" : "rgba(128, 128, 128, 0.3)"
5927
+ });
5928
+ }
5929
+ for (const route of this.otherHdRoutes) {
5930
+ for (let i = 0; i < route.route.length - 1; i++) {
5931
+ graphics.lines.push({
5932
+ points: [
5933
+ { x: route.route[i].x, y: route.route[i].y },
5934
+ { x: route.route[i + 1].x, y: route.route[i + 1].y }
5935
+ ],
5936
+ strokeWidth: 0.15,
5937
+ strokeColor: route.route[i].z === 0 ? "rgba(255, 0, 255, 0.5)" : route.route[i].z === 1 ? "rgba(128, 0, 128, 0.5)" : "rgba(0, 0, 255, 0.5)",
5938
+ // bottom layer (blue)
5939
+ layer: route.route[i].z.toString()
5940
+ });
5941
+ }
5942
+ }
5943
+ return graphics;
5944
+ }
5945
+ };
5946
+
5947
+ // lib/utils/calculate45DegreePaths.ts
5948
+ var calculate45DegreePaths = (pointA, pointB) => {
5949
+ const result = [];
5950
+ const dx = Math.abs(pointB.x - pointA.x);
5951
+ const dy = Math.abs(pointB.y - pointA.y);
5952
+ const signX = pointB.x > pointA.x ? 1 : -1;
5953
+ const signY = pointB.y > pointA.y ? 1 : -1;
5954
+ const midPoint1 = {
5955
+ x: pointB.x - signX * Math.abs(pointB.y - pointA.y),
5956
+ y: pointA.y
5957
+ };
5958
+ if ((midPoint1.x - pointA.x) * signX >= 0 && (midPoint1.x - pointB.x) * signX <= 0) {
5959
+ result.push([pointA, midPoint1, pointB]);
5960
+ }
5961
+ const midPoint2 = {
5962
+ x: pointA.x,
5963
+ y: pointB.y - signY * Math.abs(pointB.x - pointA.x)
5964
+ };
5965
+ if ((midPoint2.y - pointA.y) * signY >= 0 && (midPoint2.y - pointB.y) * signY <= 0) {
5966
+ result.push([pointA, midPoint2, pointB]);
5967
+ }
5968
+ const minDist = Math.min(dx, dy);
5969
+ const midPoint3 = {
5970
+ x: pointA.x + signX * minDist,
5971
+ y: pointA.y + signY * minDist
5972
+ };
5973
+ if ((midPoint3.x - pointA.x) * signX >= 0 && (midPoint3.x - pointB.x) * signX <= 0 && (midPoint3.y - pointA.y) * signY >= 0 && (midPoint3.y - pointB.y) * signY <= 0) {
5974
+ result.push([pointA, midPoint3, pointB]);
5975
+ }
5976
+ return result;
5977
+ };
5978
+
5979
+ // lib/utils/minimumDistanceBetweenSegments.ts
5980
+ function minimumDistanceBetweenSegments(A1, A2, B1, B2) {
5981
+ if (segmentsIntersect(A1, A2, B1, B2)) {
5982
+ return 0;
5983
+ }
5984
+ const distA1 = pointToSegmentDistance3(A1, B1, B2);
5985
+ const distA2 = pointToSegmentDistance3(A2, B1, B2);
5986
+ const distB1 = pointToSegmentDistance3(B1, A1, A2);
5987
+ const distB2 = pointToSegmentDistance3(B2, A1, A2);
5988
+ return Math.min(distA1, distA2, distB1, distB2);
5989
+ }
5990
+ function pointToSegmentDistance3(P, Q1, Q2) {
5991
+ const v = { x: Q2.x - Q1.x, y: Q2.y - Q1.y };
5992
+ const w = { x: P.x - Q1.x, y: P.y - Q1.y };
5993
+ const c1 = dotProduct(w, v);
5994
+ if (c1 <= 0) {
5995
+ return distance3(P, Q1);
5996
+ }
5997
+ const c2 = dotProduct(v, v);
5998
+ if (c2 <= c1) {
5999
+ return distance3(P, Q2);
6000
+ }
6001
+ const b = c1 / c2;
6002
+ const Pb = {
6003
+ x: Q1.x + b * v.x,
6004
+ y: Q1.y + b * v.y
6005
+ };
6006
+ return distance3(P, Pb);
6007
+ }
6008
+ function dotProduct(v1, v2) {
6009
+ return v1.x * v2.x + v1.y * v2.y;
6010
+ }
6011
+ function distance3(p1, p2) {
6012
+ const dx = p2.x - p1.x;
6013
+ const dy = p2.y - p1.y;
6014
+ return Math.sqrt(dx * dx + dy * dy);
6015
+ }
6016
+ function orientation3(p, q, r) {
6017
+ const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
6018
+ if (val === 0) return 0;
6019
+ return val > 0 ? 1 : 2;
6020
+ }
6021
+ function onSegment3(p, q, r) {
6022
+ return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y);
6023
+ }
6024
+ function segmentsIntersect(A1, A2, B1, B2) {
6025
+ const o1 = orientation3(A1, A2, B1);
6026
+ const o2 = orientation3(A1, A2, B2);
6027
+ const o3 = orientation3(B1, B2, A1);
6028
+ const o4 = orientation3(B1, B2, A2);
6029
+ if (o1 !== o2 && o3 !== o4) return true;
6030
+ if (o1 === 0 && onSegment3(A1, B1, A2)) return true;
6031
+ if (o2 === 0 && onSegment3(A1, B2, A2)) return true;
6032
+ if (o3 === 0 && onSegment3(B1, A1, B2)) return true;
6033
+ if (o4 === 0 && onSegment3(B1, A2, B2)) return true;
6034
+ return false;
6035
+ }
6036
+
6037
+ // lib/solvers/SimplifiedPathSolver/SingleSimplifiedPathSolver5_Deg45.ts
6038
+ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
6039
+ pathSegments = [];
6040
+ totalPathLength = 0;
6041
+ headDistanceAlongPath = 0;
6042
+ tailDistanceAlongPath = 0;
6043
+ stepSize = 0.25;
6044
+ // Default step size, can be adjusted
6045
+ lastValidPath = null;
6046
+ // Store the current valid path
6047
+ lastValidPathHeadDistance = 0;
6048
+ filteredObstacles = [];
6049
+ OBSTACLE_MARGIN = 0.15;
6050
+ TAIL_JUMP_RATIO = 0.8;
6051
+ constructor(params) {
6052
+ super(params);
6053
+ if (this.inputRoute.route.length <= 1) {
6054
+ this.newRoute = [...this.inputRoute.route];
6055
+ this.solved = true;
6056
+ return;
6057
+ }
6058
+ this.filteredObstacles = this.obstacles.filter(
6059
+ (obstacle) => !obstacle.connectedTo.some(
6060
+ (id) => this.connMap.areIdsConnected(this.inputRoute.connectionName, id)
6061
+ )
6062
+ );
6063
+ this.computePathSegments();
6064
+ }
6065
+ // Compute the path segments and their distances
6066
+ computePathSegments() {
6067
+ let cumulativeDistance = 0;
6068
+ for (let i = 0; i < this.inputRoute.route.length - 1; i++) {
6069
+ const start = this.inputRoute.route[i];
6070
+ const end = this.inputRoute.route[i + 1];
6071
+ const length = Math.sqrt((end.x - start.x) ** 2 + (end.y - start.y) ** 2) + i / 1e4;
6072
+ this.pathSegments.push({
6073
+ start,
6074
+ end,
6075
+ length,
6076
+ startDistance: cumulativeDistance,
6077
+ endDistance: cumulativeDistance + length
6078
+ });
6079
+ cumulativeDistance += length;
6080
+ }
6081
+ this.totalPathLength = cumulativeDistance;
6082
+ }
6083
+ // Helper to check if two points are the same
6084
+ arePointsEqual(p1, p2) {
6085
+ return p1.x === p2.x && p1.y === p2.y && p1.z === p2.z;
6086
+ }
6087
+ // Get point at a specific distance along the path
6088
+ getPointAtDistance(distance4) {
6089
+ distance4 = Math.max(0, Math.min(distance4, this.totalPathLength));
6090
+ const segment = this.pathSegments.find(
6091
+ (seg) => distance4 >= seg.startDistance && distance4 <= seg.endDistance
6092
+ );
6093
+ if (!segment) {
6094
+ return this.inputRoute.route[this.inputRoute.route.length - 1];
6095
+ }
6096
+ const factor = (distance4 - segment.startDistance) / segment.length;
6097
+ return {
6098
+ x: segment.start.x + factor * (segment.end.x - segment.start.x),
6099
+ y: segment.start.y + factor * (segment.end.y - segment.start.y),
6100
+ z: segment.start.z
6101
+ // Z doesn't interpolate - use the segment's start z value
6102
+ };
6103
+ }
6104
+ // Find nearest index in the original route for a given distance
6105
+ getNearestIndexForDistance(distance4) {
6106
+ if (distance4 <= 0) return 0;
6107
+ if (distance4 >= this.totalPathLength)
6108
+ return this.inputRoute.route.length - 1;
6109
+ const segmentIndex = this.pathSegments.findIndex(
6110
+ (seg) => distance4 >= seg.startDistance && distance4 <= seg.endDistance
6111
+ );
6112
+ if (segmentIndex === -1) return 0;
6113
+ const segment = this.pathSegments[segmentIndex];
6114
+ const midDistance = (segment.startDistance + segment.endDistance) / 2;
6115
+ return distance4 > midDistance ? segmentIndex + 1 : segmentIndex;
6116
+ }
6117
+ // Check if a path segment is valid
6118
+ isValidPathSegment(start, end) {
6119
+ for (const obstacle of this.filteredObstacles) {
6120
+ if (!obstacle.zLayers?.includes(start.z)) {
6121
+ continue;
6122
+ }
6123
+ const obstacleLeft = obstacle.center.x - obstacle.width / 2 - this.OBSTACLE_MARGIN;
6124
+ const obstacleRight = obstacle.center.x + obstacle.width / 2 + this.OBSTACLE_MARGIN;
6125
+ const obstacleTop = obstacle.center.y - obstacle.height / 2 - this.OBSTACLE_MARGIN;
6126
+ const obstacleBottom = obstacle.center.y + obstacle.height / 2 + this.OBSTACLE_MARGIN;
6127
+ if (doSegmentsIntersect(
6128
+ { x: start.x, y: start.y },
6129
+ { x: end.x, y: end.y },
6130
+ { x: obstacleLeft, y: obstacleTop },
6131
+ { x: obstacleRight, y: obstacleTop }
6132
+ ) || doSegmentsIntersect(
6133
+ { x: start.x, y: start.y },
6134
+ { x: end.x, y: end.y },
6135
+ { x: obstacleRight, y: obstacleTop },
6136
+ { x: obstacleRight, y: obstacleBottom }
6137
+ ) || doSegmentsIntersect(
6138
+ { x: start.x, y: start.y },
6139
+ { x: end.x, y: end.y },
6140
+ { x: obstacleRight, y: obstacleBottom },
6141
+ { x: obstacleLeft, y: obstacleBottom }
6142
+ ) || doSegmentsIntersect(
6143
+ { x: start.x, y: start.y },
6144
+ { x: end.x, y: end.y },
6145
+ { x: obstacleLeft, y: obstacleBottom },
6146
+ { x: obstacleLeft, y: obstacleTop }
6147
+ )) {
6148
+ return false;
6149
+ }
6150
+ }
6151
+ for (const route of this.otherHdRoutes) {
6152
+ if (this.connMap.areIdsConnected(
6153
+ this.inputRoute.connectionName,
6154
+ route.connectionName
6155
+ )) {
6156
+ continue;
6157
+ }
6158
+ for (let j = 0; j < route.route.length - 1; j++) {
6159
+ const routeStart = route.route[j];
6160
+ const routeEnd = route.route[j + 1];
6161
+ if (routeStart.z === start.z && routeEnd.z === start.z) {
6162
+ if (minimumDistanceBetweenSegments(
6163
+ { x: start.x, y: start.y },
6164
+ { x: end.x, y: end.y },
6165
+ { x: routeStart.x, y: routeStart.y },
6166
+ { x: routeEnd.x, y: routeEnd.y }
6167
+ ) < this.OBSTACLE_MARGIN) {
6168
+ return false;
6169
+ }
6170
+ }
6171
+ }
6172
+ for (const via of route.vias) {
6173
+ if (pointToSegmentDistance(via, start, end) < this.OBSTACLE_MARGIN + route.viaDiameter / 2) {
6174
+ return false;
6175
+ }
6176
+ }
6177
+ }
6178
+ return true;
6179
+ }
6180
+ // Check if a path with multiple points is valid
6181
+ isValidPath(pointsInRoute) {
6182
+ if (pointsInRoute.length < 2) return true;
6183
+ for (let i = 0; i < pointsInRoute.length - 1; i++) {
6184
+ if (pointsInRoute[i].z !== pointsInRoute[i + 1].z) {
6185
+ return false;
6186
+ }
6187
+ }
6188
+ for (let i = 0; i < pointsInRoute.length - 1; i++) {
6189
+ if (!this.isValidPathSegment(pointsInRoute[i], pointsInRoute[i + 1])) {
6190
+ return false;
6191
+ }
6192
+ }
6193
+ return true;
6194
+ }
6195
+ // Find a valid 45-degree path between two points
6196
+ find45DegreePath(start, end) {
6197
+ if (this.arePointsEqual(start, end)) {
6198
+ return [start];
6199
+ }
6200
+ if (start.z !== end.z) {
6201
+ return null;
6202
+ }
6203
+ const possiblePaths = calculate45DegreePaths(
6204
+ { x: start.x, y: start.y },
6205
+ { x: end.x, y: end.y }
6206
+ );
6207
+ for (const path of possiblePaths) {
6208
+ const fullPath = path.map((p) => ({ x: p.x, y: p.y, z: start.z }));
6209
+ if (this.isValidPath(fullPath)) {
6210
+ return fullPath;
6211
+ }
6212
+ }
6213
+ return null;
6214
+ }
6215
+ // Add a path to the result, skipping the first point if it's already added
6216
+ addPathToResult(path) {
6217
+ if (path.length === 0) return;
6218
+ for (let i = 0; i < path.length; i++) {
6219
+ if (i === 0 && this.newRoute.length > 0 && this.arePointsEqual(this.newRoute[this.newRoute.length - 1], path[i])) {
6220
+ continue;
6221
+ }
6222
+ this.newRoute.push(path[i]);
6223
+ }
6224
+ }
6225
+ _step() {
6226
+ const tailHasReachedEnd = this.tailDistanceAlongPath >= this.totalPathLength;
6227
+ const headHasReachedEnd = this.headDistanceAlongPath >= this.totalPathLength;
6228
+ if (tailHasReachedEnd) {
6229
+ const lastPoint = this.inputRoute.route[this.inputRoute.route.length - 1];
6230
+ if (this.newRoute.length === 0 || !this.arePointsEqual(this.newRoute[this.newRoute.length - 1], lastPoint)) {
6231
+ this.newRoute.push(lastPoint);
6232
+ }
6233
+ this.solved = true;
6234
+ return;
6235
+ }
6236
+ if (headHasReachedEnd) {
6237
+ const tailPoint2 = this.getPointAtDistance(this.tailDistanceAlongPath);
6238
+ const endPoint = this.inputRoute.route[this.inputRoute.route.length - 1];
6239
+ const path452 = this.find45DegreePath(tailPoint2, endPoint);
6240
+ if (path452) {
6241
+ this.addPathToResult(path452);
6242
+ this.solved = true;
6243
+ return;
6244
+ } else {
6245
+ if (this.lastValidPath) {
6246
+ this.addPathToResult(this.lastValidPath);
6247
+ this.lastValidPath = null;
6248
+ this.tailDistanceAlongPath = this.lastValidPathHeadDistance;
6249
+ } else {
6250
+ this.newRoute.push(endPoint);
6251
+ this.solved = true;
6252
+ }
6253
+ }
6254
+ }
6255
+ this.headDistanceAlongPath = Math.min(
6256
+ this.headDistanceAlongPath + this.stepSize,
6257
+ this.totalPathLength
6258
+ );
6259
+ const tailPoint = this.getPointAtDistance(this.tailDistanceAlongPath);
6260
+ const headPoint = this.getPointAtDistance(this.headDistanceAlongPath);
6261
+ const tailIndex = this.getNearestIndexForDistance(
6262
+ this.tailDistanceAlongPath
6263
+ );
6264
+ const headIndex = this.getNearestIndexForDistance(
6265
+ this.headDistanceAlongPath
6266
+ );
6267
+ let layerChangeBtwHeadAndTail = false;
6268
+ let layerChangeAtDistance = -1;
6269
+ for (let i = tailIndex; i < headIndex; i++) {
6270
+ if (i + 1 < this.inputRoute.route.length && this.inputRoute.route[i].z !== this.inputRoute.route[i + 1].z) {
6271
+ layerChangeBtwHeadAndTail = true;
6272
+ const changeSegmentIndex = i;
6273
+ layerChangeAtDistance = this.pathSegments[changeSegmentIndex].startDistance;
6274
+ break;
6275
+ }
6276
+ }
6277
+ if (layerChangeBtwHeadAndTail && layerChangeAtDistance > 0) {
6278
+ if (this.lastValidPath) {
6279
+ this.addPathToResult(this.lastValidPath);
6280
+ this.lastValidPath = null;
6281
+ }
6282
+ const pointBeforeChange = this.getPointAtDistance(layerChangeAtDistance);
6283
+ const pointAfterChange = this.inputRoute.route[this.getNearestIndexForDistance(layerChangeAtDistance) + 1];
6284
+ this.newVias.push({
6285
+ x: pointAfterChange.x,
6286
+ y: pointAfterChange.y
6287
+ });
6288
+ this.newRoute.push(pointAfterChange);
6289
+ const nextTailIndex = this.getNearestIndexForDistance(layerChangeAtDistance) + 1;
6290
+ if (this.pathSegments[nextTailIndex]) {
6291
+ this.tailDistanceAlongPath = this.pathSegments[nextTailIndex].startDistance;
6292
+ this.headDistanceAlongPath = this.tailDistanceAlongPath;
6293
+ } else {
6294
+ console.error("Creating via at end, this is probably not right");
6295
+ this.solved = true;
6296
+ return;
6297
+ }
6298
+ return;
6299
+ }
6300
+ const path45 = this.find45DegreePath(tailPoint, headPoint);
6301
+ if (!path45 && !this.lastValidPath) {
6302
+ this.tailDistanceAlongPath += this.stepSize;
6303
+ this.headDistanceAlongPath += this.stepSize;
6304
+ return;
6305
+ }
6306
+ if (path45) {
6307
+ this.lastValidPath = path45;
6308
+ this.lastValidPathHeadDistance = this.headDistanceAlongPath;
6309
+ return;
6310
+ }
6311
+ if (this.lastValidPath) {
6312
+ this.addPathToResult(this.lastValidPath);
6313
+ this.lastValidPath = null;
6314
+ this.tailDistanceAlongPath = this.lastValidPathHeadDistance;
6315
+ }
6316
+ this.headDistanceAlongPath = Math.min(
6317
+ this.headDistanceAlongPath + this.stepSize,
6318
+ this.totalPathLength
6319
+ );
6320
+ }
6321
+ visualize() {
6322
+ const graphics = this.getVisualsForNewRouteAndObstacles();
6323
+ const tailPoint = this.getPointAtDistance(this.tailDistanceAlongPath);
6324
+ const headPoint = this.getPointAtDistance(this.headDistanceAlongPath);
6325
+ graphics.points.push({
6326
+ x: tailPoint.x,
6327
+ y: tailPoint.y,
6328
+ color: "yellow",
6329
+ label: ["Tail", `z: ${tailPoint.z}`].join("\n")
6330
+ });
6331
+ graphics.points.push({
6332
+ x: headPoint.x,
6333
+ y: headPoint.y,
6334
+ color: "orange",
6335
+ label: ["Head", `z: ${headPoint.z}`].join("\n")
6336
+ });
6337
+ let distance4 = 0;
6338
+ while (distance4 < this.totalPathLength) {
6339
+ const point = this.getPointAtDistance(distance4);
6340
+ graphics.circles.push({
6341
+ center: {
6342
+ x: point.x,
6343
+ y: point.y
6344
+ },
6345
+ radius: 0.05,
6346
+ fill: "rgba(100, 100, 100, 0.5)"
6347
+ });
6348
+ distance4 += this.totalPathLength / 20;
6349
+ }
6350
+ if (this.lastValidPath && this.lastValidPath.length > 1) {
6351
+ for (let i = 0; i < this.lastValidPath.length - 1; i++) {
6352
+ graphics.lines.push({
6353
+ points: [
6354
+ { x: this.lastValidPath[i].x, y: this.lastValidPath[i].y },
6355
+ {
6356
+ x: this.lastValidPath[i + 1].x,
6357
+ y: this.lastValidPath[i + 1].y
6358
+ }
6359
+ ],
6360
+ strokeColor: "rgba(0, 255, 255, 0.9)",
6361
+ // Bright cyan
6362
+ strokeDash: "3, 3"
6363
+ // Dashed line to indicate it's a prospective path
6364
+ });
6365
+ }
6366
+ }
6367
+ return graphics;
6368
+ }
6369
+ };
6370
+
6371
+ // lib/solvers/SimplifiedPathSolver/MultiSimplifiedPathSolver.ts
6372
+ var MultiSimplifiedPathSolver = class extends BaseSolver {
6373
+ simplifiedHdRoutes;
6374
+ currentUnsimplifiedHdRouteIndex = 0;
6375
+ activeSubSolver = null;
6376
+ unsimplifiedHdRoutes;
6377
+ obstacles;
6378
+ connMap;
6379
+ colorMap;
6380
+ constructor(params) {
6381
+ super();
6382
+ this.MAX_ITERATIONS = 1e8;
6383
+ this.unsimplifiedHdRoutes = params.unsimplifiedHdRoutes;
6384
+ this.obstacles = params.obstacles;
6385
+ this.connMap = params.connMap || new ConnectivityMap({});
6386
+ this.colorMap = params.colorMap || {};
6387
+ this.simplifiedHdRoutes = [];
6388
+ }
6389
+ _step() {
6390
+ const hdRoute = this.unsimplifiedHdRoutes[this.currentUnsimplifiedHdRouteIndex];
6391
+ if (!this.activeSubSolver) {
6392
+ if (!hdRoute) {
6393
+ this.solved = true;
6394
+ return;
6395
+ }
6396
+ this.activeSubSolver = new SingleSimplifiedPathSolver5({
6397
+ inputRoute: hdRoute,
6398
+ otherHdRoutes: this.unsimplifiedHdRoutes.slice(this.currentUnsimplifiedHdRouteIndex + 1).concat(this.simplifiedHdRoutes),
6399
+ obstacles: this.obstacles,
6400
+ connMap: this.connMap,
6401
+ colorMap: this.colorMap
6402
+ });
6403
+ this.currentUnsimplifiedHdRouteIndex++;
6404
+ return;
6405
+ }
6406
+ this.activeSubSolver.step();
6407
+ if (this.activeSubSolver.solved) {
6408
+ this.simplifiedHdRoutes.push(this.activeSubSolver.simplifiedRoute);
6409
+ this.activeSubSolver = null;
6410
+ }
6411
+ }
6412
+ visualize() {
6413
+ if (this.activeSubSolver) {
6414
+ return this.activeSubSolver.visualize();
6415
+ }
6416
+ const graphics = {
6417
+ lines: [],
6418
+ points: [],
6419
+ circles: [],
6420
+ rects: [],
6421
+ coordinateSystem: "cartesian",
6422
+ title: "Multi Simplified Path Solver"
6423
+ };
6424
+ for (const route of this.unsimplifiedHdRoutes) {
6425
+ if (this.simplifiedHdRoutes.some(
6426
+ (r) => r.connectionName === route.connectionName
6427
+ )) {
6428
+ continue;
6429
+ }
6430
+ for (let i = 0; i < route.route.length - 1; i++) {
6431
+ graphics.lines.push({
6432
+ points: [
6433
+ { x: route.route[i].x, y: route.route[i].y },
6434
+ { x: route.route[i + 1].x, y: route.route[i + 1].y }
6435
+ ],
6436
+ strokeColor: route.route[i].z === 1 ? "rgba(0, 0, 255, 0.4)" : "rgba(255, 0, 0, 0.4)",
6437
+ strokeWidth: 0.15,
6438
+ strokeDash: route.route[i].z === 1 ? [0.5, 0.5] : void 0
6439
+ });
6440
+ }
6441
+ for (const via of route.vias || []) {
6442
+ graphics.circles.push({
6443
+ center: via,
6444
+ radius: route.viaDiameter / 2 || 0.3,
6445
+ // Default radius if viaDiameter not specified
6446
+ fill: "rgba(0, 0, 255, 0.4)"
6447
+ });
6448
+ }
6449
+ }
6450
+ for (const route of this.simplifiedHdRoutes) {
6451
+ const routeColor = this.colorMap?.[route.connectionName] || "rgba(128, 128, 128, 0.8)";
6452
+ for (let i = 0; i < route.route.length - 1; i++) {
6453
+ graphics.lines.push({
6454
+ points: [
6455
+ { x: route.route[i].x, y: route.route[i].y },
6456
+ { x: route.route[i + 1].x, y: route.route[i + 1].y }
6457
+ ],
6458
+ strokeWidth: 0.15,
6459
+ strokeColor: routeColor,
6460
+ strokeDash: route.route[i].z === 1 ? [0.5, 0.5] : void 0,
6461
+ step: 1
6462
+ });
6463
+ }
6464
+ for (const via of route.vias || []) {
6465
+ graphics.circles.push({
6466
+ center: via,
6467
+ radius: route.viaDiameter / 2,
6468
+ fill: "rgba(0, 0, 255, 0.5)",
6469
+ step: 1
6470
+ });
6471
+ }
6472
+ }
6473
+ for (const route of this.unsimplifiedHdRoutes) {
6474
+ for (let i = 0; i < route.route.length - 1; i++) {
6475
+ graphics.lines.push({
6476
+ points: [
6477
+ { x: route.route[i].x, y: route.route[i].y },
6478
+ { x: route.route[i + 1].x, y: route.route[i + 1].y }
6479
+ ],
6480
+ strokeWidth: 0.15,
6481
+ strokeColor: "rgba(255, 0, 0, 0.2)",
6482
+ strokeDash: [0.5, 0.5],
6483
+ step: 0,
6484
+ layer: route.route[i].z.toString()
6485
+ });
6486
+ }
6487
+ for (const point of route.vias) {
6488
+ graphics.circles.push({
6489
+ center: { x: point.x, y: point.y },
6490
+ radius: route.viaDiameter / 2,
6491
+ fill: "rgba(255, 0, 0, 0.2)",
6492
+ step: 0
6493
+ });
6494
+ }
6495
+ }
6496
+ for (const obstacle of this.obstacles) {
6497
+ graphics.rects.push({
6498
+ center: obstacle.center,
6499
+ width: obstacle.width,
6500
+ height: obstacle.height,
6501
+ fill: obstacle.layers?.includes("top") ? "rgba(255, 0, 0, 0.3)" : obstacle.layers?.includes("bottom") ? "rgba(0, 0, 255, 0.3)" : "rgba(128, 128, 128, 0.3)"
6502
+ });
6503
+ }
6504
+ if (this.currentUnsimplifiedHdRouteIndex < this.unsimplifiedHdRoutes.length) {
6505
+ const currentRoute = this.unsimplifiedHdRoutes[this.currentUnsimplifiedHdRouteIndex];
6506
+ if (currentRoute.route.length > 0) {
6507
+ graphics.circles.push({
6508
+ center: {
6509
+ x: currentRoute.route[0].x,
6510
+ y: currentRoute.route[0].y
6511
+ },
6512
+ radius: 0.2,
6513
+ fill: "yellow",
6514
+ label: "Current"
6515
+ });
6516
+ }
6517
+ }
6518
+ return graphics;
6519
+ }
6520
+ };
6521
+
5830
6522
  // lib/solvers/AutoroutingPipelineSolver.ts
5831
6523
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
5832
6524
  return {
@@ -5872,6 +6564,7 @@ var CapacityMeshSolver = class extends BaseSolver {
5872
6564
  highDensityStitchSolver;
5873
6565
  singleLayerNodeMerger;
5874
6566
  strawSolver;
6567
+ multiSimplifiedPathSolver;
5875
6568
  startTimeOfPhase;
5876
6569
  endTimeOfPhase;
5877
6570
  timeSpentOnPhase;
@@ -6017,6 +6710,18 @@ var CapacityMeshSolver = class extends BaseSolver {
6017
6710
  layerCount: cms.srj.layerCount
6018
6711
  }
6019
6712
  ]
6713
+ ),
6714
+ definePipelineStep(
6715
+ "multiSimplifiedPathSolver",
6716
+ MultiSimplifiedPathSolver,
6717
+ (cms) => [
6718
+ {
6719
+ unsimplifiedHdRoutes: cms.highDensityStitchSolver.mergedHdRoutes,
6720
+ obstacles: cms.srj.obstacles,
6721
+ connMap: cms.connMap,
6722
+ colorMap: cms.colorMap
6723
+ }
6724
+ ]
6020
6725
  )
6021
6726
  ];
6022
6727
  currentPipelineStepIndex = 0;
@@ -6065,12 +6770,21 @@ var CapacityMeshSolver = class extends BaseSolver {
6065
6770
  const segmentOptimizationViz = this.unravelMultiSectionSolver?.visualize() ?? this.segmentToPointOptimizer?.visualize();
6066
6771
  const highDensityViz = this.highDensityRouteSolver?.visualize();
6067
6772
  const highDensityStitchViz = this.highDensityStitchSolver?.visualize();
6773
+ const simplifiedPathSolverViz = this.multiSimplifiedPathSolver?.visualize();
6068
6774
  const problemViz = {
6069
- points: [...this.srj.connections.flatMap((c) => c.pointsToConnect)],
6775
+ points: [
6776
+ ...this.srj.connections.flatMap(
6777
+ (c) => c.pointsToConnect.map((p) => ({
6778
+ ...p,
6779
+ label: `${c.name} ${p.pcb_port_id ?? ""}`
6780
+ }))
6781
+ )
6782
+ ],
6070
6783
  rects: [
6071
6784
  ...(this.srj.obstacles ?? []).map((o) => ({
6072
6785
  ...o,
6073
- fill: o.layers?.includes("top") ? "rgba(255,0,0,0.25)" : o.layers?.includes("bottom") ? "rgba(0,0,255,0.25)" : "rgba(255,0,0,0.25)"
6786
+ fill: o.layers?.includes("top") ? "rgba(255,0,0,0.25)" : o.layers?.includes("bottom") ? "rgba(0,0,255,0.25)" : "rgba(255,0,0,0.25)",
6787
+ label: o.layers?.join(", ")
6074
6788
  }))
6075
6789
  ],
6076
6790
  lines: [
@@ -6108,6 +6822,7 @@ var CapacityMeshSolver = class extends BaseSolver {
6108
6822
  segmentOptimizationViz,
6109
6823
  highDensityViz ? combineVisualizations(problemViz, highDensityViz) : null,
6110
6824
  highDensityStitchViz,
6825
+ simplifiedPathSolverViz,
6111
6826
  this.solved ? combineVisualizations(
6112
6827
  problemViz,
6113
6828
  convertSrjToGraphicsObject(this.getOutputSimpleRouteJson())
@@ -6124,6 +6839,9 @@ var CapacityMeshSolver = class extends BaseSolver {
6124
6839
  const match = mstConnectionName.match(/^(.+?)_mst\d+$/);
6125
6840
  return match ? match[1] : mstConnectionName;
6126
6841
  }
6842
+ _getOutputHdRoutes() {
6843
+ return this.multiSimplifiedPathSolver?.simplifiedHdRoutes ?? this.highDensityStitchSolver.mergedHdRoutes;
6844
+ }
6127
6845
  /**
6128
6846
  * Returns the SimpleRouteJson with routes converted to SimplifiedPcbTraces
6129
6847
  */
@@ -6132,11 +6850,12 @@ var CapacityMeshSolver = class extends BaseSolver {
6132
6850
  throw new Error("Cannot get output before solving is complete");
6133
6851
  }
6134
6852
  const traces = [];
6853
+ const allHdRoutes = this._getOutputHdRoutes();
6135
6854
  for (const connection of this.netToPointPairsSolver?.newConnections ?? []) {
6136
6855
  const netConnection = this.srj.connections.find(
6137
6856
  (c) => c.name === connection.netConnectionName
6138
6857
  );
6139
- const hdRoutes = this.highDensityStitchSolver.mergedHdRoutes.filter(
6858
+ const hdRoutes = allHdRoutes.filter(
6140
6859
  (r) => r.connectionName === connection.name
6141
6860
  );
6142
6861
  for (let i = 0; i < hdRoutes.length; i++) {