@tscircuit/capacity-autorouter 0.0.48 → 0.0.50

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
  }
@@ -8358,26 +8358,59 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
8358
8358
  return;
8359
8359
  }
8360
8360
  if (layerChangeBtwHeadAndTail && layerChangeAtDistance > 0) {
8361
- const pointBeforeChange = this.getPointAtDistance(layerChangeAtDistance);
8361
+ const indexAfterLayerChange = this.getNearestIndexForDistance(layerChangeAtDistance) + 1;
8362
+ const pointAfterChange = this.inputRoute.route[indexAfterLayerChange];
8363
+ const viaLocation = { x: pointAfterChange.x, y: pointAfterChange.y };
8362
8364
  if (this.lastValidPath) {
8363
8365
  this.addPathToResult(this.lastValidPath);
8364
8366
  this.lastValidPath = null;
8365
8367
  }
8366
- const indexAfterLayerChange = this.getNearestIndexForDistance(layerChangeAtDistance) + 1;
8367
- const pointAfterChange = this.inputRoute.route[indexAfterLayerChange];
8368
- this.newVias.push({
8369
- x: pointAfterChange.x,
8370
- y: pointAfterChange.y
8368
+ const lastPointInNewRoute = this.newRoute[this.newRoute.length - 1];
8369
+ if (lastPointInNewRoute.x !== viaLocation.x || lastPointInNewRoute.y !== viaLocation.y) {
8370
+ this.newRoute.push({
8371
+ x: viaLocation.x,
8372
+ y: viaLocation.y,
8373
+ z: lastPointInNewRoute.z
8374
+ // Use the Z of the layer we are leaving
8375
+ });
8376
+ }
8377
+ this.newVias.push(viaLocation);
8378
+ this.newRoute.push({
8379
+ x: viaLocation.x,
8380
+ y: viaLocation.y,
8381
+ z: pointAfterChange.z
8382
+ // Use the Z of the layer we are entering
8371
8383
  });
8372
- this.newRoute.push(pointAfterChange);
8373
8384
  this.currentStepSize = this.maxStepSize;
8374
- if (this.pathSegments[indexAfterLayerChange]) {
8375
- this.tailDistanceAlongPath = this.pathSegments[indexAfterLayerChange].startDistance;
8385
+ const segmentIndexAfterChange = this.pathSegments.findIndex(
8386
+ (seg) => seg.start === pointAfterChange
8387
+ );
8388
+ if (segmentIndexAfterChange !== -1) {
8389
+ this.tailDistanceAlongPath = this.pathSegments[segmentIndexAfterChange].startDistance;
8376
8390
  this.headDistanceAlongPath = this.tailDistanceAlongPath;
8391
+ this.lastValidPath = null;
8392
+ this.lastValidPathHeadDistance = this.tailDistanceAlongPath;
8393
+ } else if (indexAfterLayerChange < this.inputRoute.route.length) {
8394
+ console.warn(
8395
+ "Fallback used for tailDistanceAlongPath after layer change"
8396
+ );
8397
+ const segment = this.pathSegments.find(
8398
+ (seg) => seg.start === this.inputRoute.route[indexAfterLayerChange]
8399
+ );
8400
+ if (segment) {
8401
+ this.tailDistanceAlongPath = segment.startDistance;
8402
+ this.headDistanceAlongPath = this.tailDistanceAlongPath;
8403
+ this.lastValidPath = null;
8404
+ this.lastValidPathHeadDistance = this.tailDistanceAlongPath;
8405
+ } else {
8406
+ console.error(
8407
+ "Could not find segment start after layer change, path might be incomplete."
8408
+ );
8409
+ this.solved = true;
8410
+ }
8377
8411
  } else {
8378
- console.error("Creating via at end, this is probably not right");
8412
+ console.warn("Layer change occurred at the end of the path.");
8379
8413
  this.solved = true;
8380
- return;
8381
8414
  }
8382
8415
  return;
8383
8416
  }
@@ -8773,6 +8806,537 @@ var CapacityMeshEdgeSolver2_NodeTreeOptimization = class extends CapacityMeshEdg
8773
8806
  }
8774
8807
  };
8775
8808
 
8809
+ // lib/data-structures/HighDensityRouteSpatialIndex.ts
8810
+ var getSegmentBounds2 = (segment) => {
8811
+ return {
8812
+ minX: Math.min(segment[0].x, segment[1].x),
8813
+ maxX: Math.max(segment[0].x, segment[1].x),
8814
+ minY: Math.min(segment[0].y, segment[1].y),
8815
+ maxY: Math.max(segment[0].y, segment[1].y)
8816
+ };
8817
+ };
8818
+ function computeDistSq(p1, p2) {
8819
+ const dx = p1.x - p2.x;
8820
+ const dy = p1.y - p2.y;
8821
+ return dx * dx + dy * dy;
8822
+ }
8823
+ function pointToSegmentDistanceSq(p, a, b) {
8824
+ const l2 = computeDistSq(a, b);
8825
+ if (l2 === 0) return computeDistSq(p, a);
8826
+ let t = ((p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y)) / l2;
8827
+ t = Math.max(0, Math.min(1, t));
8828
+ const projection = {
8829
+ x: a.x + t * (b.x - a.x),
8830
+ y: a.y + t * (b.y - a.y)
8831
+ };
8832
+ return computeDistSq(p, projection);
8833
+ }
8834
+ function segmentToSegmentDistanceSq(a, b, c, d) {
8835
+ if (doSegmentsIntersect(a, b, c, d)) {
8836
+ return 0;
8837
+ }
8838
+ const pA = { x: a.x, y: a.y };
8839
+ const pB = { x: b.x, y: b.y };
8840
+ const pC = { x: c.x, y: c.y };
8841
+ const pD = { x: d.x, y: d.y };
8842
+ return Math.min(
8843
+ pointToSegmentDistanceSq(pA, pC, pD),
8844
+ pointToSegmentDistanceSq(pB, pC, pD),
8845
+ pointToSegmentDistanceSq(pC, pA, pB),
8846
+ pointToSegmentDistanceSq(pD, pA, pB)
8847
+ );
8848
+ }
8849
+ var HighDensityRouteSpatialIndex = class {
8850
+ segmentBuckets;
8851
+ viaBuckets;
8852
+ // New: Store vias
8853
+ routes;
8854
+ CELL_SIZE;
8855
+ constructor(routes, cellSize = 1) {
8856
+ this.segmentBuckets = /* @__PURE__ */ new Map();
8857
+ this.viaBuckets = /* @__PURE__ */ new Map();
8858
+ this.routes = /* @__PURE__ */ new Map();
8859
+ this.CELL_SIZE = cellSize;
8860
+ const epsilon = 1e-9;
8861
+ for (const route of routes) {
8862
+ if (!route || !route.connectionName) {
8863
+ console.warn("Skipping route with missing data:", route);
8864
+ continue;
8865
+ }
8866
+ if (this.routes.has(route.connectionName)) {
8867
+ console.warn(
8868
+ `Skipping duplicate route connectionName: ${route.connectionName}`
8869
+ );
8870
+ continue;
8871
+ }
8872
+ this.routes.set(route.connectionName, route);
8873
+ if (route.route && route.route.length >= 2) {
8874
+ for (let i = 0; i < route.route.length - 1; i++) {
8875
+ const p1 = route.route[i];
8876
+ const p2 = route.route[i + 1];
8877
+ if (p1.x === p2.x && p1.y === p2.y) continue;
8878
+ const segment = [p1, p2];
8879
+ const bounds = getSegmentBounds2(segment);
8880
+ const segmentInfo = {
8881
+ segmentId: `${route.connectionName}-seg-${i}`,
8882
+ segment,
8883
+ parentRoute: route
8884
+ };
8885
+ const minIndexX = Math.floor(bounds.minX / this.CELL_SIZE);
8886
+ const maxIndexX = Math.floor((bounds.maxX + epsilon) / this.CELL_SIZE);
8887
+ const minIndexY = Math.floor(bounds.minY / this.CELL_SIZE);
8888
+ const maxIndexY = Math.floor((bounds.maxY + epsilon) / this.CELL_SIZE);
8889
+ for (let ix = minIndexX; ix <= maxIndexX; ix++) {
8890
+ for (let iy = minIndexY; iy <= maxIndexY; iy++) {
8891
+ const bucketKey = `${ix}x${iy}`;
8892
+ let bucketList = this.segmentBuckets.get(bucketKey);
8893
+ if (!bucketList) {
8894
+ bucketList = [];
8895
+ this.segmentBuckets.set(bucketKey, bucketList);
8896
+ }
8897
+ bucketList.push(segmentInfo);
8898
+ }
8899
+ }
8900
+ }
8901
+ }
8902
+ if (route.vias && route.vias.length > 0) {
8903
+ for (let i = 0; i < route.vias.length; i++) {
8904
+ const via = route.vias[i];
8905
+ if (via === void 0 || via === null) continue;
8906
+ const storedVia = {
8907
+ viaId: `${route.connectionName}-via-${i}`,
8908
+ x: via.x,
8909
+ y: via.y,
8910
+ parentRoute: route
8911
+ };
8912
+ const ix = Math.floor(via.x / this.CELL_SIZE);
8913
+ const iy = Math.floor(via.y / this.CELL_SIZE);
8914
+ const bucketKey = `${ix}x${iy}`;
8915
+ let bucketList = this.viaBuckets.get(bucketKey);
8916
+ if (!bucketList) {
8917
+ bucketList = [];
8918
+ this.viaBuckets.set(bucketKey, bucketList);
8919
+ }
8920
+ bucketList.push(storedVia);
8921
+ }
8922
+ }
8923
+ }
8924
+ }
8925
+ /**
8926
+ * Finds routes that potentially conflict with a given line segment within a margin.
8927
+ * Checks both segments and vias.
8928
+ * @param segmentStart Start point of the query segment.
8929
+ * @param segmentEnd End point of the query segment.
8930
+ * @param margin The minimum required clearance distance from the query segment's centerline.
8931
+ * @returns An array of conflicting routes and their minimum distance to the segment.
8932
+ */
8933
+ getConflictingRoutesForSegment(segmentStart, segmentEnd, margin) {
8934
+ const querySegment = [segmentStart, segmentEnd];
8935
+ const bounds = getSegmentBounds2(querySegment);
8936
+ const searchMinX = bounds.minX - margin;
8937
+ const searchMinY = bounds.minY - margin;
8938
+ const searchMaxX = bounds.maxX + margin;
8939
+ const searchMaxY = bounds.maxY + margin;
8940
+ const epsilon = 1e-9;
8941
+ const minIndexX = Math.floor(searchMinX / this.CELL_SIZE);
8942
+ const maxIndexX = Math.floor((searchMaxX + epsilon) / this.CELL_SIZE);
8943
+ const minIndexY = Math.floor(searchMinY / this.CELL_SIZE);
8944
+ const maxIndexY = Math.floor((searchMaxY + epsilon) / this.CELL_SIZE);
8945
+ const conflictingRouteData = /* @__PURE__ */ new Map();
8946
+ const checkedSegments = /* @__PURE__ */ new Set();
8947
+ const checkedVias = /* @__PURE__ */ new Set();
8948
+ const queryP1 = { x: segmentStart.x, y: segmentStart.y };
8949
+ const queryP2 = { x: segmentEnd.x, y: segmentEnd.y };
8950
+ for (let ix = minIndexX; ix <= maxIndexX; ix++) {
8951
+ for (let iy = minIndexY; iy <= maxIndexY; iy++) {
8952
+ const bucketKey = `${ix}x${iy}`;
8953
+ const segmentBucketList = this.segmentBuckets.get(bucketKey);
8954
+ if (segmentBucketList) {
8955
+ for (const segmentInfo of segmentBucketList) {
8956
+ if (checkedSegments.has(segmentInfo.segmentId)) continue;
8957
+ checkedSegments.add(segmentInfo.segmentId);
8958
+ const route = segmentInfo.parentRoute;
8959
+ const [p1, p2] = segmentInfo.segment;
8960
+ const requiredSeparation = margin + route.traceThickness / 2;
8961
+ const requiredSeparationSq = requiredSeparation * requiredSeparation;
8962
+ const distSq = segmentToSegmentDistanceSq(
8963
+ segmentStart,
8964
+ segmentEnd,
8965
+ p1,
8966
+ p2
8967
+ );
8968
+ if (distSq < requiredSeparationSq) {
8969
+ const routeName = route.connectionName;
8970
+ const existing = conflictingRouteData.get(routeName);
8971
+ if (!existing || distSq < existing.minDistSq) {
8972
+ conflictingRouteData.set(routeName, {
8973
+ route,
8974
+ minDistSq: distSq
8975
+ });
8976
+ }
8977
+ }
8978
+ }
8979
+ }
8980
+ const viaBucketList = this.viaBuckets.get(bucketKey);
8981
+ if (viaBucketList) {
8982
+ for (const viaInfo of viaBucketList) {
8983
+ if (checkedVias.has(viaInfo.viaId)) continue;
8984
+ checkedVias.add(viaInfo.viaId);
8985
+ const route = viaInfo.parentRoute;
8986
+ const viaPoint = { x: viaInfo.x, y: viaInfo.y };
8987
+ const requiredSeparation = margin + route.viaDiameter / 2;
8988
+ const requiredSeparationSq = requiredSeparation * requiredSeparation;
8989
+ const distSq = pointToSegmentDistanceSq(viaPoint, queryP1, queryP2);
8990
+ if (distSq < requiredSeparationSq) {
8991
+ const routeName = route.connectionName;
8992
+ const existing = conflictingRouteData.get(routeName);
8993
+ if (!existing || distSq < existing.minDistSq) {
8994
+ conflictingRouteData.set(routeName, {
8995
+ route,
8996
+ minDistSq: distSq
8997
+ });
8998
+ }
8999
+ }
9000
+ }
9001
+ }
9002
+ }
9003
+ }
9004
+ const results = [];
9005
+ for (const data of conflictingRouteData.values()) {
9006
+ results.push({
9007
+ conflictingRoute: data.route,
9008
+ distance: Math.sqrt(data.minDistSq)
9009
+ });
9010
+ }
9011
+ return results;
9012
+ }
9013
+ /**
9014
+ * Finds routes that pass near a given point within a margin.
9015
+ * Checks both segments and vias.
9016
+ * @param point The query point {x, y}. Z is ignored.
9017
+ * @param margin The minimum required clearance distance from the query point.
9018
+ * @returns An array of conflicting routes and their minimum distance to the point.
9019
+ */
9020
+ getConflictingRoutesNearPoint(point, margin) {
9021
+ const searchMinX = point.x - margin;
9022
+ const searchMinY = point.y - margin;
9023
+ const searchMaxX = point.x + margin;
9024
+ const searchMaxY = point.y + margin;
9025
+ const epsilon = 1e-9;
9026
+ const minIndexX = Math.floor(searchMinX / this.CELL_SIZE);
9027
+ const maxIndexX = Math.floor((searchMaxX + epsilon) / this.CELL_SIZE);
9028
+ const minIndexY = Math.floor(searchMinY / this.CELL_SIZE);
9029
+ const maxIndexY = Math.floor((searchMaxY + epsilon) / this.CELL_SIZE);
9030
+ const conflictingRouteData = /* @__PURE__ */ new Map();
9031
+ const checkedSegments = /* @__PURE__ */ new Set();
9032
+ const checkedVias = /* @__PURE__ */ new Set();
9033
+ for (let ix = minIndexX; ix <= maxIndexX; ix++) {
9034
+ for (let iy = minIndexY; iy <= maxIndexY; iy++) {
9035
+ const bucketKey = `${ix}x${iy}`;
9036
+ const segmentBucketList = this.segmentBuckets.get(bucketKey);
9037
+ if (segmentBucketList) {
9038
+ for (const segmentInfo of segmentBucketList) {
9039
+ if (checkedSegments.has(segmentInfo.segmentId)) continue;
9040
+ checkedSegments.add(segmentInfo.segmentId);
9041
+ const route = segmentInfo.parentRoute;
9042
+ const p1 = {
9043
+ x: segmentInfo.segment[0].x,
9044
+ y: segmentInfo.segment[0].y
9045
+ };
9046
+ const p2 = {
9047
+ x: segmentInfo.segment[1].x,
9048
+ y: segmentInfo.segment[1].y
9049
+ };
9050
+ const requiredSeparation = margin + route.traceThickness / 2;
9051
+ const requiredSeparationSq = requiredSeparation * requiredSeparation;
9052
+ const distSq = pointToSegmentDistanceSq(point, p1, p2);
9053
+ if (distSq < requiredSeparationSq) {
9054
+ const routeName = route.connectionName;
9055
+ const existing = conflictingRouteData.get(routeName);
9056
+ if (!existing || distSq < existing.minDistSq) {
9057
+ conflictingRouteData.set(routeName, {
9058
+ route,
9059
+ minDistSq: distSq
9060
+ });
9061
+ }
9062
+ }
9063
+ }
9064
+ }
9065
+ const viaBucketList = this.viaBuckets.get(bucketKey);
9066
+ if (viaBucketList) {
9067
+ for (const viaInfo of viaBucketList) {
9068
+ if (checkedVias.has(viaInfo.viaId)) continue;
9069
+ checkedVias.add(viaInfo.viaId);
9070
+ const route = viaInfo.parentRoute;
9071
+ const viaPoint = { x: viaInfo.x, y: viaInfo.y };
9072
+ const requiredSeparation = margin + route.viaDiameter / 2;
9073
+ const requiredSeparationSq = requiredSeparation * requiredSeparation;
9074
+ const distSq = computeDistSq(point, viaPoint);
9075
+ if (distSq < requiredSeparationSq) {
9076
+ const routeName = route.connectionName;
9077
+ const existing = conflictingRouteData.get(routeName);
9078
+ if (!existing || distSq < existing.minDistSq) {
9079
+ conflictingRouteData.set(routeName, {
9080
+ route,
9081
+ minDistSq: distSq
9082
+ });
9083
+ }
9084
+ }
9085
+ }
9086
+ }
9087
+ }
9088
+ }
9089
+ const results = [];
9090
+ for (const data of conflictingRouteData.values()) {
9091
+ results.push({
9092
+ conflictingRoute: data.route,
9093
+ distance: Math.sqrt(data.minDistSq)
9094
+ });
9095
+ }
9096
+ return results;
9097
+ }
9098
+ };
9099
+
9100
+ // lib/solvers/UselessViaRemovalSolver/SingleRouteUselessViaRemovalSolver.ts
9101
+ var SingleRouteUselessViaRemovalSolver = class extends BaseSolver {
9102
+ obstacleSHI;
9103
+ hdRouteSHI;
9104
+ unsimplifiedRoute;
9105
+ routeSections;
9106
+ currentSectionIndex;
9107
+ TRACE_THICKNESS = 0.15;
9108
+ OBSTACLE_MARGIN = 0.1;
9109
+ constructor(params) {
9110
+ super();
9111
+ this.currentSectionIndex = 1;
9112
+ this.obstacleSHI = params.obstacleSHI;
9113
+ this.hdRouteSHI = params.hdRouteSHI;
9114
+ this.unsimplifiedRoute = params.unsimplifiedRoute;
9115
+ this.routeSections = this.breakRouteIntoSections(this.unsimplifiedRoute);
9116
+ }
9117
+ breakRouteIntoSections(route) {
9118
+ const routeSections = [];
9119
+ const routePoints = route.route;
9120
+ if (routePoints.length === 0) return [];
9121
+ let currentSection = {
9122
+ startIndex: 0,
9123
+ endIndex: -1,
9124
+ z: routePoints[0].z,
9125
+ points: [routePoints[0]]
9126
+ };
9127
+ for (let i = 1; i < routePoints.length; i++) {
9128
+ if (routePoints[i].z === currentSection.z) {
9129
+ currentSection.points.push(routePoints[i]);
9130
+ } else {
9131
+ currentSection.endIndex = i - 1;
9132
+ routeSections.push(currentSection);
9133
+ currentSection = {
9134
+ startIndex: i,
9135
+ endIndex: -1,
9136
+ z: routePoints[i].z,
9137
+ points: [routePoints[i]]
9138
+ };
9139
+ }
9140
+ }
9141
+ currentSection.endIndex = routePoints.length - 1;
9142
+ routeSections.push(currentSection);
9143
+ return routeSections;
9144
+ }
9145
+ _step() {
9146
+ if (this.currentSectionIndex >= this.routeSections.length - 1) {
9147
+ this.solved = true;
9148
+ return;
9149
+ }
9150
+ const prevSection = this.routeSections[this.currentSectionIndex - 1];
9151
+ const currentSection = this.routeSections[this.currentSectionIndex];
9152
+ const nextSection = this.routeSections[this.currentSectionIndex + 1];
9153
+ if (prevSection.z !== nextSection.z) {
9154
+ this.currentSectionIndex++;
9155
+ return;
9156
+ }
9157
+ const targetZ = prevSection.z;
9158
+ if (this.canSectionMoveToLayer({ currentSection, targetZ })) {
9159
+ currentSection.z = targetZ;
9160
+ currentSection.points = currentSection.points.map((p) => ({
9161
+ ...p,
9162
+ z: targetZ
9163
+ }));
9164
+ this.currentSectionIndex += 2;
9165
+ return;
9166
+ }
9167
+ this.currentSectionIndex++;
9168
+ return;
9169
+ }
9170
+ canSectionMoveToLayer({
9171
+ currentSection,
9172
+ targetZ
9173
+ }) {
9174
+ for (let i = 0; i < currentSection.points.length - 1; i++) {
9175
+ const A = { ...currentSection.points[i], z: targetZ };
9176
+ const B = { ...currentSection.points[i + 1], z: targetZ };
9177
+ const conflictingRoutes = this.hdRouteSHI.getConflictingRoutesForSegment(
9178
+ A,
9179
+ B,
9180
+ this.TRACE_THICKNESS
9181
+ );
9182
+ for (const { conflictingRoute, distance: distance6 } of conflictingRoutes) {
9183
+ if (conflictingRoute.connectionName === this.unsimplifiedRoute.connectionName)
9184
+ continue;
9185
+ if (distance6 < this.TRACE_THICKNESS + conflictingRoute.traceThickness) {
9186
+ return false;
9187
+ }
9188
+ }
9189
+ const segmentBox = {
9190
+ centerX: (A.x + B.x) / 2,
9191
+ centerY: (A.y + B.y) / 2,
9192
+ width: Math.abs(A.x - B.x),
9193
+ height: Math.abs(A.y - B.y)
9194
+ };
9195
+ const obstacles = this.obstacleSHI.getNodesInArea(
9196
+ segmentBox.centerX,
9197
+ segmentBox.centerY,
9198
+ segmentBox.width,
9199
+ segmentBox.height
9200
+ );
9201
+ for (const obstacle of obstacles) {
9202
+ const distToObstacle = segmentToBoxMinDistance(A, B, obstacle);
9203
+ if (distToObstacle < this.TRACE_THICKNESS + this.OBSTACLE_MARGIN) {
9204
+ return false;
9205
+ }
9206
+ }
9207
+ }
9208
+ return true;
9209
+ }
9210
+ getOptimizedHdRoute() {
9211
+ const route = this.routeSections.flatMap((section) => section.points);
9212
+ const vias = [];
9213
+ for (let i = 0; i < route.length - 1; i++) {
9214
+ if (route[i].z !== route[i + 1].z) {
9215
+ vias.push({
9216
+ x: route[i].x,
9217
+ y: route[i].y
9218
+ });
9219
+ }
9220
+ }
9221
+ return {
9222
+ connectionName: this.unsimplifiedRoute.connectionName,
9223
+ route,
9224
+ traceThickness: this.unsimplifiedRoute.traceThickness,
9225
+ vias,
9226
+ viaDiameter: this.unsimplifiedRoute.viaDiameter
9227
+ };
9228
+ }
9229
+ visualize() {
9230
+ const graphics = {
9231
+ circles: [],
9232
+ lines: [],
9233
+ points: [],
9234
+ rects: [],
9235
+ coordinateSystem: "cartesian",
9236
+ title: "Single Route Useless Via Removal Solver"
9237
+ };
9238
+ for (let i = 0; i < this.routeSections.length; i++) {
9239
+ const section = this.routeSections[i];
9240
+ graphics.lines.push({
9241
+ points: section.points,
9242
+ strokeWidth: this.TRACE_THICKNESS,
9243
+ strokeColor: i === this.currentSectionIndex ? "orange" : section.z === 0 ? "red" : "blue"
9244
+ });
9245
+ }
9246
+ return graphics;
9247
+ }
9248
+ };
9249
+
9250
+ // lib/solvers/UselessViaRemovalSolver/UselessViaRemovalSolver.ts
9251
+ var UselessViaRemovalSolver = class extends BaseSolver {
9252
+ constructor(input) {
9253
+ super();
9254
+ this.input = input;
9255
+ this.unsimplifiedHdRoutes = input.unsimplifiedHdRoutes;
9256
+ this.optimizedHdRoutes = [];
9257
+ this.unprocessedRoutes = [...input.unsimplifiedHdRoutes];
9258
+ this.obstacleSHI = new ObstacleSpatialHashIndex(input.obstacles);
9259
+ this.hdRouteSHI = new HighDensityRouteSpatialIndex(
9260
+ this.unsimplifiedHdRoutes
9261
+ );
9262
+ }
9263
+ unsimplifiedHdRoutes;
9264
+ optimizedHdRoutes;
9265
+ unprocessedRoutes;
9266
+ activeSubSolver = null;
9267
+ obstacleSHI = null;
9268
+ hdRouteSHI = null;
9269
+ _step() {
9270
+ if (this.activeSubSolver) {
9271
+ this.activeSubSolver.step();
9272
+ if (this.activeSubSolver.solved) {
9273
+ this.optimizedHdRoutes.push(this.activeSubSolver.getOptimizedHdRoute());
9274
+ this.activeSubSolver = null;
9275
+ } else if (this.activeSubSolver.failed || this.activeSubSolver.error) {
9276
+ this.error = this.activeSubSolver.error;
9277
+ this.failed = true;
9278
+ }
9279
+ return;
9280
+ }
9281
+ const unprocessedRoute = this.unprocessedRoutes.shift();
9282
+ if (!unprocessedRoute) {
9283
+ this.solved = true;
9284
+ return;
9285
+ }
9286
+ this.activeSubSolver = new SingleRouteUselessViaRemovalSolver({
9287
+ hdRouteSHI: this.hdRouteSHI,
9288
+ obstacleSHI: this.obstacleSHI,
9289
+ unsimplifiedRoute: unprocessedRoute
9290
+ });
9291
+ }
9292
+ getOptimizedHdRoutes() {
9293
+ return this.optimizedHdRoutes;
9294
+ }
9295
+ visualize() {
9296
+ const visualization = {
9297
+ lines: [],
9298
+ points: [],
9299
+ rects: [],
9300
+ circles: [],
9301
+ coordinateSystem: "cartesian",
9302
+ title: "Useless Via Removal Solver"
9303
+ };
9304
+ for (const route of this.optimizedHdRoutes) {
9305
+ if (route.route.length === 0) continue;
9306
+ const color = this.input.colorMap[route.connectionName] || "#888888";
9307
+ for (let i = 0; i < route.route.length - 1; i++) {
9308
+ const current = route.route[i];
9309
+ const next = route.route[i + 1];
9310
+ if (current.z === next.z) {
9311
+ visualization.lines.push({
9312
+ points: [
9313
+ { x: current.x, y: current.y },
9314
+ { x: next.x, y: next.y }
9315
+ ],
9316
+ strokeColor: current.z === 0 ? "red" : "blue",
9317
+ strokeWidth: route.traceThickness,
9318
+ label: `${route.connectionName} (z=${current.z})`
9319
+ });
9320
+ }
9321
+ }
9322
+ for (const via of route.vias) {
9323
+ visualization.circles.push({
9324
+ center: { x: via.x, y: via.y },
9325
+ radius: route.viaDiameter / 2,
9326
+ fill: "rgba(255, 0, 255, 0.5)",
9327
+ label: `${route.connectionName} via`
9328
+ });
9329
+ }
9330
+ }
9331
+ if (this.activeSubSolver) {
9332
+ visualization.lines.push(
9333
+ ...this.activeSubSolver.visualize().lines ?? []
9334
+ );
9335
+ }
9336
+ return visualization;
9337
+ }
9338
+ };
9339
+
8776
9340
  // lib/solvers/AutoroutingPipelineSolver.ts
8777
9341
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
8778
9342
  return {
@@ -8818,6 +9382,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
8818
9382
  highDensityStitchSolver;
8819
9383
  singleLayerNodeMerger;
8820
9384
  strawSolver;
9385
+ uselessViaRemovalSolver;
8821
9386
  multiSimplifiedPathSolver;
8822
9387
  startTimeOfPhase;
8823
9388
  endTimeOfPhase;
@@ -8973,12 +9538,24 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
8973
9538
  }
8974
9539
  ]
