@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.d.ts +111 -30
- package/dist/index.js +1321 -701
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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: [
|
|
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
|
-
//
|
|
2888
|
-
var
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
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
|
-
|
|
2936
|
-
const
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
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
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
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
|
-
|
|
2979
|
-
return this.
|
|
3088
|
+
getIdsConnectedToNet(netId) {
|
|
3089
|
+
return this.netMap[netId] || [];
|
|
2980
3090
|
}
|
|
2981
|
-
|
|
2982
|
-
|
|
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
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
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
|
-
|
|
2996
|
-
const
|
|
2997
|
-
for (const
|
|
2998
|
-
const
|
|
2999
|
-
if (
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
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
|
|
3113
|
+
return true;
|
|
3008
3114
|
}
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
}
|
|
3014
|
-
|
|
3015
|
-
const
|
|
3016
|
-
|
|
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/
|
|
3201
|
-
var
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
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
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
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
|
-
|
|
3247
|
-
|
|
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
|
-
|
|
3252
|
-
|
|
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
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
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
|
-
|
|
5135
|
-
title: "Unravel Multi Section Solver"
|
|
5553
|
+
title: "Straw Solver"
|
|
5136
5554
|
};
|
|
5137
|
-
for (const
|
|
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
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
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
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
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
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
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
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
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
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
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
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
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
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
if (!
|
|
5224
|
-
|
|
5225
|
-
|
|
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
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
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
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5327
|
-
|
|
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
|
-
"
|
|
5339
|
-
|
|
5899
|
+
"nodeSolver",
|
|
5900
|
+
CapacityMeshNodeSolver2_NodeUnderObstacle,
|
|
5340
5901
|
(cms) => [
|
|
5341
|
-
|
|
5342
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
5432
|
-
this.
|
|
5433
|
-
if (this.
|
|
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.
|
|
6032
|
+
this.activeSubSolver = null;
|
|
5438
6033
|
this.currentPipelineStepIndex++;
|
|
5439
|
-
} else if (this.
|
|
5440
|
-
this.error = this.
|
|
6034
|
+
} else if (this.activeSubSolver.failed) {
|
|
6035
|
+
this.error = this.activeSubSolver?.error;
|
|
5441
6036
|
this.failed = true;
|
|
5442
|
-
this.
|
|
6037
|
+
this.activeSubSolver = null;
|
|
5443
6038
|
}
|
|
5444
6039
|
return;
|
|
5445
6040
|
}
|
|
5446
6041
|
const constructorParams = pipelineStepDef.getConstructorParams(this);
|
|
5447
|
-
this.
|
|
5448
|
-
|
|
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.
|
|
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,
|