@tscircuit/capacity-autorouter 0.0.49 → 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
  }
@@ -8806,6 +8806,537 @@ var CapacityMeshEdgeSolver2_NodeTreeOptimization = class extends CapacityMeshEdg
8806
8806
  }
8807
8807
  };
8808
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
+
8809
9340
  // lib/solvers/AutoroutingPipelineSolver.ts
8810
9341
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
8811
9342
  return {
@@ -8851,6 +9382,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
8851
9382
  highDensityStitchSolver;
8852
9383
  singleLayerNodeMerger;
8853
9384
  strawSolver;
9385
+ uselessViaRemovalSolver;
8854
9386
  multiSimplifiedPathSolver;
8855
9387
  startTimeOfPhase;
8856
9388
  endTimeOfPhase;
@@ -9006,12 +9538,24 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9006
9538
  }
9007
9539
  ]
9008
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
+ ),
9009
9553
  definePipelineStep(
9010
9554
  "multiSimplifiedPathSolver",
9011
9555
  MultiSimplifiedPathSolver,
9012
9556
  (cms) => [
9013
9557
  {
9014
- unsimplifiedHdRoutes: cms.highDensityStitchSolver.mergedHdRoutes,
9558
+ unsimplifiedHdRoutes: cms.uselessViaRemovalSolver?.getOptimizedHdRoutes() || cms.highDensityStitchSolver.mergedHdRoutes,
9015
9559
  obstacles: cms.srj.obstacles,
9016
9560
  connMap: cms.connMap,
9017
9561
  colorMap: cms.colorMap
@@ -9070,6 +9614,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9070
9614
  const segmentOptimizationViz = this.unravelMultiSectionSolver?.visualize() ?? this.segmentToPointOptimizer?.visualize();
9071
9615
  const highDensityViz = this.highDensityRouteSolver?.visualize();
9072
9616
  const highDensityStitchViz = this.highDensityStitchSolver?.visualize();
9617
+ const uselessViaRemovalViz = this.uselessViaRemovalSolver?.visualize();
9073
9618
  const simplifiedPathSolverViz = this.multiSimplifiedPathSolver?.visualize();
9074
9619
  const problemViz = {
9075
9620
  points: [
@@ -9122,6 +9667,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9122
9667
  segmentOptimizationViz,
9123
9668
  highDensityViz ? combineVisualizations(problemViz, highDensityViz) : null,
9124
9669
  highDensityStitchViz,
9670
+ uselessViaRemovalViz,
9125
9671
  simplifiedPathSolverViz,
9126
9672
  this.solved ? combineVisualizations(
9127
9673
  problemViz,
@@ -9184,7 +9730,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9184
9730
  return match ? match[1] : mstConnectionName;
9185
9731
  }
9186
9732
  _getOutputHdRoutes() {
9187
- return this.multiSimplifiedPathSolver?.simplifiedHdRoutes ?? this.highDensityStitchSolver.mergedHdRoutes;
9733
+ return this.multiSimplifiedPathSolver?.simplifiedHdRoutes ?? this.uselessViaRemovalSolver?.getOptimizedHdRoutes() ?? this.highDensityStitchSolver.mergedHdRoutes;
9188
9734
  }
9189
9735
  /**
9190
9736
  * Returns the SimpleRouteJson with routes converted to SimplifiedPcbTraces