@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 +2 -0
- package/dist/index.d.ts +10 -6
- package/dist/index.js +42 -34
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
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:
|
|
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
|
-
|
|
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) => ({
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1241
|
-
|
|
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.
|
|
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.
|
|
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",
|