@tscircuit/capacity-autorouter 0.0.29 → 0.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -85,177 +85,6 @@ var BaseSolver = class {
85
85
  }
86
86
  };
87
87
 
88
- // node_modules/@tscircuit/math-utils/dist/chunk-CHQOCSFB.js
89
- function doSegmentsIntersect(p1, q1, p2, q2) {
90
- const o1 = orientation(p1, q1, p2);
91
- const o2 = orientation(p1, q1, q2);
92
- const o3 = orientation(p2, q2, p1);
93
- const o4 = orientation(p2, q2, q1);
94
- if (o1 !== o2 && o3 !== o4) {
95
- return true;
96
- }
97
- if (o1 === 0 && onSegment(p1, p2, q1)) return true;
98
- if (o2 === 0 && onSegment(p1, q2, q1)) return true;
99
- if (o3 === 0 && onSegment(p2, p1, q2)) return true;
100
- if (o4 === 0 && onSegment(p2, q1, q2)) return true;
101
- return false;
102
- }
103
- function orientation(p, q, r) {
104
- const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
105
- if (val === 0) return 0;
106
- return val > 0 ? 1 : 2;
107
- }
108
- function onSegment(p, q, r) {
109
- return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y);
110
- }
111
- function pointToSegmentDistance(p, v, w) {
112
- const l2 = (w.x - v.x) ** 2 + (w.y - v.y) ** 2;
113
- if (l2 === 0) return distance(p, v);
114
- let t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
115
- t = Math.max(0, Math.min(1, t));
116
- const projection = {
117
- x: v.x + t * (w.x - v.x),
118
- y: v.y + t * (w.y - v.y)
119
- };
120
- return distance(p, projection);
121
- }
122
- function distance(p1, p2) {
123
- const dx = p1.x - p2.x;
124
- const dy = p1.y - p2.y;
125
- return Math.sqrt(dx * dx + dy * dy);
126
- }
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
-
144
- // lib/solvers/CapacityMeshSolver/CapacityMeshEdgeSolver.ts
145
- var CapacityMeshEdgeSolver = class extends BaseSolver {
146
- constructor(nodes) {
147
- super();
148
- this.nodes = nodes;
149
- this.edges = [];
150
- }
151
- edges;
152
- getNextCapacityMeshEdgeId() {
153
- return `ce${this.edges.length}`;
154
- }
155
- step() {
156
- this.edges = [];
157
- for (let i = 0; i < this.nodes.length; i++) {
158
- for (let j = i + 1; j < this.nodes.length; j++) {
159
- const strawNodesWithSameParent = this.nodes[i]._strawNode && this.nodes[j]._strawNode && this.nodes[i]._strawParentCapacityMeshNodeId === this.nodes[j]._strawParentCapacityMeshNodeId;
160
- if (!strawNodesWithSameParent && areNodesBordering(this.nodes[i], this.nodes[j]) && this.doNodesHaveSharedLayer(this.nodes[i], this.nodes[j])) {
161
- this.edges.push({
162
- capacityMeshEdgeId: this.getNextCapacityMeshEdgeId(),
163
- nodeIds: [
164
- this.nodes[i].capacityMeshNodeId,
165
- this.nodes[j].capacityMeshNodeId
166
- ]
167
- });
168
- }
169
- }
170
- }
171
- const targetNodes = this.nodes.filter((node) => node._containsTarget);
172
- for (const targetNode of targetNodes) {
173
- const hasEdge = this.edges.some(
174
- (edge) => edge.nodeIds.includes(targetNode.capacityMeshNodeId)
175
- );
176
- if (hasEdge) continue;
177
- let nearestNode = null;
178
- let nearestDistance = Infinity;
179
- for (const node of this.nodes) {
180
- if (node._containsObstacle) continue;
181
- if (node._containsTarget) continue;
182
- const dist = distance(targetNode.center, node.center);
183
- if (dist < nearestDistance) {
184
- nearestDistance = dist;
185
- nearestNode = node;
186
- }
187
- }
188
- if (nearestNode) {
189
- this.edges.push({
190
- capacityMeshEdgeId: this.getNextCapacityMeshEdgeId(),
191
- nodeIds: [
192
- targetNode.capacityMeshNodeId,
193
- nearestNode.capacityMeshNodeId
194
- ]
195
- });
196
- }
197
- }
198
- this.solved = true;
199
- }
200
- doNodesHaveSharedLayer(node1, node2) {
201
- return node1.availableZ.some((z) => node2.availableZ.includes(z));
202
- }
203
- visualize() {
204
- const graphics = {
205
- lines: [],
206
- points: [],
207
- rects: this.nodes.map((node) => {
208
- const lowestZ = Math.min(...node.availableZ);
209
- return {
210
- width: Math.max(node.width - 2, node.width * 0.8),
211
- height: Math.max(node.height - 2, node.height * 0.8),
212
- center: {
213
- x: node.center.x + lowestZ * node.width * 0.05,
214
- y: node.center.y - lowestZ * node.width * 0.05
215
- },
216
- fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : {
217
- "0,1": "rgba(0,0,0,0.1)",
218
- "0": "rgba(0,200,200, 0.1)",
219
- "1": "rgba(0,0,200, 0.1)"
220
- }[node.availableZ.join(",")] ?? "rgba(0,200,200,0.1)",
221
- label: [
222
- node.capacityMeshNodeId,
223
- `availableZ: ${node.availableZ.join(",")}`,
224
- `target? ${node._containsTarget ?? false}`,
225
- `obs? ${node._containsObstacle ?? false}`
226
- ].join("\n")
227
- };
228
- }),
229
- circles: []
230
- };
231
- for (const edge of this.edges) {
232
- const node1 = this.nodes.find(
233
- (node) => node.capacityMeshNodeId === edge.nodeIds[0]
234
- );
235
- const node2 = this.nodes.find(
236
- (node) => node.capacityMeshNodeId === edge.nodeIds[1]
237
- );
238
- if (node1?.center && node2?.center) {
239
- const lowestZ1 = Math.min(...node1.availableZ);
240
- const lowestZ2 = Math.min(...node2.availableZ);
241
- const nodeCenter1Adj = {
242
- x: node1.center.x + lowestZ1 * node1.width * 0.05,
243
- y: node1.center.y - lowestZ1 * node1.width * 0.05
244
- };
245
- const nodeCenter2Adj = {
246
- x: node2.center.x + lowestZ2 * node2.width * 0.05,
247
- y: node2.center.y - lowestZ2 * node2.width * 0.05
248
- };
249
- graphics.lines.push({
250
- points: [nodeCenter1Adj, nodeCenter2Adj],
251
- strokeDash: node1.availableZ.join(",") === node2.availableZ.join(",") ? void 0 : "10 5"
252
- });
253
- }
254
- }
255
- return graphics;
256
- }
257
- };
258
-
259
88
  // node_modules/@babel/runtime/helpers/esm/extends.js
