@tscircuit/capacity-autorouter 0.0.21 → 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]) && 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();
@@ -2884,294 +3037,94 @@ var HighDensitySolver = class extends BaseSolver {
2884
3037
  }
2885
3038
  };
2886
3039
 
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;
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
+ }
2934
3052
  }
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;
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
+ }
2951
3077
  }
2952
3078
  }
2953
- nodesForConnection.push(closestNode);
2954
3079
  }
2955
- if (nodesForConnection.length < 2) {
2956
- throw new Error(
2957
- `Not enough nodes for connection "${connection.name}", only ${nodesForConnection.length} found`
2958
- );
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;
2959
3085
  }
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
3086
  }
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
3087
  }
2978
- computeH(prevCandidate, node, endGoal) {
2979
- return this.getDistanceBetweenNodes(node, endGoal);
3088
+ getIdsConnectedToNet(netId) {
3089
+ return this.netMap[netId] || [];
2980
3090
  }
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;
3091
+ getNetConnectedToId(id) {
3092
+ return this.idToNetMap[id];
2989
3093
  }
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));
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;
2994
3101
  }
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
- });
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;
3108
+ }
3109
+ if (nextNetId !== netId) {
3110
+ return false;
3005
3111
  }
3006
3112
  }
3007
- return capacityPaths;
3113
+ return true;
3008
3114
  }
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
- }
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]]);
3144
3124
  }
3145
3125
  }
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
3126
  }
3127
+ return connMap;
3175
3128
  };
3176
3129
 
3177
3130
  // lib/utils/getTunedTotalCapacity1.ts
@@ -3197,267 +3150,44 @@ var calculateOptimalCapacityDepth = (initialWidth, targetMinCapacity = 0.5, maxD
3197
3150
  return Math.max(1, depth);
3198
3151
  };
3199
3152
 
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);
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;
3215
3160
  }
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;
3161
+ };
3162
+ var KDTree = class {
3163
+ root = null;
3164
+ constructor(points) {
3165
+ if (points.length > 0) {
3166
+ this.root = this.buildTree(points, 0);
3227
3167
  }
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
3168
  }
3246
- computeH(prevCandidate, node, endGoal) {
3247
- return this.getDistanceBetweenNodes(node, endGoal) + this.getNodeCapacityPenalty(node);
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;
3248
3181
  }
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
- }
3182
+ // Find the nearest neighbor to a query point
3183
+ findNearestNeighbor(queryPoint) {
3184
+ if (!this.root) {
3185
+ throw new Error("Tree is empty");
3262
3186
  }
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;
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;
3461
3191
  }
