@tscircuit/capacity-autorouter 0.0.17 → 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;
@@ -113,6 +114,12 @@ declare class CapacityMeshEdgeSolver extends BaseSolver {
113
114
  interface CapacityMeshNodeSolverOptions {
114
115
  capacityDepth?: number;
115
116
  }
117
+ interface Target {
118
+ x: number;
119
+ y: number;
120
+ connectionName: string;
121
+ availableZ: number[];
122
+ }
116
123
  declare class CapacityMeshNodeSolver extends BaseSolver {
117
124
  srj: SimpleRouteJson;
118
125
  opts: CapacityMeshNodeSolverOptions;
@@ -120,16 +127,12 @@ declare class CapacityMeshNodeSolver extends BaseSolver {
120
127
  finishedNodes: CapacityMeshNode[];
121
128
  nodeToOverlappingObstaclesMap: Map<CapacityMeshNodeId, Obstacle[]>;
122
129
  MAX_DEPTH: number;
123
- targets: Array<{
124
- x: number;
125
- y: number;
126
- connectionName: string;
127
- }>;
130
+ targets: Target[];
128
131
  constructor(srj: SimpleRouteJson, opts?: CapacityMeshNodeSolverOptions);
129
132
  _nextNodeCounter: number;
130
133
  getNextNodeId(): string;
131
134
  getCapacityFromDepth(depth: number): number;
132
- getTargetNameIfNodeContainsTarget(node: CapacityMeshNode): string | null;
135
+ getTargetIfNodeContainsTarget(node: CapacityMeshNode): Target | null;
133
136
  getOverlappingObstacles(node: CapacityMeshNode): Obstacle[];
134
137
  /**
135
138
  * Checks if the given mesh node overlaps with any obstacle.
@@ -234,6 +237,7 @@ interface NodePortSegment {
234
237
  x: number;
235
238
  y: number;
236
239
  };
240
+ availableZ: number[];
237
241
  connectionNames: string[];
238
242
  }
239
243
 
package/dist/index.js CHANGED
@@ -1093,6 +1093,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1093
1093
  width: maxWidthHeight,
1094
1094
  height: maxWidthHeight,
1095
1095
  layer: "top",
1096
+ availableZ: [0, 1],
1096
1097
  _depth: 0,
1097
1098
  _containsTarget: true,
1098
1099
  _containsObstacle: true,
@@ -1102,7 +1103,11 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1102
1103
  this.finishedNodes = [];
1103
1104
  this.nodeToOverlappingObstaclesMap = /* @__PURE__ */ new Map();
1104
1105
  this.targets = this.srj.connections.flatMap(
1105
- (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
+ }))
1106
1111
  );
1107
1112
  }
1108
1113
  unfinishedNodes;
@@ -1118,7 +1123,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1118
1123
  getCapacityFromDepth(depth) {
1119
1124
  return (this.MAX_DEPTH - depth + 1) ** 2;
1120
1125
  }
1121
- getTargetNameIfNodeContainsTarget(node) {
1126
+ getTargetIfNodeContainsTarget(node) {
1122
1127
  const overlappingObstacles = this.getOverlappingObstacles(node);
1123
1128
  for (const target of this.targets) {
1124
1129
  const targetObstacle = overlappingObstacles.find(
@@ -1126,11 +1131,11 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1126
1131
  );
1127
1132
  if (targetObstacle) {
1128
1133
  if (doRectsOverlap(node, targetObstacle)) {
1129
- return target.connectionName;
1134
+ return target;
1130
1135
  }
1131
1136
  }
1132
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) {
1133
- return target.connectionName;
1138
+ return target;
1134
1139
  }
1135
1140
  }
1136
1141
  return null;
@@ -1233,12 +1238,17 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1233
1238
  width: childNodeSize.width,
1234
1239
  height: childNodeSize.height,
1235
1240
  layer: parent.layer,
1241
+ availableZ: [0, 1],
1236
1242
  _depth: (parent._depth ?? 0) + 1,
1237
1243
  _parent: parent
1238
1244
  };
1239
1245
  childNode._containsObstacle = this.doesNodeOverlapObstacle(childNode);
1240
- childNode._targetConnectionName = this.getTargetNameIfNodeContainsTarget(childNode) ?? void 0;
1241
- 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
+ }
1242
1252
  if (childNode._containsObstacle) {
1243
1253
  childNode._completelyInsideObstacle = this.isNodeCompletelyInsideObstacle(childNode);
1244
1254
  }
@@ -1295,16 +1305,6 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1295
1305
  coordinateSystem: "cartesian",
1296
1306
  title: "Capacity Mesh Visualization"
1297
1307
  };