260
89
  function _extends() {
261
90
  return _extends = Object.assign ? Object.assign.bind() : function(n) {
@@ -1089,24 +918,6 @@ var safeTransparentize = (color, amount) => {
1089
918
  }
1090
919
  };
1091
920
 
1092
- // lib/utils/isPointInRect.ts
1093
- function isPointInRect(point, rect) {
1094
- return point.x >= rect.center.x - rect.width / 2 && point.x <= rect.center.x + rect.width / 2 && point.y >= rect.center.y - rect.height / 2 && point.y <= rect.center.y + rect.height / 2;
1095
- }
1096
-
1097
- // lib/utils/doRectsOverlap.ts
1098
- function doRectsOverlap(rect1, rect2) {
1099
- const rect1Left = rect1.center.x - rect1.width / 2;
1100
- const rect1Right = rect1.center.x + rect1.width / 2;
1101
- const rect1Top = rect1.center.y - rect1.height / 2;
1102
- const rect1Bottom = rect1.center.y + rect1.height / 2;
1103
- const rect2Left = rect2.center.x - rect2.width / 2;
1104
- const rect2Right = rect2.center.x + rect2.width / 2;
1105
- const rect2Top = rect2.center.y - rect2.height / 2;
1106
- const rect2Bottom = rect2.center.y + rect2.height / 2;
1107
- return rect1Left <= rect2Right && rect1Right >= rect2Left && rect1Top <= rect2Bottom && rect1Bottom >= rect2Top;
1108
- }
1109
-
1110
921
  // lib/utils/mapLayerNameToZ.ts
1111
922
  var mapLayerNameToZ = (layerName, layerCount) => {
1112
923
  if (layerName === "top") return 0;
@@ -1137,6 +948,108 @@ var calculateOptimalCapacityDepth = (initialWidth, targetMinCapacity = 0.5, maxD
1137
948
  return Math.max(1, depth);
1138
949
  };
1139
950
 
951
+ // lib/data-structures/ObstacleTree.ts
952
+ var ObstacleTree = class {
953
+ constructor(obstacles) {
954
+ this.obstacles = obstacles;
955
+ this.buckets = /* @__PURE__ */ new Map();
956
+ for (let i = 0; i < obstacles.length; i++) {
957
+ const obstacle = obstacles[i];
958
+ const nodeMinX = obstacle.center.x - obstacle.width / 2;
959
+ const nodeMinY = obstacle.center.y - obstacle.height / 2;
960
+ const nodeMaxX = obstacle.center.x + obstacle.width / 2;
961
+ const nodeMaxY = obstacle.center.y + obstacle.height / 2;
962
+ for (let x = nodeMinX; x <= nodeMaxX; x += this.CELL_SIZE) {
963
+ for (let y = nodeMinY; y <= nodeMaxY; y += this.CELL_SIZE) {
964
+ const bucketKey = this.getBucketKey(x, y);
965
+ const bucket = this.buckets.get(bucketKey);
966
+ if (!bucket) {
967
+ this.buckets.set(bucketKey, [[obstacle, i]]);
968
+ } else {
969
+ bucket.push([obstacle, i]);
970
+ }
971
+ }
972
+ }
973
+ }
974
+ }
975
+ buckets;
976
+ CELL_SIZE = 0.4;
977
+ getBucketKey(x, y) {
978
+ return `${Math.floor(x / this.CELL_SIZE)}x${Math.floor(y / this.CELL_SIZE)}`;
979
+ }
980
+ getNodesInArea(centerX, centerY, width, height) {
981
+ const obstacles = [];
982
+ const alreadyAddedObstacles = /* @__PURE__ */ new Set();
983
+ const minX = centerX - width / 2;
984
+ const minY = centerY - height / 2;
985
+ const maxX = centerX + width / 2;
986
+ const maxY = centerY + height / 2;
987
+ for (let x = minX; x <= maxX; x += this.CELL_SIZE) {
988
+ for (let y = minY; y <= maxY; y += this.CELL_SIZE) {
989
+ const bucketKey = this.getBucketKey(x, y);
990
+ const bucket = this.buckets.get(bucketKey) || [];
991
+ for (const obstacleWithIndex of bucket) {
992
+ if (alreadyAddedObstacles.has(obstacleWithIndex[1])) continue;
993
+ alreadyAddedObstacles.add(obstacleWithIndex[1]);
994
+ obstacles.push(obstacleWithIndex[0]);
995
+ }
996
+ }
997
+ }
998
+ return obstacles;
999
+ }
1000
+ };
1001
+
1002
+ // lib/data-structures/TargetTree.ts
1003
+ var TargetTree = class {
1004
+ constructor(targets) {
1005
+ this.targets = targets;
1006
+ this.buckets = /* @__PURE__ */ new Map();
1007
+ for (let i = 0; i < targets.length; i++) {
1008
+ const target = targets[i];
1009
+ const targetBucketMinX = Math.floor(target.bounds.minX / this.CELL_SIZE) * this.CELL_SIZE;
1010
+ const targetBucketMinY = Math.floor(target.bounds.minY / this.CELL_SIZE) * this.CELL_SIZE;
1011
+ const targetMaxX = target.bounds.maxX;
1012
+ const targetMaxY = target.bounds.maxY;
1013
+ for (let x = targetBucketMinX; x <= targetMaxX; x += this.CELL_SIZE) {
1014
+ for (let y = targetBucketMinY; y <= targetMaxY; y += this.CELL_SIZE) {
1015
+ const bucketKey = this.getBucketKey(x, y);
1016
+ const bucket = this.buckets.get(bucketKey);
1017
+ if (!bucket) {
1018
+ this.buckets.set(bucketKey, [[target, i]]);
1019
+ } else {
1020
+ bucket.push([target, i]);
1021
+ }
1022
+ }
1023
+ }
1024
+ }
1025
+ }
1026
+ buckets;
1027
+ CELL_SIZE = 5;
1028
+ getBucketKey(x, y) {
1029
+ return `${Math.floor(x / this.CELL_SIZE)}x${Math.floor(y / this.CELL_SIZE)}`;
1030
+ }
1031
+ getTargetsInArea(centerX, centerY, width, height) {
1032
+ const targets = [];
1033
+ const alreadyAddedTargets = /* @__PURE__ */ new Set();
1034
+ const minX = Math.floor((centerX - width / 2) / this.CELL_SIZE) * this.CELL_SIZE;
1035
+ const minY = Math.floor((centerY - height / 2) / this.CELL_SIZE) * this.CELL_SIZE;
1036
+ const maxX = centerX + width / 2;
1037
+ const maxY = centerY + height / 2;
1038
+ for (let x = minX; x <= maxX; x += this.CELL_SIZE) {
1039
+ for (let y = minY; y <= maxY; y += this.CELL_SIZE) {
1040
+ const bucketKey = this.getBucketKey(x, y);
1041
+ const bucket = this.buckets.get(bucketKey) || [];
1042
+ for (const targetWithIndex of bucket) {
1043
+ if (alreadyAddedTargets.has(targetWithIndex[1])) continue;
1044
+ alreadyAddedTargets.add(targetWithIndex[1]);
1045
+ targets.push(targetWithIndex[0]);
1046
+ }
1047
+ }
1048
+ }
1049
+ return targets;
1050
+ }
1051
+ };
1052
+
1140
1053
  // lib/solvers/CapacityMeshSolver/CapacityMeshNodeSolver1.ts
1141
1054
  var CapacityMeshNodeSolver = class extends BaseSolver {
1142
1055
  constructor(srj, opts = {}) {
@@ -1180,13 +1093,9 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1180
1093
  ];
1181
1094
  this.finishedNodes = [];
1182
1095
  this.nodeToXYOverlappingObstaclesMap = /* @__PURE__ */ new Map();
1183
- this.targets = this.srj.connections.flatMap(
1184
- (c) => c.pointsToConnect.map((p) => ({
1185
- ...p,
1186
- connectionName: c.name,
1187
- availableZ: p.layer === "top" ? [0] : [1]
1188
- }))
1189
- );
1096
+ this.obstacleTree = new ObstacleTree(this.srj.obstacles);
1097
+ this.targets = this.computeTargets();
1098
+ this.targetTree = new TargetTree(this.targets);
1190
1099
  }
1191
1100
  unfinishedNodes;
1192
1101
  finishedNodes;
@@ -1195,6 +1104,40 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1195
1104
  // targetObstacleMap: Record<string, { obstacle: Obstacle, node: CapacityMeshNode }>
1196
1105
  MAX_DEPTH = 4;
1197
1106
  targets;
1107
+ targetTree;
1108
+ obstacleTree;
1109
+ computeTargets() {
1110
+ const targets = [];
1111
+ for (const conn of this.srj.connections) {
1112
+ for (const ptc of conn.pointsToConnect) {
1113
+ const obstacles = this.obstacleTree.getNodesInArea(ptc.x, ptc.y, 0.01, 0.01).filter(
1114
+ (o) => o.zLayers.some((z) => ptc.layer === "top" ? z === 0 : z === 1)
1115
+ );
1116
+ let bounds = {
1117
+ minX: ptc.x - 5e-3,
1118
+ minY: ptc.y - 5e-3,
1119
+ maxX: ptc.x + 5e-3,
1120
+ maxY: ptc.y + 5e-3
1121
+ };
1122
+ if (obstacles.length > 0) {
1123
+ bounds = {
1124
+ minX: Math.min(...obstacles.map((o) => o.center.x - o.width / 2)),
1125
+ minY: Math.min(...obstacles.map((o) => o.center.y - o.height / 2)),
1126
+ maxX: Math.max(...obstacles.map((o) => o.center.x + o.width / 2)),
1127
+ maxY: Math.max(...obstacles.map((o) => o.center.y + o.height / 2))
1128
+ };
1129
+ }
1130
+ const target = {
1131
+ ...ptc,
1132
+ connectionName: conn.name,
1133
+ availableZ: ptc.layer === "top" ? [0] : [1],
1134
+ bounds
1135
+ };
1136
+ targets.push(target);
1137
+ }
1138
+ }
1139
+ return targets;
1140
+ }
1198
1141
  _nextNodeCounter = 0;
1199
1142
  getNextNodeId() {
1200
1143
  return `cn${this._nextNodeCounter++}`;
@@ -1203,18 +1146,17 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1203
1146
  return (this.MAX_DEPTH - depth + 1) ** 2;
1204
1147
  }
1205
1148
  getTargetIfNodeContainsTarget(node) {
1206
- const overlappingObstacles = this.getXYOverlappingObstacles(node);
1207
- for (const target of this.targets) {
1208
- if (!target.availableZ.some((z) => node.availableZ.includes(z))) continue;
1209
- const targetObstacle = overlappingObstacles.find(
1210
- (o) => isPointInRect(target, o)
1211
- );
1212
- if (targetObstacle) {
1213
- if (doRectsOverlap(node, targetObstacle)) {
1214
- return target;
1215
- }
1216
- }
1217
- if (target.x >= node.center.x - node.width / 2 && target.x <= node.center.x + node.width / 2 && target.y >= node.center.y - node.height / 2 && target.y <= node.center.y + node.height / 2) {
1149
+ const nearbyTargets = node.width > this.targetTree.CELL_SIZE * 4 ? this.targets : this.targetTree.getTargetsInArea(
1150
+ node.center.x,
1151
+ node.center.y,
1152
+ node.width,
1153
+ node.height
1154
+ );
1155
+ for (const target of nearbyTargets) {
1156
+ if (
1157
+ // Check if the node and target bounds overlap
1158
+ target.bounds.minX <= node.center.x + node.width / 2 && target.bounds.maxX >= node.center.x - node.width / 2 && target.bounds.minY <= node.center.y + node.height / 2 && target.bounds.maxY >= node.center.y - node.height / 2 && target.availableZ.some((z) => node.availableZ.includes(z))
1159
+ ) {
1218
1160
  return target;
1219
1161
  }
1220
1162
  }
@@ -1942,6 +1884,46 @@ var CapacitySegmentToPointSolver = class extends BaseSolver {
1942
1884
  }
1943
1885
  };
1944
1886
 
1887
+ // node_modules/@tscircuit/math-utils/dist/chunk-CHQOCSFB.js
1888
+ function doSegmentsIntersect(p1, q1, p2, q2) {
1889
+ const o1 = orientation(p1, q1, p2);
1890
+ const o2 = orientation(p1, q1, q2);
1891
+ const o3 = orientation(p2, q2, p1);
1892
+ const o4 = orientation(p2, q2, q1);
1893
+ if (o1 !== o2 && o3 !== o4) {
1894
+ return true;
1895
+ }
1896
+ if (o1 === 0 && onSegment(p1, p2, q1)) return true;
1897
+ if (o2 === 0 && onSegment(p1, q2, q1)) return true;
1898
+ if (o3 === 0 && onSegment(p2, p1, q2)) return true;
1899
+ if (o4 === 0 && onSegment(p2, q1, q2)) return true;
1900
+ return false;
1901
+ }
1902
+ function orientation(p, q, r) {
1903
+ const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
1904
+ if (val === 0) return 0;
1905
+ return val > 0 ? 1 : 2;
1906
+ }
1907
+ function onSegment(p, q, r) {
1908
+ return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y);
1909
+ }
1910
+ function pointToSegmentDistance(p, v, w) {
1911
+ const l2 = (w.x - v.x) ** 2 + (w.y - v.y) ** 2;
1912
+ if (l2 === 0) return distance(p, v);
1913
+ let t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
1914
+ t = Math.max(0, Math.min(1, t));
1915
+ const projection = {
1916
+ x: v.x + t * (w.x - v.x),
1917
+ y: v.y + t * (w.y - v.y)
1918
+ };
1919
+ return distance(p, projection);
1920
+ }
1921
+ function distance(p1, p2) {
1922
+ const dx = p1.x - p2.x;
1923
+ const dy = p1.y - p2.y;
1924
+ return Math.sqrt(dx * dx + dy * dy);
1925
+ }
1926
+
1945
1927
  // lib/solvers/HighDensitySolver/SingleHighDensityRouteSolver.ts
1946
1928
  var SingleHighDensityRouteSolver = class extends BaseSolver {
1947
1929
  obstacleRoutes;
@@ -3685,6 +3667,7 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
3685
3667
  z: mapLayerNameToZ(c.pointsToConnect[1].layer, opts.layerCount)
3686
3668
  }
3687
3669
  }));
