@tscircuit/capacity-autorouter 0.0.20 → 0.0.22

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
@@ -125,6 +125,22 @@ function distance(p1, p2) {
125
125
  return Math.sqrt(dx * dx + dy * dy);
126
126
  }
127
127
 
128
+ // lib/utils/areNodesBordering.ts
129
+ function areNodesBordering(node1, node2) {
130
+ const n1Left = node1.center.x - node1.width / 2;
131
+ const n1Right = node1.center.x + node1.width / 2;
132
+ const n1Top = node1.center.y - node1.height / 2;
133
+ const n1Bottom = node1.center.y + node1.height / 2;
134
+ const n2Left = node2.center.x - node2.width / 2;
135
+ const n2Right = node2.center.x + node2.width / 2;
136
+ const n2Top = node2.center.y - node2.height / 2;
137
+ const n2Bottom = node2.center.y + node2.height / 2;
138
+ const epsilon = 1e-3;
139
+ const shareVerticalBorder = (Math.abs(n1Right - n2Left) < epsilon || Math.abs(n1Left - n2Right) < epsilon) && Math.min(n1Bottom, n2Bottom) - Math.max(n1Top, n2Top) >= epsilon;
140
+ const shareHorizontalBorder = (Math.abs(n1Bottom - n2Top) < epsilon || Math.abs(n1Top - n2Bottom) < epsilon) && Math.min(n1Right, n2Right) - Math.max(n1Left, n2Left) >= epsilon;
141
+ return shareVerticalBorder || shareHorizontalBorder;
142
+ }
143
+
128
144
  // lib/solvers/CapacityMeshSolver/CapacityMeshEdgeSolver.ts
