@tscircuit/capacity-autorouter 0.0.21 → 0.0.23

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]) && this.doNodesHaveSharedLayer(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,20 +196,6 @@ 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;
196
- }
197
199
  doNodesHaveSharedLayer(node1, node2) {
198
200
  return node1.availableZ.some((z) => node2.availableZ.includes(z));
199
201
  }
@@ -233,8 +235,19 @@ var CapacityMeshEdgeSolver = class extends BaseSolver {
233
235
  (node) => node.capacityMeshNodeId === edge.nodeIds[1]
234
236
  );
235
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
+ };
236
248
  graphics.lines.push({
237
- points: [node1.center, node2.center]
249
+ points: [nodeCenter1Adj, nodeCenter2Adj],
250
+ strokeDash: node1.availableZ.join(",") === node2.availableZ.join(",") ? void 0 : "10 5"
238
251
  });
239
252
  }
240
253
  }
@@ -1168,6 +1181,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1168
1181
  getTargetIfNodeContainsTarget(node) {
1169
1182
  const overlappingObstacles = this.getXYOverlappingObstacles(node);
1170
1183
  for (const target of this.targets) {
1184
+ if (!target.availableZ.some((z) => node.availableZ.includes(z))) continue;
1171
1185
  const targetObstacle = overlappingObstacles.find(
1172
1186
  (o) => isPointInRect(target, o)
1173
1187
  );
@@ -1264,9 +1278,6 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1264
1278
  return true;
1265
1279
  }
1266
1280
  }
1267
- if (nodeRight < this.srj.bounds.minX || nodeLeft > this.srj.bounds.maxX || nodeBottom < this.srj.bounds.minY || nodeTop > this.srj.bounds.maxY) {
1268
- return true;
1269
- }
1270
1281
  return false;
1271
1282
  }
1272
1283
  getChildNodes(parent) {
@@ -1370,14 +1381,15 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1370
1381
  center: obstacle.center,
1371
1382
  width: obstacle.width,
1372
1383
  height: obstacle.height,
1373
- 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)",
1374
1385
  stroke: "red",
1375
- label: ["obstacle", obstacle.zLayers.join(",")].join("\n")
1386
+ label: ["obstacle", `z: ${obstacle.zLayers.join(",")}`].join("\n")
1376
1387
  });
1377
1388
  }
1378
1389
  const allNodes = [...this.finishedNodes, ...this.unfinishedNodes];