3670
+ this.MAX_ITERATIONS = 1e5;
3688
3671
  }
3689
3672
  _step() {
3690
3673
  if (this.activeSolver) {
@@ -3921,20 +3904,15 @@ var hasZRangeOverlap = (A_z1, A_z2, B_z1, B_z2) => {
3921
3904
  // lib/solvers/UnravelSolver/getIssuesInSection.ts
3922
3905
  var getIssuesInSection = (section, nodeMap, pointModifications, connMap) => {
3923
3906
  const issues = [];
3924
- const points = /* @__PURE__ */ new Map();
3925
- for (const nodeId of section.allNodeIds) {
3926
- for (const segmentPointId of section.segmentPointsInNode.get(nodeId)) {
3927
- if (!points.has(segmentPointId)) {
3928
- const ogPoint = section.segmentPointMap.get(segmentPointId);
3929
- const modPoint = pointModifications.get(segmentPointId);
3930
- points.set(segmentPointId, {
3931
- x: modPoint?.x ?? ogPoint.x,
3932
- y: modPoint?.y ?? ogPoint.y,
3933
- z: modPoint?.z ?? ogPoint.z
3934
- });
3935
- }
3936
- }
3937
- }
3907
+ const points = new Map(section.originalPointMap);
3908
+ for (const [segmentPointId, modPoint] of pointModifications.entries()) {
3909
+ const ogPoint = points.get(segmentPointId);
3910
+ points.set(segmentPointId, {
3911
+ x: modPoint.x ?? ogPoint.x,
3912
+ y: modPoint.y ?? ogPoint.y,
3913
+ z: modPoint.z ?? ogPoint.z
3914
+ });
3915
+ }
3938
3916
  for (const nodeId of section.allNodeIds) {
3939
3917
  const node = nodeMap.get(nodeId);
3940
3918
  if (!node) continue;
@@ -4058,11 +4036,14 @@ var applyOperationToPointModifications = (pointModifications, operation, getPoin
4058
4036
 
4059
4037
  // lib/solvers/UnravelSolver/createSegmentPointMap.ts
4060
4038
  var createSegmentPointMap = (dedupedSegments, segmentIdToNodeIds) => {
4039
+ const segmentPointMap = /* @__PURE__ */ new Map();
4040
+ const nodeToSegmentPointMap = /* @__PURE__ */ new Map();
4041
+ const segmentToSegmentPointMap = /* @__PURE__ */ new Map();
4061
4042
  const segmentPoints = [];
4062
4043
  let highestSegmentPointId = 0;
4063
4044
  for (const segment of dedupedSegments) {
4064
4045
  for (const point of segment.assignedPoints) {
4065
- segmentPoints.push({
4046
+ const sp = {
4066
4047
  segmentPointId: `SP${highestSegmentPointId++}`,
4067
4048
  segmentId: segment.nodePortSegmentId,
4068
4049
  capacityMeshNodeIds: segmentIdToNodeIds.get(
@@ -4073,14 +4054,26 @@ var createSegmentPointMap = (dedupedSegments, segmentIdToNodeIds) => {
4073
4054
  y: point.point.y,
4074
4055
  z: point.point.z,
4075
4056
  directlyConnectedSegmentPointIds: []
4076
- });
4057
+ };
4058
+ segmentPointMap.set(sp.segmentPointId, sp);
4059
+ for (const nodeId of sp.capacityMeshNodeIds) {
4060
+ nodeToSegmentPointMap.set(nodeId, [
4061
+ ...nodeToSegmentPointMap.get(nodeId) ?? [],
4062
+ sp.segmentPointId
4063
+ ]);
4064
+ }
4065
+ segmentToSegmentPointMap.set(segment.nodePortSegmentId, [
4066
+ ...segmentToSegmentPointMap.get(segment.nodePortSegmentId) ?? [],
4067
+ sp.segmentPointId
4068
+ ]);
4069
+ segmentPoints.push(sp);
4077
4070
  }
4078
4071
  }
4079
- const segmentPointMap = /* @__PURE__ */ new Map();
4080
- for (const segmentPoint of segmentPoints) {
4081
- segmentPointMap.set(segmentPoint.segmentPointId, segmentPoint);
4082
- }
4083
- return segmentPointMap;
4072
+ return {
4073
+ segmentPointMap,
4074
+ nodeToSegmentPointMap,
4075
+ segmentToSegmentPointMap
4076
+ };
4084
4077
  };
4085
4078
 
4086
4079
  // lib/solvers/UnravelSolver/UnravelSectionSolver.ts
@@ -4119,7 +4112,11 @@ var UnravelSectionSolver = class extends BaseSolver {
4119
4112
  this.segmentIdToNodeIds = params.segmentIdToNodeIds;
4120
4113
  this.rootNodeId = params.rootNodeId;
4121
4114
  this.colorMap = params.colorMap ?? {};
4122
- this.unravelSection = this.createUnravelSection(params.segmentPointMap);
4115
+ this.unravelSection = this.createUnravelSection({
4116
+ segmentPointMap: params.segmentPointMap,
4117
+ nodeToSegmentPointMap: params.nodeToSegmentPointMap,
4118
+ segmentToSegmentPointMap: params.segmentToSegmentPointMap
4119
+ });
4123
4120
  this.tunedNodeCapacityMap = /* @__PURE__ */ new Map();
4124
4121
  for (const nodeId of this.unravelSection.allNodeIds) {
4125
4122
  this.tunedNodeCapacityMap.set(
@@ -4130,38 +4127,43 @@ var UnravelSectionSolver = class extends BaseSolver {
4130
4127
  this.originalCandidate = this.createInitialCandidate();
4131
4128
  this.candidates = [this.originalCandidate];
4132
4129
  }
4133
- createUnravelSection(segmentPointMap) {
4130
+ createUnravelSection(largeSpMaps) {
4134
4131
  const mutableNodeIds = getNodesNearNode({
4135
4132
  nodeId: this.rootNodeId,
4136
4133
  nodeIdToSegmentIds: this.nodeIdToSegmentIds,
4137
4134
  segmentIdToNodeIds: this.segmentIdToNodeIds,
4138
4135
  hops: this.MUTABLE_HOPS
4139
4136
  });
4140
- const allNodeIds = getNodesNearNode({
4137
+ const allSectionNodeIds = getNodesNearNode({
4141
4138
  nodeId: this.rootNodeId,
4142
4139
  nodeIdToSegmentIds: this.nodeIdToSegmentIds,
4143
4140
  segmentIdToNodeIds: this.segmentIdToNodeIds,
4144
4141
  hops: this.MUTABLE_HOPS + 1
4145
4142
  });
4146
4143
  const immutableNodeIds = Array.from(
4147
- new Set(allNodeIds).difference(new Set(mutableNodeIds))
4144
+ new Set(allSectionNodeIds).difference(new Set(mutableNodeIds))
4148
4145
  );
4149
- if (!segmentPointMap) {
4150
- segmentPointMap = createSegmentPointMap(
4146
+ if (!largeSpMaps?.segmentPointMap) {
4147
+ largeSpMaps = createSegmentPointMap(
4151
4148
  this.dedupedSegments,
4152
4149
  this.segmentIdToNodeIds
4153
4150
  );
4154
4151
  }
4155
- const segmentPoints = Array.from(segmentPointMap.values());
4156
4152
  const segmentPointsInNode = /* @__PURE__ */ new Map();
4157
- for (const segmentPoint of segmentPoints) {
4158
- for (const nodeId of segmentPoint.capacityMeshNodeIds) {
4159
- segmentPointsInNode.set(nodeId, [
4160
- ...segmentPointsInNode.get(nodeId) ?? [],
4161
- segmentPoint.segmentPointId
4162
- ]);
4153
+ for (const nodeId of allSectionNodeIds) {
4154
+ segmentPointsInNode.set(
4155
+ nodeId,
4156
+ largeSpMaps.nodeToSegmentPointMap.get(nodeId)
4157
+ );
4158
+ }
4159
+ const sectionPointMap = /* @__PURE__ */ new Map();
4160
+ for (const nodeId of allSectionNodeIds) {
4161
+ for (const segmentPointId of segmentPointsInNode.get(nodeId)) {
4162
+ const point = largeSpMaps.segmentPointMap.get(segmentPointId);
4163
+ sectionPointMap.set(segmentPointId, point);
4163
4164
  }
4164
4165
  }
4166
+ const segmentPoints = Array.from(sectionPointMap.values());
4165
4167
  const segmentPointsInSegment = /* @__PURE__ */ new Map();
4166
4168
  for (const segmentPoint of segmentPoints) {
4167
4169
  segmentPointsInSegment.set(segmentPoint.segmentId, [
@@ -4169,32 +4171,31 @@ var UnravelSectionSolver = class extends BaseSolver {
4169
4171
  segmentPoint.segmentPointId
4170
4172
  ]);
4171
4173
  }
4172
- for (let i = 0; i < segmentPoints.length; i++) {
4173
- const A = segmentPoints[i];
4174
- for (let j = i + 1; j < segmentPoints.length; j++) {
4175
- const B = segmentPoints[j];
4176
- if (B.segmentPointId === A.segmentPointId) continue;
4177
- if (B.segmentId === A.segmentId) continue;
4178
- if (B.connectionName !== A.connectionName) continue;
4179
- if (A.capacityMeshNodeIds.some(
4180
- (nId) => B.capacityMeshNodeIds.includes(nId)
4181
- )) {
4174
+ for (const [nodeId, segmentPoints2] of segmentPointsInNode.entries()) {
4175
+ for (let i = 0; i < segmentPoints2.length; i++) {
4176
+ const A = largeSpMaps.segmentPointMap.get(segmentPoints2[i]);
4177
+ for (let j = i + 1; j < segmentPoints2.length; j++) {
4178
+ const B = largeSpMaps.segmentPointMap.get(segmentPoints2[j]);
4179
+ if (B.segmentPointId === A.segmentPointId) continue;
4180
+ if (B.segmentId === A.segmentId) continue;
4181
+ if (B.connectionName !== A.connectionName) continue;
4182
+ if (B.directlyConnectedSegmentPointIds.includes(A.segmentPointId))
4183
+ continue;
4182
4184
  A.directlyConnectedSegmentPointIds.push(B.segmentPointId);
4183
4185
  B.directlyConnectedSegmentPointIds.push(A.segmentPointId);
4184
4186
  }
4185
4187
  }
4186
4188
  }
4187
4189
  const segmentPairsInNode = /* @__PURE__ */ new Map();
4188
- for (const nodeId of allNodeIds) {
4190
+ for (const nodeId of allSectionNodeIds) {
4189
4191
  segmentPairsInNode.set(nodeId, []);
4190
4192
  }
4191
4193
  for (const A of segmentPoints) {
4192
4194
  for (const nodeId of A.capacityMeshNodeIds) {
4193
- const otherSegmentPoints = segmentPointsInNode.get(nodeId).map((spId) => segmentPointMap.get(spId));
4194
4195
  const segmentPairs = segmentPairsInNode.get(nodeId);
4195
4196
  if (!segmentPairs) continue;
4196
4197
  for (const BId of A.directlyConnectedSegmentPointIds) {
4197
- const B = segmentPointMap.get(BId);
4198
+ const B = largeSpMaps.segmentPointMap.get(BId);
4198
4199
  if (B.segmentPointId === A.segmentPointId) continue;
4199
4200
  if (!B.capacityMeshNodeIds.some((nId) => nId === nodeId)) continue;
4200
4201
  if (!segmentPairs.some(
@@ -4217,14 +4218,15 @@ var UnravelSectionSolver = class extends BaseSolver {
4217
4218
  }
4218
4219
  }
4219
4220
  return {
4220
- allNodeIds,
4221
+ allNodeIds: allSectionNodeIds,
4221
4222
  mutableNodeIds,
4222
4223
  immutableNodeIds,
4223
4224
  mutableSegmentIds,
4224
4225
  segmentPairsInNode,
4225
- segmentPointMap,
4226
+ segmentPointMap: sectionPointMap,
4226
4227
  segmentPointsInNode,
4227
- segmentPointsInSegment
4228
+ segmentPointsInSegment,
4229
+ originalPointMap: sectionPointMap
4228
4230
  };
4229
4231
  }
4230
4232
  createInitialCandidate() {
@@ -4407,13 +4409,17 @@ var UnravelSectionSolver = class extends BaseSolver {
4407
4409
  }
4408
4410
  return cost;
4409
4411
  }
4410
- getNeighborByApplyingOperation(currentCandidate, operation) {
4412
+ getUnexploredNeighborByApplyingOperation(currentCandidate, operation) {
4411
4413
  const pointModifications = new Map(currentCandidate.pointModifications);
4412
4414
  applyOperationToPointModifications(
4413
4415
  pointModifications,
4414
4416
  operation,
4415
4417
  (segmentPointId) => this.getPointInCandidate(currentCandidate, segmentPointId)
4416
4418
  );
4419
+ const candidateHash = createPointModificationsHash(pointModifications);
4420
+ if (this.queuedOrExploredCandidatePointModificationHashes.has(candidateHash)) {
4421
+ return null;
4422
+ }
4417
4423
  const issues = getIssuesInSection(
4418
4424
  this.unravelSection,
4419
4425
  this.nodeMap,
@@ -4432,7 +4438,7 @@ var UnravelSectionSolver = class extends BaseSolver {
4432
4438
  h: 0,
4433
4439
  f: g,
4434
4440
  pointModifications,
4435
- candidateHash: createPointModificationsHash(pointModifications),
4441
+ candidateHash,
4436
4442
  // TODO PERFORMANCE allow disabling this
4437
4443
  // candidateFullHash: createFullPointModificationsHash(
4438
4444
  // this.unravelSection.segmentPointMap,
@@ -4450,8 +4456,15 @@ var UnravelSectionSolver = class extends BaseSolver {
4450
4456
  const neighbors = [];
4451
4457
  const operations = this.getNeighborOperationsForCandidate(candidate);
4452
4458
  for (const operation of operations) {
4453
- const neighbor = this.getNeighborByApplyingOperation(candidate, operation);
4459
+ const neighbor = this.getUnexploredNeighborByApplyingOperation(
4460
+ candidate,
4461
+ operation
4462
+ );
4463
+ if (!neighbor) continue;
4454
4464
  neighbors.push(neighbor);
4465
+ this.queuedOrExploredCandidatePointModificationHashes.add(
4466
+ neighbor.candidateHash
4467
+ );
4455
4468
  }
4456
4469
  return neighbors;
4457
4470
  }
@@ -4745,6 +4758,8 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
4745
4758
  dedupedSegments;
4746
4759
  nodeIdToSegmentIds;
4747
4760
  segmentIdToNodeIds;
4761
+ nodeToSegmentPointMap;
4762
+ segmentToSegmentPointMap;
4748
4763
  colorMap;
4749
4764
  tunedNodeCapacityMap;
4750
4765
  MAX_NODE_ATTEMPTS = 2;
@@ -4791,10 +4806,10 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
4791
4806
  for (const [nodeId, node] of this.nodeMap) {
4792
4807
  this.tunedNodeCapacityMap.set(nodeId, getTunedTotalCapacity1(node));
4793
4808
  }
4794
- this.segmentPointMap = createSegmentPointMap(
4795
- this.dedupedSegments,
4796
- this.segmentIdToNodeIds
4797
- );
4809
+ const { segmentPointMap, nodeToSegmentPointMap, segmentToSegmentPointMap } = createSegmentPointMap(this.dedupedSegments, this.segmentIdToNodeIds);
4810
+ this.segmentPointMap = segmentPointMap;
4811
+ this.nodeToSegmentPointMap = nodeToSegmentPointMap;
4812
+ this.segmentToSegmentPointMap = segmentToSegmentPointMap;
4798
4813
  this.nodePfMap = this.computeInitialPfMap();
4799
4814
  }
4800
4815
  computeInitialPfMap() {
@@ -4810,9 +4825,7 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
4810
4825
  numEntryExitLayerChanges,
4811
4826
  numTransitionCrossings
4812
4827
  } = getIntraNodeCrossingsFromSegments(
4813
- this.dedupedSegments.filter(
4814
- (seg) => this.segmentIdToNodeIds.get(seg.nodePortSegmentId).includes(node.capacityMeshNodeId)
4815
- )
4828
+ this.nodeIdToSegmentIds.get(node.capacityMeshNodeId)?.map((segId) => this.dedupedSegmentMap.get(segId)) || []
4816
4829
  );
4817
4830
  const probabilityOfFailure = calculateNodeProbabilityOfFailure(
4818
4831
  node,
@@ -4854,7 +4867,9 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
4854
4867
  colorMap: this.colorMap,
4855
4868
  rootNodeId: highestPfNodeId,
4856
4869
  MUTABLE_HOPS: this.MUTABLE_HOPS,
4857
- segmentPointMap: this.segmentPointMap
4870
+ segmentPointMap: this.segmentPointMap,
4871
+ nodeToSegmentPointMap: this.nodeToSegmentPointMap,
4872
+ segmentToSegmentPointMap: this.segmentToSegmentPointMap
4858
4873
  });
4859
4874
  }
4860
4875
  this.activeSolver.step();
@@ -5423,6 +5438,7 @@ var StrawSolver = class extends BaseSolver {
5423
5438
  nodeIdCounter;
5424
5439
  constructor(params) {
5425
5440
  super();
5441
+ this.MAX_ITERATIONS = 1e5;
5426
5442
  this.strawSize = params.strawSize ?? 0.5;
5427
5443
  this.multiLayerNodes = [];
5428
5444
  this.strawNodes = [];
@@ -5615,6 +5631,72 @@ ${node.width}x${node.height}`
5615
5631
  }
5616
5632
  };
5617
5633
 
5634
+ // lib/utils/areNodesBordering.ts
5635
+ function areNodesBordering(node1, node2) {
5636
+ const n1Left = node1.center.x - node1.width / 2;
5637
+ const n1Right = node1.center.x + node1.width / 2;
5638
+ const n1Top = node1.center.y - node1.height / 2;
5639
+ const n1Bottom = node1.center.y + node1.height / 2;
5640
+ const n2Left = node2.center.x - node2.width / 2;
5641
+ const n2Right = node2.center.x + node2.width / 2;
5642
+ const n2Top = node2.center.y - node2.height / 2;
5643
+ const n2Bottom = node2.center.y + node2.height / 2;
5644
+ const epsilon = 1e-3;
5645
+ const shareVerticalBorder = (Math.abs(n1Right - n2Left) < epsilon || Math.abs(n1Left - n2Right) < epsilon) && Math.min(n1Bottom, n2Bottom) - Math.max(n1Top, n2Top) >= epsilon;
5646
+ const shareHorizontalBorder = (Math.abs(n1Bottom - n2Top) < epsilon || Math.abs(n1Top - n2Bottom) < epsilon) && Math.min(n1Right, n2Right) - Math.max(n1Left, n2Left) >= epsilon;
5647
+ return shareVerticalBorder || shareHorizontalBorder;
5648
+ }
5649
+
5650
+ // lib/data-structures/CapacityNodeTree.ts
5651
+ var CapacityNodeTree = class {
5652
+ constructor(nodes) {
5653
+ this.nodes = nodes;
5654
+ this.buckets = /* @__PURE__ */ new Map();
5655
+ for (const node of nodes) {
5656
+ const nodeMinX = node.center.x - node.width / 2;
5657
+ const nodeMinY = node.center.y - node.height / 2;
5658
+ const nodeMaxX = node.center.x + node.width / 2;
5659
+ const nodeMaxY = node.center.y + node.height / 2;
5660
+ for (let x = nodeMinX; x <= nodeMaxX; x += this.CELL_SIZE) {
5661
+ for (let y = nodeMinY; y <= nodeMaxY; y += this.CELL_SIZE) {
5662
+ const bucketKey = this.getBucketKey(x, y);
5663
+ const bucket = this.buckets.get(bucketKey);
5664
+ if (!bucket) {
5665
+ this.buckets.set(bucketKey, [node]);
5666
+ } else {
5667
+ bucket.push(node);
5668
+ }
5669
+ }
5670
+ }
5671
+ }
5672
+ }
5673
+ buckets;
5674
+ CELL_SIZE = 0.4;
5675
+ getBucketKey(x, y) {
5676
+ return `${Math.floor(x / this.CELL_SIZE)}x${Math.floor(y / this.CELL_SIZE)}`;
5677
+ }
5678
+ getNodesInArea(centerX, centerY, width, height) {
5679
+ const nodes = [];
5680
+ const alreadyAddedNodes = /* @__PURE__ */ new Set();
5681
+ const minX = centerX - width / 2;
5682
+ const minY = centerY - height / 2;
5683
+ const maxX = centerX + width / 2;
5684
+ const maxY = centerY + height / 2;
5685
+ for (let x = minX; x <= maxX; x += this.CELL_SIZE) {
5686
+ for (let y = minY; y <= maxY; y += this.CELL_SIZE) {
5687
+ const bucketKey = this.getBucketKey(x, y);
5688
+ const bucket = this.buckets.get(bucketKey) || [];
5689
+ for (const node of bucket) {
5690
+ if (alreadyAddedNodes.has(node.capacityMeshNodeId)) continue;
5691
+ alreadyAddedNodes.add(node.capacityMeshNodeId);
5692
+ nodes.push(node);
5693
+ }
5694
+ }
5695
+ }
5696
+ return nodes;
5697
+ }
5698
+ };
5699
+
5618
5700
  // lib/solvers/SingleLayerNodeMerger/SingleLayerNodeMergerSolver.ts
5619
5701
  var EPSILON = 5e-3;
5620
5702
  var SingleLayerNodeMergerSolver = class extends BaseSolver {
@@ -5623,6 +5705,7 @@ var SingleLayerNodeMergerSolver = class extends BaseSolver {
5623
5705
  absorbedNodeIds;
5624
5706
  nextBatchNodeIds;
5625
5707
  batchHadModifications;
5708
+ hasComputedAdjacentNodeIds = false;
5626
5709
  newNodes;
5627
5710
  constructor(nodes) {
5628
5711
  super();
@@ -5633,41 +5716,94 @@ var SingleLayerNodeMergerSolver = class extends BaseSolver {
5633
5716
  }
5634
5717
  this.newNodes = [];
5635
5718
  this.absorbedNodeIds = /* @__PURE__ */ new Set();
5636
- const nodeWithArea = [];
5719
+ const unprocessedNodesWithArea = [];
5637
5720
  for (const node of nodes) {
5638
5721
  if (node.availableZ.length > 1) {
5639
5722
  this.newNodes.push(node);
5640
5723
  this.absorbedNodeIds.add(node.capacityMeshNodeId);
5641
5724
  } else {
5642
- nodeWithArea.push([node.capacityMeshNodeId, node.width * node.height]);
5725
+ unprocessedNodesWithArea.push([node, node.width * node.height]);
5643
5726
  }
5644
5727
  }
5645
- nodeWithArea.sort((a, b) => a[1] - b[1]);
5646
- for (const [nodeId, area] of nodeWithArea) {
5647
- const node = this.nodeMap.get(nodeId);
5648
- this.nodeMap.set(nodeId, {
5728
+ unprocessedNodesWithArea.sort((a, b) => a[1] - b[1]);
5729
+ for (const [node, area] of unprocessedNodesWithArea) {
5730
+ const unprocessedNode = {
5649
5731
  ...node,
5650
5732
  center: { ...node.center }
5651
- });
5733
+ };
5734
+ this.nodeMap.set(node.capacityMeshNodeId, unprocessedNode);
5652
5735
  }
5653
- this.currentBatchNodeIds = nodeWithArea.map((n) => n[0]);
5736
+ this.currentBatchNodeIds = unprocessedNodesWithArea.map(
5737
+ ([node]) => node.capacityMeshNodeId
5738
+ );
5654
5739
  this.nextBatchNodeIds = [];
5655
5740
  this.batchHadModifications = false;
5656
5741
  }
5742
+ computeAdjacentNodeIdsForFirstBatch(nodes) {
5743
+ const nodeTrees = [
5744
+ new CapacityNodeTree(nodes.filter((n) => n.availableZ[0] === 0)),
5745
+ new CapacityNodeTree(nodes.filter((n) => n.availableZ[0] === 1))
5746
+ ];
5747
+ for (const node of nodes) {
5748
+ const adjacentNodes = [];
5749
+ const z = node.availableZ[0];
5750
+ const nodesInArea = nodeTrees[z].getNodesInArea(
5751
+ node.center.x,
5752
+ node.center.y,
5753
+ node.width * 4,
5754
+ node.height * 4
5755
+ );
5756
+ for (const unprocessedNode of nodesInArea) {
5757
+ if (unprocessedNode._containsTarget && unprocessedNode._targetConnectionName !== node._targetConnectionName)
5758
+ continue;
5759
+ if (unprocessedNode.capacityMeshNodeId === node.capacityMeshNodeId)
5760
+ continue;
5761
+ if (!areNodesBordering(node, unprocessedNode)) continue;
5762
+ adjacentNodes.push(unprocessedNode);
5763
+ }
5764
+ node._adjacentNodeIds = adjacentNodes.map((n) => n.capacityMeshNodeId);
5765
+ }
5766
+ }
5767
+ // getAdjacentSameLayerUnprocessedNodes1(rootNode: CapacityMeshNode) {
5768
+ // const adjacentNodes: CapacityMeshNode[] = []
5769
+ // for (const unprocessedNodeId of this.currentBatchNodeIds) {
5770
+ // const unprocessedNode = this.nodeMap.get(unprocessedNodeId)!
5771
+ // if (!areNodesBordering(rootNode, unprocessedNode)) continue
5772
+ // if (unprocessedNode.availableZ[0] !== rootNode.availableZ[0]) continue
5773
+ // if (
5774
+ // unprocessedNode._containsTarget &&
5775
+ // unprocessedNode._targetConnectionName !== rootNode._targetConnectionName
5776
+ // )
5777
+ // continue
5778
+ // if (this.absorbedNodeIds.has(unprocessedNodeId)) continue
5779
+ // adjacentNodes.push(unprocessedNode)
5780
+ // }
5781
+ // return adjacentNodes
5782
+ // }
5657
5783
  getAdjacentSameLayerUnprocessedNodes(rootNode) {
5784
+ return this.getAdjacentSameLayerUnprocessedNodes2(rootNode);
5785
+ }
5786
+ getAdjacentSameLayerUnprocessedNodes2(rootNode) {
5658
5787
  const adjacentNodes = [];
5659
- for (const unprocessedNodeId of this.currentBatchNodeIds) {
5660
- if (this.absorbedNodeIds.has(unprocessedNodeId)) continue;
5661
- const unprocessedNode = this.nodeMap.get(unprocessedNodeId);
5662
- if (unprocessedNode.availableZ[0] !== rootNode.availableZ[0]) continue;
5663
- if (unprocessedNode._containsTarget && unprocessedNode._targetConnectionName !== rootNode._targetConnectionName)
5664
- continue;
5665
- if (!areNodesBordering(rootNode, unprocessedNode)) continue;
5788
+ const unprocessedAdjNodes = Array.from(
5789
+ new Set(
5790
+ (rootNode._adjacentNodeIds ?? []).map((a) => this.nodeMap.get(a))
5791
+ )
5792
+ );
5793
+ unprocessedAdjNodes.sort((a, b) => a.width * a.height - b.width * b.height);
5794
+ for (const unprocessedNode of unprocessedAdjNodes) {
5795
+ if (this.absorbedNodeIds.has(unprocessedNode.capacityMeshNodeId)) continue;
5666
5796
  adjacentNodes.push(unprocessedNode);
5667
5797
  }
5668
5798
  return adjacentNodes;
5669
5799
  }
5670
5800
  _step() {
5801
+ if (!this.hasComputedAdjacentNodeIds) {
5802
+ this.computeAdjacentNodeIdsForFirstBatch(
5803
+ this.currentBatchNodeIds.map((id) => this.nodeMap.get(id))
5804
+ );
5805
+ this.hasComputedAdjacentNodeIds = true;
5806
+ }
5671
5807
  let rootNodeId = this.currentBatchNodeIds.pop();
5672
5808
  while (rootNodeId && this.absorbedNodeIds.has(rootNodeId)) {
5673
5809
  rootNodeId = this.currentBatchNodeIds.pop();
@@ -5696,6 +5832,19 @@ var SingleLayerNodeMergerSolver = class extends BaseSolver {
5696
5832
  this.nextBatchNodeIds.push(rootNodeId);
5697
5833
  return;
5698
5834
  }
5835
+ const absorbAdjacentNodeIds = (nodesToAbsorb) => {
5836
+ for (const adjNode of nodesToAbsorb) {
5837
+ this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
5838
+ }
5839
+ rootNode._adjacentNodeIds = Array.from(
5840
+ new Set(
5841
+ [
5842
+ ...rootNode._adjacentNodeIds ?? [],
5843
+ ...nodesToAbsorb.flatMap((n) => n._adjacentNodeIds ?? [])
5844
+ ].filter((id) => !this.absorbedNodeIds.has(id))
5845
+ )
5846
+ );
5847
+ };
5699
5848
  const adjacentNodesToLeft = adjacentNodes.filter(
5700
5849
  (adjNode) => adjNode.center.x < rootNode.center.x && Math.abs(adjNode.center.y - rootNode.center.y) < rootNode.height / 2
5701
5850
  );
@@ -5712,9 +5861,7 @@ var SingleLayerNodeMergerSolver = class extends BaseSolver {
5712
5861
  if (leftAdjNodesTakeUpEntireHeight && leftAdjNodesAreAllSameSize) {
5713
5862
  rootNode.width += leftAdjNodeWidth;
5714
5863
  rootNode.center.x = rootNode.center.x - leftAdjNodeWidth / 2;
5715
- for (const adjNode of adjacentNodesToLeft) {
5716
- this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
5717
- }
5864
+ absorbAdjacentNodeIds(adjacentNodesToLeft);
5718
5865
  rootNodeHasGrown = true;
5719
5866
  }
5720
5867
  }
@@ -5734,9 +5881,7 @@ var SingleLayerNodeMergerSolver = class extends BaseSolver {
5734
5881
  if (rightAdjNodesTakeUpEntireHeight && rightAdjNodesAreAllSameSize) {
5735
5882
  rootNode.width += rightAdjNodeWidth;
5736
5883
  rootNode.center.x = rootNode.center.x + rightAdjNodeWidth / 2;
5737
- for (const adjNode of adjacentNodesToRight) {
5738
- this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
5739
- }
5884
+ absorbAdjacentNodeIds(adjacentNodesToRight);
5740
5885
  rootNodeHasGrown = true;
5741
5886
  }
5742
5887
  }
@@ -5756,9 +5901,7 @@ var SingleLayerNodeMergerSolver = class extends BaseSolver {
5756
5901
  if (topAdjNodesTakeUpEntireWidth && topAdjNodesAreAllSameSize) {
5757
5902
  rootNode.height += topAdjNodeHeight;
5758
5903
  rootNode.center.y = rootNode.center.y + topAdjNodeHeight / 2;
5759
- for (const adjNode of adjacentNodesToTop) {
5760
- this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
5761
- }
5904
+ absorbAdjacentNodeIds(adjacentNodesToTop);
5762
5905
  rootNodeHasGrown = true;
5763
5906
  }
5764
5907
  }
@@ -5778,9 +5921,7 @@ var SingleLayerNodeMergerSolver = class extends BaseSolver {
5778
5921
  if (bottomAdjNodesTakeUpEntireWidth && bottomAdjNodesAreAllSameSize) {
5779
5922
  rootNode.height += bottomAdjNodeHeight;
5780
5923
  rootNode.center.y = rootNode.center.y - bottomAdjNodeHeight / 2;
5781
- for (const adjNode of adjacentNodesToBottom) {
5782
- this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
5783
- }
5924
+ absorbAdjacentNodeIds(adjacentNodesToBottom);
5784
5925
  rootNodeHasGrown = true;
5785
5926
  }
5786
5927
  }
@@ -5870,7 +6011,7 @@ var SingleSimplifiedPathSolver = class extends BaseSolver {
5870
6011
  this.obstacles = params.obstacles;
5871
6012
  this.connMap = params.connMap;
5872
6013
  this.colorMap = params.colorMap;
5873
- this.newRoute = [];
6014
+ this.newRoute = [this.inputRoute.route[0]];
5874
6015
  this.newVias = [];
5875
6016
  }
5876
6017
  get simplifiedRoute() {
@@ -6055,32 +6196,173 @@ function segmentsIntersect(A1, A2, B1, B2) {
6055
6196
  return false;
6056
6197
  }
6057
6198
 
6199
+ // lib/data-structures/SegmentTree.ts
6200
+ var getSegmentBounds = (segment) => {
6201
+ return {
6202
+ minX: Math.min(segment[0].x, segment[1].x),
6203
+ maxX: Math.max(segment[0].x, segment[1].x),
6204
+ minY: Math.min(segment[0].y, segment[1].y),
6205
+ maxY: Math.max(segment[0].y, segment[1].y)
6206
+ };
6207
+ };
6208
+ var SegmentTree = class {
6209
+ constructor(segments) {
6210
+ this.segments = segments;
6211
+ this.buckets = /* @__PURE__ */ new Map();
6212
+ for (const segment of segments) {
6213
+ const bounds = getSegmentBounds(segment);
6214
+ const bucketMinX = Math.floor(bounds.minX / this.CELL_SIZE) * this.CELL_SIZE;
6215
+ const bucketMinY = Math.floor(bounds.minY / this.CELL_SIZE) * this.CELL_SIZE;
6216
+ for (let x = bucketMinX; x <= bounds.maxX; x += this.CELL_SIZE) {
6217
+ for (let y = bucketMinY; y <= bounds.maxY; y += this.CELL_SIZE) {
6218
+ const bucketKey = this.getBucketKey(x, y);
6219
+ const bucket = this.buckets.get(bucketKey);
6220
+ if (!bucket) {
6221
+ this.buckets.set(bucketKey, [
6222
+ [segment[0], segment[1], this.getSegmentKey(segment)]
6223
+ ]);
6224
+ } else {
6225
+ bucket.push([segment[0], segment[1], this.getSegmentKey(segment)]);
6226
+ }
6227
+ }
6228
+ }
6229
+ }
6230
+ }
6231
+ buckets;
6232
+ CELL_SIZE = 0.4;
6233
+ getBucketKey(x, y) {
6234
+ return `${Math.floor(x / this.CELL_SIZE)}x${Math.floor(y / this.CELL_SIZE)}`;
6235
+ }
6236
+ getSegmentKey(segment) {
6237
+ return `${segment[0].x}-${segment[0].y}-${segment[0].z}-${segment[1].x}-${segment[1].y}-${segment[1].z}`;
6238
+ }
6239
+ getSegmentsThatCouldIntersect(A, B) {
6240
+ const segments = [];
6241
+ const alreadyAddedSegments = /* @__PURE__ */ new Set();
6242
+ const minX = Math.min(A.x, B.x);
6243
+ const minY = Math.min(A.y, B.y);
6244
+ const maxX = Math.max(A.x, B.x);
6245
+ const maxY = Math.max(A.y, B.y);
6246
+ const bucketMinX = Math.floor(minX / this.CELL_SIZE) * this.CELL_SIZE;
6247
+ const bucketMinY = Math.floor(minY / this.CELL_SIZE) * this.CELL_SIZE;
6248
+ for (let x = bucketMinX; x <= maxX; x += this.CELL_SIZE) {
6249
+ for (let y = bucketMinY; y <= maxY; y += this.CELL_SIZE) {
6250
+ const bucketKey = this.getBucketKey(x, y);
6251
+ const bucket = this.buckets.get(bucketKey) || [];
6252
+ for (const segment of bucket) {
6253
+ const key = segment[2];
6254
+ if (alreadyAddedSegments.has(key)) continue;
6255
+ alreadyAddedSegments.add(key);
6256
+ segments.push(segment);
6257
+ }
6258
+ }
6259
+ }
6260
+ return segments;
6261
+ }
6262
+ };
6263
+
6058
6264
  // lib/solvers/SimplifiedPathSolver/SingleSimplifiedPathSolver5_Deg45.ts
6059
6265
  var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
6060
6266
  pathSegments = [];
6061
6267
  totalPathLength = 0;
6062
6268
  headDistanceAlongPath = 0;
6063
6269
  tailDistanceAlongPath = 0;
6064
- stepSize = 0.25;
6270
+ minStepSize = 0.25;
6065
6271
  // Default step size, can be adjusted
6066
6272
  lastValidPath = null;
6067
6273
  // Store the current valid path
6068
6274
  lastValidPathHeadDistance = 0;
6275
+ /** Amount the step size is reduced when the step isn't possible */
6276
+ STEP_SIZE_REDUCTION_FACTOR = 0.25;
6277
+ maxStepSize = 4;
6278
+ currentStepSize = this.maxStepSize;
6279
+ lastHeadMoveDistance = 0;
6280
+ cachedValidPathSegments;
6069
6281
  filteredObstacles = [];
6282
+ filteredObstaclePathSegments = [];
6283
+ filteredVias = [];
6284
+ segmentTree;
6070
6285
  OBSTACLE_MARGIN = 0.15;
6071
6286
  TAIL_JUMP_RATIO = 0.8;
6072
6287
  constructor(params) {
6073
6288
  super(params);
6289
+ this.cachedValidPathSegments = /* @__PURE__ */ new Set();
6074
6290
  if (this.inputRoute.route.length <= 1) {
6075
6291
  this.newRoute = [...this.inputRoute.route];
6076
6292
  this.solved = true;
6077
6293
  return;
6078
6294
  }
6295
+ const bounds = this.inputRoute.route.reduce(
6296
+ (acc, point) => {
6297
+ acc.minX = Math.min(acc.minX, point.x);
6298
+ acc.maxX = Math.max(acc.maxX, point.x);
6299
+ acc.minY = Math.min(acc.minY, point.y);
6300
+ acc.maxY = Math.max(acc.maxY, point.y);
6301
+ return acc;
6302
+ },
6303
+ { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity }
6304
+ );
6079
6305
  this.filteredObstacles = this.obstacles.filter(
6080
6306
  (obstacle) => !obstacle.connectedTo.some(
6081
6307
  (id) => this.connMap.areIdsConnected(this.inputRoute.connectionName, id)
6082
6308
  )
6309
+ ).filter((obstacle) => {
6310
+ if (obstacle.connectedTo.some(
6311
+ (obsId) => this.connMap.areIdsConnected(this.inputRoute.connectionName, obsId)
6312
+ )) {
6313
+ return false;
6314
+ }
6315
+ const obstacleMinX = obstacle.center.x - obstacle.width / 2 - this.OBSTACLE_MARGIN;
6316
+ const obstacleMaxX = obstacle.center.x + obstacle.width / 2 + this.OBSTACLE_MARGIN;
6317
+ const obstacleMinY = obstacle.center.y - obstacle.height / 2 - this.OBSTACLE_MARGIN;
6318
+ const obstacleMaxY = obstacle.center.y + obstacle.height / 2 + this.OBSTACLE_MARGIN;
6319
+ return obstacleMinX <= bounds.maxX && obstacleMaxX >= bounds.minX && obstacleMinY <= bounds.maxY && obstacleMaxY >= bounds.minY;
6320
+ });
6321
+ this.filteredObstaclePathSegments = this.otherHdRoutes.flatMap(
6322
+ (hdRoute) => {
6323
+ if (this.connMap.areIdsConnected(
6324
+ this.inputRoute.connectionName,
6325
+ hdRoute.connectionName
6326
+ )) {
6327
+ return [];
6328
+ }
6329
+ const route = hdRoute.route;
6330
+ const segments = [];
6331
+ for (let i = 0; i < route.length - 1; i++) {
6332
+ const start = route[i];
6333
+ const end = route[i + 1];
6334
+ const minX = Math.min(start.x, end.x);
6335
+ const maxX = Math.max(start.x, end.x);
6336
+ const minY = Math.min(start.y, end.y);
6337
+ const maxY = Math.max(start.y, end.y);
6338
+ if (minX <= bounds.maxX && maxX >= bounds.minX && minY <= bounds.maxY && maxY >= bounds.minY) {
6339
+ segments.push([start, end]);
6340
+ }
6341
+ }
6342
+ return segments;
6343
+ }
6083
6344
  );
6345
+ this.segmentTree = new SegmentTree(this.filteredObstaclePathSegments);
6346
+ this.filteredVias = this.otherHdRoutes.flatMap((hdRoute) => {
6347
+ if (this.connMap.areIdsConnected(
6348
+ this.inputRoute.connectionName,
6349
+ hdRoute.connectionName
6350
+ )) {
6351
+ return [];
6352
+ }
6353
+ const vias = hdRoute.vias;
6354
+ const filteredVias = [];
6355
+ for (const via of vias) {
6356
+ const minX = via.x - hdRoute.viaDiameter / 2;
6357
+ const maxX = via.x + hdRoute.viaDiameter / 2;
6358
+ const minY = via.y - hdRoute.viaDiameter / 2;
6359
+ const maxY = via.y + hdRoute.viaDiameter / 2;
6360
+ if (minX <= bounds.maxX && maxX >= bounds.minX && minY <= bounds.maxY && maxY >= bounds.minY) {
6361
+ filteredVias.push({ ...via, diameter: hdRoute.viaDiameter });
6362
+ }
6363
+ }
6364
+ return filteredVias;
6365
+ });
6084
6366
  this.computePathSegments();
6085
6367
  }
6086
6368
  // Compute the path segments and their distances
@@ -6118,7 +6400,7 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
6118
6400
  return {
6119
6401
  x: segment.start.x + factor * (segment.end.x - segment.start.x),
6120
6402
  y: segment.start.y + factor * (segment.end.y - segment.start.y),
6121
- z: segment.start.z
6403
+ z: factor < 0.5 ? segment.start.z : segment.end.z
6122
6404
  // Z doesn't interpolate - use the segment's start z value
6123
6405
  };
6124
6406
  }
@@ -6169,33 +6451,24 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
6169
6451
  return false;
6170
6452
  }
6171
6453
  }
6172
- for (const route of this.otherHdRoutes) {
6173
- if (this.connMap.areIdsConnected(
6174
- this.inputRoute.connectionName,
6175
- route.connectionName
6176
- )) {
6177
- continue;
6178
- }
6179
- for (let j = 0; j < route.route.length - 1; j++) {
6180
- const routeStart = route.route[j];
6181
- const routeEnd = route.route[j + 1];
6182
- if (routeStart.z === start.z && routeEnd.z === start.z) {
6183
- if (minimumDistanceBetweenSegments(
6184
- { x: start.x, y: start.y },
6185
- { x: end.x, y: end.y },
6186
- { x: routeStart.x, y: routeStart.y },
6187
- { x: routeEnd.x, y: routeEnd.y }
6188
- ) < this.OBSTACLE_MARGIN) {
6189
- return false;
6190
- }
6191
- }
6192
- }
6193
- for (const via of route.vias) {
6194
- if (pointToSegmentDistance(via, start, end) < this.OBSTACLE_MARGIN + route.viaDiameter / 2) {
6454
+ const segmentsThatCouldIntersect = this.segmentTree.getSegmentsThatCouldIntersect(start, end);
6455
+ for (const [otherSegA, otherSegB] of segmentsThatCouldIntersect) {
6456
+ if (otherSegA.z === start.z && otherSegB.z === start.z) {
6457
+ if (minimumDistanceBetweenSegments(
6458
+ { x: start.x, y: start.y },
6459
+ { x: end.x, y: end.y },
6460
+ { x: otherSegA.x, y: otherSegA.y },
6461
+ { x: otherSegB.x, y: otherSegB.y }
6462
+ ) < this.OBSTACLE_MARGIN) {
6195
6463
  return false;
6196
6464
  }
6197
6465
  }
6198
6466
  }
6467
+ for (const via of this.filteredVias) {
6468
+ if (pointToSegmentDistance(via, start, end) < this.OBSTACLE_MARGIN + via.diameter / 2) {
6469
+ return false;
6470
+ }
6471
+ }
6199
6472
  return true;
6200
6473
  }
6201
6474
  // Check if a path with multiple points is valid
@@ -6242,6 +6515,24 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
6242
6515
  }
6243
6516
  this.newRoute.push(path[i]);
6244
6517
  }
6518
+ this.currentStepSize = this.maxStepSize;
6519
+ }
6520
+ moveHead(distance4) {
6521
+ this.lastHeadMoveDistance = distance4;
6522
+ this.headDistanceAlongPath = Math.min(
6523
+ this.headDistanceAlongPath + distance4,
6524
+ this.totalPathLength
6525
+ );
6526
+ }
6527
+ stepBackAndReduceStepSize() {
6528
+ this.headDistanceAlongPath = Math.max(
6529
+ this.tailDistanceAlongPath,
6530
+ this.headDistanceAlongPath - this.lastHeadMoveDistance
6531
+ );
6532
+ this.currentStepSize = Math.max(
6533
+ this.minStepSize,
6534
+ this.currentStepSize * this.STEP_SIZE_REDUCTION_FACTOR
6535
+ );
6245
6536
  }
6246
6537
  _step() {
6247
6538
  const tailHasReachedEnd = this.tailDistanceAlongPath >= this.totalPathLength;
@@ -6273,10 +6564,7 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
6273
6564
  }
6274
6565
  }
6275
6566
  }
6276
- this.headDistanceAlongPath = Math.min(
6277
- this.headDistanceAlongPath + this.stepSize,
6278
- this.totalPathLength
6279
- );
6567
+ this.moveHead(this.currentStepSize);
6280
6568
  const tailPoint = this.getPointAtDistance(this.tailDistanceAlongPath);
6281
6569
  const headPoint = this.getPointAtDistance(this.headDistanceAlongPath);
6282
6570
  const tailIndex = this.getNearestIndexForDistance(
@@ -6295,21 +6583,26 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
6295
6583
  break;
6296
6584
  }
6297
6585
  }
6586
+ if (layerChangeBtwHeadAndTail && this.lastHeadMoveDistance > this.minStepSize) {
6587
+ this.stepBackAndReduceStepSize();
6588
+ return;
6589
+ }
6298
6590
  if (layerChangeBtwHeadAndTail && layerChangeAtDistance > 0) {
6591
+ const pointBeforeChange = this.getPointAtDistance(layerChangeAtDistance);
6299
6592
  if (this.lastValidPath) {
6300
6593
  this.addPathToResult(this.lastValidPath);
6301
6594
  this.lastValidPath = null;
6302
6595
  }
6303
- const pointBeforeChange = this.getPointAtDistance(layerChangeAtDistance);
6304
- const pointAfterChange = this.inputRoute.route[this.getNearestIndexForDistance(layerChangeAtDistance) + 1];
6596
+ const indexAfterLayerChange = this.getNearestIndexForDistance(layerChangeAtDistance) + 1;
6597
+ const pointAfterChange = this.inputRoute.route[indexAfterLayerChange];
6305
6598
  this.newVias.push({
6306
6599
  x: pointAfterChange.x,
6307
6600
  y: pointAfterChange.y
6308
6601
  });
6309
6602
  this.newRoute.push(pointAfterChange);
6310
- const nextTailIndex = this.getNearestIndexForDistance(layerChangeAtDistance) + 1;
6311
- if (this.pathSegments[nextTailIndex]) {
6312
- this.tailDistanceAlongPath = this.pathSegments[nextTailIndex].startDistance;
6603
+ this.currentStepSize = this.maxStepSize;
6604
+ if (this.pathSegments[indexAfterLayerChange]) {
6605
+ this.tailDistanceAlongPath = this.pathSegments[indexAfterLayerChange].startDistance;
6313
6606
  this.headDistanceAlongPath = this.tailDistanceAlongPath;
6314
6607
  } else {
6315
6608
  console.error("Creating via at end, this is probably not right");
@@ -6319,9 +6612,13 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
6319
6612
  return;
6320
6613
  }
6321
6614
  const path45 = this.find45DegreePath(tailPoint, headPoint);
6615
+ if (!path45 && this.lastHeadMoveDistance > this.minStepSize) {
6616
+ this.stepBackAndReduceStepSize();
6617
+ return;
6618
+ }
6322
6619
  if (!path45 && !this.lastValidPath) {
6323
- this.tailDistanceAlongPath += this.stepSize;
6324
- this.headDistanceAlongPath += this.stepSize;
6620
+ this.tailDistanceAlongPath += this.minStepSize;
6621
+ this.moveHead(this.minStepSize);
6325
6622
  return;
6326
6623
  }
6327
6624
  if (path45) {
@@ -6333,11 +6630,8 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
6333
6630
  this.addPathToResult(this.lastValidPath);
6334
6631
  this.lastValidPath = null;
6335
6632
  this.tailDistanceAlongPath = this.lastValidPathHeadDistance;
6633
+ this.moveHead(this.minStepSize);
6336
6634
  }
6337
- this.headDistanceAlongPath = Math.min(
6338
- this.headDistanceAlongPath + this.stepSize,
6339
- this.totalPathLength
6340
- );
6341
6635
  }
6342
6636
  visualize() {
6343
6637
  const graphics = this.getVisualsForNewRouteAndObstacles();
@@ -6355,6 +6649,15 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
6355
6649
  color: "orange",
6356
6650
  label: ["Head", `z: ${headPoint.z}`].join("\n")
6357
6651
  });
6652
+ const tentativeHead = this.getPointAtDistance(
6653
+ this.headDistanceAlongPath + this.currentStepSize
6654
+ );
6655
+ graphics.points.push({
6656
+ x: tentativeHead.x,
6657
+ y: tentativeHead.y,
6658
+ color: "red",
6659
+ label: ["Tentative Head", `z: ${tentativeHead.z}`].join("\n")
6660
+ });
6358
6661
  let distance4 = 0;
6359
6662
  while (distance4 < this.totalPathLength) {
6360
6663
  const point = this.getPointAtDistance(distance4);
@@ -6540,6 +6843,157 @@ var MultiSimplifiedPathSolver = class extends BaseSolver {
6540
6843
  }
6541
6844
  };
6542
6845
 
6846
+ // lib/solvers/CapacityMeshSolver/CapacityMeshEdgeSolver.ts
6847
+ var CapacityMeshEdgeSolver = class extends BaseSolver {
6848
+ constructor(nodes) {
6849
+ super();
6850
+ this.nodes = nodes;
6851
+ this.edges = [];
6852
+ }
6853
+ edges;
6854
+ getNextCapacityMeshEdgeId() {
6855
+ return `ce${this.edges.length}`;
6856
+ }
6857
+ step() {
6858
+ this.edges = [];
6859
+ for (let i = 0; i < this.nodes.length; i++) {
6860
+ for (let j = i + 1; j < this.nodes.length; j++) {
6861
+ const strawNodesWithSameParent = this.nodes[i]._strawNode && this.nodes[j]._strawNode && this.nodes[i]._strawParentCapacityMeshNodeId === this.nodes[j]._strawParentCapacityMeshNodeId;
6862
+ if (!strawNodesWithSameParent && areNodesBordering(this.nodes[i], this.nodes[j]) && this.doNodesHaveSharedLayer(this.nodes[i], this.nodes[j])) {
6863
+ this.edges.push({
6864
+ capacityMeshEdgeId: this.getNextCapacityMeshEdgeId(),
6865
+ nodeIds: [
6866
+ this.nodes[i].capacityMeshNodeId,
6867
+ this.nodes[j].capacityMeshNodeId
6868
+ ]
6869
+ });
6870
+ }
6871
+ }
6872
+ }
6873
+ this.handleTargetNodes();
6874
+ this.solved = true;
6875
+ }
6876
+ handleTargetNodes() {
6877
+ const targetNodes = this.nodes.filter((node) => node._containsTarget);
6878
+ for (const targetNode of targetNodes) {
6879
+ const hasEdge = this.edges.some(
6880
+ (edge) => edge.nodeIds.includes(targetNode.capacityMeshNodeId)
6881
+ );
6882
+ if (hasEdge) continue;
6883
+ let nearestNode = null;
6884
+ let nearestDistance = Infinity;
6885
+ for (const node of this.nodes) {
6886
+ if (node._containsObstacle) continue;
6887
+ if (node._containsTarget) continue;
6888
+ const dist = distance(targetNode.center, node.center);
6889
+ if (dist < nearestDistance) {
6890
+ nearestDistance = dist;
6891
+ nearestNode = node;
6892
+ }
6893
+ }
6894
+ if (nearestNode) {
6895
+ this.edges.push({
6896
+ capacityMeshEdgeId: this.getNextCapacityMeshEdgeId(),
6897
+ nodeIds: [
6898
+ targetNode.capacityMeshNodeId,
6899
+ nearestNode.capacityMeshNodeId
6900
+ ]
6901
+ });
6902
+ }
6903
+ }
6904
+ }
6905
+ doNodesHaveSharedLayer(node1, node2) {
6906
+ return node1.availableZ.some((z) => node2.availableZ.includes(z));
6907
+ }
6908
+ visualize() {
6909
+ const graphics = {
6910
+ lines: [],
6911
+ points: [],
6912
+ rects: this.nodes.map((node) => {
6913
+ const lowestZ = Math.min(...node.availableZ);
6914
+ return {
6915
+ width: Math.max(node.width - 2, node.width * 0.8),
6916
+ height: Math.max(node.height - 2, node.height * 0.8),
6917
+ center: {
6918
+ x: node.center.x + lowestZ * node.width * 0.05,
6919
+ y: node.center.y - lowestZ * node.width * 0.05
6920
+ },
6921
+ fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : {
6922
+ "0,1": "rgba(0,0,0,0.1)",
6923
+ "0": "rgba(0,200,200, 0.1)",
6924
+ "1": "rgba(0,0,200, 0.1)"
6925
+ }[node.availableZ.join(",")] ?? "rgba(0,200,200,0.1)",
6926
+ label: [
6927
+ node.capacityMeshNodeId,
6928
+ `availableZ: ${node.availableZ.join(",")}`,
6929
+ `target? ${node._containsTarget ?? false}`,
6930
+ `obs? ${node._containsObstacle ?? false}`
6931
+ ].join("\n")
6932
+ };
6933
+ }),
6934
+ circles: []
6935
+ };
6936
+ for (const edge of this.edges) {
6937
+ const node1 = this.nodes.find(
6938
+ (node) => node.capacityMeshNodeId === edge.nodeIds[0]
6939
+ );
6940
+ const node2 = this.nodes.find(
6941
+ (node) => node.capacityMeshNodeId === edge.nodeIds[1]
6942
+ );
6943
+ if (node1?.center && node2?.center) {
6944
+ const lowestZ1 = Math.min(...node1.availableZ);
6945
+ const lowestZ2 = Math.min(...node2.availableZ);
6946
+ const nodeCenter1Adj = {
6947
+ x: node1.center.x + lowestZ1 * node1.width * 0.05,
6948
+ y: node1.center.y - lowestZ1 * node1.width * 0.05
6949
+ };
6950
+ const nodeCenter2Adj = {
6951
+ x: node2.center.x + lowestZ2 * node2.width * 0.05,
6952
+ y: node2.center.y - lowestZ2 * node2.width * 0.05
6953
+ };
6954
+ graphics.lines.push({
6955
+ points: [nodeCenter1Adj, nodeCenter2Adj],
6956
+ strokeDash: node1.availableZ.join(",") === node2.availableZ.join(",") ? void 0 : "10 5"
6957
+ });
6958
+ }
6959
+ }
6960
+ return graphics;
6961
+ }
6962
+ };
6963
+
6964
+ // lib/solvers/CapacityMeshSolver/CapacityMeshEdgeSolver2_NodeTreeOptimization.ts
6965
+ var CapacityMeshEdgeSolver2_NodeTreeOptimization = class extends CapacityMeshEdgeSolver {
6966
+ step() {
6967
+ this.edges = [];
6968
+ const edgeSet = /* @__PURE__ */ new Set();
6969
+ const nodeTree = new CapacityNodeTree(this.nodes);
6970
+ for (let i = 0; i < this.nodes.length; i++) {
6971
+ const A = this.nodes[i];
6972
+ const maybeAdjNodes = nodeTree.getNodesInArea(
6973
+ A.center.x,
6974
+ A.center.y,
6975
+ A.width * 2,
6976
+ A.height * 2
6977
+ );
6978
+ for (const B of maybeAdjNodes) {
6979
+ const areBordering = areNodesBordering(A, B);
6980
+ if (!areBordering) continue;
6981
+ const strawNodesWithSameParent = A._strawNode && B._strawNode && A._strawParentCapacityMeshNodeId === B._strawParentCapacityMeshNodeId;
6982
+ if (!strawNodesWithSameParent && this.doNodesHaveSharedLayer(A, B) && !edgeSet.has(`${A.capacityMeshNodeId}-${B.capacityMeshNodeId}`)) {
6983
+ edgeSet.add(`${A.capacityMeshNodeId}-${B.capacityMeshNodeId}`);
6984
+ edgeSet.add(`${B.capacityMeshNodeId}-${A.capacityMeshNodeId}`);
6985
+ this.edges.push({
6986
+ capacityMeshEdgeId: this.getNextCapacityMeshEdgeId(),
6987
+ nodeIds: [A.capacityMeshNodeId, B.capacityMeshNodeId]
6988
+ });
6989
+ }
6990
+ }
6991
+ }
6992
+ this.handleTargetNodes();
6993
+ this.solved = true;
6994
+ }
6995
+ };
6996
+
6543
6997
  // lib/solvers/AutoroutingPipelineSolver.ts
6544
6998
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
6545
6999
  return {
@@ -6653,9 +7107,11 @@ var CapacityMeshSolver = class extends BaseSolver {
6653
7107
  }
6654
7108
  }
6655
7109
  ),
6656
- definePipelineStep("edgeSolver", CapacityMeshEdgeSolver, (cms) => [
6657
- cms.capacityNodes
6658
- ]),
7110
+ definePipelineStep(
7111
+ "edgeSolver",
7112
+ CapacityMeshEdgeSolver2_NodeTreeOptimization,
7113
+ (cms) => [cms.capacityNodes]
7114
+ ),
6659
7115
  definePipelineStep("pathingSolver", CapacityPathingSolver5, (cms) => [
6660
7116
  {
6661
7117
  simpleRouteJson: cms.srjWithPointPairs,