@tscircuit/capacity-autorouter 0.0.49 → 0.0.51

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
@@ -965,7 +965,7 @@ var calculateOptimalCapacityDepth = (initialWidth, targetMinCapacity = 0.5, maxD
965
965
  };
966
966
 
967
967
  // lib/data-structures/ObstacleTree.ts
968
- var ObstacleTree = class {
968
+ var ObstacleSpatialHashIndex = class {
969
969
  constructor(obstacles) {
970
970
  this.obstacles = obstacles;
971
971
  this.buckets = /* @__PURE__ */ new Map();
@@ -1109,7 +1109,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1109
1109
  ];
1110
1110
  this.finishedNodes = [];
1111
1111
  this.nodeToXYOverlappingObstaclesMap = /* @__PURE__ */ new Map();
1112
- this.obstacleTree = new ObstacleTree(this.srj.obstacles);
1112
+ this.obstacleTree = new ObstacleSpatialHashIndex(this.srj.obstacles);
1113
1113
  this.targets = this.computeTargets();
1114
1114
  this.targetTree = new TargetTree(this.targets);
1115
1115
  }
@@ -2205,6 +2205,7 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
2205
2205
  debug_nodesTooCloseToObstacle;
2206
2206
  debug_nodePathToParentIntersectsObstacle;
2207
2207
  debugEnabled = true;
2208
+ initialNodeGridOffset;
2208
2209
  constructor(opts) {
2209
2210
  super();
2210
2211
  this.bounds = opts.bounds;
@@ -2250,11 +2251,18 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
2250
2251
  if (this.futureConnections && this.futureConnections.length === 0 && this.obstacleRoutes.length === 0) {
2251
2252
  this.handleSimpleCases();
2252
2253
  }
2254
+ const initialNodePosition = {
2255
+ x: Math.round(opts.A.x / (this.cellStep / 2)) * (this.cellStep / 2),
2256
+ y: Math.round(opts.A.y / (this.cellStep / 2)) * (this.cellStep / 2)
2257
+ };
2258
+ this.initialNodeGridOffset = {
2259
+ x: initialNodePosition.x - Math.round(opts.A.x / this.cellStep) * this.cellStep,
2260
+ y: initialNodePosition.y - Math.round(opts.A.y / this.cellStep) * this.cellStep
2261
+ };
2253
2262
  this.candidates = new SingleRouteCandidatePriorityQueue([
2254
2263
  {
2255
2264
  ...opts.A,
2256
- x: Math.floor(opts.A.x / this.cellStep) * this.cellStep,
2257
- y: Math.floor(opts.A.y / this.cellStep) * this.cellStep,
2265
+ ...initialNodePosition,
2258
2266
  z: opts.A.z ?? 0,
2259
2267
  g: 0,
2260
2268
  h: 0,
@@ -2317,7 +2325,7 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
2317
2325
  }
2318
2326
  }
2319
2327
  for (const via of route.vias) {
2320
- if (distance(node, via) < this.viaDiameter / 2 + margin) {
2328
+ if (distance(node, via) < this.viaDiameter / 2 + this.traceThickness / 2 + margin) {
2321
2329
  return true;
2322
2330
  }
2323
2331
  }
@@ -2484,7 +2492,13 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
2484
2492
  goalDist,
2485
2493
  currentNode.z === this.B.z
2486
2494
  );
2487
- if (goalDist <= this.cellStep * Math.SQRT2 && currentNode.z === this.B.z) {
2495
+ if (goalDist <= this.cellStep * Math.SQRT2 && currentNode.z === this.B.z && // Make sure the last segment doesn't intersect an obstacle
2496
+ !this.doesPathToParentIntersectObstacle({
2497
+ ...currentNode,
2498
+ parent: currentNode,
2499
+ x: this.B.x,
2500
+ y: this.B.y
2501
+ })) {
2488
2502
  this.solved = true;
2489
2503
  this.setSolvedPath(currentNode);
2490
2504
  }
@@ -2539,8 +2553,8 @@ z: ${this.B.z}`,
2539
2553
  if (this.debug_nodePathToParentIntersectsObstacle.has(nodeKey)) continue;
2540
2554
  graphics.rects.push({
2541
2555
  center: {
2542
- x: x + z * this.cellStep / 20,
2543
- y: y + z * this.cellStep / 20
2556
+ x: x + this.initialNodeGridOffset.x + z * this.cellStep / 20,
2557
+ y: y + this.initialNodeGridOffset.y + z * this.cellStep / 20
2544
2558
  },
2545
2559
  fill: z === 0 ? `rgba(255,0,255,${0.3 - i / this.debug_exploredNodesOrdered.length * 0.2})` : `rgba(0,0,255,${0.3 - i / this.debug_exploredNodesOrdered.length * 0.2})`,
2546
2560
  width: this.cellStep * 0.9,
@@ -5784,11 +5798,13 @@ var UnravelSectionSolver = class extends BaseSolver {
5784
5798
  colorMap;
5785
5799
  tunedNodeCapacityMap;
5786
5800
  MAX_CANDIDATES = 500;
5801
+ iterationsSinceImprovement = 0;
5787
5802
  selectedCandidateIndex = null;
5788
5803
  queuedOrExploredCandidatePointModificationHashes = /* @__PURE__ */ new Set();
5789
5804
  constructor(params) {
5790
5805
  super();
5791
5806
  this.MUTABLE_HOPS = params.MUTABLE_HOPS ?? this.MUTABLE_HOPS;
5807
+ this.MAX_ITERATIONS = 5e4;
5792
5808
  this.nodeMap = params.nodeMap;
5793
5809
  this.dedupedSegments = params.dedupedSegments;
5794
5810
  if (params.dedupedSegmentMap) {
@@ -6183,6 +6199,7 @@ var UnravelSectionSolver = class extends BaseSolver {
6183
6199
  }
6184
6200
  _step() {
6185
6201
  const candidate = this.candidates.shift();
6202
+ this.iterationsSinceImprovement++;
6186
6203
  if (!candidate) {
6187
6204
  this.solved = true;
6188
6205
  return;
@@ -6190,6 +6207,7 @@ var UnravelSectionSolver = class extends BaseSolver {
6190
6207
  this.lastProcessedCandidate = candidate;
6191
6208
  if (candidate.f < (this.bestCandidate?.f ?? Infinity)) {
6192
6209
  this.bestCandidate = candidate;
6210
+ this.iterationsSinceImprovement = 0;
6193
6211
  }
6194
6212
  this.getNeighbors(candidate).forEach((neighbor) => {
6195
6213
  const isPartialHashExplored = this.queuedOrExploredCandidatePointModificationHashes.has(
@@ -6591,8 +6609,7 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
6591
6609
  }
6592
6610
  this.activeSolver.step();
6593
6611
  const { bestCandidate, originalCandidate, lastProcessedCandidate } = this.activeSolver;
6594
- const giveUpFactor = 1 + 4 * (1 - Math.min(1, this.activeSolver.iterations / 40));
6595
- const shouldEarlyStop = lastProcessedCandidate && lastProcessedCandidate.g > bestCandidate.g * giveUpFactor;
6612
+ const shouldEarlyStop = this.activeSolver.iterationsSinceImprovement > 200;
6596
6613
  if (this.activeSolver.solved || shouldEarlyStop) {
6597
6614
  const foundBetterSolution = bestCandidate && bestCandidate.g < originalCandidate.g;
6598
6615
  if (foundBetterSolution) {
@@ -8806,6 +8823,537 @@ var CapacityMeshEdgeSolver2_NodeTreeOptimization = class extends CapacityMeshEdg
8806
8823
  }
8807
8824
  };
8808
8825
 
8826
+ // lib/data-structures/HighDensityRouteSpatialIndex.ts
8827
+ var getSegmentBounds2 = (segment) => {
8828
+ return {
8829
+ minX: Math.min(segment[0].x, segment[1].x),
8830
+ maxX: Math.max(segment[0].x, segment[1].x),
8831
+ minY: Math.min(segment[0].y, segment[1].y),
8832
+ maxY: Math.max(segment[0].y, segment[1].y)
8833
+ };
8834
+ };
8835
+ function computeDistSq(p1, p2) {
8836
+ const dx = p1.x - p2.x;
8837
+ const dy = p1.y - p2.y;
8838
+ return dx * dx + dy * dy;
8839
+ }
8840
+ function pointToSegmentDistanceSq(p, a, b) {
8841
+ const l2 = computeDistSq(a, b);
8842
+ if (l2 === 0) return computeDistSq(p, a);
8843
+ let t = ((p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y)) / l2;
8844
+ t = Math.max(0, Math.min(1, t));
8845
+ const projection = {
8846
+ x: a.x + t * (b.x - a.x),
8847
+ y: a.y + t * (b.y - a.y)
8848
+ };
8849
+ return computeDistSq(p, projection);
8850
+ }
8851
+ function segmentToSegmentDistanceSq(a, b, c, d) {
8852
+ if (doSegmentsIntersect(a, b, c, d)) {
8853
+ return 0;
8854
+ }
8855
+ const pA = { x: a.x, y: a.y };
8856
+ const pB = { x: b.x, y: b.y };
8857
+ const pC = { x: c.x, y: c.y };
8858
+ const pD = { x: d.x, y: d.y };
8859
+ return Math.min(
8860
+ pointToSegmentDistanceSq(pA, pC, pD),
8861
+ pointToSegmentDistanceSq(pB, pC, pD),
8862
+ pointToSegmentDistanceSq(pC, pA, pB),
8863
+ pointToSegmentDistanceSq(pD, pA, pB)
8864
+ );
8865
+ }
8866
+ var HighDensityRouteSpatialIndex = class {
8867
+ segmentBuckets;
8868
+ viaBuckets;
8869
+ // New: Store vias
8870
+ routes;
8871
+ CELL_SIZE;
8872
+ constructor(routes, cellSize = 1) {
8873
+ this.segmentBuckets = /* @__PURE__ */ new Map();
8874
+ this.viaBuckets = /* @__PURE__ */ new Map();
8875
+ this.routes = /* @__PURE__ */ new Map();
8876
+ this.CELL_SIZE = cellSize;
8877
+ const epsilon = 1e-9;
8878
+ for (const route of routes) {
8879
+ if (!route || !route.connectionName) {
8880
+ console.warn("Skipping route with missing data:", route);
8881
+ continue;
8882
+ }
8883
+ if (this.routes.has(route.connectionName)) {
8884
+ console.warn(
8885
+ `Skipping duplicate route connectionName: ${route.connectionName}`
8886
+ );
8887
+ continue;
8888
+ }
8889
+ this.routes.set(route.connectionName, route);
8890
+ if (route.route && route.route.length >= 2) {
8891
+ for (let i = 0; i < route.route.length - 1; i++) {
8892
+ const p1 = route.route[i];
8893
+ const p2 = route.route[i + 1];
8894
+ if (p1.x === p2.x && p1.y === p2.y) continue;
8895
+ const segment = [p1, p2];
8896
+ const bounds = getSegmentBounds2(segment);
8897
+ const segmentInfo = {
8898
+ segmentId: `${route.connectionName}-seg-${i}`,
8899
+ segment,
8900
+ parentRoute: route
8901
+ };
8902
+ const minIndexX = Math.floor(bounds.minX / this.CELL_SIZE);
8903
+ const maxIndexX = Math.floor((bounds.maxX + epsilon) / this.CELL_SIZE);
8904
+ const minIndexY = Math.floor(bounds.minY / this.CELL_SIZE);
8905
+ const maxIndexY = Math.floor((bounds.maxY + epsilon) / this.CELL_SIZE);
8906
+ for (let ix = minIndexX; ix <= maxIndexX; ix++) {
8907
+ for (let iy = minIndexY; iy <= maxIndexY; iy++) {
8908
+ const bucketKey = `${ix}x${iy}`;
8909
+ let bucketList = this.segmentBuckets.get(bucketKey);
8910
+ if (!bucketList) {
8911
+ bucketList = [];
8912
+ this.segmentBuckets.set(bucketKey, bucketList);
8913
+ }
8914
+ bucketList.push(segmentInfo);
8915
+ }
8916
+ }
8917
+ }
8918
+ }
8919
+ if (route.vias && route.vias.length > 0) {
8920
+ for (let i = 0; i < route.vias.length; i++) {
8921
+ const via = route.vias[i];
8922
+ if (via === void 0 || via === null) continue;
8923
+ const storedVia = {
8924
+ viaId: `${route.connectionName}-via-${i}`,
8925
+ x: via.x,
8926
+ y: via.y,
8927
+ parentRoute: route
8928
+ };
8929
+ const ix = Math.floor(via.x / this.CELL_SIZE);
8930
+ const iy = Math.floor(via.y / this.CELL_SIZE);
8931
+ const bucketKey = `${ix}x${iy}`;
8932
+ let bucketList = this.viaBuckets.get(bucketKey);
8933
+ if (!bucketList) {
8934
+ bucketList = [];
8935
+ this.viaBuckets.set(bucketKey, bucketList);
8936
+ }
8937
+ bucketList.push(storedVia);
8938
+ }
8939
+ }
8940
+ }
8941
+ }
8942
+ /**
8943
+ * Finds routes that potentially conflict with a given line segment within a margin.
8944
+ * Checks both segments and vias.
8945
+ * @param segmentStart Start point of the query segment.
8946
+ * @param segmentEnd End point of the query segment.
8947
+ * @param margin The minimum required clearance distance from the query segment's centerline.
8948
+ * @returns An array of conflicting routes and their minimum distance to the segment.
8949
+ */
8950
+ getConflictingRoutesForSegment(segmentStart, segmentEnd, margin) {
8951
+ const querySegment = [segmentStart, segmentEnd];
8952
+ const bounds = getSegmentBounds2(querySegment);
8953
+ const searchMinX = bounds.minX - margin;
8954
+ const searchMinY = bounds.minY - margin;
8955
+ const searchMaxX = bounds.maxX + margin;
8956
+ const searchMaxY = bounds.maxY + margin;
8957
+ const epsilon = 1e-9;
8958
+ const minIndexX = Math.floor(searchMinX / this.CELL_SIZE);
8959
+ const maxIndexX = Math.floor((searchMaxX + epsilon) / this.CELL_SIZE);
8960
+ const minIndexY = Math.floor(searchMinY / this.CELL_SIZE);
8961
+ const maxIndexY = Math.floor((searchMaxY + epsilon) / this.CELL_SIZE);
8962
+ const conflictingRouteData = /* @__PURE__ */ new Map();
8963
+ const checkedSegments = /* @__PURE__ */ new Set();
8964
+ const checkedVias = /* @__PURE__ */ new Set();
8965
+ const queryP1 = { x: segmentStart.x, y: segmentStart.y };
8966
+ const queryP2 = { x: segmentEnd.x, y: segmentEnd.y };
8967
+ for (let ix = minIndexX; ix <= maxIndexX; ix++) {
8968
+ for (let iy = minIndexY; iy <= maxIndexY; iy++) {
8969
+ const bucketKey = `${ix}x${iy}`;
8970
+ const segmentBucketList = this.segmentBuckets.get(bucketKey);
8971
+ if (segmentBucketList) {
8972
+ for (const segmentInfo of segmentBucketList) {
8973
+ if (checkedSegments.has(segmentInfo.segmentId)) continue;
8974
+ checkedSegments.add(segmentInfo.segmentId);
8975
+ const route = segmentInfo.parentRoute;
8976
+ const [p1, p2] = segmentInfo.segment;
8977
+ const requiredSeparation = margin + route.traceThickness / 2;
8978
+ const requiredSeparationSq = requiredSeparation * requiredSeparation;
8979
+ const distSq = segmentToSegmentDistanceSq(
8980
+ segmentStart,
8981
+ segmentEnd,
8982
+ p1,
8983
+ p2
8984
+ );
8985
+ if (distSq < requiredSeparationSq) {
8986
+ const routeName = route.connectionName;
8987
+ const existing = conflictingRouteData.get(routeName);
8988
+ if (!existing || distSq < existing.minDistSq) {
8989
+ conflictingRouteData.set(routeName, {
8990
+ route,
8991
+ minDistSq: distSq
8992
+ });
8993
+ }
8994
+ }
8995
+ }
8996
+ }
8997
+ const viaBucketList = this.viaBuckets.get(bucketKey);
8998
+ if (viaBucketList) {
8999
+ for (const viaInfo of viaBucketList) {
9000
+ if (checkedVias.has(viaInfo.viaId)) continue;
9001
+ checkedVias.add(viaInfo.viaId);
9002
+ const route = viaInfo.parentRoute;
9003
+ const viaPoint = { x: viaInfo.x, y: viaInfo.y };
9004
+ const requiredSeparation = margin + route.viaDiameter / 2;
9005
+ const requiredSeparationSq = requiredSeparation * requiredSeparation;
9006
+ const distSq = pointToSegmentDistanceSq(viaPoint, queryP1, queryP2);
9007
+ if (distSq < requiredSeparationSq) {
9008
+ const routeName = route.connectionName;
9009
+ const existing = conflictingRouteData.get(routeName);
9010
+ if (!existing || distSq < existing.minDistSq) {
9011
+ conflictingRouteData.set(routeName, {
9012
+ route,
9013
+ minDistSq: distSq
9014
+ });
9015
+ }
9016
+ }
9017
+ }
9018
+ }
9019
+ }
9020
+ }
9021
+ const results = [];
9022
+ for (const data of conflictingRouteData.values()) {
9023
+ results.push({
9024
+ conflictingRoute: data.route,
9025
+ distance: Math.sqrt(data.minDistSq)
9026
+ });
9027
+ }
9028
+ return results;
9029
+ }
9030
+ /**
9031
+ * Finds routes that pass near a given point within a margin.
9032
+ * Checks both segments and vias.
9033
+ * @param point The query point {x, y}. Z is ignored.
9034
+ * @param margin The minimum required clearance distance from the query point.
9035
+ * @returns An array of conflicting routes and their minimum distance to the point.
9036
+ */
9037
+ getConflictingRoutesNearPoint(point, margin) {
9038
+ const searchMinX = point.x - margin;
9039
+ const searchMinY = point.y - margin;
9040
+ const searchMaxX = point.x + margin;
9041
+ const searchMaxY = point.y + margin;
9042
+ const epsilon = 1e-9;
9043
+ const minIndexX = Math.floor(searchMinX / this.CELL_SIZE);
9044
+ const maxIndexX = Math.floor((searchMaxX + epsilon) / this.CELL_SIZE);
9045
+ const minIndexY = Math.floor(searchMinY / this.CELL_SIZE);
9046
+ const maxIndexY = Math.floor((searchMaxY + epsilon) / this.CELL_SIZE);
9047
+ const conflictingRouteData = /* @__PURE__ */ new Map();
9048
+ const checkedSegments = /* @__PURE__ */ new Set();
9049
+ const checkedVias = /* @__PURE__ */ new Set();
9050
+ for (let ix = minIndexX; ix <= maxIndexX; ix++) {
9051
+ for (let iy = minIndexY; iy <= maxIndexY; iy++) {
9052
+ const bucketKey = `${ix}x${iy}`;
9053
+ const segmentBucketList = this.segmentBuckets.get(bucketKey);
9054
+ if (segmentBucketList) {
9055
+ for (const segmentInfo of segmentBucketList) {
9056
+ if (checkedSegments.has(segmentInfo.segmentId)) continue;
9057
+ checkedSegments.add(segmentInfo.segmentId);
9058
+ const route = segmentInfo.parentRoute;
9059
+ const p1 = {
9060
+ x: segmentInfo.segment[0].x,
9061
+ y: segmentInfo.segment[0].y
9062
+ };
9063
+ const p2 = {
9064
+ x: segmentInfo.segment[1].x,
9065
+ y: segmentInfo.segment[1].y
9066
+ };
9067
+ const requiredSeparation = margin + route.traceThickness / 2;
9068
+ const requiredSeparationSq = requiredSeparation * requiredSeparation;
9069
+ const distSq = pointToSegmentDistanceSq(point, p1, p2);
9070
+ if (distSq < requiredSeparationSq) {
9071
+ const routeName = route.connectionName;
9072
+ const existing = conflictingRouteData.get(routeName);
9073
+ if (!existing || distSq < existing.minDistSq) {
9074
+ conflictingRouteData.set(routeName, {
9075
+ route,
9076
+ minDistSq: distSq
9077
+ });
9078
+ }
9079
+ }
9080
+ }
9081
+ }
9082
+ const viaBucketList = this.viaBuckets.get(bucketKey);
9083
+ if (viaBucketList) {
9084
+ for (const viaInfo of viaBucketList) {
9085
+ if (checkedVias.has(viaInfo.viaId)) continue;
9086
+ checkedVias.add(viaInfo.viaId);
9087
+ const route = viaInfo.parentRoute;
9088
+ const viaPoint = { x: viaInfo.x, y: viaInfo.y };
9089
+ const requiredSeparation = margin + route.viaDiameter / 2;
9090
+ const requiredSeparationSq = requiredSeparation * requiredSeparation;
9091
+ const distSq = computeDistSq(point, viaPoint);
9092
+ if (distSq < requiredSeparationSq) {
9093
+ const routeName = route.connectionName;
9094
+ const existing = conflictingRouteData.get(routeName);
9095
+ if (!existing || distSq < existing.minDistSq) {
9096
+ conflictingRouteData.set(routeName, {
9097
+ route,
9098
+ minDistSq: distSq
9099
+ });
9100
+ }
9101
+ }
9102
+ }
9103
+ }
9104
+ }
9105
+ }
9106
+ const results = [];
9107
+ for (const data of conflictingRouteData.values()) {
9108
+ results.push({
9109
+ conflictingRoute: data.route,
9110
+ distance: Math.sqrt(data.minDistSq)
9111
+ });
9112
+ }
9113
+ return results;
9114
+ }
9115
+ };
9116
+
9117
+ // lib/solvers/UselessViaRemovalSolver/SingleRouteUselessViaRemovalSolver.ts
9118
+ var SingleRouteUselessViaRemovalSolver = class extends BaseSolver {
9119
+ obstacleSHI;
9120
+ hdRouteSHI;
9121
+ unsimplifiedRoute;
9122
+ routeSections;
9123
+ currentSectionIndex;
9124
+ TRACE_THICKNESS = 0.15;
9125
+ OBSTACLE_MARGIN = 0.1;
9126
+ constructor(params) {
9127
+ super();
9128
+ this.currentSectionIndex = 1;
9129
+ this.obstacleSHI = params.obstacleSHI;
9130
+ this.hdRouteSHI = params.hdRouteSHI;
9131
+ this.unsimplifiedRoute = params.unsimplifiedRoute;
9132
+ this.routeSections = this.breakRouteIntoSections(this.unsimplifiedRoute);
9133
+ }
9134
+ breakRouteIntoSections(route) {
9135
+ const routeSections = [];
9136
+ const routePoints = route.route;
9137
+ if (routePoints.length === 0) return [];
9138
+ let currentSection = {
9139
+ startIndex: 0,
9140
+ endIndex: -1,
9141
+ z: routePoints[0].z,
9142
+ points: [routePoints[0]]
9143
+ };
9144
+ for (let i = 1; i < routePoints.length; i++) {
9145
+ if (routePoints[i].z === currentSection.z) {
9146
+ currentSection.points.push(routePoints[i]);
9147
+ } else {
9148
+ currentSection.endIndex = i - 1;
9149
+ routeSections.push(currentSection);
9150
+ currentSection = {
9151
+ startIndex: i,
9152
+ endIndex: -1,
9153
+ z: routePoints[i].z,
9154
+ points: [routePoints[i]]
9155
+ };
9156
+ }
9157
+ }
9158
+ currentSection.endIndex = routePoints.length - 1;
9159
+ routeSections.push(currentSection);
9160
+ return routeSections;
9161
+ }
9162
+ _step() {
9163
+ if (this.currentSectionIndex >= this.routeSections.length - 1) {
9164
+ this.solved = true;
9165
+ return;
9166
+ }
9167
+ const prevSection = this.routeSections[this.currentSectionIndex - 1];
9168
+ const currentSection = this.routeSections[this.currentSectionIndex];
9169
+ const nextSection = this.routeSections[this.currentSectionIndex + 1];
9170
+ if (prevSection.z !== nextSection.z) {
9171
+ this.currentSectionIndex++;
9172
+ return;
9173
+ }
9174
+ const targetZ = prevSection.z;
9175
+ if (this.canSectionMoveToLayer({ currentSection, targetZ })) {
9176
+ currentSection.z = targetZ;
9177
+ currentSection.points = currentSection.points.map((p) => ({
9178
+ ...p,
9179
+ z: targetZ
9180
+ }));
9181
+ this.currentSectionIndex += 2;
9182
+ return;
9183
+ }
9184
+ this.currentSectionIndex++;
9185
+ return;
9186
+ }
9187
+ canSectionMoveToLayer({
9188
+ currentSection,
9189
+ targetZ
9190
+ }) {
9191
+ for (let i = 0; i < currentSection.points.length - 1; i++) {
9192
+ const A = { ...currentSection.points[i], z: targetZ };
9193
+ const B = { ...currentSection.points[i + 1], z: targetZ };
9194
+ const conflictingRoutes = this.hdRouteSHI.getConflictingRoutesForSegment(
9195
+ A,
9196
+ B,
9197
+ this.TRACE_THICKNESS
9198
+ );
9199
+ for (const { conflictingRoute, distance: distance6 } of conflictingRoutes) {
9200
+ if (conflictingRoute.connectionName === this.unsimplifiedRoute.connectionName)
9201
+ continue;
9202
+ if (distance6 < this.TRACE_THICKNESS + conflictingRoute.traceThickness) {
9203
+ return false;
9204
+ }
9205
+ }
9206
+ const segmentBox = {
9207
+ centerX: (A.x + B.x) / 2,
9208
+ centerY: (A.y + B.y) / 2,
9209
+ width: Math.abs(A.x - B.x),
9210
+ height: Math.abs(A.y - B.y)
9211
+ };
9212
+ const obstacles = this.obstacleSHI.getNodesInArea(
9213
+ segmentBox.centerX,
9214
+ segmentBox.centerY,
9215
+ segmentBox.width,
9216
+ segmentBox.height
9217
+ );
9218
+ for (const obstacle of obstacles) {
9219
+ const distToObstacle = segmentToBoxMinDistance(A, B, obstacle);
9220
+ if (distToObstacle < this.TRACE_THICKNESS + this.OBSTACLE_MARGIN) {
9221
+ return false;
9222
+ }
9223
+ }
9224
+ }
9225
+ return true;
9226
+ }
9227
+ getOptimizedHdRoute() {
9228
+ const route = this.routeSections.flatMap((section) => section.points);
9229
+ const vias = [];
9230
+ for (let i = 0; i < route.length - 1; i++) {
9231
+ if (route[i].z !== route[i + 1].z) {
9232
+ vias.push({
9233
+ x: route[i].x,
9234
+ y: route[i].y
9235
+ });
9236
+ }
9237
+ }
9238
+ return {
9239
+ connectionName: this.unsimplifiedRoute.connectionName,
9240
+ route,
9241
+ traceThickness: this.unsimplifiedRoute.traceThickness,
9242
+ vias,
9243
+ viaDiameter: this.unsimplifiedRoute.viaDiameter
9244
+ };
9245
+ }
9246
+ visualize() {
9247
+ const graphics = {
9248
+ circles: [],
9249
+ lines: [],
9250
+ points: [],
9251
+ rects: [],
9252
+ coordinateSystem: "cartesian",
9253
+ title: "Single Route Useless Via Removal Solver"
9254
+ };
9255
+ for (let i = 0; i < this.routeSections.length; i++) {
9256
+ const section = this.routeSections[i];
9257
+ graphics.lines.push({
9258
+ points: section.points,
9259
+ strokeWidth: this.TRACE_THICKNESS,
9260
+ strokeColor: i === this.currentSectionIndex ? "orange" : section.z === 0 ? "red" : "blue"
9261
+ });
9262
+ }
9263
+ return graphics;
9264
+ }
9265
+ };
9266
+
9267
+ // lib/solvers/UselessViaRemovalSolver/UselessViaRemovalSolver.ts
9268
+ var UselessViaRemovalSolver = class extends BaseSolver {
9269
+ constructor(input) {
9270
+ super();
9271
+ this.input = input;
9272
+ this.unsimplifiedHdRoutes = input.unsimplifiedHdRoutes;
9273
+ this.optimizedHdRoutes = [];
9274
+ this.unprocessedRoutes = [...input.unsimplifiedHdRoutes];
9275
+ this.obstacleSHI = new ObstacleSpatialHashIndex(input.obstacles);
9276
+ this.hdRouteSHI = new HighDensityRouteSpatialIndex(
9277
+ this.unsimplifiedHdRoutes
9278
+ );
9279
+ }
9280
+ unsimplifiedHdRoutes;
9281
+ optimizedHdRoutes;
9282
+ unprocessedRoutes;
9283
+ activeSubSolver = null;
9284
+ obstacleSHI = null;
9285
+ hdRouteSHI = null;
9286
+ _step() {
9287
+ if (this.activeSubSolver) {
9288
+ this.activeSubSolver.step();
9289
+ if (this.activeSubSolver.solved) {
9290
+ this.optimizedHdRoutes.push(this.activeSubSolver.getOptimizedHdRoute());
9291
+ this.activeSubSolver = null;
9292
+ } else if (this.activeSubSolver.failed || this.activeSubSolver.error) {
9293
+ this.error = this.activeSubSolver.error;
9294
+ this.failed = true;
9295
+ }
9296
+ return;
9297
+ }
9298
+ const unprocessedRoute = this.unprocessedRoutes.shift();
9299
+ if (!unprocessedRoute) {
9300
+ this.solved = true;
9301
+ return;
9302
+ }
9303
+ this.activeSubSolver = new SingleRouteUselessViaRemovalSolver({
9304
+ hdRouteSHI: this.hdRouteSHI,
9305
+ obstacleSHI: this.obstacleSHI,
9306
+ unsimplifiedRoute: unprocessedRoute
9307
+ });
9308
+ }
9309
+ getOptimizedHdRoutes() {
9310
+ return this.optimizedHdRoutes;
9311
+ }
9312
+ visualize() {
9313
+ const visualization = {
9314
+ lines: [],
9315
+ points: [],
9316
+ rects: [],
9317
+ circles: [],
9318
+ coordinateSystem: "cartesian",
9319
+ title: "Useless Via Removal Solver"
9320
+ };
9321
+ for (const route of this.optimizedHdRoutes) {
9322
+ if (route.route.length === 0) continue;
9323
+ const color = this.input.colorMap[route.connectionName] || "#888888";
9324
+ for (let i = 0; i < route.route.length - 1; i++) {
9325
+ const current = route.route[i];
9326
+ const next = route.route[i + 1];
9327
+ if (current.z === next.z) {
9328
+ visualization.lines.push({
9329
+ points: [
9330
+ { x: current.x, y: current.y },
9331
+ { x: next.x, y: next.y }
9332
+ ],
9333
+ strokeColor: current.z === 0 ? "red" : "blue",
9334
+ strokeWidth: route.traceThickness,
9335
+ label: `${route.connectionName} (z=${current.z})`
9336
+ });
9337
+ }
9338
+ }
9339
+ for (const via of route.vias) {
9340
+ visualization.circles.push({
9341
+ center: { x: via.x, y: via.y },
9342
+ radius: route.viaDiameter / 2,
9343
+ fill: "rgba(255, 0, 255, 0.5)",
9344
+ label: `${route.connectionName} via`
9345
+ });
9346
+ }
9347
+ }
9348
+ if (this.activeSubSolver) {
9349
+ visualization.lines.push(
9350
+ ...this.activeSubSolver.visualize().lines ?? []
9351
+ );
9352
+ }
9353
+ return visualization;
9354
+ }
9355
+ };
9356
+
8809
9357
  // lib/solvers/AutoroutingPipelineSolver.ts
8810
9358
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
8811
9359
  return {
@@ -8851,6 +9399,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
8851
9399
  highDensityStitchSolver;
8852
9400
  singleLayerNodeMerger;
8853
9401
  strawSolver;
9402
+ uselessViaRemovalSolver;
8854
9403
  multiSimplifiedPathSolver;
8855
9404
  startTimeOfPhase;
8856
9405
  endTimeOfPhase;
@@ -9006,12 +9555,24 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9006
9555
  }
9007
9556
  ]
9008
9557
  ),
9558
+ definePipelineStep(
9559
+ "uselessViaRemovalSolver",
9560
+ UselessViaRemovalSolver,
9561
+ (cms) => [
9562
+ {
9563
+ unsimplifiedHdRoutes: cms.highDensityStitchSolver.mergedHdRoutes,
9564
+ obstacles: cms.srj.obstacles,
9565
+ colorMap: cms.colorMap,
9566
+ layerCount: cms.srj.layerCount
9567
+ }
9568
+ ]
9569
+ ),
9009
9570
  definePipelineStep(
9010
9571
  "multiSimplifiedPathSolver",
9011
9572
  MultiSimplifiedPathSolver,
9012
9573
  (cms) => [
9013
9574
  {
9014
- unsimplifiedHdRoutes: cms.highDensityStitchSolver.mergedHdRoutes,
9575
+ unsimplifiedHdRoutes: cms.uselessViaRemovalSolver?.getOptimizedHdRoutes() || cms.highDensityStitchSolver.mergedHdRoutes,
9015
9576
  obstacles: cms.srj.obstacles,
9016
9577
  connMap: cms.connMap,
9017
9578
  colorMap: cms.colorMap
@@ -9070,6 +9631,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9070
9631
  const segmentOptimizationViz = this.unravelMultiSectionSolver?.visualize() ?? this.segmentToPointOptimizer?.visualize();
9071
9632
  const highDensityViz = this.highDensityRouteSolver?.visualize();
9072
9633
  const highDensityStitchViz = this.highDensityStitchSolver?.visualize();
9634
+ const uselessViaRemovalViz = this.uselessViaRemovalSolver?.visualize();
9073
9635
  const simplifiedPathSolverViz = this.multiSimplifiedPathSolver?.visualize();
9074
9636
  const problemViz = {
9075
9637
  points: [
@@ -9122,6 +9684,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9122
9684
  segmentOptimizationViz,
9123
9685
  highDensityViz ? combineVisualizations(problemViz, highDensityViz) : null,
9124
9686
  highDensityStitchViz,
9687
+ uselessViaRemovalViz,
9125
9688
  simplifiedPathSolverViz,
9126
9689
  this.solved ? combineVisualizations(
9127
9690
  problemViz,
@@ -9184,7 +9747,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9184
9747
  return match ? match[1] : mstConnectionName;
9185
9748
  }
9186
9749
  _getOutputHdRoutes() {
9187
- return this.multiSimplifiedPathSolver?.simplifiedHdRoutes ?? this.highDensityStitchSolver.mergedHdRoutes;
9750
+ return this.multiSimplifiedPathSolver?.simplifiedHdRoutes ?? this.uselessViaRemovalSolver?.getOptimizedHdRoutes() ?? this.highDensityStitchSolver.mergedHdRoutes;
9188
9751
  }
9189
9752
  /**
9190
9753
  * Returns the SimpleRouteJson with routes converted to SimplifiedPcbTraces