@tscircuit/capacity-autorouter 0.0.16 → 0.0.18

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/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @tscircuit/capacity-autorouter
2
2
 
3
+ [View Online Playground](https://unraveller.vercel.app) · [tscircuit docs](https://docs.tscircuit.com)
4
+
3
5
  A high-density PCB autorouter library for node.js and TypeScript projects. Part of the [tscircuit project](https://github.com/tscircuit/tscircuit) · [discord](https://tscircuit.com/join) · [twitter](https://x.com/seveibar) · [try tscircuit online](https://tscircuit.com)
4
6
 
5
7
  Check out this [short youtube explanation of this autorouter](https://youtu.be/MmTk0806fAo)
package/dist/index.d.ts CHANGED
@@ -66,6 +66,7 @@ interface CapacityMeshNode {
66
66
  width: number;
67
67
  height: number;
68
68
  layer: string;
69
+ availableZ: number[];
69
70
  _depth?: number;
70
71
  _completelyInsideObstacle?: boolean;
71
72
  _containsObstacle?: boolean;
@@ -92,6 +93,7 @@ declare class BaseSolver {
92
93
  iterations: number;
93
94
  progress: number;
94
95
  error: string | null;
96
+ failedSubSolvers?: BaseSolver[];
95
97
  /** DO NOT OVERRIDE! Override _step() instead */
96
98
  step(): void;
97
99
  _step(): void;
@@ -112,6 +114,12 @@ declare class CapacityMeshEdgeSolver extends BaseSolver {
112
114
  interface CapacityMeshNodeSolverOptions {
113
115
  capacityDepth?: number;
114
116
  }
117
+ interface Target {
118
+ x: number;
119
+ y: number;
120
+ connectionName: string;
121
+ availableZ: number[];
122
+ }
115
123
  declare class CapacityMeshNodeSolver extends BaseSolver {
116
124
  srj: SimpleRouteJson;
117
125
  opts: CapacityMeshNodeSolverOptions;
@@ -119,16 +127,12 @@ declare class CapacityMeshNodeSolver extends BaseSolver {
119
127
  finishedNodes: CapacityMeshNode[];
120
128
  nodeToOverlappingObstaclesMap: Map<CapacityMeshNodeId, Obstacle[]>;
121
129
  MAX_DEPTH: number;
122
- targets: Array<{
123
- x: number;
124
- y: number;
125
- connectionName: string;
126
- }>;
130
+ targets: Target[];
127
131
  constructor(srj: SimpleRouteJson, opts?: CapacityMeshNodeSolverOptions);
128
132
  _nextNodeCounter: number;
129
133
  getNextNodeId(): string;
130
134
  getCapacityFromDepth(depth: number): number;
131
- getTargetNameIfNodeContainsTarget(node: CapacityMeshNode): string | null;
135
+ getTargetIfNodeContainsTarget(node: CapacityMeshNode): Target | null;
132
136
  getOverlappingObstacles(node: CapacityMeshNode): Obstacle[];
133
137
  /**
134
138
  * Checks if the given mesh node overlaps with any obstacle.
@@ -233,6 +237,7 @@ interface NodePortSegment {
233
237
  x: number;
234
238
  y: number;
235
239
  };
240
+ availableZ: number[];
236
241
  connectionNames: string[];
237
242
  }
238
243
 
@@ -473,7 +478,7 @@ declare class SingleHighDensityRouteSolver extends BaseSolver {
473
478
  handleSimpleCases(): void;
474
479
  get viaPenaltyDistance(): number;
475
480
  isNodeTooCloseToObstacle(node: Node, margin?: number, isVia?: boolean): boolean;
476
- isNodeTooCloseToEdge(node: Node): boolean;
481
+ isNodeTooCloseToEdge(node: Node, isVia?: boolean): boolean;
477
482
  doesPathToParentIntersectObstacle(node: Node): boolean;
478
483
  computeH(node: Node): number;
479
484
  computeG(node: Node): number;
@@ -504,11 +509,12 @@ declare class IntraNodeRouteSolver extends BaseSolver {
504
509
  }[];
505
510
  totalConnections: number;
506
511
  solvedRoutes: HighDensityIntraNodeRoute[];
507
- failedSolvers: SingleHighDensityRouteSolver[];
512
+ failedSubSolvers: SingleHighDensityRouteSolver[];
508
513
  hyperParameters: Partial<HighDensityHyperParameters>;
509
514
  minDistBetweenEnteringPoints: number;
510
515
  activeSolver: SingleHighDensityRouteSolver | null;
511
516
  connMap?: ConnectivityMap;
517
+ get failedSolvers(): SingleHighDensityRouteSolver[];
512
518
  constructor(params: {
513
519
  nodeWithPortPoints: NodeWithPortPoints;
514
520
  colorMap?: Record<string, string>;
package/dist/index.js CHANGED
@@ -43,6 +43,7 @@ var BaseSolver = class {
43
43
  iterations = 0;
44
44
  progress = 0;
45
45
  error = null;
46
+ failedSubSolvers;
46
47
  /** DO NOT OVERRIDE! Override _step() instead */
47
48
  step() {
48
49
  if (this.solved) return;
@@ -1092,6 +1093,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1092
1093
  width: maxWidthHeight,
1093
1094
  height: maxWidthHeight,
1094
1095
  layer: "top",
1096
+ availableZ: [0, 1],
1095
1097
  _depth: 0,
1096
1098
  _containsTarget: true,
1097
1099
  _containsObstacle: true,
@@ -1101,7 +1103,11 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1101
1103
  this.finishedNodes = [];
1102
1104
  this.nodeToOverlappingObstaclesMap = /* @__PURE__ */ new Map();
1103
1105
  this.targets = this.srj.connections.flatMap(
1104
- (c) => c.pointsToConnect.map((p) => ({ ...p, connectionName: c.name }))
1106
+ (c) => c.pointsToConnect.map((p) => ({
1107
+ ...p,
1108
+ connectionName: c.name,
1109
+ availableZ: p.layer === "top" ? [0] : [1]
1110
+ }))
1105
1111
  );
1106
1112
  }
1107
1113
  unfinishedNodes;
@@ -1117,7 +1123,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1117
1123
  getCapacityFromDepth(depth) {
1118
1124
  return (this.MAX_DEPTH - depth + 1) ** 2;
1119
1125
  }
1120
- getTargetNameIfNodeContainsTarget(node) {
1126
+ getTargetIfNodeContainsTarget(node) {
1121
1127
  const overlappingObstacles = this.getOverlappingObstacles(node);
1122
1128
  for (const target of this.targets) {
1123
1129
  const targetObstacle = overlappingObstacles.find(
@@ -1125,11 +1131,11 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1125
1131
  );
1126
1132
  if (targetObstacle) {
1127
1133
  if (doRectsOverlap(node, targetObstacle)) {
1128
- return target.connectionName;
1134
+ return target;
1129
1135
  }
1130
1136
  }
1131
1137
  if (target.x >= node.center.x - node.width / 2 && target.x <= node.center.x + node.width / 2 && target.y >= node.center.y - node.height / 2 && target.y <= node.center.y + node.height / 2) {
1132
- return target.connectionName;
1138
+ return target;
1133
1139
  }
1134
1140
  }
1135
1141
  return null;
@@ -1232,12 +1238,17 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1232
1238
  width: childNodeSize.width,
1233
1239
  height: childNodeSize.height,
1234
1240
  layer: parent.layer,
1241
+ availableZ: [0, 1],
1235
1242
  _depth: (parent._depth ?? 0) + 1,
1236
1243
  _parent: parent
1237
1244
  };
1238
1245
  childNode._containsObstacle = this.doesNodeOverlapObstacle(childNode);
1239
- childNode._targetConnectionName = this.getTargetNameIfNodeContainsTarget(childNode) ?? void 0;
1240
- childNode._containsTarget = Boolean(childNode._targetConnectionName);
1246
+ const target = this.getTargetIfNodeContainsTarget(childNode);
1247
+ if (target) {
1248
+ childNode._targetConnectionName = target.connectionName;
1249
+ childNode.availableZ = target.availableZ;
1250
+ childNode._containsTarget = true;
1251
+ }
1241
1252
  if (childNode._containsObstacle) {
1242
1253
  childNode._completelyInsideObstacle = this.isNodeCompletelyInsideObstacle(childNode);
1243
1254
  }
@@ -1294,16 +1305,6 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1294
1305
  coordinateSystem: "cartesian",
1295
1306
  title: "Capacity Mesh Visualization"
1296
1307
  };
1297
- const allNodes = [...this.finishedNodes, ...this.unfinishedNodes];
1298
- for (const node of allNodes) {
1299
- graphics.rects.push({
1300
- center: node.center,
1301
- width: Math.max(node.width - 2, node.width * 0.8),
1302
- height: Math.max(node.height - 2, node.height * 0.8),
1303
- fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : "rgba(0,0,0,0.1)",
1304
- label: node.capacityMeshNodeId
1305
- });
1306
- }
1307
1308
  for (const obstacle of this.srj.obstacles) {
1308
1309
  graphics.rects.push({
1309
1310
  center: obstacle.center,
@@ -1314,13 +1315,24 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1314
1315
  label: "obstacle"
1315
1316
  });
1316
1317
  }
1318
+ const allNodes = [...this.finishedNodes, ...this.unfinishedNodes];
1319
+ for (const node of allNodes) {
1320
+ graphics.rects.push({
1321
+ center: node.center,
1322
+ width: Math.max(node.width - 2, node.width * 0.8),
1323
+ height: Math.max(node.height - 2, node.height * 0.8),
1324
+ fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : "rgba(0,0,0,0.1)",
1325
+ label: `${node.capacityMeshNodeId}
1326
+ availableZ: ${node.availableZ.join(",")}`
1327
+ });
1328
+ }
1317
1329
  this.srj.connections.forEach((connection, index) => {
1318
1330
  const color = COLORS[index % COLORS.length];
1319
1331
  for (const pt of connection.pointsToConnect) {
1320
1332
  graphics.points.push({
1321
1333
  x: pt.x,
1322
1334
  y: pt.y,
1323
- label: `conn-${index}`,
1335
+ label: `conn-${index} (${pt.layer})`,
1324
1336
  color
1325
1337
  });
1326
1338
  }
@@ -1390,11 +1402,16 @@ var CapacityEdgeToPortSegmentSolver = class extends BaseSolver {
1390
1402
  const adjNode = this.nodeMap.get(adjNodeId);
1391
1403
  if (!adjNode) continue;
1392
1404
  const segment = findOverlappingSegment(node, adjNode);
1405
+ const mutuallyAvailableZ = adjNode.availableZ.filter(
1406
+ (z) => node.availableZ.includes(z)
1407
+ );
1408
+ if (mutuallyAvailableZ.length === 0) continue;
1393
1409
  const portSegment = {
1394
1410
  capacityMeshNodeId: nodeId,
1395
1411
  start: segment.start,
1396
1412
  end: segment.end,
1397
- connectionNames: [path.connectionName]
1413
+ connectionNames: [path.connectionName],
1414
+ availableZ: mutuallyAvailableZ
1398
1415
  };
1399
1416
  nodePortSegments.push(portSegment);
1400
1417
  }
@@ -1413,7 +1430,7 @@ var CapacityEdgeToPortSegmentSolver = class extends BaseSolver {
1413
1430
  const node = this.nodeMap.get(nodeId);
1414
1431
  segments.forEach((segment) => {
1415
1432
  const isVertical = segment.start.x === segment.end.x;
1416
- const THICKNESS = 0.5 / segment.connectionNames.length;
1433
+ const THICKNESS = 0.1 / segment.connectionNames.length;
1417
1434
  for (let i = 0; i < segment.connectionNames.length; i++) {
1418
1435
  const offsetAmount = (i / (segment.connectionNames.length - 1 + 1e-6) - 0.5) * THICKNESS;
1419
1436
  const offset = {
@@ -1435,7 +1452,8 @@ var CapacityEdgeToPortSegmentSolver = class extends BaseSolver {
1435
1452
  this.colorMap[segment.connectionNames[i]],
1436
1453
  0.6
1437
1454
  ),
1438
- label: `${nodeId}: ${segment.connectionNames.join(", ")}`
1455
+ label: `${nodeId}: ${segment.connectionNames.join(", ")}
1456
+ availableZ: ${segment.availableZ.join(",")}`
1439
1457
  });
1440
1458
  }
1441
1459
  });
@@ -1536,7 +1554,7 @@ var CapacitySegmentToPointSolver = class extends BaseSolver {
1536
1554
  const center = {
1537
1555
  x: (seg.start.x + seg.end.x) / 2,
1538
1556
  y: (seg.start.y + seg.end.y) / 2,
1539
- z: 0
1557
+ z: seg.availableZ[0]
1540
1558
  };
1541
1559
  seg.assignedPoints = [
1542
1560
  { connectionName: seg.connectionNames[0], point: center }
@@ -1563,7 +1581,7 @@ var CapacitySegmentToPointSolver = class extends BaseSolver {
1563
1581
  points.push({
1564
1582
  x: candidate.start.x + dx * fraction,
1565
1583
  y: candidate.start.y + dy * fraction,
1566
- z: 0
1584
+ z: candidate.availableZ[0]
1567
1585
  });
1568
1586
  }
1569
1587
  ;
@@ -1809,9 +1827,15 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
1809
1827
  }
1810
1828
  return false;
1811
1829
  }
1812
- isNodeTooCloseToEdge(node) {
1813
- const viaRadius = this.viaDiameter / 2;
1814
- return node.x < this.bounds.minX + viaRadius || node.x > this.bounds.maxX - viaRadius || node.y < this.bounds.minY + viaRadius || node.y > this.bounds.maxY - viaRadius;
1830
+ isNodeTooCloseToEdge(node, isVia) {
1831
+ const margin = isVia ? this.viaDiameter / 2 : this.obstacleMargin;
1832
+ const tooClose = node.x < this.bounds.minX + margin || node.x > this.bounds.maxX - margin || node.y < this.bounds.minY + margin || node.y > this.bounds.maxY - margin;
1833
+ if (tooClose && !isVia) {
1834
+ if (distance(node, this.B) < margin * 2) {
1835
+ return false;
1836
+ }
1837
+ }
1838
+ return tooClose;
1815
1839
  }
1816
1840
  doesPathToParentIntersectObstacle(node) {
1817
1841
  const parent = node.parent;
@@ -1865,6 +1889,10 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
1865
1889
  this.exploredNodes.add(neighborKey);
1866
1890
  continue;
1867
1891
  }
1892
+ if (this.isNodeTooCloseToEdge(neighbor, false)) {
1893
+ this.exploredNodes.add(neighborKey);
1894
+ continue;
1895
+ }
1868
1896
  if (this.doesPathToParentIntersectObstacle(neighbor)) {
1869
1897
  this.debug_nodePathToParentIntersectsObstacle.add(neighborKey);
1870
1898
  this.exploredNodes.add(neighborKey);
@@ -1883,9 +1911,9 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
1883
1911
  };
1884
1912
  if (!this.exploredNodes.has(this.getNodeKey(viaNeighbor)) && !this.isNodeTooCloseToObstacle(
1885
1913
  viaNeighbor,
1886
- this.viaDiameter / 2 + this.obstacleMargin,
1914
+ this.viaDiameter / 2 + this.obstacleMargin / 2,
1887
1915
  true
1888
- ) && !this.isNodeTooCloseToEdge(viaNeighbor)) {
1916
+ ) && !this.isNodeTooCloseToEdge(viaNeighbor, true)) {
1889
1917
  viaNeighbor.g = this.computeG(viaNeighbor);
1890
1918
  viaNeighbor.h = this.computeH(viaNeighbor);
1891
1919
  viaNeighbor.f = this.computeF(viaNeighbor.g, viaNeighbor.h);
@@ -1977,13 +2005,15 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
1977
2005
  graphics.points.push({
1978
2006
  x: this.A.x,
1979
2007
  y: this.A.y,
1980
- label: "Input A",
2008
+ label: `Input A
2009
+ z: ${this.A.z}`,
1981
2010
  color: "orange"
1982
2011
  });
1983
2012
  graphics.points.push({
1984
2013
  x: this.B.x,
1985
2014
  y: this.B.y,
1986
- label: "Input B",
2015
+ label: `Input B
2016
+ z: ${this.B.z}`,
1987
2017
  color: "orange"
1988
2018
  });
1989
2019
  graphics.lines.push({
@@ -2219,11 +2249,15 @@ var IntraNodeRouteSolver = class extends BaseSolver {
2219
2249
  unsolvedConnections;
2220
2250
  totalConnections;
2221
2251
  solvedRoutes;
2222
- failedSolvers;
2252
+ failedSubSolvers;
2223
2253
  hyperParameters;
2224
2254
  minDistBetweenEnteringPoints;
2225
2255
  activeSolver = null;
2226
2256
  connMap;
2257
+ // Legacy compat
2258
+ get failedSolvers() {
2259
+ return this.failedSubSolvers;
2260
+ }
2227
2261
  constructor(params) {
2228
2262
  const { nodeWithPortPoints, colorMap } = params;
2229
2263
  super();
@@ -2231,7 +2265,7 @@ var IntraNodeRouteSolver = class extends BaseSolver {
2231
2265
  this.colorMap = colorMap ?? {};
2232
2266
  this.solvedRoutes = [];
2233
2267
  this.hyperParameters = params.hyperParameters ?? {};
2234
- this.failedSolvers = [];
2268
+ this.failedSubSolvers = [];
2235
2269
  this.connMap = params.connMap;
2236
2270
  const unsolvedConnectionsMap = /* @__PURE__ */ new Map();
2237
2271
  for (const { connectionName, x, y, z } of nodeWithPortPoints.portPoints) {
@@ -2292,9 +2326,9 @@ var IntraNodeRouteSolver = class extends BaseSolver {
2292
2326
  this.solvedRoutes.push(this.activeSolver.solvedPath);
2293
2327
  this.activeSolver = null;
2294
2328
  } else if (this.activeSolver.failed) {
2295
- this.failedSolvers.push(this.activeSolver);
2329
+ this.failedSubSolvers.push(this.activeSolver);
2296
2330
  this.activeSolver = null;
2297
- this.error = this.failedSolvers.map((s) => s.error).join("\n");
2331
+ this.error = this.failedSubSolvers.map((s) => s.error).join("\n");
2298
2332
  this.failed = true;
2299
2333
  }
2300
2334
  return;
@@ -2302,7 +2336,7 @@ var IntraNodeRouteSolver = class extends BaseSolver {
2302
2336
  const unsolvedConnection = this.unsolvedConnections.pop();
2303
2337
  this.progress = this.computeProgress();
2304
2338
  if (!unsolvedConnection) {
2305
- this.solved = this.failedSolvers.length === 0;
2339
+ this.solved = this.failedSubSolvers.length === 0;
2306
2340
  return;
2307
2341
  }
2308
2342
  if (unsolvedConnection.points.length === 1) {
@@ -2373,6 +2407,20 @@ var IntraNodeRouteSolver = class extends BaseSolver {
2373
2407
  }
2374
2408
  }
2375
2409
  }
2410
+ const bounds = getBoundsFromNodeWithPortPoints(this.nodeWithPortPoints);
2411
+ const { minX, minY, maxX, maxY } = bounds;
2412
+ graphics.lines.push({
2413
+ points: [
2414
+ { x: minX, y: minY },
2415
+ { x: maxX, y: minY },
2416
+ { x: maxX, y: maxY },
2417
+ { x: minX, y: maxY },
2418
+ { x: minX, y: minY }
2419
+ ],
2420
+ strokeColor: "rgba(255, 0, 0, 0.25)",
2421
+ strokeDash: "4 4",
2422
+ layer: "border"
2423
+ });
2376
2424
  return graphics;
2377
2425
  }
2378
2426
  };
@@ -3269,6 +3317,7 @@ var CapacityNodeTargetMerger = class extends BaseSolver {
3269
3317
  width: bounds.maxX - bounds.minX,
3270
3318
  height: bounds.maxY - bounds.minY,
3271
3319
  layer: connectedNodes[0].layer,
3320
+ availableZ: connectedNodes[0].availableZ,
3272
3321
  _completelyInsideObstacle: false,
3273
3322
  _containsObstacle: true,
3274
3323
  _containsTarget: true,
@@ -3527,11 +3576,6 @@ var CapacitySegmentPointOptimizer = class extends BaseSolver {
3527
3576
  const segments = segmentIds.map(
3528
3577
  (segmentId) => this.currentMutatedSegments.get(segmentId)
3529
3578
  );
3530
- const points = segments.flatMap((s) => s.assignedPoints);
3531
- if (points.length <= 2) {
3532
- if (points.length <= 1) return 0;
3533
- return 0;
3534
- }
3535
3579
  const {
3536
3580
  numEntryExitLayerChanges,
3537
3581
  numSameLayerCrossings,
@@ -3606,10 +3650,6 @@ var CapacitySegmentPointOptimizer = class extends BaseSolver {
3606
3650
  point2Index: randomPointIndex2
3607
3651
  };
3608
3652
  }
3609
- const nodeIds = this.segmentIdToNodeIds.get(segment.nodePortSegmentId);
3610
- if (nodeIds?.some((nodeId) => this.nodesThatCantFitVias.has(nodeId))) {
3611
- return null;
3612
- }
3613
3653
  const randomPointIndex = Math.floor(
3614
3654
  this.random() * segment.assignedPoints.length
3615
3655
  );
@@ -3815,9 +3855,7 @@ var CapacitySegmentPointOptimizer = class extends BaseSolver {
3815
3855
  this.solved = true;
3816
3856
  return;
3817
3857
  }
3818
- const op = this.getRandomCombinedOperationNearNode(
3819
- this.getRandomWeightedNodeId()
3820
- );
3858
+ const op = this.getRandomCombinedOperationOnSingleNode();
3821
3859
  this.lastCreatedOperation = op;
3822
3860
  this.applyOperation(op);
3823
3861
  const {
@@ -4689,6 +4727,7 @@ var convertSrjToGraphicsObject = (srj) => {
4689
4727
  { x: routePoint.x, y: routePoint.y },
4690
4728
  { x: nextRoutePoint.x, y: nextRoutePoint.y }
4691
4729
  ],
4730
+ strokeWidth: 0.15,
4692
4731
  strokeColor: safeTransparentize(
4693
4732
  {
4694
4733
  top: "red",