3462
3192
  nearestNeighborSearch(node, queryPoint, depth, best, bestDistance) {
3463
3193
  if (!node) {
@@ -4347,6 +4077,7 @@ var createSegmentPointMap = (dedupedSegments, segmentIdToNodeIds) => {
4347
4077
  var UnravelSectionSolver = class extends BaseSolver {
4348
4078
  nodeMap;
4349
4079
  dedupedSegments;
4080
+ dedupedSegmentMap;
4350
4081
  MUTABLE_HOPS = 1;
4351
4082
  unravelSection;
4352
4083
  candidates = [];
@@ -4366,6 +4097,14 @@ var UnravelSectionSolver = class extends BaseSolver {
4366
4097
  this.MUTABLE_HOPS = params.MUTABLE_HOPS ?? this.MUTABLE_HOPS;
4367
4098
  this.nodeMap = params.nodeMap;
4368
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
+ }
4369
4108
  this.nodeIdToSegmentIds = params.nodeIdToSegmentIds;
4370
4109
  this.segmentIdToNodeIds = params.segmentIdToNodeIds;
4371
4110
  this.rootNodeId = params.rootNodeId;
@@ -4524,14 +4263,20 @@ var UnravelSectionSolver = class extends BaseSolver {
4524
4263
  const [APointId, BPointId] = issue.segmentPoints;
4525
4264
  const pointA = this.getPointInCandidate(candidate, APointId);
4526
4265
  const pointB = this.getPointInCandidate(candidate, BPointId);
4527
- 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)) {
4528
4273
  operations.push({
4529
4274
  type: "change_layer",
4530
4275
  newZ: pointB.z,
4531
4276
  segmentPointIds: [APointId]
4532
4277
  });
4533
4278
  }
4534
- if (this.unravelSection.mutableSegmentIds.has(pointB.segmentId)) {
4279
+ if (this.unravelSection.mutableSegmentIds.has(pointB.segmentId) && bAvailableZ.includes(pointA.z)) {
4535
4280
  operations.push({
4536
4281
  type: "change_layer",
4537
4282
  newZ: pointA.z,
@@ -4986,6 +4731,7 @@ var calculateNodeProbabilityOfFailure = (node, numSameLayerCrossings, numEntryEx
4986
4731
  // lib/solvers/UnravelSolver/UnravelMultiSectionSolver.ts
4987
4732
  var UnravelMultiSectionSolver = class extends BaseSolver {
4988
4733
  nodeMap;
4734
+ dedupedSegmentMap;
4989
4735
  dedupedSegments;
4990
4736
  nodeIdToSegmentIds;
4991
4737
  segmentIdToNodeIds;
@@ -5009,6 +4755,10 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
5009
4755
  super();
5010
4756
  this.MAX_ITERATIONS = 1e5;
5011
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
+ }
5012
4762
  this.nodeMap = /* @__PURE__ */ new Map();
5013
4763
  for (const node of nodes) {
5014
4764
  this.nodeMap.set(node.capacityMeshNodeId, node);
@@ -5087,6 +4837,7 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
5087
4837
  );
5088
4838
  this.activeSolver = new UnravelSectionSolver({
5089
4839
  dedupedSegments: this.dedupedSegments,
4840
+ dedupedSegmentMap: this.dedupedSegmentMap,
5090
4841
  nodeMap: this.nodeMap,
5091
4842
  nodeIdToSegmentIds: this.nodeIdToSegmentIds,
5092
4843
  segmentIdToNodeIds: this.segmentIdToNodeIds,
@@ -5113,148 +4864,963 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
5113
4864
  segmentPoint.z = pointModification.z ?? segmentPoint.z;
5114
4865
  }
5115
4866
  }
5116
- for (const nodeId of this.activeSolver.unravelSection.allNodeIds) {
5117
- this.nodePfMap.set(
5118
- nodeId,
5119
- this.computeNodePf(this.nodeMap.get(nodeId))
5120
- );
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
+ });
5121
5523
  }
5122
- this.activeSolver = null;
5123
5524
  }
5525
+ return result;
5124
5526
  }
5125
- visualize() {
5126
- if (this.activeSolver) {
5127
- 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;
5128
5543
  }
5544
+ const strawNodes = this.createStrawsForNode(rootNode);
5545
+ this.strawNodes.push(...strawNodes);
5546
+ }
5547
+ visualize() {
5129
5548
  const graphics = {
5549
+ rects: [],
5130
5550
  lines: [],
5131
5551
  points: [],
5132
- rects: [],
5133
5552
  circles: [],
5134
- coordinateSystem: "cartesian",
5135
- title: "Unravel Multi Section Solver"
5553
+ title: "Straw Solver"
5136
5554
  };
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)`;
5555
+ for (const node of this.unprocessedNodes) {
5143
5556
  graphics.rects.push({
5144
5557
  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
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}`
5151
5565
  });
5152
5566
  }
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"
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}`
5161
5578
  });
5162
5579
  }
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, []);
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]);
5167
5622
  }
5168
- pointsBySegment.get(point.segmentId).push(point);
5169
5623
  }
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"
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;
5182
5653
  });
5654
+ this.nextBatchNodeIds = [];
5655
+ this.batchHadModifications = false;
5656
+ return;
5183
5657
  }
5658
+ this.solved = true;
5659
+ this.newNodes.push(
5660
+ ...this.nextBatchNodeIds.map((id) => this.nodeMap.get(id))
5661
+ );
5662
+ return;
5184
5663
  }
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;
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);
5193
5689
  }
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
- });
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);
5217
5711
  }
5712
+ rootNodeHasGrown = true;
5218
5713
  }
5219
5714
  }
5220
- return graphics;
5221
- }
5222
- getNodesWithPortPoints() {
5223
- if (!this.solved) {
5224
- throw new Error(
5225
- "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
5226
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
+ }
5227
5736
  }
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
- });
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);
5241
5755
  }
5756
+ rootNodeHasGrown = true;
5242
5757
  }
5243
5758
  }
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
- });
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)";
5254
5800
  }
5801
+ rect.layer = `z${node.availableZ.join(",")}`;
5802
+ rect.label = `${rect.label}
5803
+ (unprocessed)`;
5804
+ graphics.rects.push(rect);
5255
5805
  }
5256
5806
  }
5257
- 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;
5258
5824
  }
5259
5825
  };
5260
5826
 
@@ -5301,12 +5867,15 @@ var CapacityMeshSolver = class extends BaseSolver {
5301
5867
  segmentToPointOptimizer;
5302
5868
  highDensityRouteSolver;
5303
5869
  highDensityStitchSolver;
5870
+ singleLayerNodeMerger;
5871
+ strawSolver;
5304
5872
  startTimeOfPhase;
5305
5873
  endTimeOfPhase;
5306
5874
  timeSpentOnPhase;
5307
- activeSolver = null;
5875
+ activeSubSolver = null;
5308
5876
  connMap;
5309
5877
  srjWithPointPairs;
5878
+ capacityNodes = null;
5310
5879
  pipelineDef = [
5311
5880
  definePipelineStep(
5312
5881
  "netToPointPairsSolver",
@@ -5322,39 +5891,65 @@ var CapacityMeshSolver = class extends BaseSolver {
5322
5891
  }
5323
5892
  }
5324
5893
  ),
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
- ]),
5894
+ // definePipelineStep("nodeSolver", CapacityMeshNodeSolver, (cms) => [
5895
+ // cms.netToPointPairsSolver?.getNewSimpleRouteJson() || cms.srj,
5896
+ // cms.opts,
5897
+ // ]),
5337
5898
  definePipelineStep(
5338
- "pathingSolver",
5339
- CapacityPathingSolver4_FlexibleNegativeCapacity,
5899
+ "nodeSolver",
5900
+ CapacityMeshNodeSolver2_NodeUnderObstacle,
5340
5901
  (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
- }
5902
+ cms.netToPointPairsSolver?.getNewSimpleRouteJson() || cms.srj,
5903
+ cms.opts
5350
5904
  ]
5351
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
+ ]),
5352
5947
  definePipelineStep(
5353
5948
  "edgeToPortSegmentSolver",
5354
5949
  CapacityEdgeToPortSegmentSolver,
5355
5950
  (cms) => [
5356
5951
  {
5357
- nodes: cms.nodeTargetMerger?.newNodes || [],
5952
+ nodes: cms.capacityNodes,
5358
5953
  edges: cms.edgeSolver?.edges || [],
5359
5954
  capacityPaths: cms.pathingSolver?.getCapacityPaths() || [],
5360
5955
  colorMap: cms.colorMap
@@ -5375,7 +5970,7 @@ var CapacityMeshSolver = class extends BaseSolver {
5375
5970
  {
5376
5971
  segments: allSegments,
5377
5972
  colorMap: cms.colorMap,
5378
- nodes: cms.nodeTargetMerger?.newNodes || []
5973
+ nodes: cms.capacityNodes
5379
5974
  }
5380
5975
  ];
5381
5976
  }
@@ -5398,7 +5993,7 @@ var CapacityMeshSolver = class extends BaseSolver {
5398
5993
  {
5399
5994
  assignedSegments: cms.segmentToPointSolver?.solvedSegments || [],
5400
5995
  colorMap: cms.colorMap,
5401
- nodes: cms.nodeTargetMerger?.newNodes || []
5996
+ nodes: cms.capacityNodes
5402
5997
  }
5403
5998
  ]
5404
5999
  ),
@@ -5428,26 +6023,24 @@ var CapacityMeshSolver = class extends BaseSolver {
5428
6023
  this.solved = true;
5429
6024
  return;
5430
6025
  }
5431
- if (this.activeSolver) {
5432
- this.activeSolver.step();
5433
- if (this.activeSolver.solved) {
6026
+ if (this.activeSubSolver) {
6027
+ this.activeSubSolver.step();
6028
+ if (this.activeSubSolver.solved) {
5434
6029
  this.endTimeOfPhase[pipelineStepDef.solverName] = performance.now();
5435
6030
  this.timeSpentOnPhase[pipelineStepDef.solverName] = this.endTimeOfPhase[pipelineStepDef.solverName] - this.startTimeOfPhase[pipelineStepDef.solverName];
5436
6031
  pipelineStepDef.onSolved?.(this);
5437
- this.activeSolver = null;
6032
+ this.activeSubSolver = null;
5438
6033
  this.currentPipelineStepIndex++;
5439
- } else if (this.activeSolver.failed) {
5440
- this.error = this.activeSolver?.error;
6034
+ } else if (this.activeSubSolver.failed) {
6035
+ this.error = this.activeSubSolver?.error;
5441
6036
  this.failed = true;
5442
- this.activeSolver = null;
6037
+ this.activeSubSolver = null;
5443
6038
  }
5444
6039
  return;
5445
6040
  }
5446
6041
  const constructorParams = pipelineStepDef.getConstructorParams(this);
5447
- this.activeSolver = new pipelineStepDef.solverClass(
5448
- ...constructorParams
5449
- );
5450
- this[pipelineStepDef.solverName] = this.activeSolver;
6042
+ this.activeSubSolver = new pipelineStepDef.solverClass(...constructorParams);
6043
+ this[pipelineStepDef.solverName] = this.activeSubSolver;
5451
6044
  this.timeSpentOnPhase[pipelineStepDef.solverName] = 0;
5452
6045
  this.startTimeOfPhase[pipelineStepDef.solverName] = performance.now();
5453
6046
  }
@@ -5455,9 +6048,13 @@ var CapacityMeshSolver = class extends BaseSolver {
5455
6048
  return this.pipelineDef[this.currentPipelineStepIndex]?.solverName ?? "none";
5456
6049
  }
5457
6050
  visualize() {
5458
- if (!this.solved && this.activeSolver) return this.activeSolver.visualize();
6051
+ if (!this.solved && this.activeSubSolver)
6052
+ return this.activeSubSolver.visualize();
5459
6053
  const netToPPSolver = this.netToPointPairsSolver?.visualize();
5460
6054
  const nodeViz = this.nodeSolver?.visualize();
6055
+ const nodeTargetMergerViz = this.nodeTargetMerger?.visualize();
6056
+ const singleLayerNodeMergerViz = this.singleLayerNodeMerger?.visualize();
6057
+ const strawSolverViz = this.strawSolver?.visualize();
5461
6058
  const edgeViz = this.edgeSolver?.visualize();
5462
6059
  const pathingViz = this.pathingSolver?.visualize();
5463
6060
  const edgeToPortSegmentViz = this.edgeToPortSegmentSolver?.visualize();
@@ -5470,14 +6067,37 @@ var CapacityMeshSolver = class extends BaseSolver {
5470
6067
  rects: [
5471
6068
  ...(this.srj.obstacles ?? []).map((o) => ({
5472
6069
  ...o,
5473
- 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)"
5474
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
+ }
5475
6092
  ]
5476
6093
  };
5477
6094
  const visualizations = [
5478
6095
  problemViz,
5479
6096
  netToPPSolver,
5480
6097
  nodeViz,
6098
+ nodeTargetMergerViz,
6099
+ singleLayerNodeMergerViz,
6100
+ strawSolverViz,
5481
6101
  edgeViz,
5482
6102
  pathingViz,
5483
6103
  edgeToPortSegmentViz,