8975
9540
  ),
9541
+ definePipelineStep(
9542
+ "uselessViaRemovalSolver",
9543
+ UselessViaRemovalSolver,
9544
+ (cms) => [
9545
+ {
9546
+ unsimplifiedHdRoutes: cms.highDensityStitchSolver.mergedHdRoutes,
9547
+ obstacles: cms.srj.obstacles,
9548
+ colorMap: cms.colorMap,
9549
+ layerCount: cms.srj.layerCount
9550
+ }
9551
+ ]
9552
+ ),
8976
9553
  definePipelineStep(
8977
9554
  "multiSimplifiedPathSolver",
8978
9555
  MultiSimplifiedPathSolver,
8979
9556
  (cms) => [
8980
9557
  {
8981
- unsimplifiedHdRoutes: cms.highDensityStitchSolver.mergedHdRoutes,
9558
+ unsimplifiedHdRoutes: cms.uselessViaRemovalSolver?.getOptimizedHdRoutes() || cms.highDensityStitchSolver.mergedHdRoutes,
8982
9559
  obstacles: cms.srj.obstacles,
8983
9560
  connMap: cms.connMap,
8984
9561
  colorMap: cms.colorMap
@@ -9037,6 +9614,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9037
9614
  const segmentOptimizationViz = this.unravelMultiSectionSolver?.visualize() ?? this.segmentToPointOptimizer?.visualize();
9038
9615
  const highDensityViz = this.highDensityRouteSolver?.visualize();
9039
9616
  const highDensityStitchViz = this.highDensityStitchSolver?.visualize();
9617
+ const uselessViaRemovalViz = this.uselessViaRemovalSolver?.visualize();
9040
9618
  const simplifiedPathSolverViz = this.multiSimplifiedPathSolver?.visualize();
9041
9619
  const problemViz = {
9042
9620
  points: [
@@ -9089,6 +9667,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9089
9667
  segmentOptimizationViz,
9090
9668
  highDensityViz ? combineVisualizations(problemViz, highDensityViz) : null,
9091
9669
  highDensityStitchViz,
9670
+ uselessViaRemovalViz,
9092
9671
  simplifiedPathSolverViz,
9093
9672
  this.solved ? combineVisualizations(
9094
9673
  problemViz,
@@ -9151,7 +9730,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9151
9730
  return match ? match[1] : mstConnectionName;
9152
9731
  }
9153
9732
  _getOutputHdRoutes() {
9154
- return this.multiSimplifiedPathSolver?.simplifiedHdRoutes ?? this.highDensityStitchSolver.mergedHdRoutes;
9733
+ return this.multiSimplifiedPathSolver?.simplifiedHdRoutes ?? this.uselessViaRemovalSolver?.getOptimizedHdRoutes() ?? this.highDensityStitchSolver.mergedHdRoutes;
9155
9734
  }
9156
9735
  /**
9157
9736
  * Returns the SimpleRouteJson with routes converted to SimplifiedPcbTraces