1298
- const allNodes = [...this.finishedNodes, ...this.unfinishedNodes];
1299
- for (const node of allNodes) {
1300
- graphics.rects.push({
1301
- center: node.center,
1302
- width: Math.max(node.width - 2, node.width * 0.8),
1303
- height: Math.max(node.height - 2, node.height * 0.8),
1304
- fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : "rgba(0,0,0,0.1)",
1305
- label: node.capacityMeshNodeId
1306
- });
1307
- }
1308
1308
  for (const obstacle of this.srj.obstacles) {
1309
1309
  graphics.rects.push({
1310
1310
  center: obstacle.center,
@@ -1315,13 +1315,24 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1315
1315
  label: "obstacle"
1316
1316
  });
1317
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
+ }
1318
1329
  this.srj.connections.forEach((connection, index) => {
1319
1330
  const color = COLORS[index % COLORS.length];
1320
1331
  for (const pt of connection.pointsToConnect) {
1321
1332
  graphics.points.push({
1322
1333
  x: pt.x,
1323
1334
  y: pt.y,
1324
- label: `conn-${index}`,
1335
+ label: `conn-${index} (${pt.layer})`,
1325
1336
  color
1326
1337
  });
1327
1338
  }
@@ -1391,11 +1402,16 @@ var CapacityEdgeToPortSegmentSolver = class extends BaseSolver {
1391
1402
  const adjNode = this.nodeMap.get(adjNodeId);
1392
1403
  if (!adjNode) continue;
1393
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;
1394
1409
  const portSegment = {
1395
1410
  capacityMeshNodeId: nodeId,
1396
1411
  start: segment.start,
1397
1412
  end: segment.end,
1398
- connectionNames: [path.connectionName]
1413
+ connectionNames: [path.connectionName],
1414
+ availableZ: mutuallyAvailableZ
1399
1415
  };
1400
1416
  nodePortSegments.push(portSegment);
1401
1417
  }
@@ -1414,7 +1430,7 @@ var CapacityEdgeToPortSegmentSolver = class extends BaseSolver {
1414
1430
  const node = this.nodeMap.get(nodeId);
1415
1431
  segments.forEach((segment) => {
1416
1432
  const isVertical = segment.start.x === segment.end.x;
1417
- const THICKNESS = 0.5 / segment.connectionNames.length;
1433
+ const THICKNESS = 0.1 / segment.connectionNames.length;
1418
1434
  for (let i = 0; i < segment.connectionNames.length; i++) {
1419
1435
  const offsetAmount = (i / (segment.connectionNames.length - 1 + 1e-6) - 0.5) * THICKNESS;
1420
1436
  const offset = {
@@ -1436,7 +1452,8 @@ var CapacityEdgeToPortSegmentSolver = class extends BaseSolver {
1436
1452
  this.colorMap[segment.connectionNames[i]],
1437
1453
  0.6
1438
1454
  ),
1439
- label: `${nodeId}: ${segment.connectionNames.join(", ")}`
1455
+ label: `${nodeId}: ${segment.connectionNames.join(", ")}
1456
+ availableZ: ${segment.availableZ.join(",")}`
1440
1457
  });
1441
1458
  }
1442
1459
  });
@@ -1537,7 +1554,7 @@ var CapacitySegmentToPointSolver = class extends BaseSolver {
1537
1554
  const center = {
1538
1555
  x: (seg.start.x + seg.end.x) / 2,
1539
1556
  y: (seg.start.y + seg.end.y) / 2,
1540
- z: 0
1557
+ z: seg.availableZ[0]
1541
1558
  };
1542
1559
  seg.assignedPoints = [
1543
1560
  { connectionName: seg.connectionNames[0], point: center }
@@ -1564,7 +1581,7 @@ var CapacitySegmentToPointSolver = class extends BaseSolver {
1564
1581
  points.push({
1565
1582
  x: candidate.start.x + dx * fraction,
1566
1583
  y: candidate.start.y + dy * fraction,
1567
- z: 0
1584
+ z: candidate.availableZ[0]
1568
1585
  });
1569
1586
  }
1570
1587
  ;
@@ -3300,6 +3317,7 @@ var CapacityNodeTargetMerger = class extends BaseSolver {
3300
3317
  width: bounds.maxX - bounds.minX,
3301
3318
  height: bounds.maxY - bounds.minY,
3302
3319
  layer: connectedNodes[0].layer,
3320
+ availableZ: connectedNodes[0].availableZ,
3303
3321
  _completelyInsideObstacle: false,
3304
3322
  _containsObstacle: true,
3305
3323
  _containsTarget: true,
@@ -3558,11 +3576,6 @@ var CapacitySegmentPointOptimizer = class extends BaseSolver {
3558
3576
  const segments = segmentIds.map(
3559
3577
  (segmentId) => this.currentMutatedSegments.get(segmentId)
3560
3578
  );
3561
- const points = segments.flatMap((s) => s.assignedPoints);
3562
- if (points.length <= 2) {
3563
- if (points.length <= 1) return 0;
3564
- return 0;
3565
- }
3566
3579
  const {
3567
3580
  numEntryExitLayerChanges,
3568
3581
  numSameLayerCrossings,
@@ -3637,10 +3650,6 @@ var CapacitySegmentPointOptimizer = class extends BaseSolver {
3637
3650
  point2Index: randomPointIndex2
3638
3651
  };
3639
3652
  }
3640
- const nodeIds = this.segmentIdToNodeIds.get(segment.nodePortSegmentId);
3641
- if (nodeIds?.some((nodeId) => this.nodesThatCantFitVias.has(nodeId))) {
3642
- return null;
3643
- }
3644
3653
  const randomPointIndex = Math.floor(
3645
3654
  this.random() * segment.assignedPoints.length
3646
3655
  );
@@ -3846,9 +3855,7 @@ var CapacitySegmentPointOptimizer = class extends BaseSolver {
3846
3855
  this.solved = true;
3847
3856
  return;
3848
3857
  }
3849
- const op = this.getRandomCombinedOperationNearNode(
3850
- this.getRandomWeightedNodeId()
3851
- );
3858
+ const op = this.getRandomCombinedOperationOnSingleNode();
3852
3859
  this.lastCreatedOperation = op;
3853
3860
  this.applyOperation(op);
3854
3861
  const {
@@ -4720,6 +4727,7 @@ var convertSrjToGraphicsObject = (srj) => {
4720
4727
  { x: routePoint.x, y: routePoint.y },
4721
4728
  { x: nextRoutePoint.x, y: nextRoutePoint.y }
4722
4729
  ],
4730
+ strokeWidth: 0.15,
4723
4731
  strokeColor: safeTransparentize(
4724
4732
  {
4725
4733
  top: "red",