1379
1390
  for (const node of allNodes) {
1380
1391
  const lowestZ = Math.min(...node.availableZ);
1392
+ const isNextToBeProcessed = this.unfinishedNodes.length > 0 && node === this.unfinishedNodes[this.unfinishedNodes.length - 1];
1381
1393
  graphics.rects.push({
1382
1394
  center: {
1383
1395
  x: node.center.x + lowestZ * node.width * 0.05,
@@ -1390,6 +1402,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1390
1402
  "0": "rgba(0,200,200, 0.1)",
1391
1403
  "1": "rgba(0,0,200, 0.1)"
1392
1404
  }[node.availableZ.join(",")] ?? "rgba(0,200,200,0.1)",
1405
+ stroke: isNextToBeProcessed ? "rgba(255,165,0,0.5)" : void 0,
1393
1406
  label: [
1394
1407
  node.capacityMeshNodeId,
1395
1408
  `availableZ: ${node.availableZ.join(",")}`,
@@ -1414,6 +1427,146 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1414
1427
  }
1415
1428
  };
1416
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
+
1417
1570
  // lib/solvers/CapacityMeshSolver/getNodeEdgeMap.ts
1418
1571
  function getNodeEdgeMap(edges) {
1419
1572
  const nodeEdgeMap = /* @__PURE__ */ new Map();
@@ -1712,7 +1865,10 @@ var CapacitySegmentToPointSolver = class extends BaseSolver {
1712
1865
  (seg) => seg.assignedPoints.map((ap) => ({
1713
1866
  x: ap.point.x,
1714
1867
  y: ap.point.y,
1715
- label: `${seg.capacityMeshNodeId}-${ap.connectionName}`,
1868
+ label: [
1869
+ `${seg.capacityMeshNodeId}-${ap.connectionName}`,
1870
+ `z: ${seg.availableZ.join(",")}`
1871
+ ].join("\n"),
1716
1872
  color: this.colorMap[ap.connectionName],
1717
1873
  step: 4
1718
1874
  }))
@@ -1904,7 +2060,7 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
1904
2060
  const margin = isVia ? this.viaDiameter / 2 : this.obstacleMargin;
1905
2061
  const tooClose = node.x < this.bounds.minX + margin || node.x > this.bounds.maxX - margin || node.y < this.bounds.minY + margin || node.y > this.bounds.maxY - margin;
1906
2062
  if (tooClose && !isVia) {
1907
- if (distance(node, this.B) < margin * 2) {
2063
+ if (distance(node, this.B) < margin * 2 || distance(node, this.A) < margin * 2) {
1908
2064
  return false;
1909
2065
  }
1910
2066
  }
@@ -2884,294 +3040,94 @@ var HighDensitySolver = class extends BaseSolver {
2884
3040
  }
2885
3041
  };
2886
3042
 
2887
- // lib/solvers/CapacityPathingSolver/CapacityPathingSolver.ts
2888
- var CapacityPathingSolver = class extends BaseSolver {
2889
- connectionsWithNodes;
2890
- usedNodeCapacityMap;
2891
- simpleRouteJson;
2892
- nodes;
2893
- edges;
2894
- GREEDY_MULTIPLIER = 1.1;
2895
- nodeMap;
2896
- nodeEdgeMap;
2897
- connectionNameToGoalNodeIds;
2898
- colorMap;
2899
- maxDepthOfNodes;
2900
- activeCandidateStraightLineDistance;
2901
- hyperParameters;
2902
- constructor({
2903
- simpleRouteJson,
2904
- nodes,
2905
- edges,
2906
- colorMap,
2907
- MAX_ITERATIONS = 1e6,
2908
- hyperParameters = {}
2909
- }) {
2910
- super();
2911
- this.MAX_ITERATIONS = MAX_ITERATIONS;
2912
- this.simpleRouteJson = simpleRouteJson;
2913
- this.nodes = nodes;
2914
- this.edges = edges;
2915
- this.colorMap = colorMap ?? {};
2916
- const { connectionsWithNodes, connectionNameToGoalNodeIds } = this.getConnectionsWithNodes();
2917
- this.connectionsWithNodes = connectionsWithNodes;
2918
- this.connectionNameToGoalNodeIds = connectionNameToGoalNodeIds;
2919
- this.hyperParameters = hyperParameters;
2920
- this.usedNodeCapacityMap = new Map(
2921
- this.nodes.map((node) => [node.capacityMeshNodeId, 0])
2922
- );
2923
- this.nodeMap = new Map(
2924
- this.nodes.map((node) => [node.capacityMeshNodeId, node])
2925
- );
2926
- this.nodeEdgeMap = getNodeEdgeMap(this.edges);
2927
- this.maxDepthOfNodes = Math.max(
2928
- ...this.nodes.map((node) => node._depth ?? 0)
2929
- );
2930
- }
2931
- getTotalCapacity(node) {
2932
- const depth = node._depth ?? 0;
2933
- return (this.maxDepthOfNodes - depth + 1) ** 2;
3043
+ // node_modules/circuit-json-to-connectivity-map/dist/index.js
3044
+ var ConnectivityMap = class {
3045
+ netMap;
3046
+ idToNetMap;
3047
+ constructor(netMap) {
3048
+ this.netMap = netMap;
3049
+ this.idToNetMap = {};
3050
+ for (const [netId, ids] of Object.entries(netMap)) {
3051
+ for (const id of ids) {
3052
+ this.idToNetMap[id] = netId;
3053
+ }
3054
+ }
2934
3055
  }
2935
- getConnectionsWithNodes() {
2936
- const connectionsWithNodes = [];
2937
- const nodesWithTargets = this.nodes.filter((node) => node._containsTarget);
2938
- const connectionNameToGoalNodeIds = /* @__PURE__ */ new Map();
2939
- for (const connection of this.simpleRouteJson.connections) {
2940
- const nodesForConnection = [];
2941
- for (const point of connection.pointsToConnect) {
2942
- let closestNode = this.nodes[0];
2943
- let minDistance = Number.MAX_VALUE;
2944
- for (const node of nodesWithTargets) {
2945
- const distance3 = Math.sqrt(
2946
- (node.center.x - point.x) ** 2 + (node.center.y - point.y) ** 2
2947
- );
2948
- if (distance3 < minDistance) {
2949
- minDistance = distance3;
2950
- closestNode = node;
3056
+ addConnections(connections) {
3057
+ for (const connection of connections) {
3058
+ const existingNets = /* @__PURE__ */ new Set();
3059
+ for (const id of connection) {
3060
+ const existingNetId = this.idToNetMap[id];
3061
+ if (existingNetId) {
3062
+ existingNets.add(existingNetId);
3063
+ }
3064
+ }
3065
+ let targetNetId;
3066
+ if (existingNets.size === 0) {
3067
+ targetNetId = `connectivity_net${Object.keys(this.netMap).length}`;
3068
+ this.netMap[targetNetId] = [];
3069
+ } else if (existingNets.size === 1) {
3070
+ targetNetId = existingNets.values().next().value ?? `connectivity_net${Object.keys(this.netMap).length}`;
3071
+ } else {
3072
+ targetNetId = existingNets.values().next().value ?? `connectivity_net${Object.keys(this.netMap).length}`;
3073
+ for (const netId of existingNets) {
3074
+ if (netId !== targetNetId) {
3075
+ this.netMap[targetNetId].push(...this.netMap[netId]);
3076
+ this.netMap[netId] = this.netMap[targetNetId];
3077
+ for (const id of this.netMap[targetNetId]) {
3078
+ this.idToNetMap[id] = targetNetId;
3079
+ }
2951
3080
  }
2952
3081
  }
2953
- nodesForConnection.push(closestNode);
2954
3082
  }
2955
- if (nodesForConnection.length < 2) {
2956
- throw new Error(
2957
- `Not enough nodes for connection "${connection.name}", only ${nodesForConnection.length} found`
2958
- );
3083
+ for (const id of connection) {
3084
+ if (!this.netMap[targetNetId].includes(id)) {
3085
+ this.netMap[targetNetId].push(id);
3086
+ }
3087
+ this.idToNetMap[id] = targetNetId;
2959
3088
  }
2960
- connectionNameToGoalNodeIds.set(
2961
- connection.name,
2962
- nodesForConnection.map((n) => n.capacityMeshNodeId)
2963
- );
2964
- connectionsWithNodes.push({
2965
- connection,
2966
- nodes: nodesForConnection,
2967
- pathFound: false
2968
- });
2969
3089
  }
2970
- return { connectionsWithNodes, connectionNameToGoalNodeIds };
2971
- }
2972
- currentConnectionIndex = 0;
2973
- candidates;
2974
- visitedNodes;
2975
- computeG(prevCandidate, node, endGoal) {
2976
- return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node);
2977
3090
  }
2978
- computeH(prevCandidate, node, endGoal) {
2979
- return this.getDistanceBetweenNodes(node, endGoal);
3091
+ getIdsConnectedToNet(netId) {
3092
+ return this.netMap[netId] || [];
2980
3093
  }
2981
- getBacktrackedPath(candidate) {
2982
- const path = [];
2983
- let currentCandidate = candidate;
2984
- while (currentCandidate) {
2985
- path.push(currentCandidate.node);
2986
- currentCandidate = currentCandidate.prevCandidate;
2987
- }
2988
- return path;
3094
+ getNetConnectedToId(id) {
3095
+ return this.idToNetMap[id];
2989
3096
  }
2990
- getNeighboringNodes(node) {
2991
- return this.nodeEdgeMap.get(node.capacityMeshNodeId).flatMap(
2992
- (edge) => edge.nodeIds.filter((n) => n !== node.capacityMeshNodeId)
2993
- ).map((n) => this.nodeMap.get(n));
3097
+ areIdsConnected(id1, id2) {
3098
+ if (id1 === id2) return true;
3099
+ const netId1 = this.getNetConnectedToId(id1);
3100
+ if (!netId1) return false;
3101
+ const netId2 = this.getNetConnectedToId(id2);
3102
+ if (!netId2) return false;
3103
+ return netId1 === netId2 || netId2 === id1 || netId2 === id1;
2994
3104
  }
2995
- getCapacityPaths() {
2996
- const capacityPaths = [];
2997
- for (const connection of this.connectionsWithNodes) {
2998
- const path = connection.path;
2999
- if (path) {
3000
- capacityPaths.push({
3001
- capacityPathId: connection.connection.name,
3002
- connectionName: connection.connection.name,
3003
- nodeIds: path.map((node) => node.capacityMeshNodeId)
3004
- });
3105
+ areAllIdsConnected(ids) {
3106
+ const netId = this.getNetConnectedToId(ids[0]);
3107
+ for (const id of ids) {
3108
+ const nextNetId = this.getNetConnectedToId(id);
3109
+ if (nextNetId === void 0) {
3110
+ return false;
3111
+ }
3112
+ if (nextNetId !== netId) {
3113
+ return false;
3005
3114
  }
3006
3115
  }
3007
- return capacityPaths;
3116
+ return true;
3008
3117
  }
3009
- doesNodeHaveCapacityForTrace(node) {
3010
- const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
3011
- const totalCapacity = this.getTotalCapacity(node);
3012
- return usedCapacity < totalCapacity;
3013
- }
3014
- canTravelThroughObstacle(node, connectionName) {
3015
- const goalNodeIds = this.connectionNameToGoalNodeIds.get(connectionName);
3016
- return goalNodeIds?.includes(node.capacityMeshNodeId) ?? false;
3017
- }
3018
- getDistanceBetweenNodes(A, B) {
3019
- return Math.sqrt(
3020
- (A.center.x - B.center.x) ** 2 + (A.center.y - B.center.y) ** 2
3021
- );
3022
- }
3023
- reduceCapacityAlongPath(nextConnection) {
3024
- for (const node of nextConnection.path ?? []) {
3025
- this.usedNodeCapacityMap.set(
3026
- node.capacityMeshNodeId,
3027
- this.usedNodeCapacityMap.get(node.capacityMeshNodeId) + 1
3028
- );
3029
- }
3030
- }
3031
- isConnectedToEndGoal(node, endGoal) {
3032
- return this.nodeEdgeMap.get(node.capacityMeshNodeId).some((edge) => edge.nodeIds.includes(endGoal.capacityMeshNodeId));
3033
- }
3034
- _step() {
3035
- const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
3036
- if (!nextConnection) {
3037
- this.solved = true;
3038
- return;
3039
- }
3040
- const [start, end] = nextConnection.nodes;
3041
- if (!this.candidates) {
3042
- this.candidates = [{ prevCandidate: null, node: start, f: 0, g: 0, h: 0 }];
3043
- this.visitedNodes = /* @__PURE__ */ new Set([start.capacityMeshNodeId]);
3044
- this.activeCandidateStraightLineDistance = distance(
3045
- start.center,
3046
- end.center
3047
- );
3048
- }
3049
- this.candidates.sort((a, b) => a.f - b.f);
3050
- const currentCandidate = this.candidates.shift();
3051
- if (!currentCandidate) {
3052
- console.error(
3053
- `Ran out of candidates on connection ${nextConnection.connection.name}`
3054
- );
3055
- this.currentConnectionIndex++;
3056
- this.candidates = null;
3057
- this.visitedNodes = null;
3058
- return;
3059
- }
3060
- if (this.isConnectedToEndGoal(currentCandidate.node, end)) {
3061
- nextConnection.path = this.getBacktrackedPath({
3062
- prevCandidate: currentCandidate,
3063
- node: end,
3064
- f: 0,
3065
- g: 0,
3066
- h: 0
3067
- });
3068
- this.reduceCapacityAlongPath(nextConnection);
3069
- this.currentConnectionIndex++;
3070
- this.candidates = null;
3071
- this.visitedNodes = null;
3072
- return;
3073
- }
3074
- const neighborNodes = this.getNeighboringNodes(currentCandidate.node);
3075
- for (const neighborNode of neighborNodes) {
3076
- if (this.visitedNodes?.has(neighborNode.capacityMeshNodeId)) {
3077
- continue;
3078
- }
3079
- if (!this.doesNodeHaveCapacityForTrace(neighborNode)) {
3080
- continue;
3081
- }
3082
- const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
3083
- if (neighborNode._containsObstacle && !this.canTravelThroughObstacle(neighborNode, connectionName)) {
3084
- continue;
3085
- }
3086
- const g = this.computeG(currentCandidate, neighborNode, end);
3087
- const h = this.computeH(currentCandidate, neighborNode, end);
3088
- const f = g + h * this.GREEDY_MULTIPLIER;
3089
- const newCandidate = {
3090
- prevCandidate: currentCandidate,
3091
- node: neighborNode,
3092
- f,
3093
- g,
3094
- h
3095
- };
3096
- this.candidates.push(newCandidate);
3097
- }
3098
- this.visitedNodes.add(currentCandidate.node.capacityMeshNodeId);
3099
- }
3100
- visualize() {
3101
- const graphics = {
3102
- lines: [],
3103
- points: [],
3104
- rects: [],
3105
- circles: []
3106
- };
3107
- if (this.connectionsWithNodes) {
3108
- for (let i = 0; i < this.connectionsWithNodes.length; i++) {
3109
- const conn = this.connectionsWithNodes[i];
3110
- if (conn.path && conn.path.length > 0) {
3111
- const pathPoints = conn.path.map(({ center: { x, y }, width }) => ({
3112
- // slight offset to allow viewing overlapping paths
3113
- x: x + (i % 10 + i % 19) * (0.01 * width),
3114
- y: y + (i % 10 + i % 19) * (0.01 * width)
3115
- }));
3116
- graphics.lines.push({
3117
- points: pathPoints,
3118
- strokeColor: this.colorMap[conn.connection.name]
3119
- });
3120
- }
3121
- }
3122
- }
3123
- for (const node of this.nodes) {
3124
- graphics.rects.push({
3125
- center: node.center,
3126
- width: Math.max(node.width - 2, node.width * 0.8),
3127
- height: Math.max(node.height - 2, node.height * 0.8),
3128
- fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : "rgba(0,0,0,0.1)",
3129
- label: `${node.capacityMeshNodeId}
3130
- ${this.usedNodeCapacityMap.get(node.capacityMeshNodeId)}/${this.getTotalCapacity(node).toFixed(2)}
3131
- ${node.width.toFixed(2)}x${node.height.toFixed(2)}`
3132
- });
3133
- }
3134
- if (this.connectionsWithNodes) {
3135
- for (const conn of this.connectionsWithNodes) {
3136
- if (conn.connection?.pointsToConnect) {
3137
- for (const point of conn.connection.pointsToConnect) {
3138
- graphics.points.push({
3139
- x: point.x,
3140
- y: point.y
3141
- });
3142
- }
3143
- }
3118
+ };
3119
+
3120
+ // lib/utils/getConnectivityMapFromSimpleRouteJson.ts
3121
+ var getConnectivityMapFromSimpleRouteJson = (srj) => {
3122
+ const connMap = new ConnectivityMap({});
3123
+ for (const connection of srj.connections) {
3124
+ for (const point of connection.pointsToConnect) {
3125
+ if ("pcb_port_id" in point && point.pcb_port_id) {
3126
+ connMap.addConnections([[connection.name, point.pcb_port_id]]);
3144
3127
  }
3145
3128
  }
3146
- const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
3147
- if (nextConnection) {
3148
- const [start, end] = nextConnection.connection.pointsToConnect;
3149
- graphics.lines.push({
3150
- points: [
3151
- { x: start.x, y: start.y },
3152
- { x: end.x, y: end.y }
3153
- ],
3154
- strokeColor: "red",
3155
- strokeDash: "10 5"
3156
- });
3157
- }
3158
- if (this.candidates) {
3159
- const topCandidates = this.candidates.slice(0, 5);
3160
- const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
3161
- topCandidates.forEach((candidate, index) => {
3162
- const opacity = 0.5 * (1 - index / 5);
3163
- const backtrackedPath = this.getBacktrackedPath(candidate);
3164
- graphics.lines.push({
3165
- points: backtrackedPath.map(({ center: { x, y } }) => ({ x, y })),
3166
- strokeColor: safeTransparentize(
3167
- this.colorMap[connectionName] ?? "red",
3168
- 1 - opacity
3169
- )
3170
- });
3171
- });
3172
- }
3173
- return graphics;
3174
3129
  }
3130
+ return connMap;
3175
3131
  };
3176
3132
 
3177
3133
  // lib/utils/getTunedTotalCapacity1.ts
@@ -3197,267 +3153,44 @@ var calculateOptimalCapacityDepth = (initialWidth, targetMinCapacity = 0.5, maxD
3197
3153
  return Math.max(1, depth);
3198
3154
  };
3199
3155
 
3200
- // lib/solvers/CapacityPathingSolver/CapacityPathingSolver4_FlexibleNegativeCapacity_AvoidLowCapacity_FixedDistanceCost.ts
3201
- var CapacityPathingSolver4_FlexibleNegativeCapacity = class extends CapacityPathingSolver {
3202
- NEGATIVE_CAPACITY_PENALTY_FACTOR = 1;
3203
- REDUCED_CAPACITY_PENALTY_FACTOR = 1;
3204
- get maxCapacityFactor() {
3205
- return this.hyperParameters.MAX_CAPACITY_FACTOR ?? 1;
3206
- }
3207
- /**
3208
- * In the FlexibleNegativeCapacity mode, we allow negative capacity
3209
- */
3210
- doesNodeHaveCapacityForTrace(node) {
3211
- return true;
3212
- }
3213
- getTotalCapacity(node) {
3214
- return getTunedTotalCapacity1(node, this.maxCapacityFactor);
3156
+ // lib/solvers/NetToPointPairsSolver/buildMinimumSpanningTree.ts
3157
+ var KDNode = class {
3158
+ point;
3159
+ left = null;
3160
+ right = null;
3161
+ constructor(point) {
3162
+ this.point = point;
3215
3163
  }
3216
- /**
3217
- * Penalty you pay for using this node
3218
- */
3219
- getNodeCapacityPenalty(node) {
3220
- const totalCapacity = this.getTotalCapacity(node);
3221
- const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
3222
- const remainingCapacity = totalCapacity - usedCapacity;
3223
- const dist = this.activeCandidateStraightLineDistance;
3224
- if (remainingCapacity <= 0) {
3225
- const penalty = (-remainingCapacity + 1) / totalCapacity * dist * (this.NEGATIVE_CAPACITY_PENALTY_FACTOR / 4);
3226
- return penalty ** 2;
3164
+ };
3165
+ var KDTree = class {
3166
+ root = null;
3167
+ constructor(points) {
3168
+ if (points.length > 0) {
3169
+ this.root = this.buildTree(points, 0);
3227
3170
  }
3228
- return 1 / remainingCapacity * dist * this.REDUCED_CAPACITY_PENALTY_FACTOR / 8;
3229
- }
3230
- /**
3231
- * We're rewarding travel into big nodes.
3232
- *
3233
- * To minimize shortest path, you'd want to comment this out.
3234
- */
3235
- getDistanceBetweenNodes(A, B) {
3236
- const dx = A.center.x - B.center.x;
3237
- const dy = A.center.y - B.center.y;
3238
- const szx = Math.max(A.width, B.width);
3239
- const szy = Math.max(A.height, B.height);
3240
- const dist = Math.sqrt(dx ** 2 + dy ** 2) / (szx * szy);
3241
- return dist;
3242
- }
3243
- computeG(prevCandidate, node, endGoal) {
3244
- return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node) + this.getNodeCapacityPenalty(node);
3245
3171
  }
3246
- computeH(prevCandidate, node, endGoal) {
3247
- return this.getDistanceBetweenNodes(node, endGoal) + this.getNodeCapacityPenalty(node);
3172
+ buildTree(points, depth) {
3173
+ const axis = depth % 2 === 0 ? "x" : "y";
3174
+ points.sort((a, b) => a[axis] - b[axis]);
3175
+ const medianIndex = Math.floor(points.length / 2);
3176
+ const node = new KDNode(points[medianIndex]);
3177
+ if (medianIndex > 0) {
3178
+ node.left = this.buildTree(points.slice(0, medianIndex), depth + 1);
3179
+ }
3180
+ if (medianIndex < points.length - 1) {
3181
+ node.right = this.buildTree(points.slice(medianIndex + 1), depth + 1);
3182
+ }
3183
+ return node;
3248
3184
  }
3249
- };
3250
-
3251
- // node_modules/circuit-json-to-connectivity-map/dist/index.js
3252
- var ConnectivityMap = class {
3253
- netMap;
3254
- idToNetMap;
3255
- constructor(netMap) {
3256
- this.netMap = netMap;
3257
- this.idToNetMap = {};
3258
- for (const [netId, ids] of Object.entries(netMap)) {
3259
- for (const id of ids) {
3260
- this.idToNetMap[id] = netId;
3261
- }
3185
+ // Find the nearest neighbor to a query point
3186
+ findNearestNeighbor(queryPoint) {
3187
+ if (!this.root) {
3188
+ throw new Error("Tree is empty");
3262
3189
  }
3263
- }
3264
- addConnections(connections) {
3265
- for (const connection of connections) {
3266
- const existingNets = /* @__PURE__ */ new Set();
3267
- for (const id of connection) {
3268
- const existingNetId = this.idToNetMap[id];
3269
- if (existingNetId) {
3270
- existingNets.add(existingNetId);
3271
- }
3272
- }
3273
- let targetNetId;
3274
- if (existingNets.size === 0) {
3275
- targetNetId = `connectivity_net${Object.keys(this.netMap).length}`;
3276
- this.netMap[targetNetId] = [];
3277
- } else if (existingNets.size === 1) {
3278
- targetNetId = existingNets.values().next().value ?? `connectivity_net${Object.keys(this.netMap).length}`;
3279
- } else {
3280
- targetNetId = existingNets.values().next().value ?? `connectivity_net${Object.keys(this.netMap).length}`;
3281
- for (const netId of existingNets) {
3282
- if (netId !== targetNetId) {
3283
- this.netMap[targetNetId].push(...this.netMap[netId]);
3284
- this.netMap[netId] = this.netMap[targetNetId];
3285
- for (const id of this.netMap[targetNetId]) {
3286
- this.idToNetMap[id] = targetNetId;
3287
- }
3288
- }
3289
- }
3290
- }
3291
- for (const id of connection) {
3292
- if (!this.netMap[targetNetId].includes(id)) {
3293
- this.netMap[targetNetId].push(id);
3294
- }
3295
- this.idToNetMap[id] = targetNetId;
3296
- }
3297
- }
3298
- }
3299
- getIdsConnectedToNet(netId) {
3300
- return this.netMap[netId] || [];
3301
- }
3302
- getNetConnectedToId(id) {
3303
- return this.idToNetMap[id];
3304
- }
3305
- areIdsConnected(id1, id2) {
3306
- if (id1 === id2) return true;
3307
- const netId1 = this.getNetConnectedToId(id1);
3308
- if (!netId1) return false;
3309
- const netId2 = this.getNetConnectedToId(id2);
3310
- if (!netId2) return false;
3311
- return netId1 === netId2 || netId2 === id1 || netId2 === id1;
3312
- }
3313
- areAllIdsConnected(ids) {
3314
- const netId = this.getNetConnectedToId(ids[0]);
3315
- for (const id of ids) {
3316
- const nextNetId = this.getNetConnectedToId(id);
3317
- if (nextNetId === void 0) {
3318
- return false;
3319
- }
3320
- if (nextNetId !== netId) {
3321
- return false;
3322
- }
3323
- }
3324
- return true;
3325
- }
3326
- };
3327
-
3328
- // lib/utils/getConnectivityMapFromSimpleRouteJson.ts
3329
- var getConnectivityMapFromSimpleRouteJson = (srj) => {
3330
- const connMap = new ConnectivityMap({});
3331
- for (const connection of srj.connections) {
3332
- for (const point of connection.pointsToConnect) {
3333
- if ("pcb_port_id" in point && point.pcb_port_id) {
3334
- connMap.addConnections([[connection.name, point.pcb_port_id]]);
3335
- }
3336
- }
3337
- }
3338
- return connMap;
3339
- };
3340
-
3341
- // lib/solvers/CapacityMeshSolver/CapacityNodeTargetMerger.ts
3342
- var CapacityNodeTargetMerger = class extends BaseSolver {
3343
- constructor(nodes, obstacles, connMap) {
3344
- super();
3345
- this.nodes = nodes;
3346
- this.connMap = connMap;
3347
- this.MAX_ITERATIONS = 1e5;
3348
- this.unprocessedObstacles = [...obstacles];
3349
- this.newNodes = [];
3350
- this.removedNodeIds = /* @__PURE__ */ new Set();
3351
- }
3352
- unprocessedObstacles;
3353
- newNodes;
3354
- removedNodeIds;
3355
- _step() {
3356
- const obstacle = this.unprocessedObstacles.pop();
3357
- if (!obstacle) {
3358
- for (const node of this.nodes) {
3359
- if (this.removedNodeIds.has(node.capacityMeshNodeId)) continue;
3360
- this.newNodes.push(node);
3361
- }
3362
- this.solved = true;
3363
- return;
3364
- }
3365
- const connectedNodes = this.nodes.filter((n) => {
3366
- if (!n._targetConnectionName) return false;
3367
- const implicitlyConnected = doRectsOverlap(n, obstacle);
3368
- return implicitlyConnected;
3369
- });
3370
- if (connectedNodes.length === 0) return;
3371
- const connectionName = connectedNodes[0]._targetConnectionName;
3372
- const bounds = {
3373
- minX: Infinity,
3374
- minY: Infinity,
3375
- maxX: -Infinity,
3376
- maxY: -Infinity
3377
- };
3378
- for (const node of connectedNodes) {
3379
- bounds.minX = Math.min(bounds.minX, node.center.x - node.width / 2);
3380
- bounds.minY = Math.min(bounds.minY, node.center.y - node.height / 2);
3381
- bounds.maxX = Math.max(bounds.maxX, node.center.x + node.width / 2);
3382
- bounds.maxY = Math.max(bounds.maxY, node.center.y + node.height / 2);
3383
- }
3384
- const newNode = {
3385
- capacityMeshNodeId: connectedNodes[0].capacityMeshNodeId,
3386
- center: {
3387
- x: (bounds.minX + bounds.maxX) / 2,
3388
- y: (bounds.minY + bounds.maxY) / 2
3389
- },
3390
- width: bounds.maxX - bounds.minX,
3391
- height: bounds.maxY - bounds.minY,
3392
- layer: connectedNodes[0].layer,
3393
- availableZ: connectedNodes[0].availableZ,
3394
- _completelyInsideObstacle: false,
3395
- _containsObstacle: true,
3396
- _containsTarget: true,
3397
- _targetConnectionName: connectionName,
3398
- _depth: connectedNodes[0]._depth,
3399
- _parent: connectedNodes[0]._parent
3400
- };
3401
- this.newNodes.push(newNode);
3402
- for (const node of connectedNodes) {
3403
- this.removedNodeIds.add(node.capacityMeshNodeId);
3404
- }
3405
- }
3406
- visualize() {
3407
- const graphics = {
3408
- rects: []
3409
- };
3410
- for (const node of this.newNodes) {
3411
- graphics.rects.push({
3412
- center: node.center,
3413
- width: Math.max(node.width - 2, node.width * 0.8),
3414
- height: Math.max(node.height - 2, node.height * 0.8),
3415
- fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : "rgba(0,0,0,0.1)",
3416
- label: node.capacityMeshNodeId
3417
- });
3418
- }
3419
- return graphics;
3420
- }
3421
- };
3422
-
3423
- // lib/solvers/NetToPointPairsSolver/buildMinimumSpanningTree.ts
3424
- var KDNode = class {
3425
- point;
3426
- left = null;
3427
- right = null;
3428
- constructor(point) {
3429
- this.point = point;
3430
- }
3431
- };
3432
- var KDTree = class {
3433
- root = null;
3434
- constructor(points) {
3435
- if (points.length > 0) {
3436
- this.root = this.buildTree(points, 0);
3437
- }
3438
- }
3439
- buildTree(points, depth) {
3440
- const axis = depth % 2 === 0 ? "x" : "y";
3441
- points.sort((a, b) => a[axis] - b[axis]);
3442
- const medianIndex = Math.floor(points.length / 2);
3443
- const node = new KDNode(points[medianIndex]);
3444
- if (medianIndex > 0) {
3445
- node.left = this.buildTree(points.slice(0, medianIndex), depth + 1);
3446
- }
3447
- if (medianIndex < points.length - 1) {
3448
- node.right = this.buildTree(points.slice(medianIndex + 1), depth + 1);
3449
- }
3450
- return node;
3451
- }
3452
- // Find the nearest neighbor to a query point
3453
- findNearestNeighbor(queryPoint) {
3454
- if (!this.root) {
3455
- throw new Error("Tree is empty");
3456
- }
3457
- const best = this.root.point;
3458
- const bestDistance = this.distance(queryPoint, best);
3459
- this.nearestNeighborSearch(this.root, queryPoint, 0, best, bestDistance);
3460
- return best;
3190
+ const best = this.root.point;
3191
+ const bestDistance = this.distance(queryPoint, best);
3192
+ this.nearestNeighborSearch(this.root, queryPoint, 0, best, bestDistance);
3193
+ return best;
3461
3194
  }
3462
3195
  nearestNeighborSearch(node, queryPoint, depth, best, bestDistance) {
3463
3196
  if (!node) {
@@ -4347,6 +4080,7 @@ var createSegmentPointMap = (dedupedSegments, segmentIdToNodeIds) => {
4347
4080
  var UnravelSectionSolver = class extends BaseSolver {
4348
4081
  nodeMap;
4349
4082
  dedupedSegments;
4083
+ dedupedSegmentMap;
4350
4084
  MUTABLE_HOPS = 1;
4351
4085
  unravelSection;
4352
4086
  candidates = [];
@@ -4366,6 +4100,14 @@ var UnravelSectionSolver = class extends BaseSolver {
4366
4100
  this.MUTABLE_HOPS = params.MUTABLE_HOPS ?? this.MUTABLE_HOPS;
4367
4101
  this.nodeMap = params.nodeMap;
4368
4102
  this.dedupedSegments = params.dedupedSegments;
4103
+ if (params.dedupedSegmentMap) {
4104
+ this.dedupedSegmentMap = params.dedupedSegmentMap;
4105
+ } else {
4106
+ this.dedupedSegmentMap = /* @__PURE__ */ new Map();
4107
+ for (const segment of this.dedupedSegments) {
4108
+ this.dedupedSegmentMap.set(segment.nodePortSegmentId, segment);
4109
+ }
4110
+ }
4369
4111
  this.nodeIdToSegmentIds = params.nodeIdToSegmentIds;
4370
4112
  this.segmentIdToNodeIds = params.segmentIdToNodeIds;
4371
4113
  this.rootNodeId = params.rootNodeId;
@@ -4524,14 +4266,20 @@ var UnravelSectionSolver = class extends BaseSolver {
4524
4266
  const [APointId, BPointId] = issue.segmentPoints;
4525
4267
  const pointA = this.getPointInCandidate(candidate, APointId);
4526
4268
  const pointB = this.getPointInCandidate(candidate, BPointId);
4527
- if (this.unravelSection.mutableSegmentIds.has(pointA.segmentId)) {
4269
+ const aAvailableZ = this.dedupedSegmentMap.get(
4270
+ pointA.segmentId
4271
+ ).availableZ;
4272
+ const bAvailableZ = this.dedupedSegmentMap.get(
4273
+ pointB.segmentId
4274
+ ).availableZ;
4275
+ if (this.unravelSection.mutableSegmentIds.has(pointA.segmentId) && aAvailableZ.includes(pointB.z)) {
4528
4276
  operations.push({
4529
4277
  type: "change_layer",
4530
4278
  newZ: pointB.z,
4531
4279
  segmentPointIds: [APointId]
4532
4280
  });
4533
4281
  }
4534
- if (this.unravelSection.mutableSegmentIds.has(pointB.segmentId)) {
4282
+ if (this.unravelSection.mutableSegmentIds.has(pointB.segmentId) && bAvailableZ.includes(pointA.z)) {
4535
4283
  operations.push({
4536
4284
  type: "change_layer",
4537
4285
  newZ: pointA.z,
@@ -4986,6 +4734,7 @@ var calculateNodeProbabilityOfFailure = (node, numSameLayerCrossings, numEntryEx
4986
4734
  // lib/solvers/UnravelSolver/UnravelMultiSectionSolver.ts
4987
4735
  var UnravelMultiSectionSolver = class extends BaseSolver {
4988
4736
  nodeMap;
4737
+ dedupedSegmentMap;
4989
4738
  dedupedSegments;
4990
4739
  nodeIdToSegmentIds;
4991
4740
  segmentIdToNodeIds;
@@ -5009,6 +4758,10 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
5009
4758
  super();
5010
4759
  this.MAX_ITERATIONS = 1e5;
5011
4760
  this.dedupedSegments = getDedupedSegments(assignedSegments);
4761
+ this.dedupedSegmentMap = /* @__PURE__ */ new Map();
4762
+ for (const segment of this.dedupedSegments) {
4763
+ this.dedupedSegmentMap.set(segment.nodePortSegmentId, segment);
4764
+ }
5012
4765
  this.nodeMap = /* @__PURE__ */ new Map();
5013
4766
  for (const node of nodes) {
5014
4767
  this.nodeMap.set(node.capacityMeshNodeId, node);
@@ -5087,6 +4840,7 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
5087
4840
  );
5088
4841
  this.activeSolver = new UnravelSectionSolver({
5089
4842
  dedupedSegments: this.dedupedSegments,
4843
+ dedupedSegmentMap: this.dedupedSegmentMap,
5090
4844
  nodeMap: this.nodeMap,
5091
4845
  nodeIdToSegmentIds: this.nodeIdToSegmentIds,
5092
4846
  segmentIdToNodeIds: this.segmentIdToNodeIds,
@@ -5113,148 +4867,963 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
5113
4867
  segmentPoint.z = pointModification.z ?? segmentPoint.z;
5114
4868
  }
5115
4869
  }
5116
- for (const nodeId of this.activeSolver.unravelSection.allNodeIds) {
5117
- this.nodePfMap.set(
5118
- nodeId,
5119
- this.computeNodePf(this.nodeMap.get(nodeId))
5120
- );
4870
+ for (const nodeId of this.activeSolver.unravelSection.allNodeIds) {
4871
+ this.nodePfMap.set(
4872
+ nodeId,
4873
+ this.computeNodePf(this.nodeMap.get(nodeId))
4874
+ );
4875
+ }
4876
+ this.activeSolver = null;
4877
+ }
4878
+ }
4879
+ visualize() {
4880
+ if (this.activeSolver) {
4881
+ return this.activeSolver.visualize();
4882
+ }
4883
+ const graphics = {
4884
+ lines: [],
4885
+ points: [],
4886
+ rects: [],
4887
+ circles: [],
4888
+ coordinateSystem: "cartesian",
4889
+ title: "Unravel Multi Section Solver"
4890
+ };
4891
+ for (const [nodeId, node] of this.nodeMap.entries()) {
4892
+ const probabilityOfFailure = this.nodePfMap.get(nodeId) || 0;
4893
+ const pf = Math.min(probabilityOfFailure, 1);
4894
+ const red = Math.floor(255 * pf);
4895
+ const green = Math.floor(255 * (1 - pf));
4896
+ const color = `rgb(${red}, ${green}, 0)`;
4897
+ if ((this.attemptsToFixNode.get(nodeId) ?? 0) === 0 && pf === 0) {
4898
+ continue;
4899
+ }
4900
+ graphics.rects.push({
4901
+ center: node.center,
4902
+ label: [
4903
+ nodeId,
4904
+ `${node.width.toFixed(2)}x${node.height.toFixed(2)}`,
4905
+ `Pf: ${probabilityOfFailure.toFixed(3)}`
4906
+ ].join("\n"),
4907
+ color,
4908
+ width: node.width / 8,
4909
+ height: node.height / 8
4910
+ });
4911
+ }
4912
+ for (const segmentPoint of this.segmentPointMap.values()) {
4913
+ const segment = this.dedupedSegmentMap.get(segmentPoint.segmentId);
4914
+ graphics.points.push({
4915
+ x: segmentPoint.x,
4916
+ y: segmentPoint.y,
4917
+ label: [
4918
+ segmentPoint.segmentPointId,
4919
+ segmentPoint.segmentId,
4920
+ `z: ${segmentPoint.z}`,
4921
+ `segment.availableZ: ${segment?.availableZ.join(",")}`
4922
+ ].join("\n"),
4923
+ color: this.colorMap[segmentPoint.connectionName] || "#000"
4924
+ });
4925
+ }
4926
+ const pointsBySegment = /* @__PURE__ */ new Map();
4927
+ for (const point of this.segmentPointMap.values()) {
4928
+ if (!pointsBySegment.has(point.segmentId)) {
4929
+ pointsBySegment.set(point.segmentId, []);
4930
+ }
4931
+ pointsBySegment.get(point.segmentId).push(point);
4932
+ }
4933
+ for (const [segmentId, points] of pointsBySegment.entries()) {
4934
+ if (points.length < 2) continue;
4935
+ const sortedPoints = [...points].sort(
4936
+ (a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y
4937
+ );
4938
+ for (let i = 0; i < sortedPoints.length - 1; i++) {
4939
+ graphics.lines.push({
4940
+ points: [
4941
+ { x: sortedPoints[i].x, y: sortedPoints[i].y },
4942
+ { x: sortedPoints[i + 1].x, y: sortedPoints[i + 1].y }
4943
+ ],
4944
+ strokeColor: this.colorMap[segmentId] || "#000"
4945
+ });
4946
+ }
4947
+ }
4948
+ const processedConnections = /* @__PURE__ */ new Set();
4949
+ const allPoints = Array.from(this.segmentPointMap.values());
4950
+ for (let i = 0; i < allPoints.length; i++) {
4951
+ const point1 = allPoints[i];
4952
+ for (let j = i + 1; j < allPoints.length; j++) {
4953
+ const point2 = allPoints[j];
4954
+ if (point1.connectionName !== point2.connectionName || point1.segmentId === point2.segmentId) {
4955
+ continue;
4956
+ }
4957
+ const hasSharedNode = point1.capacityMeshNodeIds.some(
4958
+ (nodeId) => point2.capacityMeshNodeIds.includes(nodeId)
4959
+ );
4960
+ if (hasSharedNode) {
4961
+ const connectionKey = `${point1.segmentPointId}-${point2.segmentPointId}`;
4962
+ if (processedConnections.has(connectionKey)) continue;
4963
+ processedConnections.add(connectionKey);
4964
+ const sameLayer = point1.z === point2.z;
4965
+ const layer = point1.z;
4966
+ let strokeDash;
4967
+ if (sameLayer) {
4968
+ strokeDash = layer === 0 ? void 0 : "10 5";
4969
+ } else {
4970
+ strokeDash = "3 3 10";
4971
+ }
4972
+ graphics.lines.push({
4973
+ points: [
4974
+ { x: point1.x, y: point1.y },
4975
+ { x: point2.x, y: point2.y }
4976
+ ],
4977
+ strokeDash,
4978
+ strokeColor: this.colorMap[point1.connectionName] || "#666"
4979
+ });
4980
+ }
4981
+ }
4982
+ }
4983
+ return graphics;
4984
+ }
4985
+ getNodesWithPortPoints() {
4986
+ if (!this.solved) {
4987
+ throw new Error(
4988
+ "CapacitySegmentToPointSolver not solved, can't give port points yet"
4989
+ );
4990
+ }
4991
+ const nodeWithPortPointsMap = /* @__PURE__ */ new Map();
4992
+ for (const segment of this.dedupedSegments) {
4993
+ const segId = segment.nodePortSegmentId;
4994
+ for (const nodeId of this.segmentIdToNodeIds.get(segId)) {
4995
+ const node = this.nodeMap.get(nodeId);
4996
+ if (!nodeWithPortPointsMap.has(nodeId)) {
4997
+ nodeWithPortPointsMap.set(nodeId, {
4998
+ capacityMeshNodeId: nodeId,
4999
+ portPoints: [],
5000
+ center: node.center,
5001
+ width: node.width,
5002
+ height: node.height
5003
+ });
5004
+ }
5005
+ }
5006
+ }
5007
+ for (const segmentPoint of this.segmentPointMap.values()) {
5008
+ for (const nodeId of segmentPoint.capacityMeshNodeIds) {
5009
+ const nodeWithPortPoints = nodeWithPortPointsMap.get(nodeId);
5010
+ if (nodeWithPortPoints) {
5011
+ nodeWithPortPoints.portPoints.push({
5012
+ x: segmentPoint.x,
5013
+ y: segmentPoint.y,
5014
+ z: segmentPoint.z,
5015
+ connectionName: segmentPoint.connectionName
5016
+ });
5017
+ }
5018
+ }
5019
+ }
5020
+ return Array.from(nodeWithPortPointsMap.values());
5021
+ }
5022
+ };
5023
+
5024
+ // lib/utils/createRectFromCapacityNode.ts
5025
+ var createRectFromCapacityNode = (node, opts = {}) => {
5026
+ const lowestZ = Math.min(...node.availableZ);
5027
+ return {
5028
+ center: !opts.rectMargin ? {
5029
+ x: node.center.x + lowestZ * node.width * 0.05,
5030
+ y: node.center.y - lowestZ * node.width * 0.05
5031
+ } : node.center,
5032
+ width: opts.rectMargin ? node.width - opts.rectMargin * 2 : Math.max(node.width - 0.5, node.width * 0.8),
5033
+ height: opts.rectMargin ? node.height - opts.rectMargin * 2 : Math.max(node.height - 0.5, node.height * 0.8),
5034
+ fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : {
5035
+ "0,1": "rgba(0,0,0,0.1)",
5036
+ "0": "rgba(0,200,200, 0.1)",
5037
+ "1": "rgba(0,0,200, 0.1)"
5038
+ }[node.availableZ.join(",")] ?? "rgba(0,200,200,0.1)",
5039
+ label: [
5040
+ node.capacityMeshNodeId,
5041
+ `availableZ: ${node.availableZ.join(",")}`,
5042
+ `${node._containsTarget ? "containsTarget" : ""}`,
5043
+ `${node._containsObstacle ? "containsObstacle" : ""}`
5044
+ ].filter(Boolean).join("\n")
5045
+ };
5046
+ };
5047
+
5048
+ // lib/solvers/CapacityPathingSolver/CapacityPathingSolver.ts
5049
+ var CapacityPathingSolver = class extends BaseSolver {
5050
+ connectionsWithNodes;
5051
+ usedNodeCapacityMap;
5052
+ simpleRouteJson;
5053
+ nodes;
5054
+ edges;
5055
+ GREEDY_MULTIPLIER = 1.1;
5056
+ nodeMap;
5057
+ nodeEdgeMap;
5058
+ connectionNameToGoalNodeIds;
5059
+ colorMap;
5060
+ maxDepthOfNodes;
5061
+ activeCandidateStraightLineDistance;
5062
+ debug_lastNodeCostMap;
5063
+ hyperParameters;
5064
+ constructor({
5065
+ simpleRouteJson,
5066
+ nodes,
5067
+ edges,
5068
+ colorMap,
5069
+ MAX_ITERATIONS = 1e6,
5070
+ hyperParameters = {}
5071
+ }) {
5072
+ super();
5073
+ this.MAX_ITERATIONS = MAX_ITERATIONS;
5074
+ this.simpleRouteJson = simpleRouteJson;
5075
+ this.nodes = nodes;
5076
+ this.edges = edges;
5077
+ this.colorMap = colorMap ?? {};
5078
+ const { connectionsWithNodes, connectionNameToGoalNodeIds } = this.getConnectionsWithNodes();
5079
+ this.connectionsWithNodes = connectionsWithNodes;
5080
+ this.connectionNameToGoalNodeIds = connectionNameToGoalNodeIds;
5081
+ this.hyperParameters = hyperParameters;
5082
+ this.usedNodeCapacityMap = new Map(
5083
+ this.nodes.map((node) => [node.capacityMeshNodeId, 0])
5084
+ );
5085
+ this.nodeMap = new Map(
5086
+ this.nodes.map((node) => [node.capacityMeshNodeId, node])
5087
+ );
5088
+ this.nodeEdgeMap = getNodeEdgeMap(this.edges);
5089
+ this.maxDepthOfNodes = Math.max(
5090
+ ...this.nodes.map((node) => node._depth ?? 0)
5091
+ );
5092
+ this.debug_lastNodeCostMap = /* @__PURE__ */ new Map();
5093
+ }
5094
+ getTotalCapacity(node) {
5095
+ const depth = node._depth ?? 0;
5096
+ return (this.maxDepthOfNodes - depth + 1) ** 2;
5097
+ }
5098
+ getConnectionsWithNodes() {
5099
+ const connectionsWithNodes = [];
5100
+ const nodesWithTargets = this.nodes.filter((node) => node._containsTarget);
5101
+ const connectionNameToGoalNodeIds = /* @__PURE__ */ new Map();
5102
+ for (const connection of this.simpleRouteJson.connections) {
5103
+ const nodesForConnection = [];
5104
+ for (const point of connection.pointsToConnect) {
5105
+ let closestNode = this.nodes[0];
5106
+ let minDistance = Number.MAX_VALUE;
5107
+ for (const node of nodesWithTargets) {
5108
+ const distance3 = Math.sqrt(
5109
+ (node.center.x - point.x) ** 2 + (node.center.y - point.y) ** 2
5110
+ );
5111
+ if (distance3 < minDistance) {
5112
+ minDistance = distance3;
5113
+ closestNode = node;
5114
+ }
5115
+ }
5116
+ nodesForConnection.push(closestNode);
5117
+ }
5118
+ if (nodesForConnection.length < 2) {
5119
+ throw new Error(
5120
+ `Not enough nodes for connection "${connection.name}", only ${nodesForConnection.length} found`
5121
+ );
5122
+ }
5123
+ connectionNameToGoalNodeIds.set(
5124
+ connection.name,
5125
+ nodesForConnection.map((n) => n.capacityMeshNodeId)
5126
+ );
5127
+ connectionsWithNodes.push({
5128
+ connection,
5129
+ nodes: nodesForConnection,
5130
+ pathFound: false
5131
+ });
5132
+ }
5133
+ return { connectionsWithNodes, connectionNameToGoalNodeIds };
5134
+ }
5135
+ currentConnectionIndex = 0;
5136
+ candidates;
5137
+ visitedNodes;
5138
+ computeG(prevCandidate, node, endGoal) {
5139
+ return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node);
5140
+ }
5141
+ computeH(prevCandidate, node, endGoal) {
5142
+ return this.getDistanceBetweenNodes(node, endGoal);
5143
+ }
5144
+ getBacktrackedPath(candidate) {
5145
+ const path = [];
5146
+ let currentCandidate = candidate;
5147
+ while (currentCandidate) {
5148
+ path.push(currentCandidate.node);
5149
+ currentCandidate = currentCandidate.prevCandidate;
5150
+ }
5151
+ return path;
5152
+ }
5153
+ getNeighboringNodes(node) {
5154
+ return this.nodeEdgeMap.get(node.capacityMeshNodeId).flatMap(
5155
+ (edge) => edge.nodeIds.filter((n) => n !== node.capacityMeshNodeId)
5156
+ ).map((n) => this.nodeMap.get(n));
5157
+ }
5158
+ getCapacityPaths() {
5159
+ const capacityPaths = [];
5160
+ for (const connection of this.connectionsWithNodes) {
5161
+ const path = connection.path;
5162
+ if (path) {
5163
+ capacityPaths.push({
5164
+ capacityPathId: connection.connection.name,
5165
+ connectionName: connection.connection.name,
5166
+ nodeIds: path.map((node) => node.capacityMeshNodeId)
5167
+ });
5168
+ }
5169
+ }
5170
+ return capacityPaths;
5171
+ }
5172
+ doesNodeHaveCapacityForTrace(node) {
5173
+ const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
5174
+ const totalCapacity = this.getTotalCapacity(node);
5175
+ if (node.availableZ.length === 1 && !node._containsTarget && usedCapacity > 0)
5176
+ return false;
5177
+ return usedCapacity < totalCapacity;
5178
+ }
5179
+ canTravelThroughObstacle(node, connectionName) {
5180
+ const goalNodeIds = this.connectionNameToGoalNodeIds.get(connectionName);
5181
+ return goalNodeIds?.includes(node.capacityMeshNodeId) ?? false;
5182
+ }
5183
+ getDistanceBetweenNodes(A, B) {
5184
+ return Math.sqrt(
5185
+ (A.center.x - B.center.x) ** 2 + (A.center.y - B.center.y) ** 2
5186
+ );
5187
+ }
5188
+ reduceCapacityAlongPath(nextConnection) {
5189
+ for (const node of nextConnection.path ?? []) {
5190
+ this.usedNodeCapacityMap.set(
5191
+ node.capacityMeshNodeId,
5192
+ this.usedNodeCapacityMap.get(node.capacityMeshNodeId) + 1
5193
+ );
5194
+ }
5195
+ }
5196
+ isConnectedToEndGoal(node, endGoal) {
5197
+ return this.nodeEdgeMap.get(node.capacityMeshNodeId).some((edge) => edge.nodeIds.includes(endGoal.capacityMeshNodeId));
5198
+ }
5199
+ _step() {
5200
+ const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
5201
+ if (!nextConnection) {
5202
+ this.solved = true;
5203
+ return;
5204
+ }
5205
+ const [start, end] = nextConnection.nodes;
5206
+ if (!this.candidates) {
5207
+ this.candidates = [{ prevCandidate: null, node: start, f: 0, g: 0, h: 0 }];
5208
+ this.debug_lastNodeCostMap = /* @__PURE__ */ new Map();
5209
+ this.visitedNodes = /* @__PURE__ */ new Set([start.capacityMeshNodeId]);
5210
+ this.activeCandidateStraightLineDistance = distance(
5211
+ start.center,
5212
+ end.center
5213
+ );
5214
+ }
5215
+ this.candidates.sort((a, b) => a.f - b.f);
5216
+ const currentCandidate = this.candidates.shift();
5217
+ if (!currentCandidate) {
5218
+ console.error(
5219
+ `Ran out of candidates on connection ${nextConnection.connection.name}`
5220
+ );
5221
+ this.currentConnectionIndex++;
5222
+ this.candidates = null;
5223
+ this.visitedNodes = null;
5224
+ return;
5225
+ }
5226
+ if (this.isConnectedToEndGoal(currentCandidate.node, end)) {
5227
+ nextConnection.path = this.getBacktrackedPath({
5228
+ prevCandidate: currentCandidate,
5229
+ node: end,
5230
+ f: 0,
5231
+ g: 0,
5232
+ h: 0
5233
+ });
5234
+ this.reduceCapacityAlongPath(nextConnection);
5235
+ this.currentConnectionIndex++;
5236
+ this.candidates = null;
5237
+ this.visitedNodes = null;
5238
+ return;
5239
+ }
5240
+ const neighborNodes = this.getNeighboringNodes(currentCandidate.node);
5241
+ for (const neighborNode of neighborNodes) {
5242
+ if (this.visitedNodes?.has(neighborNode.capacityMeshNodeId)) {
5243
+ continue;
5244
+ }
5245
+ if (!this.doesNodeHaveCapacityForTrace(neighborNode)) {
5246
+ continue;
5247
+ }
5248
+ const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
5249
+ if (neighborNode._containsObstacle && !this.canTravelThroughObstacle(neighborNode, connectionName)) {
5250
+ continue;
5251
+ }
5252
+ const g = this.computeG(currentCandidate, neighborNode, end);
5253
+ const h = this.computeH(currentCandidate, neighborNode, end);
5254
+ const f = g + h * this.GREEDY_MULTIPLIER;
5255
+ this.debug_lastNodeCostMap.set(neighborNode.capacityMeshNodeId, {
5256
+ f,
5257
+ g,
5258
+ h
5259
+ });
5260
+ const newCandidate = {
5261
+ prevCandidate: currentCandidate,
5262
+ node: neighborNode,
5263
+ f,
5264
+ g,
5265
+ h
5266
+ };
5267
+ this.candidates.push(newCandidate);
5268
+ }
5269
+ this.visitedNodes.add(currentCandidate.node.capacityMeshNodeId);
5270
+ }
5271
+ visualize() {
5272
+ const graphics = {
5273
+ lines: [],
5274
+ points: [],
5275
+ rects: [],
5276
+ circles: []
5277
+ };
5278
+ if (this.connectionsWithNodes) {
5279
+ for (let i = 0; i < this.connectionsWithNodes.length; i++) {
5280
+ const conn = this.connectionsWithNodes[i];
5281
+ if (conn.path && conn.path.length > 0) {
5282
+ const pathPoints = conn.path.map(({ center: { x, y }, width }) => ({
5283
+ // slight offset to allow viewing overlapping paths
5284
+ x: x + (i % 10 + i % 19) * (0.01 * width),
5285
+ y: y + (i % 10 + i % 19) * (0.01 * width)
5286
+ }));
5287
+ graphics.lines.push({
5288
+ points: pathPoints,
5289
+ strokeColor: this.colorMap[conn.connection.name]
5290
+ });
5291
+ }
5292
+ }
5293
+ }
5294
+ for (const node of this.nodes) {
5295
+ const nodeCosts = this.debug_lastNodeCostMap.get(node.capacityMeshNodeId);
5296
+ graphics.rects.push({
5297
+ ...createRectFromCapacityNode(node),
5298
+ label: [
5299
+ `${node.capacityMeshNodeId}`,
5300
+ `${this.usedNodeCapacityMap.get(node.capacityMeshNodeId)}/${this.getTotalCapacity(node).toFixed(2)}`,
5301
+ `${node.width.toFixed(2)}x${node.height.toFixed(2)}`,
5302
+ `g: ${nodeCosts?.g !== void 0 ? nodeCosts.g.toFixed(2) : "?"}`,
5303
+ `h: ${nodeCosts?.h !== void 0 ? nodeCosts.h.toFixed(2) : "?"}`,
5304
+ `f: ${nodeCosts?.f !== void 0 ? nodeCosts.f.toFixed(2) : "?"}`
5305
+ ].join("\n")
5306
+ });
5307
+ }
5308
+ if (this.connectionsWithNodes) {
5309
+ for (const conn of this.connectionsWithNodes) {
5310
+ if (conn.connection?.pointsToConnect) {
5311
+ for (const point of conn.connection.pointsToConnect) {
5312
+ graphics.points.push({
5313
+ x: point.x,
5314
+ y: point.y
5315
+ });
5316
+ }
5317
+ }
5318
+ }
5319
+ }
5320
+ const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
5321
+ if (nextConnection) {
5322
+ const [start, end] = nextConnection.connection.pointsToConnect;
5323
+ graphics.lines.push({
5324
+ points: [
5325
+ { x: start.x, y: start.y },
5326
+ { x: end.x, y: end.y }
5327
+ ],
5328
+ strokeColor: "red",
5329
+ strokeDash: "10 5"
5330
+ });
5331
+ }
5332
+ if (this.candidates) {
5333
+ const topCandidates = this.candidates.slice(0, 5);
5334
+ const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
5335
+ topCandidates.forEach((candidate, index) => {
5336
+ const opacity = 0.5 * (1 - index / 5);
5337
+ const backtrackedPath = this.getBacktrackedPath(candidate);
5338
+ graphics.lines.push({
5339
+ points: backtrackedPath.map(({ center: { x, y } }) => ({ x, y })),
5340
+ strokeColor: safeTransparentize(
5341
+ this.colorMap[connectionName] ?? "red",
5342
+ 1 - opacity
5343
+ )
5344
+ });
5345
+ });
5346
+ }
5347
+ return graphics;
5348
+ }
5349
+ };
5350
+
5351
+ // lib/solvers/CapacityPathingSolver/CapacityPathingSolver5.ts
5352
+ var CapacityPathingSolver5 = class extends CapacityPathingSolver {
5353
+ NEGATIVE_CAPACITY_PENALTY_FACTOR = 1;
5354
+ REDUCED_CAPACITY_PENALTY_FACTOR = 1;
5355
+ constructor(...args) {
5356
+ super(...args);
5357
+ this.GREEDY_MULTIPLIER = 2.5;
5358
+ }
5359
+ get maxCapacityFactor() {
5360
+ return this.hyperParameters.MAX_CAPACITY_FACTOR ?? 1;
5361
+ }
5362
+ getTotalCapacity(node) {
5363
+ return getTunedTotalCapacity1(node, this.maxCapacityFactor);
5364
+ }
5365
+ /**
5366
+ * Penalty you pay for using this node
5367
+ */
5368
+ getNodeCapacityPenalty(node) {
5369
+ return 0.05;
5370
+ if (node.availableZ.length === 1) {
5371
+ return 0;
5372
+ }
5373
+ const totalCapacity = this.getTotalCapacity(node);
5374
+ const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
5375
+ const remainingCapacity = totalCapacity - usedCapacity;
5376
+ const dist = this.activeCandidateStraightLineDistance;
5377
+ if (remainingCapacity <= 0) {
5378
+ const penalty = (-remainingCapacity + 1) / totalCapacity * dist * (this.NEGATIVE_CAPACITY_PENALTY_FACTOR / 4);
5379
+ return penalty ** 2;
5380
+ }
5381
+ return 1 / remainingCapacity * dist * this.REDUCED_CAPACITY_PENALTY_FACTOR / 8;
5382
+ }
5383
+ /**
5384
+ * We're rewarding travel into big nodes.
5385
+ *
5386
+ * To minimize shortest path, you'd want to comment this out.
5387
+ */
5388
+ getDistanceBetweenNodes(A, B) {
5389
+ const dx = A.center.x - B.center.x;
5390
+ const dy = A.center.y - B.center.y;
5391
+ return Math.sqrt(dx ** 2 + dy ** 2);
5392
+ }
5393
+ computeG(prevCandidate, node, endGoal) {
5394
+ return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node) + this.getNodeCapacityPenalty(node);
5395
+ }
5396
+ computeH(prevCandidate, node, endGoal) {
5397
+ return this.getDistanceBetweenNodes(node, endGoal);
5398
+ }
5399
+ };
5400
+
5401
+ // lib/solvers/StrawSolver/StrawSolver.ts
5402
+ var StrawSolver = class extends BaseSolver {
5403
+ multiLayerNodes;
5404
+ strawNodes;
5405
+ skippedNodes;
5406
+ unprocessedNodes;
5407
+ strawSize;
5408
+ nodeIdCounter;
5409
+ constructor(params) {
5410
+ super();
5411
+ this.strawSize = params.strawSize ?? 0.5;
5412
+ this.multiLayerNodes = [];
5413
+ this.strawNodes = [];
5414
+ this.skippedNodes = [];
5415
+ this.nodeIdCounter = 0;
5416
+ this.unprocessedNodes = [];
5417
+ for (const node of params.nodes) {
5418
+ if (node.availableZ.length === 1) {
5419
+ this.unprocessedNodes.push(node);
5420
+ } else {
5421
+ this.multiLayerNodes.push(node);
5422
+ }
5423
+ }
5424
+ }
5425
+ getCapacityOfMultiLayerNodesWithinBounds(bounds) {
5426
+ let totalCapacity = 0;
5427
+ for (const node of this.multiLayerNodes) {
5428
+ const nodeMinX = node.center.x - node.width / 2;
5429
+ const nodeMaxX = node.center.x + node.width / 2;
5430
+ const nodeMinY = node.center.y - node.height / 2;
5431
+ const nodeMaxY = node.center.y + node.height / 2;
5432
+ const overlapMinX = Math.max(bounds.minX, nodeMinX);
5433
+ const overlapMaxX = Math.min(bounds.maxX, nodeMaxX);
5434
+ const overlapMinY = Math.max(bounds.minY, nodeMinY);
5435
+ const overlapMaxY = Math.min(bounds.maxY, nodeMaxY);
5436
+ if (overlapMinX < overlapMaxX && overlapMinY < overlapMaxY) {
5437
+ const overlapWidth = overlapMaxX - overlapMinX;
5438
+ const overlapHeight = overlapMaxY - overlapMinY;
5439
+ const overlapArea = overlapWidth * overlapHeight;
5440
+ const nodeArea = node.width * node.height;
5441
+ const proportion = overlapArea / nodeArea;
5442
+ totalCapacity += getTunedTotalCapacity1(node) * proportion;
5443
+ }
5444
+ }
5445
+ return totalCapacity;
5446
+ }
5447
+ getSurroundingCapacities(node) {
5448
+ const searchDistance = Math.min(node.width, node.height);
5449
+ const leftSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
5450
+ minX: node.center.x - node.width / 2 - searchDistance,
5451
+ maxX: node.center.x - node.width / 2,
5452
+ minY: node.center.y - node.height / 2,
5453
+ maxY: node.center.y + node.height / 2
5454
+ });
5455
+ const rightSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
5456
+ minX: node.center.x + node.width / 2,
5457
+ maxX: node.center.x + node.width / 2 + searchDistance,
5458
+ minY: node.center.y - node.height / 2,
5459
+ maxY: node.center.y + node.height / 2
5460
+ });
5461
+ const topSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
5462
+ minX: node.center.x - node.width / 2,
5463
+ maxX: node.center.x + node.width / 2,
5464
+ minY: node.center.y - node.height / 2 - searchDistance,
5465
+ maxY: node.center.y - node.height / 2
5466
+ });
5467
+ const bottomSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
5468
+ minX: node.center.x - node.width / 2,
5469
+ maxX: node.center.x + node.width / 2,
5470
+ minY: node.center.y + node.height / 2,
5471
+ maxY: node.center.y + node.height / 2 + searchDistance
5472
+ });
5473
+ return {
5474
+ leftSurroundingCapacity,
5475
+ rightSurroundingCapacity,
5476
+ topSurroundingCapacity,
5477
+ bottomSurroundingCapacity
5478
+ };
5479
+ }
5480
+ /**
5481
+ * Creates straw nodes from a single-layer node based on surrounding capacities
5482
+ */
5483
+ createStrawsForNode(node) {
5484
+ const result = [];
5485
+ const {
5486
+ leftSurroundingCapacity,
5487
+ rightSurroundingCapacity,
5488
+ topSurroundingCapacity,
5489
+ bottomSurroundingCapacity
5490
+ } = this.getSurroundingCapacities(node);
5491
+ const horizontalCapacity = leftSurroundingCapacity + rightSurroundingCapacity;
5492
+ const verticalCapacity = topSurroundingCapacity + bottomSurroundingCapacity;
5493
+ const layerPrefersFactor = 1;
5494
+ const effectiveHorizontalCapacity = horizontalCapacity * layerPrefersFactor;
5495
+ if (effectiveHorizontalCapacity > verticalCapacity) {
5496
+ const numStraws = Math.floor(node.height / this.strawSize);
5497
+ const strawHeight = node.height / numStraws;
5498
+ for (let i = 0; i < numStraws; i++) {
5499
+ const strawCenterY = node.center.y - node.height / 2 + i * strawHeight + strawHeight / 2;
5500
+ result.push({
5501
+ capacityMeshNodeId: `${node.capacityMeshNodeId}_straw${i}`,
5502
+ center: { x: node.center.x, y: strawCenterY },
5503
+ width: node.width,
5504
+ height: strawHeight,
5505
+ layer: node.layer,
5506
+ availableZ: [...node.availableZ],
5507
+ _depth: node._depth,
5508
+ _strawNode: true
5509
+ });
5510
+ }
5511
+ } else {
5512
+ const numStraws = Math.floor(node.width / this.strawSize);
5513
+ const strawWidth = node.width / numStraws;
5514
+ for (let i = 0; i < numStraws; i++) {
5515
+ const strawCenterX = node.center.x - node.width / 2 + i * strawWidth + strawWidth / 2;
5516
+ result.push({
5517
+ capacityMeshNodeId: `${node.capacityMeshNodeId}_straw${i}`,
5518
+ center: { x: strawCenterX, y: node.center.y },
5519
+ width: strawWidth,
5520
+ height: node.height,
5521
+ layer: node.layer,
5522
+ availableZ: [...node.availableZ],
5523
+ _depth: node._depth,
5524
+ _strawNode: true
5525
+ });
5121
5526
  }
5122
- this.activeSolver = null;
5123
5527
  }
5528
+ return result;
5124
5529
  }
5125
- visualize() {
5126
- if (this.activeSolver) {
5127
- return this.activeSolver.visualize();
5530
+ getResultNodes() {
5531
+ return [...this.multiLayerNodes, ...this.strawNodes, ...this.skippedNodes];
5532
+ }
5533
+ _step() {
5534
+ const rootNode = this.unprocessedNodes.pop();
5535
+ if (!rootNode) {
5536
+ this.solved = true;
5537
+ return;
5538
+ }
5539
+ if (rootNode.width < this.strawSize * 5 && rootNode.height < this.strawSize * 5) {
5540
+ this.skippedNodes.push(rootNode);
5541
+ return;
5542
+ }
5543
+ if (rootNode._containsTarget) {
5544
+ this.skippedNodes.push(rootNode);
5545
+ return;
5128
5546
  }
5547
+ const strawNodes = this.createStrawsForNode(rootNode);
5548
+ this.strawNodes.push(...strawNodes);
5549
+ }
5550
+ visualize() {
5129
5551
  const graphics = {
5552
+ rects: [],
5130
5553
  lines: [],
5131
5554
  points: [],
5132
- rects: [],
5133
5555
  circles: [],
5134
- coordinateSystem: "cartesian",
5135
- title: "Unravel Multi Section Solver"
5556
+ title: "Straw Solver"
5136
5557
  };
5137
- for (const [nodeId, node] of this.nodeMap.entries()) {
5138
- const probabilityOfFailure = this.nodePfMap.get(nodeId) || 0;
5139
- const pf = Math.min(probabilityOfFailure, 1);
5140
- const red = Math.floor(255 * pf);
5141
- const green = Math.floor(255 * (1 - pf));
5142
- const color = `rgb(${red}, ${green}, 0)`;
5558
+ for (const node of this.unprocessedNodes) {
5143
5559
  graphics.rects.push({
5144
5560
  center: node.center,
5145
- label: `${nodeId}
5146
- ${node.width.toFixed(2)}x${node.height.toFixed(2)}
5147
- Pf: ${probabilityOfFailure.toFixed(3)}`,
5148
- color,
5149
- width: node.width / 8,
5150
- height: node.height / 8
5561
+ width: node.width,
5562
+ height: node.height,
5563
+ fill: "rgba(200, 200, 200, 0.5)",
5564
+ stroke: "rgba(0, 0, 0, 0.5)",
5565
+ label: `${node.capacityMeshNodeId}
5566
+ Unprocessed
5567
+ ${node.width}x${node.height}`
5151
5568
  });
5152
5569
  }
5153
- for (const segmentPoint of this.segmentPointMap.values()) {
5154
- graphics.points.push({
5155
- x: segmentPoint.x,
5156
- y: segmentPoint.y,
5157
- label: `${segmentPoint.segmentPointId}
5158
- Segment: ${segmentPoint.segmentId}
5159
- Layer: ${segmentPoint.z}`,
5160
- color: this.colorMap[segmentPoint.connectionName] || "#000"
5570
+ for (const node of this.strawNodes) {
5571
+ const color = node.availableZ[0] === 0 ? "rgba(0, 150, 255, 0.5)" : "rgba(255, 100, 0, 0.5)";
5572
+ graphics.rects.push({
5573
+ center: node.center,
5574
+ width: node.width,
5575
+ height: node.height,
5576
+ fill: color,
5577
+ stroke: "rgba(0, 0, 0, 0.5)",
5578
+ label: `${node.capacityMeshNodeId}
5579
+ Layer: ${node.availableZ[0]}
5580
+ ${node.width}x${node.height}`
5161
5581
  });
5162
5582
  }
5163
- const pointsBySegment = /* @__PURE__ */ new Map();
5164
- for (const point of this.segmentPointMap.values()) {
5165
- if (!pointsBySegment.has(point.segmentId)) {
5166
- pointsBySegment.set(point.segmentId, []);
5583
+ for (const node of this.multiLayerNodes) {
5584
+ graphics.rects.push({
5585
+ center: node.center,
5586
+ width: node.width * 0.9,
5587
+ height: node.height * 0.9,
5588
+ fill: "rgba(100, 255, 100, 0.5)",
5589
+ stroke: "rgba(0, 0, 0, 0.5)",
5590
+ layer: `z${node.availableZ.join(",")}`,
5591
+ label: `${node.capacityMeshNodeId}
5592
+ Layers: ${node.availableZ.join(",")}
5593
+ ${node.width}x${node.height}`
5594
+ });
5595
+ }
5596
+ return graphics;
5597
+ }
5598
+ };
5599
+
5600
+ // lib/solvers/SingleLayerNodeMerger/SingleLayerNodeMergerSolver.ts
5601
+ var EPSILON = 5e-3;
5602
+ var SingleLayerNodeMergerSolver = class extends BaseSolver {
5603
+ nodeMap;
5604
+ currentBatchNodeIds;
5605
+ absorbedNodeIds;
5606
+ nextBatchNodeIds;
5607
+ batchHadModifications;
5608
+ newNodes;
5609
+ constructor(nodes) {
5610
+ super();
5611
+ this.nodeMap = /* @__PURE__ */ new Map();
5612
+ this.MAX_ITERATIONS = 1e5;
5613
+ for (const node of nodes) {
5614
+ this.nodeMap.set(node.capacityMeshNodeId, node);
5615
+ }
5616
+ this.newNodes = [];
5617
+ this.absorbedNodeIds = /* @__PURE__ */ new Set();
5618
+ const nodeWithArea = [];
5619
+ for (const node of nodes) {
5620
+ if (node.availableZ.length > 1) {
5621
+ this.newNodes.push(node);
5622
+ this.absorbedNodeIds.add(node.capacityMeshNodeId);
5623
+ } else {
5624
+ nodeWithArea.push([node.capacityMeshNodeId, node.width * node.height]);
5167
5625
  }
5168
- pointsBySegment.get(point.segmentId).push(point);
5169
5626
  }
5170
- for (const [segmentId, points] of pointsBySegment.entries()) {
5171
- if (points.length < 2) continue;
5172
- const sortedPoints = [...points].sort(
5173
- (a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y
5174
- );
5175
- for (let i = 0; i < sortedPoints.length - 1; i++) {
5176
- graphics.lines.push({
5177
- points: [
5178
- { x: sortedPoints[i].x, y: sortedPoints[i].y },
5179
- { x: sortedPoints[i + 1].x, y: sortedPoints[i + 1].y }
5180
- ],
5181
- strokeColor: this.colorMap[segmentId] || "#000"
5627
+ nodeWithArea.sort((a, b) => a[1] - b[1]);
5628
+ this.currentBatchNodeIds = nodeWithArea.map((n) => n[0]);
5629
+ this.nextBatchNodeIds = [];
5630
+ this.batchHadModifications = false;
5631
+ }
5632
+ getAdjacentSameLayerUnprocessedNodes(rootNode) {
5633
+ const adjacentNodes = [];
5634
+ for (const unprocessedNodeId of this.currentBatchNodeIds) {
5635
+ if (this.absorbedNodeIds.has(unprocessedNodeId)) continue;
5636
+ const unprocessedNode = this.nodeMap.get(unprocessedNodeId);
5637
+ if (unprocessedNode.availableZ[0] !== rootNode.availableZ[0]) continue;
5638
+ if (unprocessedNode._containsTarget && unprocessedNode._targetConnectionName !== rootNode._targetConnectionName)
5639
+ continue;
5640
+ if (!areNodesBordering(rootNode, unprocessedNode)) continue;
5641
+ adjacentNodes.push(unprocessedNode);
5642
+ }
5643
+ return adjacentNodes;
5644
+ }
5645
+ _step() {
5646
+ let rootNodeId = this.currentBatchNodeIds.pop();
5647
+ while (rootNodeId && this.absorbedNodeIds.has(rootNodeId)) {
5648
+ rootNodeId = this.currentBatchNodeIds.pop();
5649
+ }
5650
+ if (!rootNodeId) {
5651
+ if (this.batchHadModifications) {
5652
+ this.currentBatchNodeIds = this.nextBatchNodeIds.sort((a, b) => {
5653
+ const A = this.nodeMap.get(a);
5654
+ const B = this.nodeMap.get(b);
5655
+ return A.width * A.height - B.width * B.height;
5182
5656
  });
5657
+ this.nextBatchNodeIds = [];
5658
+ this.batchHadModifications = false;
5659
+ return;
5183
5660
  }
5661
+ this.solved = true;
5662
+ this.newNodes.push(
5663
+ ...this.nextBatchNodeIds.map((id) => this.nodeMap.get(id))
5664
+ );
5665
+ return;
5184
5666
  }
5185
- const processedConnections = /* @__PURE__ */ new Set();
5186
- const allPoints = Array.from(this.segmentPointMap.values());
5187
- for (let i = 0; i < allPoints.length; i++) {
5188
- const point1 = allPoints[i];
5189
- for (let j = i + 1; j < allPoints.length; j++) {
5190
- const point2 = allPoints[j];
5191
- if (point1.connectionName !== point2.connectionName || point1.segmentId === point2.segmentId) {
5192
- continue;
5667
+ const rootNode = this.nodeMap.get(rootNodeId);
5668
+ let rootNodeHasGrown = false;
5669
+ const adjacentNodes = this.getAdjacentSameLayerUnprocessedNodes(rootNode);
5670
+ if (adjacentNodes.length === 0) {
5671
+ this.nextBatchNodeIds.push(rootNodeId);
5672
+ return;
5673
+ }
5674
+ const adjacentNodesToLeft = adjacentNodes.filter(
5675
+ (adjNode) => adjNode.center.x < rootNode.center.x && Math.abs(adjNode.center.y - rootNode.center.y) < rootNode.height / 2
5676
+ );
5677
+ if (adjacentNodesToLeft.length > 0) {
5678
+ const { width: leftAdjNodeWidth, height: leftAdjNodeHeight } = adjacentNodesToLeft[0];
5679
+ const leftAdjNodesAreAllSameSize = adjacentNodesToLeft.every(
5680
+ (adjNode) => adjNode.width === leftAdjNodeWidth && adjNode.height === leftAdjNodeHeight
5681
+ );
5682
+ const leftAdjNodesTakeUpEntireHeight = Math.abs(
5683
+ adjacentNodesToLeft.reduce((acc, adjNode) => {
5684
+ return acc + adjNode.height;
5685
+ }, 0) - rootNode.height
5686
+ ) < EPSILON;
5687
+ if (leftAdjNodesTakeUpEntireHeight && leftAdjNodesAreAllSameSize) {
5688
+ rootNode.width += leftAdjNodeWidth;
5689
+ rootNode.center.x = rootNode.center.x - leftAdjNodeWidth / 2;
5690
+ for (const adjNode of adjacentNodesToLeft) {
5691
+ this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
5193
5692
  }
5194
- const hasSharedNode = point1.capacityMeshNodeIds.some(
5195
- (nodeId) => point2.capacityMeshNodeIds.includes(nodeId)
5196
- );
5197
- if (hasSharedNode) {
5198
- const connectionKey = `${point1.segmentPointId}-${point2.segmentPointId}`;
5199
- if (processedConnections.has(connectionKey)) continue;
5200
- processedConnections.add(connectionKey);
5201
- const sameLayer = point1.z === point2.z;
5202
- const layer = point1.z;
5203
- let strokeDash;
5204
- if (sameLayer) {
5205
- strokeDash = layer === 0 ? void 0 : "10 5";
5206
- } else {
5207
- strokeDash = "3 3 10";
5208
- }
5209
- graphics.lines.push({
5210
- points: [
5211
- { x: point1.x, y: point1.y },
5212
- { x: point2.x, y: point2.y }
5213
- ],
5214
- strokeDash,
5215
- strokeColor: this.colorMap[point1.connectionName] || "#666"
5216
- });
5693
+ rootNodeHasGrown = true;
5694
+ }
5695
+ }
5696
+ const adjacentNodesToRight = adjacentNodes.filter(
5697
+ (adjNode) => adjNode.center.x > rootNode.center.x && Math.abs(adjNode.center.y - rootNode.center.y) < rootNode.height / 2
5698
+ );
5699
+ if (adjacentNodesToRight.length > 0 && !rootNodeHasGrown) {
5700
+ const { width: rightAdjNodeWidth, height: rightAdjNodeHeight } = adjacentNodesToRight[0];
5701
+ const rightAdjNodesAreAllSameSize = adjacentNodesToRight.every(
5702
+ (adjNode) => adjNode.width === rightAdjNodeWidth && adjNode.height === rightAdjNodeHeight
5703
+ );
5704
+ const rightAdjNodesTakeUpEntireHeight = Math.abs(
5705
+ adjacentNodesToRight.reduce((acc, adjNode) => {
5706
+ return acc + adjNode.height;
5707
+ }, 0) - rootNode.height
5708
+ ) < EPSILON;
5709
+ if (rightAdjNodesTakeUpEntireHeight && rightAdjNodesAreAllSameSize) {
5710
+ rootNode.width += rightAdjNodeWidth;
5711
+ rootNode.center.x = rootNode.center.x + rightAdjNodeWidth / 2;
5712
+ for (const adjNode of adjacentNodesToRight) {
5713
+ this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
5217
5714
  }
5715
+ rootNodeHasGrown = true;
5218
5716
  }
5219
5717
  }
5220
- return graphics;
5221
- }
5222
- getNodesWithPortPoints() {
5223
- if (!this.solved) {
5224
- throw new Error(
5225
- "CapacitySegmentToPointSolver not solved, can't give port points yet"
5718
+ const adjacentNodesToTop = adjacentNodes.filter(
5719
+ (adjNode) => adjNode.center.y > rootNode.center.y && Math.abs(adjNode.center.x - rootNode.center.x) < rootNode.width / 2
5720
+ );
5721
+ if (adjacentNodesToTop.length > 0 && !rootNodeHasGrown) {
5722
+ const { width: topAdjNodeWidth, height: topAdjNodeHeight } = adjacentNodesToTop[0];
5723
+ const topAdjNodesAreAllSameSize = adjacentNodesToTop.every(
5724
+ (adjNode) => adjNode.width === topAdjNodeWidth && adjNode.height === topAdjNodeHeight
5226
5725
  );
5726
+ const topAdjNodesTakeUpEntireWidth = Math.abs(
5727
+ adjacentNodesToTop.reduce((acc, adjNode) => {
5728
+ return acc + adjNode.width;
5729
+ }, 0) - rootNode.width
5730
+ ) < EPSILON;
5731
+ if (topAdjNodesTakeUpEntireWidth && topAdjNodesAreAllSameSize) {
5732
+ rootNode.height += topAdjNodeHeight;
5733
+ rootNode.center.y = rootNode.center.y + topAdjNodeHeight / 2;
5734
+ for (const adjNode of adjacentNodesToTop) {
5735
+ this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
5736
+ }
5737
+ rootNodeHasGrown = true;
5738
+ }
5227
5739
  }
5228
- const nodeWithPortPointsMap = /* @__PURE__ */ new Map();
5229
- for (const segment of this.dedupedSegments) {
5230
- const segId = segment.nodePortSegmentId;
5231
- for (const nodeId of this.segmentIdToNodeIds.get(segId)) {
5232
- const node = this.nodeMap.get(nodeId);
5233
- if (!nodeWithPortPointsMap.has(nodeId)) {
5234
- nodeWithPortPointsMap.set(nodeId, {
5235
- capacityMeshNodeId: nodeId,
5236
- portPoints: [],
5237
- center: node.center,
5238
- width: node.width,
5239
- height: node.height
5240
- });
5740
+ const adjacentNodesToBottom = adjacentNodes.filter(
5741
+ (adjNode) => adjNode.center.y < rootNode.center.y && Math.abs(adjNode.center.x - rootNode.center.x) < rootNode.width / 2
5742
+ );
5743
+ if (adjacentNodesToBottom.length > 0 && !rootNodeHasGrown) {
5744
+ const { width: bottomAdjNodeWidth, height: bottomAdjNodeHeight } = adjacentNodesToBottom[0];
5745
+ const bottomAdjNodesAreAllSameSize = adjacentNodesToBottom.every(
5746
+ (adjNode) => adjNode.width === bottomAdjNodeWidth && adjNode.height === bottomAdjNodeHeight
5747
+ );
5748
+ const bottomAdjNodesTakeUpEntireWidth = Math.abs(
5749
+ adjacentNodesToBottom.reduce((acc, adjNode) => {
5750
+ return acc + adjNode.width;
5751
+ }, 0) - rootNode.width
5752
+ ) < EPSILON;
5753
+ if (bottomAdjNodesTakeUpEntireWidth && bottomAdjNodesAreAllSameSize) {
5754
+ rootNode.height += bottomAdjNodeHeight;
5755
+ rootNode.center.y = rootNode.center.y - bottomAdjNodeHeight / 2;
5756
+ for (const adjNode of adjacentNodesToBottom) {
5757
+ this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
5241
5758
  }
5759
+ rootNodeHasGrown = true;
5242
5760
  }
5243
5761
  }
5244
- for (const segmentPoint of this.segmentPointMap.values()) {
5245
- for (const nodeId of segmentPoint.capacityMeshNodeIds) {
5246
- const nodeWithPortPoints = nodeWithPortPointsMap.get(nodeId);
5247
- if (nodeWithPortPoints) {
5248
- nodeWithPortPoints.portPoints.push({
5249
- x: segmentPoint.x,
5250
- y: segmentPoint.y,
5251
- z: segmentPoint.z,
5252
- connectionName: segmentPoint.connectionName
5253
- });
5762
+ if (rootNodeHasGrown) {
5763
+ this.batchHadModifications = true;
5764
+ this.currentBatchNodeIds.push(rootNodeId);
5765
+ } else {
5766
+ this.nextBatchNodeIds.unshift(rootNodeId);
5767
+ }
5768
+ }
5769
+ visualize() {
5770
+ const graphics = {
5771
+ circles: [],
5772
+ lines: [],
5773
+ points: [],
5774
+ rects: [],
5775
+ coordinateSystem: "cartesian",
5776
+ title: "Same Layer Node Merger"
5777
+ };
5778
+ for (const node of this.newNodes) {
5779
+ graphics.rects.push(createRectFromCapacityNode(node));
5780
+ }
5781
+ const nextNodeIdInBatch = this.currentBatchNodeIds[this.currentBatchNodeIds.length - 1];
5782
+ let adjacentNodes;
5783
+ if (nextNodeIdInBatch) {
5784
+ adjacentNodes = this.getAdjacentSameLayerUnprocessedNodes(
5785
+ this.nodeMap.get(nextNodeIdInBatch)
5786
+ );
5787
+ }
5788
+ for (const nodeId of this.currentBatchNodeIds) {
5789
+ const node = this.nodeMap.get(nodeId);
5790
+ if (this.absorbedNodeIds.has(nodeId)) continue;
5791
+ if (node) {
5792
+ const rect = createRectFromCapacityNode(node, {
5793
+ rectMargin: 0.01
5794
+ });
5795
+ if (nodeId === nextNodeIdInBatch) {
5796
+ rect.stroke = "rgba(0, 255, 0, 0.8)";
5797
+ } else if (adjacentNodes?.some(
5798
+ (adjNode) => adjNode.capacityMeshNodeId === nodeId
5799
+ )) {
5800
+ rect.stroke = "rgba(128, 0, 128, 0.8)";
5801
+ } else {
5802
+ rect.stroke = "rgba(255, 165, 0, 0.8)";
5254
5803
  }
5804
+ rect.layer = `z${node.availableZ.join(",")}`;
5805
+ rect.label = `${rect.label}
5806
+ (unprocessed)`;
5807
+ graphics.rects.push(rect);
5255
5808
  }
5256
5809
  }
5257
- return Array.from(nodeWithPortPointsMap.values());
5810
+ for (const nodeId of this.nextBatchNodeIds) {
5811
+ const node = this.nodeMap.get(nodeId);
5812
+ if (this.absorbedNodeIds.has(nodeId)) continue;
5813
+ if (node) {
5814
+ const rect = createRectFromCapacityNode(node, {
5815
+ rectMargin: 0.01
5816
+ });
5817
+ rect.layer = `z${node.availableZ.join(",")}`;
5818
+ rect.stroke = "rgba(0, 217, 255, 0.8)";
5819
+ rect.label = `${rect.label}
5820
+ x: ${node.center.x}, y: ${node.center.y}
5821
+ ${node.width}x${node.height}
5822
+ (next batch)`;
5823
+ graphics.rects.push(rect);
5824
+ }
5825
+ }
5826
+ return graphics;
5258
5827
  }
5259
5828
  };
5260
5829
 
@@ -5301,12 +5870,15 @@ var CapacityMeshSolver = class extends BaseSolver {
5301
5870
  segmentToPointOptimizer;
5302
5871
  highDensityRouteSolver;
5303
5872
  highDensityStitchSolver;
5873
+ singleLayerNodeMerger;
5874
+ strawSolver;
5304
5875
  startTimeOfPhase;
5305
5876
  endTimeOfPhase;
5306
5877
  timeSpentOnPhase;
5307
- activeSolver = null;
5878
+ activeSubSolver = null;
5308
5879
  connMap;
5309
5880
  srjWithPointPairs;
5881
+ capacityNodes = null;
5310
5882
  pipelineDef = [
5311
5883
  definePipelineStep(
5312
5884
  "netToPointPairsSolver",
@@ -5322,39 +5894,65 @@ var CapacityMeshSolver = class extends BaseSolver {
5322
5894
  }
5323
5895
  }
5324
5896
  ),
5325
- definePipelineStep("nodeSolver", CapacityMeshNodeSolver, (cms) => [
5326
- cms.netToPointPairsSolver?.getNewSimpleRouteJson() || cms.srj,
5327
- cms.opts
5328
- ]),
5329
- definePipelineStep("nodeTargetMerger", CapacityNodeTargetMerger, (cms) => [
5330
- cms.nodeSolver?.finishedNodes || [],
5331
- cms.srj.obstacles,
5332
- cms.connMap
5333
- ]),
5334
- definePipelineStep("edgeSolver", CapacityMeshEdgeSolver, (cms) => [
5335
- cms.nodeTargetMerger?.newNodes || []
5336
- ]),
5897
+ // definePipelineStep("nodeSolver", CapacityMeshNodeSolver, (cms) => [
5898
+ // cms.netToPointPairsSolver?.getNewSimpleRouteJson() || cms.srj,
5899
+ // cms.opts,
5900
+ // ]),
5337
5901
  definePipelineStep(
5338
- "pathingSolver",
5339
- CapacityPathingSolver4_FlexibleNegativeCapacity,
5902
+ "nodeSolver",
5903
+ CapacityMeshNodeSolver2_NodeUnderObstacle,
5340
5904
  (cms) => [
5341
- {
5342
- simpleRouteJson: cms.srjWithPointPairs,
5343
- nodes: cms.nodeTargetMerger?.newNodes || [],
5344
- edges: cms.edgeSolver?.edges || [],
5345
- colorMap: cms.colorMap,
5346
- hyperParameters: {
5347
- MAX_CAPACITY_FACTOR: 1
5348
- }
5349
- }
5905
+ cms.netToPointPairsSolver?.getNewSimpleRouteJson() || cms.srj,
5906
+ cms.opts
5350
5907
  ]
5351
5908
  ),
5909
+ // definePipelineStep("nodeTargetMerger", CapacityNodeTargetMerger, (cms) => [
5910
+ // cms.nodeSolver?.finishedNodes || [],
5911
+ // cms.srj.obstacles,
5912
+ // cms.connMap,
5913
+ // ]),
5914
+ // definePipelineStep("nodeTargetMerger", CapacityNodeTargetMerger2, (cms) => [
5915
+ // cms.nodeSolver?.finishedNodes || [],
5916
+ // cms.srj.obstacles,
5917
+ // cms.connMap,
5918
+ // cms.colorMap,
5919
+ // cms.srj.connections,
5920
+ // ]),
5921
+ definePipelineStep(
5922
+ "singleLayerNodeMerger",
5923
+ SingleLayerNodeMergerSolver,
5924
+ (cms) => [cms.nodeSolver?.finishedNodes]
5925
+ ),
5926
+ definePipelineStep(
5927
+ "strawSolver",
5928
+ StrawSolver,
5929
+ (cms) => [{ nodes: cms.singleLayerNodeMerger?.newNodes }],
5930
+ {
5931
+ onSolved: (cms) => {
5932
+ cms.capacityNodes = cms.strawSolver?.getResultNodes();
5933
+ }
5934
+ }
5935
+ ),
5936
+ definePipelineStep("edgeSolver", CapacityMeshEdgeSolver, (cms) => [
5937
+ cms.capacityNodes
5938
+ ]),
5939
+ definePipelineStep("pathingSolver", CapacityPathingSolver5, (cms) => [
5940
+ {
5941
+ simpleRouteJson: cms.srjWithPointPairs,
5942
+ nodes: cms.capacityNodes,
5943
+ edges: cms.edgeSolver?.edges || [],
5944
+ colorMap: cms.colorMap,
5945
+ hyperParameters: {
5946
+ MAX_CAPACITY_FACTOR: 1
5947
+ }
5948
+ }
5949
+ ]),
5352
5950
  definePipelineStep(
5353
5951
  "edgeToPortSegmentSolver",
5354
5952
  CapacityEdgeToPortSegmentSolver,
5355
5953
  (cms) => [
5356
5954
  {
5357
- nodes: cms.nodeTargetMerger?.newNodes || [],
5955
+ nodes: cms.capacityNodes,
5358
5956
  edges: cms.edgeSolver?.edges || [],
5359
5957
  capacityPaths: cms.pathingSolver?.getCapacityPaths() || [],
5360
5958
  colorMap: cms.colorMap
@@ -5375,7 +5973,7 @@ var CapacityMeshSolver = class extends BaseSolver {
5375
5973
  {
5376
5974
  segments: allSegments,
5377
5975
  colorMap: cms.colorMap,
5378
- nodes: cms.nodeTargetMerger?.newNodes || []
5976
+ nodes: cms.capacityNodes
5379
5977
  }
5380
5978
  ];
5381
5979
  }
@@ -5398,7 +5996,7 @@ var CapacityMeshSolver = class extends BaseSolver {
5398
5996
  {
5399
5997
  assignedSegments: cms.segmentToPointSolver?.solvedSegments || [],
5400
5998
  colorMap: cms.colorMap,
5401
- nodes: cms.nodeTargetMerger?.newNodes || []
5999
+ nodes: cms.capacityNodes
5402
6000
  }
5403
6001
  ]
5404
6002
  ),
@@ -5428,26 +6026,24 @@ var CapacityMeshSolver = class extends BaseSolver {
5428
6026
  this.solved = true;
5429
6027
  return;
5430
6028
  }
5431
- if (this.activeSolver) {
5432
- this.activeSolver.step();
5433
- if (this.activeSolver.solved) {
6029
+ if (this.activeSubSolver) {
6030
+ this.activeSubSolver.step();
6031
+ if (this.activeSubSolver.solved) {
5434
6032
  this.endTimeOfPhase[pipelineStepDef.solverName] = performance.now();
5435
6033
  this.timeSpentOnPhase[pipelineStepDef.solverName] = this.endTimeOfPhase[pipelineStepDef.solverName] - this.startTimeOfPhase[pipelineStepDef.solverName];
5436
6034
  pipelineStepDef.onSolved?.(this);
5437
- this.activeSolver = null;
6035
+ this.activeSubSolver = null;
5438
6036
  this.currentPipelineStepIndex++;
5439
- } else if (this.activeSolver.failed) {
5440
- this.error = this.activeSolver?.error;
6037
+ } else if (this.activeSubSolver.failed) {
6038
+ this.error = this.activeSubSolver?.error;
5441
6039
  this.failed = true;
5442
- this.activeSolver = null;
6040
+ this.activeSubSolver = null;
5443
6041
  }
5444
6042
  return;
5445
6043
  }
5446
6044
  const constructorParams = pipelineStepDef.getConstructorParams(this);
5447
- this.activeSolver = new pipelineStepDef.solverClass(
5448
- ...constructorParams
5449
- );
5450
- this[pipelineStepDef.solverName] = this.activeSolver;
6045
+ this.activeSubSolver = new pipelineStepDef.solverClass(...constructorParams);
6046
+ this[pipelineStepDef.solverName] = this.activeSubSolver;
5451
6047
  this.timeSpentOnPhase[pipelineStepDef.solverName] = 0;
5452
6048
  this.startTimeOfPhase[pipelineStepDef.solverName] = performance.now();
5453
6049
  }
@@ -5455,9 +6051,13 @@ var CapacityMeshSolver = class extends BaseSolver {
5455
6051
  return this.pipelineDef[this.currentPipelineStepIndex]?.solverName ?? "none";
5456
6052
  }
5457
6053
  visualize() {
5458
- if (!this.solved && this.activeSolver) return this.activeSolver.visualize();
6054
+ if (!this.solved && this.activeSubSolver)
6055
+ return this.activeSubSolver.visualize();
5459
6056
  const netToPPSolver = this.netToPointPairsSolver?.visualize();
5460
6057
  const nodeViz = this.nodeSolver?.visualize();
6058
+ const nodeTargetMergerViz = this.nodeTargetMerger?.visualize();
6059
+ const singleLayerNodeMergerViz = this.singleLayerNodeMerger?.visualize();
6060
+ const strawSolverViz = this.strawSolver?.visualize();
5461
6061
  const edgeViz = this.edgeSolver?.visualize();
5462
6062
  const pathingViz = this.pathingSolver?.visualize();
5463
6063
  const edgeToPortSegmentViz = this.edgeToPortSegmentSolver?.visualize();
@@ -5470,14 +6070,37 @@ var CapacityMeshSolver = class extends BaseSolver {
5470
6070
  rects: [
5471
6071
  ...(this.srj.obstacles ?? []).map((o) => ({
5472
6072
  ...o,
5473
- fill: "rgba(255,0,0,0.25)"
6073
+ 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)"
5474
6074
  }))
6075
+ ],
6076
+ lines: [
6077
+ {
6078
+ points: [
6079
+ // Add five points representing the bounds of the PCB
6080
+ {
6081
+ x: this.srj.bounds?.minX ?? -50,
6082
+ y: this.srj.bounds?.minY ?? -50
6083
+ },
6084
+ { x: this.srj.bounds?.maxX ?? 50, y: this.srj.bounds?.minY ?? -50 },
6085
+ { x: this.srj.bounds?.maxX ?? 50, y: this.srj.bounds?.maxY ?? 50 },
6086
+ { x: this.srj.bounds?.minX ?? -50, y: this.srj.bounds?.maxY ?? 50 },
6087
+ {
6088
+ x: this.srj.bounds?.minX ?? -50,
6089
+ y: this.srj.bounds?.minY ?? -50
6090
+ }
6091
+ // Close the rectangle
6092
+ ],
6093
+ strokeColor: "rgba(255,0,0,0.25)"
6094
+ }
5475
6095
  ]
5476
6096
  };
5477
6097
  const visualizations = [
5478
6098
  problemViz,
5479
6099
  netToPPSolver,
5480
6100
  nodeViz,
6101
+ nodeTargetMergerViz,
6102
+ singleLayerNodeMergerViz,
6103
+ strawSolverViz,
5481
6104
  edgeViz,
5482
6105
  pathingViz,
5483
6106
  edgeToPortSegmentViz,