129
145
  var CapacityMeshEdgeSolver = class extends BaseSolver {
130
146
  constructor(nodes) {
@@ -140,7 +156,7 @@ var CapacityMeshEdgeSolver = class extends BaseSolver {
140
156
  this.edges = [];
141
157
  for (let i = 0; i < this.nodes.length; i++) {
142
158
  for (let j = i + 1; j < this.nodes.length; j++) {
143
- if (this.areNodesBordering(this.nodes[i], this.nodes[j])) {
159
+ if (!(this.nodes[i]._strawNode && this.nodes[j]._strawNode) && areNodesBordering(this.nodes[i], this.nodes[j]) && this.doNodesHaveSharedLayer(this.nodes[i], this.nodes[j])) {
144
160
  this.edges.push({
145
161
  capacityMeshEdgeId: this.getNextCapacityMeshEdgeId(),
146
162
  nodeIds: [
@@ -180,30 +196,35 @@ var CapacityMeshEdgeSolver = class extends BaseSolver {
180
196
  }
181
197
  this.solved = true;
182
198
  }
183
- areNodesBordering(node1, node2) {
184
- const n1Left = node1.center.x - node1.width / 2;
185
- const n1Right = node1.center.x + node1.width / 2;
186
- const n1Top = node1.center.y - node1.height / 2;
187
- const n1Bottom = node1.center.y + node1.height / 2;
188
- const n2Left = node2.center.x - node2.width / 2;
189
- const n2Right = node2.center.x + node2.width / 2;
190
- const n2Top = node2.center.y - node2.height / 2;
191
- const n2Bottom = node2.center.y + node2.height / 2;
192
- const epsilon = 1e-3;
193
- const shareVerticalBorder = (Math.abs(n1Right - n2Left) < epsilon || Math.abs(n1Left - n2Right) < epsilon) && Math.min(n1Bottom, n2Bottom) - Math.max(n1Top, n2Top) >= epsilon;
194
- const shareHorizontalBorder = (Math.abs(n1Bottom - n2Top) < epsilon || Math.abs(n1Top - n2Bottom) < epsilon) && Math.min(n1Right, n2Right) - Math.max(n1Left, n2Left) >= epsilon;
195
- return shareVerticalBorder || shareHorizontalBorder;
199
+ doNodesHaveSharedLayer(node1, node2) {
200
+ return node1.availableZ.some((z) => node2.availableZ.includes(z));
196
201
  }
197
202
  visualize() {
198
203
  const graphics = {
199
204
  lines: [],
200
205
  points: [],
201
- rects: this.nodes.map((node) => ({
202
- width: Math.max(node.width - 2, node.width * 0.8),
203
- height: Math.max(node.height - 2, node.height * 0.8),
204
- center: node.center,
205
- fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : node._containsTarget ? "rgba(0,255,0,0.2)" : "rgba(0,0,0,0.1)"
206
- })),
206
+ rects: this.nodes.map((node) => {
207
+ const lowestZ = Math.min(...node.availableZ);
208
+ return {
209
+ width: Math.max(node.width - 2, node.width * 0.8),
210
+ height: Math.max(node.height - 2, node.height * 0.8),
211
+ center: {
212
+ x: node.center.x + lowestZ * node.width * 0.05,
213
+ y: node.center.y - lowestZ * node.width * 0.05
214
+ },
215
+ fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : {
216
+ "0,1": "rgba(0,0,0,0.1)",
217
+ "0": "rgba(0,200,200, 0.1)",
218
+ "1": "rgba(0,0,200, 0.1)"
219
+ }[node.availableZ.join(",")] ?? "rgba(0,200,200,0.1)",
220
+ label: [
221
+ node.capacityMeshNodeId,
222
+ `availableZ: ${node.availableZ.join(",")}`,
223
+ `target? ${node._containsTarget ?? false}`,
224
+ `obs? ${node._containsObstacle ?? false}`
225
+ ].join("\n")
226
+ };
227
+ }),
207
228
  circles: []
208
229
  };
209
230
  for (const edge of this.edges) {
@@ -214,8 +235,19 @@ var CapacityMeshEdgeSolver = class extends BaseSolver {
214
235
  (node) => node.capacityMeshNodeId === edge.nodeIds[1]
215
236
  );
216
237
  if (node1?.center && node2?.center) {
238
+ const lowestZ1 = Math.min(...node1.availableZ);
239
+ const lowestZ2 = Math.min(...node2.availableZ);
240
+ const nodeCenter1Adj = {
241
+ x: node1.center.x + lowestZ1 * node1.width * 0.05,
242
+ y: node1.center.y - lowestZ1 * node1.width * 0.05
243
+ };
244
+ const nodeCenter2Adj = {
245
+ x: node2.center.x + lowestZ2 * node2.width * 0.05,
246
+ y: node2.center.y - lowestZ2 * node2.width * 0.05
247
+ };
217
248
  graphics.lines.push({
218
- points: [node1.center, node2.center]
249
+ points: [nodeCenter1Adj, nodeCenter2Adj],
250
+ strokeDash: node1.availableZ.join(",") === node2.availableZ.join(",") ? void 0 : "10 5"
219
251
  });
220
252
  }
221
253
  }
@@ -1074,7 +1106,14 @@ function doRectsOverlap(rect1, rect2) {
1074
1106
  return rect1Left <= rect2Right && rect1Right >= rect2Left && rect1Top <= rect2Bottom && rect1Bottom >= rect2Top;
1075
1107
  }
1076
1108
 
1077
- // lib/solvers/CapacityMeshSolver/CapacityMeshNodeSolver.ts
1109
+ // lib/utils/mapLayerNameToZ.ts
1110
+ var mapLayerNameToZ = (layerName, layerCount) => {
1111
+ if (layerName === "top") return 0;
1112
+ if (layerName === "bottom") return layerCount - 1;
1113
+ return parseInt(layerName.slice(5));
1114
+ };
1115
+
1116
+ // lib/solvers/CapacityMeshSolver/CapacityMeshNodeSolver1.ts
1078
1117
  var CapacityMeshNodeSolver = class extends BaseSolver {
1079
1118
  constructor(srj, opts = {}) {
1080
1119
  super();
@@ -1082,6 +1121,16 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1082
1121
  this.opts = opts;
1083
1122
  this.MAX_DEPTH = opts?.capacityDepth ?? this.MAX_DEPTH;
1084
1123
  this.MAX_ITERATIONS = 1e5;
1124
+ this.layerCount = srj.layerCount ?? 2;
1125
+ for (const obstacle of srj.obstacles) {
1126
+ if (!obstacle.zLayers) {
1127
+ const zLayers = [];
1128
+ for (const layer of obstacle.layers) {
1129
+ zLayers.push(mapLayerNameToZ(layer, srj.layerCount));
1130
+ }
1131
+ obstacle.zLayers = zLayers;
1132
+ }
1133
+ }
1085
1134
  const boundsCenter = {
1086
1135
  x: (srj.bounds.minX + srj.bounds.maxX) / 2,
1087
1136
  y: (srj.bounds.minY + srj.bounds.maxY) / 2
@@ -1106,7 +1155,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1106
1155
  }
1107
1156
  ];
1108
1157
  this.finishedNodes = [];
1109
- this.nodeToOverlappingObstaclesMap = /* @__PURE__ */ new Map();
1158
+ this.nodeToXYOverlappingObstaclesMap = /* @__PURE__ */ new Map();
1110
1159
  this.targets = this.srj.connections.flatMap(
1111
1160
  (c) => c.pointsToConnect.map((p) => ({
1112
1161
  ...p,
@@ -1117,7 +1166,8 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1117
1166
  }
1118
1167
  unfinishedNodes;
1119
1168
  finishedNodes;
1120
- nodeToOverlappingObstaclesMap;
1169
+ nodeToXYOverlappingObstaclesMap;
1170
+ layerCount;
1121
1171
  // targetObstacleMap: Record<string, { obstacle: Obstacle, node: CapacityMeshNode }>
1122
1172
  MAX_DEPTH = 4;
1123
1173
  targets;
@@ -1129,8 +1179,9 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1129
1179
  return (this.MAX_DEPTH - depth + 1) ** 2;
1130
1180
  }
1131
1181
  getTargetIfNodeContainsTarget(node) {
1132
- const overlappingObstacles = this.getOverlappingObstacles(node);
1182
+ const overlappingObstacles = this.getXYOverlappingObstacles(node);
1133
1183
  for (const target of this.targets) {
1184
+ if (!target.availableZ.some((z) => node.availableZ.includes(z))) continue;
1134
1185
  const targetObstacle = overlappingObstacles.find(
1135
1186
  (o) => isPointInRect(target, o)
1136
1187
  );
@@ -1145,8 +1196,8 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1145
1196
  }
1146
1197
  return null;
1147
1198
  }
1148
- getOverlappingObstacles(node) {
1149
- const cachedObstacles = this.nodeToOverlappingObstaclesMap.get(
1199
+ getXYOverlappingObstacles(node) {
1200
+ const cachedObstacles = this.nodeToXYOverlappingObstaclesMap.get(
1150
1201
  node.capacityMeshNodeId
1151
1202
  );
1152
1203
  if (cachedObstacles) {
@@ -1157,7 +1208,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1157
1208
  const nodeRight = node.center.x + node.width / 2;
1158
1209
  const nodeTop = node.center.y - node.height / 2;
1159
1210
  const nodeBottom = node.center.y + node.height / 2;
1160
- const obstacles = node._parent ? this.getOverlappingObstacles(node._parent) : this.srj.obstacles;
1211
+ const obstacles = node._parent ? this.getXYOverlappingObstacles(node._parent) : this.srj.obstacles;
1161
1212
  for (const obstacle of obstacles) {
1162
1213
  const obsLeft = obstacle.center.x - obstacle.width / 2;
1163
1214
  const obsRight = obstacle.center.x + obstacle.width / 2;
@@ -1165,20 +1216,38 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1165
1216
  const obsBottom = obstacle.center.y + obstacle.height / 2;
1166
1217
  if (nodeRight >= obsLeft && nodeLeft <= obsRight && nodeBottom >= obsTop && nodeTop <= obsBottom) {
1167
1218
  overlappingObstacles.push(obstacle);
1219
+ continue;
1220
+ }
1221
+ if (nodeLeft >= obsLeft && nodeRight <= obsRight && nodeTop >= obsTop && nodeBottom <= obsBottom) {
1222
+ overlappingObstacles.push(obstacle);
1223
+ continue;
1224
+ }
1225
+ if (obsLeft >= nodeLeft && obsRight <= nodeRight && obsTop >= nodeTop && obsBottom <= nodeBottom) {
1226
+ overlappingObstacles.push(obstacle);
1168
1227
  }
1169
1228
  }
1170
- this.nodeToOverlappingObstaclesMap.set(
1229
+ this.nodeToXYOverlappingObstaclesMap.set(
1171
1230
  node.capacityMeshNodeId,
1172
1231
  overlappingObstacles
1173
1232
  );
1174
1233
  return overlappingObstacles;
1175
1234
  }
1235
+ getXYZOverlappingObstacles(node) {
1236
+ const xyOverlappingObstacles = this.getXYOverlappingObstacles(node);
1237
+ const xyzOverlappingObstacles = [];
1238
+ for (const obstacle of xyOverlappingObstacles) {
1239
+ if (node.availableZ.some((z) => obstacle.zLayers.includes(z))) {
1240
+ xyzOverlappingObstacles.push(obstacle);
1241
+ }
1242
+ }
1243
+ return xyzOverlappingObstacles;
1244
+ }
1176
1245
  /**
1177
1246
  * Checks if the given mesh node overlaps with any obstacle.
1178
1247
  * We treat both obstacles and nodes as axis‐aligned rectangles.
1179
1248
  */
1180
1249
  doesNodeOverlapObstacle(node) {
1181
- const overlappingObstacles = this.getOverlappingObstacles(node);
1250
+ const overlappingObstacles = this.getXYZOverlappingObstacles(node);
1182
1251
  if (overlappingObstacles.length > 0) {
1183
1252
  return true;
1184
1253
  }
@@ -1195,7 +1264,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1195
1264
  * Checks if the entire node is contained within any obstacle.
1196
1265
  */
1197
1266
  isNodeCompletelyInsideObstacle(node) {
1198
- const overlappingObstacles = this.getOverlappingObstacles(node);
1267
+ const overlappingObstacles = this.getXYZOverlappingObstacles(node);
1199
1268
  const nodeLeft = node.center.x - node.width / 2;
1200
1269
  const nodeRight = node.center.x + node.width / 2;
1201
1270
  const nodeTop = node.center.y - node.height / 2;
@@ -1209,9 +1278,6 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1209
1278
  return true;
1210
1279
  }
1211
1280
  }
1212
- if (nodeRight < this.srj.bounds.minX || nodeLeft > this.srj.bounds.maxX || nodeBottom < this.srj.bounds.minY || nodeTop > this.srj.bounds.maxY) {
1213
- return true;
1214
- }
1215
1281
  return false;
1216
1282
  }
1217
1283
  getChildNodes(parent) {
@@ -1263,7 +1329,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1263
1329
  }
1264
1330
  return childNodes;
1265
1331
  }
1266
- shouldNodeBeSubdivided(node) {
1332
+ shouldNodeBeXYSubdivided(node) {
1267
1333
  if (node._depth >= this.MAX_DEPTH) return false;
1268
1334
  if (node._containsTarget) return true;
1269
1335
  if (node._containsObstacle && !node._completelyInsideObstacle) return true;
@@ -1279,7 +1345,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1279
1345
  const finishedNewNodes = [];
1280
1346
  const unfinishedNewNodes = [];
1281
1347
  for (const newNode of newNodes) {
1282
- const shouldBeSubdivided = this.shouldNodeBeSubdivided(newNode);
1348
+ const shouldBeSubdivided = this.shouldNodeBeXYSubdivided(newNode);
1283
1349
  if (shouldBeSubdivided) {
1284
1350
  unfinishedNewNodes.push(newNode);
1285
1351
  } else if (!shouldBeSubdivided && !newNode._containsObstacle) {
@@ -1315,22 +1381,37 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1315
1381
  center: obstacle.center,
1316
1382
  width: obstacle.width,
1317
1383
  height: obstacle.height,
1318
- fill: "rgba(255,0,0,0.3)",
1384
+ fill: obstacle.zLayers?.length === 1 && obstacle.zLayers?.includes(1) ? "rgba(0,0,255,0.3)" : "rgba(255,0,0,0.3)",
1319
1385
  stroke: "red",
1320
- label: "obstacle"
1386
+ label: ["obstacle", `z: ${obstacle.zLayers.join(",")}`].join("\n")
1321
1387
  });
1322
1388
  }
1323
1389
  const allNodes = [...this.finishedNodes, ...this.unfinishedNodes];
1324
1390
  for (const node of allNodes) {
1391
+ const lowestZ = Math.min(...node.availableZ);
1392
+ const isNextToBeProcessed = this.unfinishedNodes.length > 0 && node === this.unfinishedNodes[this.unfinishedNodes.length - 1];
1325
1393
  graphics.rects.push({
1326
- center: node.center,
1394
+ center: {
1395
+ x: node.center.x + lowestZ * node.width * 0.05,
1396
+ y: node.center.y - lowestZ * node.width * 0.05
1397
+ },
1327
1398
  width: Math.max(node.width - 2, node.width * 0.8),
1328
1399
  height: Math.max(node.height - 2, node.height * 0.8),
1329
- fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : "rgba(0,0,0,0.1)",
1330
- label: `${node.capacityMeshNodeId}
1331
- availableZ: ${node.availableZ.join(",")}`
1400
+ fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : {
1401
+ "0,1": "rgba(0,0,0,0.1)",
1402
+ "0": "rgba(0,200,200, 0.1)",
1403
+ "1": "rgba(0,0,200, 0.1)"
1404
+ }[node.availableZ.join(",")] ?? "rgba(0,200,200,0.1)",
1405
+ stroke: isNextToBeProcessed ? "rgba(255,165,0,0.5)" : void 0,
1406
+ label: [
1407
+ node.capacityMeshNodeId,
1408
+ `availableZ: ${node.availableZ.join(",")}`,
1409
+ `target? ${node._containsTarget ?? false}`,
1410
+ `obs? ${node._containsObstacle ?? false}`
1411
+ ].join("\n")
1332
1412
  });
1333
1413
  }
1414
+ graphics.rects.sort((a, b) => a.center.y - b.center.y);
1334
1415
  this.srj.connections.forEach((connection, index) => {
1335
1416
  const color = COLORS[index % COLORS.length];
1336
1417
  for (const pt of connection.pointsToConnect) {
@@ -1346,6 +1427,146 @@ availableZ: ${node.availableZ.join(",")}`
1346
1427
  }
1347
1428
  };
1348
1429
 
1430
+ // lib/solvers/CapacityMeshSolver/CapacityMeshNodeSolver2_NodesUnderObstacles.ts
1431
+ var CapacityMeshNodeSolver2_NodeUnderObstacle = class extends CapacityMeshNodeSolver {
1432
+ constructor(srj, opts = {}) {
1433
+ super(srj, opts);
1434
+ this.srj = srj;
1435
+ this.opts = opts;
1436
+ }
1437
+ isNodeCompletelyOutsideBounds(node) {
1438
+ return node.center.x + node.width / 2 < this.srj.bounds.minX || node.center.x - node.width / 2 > this.srj.bounds.maxX || node.center.y + node.height / 2 < this.srj.bounds.minY || node.center.y - node.height / 2 > this.srj.bounds.maxY;
1439
+ }
1440
+ isNodePartiallyOutsideBounds(node) {
1441
+ return node.center.x - node.width / 2 < this.srj.bounds.minX || node.center.x + node.width / 2 > this.srj.bounds.maxX || node.center.y - node.height / 2 < this.srj.bounds.minY || node.center.y + node.height / 2 > this.srj.bounds.maxY;
1442
+ }
1443
+ createChildNodeAtPosition(parent, opts) {
1444
+ const childNode = {
1445
+ capacityMeshNodeId: this.getNextNodeId(),
1446
+ center: opts.center,
1447
+ width: opts.width,
1448
+ height: opts.height,
1449
+ layer: parent.layer,
1450
+ availableZ: opts.availableZ,
1451
+ _depth: opts._depth ?? (parent._depth ?? 0) + 1,
1452
+ _parent: parent
1453
+ };
1454
+ const overlappingObstacles = this.getXYZOverlappingObstacles(childNode);
1455
+ childNode._containsObstacle = overlappingObstacles.length > 0 || this.isNodePartiallyOutsideBounds(childNode);
1456
+ const target = this.getTargetIfNodeContainsTarget(childNode);
1457
+ if (target) {
1458
+ childNode._targetConnectionName = target.connectionName;
1459
+ childNode._containsTarget = true;
1460
+ }
1461
+ if (childNode._containsObstacle) {
1462
+ childNode._completelyInsideObstacle = this.isNodeCompletelyInsideObstacle(childNode);
1463
+ }
1464
+ return childNode;
1465
+ }
1466
+ getZSubdivisionChildNodes(node) {
1467
+ if (node.availableZ.length === 1) return [];
1468
+ const childNodes = [];
1469
+ const otherZBlocks = [[0], [1]];
1470
+ for (const zBlock of otherZBlocks) {
1471
+ const childNode = this.createChildNodeAtPosition(node, {
1472
+ center: { ...node.center },
1473
+ width: node.width,
1474
+ height: node.height,
1475
+ availableZ: zBlock,
1476
+ // z-subdivision doesn't count towards depth, should be same as parent
1477
+ _depth: node._depth
1478
+ });
1479
+ if (this.isNodeCompletelyOutsideBounds(childNode)) {
1480
+ continue;
1481
+ }
1482
+ childNodes.push(childNode);
1483
+ }
1484
+ return childNodes;
1485
+ }
1486
+ getChildNodes(parent) {
1487
+ if (parent._depth >= this.MAX_DEPTH) return [];
1488
+ const childNodes = [];
1489
+ const childNodeSize = { width: parent.width / 2, height: parent.height / 2 };
1490
+ const childNodePositions = [
1491
+ {
1492
+ x: parent.center.x - childNodeSize.width / 2,
1493
+ y: parent.center.y - childNodeSize.height / 2
1494
+ },
1495
+ {
1496
+ x: parent.center.x + childNodeSize.width / 2,
1497
+ y: parent.center.y - childNodeSize.height / 2
1498
+ },
1499
+ {
1500
+ x: parent.center.x - childNodeSize.width / 2,
1501
+ y: parent.center.y + childNodeSize.height / 2
1502
+ },
1503
+ {
1504
+ x: parent.center.x + childNodeSize.width / 2,
1505
+ y: parent.center.y + childNodeSize.height / 2
1506
+ }
1507
+ ];
1508
+ for (const position of childNodePositions) {
1509
+ const childNode = this.createChildNodeAtPosition(parent, {
1510
+ center: position,
1511
+ width: childNodeSize.width,
1512
+ height: childNodeSize.height,
1513
+ availableZ: parent.availableZ
1514
+ });
1515
+ if (this.isNodeCompletelyOutsideBounds(childNode)) {
1516
+ continue;
1517
+ }
1518
+ childNodes.push(childNode);
1519
+ }
1520
+ return childNodes;
1521
+ }
1522
+ shouldNodeBeXYSubdivided(node) {
1523
+ if (node._depth >= this.MAX_DEPTH) return false;
1524
+ if (node._containsTarget) return true;
1525
+ if (node.availableZ.length === 1 && node._depth <= this.MAX_DEPTH)
1526
+ return true;
1527
+ if (node._containsObstacle && !node._completelyInsideObstacle) return true;
1528
+ return false;
1529
+ }
1530
+ _step() {
1531
+ const nextNode = this.unfinishedNodes.pop();
1532
+ if (!nextNode) {
1533
+ this.solved = true;
1534
+ return;
1535
+ }
1536
+ const childNodes = this.getChildNodes(nextNode);
1537
+ const finishedNewNodes = [];
1538
+ const unfinishedNewNodes = [];
1539
+ for (const childNode of childNodes) {
1540
+ const shouldBeXYSubdivided = this.shouldNodeBeXYSubdivided(childNode);
1541
+ const shouldBeZSubdivided = childNode.availableZ.length > 1 && !shouldBeXYSubdivided && childNode._containsObstacle;
1542
+ if (shouldBeXYSubdivided) {
1543
+ unfinishedNewNodes.push(childNode);
1544
+ } else if (!shouldBeXYSubdivided && !childNode._containsObstacle) {
1545
+ finishedNewNodes.push(childNode);
1546
+ } else if (!shouldBeXYSubdivided && childNode._containsTarget) {
1547
+ if (shouldBeZSubdivided) {
1548
+ const zSubNodes = this.getZSubdivisionChildNodes(childNode);
1549
+ finishedNewNodes.push(
1550
+ ...zSubNodes.filter(
1551
+ (n) => n._containsTarget || !n._containsObstacle
1552
+ )
1553
+ );
1554
+ } else {
1555
+ finishedNewNodes.push(childNode);
1556
+ }
1557
+ } else if (shouldBeZSubdivided) {
1558
+ finishedNewNodes.push(
1559
+ ...this.getZSubdivisionChildNodes(childNode).filter(
1560
+ (zSubNode) => !zSubNode._containsObstacle
1561
+ )
1562
+ );
1563
+ }
1564
+ }
1565
+ this.unfinishedNodes.push(...unfinishedNewNodes);
1566
+ this.finishedNodes.push(...finishedNewNodes);
1567
+ }
1568
+ };
1569
+
1349
1570
  // lib/solvers/CapacityMeshSolver/getNodeEdgeMap.ts
1350
1571
  function getNodeEdgeMap(edges) {
1351
1572
  const nodeEdgeMap = /* @__PURE__ */ new Map();
@@ -2816,294 +3037,94 @@ var HighDensitySolver = class extends BaseSolver {
2816
3037
  }
2817
3038
  };
2818
3039
 
2819
- // lib/solvers/CapacityPathingSolver/CapacityPathingSolver.ts
2820
- var CapacityPathingSolver = class extends BaseSolver {
2821
- connectionsWithNodes;
2822
- usedNodeCapacityMap;
2823
- simpleRouteJson;
2824
- nodes;
2825
- edges;
2826
- GREEDY_MULTIPLIER = 1.1;
2827
- nodeMap;
2828
- nodeEdgeMap;
2829
- connectionNameToGoalNodeIds;
2830
- colorMap;
2831
- maxDepthOfNodes;
2832
- activeCandidateStraightLineDistance;
2833
- hyperParameters;
2834
- constructor({
2835
- simpleRouteJson,
2836
- nodes,
2837
- edges,
2838
- colorMap,
2839
- MAX_ITERATIONS = 1e6,
2840
- hyperParameters = {}
2841
- }) {
2842
- super();
2843
- this.MAX_ITERATIONS = MAX_ITERATIONS;
2844
- this.simpleRouteJson = simpleRouteJson;
2845
- this.nodes = nodes;
2846
- this.edges = edges;
2847
- this.colorMap = colorMap ?? {};
2848
- const { connectionsWithNodes, connectionNameToGoalNodeIds } = this.getConnectionsWithNodes();
2849
- this.connectionsWithNodes = connectionsWithNodes;
2850
- this.connectionNameToGoalNodeIds = connectionNameToGoalNodeIds;
2851
- this.hyperParameters = hyperParameters;
2852
- this.usedNodeCapacityMap = new Map(
2853
- this.nodes.map((node) => [node.capacityMeshNodeId, 0])
2854
- );
2855
- this.nodeMap = new Map(
2856
- this.nodes.map((node) => [node.capacityMeshNodeId, node])
2857
- );
2858
- this.nodeEdgeMap = getNodeEdgeMap(this.edges);
2859
- this.maxDepthOfNodes = Math.max(
2860
- ...this.nodes.map((node) => node._depth ?? 0)
2861
- );
2862
- }
2863
- getTotalCapacity(node) {
2864
- const depth = node._depth ?? 0;
2865
- return (this.maxDepthOfNodes - depth + 1) ** 2;
3040
+ // node_modules/circuit-json-to-connectivity-map/dist/index.js
3041
+ var ConnectivityMap = class {
3042
+ netMap;
3043
+ idToNetMap;
3044
+ constructor(netMap) {
3045
+ this.netMap = netMap;
3046
+ this.idToNetMap = {};
3047
+ for (const [netId, ids] of Object.entries(netMap)) {
3048
+ for (const id of ids) {
3049
+ this.idToNetMap[id] = netId;
3050
+ }
3051
+ }
2866
3052
  }
2867
- getConnectionsWithNodes() {
2868
- const connectionsWithNodes = [];
2869
- const nodesWithTargets = this.nodes.filter((node) => node._containsTarget);
2870
- const connectionNameToGoalNodeIds = /* @__PURE__ */ new Map();
2871
- for (const connection of this.simpleRouteJson.connections) {
2872
- const nodesForConnection = [];
2873
- for (const point of connection.pointsToConnect) {
2874
- let closestNode = this.nodes[0];
2875
- let minDistance = Number.MAX_VALUE;
2876
- for (const node of nodesWithTargets) {
2877
- const distance3 = Math.sqrt(
2878
- (node.center.x - point.x) ** 2 + (node.center.y - point.y) ** 2
2879
- );
2880
- if (distance3 < minDistance) {
2881
- minDistance = distance3;
2882
- closestNode = node;
3053
+ addConnections(connections) {
3054
+ for (const connection of connections) {
3055
+ const existingNets = /* @__PURE__ */ new Set();
3056
+ for (const id of connection) {
3057
+ const existingNetId = this.idToNetMap[id];
3058
+ if (existingNetId) {
3059
+ existingNets.add(existingNetId);
3060
+ }
3061
+ }
3062
+ let targetNetId;
3063
+ if (existingNets.size === 0) {
3064
+ targetNetId = `connectivity_net${Object.keys(this.netMap).length}`;
3065
+ this.netMap[targetNetId] = [];
3066
+ } else if (existingNets.size === 1) {
3067
+ targetNetId = existingNets.values().next().value ?? `connectivity_net${Object.keys(this.netMap).length}`;
3068
+ } else {
3069
+ targetNetId = existingNets.values().next().value ?? `connectivity_net${Object.keys(this.netMap).length}`;
3070
+ for (const netId of existingNets) {
3071
+ if (netId !== targetNetId) {
3072
+ this.netMap[targetNetId].push(...this.netMap[netId]);
3073
+ this.netMap[netId] = this.netMap[targetNetId];
3074
+ for (const id of this.netMap[targetNetId]) {
3075
+ this.idToNetMap[id] = targetNetId;
3076
+ }
2883
3077
  }
2884
3078
  }
2885
- nodesForConnection.push(closestNode);
2886
3079
  }
2887
- if (nodesForConnection.length < 2) {
2888
- throw new Error(
2889
- `Not enough nodes for connection "${connection.name}", only ${nodesForConnection.length} found`
2890
- );
3080
+ for (const id of connection) {
3081
+ if (!this.netMap[targetNetId].includes(id)) {
3082
+ this.netMap[targetNetId].push(id);
3083
+ }
3084
+ this.idToNetMap[id] = targetNetId;
2891
3085
  }
2892
- connectionNameToGoalNodeIds.set(
2893
- connection.name,
2894
- nodesForConnection.map((n) => n.capacityMeshNodeId)
2895
- );
2896
- connectionsWithNodes.push({
2897
- connection,
2898
- nodes: nodesForConnection,
2899
- pathFound: false
2900
- });
2901
3086
  }
2902
- return { connectionsWithNodes, connectionNameToGoalNodeIds };
2903
- }
2904
- currentConnectionIndex = 0;
2905
- candidates;
2906
- visitedNodes;
2907
- computeG(prevCandidate, node, endGoal) {
2908
- return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node);
2909
- }
2910
- computeH(prevCandidate, node, endGoal) {
2911
- return this.getDistanceBetweenNodes(node, endGoal);
2912
3087
  }
2913
- getBacktrackedPath(candidate) {
2914
- const path = [];
2915
- let currentCandidate = candidate;
2916
- while (currentCandidate) {
2917
- path.push(currentCandidate.node);
2918
- currentCandidate = currentCandidate.prevCandidate;
2919
- }
2920
- return path;
3088
+ getIdsConnectedToNet(netId) {
3089
+ return this.netMap[netId] || [];
2921
3090
  }
2922
- getNeighboringNodes(node) {
2923
- return this.nodeEdgeMap.get(node.capacityMeshNodeId).flatMap(
2924
- (edge) => edge.nodeIds.filter((n) => n !== node.capacityMeshNodeId)
2925
- ).map((n) => this.nodeMap.get(n));
3091
+ getNetConnectedToId(id) {
3092
+ return this.idToNetMap[id];
2926
3093
  }
2927
- getCapacityPaths() {
2928
- const capacityPaths = [];
2929
- for (const connection of this.connectionsWithNodes) {
2930
- const path = connection.path;
2931
- if (path) {
2932
- capacityPaths.push({
2933
- capacityPathId: connection.connection.name,
2934
- connectionName: connection.connection.name,
2935
- nodeIds: path.map((node) => node.capacityMeshNodeId)
2936
- });
2937
- }
2938
- }
2939
- return capacityPaths;
2940
- }
2941
- doesNodeHaveCapacityForTrace(node) {
2942
- const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
2943
- const totalCapacity = this.getTotalCapacity(node);
2944
- return usedCapacity < totalCapacity;
2945
- }
2946
- canTravelThroughObstacle(node, connectionName) {
2947
- const goalNodeIds = this.connectionNameToGoalNodeIds.get(connectionName);
2948
- return goalNodeIds?.includes(node.capacityMeshNodeId) ?? false;
2949
- }
2950
- getDistanceBetweenNodes(A, B) {
2951
- return Math.sqrt(
2952
- (A.center.x - B.center.x) ** 2 + (A.center.y - B.center.y) ** 2
2953
- );
2954
- }
2955
- reduceCapacityAlongPath(nextConnection) {
2956
- for (const node of nextConnection.path ?? []) {
2957
- this.usedNodeCapacityMap.set(
2958
- node.capacityMeshNodeId,
2959
- this.usedNodeCapacityMap.get(node.capacityMeshNodeId) + 1
2960
- );
2961
- }
2962
- }
2963
- isConnectedToEndGoal(node, endGoal) {
2964
- return this.nodeEdgeMap.get(node.capacityMeshNodeId).some((edge) => edge.nodeIds.includes(endGoal.capacityMeshNodeId));
3094
+ areIdsConnected(id1, id2) {
3095
+ if (id1 === id2) return true;
3096
+ const netId1 = this.getNetConnectedToId(id1);
3097
+ if (!netId1) return false;
3098
+ const netId2 = this.getNetConnectedToId(id2);
3099
+ if (!netId2) return false;
3100
+ return netId1 === netId2 || netId2 === id1 || netId2 === id1;
2965
3101
  }
2966
- _step() {
2967
- const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
2968
- if (!nextConnection) {
2969
- this.solved = true;
2970
- return;
2971
- }
2972
- const [start, end] = nextConnection.nodes;
2973
- if (!this.candidates) {
2974
- this.candidates = [{ prevCandidate: null, node: start, f: 0, g: 0, h: 0 }];
2975
- this.visitedNodes = /* @__PURE__ */ new Set([start.capacityMeshNodeId]);
2976
- this.activeCandidateStraightLineDistance = distance(
2977
- start.center,
2978
- end.center
2979
- );
2980
- }
2981
- this.candidates.sort((a, b) => a.f - b.f);
2982
- const currentCandidate = this.candidates.shift();
2983
- if (!currentCandidate) {
2984
- console.error(
2985
- `Ran out of candidates on connection ${nextConnection.connection.name}`
2986
- );
2987
- this.currentConnectionIndex++;
2988
- this.candidates = null;
2989
- this.visitedNodes = null;
2990
- return;
2991
- }
2992
- if (this.isConnectedToEndGoal(currentCandidate.node, end)) {
2993
- nextConnection.path = this.getBacktrackedPath({
2994
- prevCandidate: currentCandidate,
2995
- node: end,
2996
- f: 0,
2997
- g: 0,
2998
- h: 0
2999
- });
3000
- this.reduceCapacityAlongPath(nextConnection);
3001
- this.currentConnectionIndex++;
3002
- this.candidates = null;
3003
- this.visitedNodes = null;
3004
- return;
3005
- }
3006
- const neighborNodes = this.getNeighboringNodes(currentCandidate.node);
3007
- for (const neighborNode of neighborNodes) {
3008
- if (this.visitedNodes?.has(neighborNode.capacityMeshNodeId)) {
3009
- continue;
3010
- }
3011
- if (!this.doesNodeHaveCapacityForTrace(neighborNode)) {
3012
- continue;
3102
+ areAllIdsConnected(ids) {
3103
+ const netId = this.getNetConnectedToId(ids[0]);
3104
+ for (const id of ids) {
3105
+ const nextNetId = this.getNetConnectedToId(id);
3106
+ if (nextNetId === void 0) {
3107
+ return false;
3013
3108
  }
3014
- const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
3015
- if (neighborNode._containsObstacle && !this.canTravelThroughObstacle(neighborNode, connectionName)) {
3016
- continue;
3109
+ if (nextNetId !== netId) {
3110
+ return false;
3017
3111
  }
3018
- const g = this.computeG(currentCandidate, neighborNode, end);
3019
- const h = this.computeH(currentCandidate, neighborNode, end);
3020
- const f = g + h * this.GREEDY_MULTIPLIER;
3021
- const newCandidate = {
3022
- prevCandidate: currentCandidate,
3023
- node: neighborNode,
3024
- f,
3025
- g,
3026
- h
3027
- };
3028
- this.candidates.push(newCandidate);
3029
3112
  }
3030
- this.visitedNodes.add(currentCandidate.node.capacityMeshNodeId);
3113
+ return true;
3031
3114
  }
3032
- visualize() {
3033
- const graphics = {
3034
- lines: [],
3035
- points: [],
3036
- rects: [],
3037
- circles: []
3038
- };
3039
- if (this.connectionsWithNodes) {
3040
- for (let i = 0; i < this.connectionsWithNodes.length; i++) {
3041
- const conn = this.connectionsWithNodes[i];
3042
- if (conn.path && conn.path.length > 0) {
3043
- const pathPoints = conn.path.map(({ center: { x, y }, width }) => ({
3044
- // slight offset to allow viewing overlapping paths
3045
- x: x + (i % 10 + i % 19) * (0.01 * width),
3046
- y: y + (i % 10 + i % 19) * (0.01 * width)
3047
- }));
3048
- graphics.lines.push({
3049
- points: pathPoints,
3050
- strokeColor: this.colorMap[conn.connection.name]
3051
- });
3052
- }
3053
- }
3054
- }
3055
- for (const node of this.nodes) {
3056
- graphics.rects.push({
3057
- center: node.center,
3058
- width: Math.max(node.width - 2, node.width * 0.8),
3059
- height: Math.max(node.height - 2, node.height * 0.8),
3060
- fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : "rgba(0,0,0,0.1)",
3061
- label: `${node.capacityMeshNodeId}
3062
- ${this.usedNodeCapacityMap.get(node.capacityMeshNodeId)}/${this.getTotalCapacity(node).toFixed(2)}
3063
- ${node.width.toFixed(2)}x${node.height.toFixed(2)}`
3064
- });
3065
- }
3066
- if (this.connectionsWithNodes) {
3067
- for (const conn of this.connectionsWithNodes) {
3068
- if (conn.connection?.pointsToConnect) {
3069
- for (const point of conn.connection.pointsToConnect) {
3070
- graphics.points.push({
3071
- x: point.x,
3072
- y: point.y
3073
- });
3074
- }
3075
- }
3115
+ };
3116
+
3117
+ // lib/utils/getConnectivityMapFromSimpleRouteJson.ts
3118
+ var getConnectivityMapFromSimpleRouteJson = (srj) => {
3119
+ const connMap = new ConnectivityMap({});
3120
+ for (const connection of srj.connections) {
3121
+ for (const point of connection.pointsToConnect) {
3122
+ if ("pcb_port_id" in point && point.pcb_port_id) {
3123
+ connMap.addConnections([[connection.name, point.pcb_port_id]]);
3076
3124
  }
3077
3125
  }
3078
- const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
3079
- if (nextConnection) {
3080
- const [start, end] = nextConnection.connection.pointsToConnect;
3081
- graphics.lines.push({
3082
- points: [
3083
- { x: start.x, y: start.y },
3084
- { x: end.x, y: end.y }
3085
- ],
3086
- strokeColor: "red",
3087
- strokeDash: "10 5"
3088
- });
3089
- }
3090
- if (this.candidates) {
3091
- const topCandidates = this.candidates.slice(0, 5);
3092
- const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
3093
- topCandidates.forEach((candidate, index) => {
3094
- const opacity = 0.5 * (1 - index / 5);
3095
- const backtrackedPath = this.getBacktrackedPath(candidate);
3096
- graphics.lines.push({
3097
- points: backtrackedPath.map(({ center: { x, y } }) => ({ x, y })),
3098
- strokeColor: safeTransparentize(
3099
- this.colorMap[connectionName] ?? "red",
3100
- 1 - opacity
3101
- )
3102
- });
3103
- });
3104
- }
3105
- return graphics;
3106
3126
  }
3127
+ return connMap;
3107
3128
  };
3108
3129
 
3109
3130
  // lib/utils/getTunedTotalCapacity1.ts
@@ -3129,267 +3150,44 @@ var calculateOptimalCapacityDepth = (initialWidth, targetMinCapacity = 0.5, maxD
3129
3150
  return Math.max(1, depth);
3130
3151
  };
3131
3152
 
3132
- // lib/solvers/CapacityPathingSolver/CapacityPathingSolver4_FlexibleNegativeCapacity_AvoidLowCapacity_FixedDistanceCost.ts
3133
- var CapacityPathingSolver4_FlexibleNegativeCapacity = class extends CapacityPathingSolver {
3134
- NEGATIVE_CAPACITY_PENALTY_FACTOR = 1;
3135
- REDUCED_CAPACITY_PENALTY_FACTOR = 1;
3136
- get maxCapacityFactor() {
3137
- return this.hyperParameters.MAX_CAPACITY_FACTOR ?? 1;
3153
+ // lib/solvers/NetToPointPairsSolver/buildMinimumSpanningTree.ts
3154
+ var KDNode = class {
3155
+ point;
3156
+ left = null;
3157
+ right = null;
3158
+ constructor(point) {
3159
+ this.point = point;
3138
3160
  }
3139
- /**
3140
- * In the FlexibleNegativeCapacity mode, we allow negative capacity
3141
- */
3142
- doesNodeHaveCapacityForTrace(node) {
3143
- return true;
3161
+ };
3162
+ var KDTree = class {
3163
+ root = null;
3164
+ constructor(points) {
3165
+ if (points.length > 0) {
3166
+ this.root = this.buildTree(points, 0);
3167
+ }
3144
3168
  }
3145
- getTotalCapacity(node) {
3146
- return getTunedTotalCapacity1(node, this.maxCapacityFactor);
3169
+ buildTree(points, depth) {
3170
+ const axis = depth % 2 === 0 ? "x" : "y";
3171
+ points.sort((a, b) => a[axis] - b[axis]);
3172
+ const medianIndex = Math.floor(points.length / 2);
3173
+ const node = new KDNode(points[medianIndex]);
3174
+ if (medianIndex > 0) {
3175
+ node.left = this.buildTree(points.slice(0, medianIndex), depth + 1);
3176
+ }
3177
+ if (medianIndex < points.length - 1) {
3178
+ node.right = this.buildTree(points.slice(medianIndex + 1), depth + 1);
3179
+ }
3180
+ return node;
3147
3181
  }
3148
- /**
3149
- * Penalty you pay for using this node
3150
- */
3151
- getNodeCapacityPenalty(node) {
3152
- const totalCapacity = this.getTotalCapacity(node);
3153
- const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
3154
- const remainingCapacity = totalCapacity - usedCapacity;
3155
- const dist = this.activeCandidateStraightLineDistance;
3156
- if (remainingCapacity <= 0) {
3157
- const penalty = (-remainingCapacity + 1) / totalCapacity * dist * (this.NEGATIVE_CAPACITY_PENALTY_FACTOR / 4);
3158
- return penalty ** 2;
3182
+ // Find the nearest neighbor to a query point
3183
+ findNearestNeighbor(queryPoint) {
3184
+ if (!this.root) {
3185
+ throw new Error("Tree is empty");
3159
3186
  }
3160
- return 1 / remainingCapacity * dist * this.REDUCED_CAPACITY_PENALTY_FACTOR / 8;
3161
- }
3162
- /**
3163
- * We're rewarding travel into big nodes.
3164
- *
3165
- * To minimize shortest path, you'd want to comment this out.
3166
- */
3167
- getDistanceBetweenNodes(A, B) {
3168
- const dx = A.center.x - B.center.x;
3169
- const dy = A.center.y - B.center.y;
3170
- const szx = Math.max(A.width, B.width);
3171
- const szy = Math.max(A.height, B.height);
3172
- const dist = Math.sqrt(dx ** 2 + dy ** 2) / (szx * szy);
3173
- return dist;
3174
- }
3175
- computeG(prevCandidate, node, endGoal) {
3176
- return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node) + this.getNodeCapacityPenalty(node);
3177
- }
3178
- computeH(prevCandidate, node, endGoal) {
3179
- return this.getDistanceBetweenNodes(node, endGoal) + this.getNodeCapacityPenalty(node);
3180
- }
3181
- };
3182
-
3183
- // node_modules/circuit-json-to-connectivity-map/dist/index.js
3184
- var ConnectivityMap = class {
3185
- netMap;
3186
- idToNetMap;
3187
- constructor(netMap) {
3188
- this.netMap = netMap;
3189
- this.idToNetMap = {};
3190
- for (const [netId, ids] of Object.entries(netMap)) {
3191
- for (const id of ids) {
3192
- this.idToNetMap[id] = netId;
3193
- }
3194
- }
3195
- }
3196
- addConnections(connections) {
3197
- for (const connection of connections) {
3198
- const existingNets = /* @__PURE__ */ new Set();
3199
- for (const id of connection) {
3200
- const existingNetId = this.idToNetMap[id];
3201
- if (existingNetId) {
3202
- existingNets.add(existingNetId);
3203
- }
3204
- }
3205
- let targetNetId;
3206
- if (existingNets.size === 0) {
3207
- targetNetId = `connectivity_net${Object.keys(this.netMap).length}`;
3208
- this.netMap[targetNetId] = [];
3209
- } else if (existingNets.size === 1) {
3210
- targetNetId = existingNets.values().next().value ?? `connectivity_net${Object.keys(this.netMap).length}`;
3211
- } else {
3212
- targetNetId = existingNets.values().next().value ?? `connectivity_net${Object.keys(this.netMap).length}`;
3213
- for (const netId of existingNets) {
3214
- if (netId !== targetNetId) {
3215
- this.netMap[targetNetId].push(...this.netMap[netId]);
3216
- this.netMap[netId] = this.netMap[targetNetId];
3217
- for (const id of this.netMap[targetNetId]) {
3218
- this.idToNetMap[id] = targetNetId;
3219
- }
3220
- }
3221
- }
3222
- }
3223
- for (const id of connection) {
3224
- if (!this.netMap[targetNetId].includes(id)) {
3225
- this.netMap[targetNetId].push(id);
3226
- }
3227
- this.idToNetMap[id] = targetNetId;
3228
- }
3229
- }
3230
- }
3231
- getIdsConnectedToNet(netId) {
3232
- return this.netMap[netId] || [];
3233
- }
3234
- getNetConnectedToId(id) {
3235
- return this.idToNetMap[id];
3236
- }
3237
- areIdsConnected(id1, id2) {
3238
- if (id1 === id2) return true;
3239
- const netId1 = this.getNetConnectedToId(id1);
3240
- if (!netId1) return false;
3241
- const netId2 = this.getNetConnectedToId(id2);
3242
- if (!netId2) return false;
3243
- return netId1 === netId2 || netId2 === id1 || netId2 === id1;
3244
- }
3245
- areAllIdsConnected(ids) {
3246
- const netId = this.getNetConnectedToId(ids[0]);
3247
- for (const id of ids) {
3248
- const nextNetId = this.getNetConnectedToId(id);
3249
- if (nextNetId === void 0) {
3250
- return false;
3251
- }
3252
- if (nextNetId !== netId) {
3253
- return false;
3254
- }
3255
- }
3256
- return true;
3257
- }
3258
- };
3259
-
3260
- // lib/utils/getConnectivityMapFromSimpleRouteJson.ts
3261
- var getConnectivityMapFromSimpleRouteJson = (srj) => {
3262
- const connMap = new ConnectivityMap({});
3263
- for (const connection of srj.connections) {
3264
- for (const point of connection.pointsToConnect) {
3265
- if ("pcb_port_id" in point && point.pcb_port_id) {
3266
- connMap.addConnections([[connection.name, point.pcb_port_id]]);
3267
- }
3268
- }
3269
- }
3270
- return connMap;
3271
- };
3272
-
3273
- // lib/solvers/CapacityMeshSolver/CapacityNodeTargetMerger.ts
3274
- var CapacityNodeTargetMerger = class extends BaseSolver {
3275
- constructor(nodes, obstacles, connMap) {
3276
- super();
3277
- this.nodes = nodes;
3278
- this.connMap = connMap;
3279
- this.MAX_ITERATIONS = 1e5;
3280
- this.unprocessedObstacles = [...obstacles];
3281
- this.newNodes = [];
3282
- this.removedNodeIds = /* @__PURE__ */ new Set();
3283
- }
3284
- unprocessedObstacles;
3285
- newNodes;
3286
- removedNodeIds;
3287
- _step() {
3288
- const obstacle = this.unprocessedObstacles.pop();
3289
- if (!obstacle) {
3290
- for (const node of this.nodes) {
3291
- if (this.removedNodeIds.has(node.capacityMeshNodeId)) continue;
3292
- this.newNodes.push(node);
3293
- }
3294
- this.solved = true;
3295
- return;
3296
- }
3297
- const connectedNodes = this.nodes.filter((n) => {
3298
- if (!n._targetConnectionName) return false;
3299
- const implicitlyConnected = doRectsOverlap(n, obstacle);
3300
- return implicitlyConnected;
3301
- });
3302
- if (connectedNodes.length === 0) return;
3303
- const connectionName = connectedNodes[0]._targetConnectionName;
3304
- const bounds = {
3305
- minX: Infinity,
3306
- minY: Infinity,
3307
- maxX: -Infinity,
3308
- maxY: -Infinity
3309
- };
3310
- for (const node of connectedNodes) {
3311
- bounds.minX = Math.min(bounds.minX, node.center.x - node.width / 2);
3312
- bounds.minY = Math.min(bounds.minY, node.center.y - node.height / 2);
3313
- bounds.maxX = Math.max(bounds.maxX, node.center.x + node.width / 2);
3314
- bounds.maxY = Math.max(bounds.maxY, node.center.y + node.height / 2);
3315
- }
3316
- const newNode = {
3317
- capacityMeshNodeId: connectedNodes[0].capacityMeshNodeId,
3318
- center: {
3319
- x: (bounds.minX + bounds.maxX) / 2,
3320
- y: (bounds.minY + bounds.maxY) / 2
3321
- },
3322
- width: bounds.maxX - bounds.minX,
3323
- height: bounds.maxY - bounds.minY,
3324
- layer: connectedNodes[0].layer,
3325
- availableZ: connectedNodes[0].availableZ,
3326
- _completelyInsideObstacle: false,
3327
- _containsObstacle: true,
3328
- _containsTarget: true,
3329
- _targetConnectionName: connectionName,
3330
- _depth: connectedNodes[0]._depth,
3331
- _parent: connectedNodes[0]._parent
3332
- };
3333
- this.newNodes.push(newNode);
3334
- for (const node of connectedNodes) {
3335
- this.removedNodeIds.add(node.capacityMeshNodeId);
3336
- }
3337
- }
3338
- visualize() {
3339
- const graphics = {
3340
- rects: []
3341
- };
3342
- for (const node of this.newNodes) {
3343
- graphics.rects.push({
3344
- center: node.center,
3345
- width: Math.max(node.width - 2, node.width * 0.8),
3346
- height: Math.max(node.height - 2, node.height * 0.8),
3347
- fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : "rgba(0,0,0,0.1)",
3348
- label: node.capacityMeshNodeId
3349
- });
3350
- }
3351
- return graphics;
3352
- }
3353
- };
3354
-
3355
- // lib/solvers/NetToPointPairsSolver/buildMinimumSpanningTree.ts
3356
- var KDNode = class {
3357
- point;
3358
- left = null;
3359
- right = null;
3360
- constructor(point) {
3361
- this.point = point;
3362
- }
3363
- };
3364
- var KDTree = class {
3365
- root = null;
3366
- constructor(points) {
3367
- if (points.length > 0) {
3368
- this.root = this.buildTree(points, 0);
3369
- }
3370
- }
3371
- buildTree(points, depth) {
3372
- const axis = depth % 2 === 0 ? "x" : "y";
3373
- points.sort((a, b) => a[axis] - b[axis]);
3374
- const medianIndex = Math.floor(points.length / 2);
3375
- const node = new KDNode(points[medianIndex]);
3376
- if (medianIndex > 0) {
3377
- node.left = this.buildTree(points.slice(0, medianIndex), depth + 1);
3378
- }
3379
- if (medianIndex < points.length - 1) {
3380
- node.right = this.buildTree(points.slice(medianIndex + 1), depth + 1);
3381
- }
3382
- return node;
3383
- }
3384
- // Find the nearest neighbor to a query point
3385
- findNearestNeighbor(queryPoint) {
3386
- if (!this.root) {
3387
- throw new Error("Tree is empty");
3388
- }
3389
- const best = this.root.point;
3390
- const bestDistance = this.distance(queryPoint, best);
3391
- this.nearestNeighborSearch(this.root, queryPoint, 0, best, bestDistance);
3392
- return best;
3187
+ const best = this.root.point;
3188
+ const bestDistance = this.distance(queryPoint, best);
3189
+ this.nearestNeighborSearch(this.root, queryPoint, 0, best, bestDistance);
3190
+ return best;
3393
3191
  }
3394
3192
  nearestNeighborSearch(node, queryPoint, depth, best, bestDistance) {
3395
3193
  if (!node) {
@@ -3717,13 +3515,6 @@ var convertHdRouteToSimplifiedRoute = (hdRoute, layerCount) => {
3717
3515
  return result;
3718
3516
  };
3719
3517
 
3720
- // lib/utils/mapLayerNameToZ.ts
3721
- var mapLayerNameToZ = (layerName, layerCount) => {
3722
- if (layerName === "top") return 0;
3723
- if (layerName === "bottom") return layerCount - 1;
3724
- return parseInt(layerName.slice(5));
3725
- };
3726
-
3727
3518
  // lib/solvers/RouteStitchingSolver/SingleHighDensityRouteStitchSolver.ts
3728
3519
  var SingleHighDensityRouteStitchSolver = class extends BaseSolver {
3729
3520
  mergedHdRoute;
@@ -4286,6 +4077,7 @@ var createSegmentPointMap = (dedupedSegments, segmentIdToNodeIds) => {
4286
4077
  var UnravelSectionSolver = class extends BaseSolver {
4287
4078
  nodeMap;
4288
4079
  dedupedSegments;
4080
+ dedupedSegmentMap;
4289
4081
  MUTABLE_HOPS = 1;
4290
4082
  unravelSection;
4291
4083
  candidates = [];
@@ -4305,6 +4097,14 @@ var UnravelSectionSolver = class extends BaseSolver {
4305
4097
  this.MUTABLE_HOPS = params.MUTABLE_HOPS ?? this.MUTABLE_HOPS;
4306
4098
  this.nodeMap = params.nodeMap;
4307
4099
  this.dedupedSegments = params.dedupedSegments;
4100
+ if (params.dedupedSegmentMap) {
4101
+ this.dedupedSegmentMap = params.dedupedSegmentMap;
4102
+ } else {
4103
+ this.dedupedSegmentMap = /* @__PURE__ */ new Map();
4104
+ for (const segment of this.dedupedSegments) {
4105
+ this.dedupedSegmentMap.set(segment.nodePortSegmentId, segment);
4106
+ }
4107
+ }
4308
4108
  this.nodeIdToSegmentIds = params.nodeIdToSegmentIds;
4309
4109
  this.segmentIdToNodeIds = params.segmentIdToNodeIds;
4310
4110
  this.rootNodeId = params.rootNodeId;
@@ -4463,14 +4263,20 @@ var UnravelSectionSolver = class extends BaseSolver {
4463
4263
  const [APointId, BPointId] = issue.segmentPoints;
4464
4264
  const pointA = this.getPointInCandidate(candidate, APointId);
4465
4265
  const pointB = this.getPointInCandidate(candidate, BPointId);
4466
- if (this.unravelSection.mutableSegmentIds.has(pointA.segmentId)) {
4266
+ const aAvailableZ = this.dedupedSegmentMap.get(
4267
+ pointA.segmentId
4268
+ ).availableZ;
4269
+ const bAvailableZ = this.dedupedSegmentMap.get(
4270
+ pointB.segmentId
4271
+ ).availableZ;
4272
+ if (this.unravelSection.mutableSegmentIds.has(pointA.segmentId) && aAvailableZ.includes(pointB.z)) {
4467
4273
  operations.push({
4468
4274
  type: "change_layer",
4469
4275
  newZ: pointB.z,
4470
4276
  segmentPointIds: [APointId]
4471
4277
  });
4472
4278
  }
4473
- if (this.unravelSection.mutableSegmentIds.has(pointB.segmentId)) {
4279
+ if (this.unravelSection.mutableSegmentIds.has(pointB.segmentId) && bAvailableZ.includes(pointA.z)) {
4474
4280
  operations.push({
4475
4281
  type: "change_layer",
4476
4282
  newZ: pointA.z,
@@ -4925,6 +4731,7 @@ var calculateNodeProbabilityOfFailure = (node, numSameLayerCrossings, numEntryEx
4925
4731
  // lib/solvers/UnravelSolver/UnravelMultiSectionSolver.ts
4926
4732
  var UnravelMultiSectionSolver = class extends BaseSolver {
4927
4733
  nodeMap;
4734
+ dedupedSegmentMap;
4928
4735
  dedupedSegments;
4929
4736
  nodeIdToSegmentIds;
4930
4737
  segmentIdToNodeIds;
@@ -4948,6 +4755,10 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
4948
4755
  super();
4949
4756
  this.MAX_ITERATIONS = 1e5;
4950
4757
  this.dedupedSegments = getDedupedSegments(assignedSegments);
4758
+ this.dedupedSegmentMap = /* @__PURE__ */ new Map();
4759
+ for (const segment of this.dedupedSegments) {
4760
+ this.dedupedSegmentMap.set(segment.nodePortSegmentId, segment);
4761
+ }
4951
4762
  this.nodeMap = /* @__PURE__ */ new Map();
4952
4763
  for (const node of nodes) {
4953
4764
  this.nodeMap.set(node.capacityMeshNodeId, node);
@@ -5026,6 +4837,7 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
5026
4837
  );
5027
4838
  this.activeSolver = new UnravelSectionSolver({
5028
4839
  dedupedSegments: this.dedupedSegments,
4840
+ dedupedSegmentMap: this.dedupedSegmentMap,
5029
4841
  nodeMap: this.nodeMap,
5030
4842
  nodeIdToSegmentIds: this.nodeIdToSegmentIds,
5031
4843
  segmentIdToNodeIds: this.segmentIdToNodeIds,
@@ -5052,152 +4864,967 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
5052
4864
  segmentPoint.z = pointModification.z ?? segmentPoint.z;
5053
4865
  }
5054
4866
  }
5055
- for (const nodeId of this.activeSolver.unravelSection.allNodeIds) {
5056
- this.nodePfMap.set(
5057
- nodeId,
5058
- this.computeNodePf(this.nodeMap.get(nodeId))
5059
- );
4867
+ for (const nodeId of this.activeSolver.unravelSection.allNodeIds) {
4868
+ this.nodePfMap.set(
4869
+ nodeId,
4870
+ this.computeNodePf(this.nodeMap.get(nodeId))
4871
+ );
4872
+ }
4873
+ this.activeSolver = null;
4874
+ }
4875
+ }
4876
+ visualize() {
4877
+ if (this.activeSolver) {
4878
+ return this.activeSolver.visualize();
4879
+ }
4880
+ const graphics = {
4881
+ lines: [],
4882
+ points: [],
4883
+ rects: [],
4884
+ circles: [],
4885
+ coordinateSystem: "cartesian",
4886
+ title: "Unravel Multi Section Solver"
4887
+ };
4888
+ for (const [nodeId, node] of this.nodeMap.entries()) {
4889
+ const probabilityOfFailure = this.nodePfMap.get(nodeId) || 0;
4890
+ const pf = Math.min(probabilityOfFailure, 1);
4891
+ const red = Math.floor(255 * pf);
4892
+ const green = Math.floor(255 * (1 - pf));
4893
+ const color = `rgb(${red}, ${green}, 0)`;
4894
+ if ((this.attemptsToFixNode.get(nodeId) ?? 0) === 0 && pf === 0) {
4895
+ continue;
4896
+ }
4897
+ graphics.rects.push({
4898
+ center: node.center,
4899
+ label: [
4900
+ nodeId,
4901
+ `${node.width.toFixed(2)}x${node.height.toFixed(2)}`,
4902
+ `Pf: ${probabilityOfFailure.toFixed(3)}`
4903
+ ].join("\n"),
4904
+ color,
4905
+ width: node.width / 8,
4906
+ height: node.height / 8
4907
+ });
4908
+ }
4909
+ for (const segmentPoint of this.segmentPointMap.values()) {
4910
+ const segment = this.dedupedSegmentMap.get(segmentPoint.segmentId);
4911
+ graphics.points.push({
4912
+ x: segmentPoint.x,
4913
+ y: segmentPoint.y,
4914
+ label: [
4915
+ segmentPoint.segmentPointId,
4916
+ segmentPoint.segmentId,
4917
+ `z: ${segmentPoint.z}`,
4918
+ `segment.availableZ: ${segment?.availableZ.join(",")}`
4919
+ ].join("\n"),
4920
+ color: this.colorMap[segmentPoint.connectionName] || "#000"
4921
+ });
4922
+ }
4923
+ const pointsBySegment = /* @__PURE__ */ new Map();
4924
+ for (const point of this.segmentPointMap.values()) {
4925
+ if (!pointsBySegment.has(point.segmentId)) {
4926
+ pointsBySegment.set(point.segmentId, []);
4927
+ }
4928
+ pointsBySegment.get(point.segmentId).push(point);
4929
+ }
4930
+ for (const [segmentId, points] of pointsBySegment.entries()) {
4931
+ if (points.length < 2) continue;
4932
+ const sortedPoints = [...points].sort(
4933
+ (a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y
4934
+ );
4935
+ for (let i = 0; i < sortedPoints.length - 1; i++) {
4936
+ graphics.lines.push({
4937
+ points: [
4938
+ { x: sortedPoints[i].x, y: sortedPoints[i].y },
4939
+ { x: sortedPoints[i + 1].x, y: sortedPoints[i + 1].y }
4940
+ ],
4941
+ strokeColor: this.colorMap[segmentId] || "#000"
4942
+ });
4943
+ }
4944
+ }
4945
+ const processedConnections = /* @__PURE__ */ new Set();
4946
+ const allPoints = Array.from(this.segmentPointMap.values());
4947
+ for (let i = 0; i < allPoints.length; i++) {
4948
+ const point1 = allPoints[i];
4949
+ for (let j = i + 1; j < allPoints.length; j++) {
4950
+ const point2 = allPoints[j];
4951
+ if (point1.connectionName !== point2.connectionName || point1.segmentId === point2.segmentId) {
4952
+ continue;
4953
+ }
4954
+ const hasSharedNode = point1.capacityMeshNodeIds.some(
4955
+ (nodeId) => point2.capacityMeshNodeIds.includes(nodeId)
4956
+ );
4957
+ if (hasSharedNode) {
4958
+ const connectionKey = `${point1.segmentPointId}-${point2.segmentPointId}`;
4959
+ if (processedConnections.has(connectionKey)) continue;
4960
+ processedConnections.add(connectionKey);
4961
+ const sameLayer = point1.z === point2.z;
4962
+ const layer = point1.z;
4963
+ let strokeDash;
4964
+ if (sameLayer) {
4965
+ strokeDash = layer === 0 ? void 0 : "10 5";
4966
+ } else {
4967
+ strokeDash = "3 3 10";
4968
+ }
4969
+ graphics.lines.push({
4970
+ points: [
4971
+ { x: point1.x, y: point1.y },
4972
+ { x: point2.x, y: point2.y }
4973
+ ],
4974
+ strokeDash,
4975
+ strokeColor: this.colorMap[point1.connectionName] || "#666"
4976
+ });
4977
+ }
4978
+ }
4979
+ }
4980
+ return graphics;
4981
+ }
4982
+ getNodesWithPortPoints() {
4983
+ if (!this.solved) {
4984
+ throw new Error(
4985
+ "CapacitySegmentToPointSolver not solved, can't give port points yet"
4986
+ );
4987
+ }
4988
+ const nodeWithPortPointsMap = /* @__PURE__ */ new Map();
4989
+ for (const segment of this.dedupedSegments) {
4990
+ const segId = segment.nodePortSegmentId;
4991
+ for (const nodeId of this.segmentIdToNodeIds.get(segId)) {
4992
+ const node = this.nodeMap.get(nodeId);
4993
+ if (!nodeWithPortPointsMap.has(nodeId)) {
4994
+ nodeWithPortPointsMap.set(nodeId, {
4995
+ capacityMeshNodeId: nodeId,
4996
+ portPoints: [],
4997
+ center: node.center,
4998
+ width: node.width,
4999
+ height: node.height
5000
+ });
5001
+ }
5002
+ }
5003
+ }
5004
+ for (const segmentPoint of this.segmentPointMap.values()) {
5005
+ for (const nodeId of segmentPoint.capacityMeshNodeIds) {
5006
+ const nodeWithPortPoints = nodeWithPortPointsMap.get(nodeId);
5007
+ if (nodeWithPortPoints) {
5008
+ nodeWithPortPoints.portPoints.push({
5009
+ x: segmentPoint.x,
5010
+ y: segmentPoint.y,
5011
+ z: segmentPoint.z,
5012
+ connectionName: segmentPoint.connectionName
5013
+ });
5014
+ }
5015
+ }
5016
+ }
5017
+ return Array.from(nodeWithPortPointsMap.values());
5018
+ }
5019
+ };
5020
+
5021
+ // lib/utils/createRectFromCapacityNode.ts
5022
+ var createRectFromCapacityNode = (node, opts = {}) => {
5023
+ const lowestZ = Math.min(...node.availableZ);
5024
+ return {
5025
+ center: !opts.rectMargin ? {
5026
+ x: node.center.x + lowestZ * node.width * 0.05,
5027
+ y: node.center.y - lowestZ * node.width * 0.05
5028
+ } : node.center,
5029
+ width: opts.rectMargin ? node.width - opts.rectMargin * 2 : Math.max(node.width - 0.5, node.width * 0.8),
5030
+ height: opts.rectMargin ? node.height - opts.rectMargin * 2 : Math.max(node.height - 0.5, node.height * 0.8),
5031
+ fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : {
5032
+ "0,1": "rgba(0,0,0,0.1)",
5033
+ "0": "rgba(0,200,200, 0.1)",
5034
+ "1": "rgba(0,0,200, 0.1)"
5035
+ }[node.availableZ.join(",")] ?? "rgba(0,200,200,0.1)",
5036
+ label: [
5037
+ node.capacityMeshNodeId,
5038
+ `availableZ: ${node.availableZ.join(",")}`,
5039
+ `${node._containsTarget ? "containsTarget" : ""}`,
5040
+ `${node._containsObstacle ? "containsObstacle" : ""}`
5041
+ ].filter(Boolean).join("\n")
5042
+ };
5043
+ };
5044
+
5045
+ // lib/solvers/CapacityPathingSolver/CapacityPathingSolver.ts
5046
+ var CapacityPathingSolver = class extends BaseSolver {
5047
+ connectionsWithNodes;
5048
+ usedNodeCapacityMap;
5049
+ simpleRouteJson;
5050
+ nodes;
5051
+ edges;
5052
+ GREEDY_MULTIPLIER = 1.1;
5053
+ nodeMap;
5054
+ nodeEdgeMap;
5055
+ connectionNameToGoalNodeIds;
5056
+ colorMap;
5057
+ maxDepthOfNodes;
5058
+ activeCandidateStraightLineDistance;
5059
+ debug_lastNodeCostMap;
5060
+ hyperParameters;
5061
+ constructor({
5062
+ simpleRouteJson,
5063
+ nodes,
5064
+ edges,
5065
+ colorMap,
5066
+ MAX_ITERATIONS = 1e6,
5067
+ hyperParameters = {}
5068
+ }) {
5069
+ super();
5070
+ this.MAX_ITERATIONS = MAX_ITERATIONS;
5071
+ this.simpleRouteJson = simpleRouteJson;
5072
+ this.nodes = nodes;
5073
+ this.edges = edges;
5074
+ this.colorMap = colorMap ?? {};
5075
+ const { connectionsWithNodes, connectionNameToGoalNodeIds } = this.getConnectionsWithNodes();
5076
+ this.connectionsWithNodes = connectionsWithNodes;
5077
+ this.connectionNameToGoalNodeIds = connectionNameToGoalNodeIds;
5078
+ this.hyperParameters = hyperParameters;
5079
+ this.usedNodeCapacityMap = new Map(
5080
+ this.nodes.map((node) => [node.capacityMeshNodeId, 0])
5081
+ );
5082
+ this.nodeMap = new Map(
5083
+ this.nodes.map((node) => [node.capacityMeshNodeId, node])
5084
+ );
5085
+ this.nodeEdgeMap = getNodeEdgeMap(this.edges);
5086
+ this.maxDepthOfNodes = Math.max(
5087
+ ...this.nodes.map((node) => node._depth ?? 0)
5088
+ );
5089
+ this.debug_lastNodeCostMap = /* @__PURE__ */ new Map();
5090
+ }
5091
+ getTotalCapacity(node) {
5092
+ const depth = node._depth ?? 0;
5093
+ return (this.maxDepthOfNodes - depth + 1) ** 2;
5094
+ }
5095
+ getConnectionsWithNodes() {
5096
+ const connectionsWithNodes = [];
5097
+ const nodesWithTargets = this.nodes.filter((node) => node._containsTarget);
5098
+ const connectionNameToGoalNodeIds = /* @__PURE__ */ new Map();
5099
+ for (const connection of this.simpleRouteJson.connections) {
5100
+ const nodesForConnection = [];
5101
+ for (const point of connection.pointsToConnect) {
5102
+ let closestNode = this.nodes[0];
5103
+ let minDistance = Number.MAX_VALUE;
5104
+ for (const node of nodesWithTargets) {
5105
+ const distance3 = Math.sqrt(
5106
+ (node.center.x - point.x) ** 2 + (node.center.y - point.y) ** 2
5107
+ );
5108
+ if (distance3 < minDistance) {
5109
+ minDistance = distance3;
5110
+ closestNode = node;
5111
+ }
5112
+ }
5113
+ nodesForConnection.push(closestNode);
5114
+ }
5115
+ if (nodesForConnection.length < 2) {
5116
+ throw new Error(
5117
+ `Not enough nodes for connection "${connection.name}", only ${nodesForConnection.length} found`
5118
+ );
5119
+ }
5120
+ connectionNameToGoalNodeIds.set(
5121
+ connection.name,
5122
+ nodesForConnection.map((n) => n.capacityMeshNodeId)
5123
+ );
5124
+ connectionsWithNodes.push({
5125
+ connection,
5126
+ nodes: nodesForConnection,
5127
+ pathFound: false
5128
+ });
5129
+ }
5130
+ return { connectionsWithNodes, connectionNameToGoalNodeIds };
5131
+ }
5132
+ currentConnectionIndex = 0;
5133
+ candidates;
5134
+ visitedNodes;
5135
+ computeG(prevCandidate, node, endGoal) {
5136
+ return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node);
5137
+ }
5138
+ computeH(prevCandidate, node, endGoal) {
5139
+ return this.getDistanceBetweenNodes(node, endGoal);
5140
+ }
5141
+ getBacktrackedPath(candidate) {
5142
+ const path = [];
5143
+ let currentCandidate = candidate;
5144
+ while (currentCandidate) {
5145
+ path.push(currentCandidate.node);
5146
+ currentCandidate = currentCandidate.prevCandidate;
5147
+ }
5148
+ return path;
5149
+ }
5150
+ getNeighboringNodes(node) {
5151
+ return this.nodeEdgeMap.get(node.capacityMeshNodeId).flatMap(
5152
+ (edge) => edge.nodeIds.filter((n) => n !== node.capacityMeshNodeId)
5153
+ ).map((n) => this.nodeMap.get(n));
5154
+ }
5155
+ getCapacityPaths() {
5156
+ const capacityPaths = [];
5157
+ for (const connection of this.connectionsWithNodes) {
5158
+ const path = connection.path;
5159
+ if (path) {
5160
+ capacityPaths.push({
5161
+ capacityPathId: connection.connection.name,
5162
+ connectionName: connection.connection.name,
5163
+ nodeIds: path.map((node) => node.capacityMeshNodeId)
5164
+ });
5165
+ }
5166
+ }
5167
+ return capacityPaths;
5168
+ }
5169
+ doesNodeHaveCapacityForTrace(node) {
5170
+ const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
5171
+ const totalCapacity = this.getTotalCapacity(node);
5172
+ if (node.availableZ.length === 1 && !node._containsTarget && usedCapacity > 0)
5173
+ return false;
5174
+ return usedCapacity < totalCapacity;
5175
+ }
5176
+ canTravelThroughObstacle(node, connectionName) {
5177
+ const goalNodeIds = this.connectionNameToGoalNodeIds.get(connectionName);
5178
+ return goalNodeIds?.includes(node.capacityMeshNodeId) ?? false;
5179
+ }
5180
+ getDistanceBetweenNodes(A, B) {
5181
+ return Math.sqrt(
5182
+ (A.center.x - B.center.x) ** 2 + (A.center.y - B.center.y) ** 2
5183
+ );
5184
+ }
5185
+ reduceCapacityAlongPath(nextConnection) {
5186
+ for (const node of nextConnection.path ?? []) {
5187
+ this.usedNodeCapacityMap.set(
5188
+ node.capacityMeshNodeId,
5189
+ this.usedNodeCapacityMap.get(node.capacityMeshNodeId) + 1
5190
+ );
5191
+ }
5192
+ }
5193
+ isConnectedToEndGoal(node, endGoal) {
5194
+ return this.nodeEdgeMap.get(node.capacityMeshNodeId).some((edge) => edge.nodeIds.includes(endGoal.capacityMeshNodeId));
5195
+ }
5196
+ _step() {
5197
+ const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
5198
+ if (!nextConnection) {
5199
+ this.solved = true;
5200
+ return;
5201
+ }
5202
+ const [start, end] = nextConnection.nodes;
5203
+ if (!this.candidates) {
5204
+ this.candidates = [{ prevCandidate: null, node: start, f: 0, g: 0, h: 0 }];
5205
+ this.debug_lastNodeCostMap = /* @__PURE__ */ new Map();
5206
+ this.visitedNodes = /* @__PURE__ */ new Set([start.capacityMeshNodeId]);
5207
+ this.activeCandidateStraightLineDistance = distance(
5208
+ start.center,
5209
+ end.center
5210
+ );
5211
+ }
5212
+ this.candidates.sort((a, b) => a.f - b.f);
5213
+ const currentCandidate = this.candidates.shift();
5214
+ if (!currentCandidate) {
5215
+ console.error(
5216
+ `Ran out of candidates on connection ${nextConnection.connection.name}`
5217
+ );
5218
+ this.currentConnectionIndex++;
5219
+ this.candidates = null;
5220
+ this.visitedNodes = null;
5221
+ return;
5222
+ }
5223
+ if (this.isConnectedToEndGoal(currentCandidate.node, end)) {
5224
+ nextConnection.path = this.getBacktrackedPath({
5225
+ prevCandidate: currentCandidate,
5226
+ node: end,
5227
+ f: 0,
5228
+ g: 0,
5229
+ h: 0
5230
+ });
5231
+ this.reduceCapacityAlongPath(nextConnection);
5232
+ this.currentConnectionIndex++;
5233
+ this.candidates = null;
5234
+ this.visitedNodes = null;
5235
+ return;
5236
+ }
5237
+ const neighborNodes = this.getNeighboringNodes(currentCandidate.node);
5238
+ for (const neighborNode of neighborNodes) {
5239
+ if (this.visitedNodes?.has(neighborNode.capacityMeshNodeId)) {
5240
+ continue;
5241
+ }
5242
+ if (!this.doesNodeHaveCapacityForTrace(neighborNode)) {
5243
+ continue;
5244
+ }
5245
+ const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
5246
+ if (neighborNode._containsObstacle && !this.canTravelThroughObstacle(neighborNode, connectionName)) {
5247
+ continue;
5248
+ }
5249
+ const g = this.computeG(currentCandidate, neighborNode, end);
5250
+ const h = this.computeH(currentCandidate, neighborNode, end);
5251
+ const f = g + h * this.GREEDY_MULTIPLIER;
5252
+ this.debug_lastNodeCostMap.set(neighborNode.capacityMeshNodeId, {
5253
+ f,
5254
+ g,
5255
+ h
5256
+ });
5257
+ const newCandidate = {
5258
+ prevCandidate: currentCandidate,
5259
+ node: neighborNode,
5260
+ f,
5261
+ g,
5262
+ h
5263
+ };
5264
+ this.candidates.push(newCandidate);
5265
+ }
5266
+ this.visitedNodes.add(currentCandidate.node.capacityMeshNodeId);
5267
+ }
5268
+ visualize() {
5269
+ const graphics = {
5270
+ lines: [],
5271
+ points: [],
5272
+ rects: [],
5273
+ circles: []
5274
+ };
5275
+ if (this.connectionsWithNodes) {
5276
+ for (let i = 0; i < this.connectionsWithNodes.length; i++) {
5277
+ const conn = this.connectionsWithNodes[i];
5278
+ if (conn.path && conn.path.length > 0) {
5279
+ const pathPoints = conn.path.map(({ center: { x, y }, width }) => ({
5280
+ // slight offset to allow viewing overlapping paths
5281
+ x: x + (i % 10 + i % 19) * (0.01 * width),
5282
+ y: y + (i % 10 + i % 19) * (0.01 * width)
5283
+ }));
5284
+ graphics.lines.push({
5285
+ points: pathPoints,
5286
+ strokeColor: this.colorMap[conn.connection.name]
5287
+ });
5288
+ }
5289
+ }
5290
+ }
5291
+ for (const node of this.nodes) {
5292
+ const nodeCosts = this.debug_lastNodeCostMap.get(node.capacityMeshNodeId);
5293
+ graphics.rects.push({
5294
+ ...createRectFromCapacityNode(node),
5295
+ label: [
5296
+ `${node.capacityMeshNodeId}`,
5297
+ `${this.usedNodeCapacityMap.get(node.capacityMeshNodeId)}/${this.getTotalCapacity(node).toFixed(2)}`,
5298
+ `${node.width.toFixed(2)}x${node.height.toFixed(2)}`,
5299
+ `g: ${nodeCosts?.g !== void 0 ? nodeCosts.g.toFixed(2) : "?"}`,
5300
+ `h: ${nodeCosts?.h !== void 0 ? nodeCosts.h.toFixed(2) : "?"}`,
5301
+ `f: ${nodeCosts?.f !== void 0 ? nodeCosts.f.toFixed(2) : "?"}`
5302
+ ].join("\n")
5303
+ });
5304
+ }
5305
+ if (this.connectionsWithNodes) {
5306
+ for (const conn of this.connectionsWithNodes) {
5307
+ if (conn.connection?.pointsToConnect) {
5308
+ for (const point of conn.connection.pointsToConnect) {
5309
+ graphics.points.push({
5310
+ x: point.x,
5311
+ y: point.y
5312
+ });
5313
+ }
5314
+ }
5315
+ }
5316
+ }
5317
+ const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
5318
+ if (nextConnection) {
5319
+ const [start, end] = nextConnection.connection.pointsToConnect;
5320
+ graphics.lines.push({
5321
+ points: [
5322
+ { x: start.x, y: start.y },
5323
+ { x: end.x, y: end.y }
5324
+ ],
5325
+ strokeColor: "red",
5326
+ strokeDash: "10 5"
5327
+ });
5328
+ }
5329
+ if (this.candidates) {
5330
+ const topCandidates = this.candidates.slice(0, 5);
5331
+ const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
5332
+ topCandidates.forEach((candidate, index) => {
5333
+ const opacity = 0.5 * (1 - index / 5);
5334
+ const backtrackedPath = this.getBacktrackedPath(candidate);
5335
+ graphics.lines.push({
5336
+ points: backtrackedPath.map(({ center: { x, y } }) => ({ x, y })),
5337
+ strokeColor: safeTransparentize(
5338
+ this.colorMap[connectionName] ?? "red",
5339
+ 1 - opacity
5340
+ )
5341
+ });
5342
+ });
5343
+ }
5344
+ return graphics;
5345
+ }
5346
+ };
5347
+
5348
+ // lib/solvers/CapacityPathingSolver/CapacityPathingSolver5.ts
5349
+ var CapacityPathingSolver5 = class extends CapacityPathingSolver {
5350
+ NEGATIVE_CAPACITY_PENALTY_FACTOR = 1;
5351
+ REDUCED_CAPACITY_PENALTY_FACTOR = 1;
5352
+ constructor(...args) {
5353
+ super(...args);
5354
+ this.GREEDY_MULTIPLIER = 2.5;
5355
+ }
5356
+ get maxCapacityFactor() {
5357
+ return this.hyperParameters.MAX_CAPACITY_FACTOR ?? 1;
5358
+ }
5359
+ getTotalCapacity(node) {
5360
+ return getTunedTotalCapacity1(node, this.maxCapacityFactor);
5361
+ }
5362
+ /**
5363
+ * Penalty you pay for using this node
5364
+ */
5365
+ getNodeCapacityPenalty(node) {
5366
+ return 0.05;
5367
+ if (node.availableZ.length === 1) {
5368
+ return 0;
5369
+ }
5370
+ const totalCapacity = this.getTotalCapacity(node);
5371
+ const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
5372
+ const remainingCapacity = totalCapacity - usedCapacity;
5373
+ const dist = this.activeCandidateStraightLineDistance;
5374
+ if (remainingCapacity <= 0) {
5375
+ const penalty = (-remainingCapacity + 1) / totalCapacity * dist * (this.NEGATIVE_CAPACITY_PENALTY_FACTOR / 4);
5376
+ return penalty ** 2;
5377
+ }
5378
+ return 1 / remainingCapacity * dist * this.REDUCED_CAPACITY_PENALTY_FACTOR / 8;
5379
+ }
5380
+ /**
5381
+ * We're rewarding travel into big nodes.
5382
+ *
5383
+ * To minimize shortest path, you'd want to comment this out.
5384
+ */
5385
+ getDistanceBetweenNodes(A, B) {
5386
+ const dx = A.center.x - B.center.x;
5387
+ const dy = A.center.y - B.center.y;
5388
+ return Math.sqrt(dx ** 2 + dy ** 2);
5389
+ }
5390
+ computeG(prevCandidate, node, endGoal) {
5391
+ return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node) + this.getNodeCapacityPenalty(node);
5392
+ }
5393
+ computeH(prevCandidate, node, endGoal) {
5394
+ return this.getDistanceBetweenNodes(node, endGoal);
5395
+ }
5396
+ };
5397
+
5398
+ // lib/solvers/StrawSolver/StrawSolver.ts
5399
+ var StrawSolver = class extends BaseSolver {
5400
+ multiLayerNodes;
5401
+ strawNodes;
5402
+ skippedNodes;
5403
+ unprocessedNodes;
5404
+ strawSize;
5405
+ nodeIdCounter;
5406
+ constructor(params) {
5407
+ super();
5408
+ this.strawSize = params.strawSize ?? 0.5;
5409
+ this.multiLayerNodes = [];
5410
+ this.strawNodes = [];
5411
+ this.skippedNodes = [];
5412
+ this.nodeIdCounter = 0;
5413
+ this.unprocessedNodes = [];
5414
+ for (const node of params.nodes) {
5415
+ if (node.availableZ.length === 1) {
5416
+ this.unprocessedNodes.push(node);
5417
+ } else {
5418
+ this.multiLayerNodes.push(node);
5419
+ }
5420
+ }
5421
+ }
5422
+ getCapacityOfMultiLayerNodesWithinBounds(bounds) {
5423
+ let totalCapacity = 0;
5424
+ for (const node of this.multiLayerNodes) {
5425
+ const nodeMinX = node.center.x - node.width / 2;
5426
+ const nodeMaxX = node.center.x + node.width / 2;
5427
+ const nodeMinY = node.center.y - node.height / 2;
5428
+ const nodeMaxY = node.center.y + node.height / 2;
5429
+ const overlapMinX = Math.max(bounds.minX, nodeMinX);
5430
+ const overlapMaxX = Math.min(bounds.maxX, nodeMaxX);
5431
+ const overlapMinY = Math.max(bounds.minY, nodeMinY);
5432
+ const overlapMaxY = Math.min(bounds.maxY, nodeMaxY);
5433
+ if (overlapMinX < overlapMaxX && overlapMinY < overlapMaxY) {
5434
+ const overlapWidth = overlapMaxX - overlapMinX;
5435
+ const overlapHeight = overlapMaxY - overlapMinY;
5436
+ const overlapArea = overlapWidth * overlapHeight;
5437
+ const nodeArea = node.width * node.height;
5438
+ const proportion = overlapArea / nodeArea;
5439
+ totalCapacity += getTunedTotalCapacity1(node) * proportion;
5440
+ }
5441
+ }
5442
+ return totalCapacity;
5443
+ }
5444
+ getSurroundingCapacities(node) {
5445
+ const searchDistance = Math.min(node.width, node.height);
5446
+ const leftSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
5447
+ minX: node.center.x - node.width / 2 - searchDistance,
5448
+ maxX: node.center.x - node.width / 2,
5449
+ minY: node.center.y - node.height / 2,
5450
+ maxY: node.center.y + node.height / 2
5451
+ });
5452
+ const rightSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
5453
+ minX: node.center.x + node.width / 2,
5454
+ maxX: node.center.x + node.width / 2 + searchDistance,
5455
+ minY: node.center.y - node.height / 2,
5456
+ maxY: node.center.y + node.height / 2
5457
+ });
5458
+ const topSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
5459
+ minX: node.center.x - node.width / 2,
5460
+ maxX: node.center.x + node.width / 2,
5461
+ minY: node.center.y - node.height / 2 - searchDistance,
5462
+ maxY: node.center.y - node.height / 2
5463
+ });
5464
+ const bottomSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
5465
+ minX: node.center.x - node.width / 2,
5466
+ maxX: node.center.x + node.width / 2,
5467
+ minY: node.center.y + node.height / 2,
5468
+ maxY: node.center.y + node.height / 2 + searchDistance
5469
+ });
5470
+ return {
5471
+ leftSurroundingCapacity,
5472
+ rightSurroundingCapacity,
5473
+ topSurroundingCapacity,
5474
+ bottomSurroundingCapacity
5475
+ };
5476
+ }
5477
+ /**
5478
+ * Creates straw nodes from a single-layer node based on surrounding capacities
5479
+ */
5480
+ createStrawsForNode(node) {
5481
+ const result = [];
5482
+ const {
5483
+ leftSurroundingCapacity,
5484
+ rightSurroundingCapacity,
5485
+ topSurroundingCapacity,
5486
+ bottomSurroundingCapacity
5487
+ } = this.getSurroundingCapacities(node);
5488
+ const horizontalCapacity = leftSurroundingCapacity + rightSurroundingCapacity;
5489
+ const verticalCapacity = topSurroundingCapacity + bottomSurroundingCapacity;
5490
+ const layerPrefersFactor = 1;
5491
+ const effectiveHorizontalCapacity = horizontalCapacity * layerPrefersFactor;
5492
+ if (effectiveHorizontalCapacity > verticalCapacity) {
5493
+ const numStraws = Math.floor(node.height / this.strawSize);
5494
+ const strawHeight = node.height / numStraws;
5495
+ for (let i = 0; i < numStraws; i++) {
5496
+ const strawCenterY = node.center.y - node.height / 2 + i * strawHeight + strawHeight / 2;
5497
+ result.push({
5498
+ capacityMeshNodeId: `${node.capacityMeshNodeId}_straw${i}`,
5499
+ center: { x: node.center.x, y: strawCenterY },
5500
+ width: node.width,
5501
+ height: strawHeight,
5502
+ layer: node.layer,
5503
+ availableZ: [...node.availableZ],
5504
+ _depth: node._depth,
5505
+ _strawNode: true
5506
+ });
5507
+ }
5508
+ } else {
5509
+ const numStraws = Math.floor(node.width / this.strawSize);
5510
+ const strawWidth = node.width / numStraws;
5511
+ for (let i = 0; i < numStraws; i++) {
5512
+ const strawCenterX = node.center.x - node.width / 2 + i * strawWidth + strawWidth / 2;
5513
+ result.push({
5514
+ capacityMeshNodeId: `${node.capacityMeshNodeId}_straw${i}`,
5515
+ center: { x: strawCenterX, y: node.center.y },
5516
+ width: strawWidth,
5517
+ height: node.height,
5518
+ layer: node.layer,
5519
+ availableZ: [...node.availableZ],
5520
+ _depth: node._depth,
5521
+ _strawNode: true
5522
+ });
5060
5523
  }
5061
- this.activeSolver = null;
5062
5524
  }
5525
+ return result;
5063
5526
  }
5064
- visualize() {
5065
- if (this.activeSolver) {
5066
- return this.activeSolver.visualize();
5527
+ getResultNodes() {
5528
+ return [...this.multiLayerNodes, ...this.strawNodes, ...this.skippedNodes];
5529
+ }
5530
+ _step() {
5531
+ const rootNode = this.unprocessedNodes.pop();
5532
+ if (!rootNode) {
5533
+ this.solved = true;
5534
+ return;
5535
+ }
5536
+ if (rootNode.width < this.strawSize * 5 && rootNode.height < this.strawSize * 5) {
5537
+ this.skippedNodes.push(rootNode);
5538
+ return;
5539
+ }
5540
+ if (rootNode._containsTarget) {
5541
+ this.skippedNodes.push(rootNode);
5542
+ return;
5067
5543
  }
5544
+ const strawNodes = this.createStrawsForNode(rootNode);
5545
+ this.strawNodes.push(...strawNodes);
5546
+ }
5547
+ visualize() {
5068
5548
  const graphics = {
5549
+ rects: [],
5069
5550
  lines: [],
5070
5551
  points: [],
5071
- rects: [],
5072
5552
  circles: [],
5073
- coordinateSystem: "cartesian",
5074
- title: "Unravel Multi Section Solver"
5553
+ title: "Straw Solver"
5075
5554
  };
5076
- for (const [nodeId, node] of this.nodeMap.entries()) {
5077
- const probabilityOfFailure = this.nodePfMap.get(nodeId) || 0;
5078
- const pf = Math.min(probabilityOfFailure, 1);
5079
- const red = Math.floor(255 * pf);
5080
- const green = Math.floor(255 * (1 - pf));
5081
- const color = `rgb(${red}, ${green}, 0)`;
5555
+ for (const node of this.unprocessedNodes) {
5082
5556
  graphics.rects.push({
5083
5557
  center: node.center,
5084
- label: `${nodeId}
5085
- ${node.width.toFixed(2)}x${node.height.toFixed(2)}
5086
- Pf: ${probabilityOfFailure.toFixed(3)}`,
5087
- color,
5088
- width: node.width / 8,
5089
- height: node.height / 8
5558
+ width: node.width,
5559
+ height: node.height,
5560
+ fill: "rgba(200, 200, 200, 0.5)",
5561
+ stroke: "rgba(0, 0, 0, 0.5)",
5562
+ label: `${node.capacityMeshNodeId}
5563
+ Unprocessed
5564
+ ${node.width}x${node.height}`
5090
5565
  });
5091
5566
  }
5092
- for (const segmentPoint of this.segmentPointMap.values()) {
5093
- graphics.points.push({
5094
- x: segmentPoint.x,
5095
- y: segmentPoint.y,
5096
- label: `${segmentPoint.segmentPointId}
5097
- Segment: ${segmentPoint.segmentId}
5098
- Layer: ${segmentPoint.z}`,
5099
- color: this.colorMap[segmentPoint.connectionName] || "#000"
5567
+ for (const node of this.strawNodes) {
5568
+ const color = node.availableZ[0] === 0 ? "rgba(0, 150, 255, 0.5)" : "rgba(255, 100, 0, 0.5)";
5569
+ graphics.rects.push({
5570
+ center: node.center,
5571
+ width: node.width,
5572
+ height: node.height,
5573
+ fill: color,
5574
+ stroke: "rgba(0, 0, 0, 0.5)",
5575
+ label: `${node.capacityMeshNodeId}
5576
+ Layer: ${node.availableZ[0]}
5577
+ ${node.width}x${node.height}`
5100
5578
  });
5101
5579
  }
5102
- const pointsBySegment = /* @__PURE__ */ new Map();
5103
- for (const point of this.segmentPointMap.values()) {
5104
- if (!pointsBySegment.has(point.segmentId)) {
5105
- pointsBySegment.set(point.segmentId, []);
5580
+ for (const node of this.multiLayerNodes) {
5581
+ graphics.rects.push({
5582
+ center: node.center,
5583
+ width: node.width * 0.9,
5584
+ height: node.height * 0.9,
5585
+ fill: "rgba(100, 255, 100, 0.5)",
5586
+ stroke: "rgba(0, 0, 0, 0.5)",
5587
+ layer: `z${node.availableZ.join(",")}`,
5588
+ label: `${node.capacityMeshNodeId}
5589
+ Layers: ${node.availableZ.join(",")}
5590
+ ${node.width}x${node.height}`
5591
+ });
5592
+ }
5593
+ return graphics;
5594
+ }
5595
+ };
5596
+
5597
+ // lib/solvers/SingleLayerNodeMerger/SingleLayerNodeMergerSolver.ts
5598
+ var EPSILON = 5e-3;
5599
+ var SingleLayerNodeMergerSolver = class extends BaseSolver {
5600
+ nodeMap;
5601
+ currentBatchNodeIds;
5602
+ absorbedNodeIds;
5603
+ nextBatchNodeIds;
5604
+ batchHadModifications;
5605
+ newNodes;
5606
+ constructor(nodes) {
5607
+ super();
5608
+ this.nodeMap = /* @__PURE__ */ new Map();
5609
+ this.MAX_ITERATIONS = 1e5;
5610
+ for (const node of nodes) {
5611
+ this.nodeMap.set(node.capacityMeshNodeId, node);
5612
+ }
5613
+ this.newNodes = [];
5614
+ this.absorbedNodeIds = /* @__PURE__ */ new Set();
5615
+ const nodeWithArea = [];
5616
+ for (const node of nodes) {
5617
+ if (node.availableZ.length > 1) {
5618
+ this.newNodes.push(node);
5619
+ this.absorbedNodeIds.add(node.capacityMeshNodeId);
5620
+ } else {
5621
+ nodeWithArea.push([node.capacityMeshNodeId, node.width * node.height]);
5106
5622
  }
5107
- pointsBySegment.get(point.segmentId).push(point);
5108
5623
  }
5109
- for (const [segmentId, points] of pointsBySegment.entries()) {
5110
- if (points.length < 2) continue;
5111
- const sortedPoints = [...points].sort(
5112
- (a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y
5113
- );
5114
- for (let i = 0; i < sortedPoints.length - 1; i++) {
5115
- graphics.lines.push({
5116
- points: [
5117
- { x: sortedPoints[i].x, y: sortedPoints[i].y },
5118
- { x: sortedPoints[i + 1].x, y: sortedPoints[i + 1].y }
5119
- ],
5120
- strokeColor: this.colorMap[segmentId] || "#000"
5624
+ nodeWithArea.sort((a, b) => a[1] - b[1]);
5625
+ this.currentBatchNodeIds = nodeWithArea.map((n) => n[0]);
5626
+ this.nextBatchNodeIds = [];
5627
+ this.batchHadModifications = false;
5628
+ }
5629
+ getAdjacentSameLayerUnprocessedNodes(rootNode) {
5630
+ const adjacentNodes = [];
5631
+ for (const unprocessedNodeId of this.currentBatchNodeIds) {
5632
+ if (this.absorbedNodeIds.has(unprocessedNodeId)) continue;
5633
+ const unprocessedNode = this.nodeMap.get(unprocessedNodeId);
5634
+ if (unprocessedNode.availableZ[0] !== rootNode.availableZ[0]) continue;
5635
+ if (unprocessedNode._containsTarget && unprocessedNode._targetConnectionName !== rootNode._targetConnectionName)
5636
+ continue;
5637
+ if (!areNodesBordering(rootNode, unprocessedNode)) continue;
5638
+ adjacentNodes.push(unprocessedNode);
5639
+ }
5640
+ return adjacentNodes;
5641
+ }
5642
+ _step() {
5643
+ let rootNodeId = this.currentBatchNodeIds.pop();
5644
+ while (rootNodeId && this.absorbedNodeIds.has(rootNodeId)) {
5645
+ rootNodeId = this.currentBatchNodeIds.pop();
5646
+ }
5647
+ if (!rootNodeId) {
5648
+ if (this.batchHadModifications) {
5649
+ this.currentBatchNodeIds = this.nextBatchNodeIds.sort((a, b) => {
5650
+ const A = this.nodeMap.get(a);
5651
+ const B = this.nodeMap.get(b);
5652
+ return A.width * A.height - B.width * B.height;
5121
5653
  });
5654
+ this.nextBatchNodeIds = [];
5655
+ this.batchHadModifications = false;
5656
+ return;
5122
5657
  }
5658
+ this.solved = true;
5659
+ this.newNodes.push(
5660
+ ...this.nextBatchNodeIds.map((id) => this.nodeMap.get(id))
5661
+ );
5662
+ return;
5123
5663
  }
5124
- const processedConnections = /* @__PURE__ */ new Set();
5125
- const allPoints = Array.from(this.segmentPointMap.values());
5126
- for (let i = 0; i < allPoints.length; i++) {
5127
- const point1 = allPoints[i];
5128
- for (let j = i + 1; j < allPoints.length; j++) {
5129
- const point2 = allPoints[j];
5130
- if (point1.connectionName !== point2.connectionName || point1.segmentId === point2.segmentId) {
5131
- continue;
5664
+ const rootNode = this.nodeMap.get(rootNodeId);
5665
+ let rootNodeHasGrown = false;
5666
+ const adjacentNodes = this.getAdjacentSameLayerUnprocessedNodes(rootNode);
5667
+ if (adjacentNodes.length === 0) {
5668
+ this.nextBatchNodeIds.push(rootNodeId);
5669
+ return;
5670
+ }
5671
+ const adjacentNodesToLeft = adjacentNodes.filter(
5672
+ (adjNode) => adjNode.center.x < rootNode.center.x && Math.abs(adjNode.center.y - rootNode.center.y) < rootNode.height / 2
5673
+ );
5674
+ if (adjacentNodesToLeft.length > 0) {
5675
+ const { width: leftAdjNodeWidth, height: leftAdjNodeHeight } = adjacentNodesToLeft[0];
5676
+ const leftAdjNodesAreAllSameSize = adjacentNodesToLeft.every(
5677
+ (adjNode) => adjNode.width === leftAdjNodeWidth && adjNode.height === leftAdjNodeHeight
5678
+ );
5679
+ const leftAdjNodesTakeUpEntireHeight = Math.abs(
5680
+ adjacentNodesToLeft.reduce((acc, adjNode) => {
5681
+ return acc + adjNode.height;
5682
+ }, 0) - rootNode.height
5683
+ ) < EPSILON;
5684
+ if (leftAdjNodesTakeUpEntireHeight && leftAdjNodesAreAllSameSize) {
5685
+ rootNode.width += leftAdjNodeWidth;
5686
+ rootNode.center.x = rootNode.center.x - leftAdjNodeWidth / 2;
5687
+ for (const adjNode of adjacentNodesToLeft) {
5688
+ this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
5132
5689
  }
5133
- const hasSharedNode = point1.capacityMeshNodeIds.some(
5134
- (nodeId) => point2.capacityMeshNodeIds.includes(nodeId)
5135
- );
5136
- if (hasSharedNode) {
5137
- const connectionKey = `${point1.segmentPointId}-${point2.segmentPointId}`;
5138
- if (processedConnections.has(connectionKey)) continue;
5139
- processedConnections.add(connectionKey);
5140
- const sameLayer = point1.z === point2.z;
5141
- const layer = point1.z;
5142
- let strokeDash;
5143
- if (sameLayer) {
5144
- strokeDash = layer === 0 ? void 0 : "10 5";
5145
- } else {
5146
- strokeDash = "3 3 10";
5147
- }
5148
- graphics.lines.push({
5149
- points: [
5150
- { x: point1.x, y: point1.y },
5151
- { x: point2.x, y: point2.y }
5152
- ],
5153
- strokeDash,
5154
- strokeColor: this.colorMap[point1.connectionName] || "#666"
5155
- });
5690
+ rootNodeHasGrown = true;
5691
+ }
5692
+ }
5693
+ const adjacentNodesToRight = adjacentNodes.filter(
5694
+ (adjNode) => adjNode.center.x > rootNode.center.x && Math.abs(adjNode.center.y - rootNode.center.y) < rootNode.height / 2
5695
+ );
5696
+ if (adjacentNodesToRight.length > 0 && !rootNodeHasGrown) {
5697
+ const { width: rightAdjNodeWidth, height: rightAdjNodeHeight } = adjacentNodesToRight[0];
5698
+ const rightAdjNodesAreAllSameSize = adjacentNodesToRight.every(
5699
+ (adjNode) => adjNode.width === rightAdjNodeWidth && adjNode.height === rightAdjNodeHeight
5700
+ );
5701
+ const rightAdjNodesTakeUpEntireHeight = Math.abs(
5702
+ adjacentNodesToRight.reduce((acc, adjNode) => {
5703
+ return acc + adjNode.height;
5704
+ }, 0) - rootNode.height
5705
+ ) < EPSILON;
5706
+ if (rightAdjNodesTakeUpEntireHeight && rightAdjNodesAreAllSameSize) {
5707
+ rootNode.width += rightAdjNodeWidth;
5708
+ rootNode.center.x = rootNode.center.x + rightAdjNodeWidth / 2;
5709
+ for (const adjNode of adjacentNodesToRight) {
5710
+ this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
5156
5711
  }
5712
+ rootNodeHasGrown = true;
5157
5713
  }
5158
5714
  }
5159
- return graphics;
5160
- }
5161
- getNodesWithPortPoints() {
5162
- if (!this.solved) {
5163
- throw new Error(
5164
- "CapacitySegmentToPointSolver not solved, can't give port points yet"
5715
+ const adjacentNodesToTop = adjacentNodes.filter(
5716
+ (adjNode) => adjNode.center.y > rootNode.center.y && Math.abs(adjNode.center.x - rootNode.center.x) < rootNode.width / 2
5717
+ );
5718
+ if (adjacentNodesToTop.length > 0 && !rootNodeHasGrown) {
5719
+ const { width: topAdjNodeWidth, height: topAdjNodeHeight } = adjacentNodesToTop[0];
5720
+ const topAdjNodesAreAllSameSize = adjacentNodesToTop.every(
5721
+ (adjNode) => adjNode.width === topAdjNodeWidth && adjNode.height === topAdjNodeHeight
5165
5722
  );
5723
+ const topAdjNodesTakeUpEntireWidth = Math.abs(
5724
+ adjacentNodesToTop.reduce((acc, adjNode) => {
5725
+ return acc + adjNode.width;
5726
+ }, 0) - rootNode.width
5727
+ ) < EPSILON;
5728
+ if (topAdjNodesTakeUpEntireWidth && topAdjNodesAreAllSameSize) {
5729
+ rootNode.height += topAdjNodeHeight;
5730
+ rootNode.center.y = rootNode.center.y + topAdjNodeHeight / 2;
5731
+ for (const adjNode of adjacentNodesToTop) {
5732
+ this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
5733
+ }
5734
+ rootNodeHasGrown = true;
5735
+ }
5166
5736
  }
5167
- const nodeWithPortPointsMap = /* @__PURE__ */ new Map();
5168
- for (const segment of this.dedupedSegments) {
5169
- const segId = segment.nodePortSegmentId;
5170
- for (const nodeId of this.segmentIdToNodeIds.get(segId)) {
5171
- const node = this.nodeMap.get(nodeId);
5172
- if (!nodeWithPortPointsMap.has(nodeId)) {
5173
- nodeWithPortPointsMap.set(nodeId, {
5174
- capacityMeshNodeId: nodeId,
5175
- portPoints: [],
5176
- center: node.center,
5177
- width: node.width,
5178
- height: node.height
5179
- });
5737
+ const adjacentNodesToBottom = adjacentNodes.filter(
5738
+ (adjNode) => adjNode.center.y < rootNode.center.y && Math.abs(adjNode.center.x - rootNode.center.x) < rootNode.width / 2
5739
+ );
5740
+ if (adjacentNodesToBottom.length > 0 && !rootNodeHasGrown) {
5741
+ const { width: bottomAdjNodeWidth, height: bottomAdjNodeHeight } = adjacentNodesToBottom[0];
5742
+ const bottomAdjNodesAreAllSameSize = adjacentNodesToBottom.every(
5743
+ (adjNode) => adjNode.width === bottomAdjNodeWidth && adjNode.height === bottomAdjNodeHeight
5744
+ );
5745
+ const bottomAdjNodesTakeUpEntireWidth = Math.abs(
5746
+ adjacentNodesToBottom.reduce((acc, adjNode) => {
5747
+ return acc + adjNode.width;
5748
+ }, 0) - rootNode.width
5749
+ ) < EPSILON;
5750
+ if (bottomAdjNodesTakeUpEntireWidth && bottomAdjNodesAreAllSameSize) {
5751
+ rootNode.height += bottomAdjNodeHeight;
5752
+ rootNode.center.y = rootNode.center.y - bottomAdjNodeHeight / 2;
5753
+ for (const adjNode of adjacentNodesToBottom) {
5754
+ this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
5180
5755
  }
5756
+ rootNodeHasGrown = true;
5181
5757
  }
5182
5758
  }
5183
- for (const segmentPoint of this.segmentPointMap.values()) {
5184
- for (const nodeId of segmentPoint.capacityMeshNodeIds) {
5185
- const nodeWithPortPoints = nodeWithPortPointsMap.get(nodeId);
5186
- if (nodeWithPortPoints) {
5187
- nodeWithPortPoints.portPoints.push({
5188
- x: segmentPoint.x,
5189
- y: segmentPoint.y,
5190
- z: segmentPoint.z,
5191
- connectionName: segmentPoint.connectionName
5192
- });
5759
+ if (rootNodeHasGrown) {
5760
+ this.batchHadModifications = true;
5761
+ this.currentBatchNodeIds.push(rootNodeId);
5762
+ } else {
5763
+ this.nextBatchNodeIds.unshift(rootNodeId);
5764
+ }
5765
+ }
5766
+ visualize() {
5767
+ const graphics = {
5768
+ circles: [],
5769
+ lines: [],
5770
+ points: [],
5771
+ rects: [],
5772
+ coordinateSystem: "cartesian",
5773
+ title: "Same Layer Node Merger"
5774
+ };
5775
+ for (const node of this.newNodes) {
5776
+ graphics.rects.push(createRectFromCapacityNode(node));
5777
+ }
5778
+ const nextNodeIdInBatch = this.currentBatchNodeIds[this.currentBatchNodeIds.length - 1];
5779
+ let adjacentNodes;
5780
+ if (nextNodeIdInBatch) {
5781
+ adjacentNodes = this.getAdjacentSameLayerUnprocessedNodes(
5782
+ this.nodeMap.get(nextNodeIdInBatch)
5783
+ );
5784
+ }
5785
+ for (const nodeId of this.currentBatchNodeIds) {
5786
+ const node = this.nodeMap.get(nodeId);
5787
+ if (this.absorbedNodeIds.has(nodeId)) continue;
5788
+ if (node) {
5789
+ const rect = createRectFromCapacityNode(node, {
5790
+ rectMargin: 0.01
5791
+ });
5792
+ if (nodeId === nextNodeIdInBatch) {
5793
+ rect.stroke = "rgba(0, 255, 0, 0.8)";
5794
+ } else if (adjacentNodes?.some(
5795
+ (adjNode) => adjNode.capacityMeshNodeId === nodeId
5796
+ )) {
5797
+ rect.stroke = "rgba(128, 0, 128, 0.8)";
5798
+ } else {
5799
+ rect.stroke = "rgba(255, 165, 0, 0.8)";
5193
5800
  }
5801
+ rect.layer = `z${node.availableZ.join(",")}`;
5802
+ rect.label = `${rect.label}
5803
+ (unprocessed)`;
5804
+ graphics.rects.push(rect);
5194
5805
  }
5195
5806
  }
5196
- return Array.from(nodeWithPortPointsMap.values());
5807
+ for (const nodeId of this.nextBatchNodeIds) {
5808
+ const node = this.nodeMap.get(nodeId);
5809
+ if (this.absorbedNodeIds.has(nodeId)) continue;
5810
+ if (node) {
5811
+ const rect = createRectFromCapacityNode(node, {
5812
+ rectMargin: 0.01
5813
+ });
5814
+ rect.layer = `z${node.availableZ.join(",")}`;
5815
+ rect.stroke = "rgba(0, 217, 255, 0.8)";
5816
+ rect.label = `${rect.label}
5817
+ x: ${node.center.x}, y: ${node.center.y}
5818
+ ${node.width}x${node.height}
5819
+ (next batch)`;
5820
+ graphics.rects.push(rect);
5821
+ }
5822
+ }
5823
+ return graphics;
5197
5824
  }
5198
5825
  };
5199
5826
 
5200
- // lib/solvers/CapacityMeshSolver/CapacityMeshSolver.ts
5827
+ // lib/solvers/AutoroutingPipelineSolver.ts
5201
5828
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
5202
5829
  return {
5203
5830
  solverName,
@@ -5240,12 +5867,15 @@ var CapacityMeshSolver = class extends BaseSolver {
5240
5867
  segmentToPointOptimizer;
5241
5868
  highDensityRouteSolver;
5242
5869
  highDensityStitchSolver;
5870
+ singleLayerNodeMerger;
5871
+ strawSolver;
5243
5872
  startTimeOfPhase;
5244
5873
  endTimeOfPhase;
5245
5874
  timeSpentOnPhase;
5246
- activeSolver = null;
5875
+ activeSubSolver = null;
5247
5876
  connMap;
5248
5877
  srjWithPointPairs;
5878
+ capacityNodes = null;
5249
5879
  pipelineDef = [
5250
5880
  definePipelineStep(
5251
5881
  "netToPointPairsSolver",
@@ -5261,39 +5891,65 @@ var CapacityMeshSolver = class extends BaseSolver {
5261
5891
  }
5262
5892
  }
5263
5893
  ),
5264
- definePipelineStep("nodeSolver", CapacityMeshNodeSolver, (cms) => [
5265
- cms.netToPointPairsSolver?.getNewSimpleRouteJson() || cms.srj,
5266
- cms.opts
5267
- ]),
5268
- definePipelineStep("nodeTargetMerger", CapacityNodeTargetMerger, (cms) => [
5269
- cms.nodeSolver?.finishedNodes || [],
5270
- cms.srj.obstacles,
5271
- cms.connMap
5272
- ]),
5273
- definePipelineStep("edgeSolver", CapacityMeshEdgeSolver, (cms) => [
5274
- cms.nodeTargetMerger?.newNodes || []
5275
- ]),
5894
+ // definePipelineStep("nodeSolver", CapacityMeshNodeSolver, (cms) => [
5895
+ // cms.netToPointPairsSolver?.getNewSimpleRouteJson() || cms.srj,
5896
+ // cms.opts,
5897
+ // ]),
5276
5898
  definePipelineStep(
5277
- "pathingSolver",
5278
- CapacityPathingSolver4_FlexibleNegativeCapacity,
5899
+ "nodeSolver",
5900
+ CapacityMeshNodeSolver2_NodeUnderObstacle,
5279
5901
  (cms) => [
5280
- {
5281
- simpleRouteJson: cms.srjWithPointPairs,
5282
- nodes: cms.nodeTargetMerger?.newNodes || [],
5283
- edges: cms.edgeSolver?.edges || [],
5284
- colorMap: cms.colorMap,
5285
- hyperParameters: {
5286
- MAX_CAPACITY_FACTOR: 1
5287
- }
5288
- }
5902
+ cms.netToPointPairsSolver?.getNewSimpleRouteJson() || cms.srj,
5903
+ cms.opts
5289
5904
  ]
5290
5905
  ),
5906
+ // definePipelineStep("nodeTargetMerger", CapacityNodeTargetMerger, (cms) => [
5907
+ // cms.nodeSolver?.finishedNodes || [],
5908
+ // cms.srj.obstacles,
5909
+ // cms.connMap,
5910
+ // ]),
5911
+ // definePipelineStep("nodeTargetMerger", CapacityNodeTargetMerger2, (cms) => [
5912
+ // cms.nodeSolver?.finishedNodes || [],
5913
+ // cms.srj.obstacles,
5914
+ // cms.connMap,
5915
+ // cms.colorMap,
5916
+ // cms.srj.connections,
5917
+ // ]),
5918
+ definePipelineStep(
5919
+ "singleLayerNodeMerger",
5920
+ SingleLayerNodeMergerSolver,
5921
+ (cms) => [cms.nodeSolver?.finishedNodes]
5922
+ ),
5923
+ definePipelineStep(
5924
+ "strawSolver",
5925
+ StrawSolver,
5926
+ (cms) => [{ nodes: cms.singleLayerNodeMerger?.newNodes }],
5927
+ {
5928
+ onSolved: (cms) => {
5929
+ cms.capacityNodes = cms.strawSolver?.getResultNodes();
5930
+ }
5931
+ }
5932
+ ),
5933
+ definePipelineStep("edgeSolver", CapacityMeshEdgeSolver, (cms) => [
5934
+ cms.capacityNodes
5935
+ ]),
5936
+ definePipelineStep("pathingSolver", CapacityPathingSolver5, (cms) => [
5937
+ {
5938
+ simpleRouteJson: cms.srjWithPointPairs,
5939
+ nodes: cms.capacityNodes,
5940
+ edges: cms.edgeSolver?.edges || [],
5941
+ colorMap: cms.colorMap,
5942
+ hyperParameters: {
5943
+ MAX_CAPACITY_FACTOR: 1
5944
+ }
5945
+ }
5946
+ ]),
5291
5947
  definePipelineStep(
5292
5948
  "edgeToPortSegmentSolver",
5293
5949
  CapacityEdgeToPortSegmentSolver,
5294
5950
  (cms) => [
5295
5951
  {
5296
- nodes: cms.nodeTargetMerger?.newNodes || [],
5952
+ nodes: cms.capacityNodes,
5297
5953
  edges: cms.edgeSolver?.edges || [],
5298
5954
  capacityPaths: cms.pathingSolver?.getCapacityPaths() || [],
5299
5955
  colorMap: cms.colorMap
@@ -5314,7 +5970,7 @@ var CapacityMeshSolver = class extends BaseSolver {
5314
5970
  {
5315
5971
  segments: allSegments,
5316
5972
  colorMap: cms.colorMap,
5317
- nodes: cms.nodeTargetMerger?.newNodes || []
5973
+ nodes: cms.capacityNodes
5318
5974
  }
5319
5975
  ];
5320
5976
  }
@@ -5337,7 +5993,7 @@ var CapacityMeshSolver = class extends BaseSolver {
5337
5993
  {
5338
5994
  assignedSegments: cms.segmentToPointSolver?.solvedSegments || [],
5339
5995
  colorMap: cms.colorMap,
5340
- nodes: cms.nodeTargetMerger?.newNodes || []
5996
+ nodes: cms.capacityNodes
5341
5997
  }
5342
5998
  ]
5343
5999
  ),
@@ -5367,26 +6023,24 @@ var CapacityMeshSolver = class extends BaseSolver {
5367
6023
  this.solved = true;
5368
6024
  return;
5369
6025
  }
5370
- if (this.activeSolver) {
5371
- this.activeSolver.step();
5372
- if (this.activeSolver.solved) {
6026
+ if (this.activeSubSolver) {
6027
+ this.activeSubSolver.step();
6028
+ if (this.activeSubSolver.solved) {
5373
6029
  this.endTimeOfPhase[pipelineStepDef.solverName] = performance.now();
5374
6030
  this.timeSpentOnPhase[pipelineStepDef.solverName] = this.endTimeOfPhase[pipelineStepDef.solverName] - this.startTimeOfPhase[pipelineStepDef.solverName];
5375
6031
  pipelineStepDef.onSolved?.(this);
5376
- this.activeSolver = null;
6032
+ this.activeSubSolver = null;
5377
6033
  this.currentPipelineStepIndex++;
5378
- } else if (this.activeSolver.failed) {
5379
- this.error = this.activeSolver?.error;
6034
+ } else if (this.activeSubSolver.failed) {
6035
+ this.error = this.activeSubSolver?.error;
5380
6036
  this.failed = true;
5381
- this.activeSolver = null;
6037
+ this.activeSubSolver = null;
5382
6038
  }
5383
6039
  return;
5384
6040
  }
5385
6041
  const constructorParams = pipelineStepDef.getConstructorParams(this);
5386
- this.activeSolver = new pipelineStepDef.solverClass(
5387
- ...constructorParams
5388
- );
5389
- this[pipelineStepDef.solverName] = this.activeSolver;
6042
+ this.activeSubSolver = new pipelineStepDef.solverClass(...constructorParams);
6043
+ this[pipelineStepDef.solverName] = this.activeSubSolver;
5390
6044
  this.timeSpentOnPhase[pipelineStepDef.solverName] = 0;
5391
6045
  this.startTimeOfPhase[pipelineStepDef.solverName] = performance.now();
5392
6046
  }
@@ -5394,9 +6048,13 @@ var CapacityMeshSolver = class extends BaseSolver {
5394
6048
  return this.pipelineDef[this.currentPipelineStepIndex]?.solverName ?? "none";
5395
6049
  }
5396
6050
  visualize() {
5397
- if (!this.solved && this.activeSolver) return this.activeSolver.visualize();
6051
+ if (!this.solved && this.activeSubSolver)
6052
+ return this.activeSubSolver.visualize();
5398
6053
  const netToPPSolver = this.netToPointPairsSolver?.visualize();
5399
6054
  const nodeViz = this.nodeSolver?.visualize();
6055
+ const nodeTargetMergerViz = this.nodeTargetMerger?.visualize();
6056
+ const singleLayerNodeMergerViz = this.singleLayerNodeMerger?.visualize();
6057
+ const strawSolverViz = this.strawSolver?.visualize();
5400
6058
  const edgeViz = this.edgeSolver?.visualize();
5401
6059
  const pathingViz = this.pathingSolver?.visualize();
5402
6060
  const edgeToPortSegmentViz = this.edgeToPortSegmentSolver?.visualize();
@@ -5409,14 +6067,37 @@ var CapacityMeshSolver = class extends BaseSolver {
5409
6067
  rects: [
5410
6068
  ...(this.srj.obstacles ?? []).map((o) => ({
5411
6069
  ...o,
5412
- fill: "rgba(255,0,0,0.25)"
6070
+ fill: o.layers?.includes("top") ? "rgba(255,0,0,0.25)" : o.layers?.includes("bottom") ? "rgba(0,0,255,0.25)" : "rgba(255,0,0,0.25)"
5413
6071
  }))
6072
+ ],
6073
+ lines: [
6074
+ {
6075
+ points: [
6076
+ // Add five points representing the bounds of the PCB
6077
+ {
6078
+ x: this.srj.bounds?.minX ?? -50,
6079
+ y: this.srj.bounds?.minY ?? -50
6080
+ },
6081
+ { x: this.srj.bounds?.maxX ?? 50, y: this.srj.bounds?.minY ?? -50 },
6082
+ { x: this.srj.bounds?.maxX ?? 50, y: this.srj.bounds?.maxY ?? 50 },
6083
+ { x: this.srj.bounds?.minX ?? -50, y: this.srj.bounds?.maxY ?? 50 },
6084
+ {
6085
+ x: this.srj.bounds?.minX ?? -50,
6086
+ y: this.srj.bounds?.minY ?? -50
6087
+ }
6088
+ // Close the rectangle
6089
+ ],
6090
+ strokeColor: "rgba(255,0,0,0.25)"
6091
+ }
5414
6092
  ]
5415
6093
  };
5416
6094
  const visualizations = [
5417
6095
  problemViz,
5418
6096
  netToPPSolver,
5419
6097
  nodeViz,
6098
+ nodeTargetMergerViz,
6099
+ singleLayerNodeMergerViz,
6100
+ strawSolverViz,
5420
6101
  edgeViz,
5421
6102
  pathingViz,
5422
6103
  edgeToPortSegmentViz,