@tscircuit/capacity-autorouter 0.0.49 → 0.0.51
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 +150 -39
- package/dist/index.js +575 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -965,7 +965,7 @@ var calculateOptimalCapacityDepth = (initialWidth, targetMinCapacity = 0.5, maxD
|
|
|
965
965
|
};
|
|
966
966
|
|
|
967
967
|
// lib/data-structures/ObstacleTree.ts
|
|
968
|
-
var
|
|
968
|
+
var ObstacleSpatialHashIndex = class {
|
|
969
969
|
constructor(obstacles) {
|
|
970
970
|
this.obstacles = obstacles;
|
|
971
971
|
this.buckets = /* @__PURE__ */ new Map();
|
|
@@ -1109,7 +1109,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
|
|
|
1109
1109
|
];
|
|
1110
1110
|
this.finishedNodes = [];
|
|
1111
1111
|
this.nodeToXYOverlappingObstaclesMap = /* @__PURE__ */ new Map();
|
|
1112
|
-
this.obstacleTree = new
|
|
1112
|
+
this.obstacleTree = new ObstacleSpatialHashIndex(this.srj.obstacles);
|
|
1113
1113
|
this.targets = this.computeTargets();
|
|
1114
1114
|
this.targetTree = new TargetTree(this.targets);
|
|
1115
1115
|
}
|
|
@@ -2205,6 +2205,7 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
|
|
|
2205
2205
|
debug_nodesTooCloseToObstacle;
|
|
2206
2206
|
debug_nodePathToParentIntersectsObstacle;
|
|
2207
2207
|
debugEnabled = true;
|
|
2208
|
+
initialNodeGridOffset;
|
|
2208
2209
|
constructor(opts) {
|
|
2209
2210
|
super();
|
|
2210
2211
|
this.bounds = opts.bounds;
|
|
@@ -2250,11 +2251,18 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
|
|
|
2250
2251
|
if (this.futureConnections && this.futureConnections.length === 0 && this.obstacleRoutes.length === 0) {
|
|
2251
2252
|
this.handleSimpleCases();
|
|
2252
2253
|
}
|
|
2254
|
+
const initialNodePosition = {
|
|
2255
|
+
x: Math.round(opts.A.x / (this.cellStep / 2)) * (this.cellStep / 2),
|
|
2256
|
+
y: Math.round(opts.A.y / (this.cellStep / 2)) * (this.cellStep / 2)
|
|
2257
|
+
};
|
|
2258
|
+
this.initialNodeGridOffset = {
|
|
2259
|
+
x: initialNodePosition.x - Math.round(opts.A.x / this.cellStep) * this.cellStep,
|
|
2260
|
+
y: initialNodePosition.y - Math.round(opts.A.y / this.cellStep) * this.cellStep
|
|
2261
|
+
};
|
|
2253
2262
|
this.candidates = new SingleRouteCandidatePriorityQueue([
|
|
2254
2263
|
{
|
|
2255
2264
|
...opts.A,
|
|
2256
|
-
|
|
2257
|
-
y: Math.floor(opts.A.y / this.cellStep) * this.cellStep,
|
|
2265
|
+
...initialNodePosition,
|
|
2258
2266
|
z: opts.A.z ?? 0,
|
|
2259
2267
|
g: 0,
|
|
2260
2268
|
h: 0,
|
|
@@ -2317,7 +2325,7 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
|
|
|
2317
2325
|
}
|
|
2318
2326
|
}
|
|
2319
2327
|
for (const via of route.vias) {
|
|
2320
|
-
if (distance(node, via) < this.viaDiameter / 2 + margin) {
|
|
2328
|
+
if (distance(node, via) < this.viaDiameter / 2 + this.traceThickness / 2 + margin) {
|
|
2321
2329
|
return true;
|
|
2322
2330
|
}
|
|
2323
2331
|
}
|
|
@@ -2484,7 +2492,13 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
|
|
|
2484
2492
|
goalDist,
|
|
2485
2493
|
currentNode.z === this.B.z
|
|
2486
2494
|
);
|
|
2487
|
-
if (goalDist <= this.cellStep * Math.SQRT2 && currentNode.z === this.B.z
|
|
2495
|
+
if (goalDist <= this.cellStep * Math.SQRT2 && currentNode.z === this.B.z && // Make sure the last segment doesn't intersect an obstacle
|
|
2496
|
+
!this.doesPathToParentIntersectObstacle({
|
|
2497
|
+
...currentNode,
|
|
2498
|
+
parent: currentNode,
|
|
2499
|
+
x: this.B.x,
|
|
2500
|
+
y: this.B.y
|
|
2501
|
+
})) {
|
|
2488
2502
|
this.solved = true;
|
|
2489
2503
|
this.setSolvedPath(currentNode);
|
|
2490
2504
|
}
|
|
@@ -2539,8 +2553,8 @@ z: ${this.B.z}`,
|
|
|
2539
2553
|
if (this.debug_nodePathToParentIntersectsObstacle.has(nodeKey)) continue;
|
|
2540
2554
|
graphics.rects.push({
|
|
2541
2555
|
center: {
|
|
2542
|
-
x: x + z * this.cellStep / 20,
|
|
2543
|
-
y: y + z * this.cellStep / 20
|
|
2556
|
+
x: x + this.initialNodeGridOffset.x + z * this.cellStep / 20,
|
|
2557
|
+
y: y + this.initialNodeGridOffset.y + z * this.cellStep / 20
|
|
2544
2558
|
},
|
|
2545
2559
|
fill: z === 0 ? `rgba(255,0,255,${0.3 - i / this.debug_exploredNodesOrdered.length * 0.2})` : `rgba(0,0,255,${0.3 - i / this.debug_exploredNodesOrdered.length * 0.2})`,
|
|
2546
2560
|
width: this.cellStep * 0.9,
|
|
@@ -5784,11 +5798,13 @@ var UnravelSectionSolver = class extends BaseSolver {
|
|
|
5784
5798
|
colorMap;
|
|
5785
5799
|
tunedNodeCapacityMap;
|
|
5786
5800
|
MAX_CANDIDATES = 500;
|
|
5801
|
+
iterationsSinceImprovement = 0;
|
|
5787
5802
|
selectedCandidateIndex = null;
|
|
5788
5803
|
queuedOrExploredCandidatePointModificationHashes = /* @__PURE__ */ new Set();
|
|
5789
5804
|
constructor(params) {
|
|
5790
5805
|
super();
|
|
5791
5806
|
this.MUTABLE_HOPS = params.MUTABLE_HOPS ?? this.MUTABLE_HOPS;
|
|
5807
|
+
this.MAX_ITERATIONS = 5e4;
|
|
5792
5808
|
this.nodeMap = params.nodeMap;
|
|
5793
5809
|
this.dedupedSegments = params.dedupedSegments;
|
|
5794
5810
|
if (params.dedupedSegmentMap) {
|
|
@@ -6183,6 +6199,7 @@ var UnravelSectionSolver = class extends BaseSolver {
|
|
|
6183
6199
|
}
|
|
6184
6200
|
_step() {
|
|
6185
6201
|
const candidate = this.candidates.shift();
|
|
6202
|
+
this.iterationsSinceImprovement++;
|
|
6186
6203
|
if (!candidate) {
|
|
6187
6204
|
this.solved = true;
|
|
6188
6205
|
return;
|
|
@@ -6190,6 +6207,7 @@ var UnravelSectionSolver = class extends BaseSolver {
|
|
|
6190
6207
|
this.lastProcessedCandidate = candidate;
|
|
6191
6208
|
if (candidate.f < (this.bestCandidate?.f ?? Infinity)) {
|
|
6192
6209
|
this.bestCandidate = candidate;
|
|
6210
|
+
this.iterationsSinceImprovement = 0;
|
|
6193
6211
|
}
|
|
6194
6212
|
this.getNeighbors(candidate).forEach((neighbor) => {
|
|
6195
6213
|
const isPartialHashExplored = this.queuedOrExploredCandidatePointModificationHashes.has(
|
|
@@ -6591,8 +6609,7 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
|
|
|
6591
6609
|
}
|
|
6592
6610
|
this.activeSolver.step();
|
|
6593
6611
|
const { bestCandidate, originalCandidate, lastProcessedCandidate } = this.activeSolver;
|
|
6594
|
-
const
|
|
6595
|
-
const shouldEarlyStop = lastProcessedCandidate && lastProcessedCandidate.g > bestCandidate.g * giveUpFactor;
|
|
6612
|
+
const shouldEarlyStop = this.activeSolver.iterationsSinceImprovement > 200;
|
|
6596
6613
|
if (this.activeSolver.solved || shouldEarlyStop) {
|
|
6597
6614
|
const foundBetterSolution = bestCandidate && bestCandidate.g < originalCandidate.g;
|
|
6598
6615
|
if (foundBetterSolution) {
|
|
@@ -8806,6 +8823,537 @@ var CapacityMeshEdgeSolver2_NodeTreeOptimization = class extends CapacityMeshEdg
|
|
|
8806
8823
|
}
|
|
8807
8824
|
};
|
|
8808
8825
|
|
|
8826
|
+
// lib/data-structures/HighDensityRouteSpatialIndex.ts
|
|
8827
|
+
var getSegmentBounds2 = (segment) => {
|
|
8828
|
+
return {
|
|
8829
|
+
minX: Math.min(segment[0].x, segment[1].x),
|
|
8830
|
+
maxX: Math.max(segment[0].x, segment[1].x),
|
|
8831
|
+
minY: Math.min(segment[0].y, segment[1].y),
|
|
8832
|
+
maxY: Math.max(segment[0].y, segment[1].y)
|
|
8833
|
+
};
|
|
8834
|
+
};
|
|
8835
|
+
function computeDistSq(p1, p2) {
|
|
8836
|
+
const dx = p1.x - p2.x;
|
|
8837
|
+
const dy = p1.y - p2.y;
|
|
8838
|
+
return dx * dx + dy * dy;
|
|
8839
|
+
}
|
|
8840
|
+
function pointToSegmentDistanceSq(p, a, b) {
|
|
8841
|
+
const l2 = computeDistSq(a, b);
|
|
8842
|
+
if (l2 === 0) return computeDistSq(p, a);
|
|
8843
|
+
let t = ((p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y)) / l2;
|
|
8844
|
+
t = Math.max(0, Math.min(1, t));
|
|
8845
|
+
const projection = {
|
|
8846
|
+
x: a.x + t * (b.x - a.x),
|
|
8847
|
+
y: a.y + t * (b.y - a.y)
|
|
8848
|
+
};
|
|
8849
|
+
return computeDistSq(p, projection);
|
|
8850
|
+
}
|
|
8851
|
+
function segmentToSegmentDistanceSq(a, b, c, d) {
|
|
8852
|
+
if (doSegmentsIntersect(a, b, c, d)) {
|
|
8853
|
+
return 0;
|
|
8854
|
+
}
|
|
8855
|
+
const pA = { x: a.x, y: a.y };
|
|
8856
|
+
const pB = { x: b.x, y: b.y };
|
|
8857
|
+
const pC = { x: c.x, y: c.y };
|
|
8858
|
+
const pD = { x: d.x, y: d.y };
|
|
8859
|
+
return Math.min(
|
|
8860
|
+
pointToSegmentDistanceSq(pA, pC, pD),
|
|
8861
|
+
pointToSegmentDistanceSq(pB, pC, pD),
|
|
8862
|
+
pointToSegmentDistanceSq(pC, pA, pB),
|
|
8863
|
+
pointToSegmentDistanceSq(pD, pA, pB)
|
|
8864
|
+
);
|
|
8865
|
+
}
|
|
8866
|
+
var HighDensityRouteSpatialIndex = class {
|
|
8867
|
+
segmentBuckets;
|
|
8868
|
+
viaBuckets;
|
|
8869
|
+
// New: Store vias
|
|
8870
|
+
routes;
|
|
8871
|
+
CELL_SIZE;
|
|
8872
|
+
constructor(routes, cellSize = 1) {
|
|
8873
|
+
this.segmentBuckets = /* @__PURE__ */ new Map();
|
|
8874
|
+
this.viaBuckets = /* @__PURE__ */ new Map();
|
|
8875
|
+
this.routes = /* @__PURE__ */ new Map();
|
|
8876
|
+
this.CELL_SIZE = cellSize;
|
|
8877
|
+
const epsilon = 1e-9;
|
|
8878
|
+
for (const route of routes) {
|
|
8879
|
+
if (!route || !route.connectionName) {
|
|
8880
|
+
console.warn("Skipping route with missing data:", route);
|
|
8881
|
+
continue;
|
|
8882
|
+
}
|
|
8883
|
+
if (this.routes.has(route.connectionName)) {
|
|
8884
|
+
console.warn(
|
|
8885
|
+
`Skipping duplicate route connectionName: ${route.connectionName}`
|
|
8886
|
+
);
|
|
8887
|
+
continue;
|
|
8888
|
+
}
|
|
8889
|
+
this.routes.set(route.connectionName, route);
|
|
8890
|
+
if (route.route && route.route.length >= 2) {
|
|
8891
|
+
for (let i = 0; i < route.route.length - 1; i++) {
|
|
8892
|
+
const p1 = route.route[i];
|
|
8893
|
+
const p2 = route.route[i + 1];
|
|
8894
|
+
if (p1.x === p2.x && p1.y === p2.y) continue;
|
|
8895
|
+
const segment = [p1, p2];
|
|
8896
|
+
const bounds = getSegmentBounds2(segment);
|
|
8897
|
+
const segmentInfo = {
|
|
8898
|
+
segmentId: `${route.connectionName}-seg-${i}`,
|
|
8899
|
+
segment,
|
|
8900
|
+
parentRoute: route
|
|
8901
|
+
};
|
|
8902
|
+
const minIndexX = Math.floor(bounds.minX / this.CELL_SIZE);
|
|
8903
|
+
const maxIndexX = Math.floor((bounds.maxX + epsilon) / this.CELL_SIZE);
|
|
8904
|
+
const minIndexY = Math.floor(bounds.minY / this.CELL_SIZE);
|
|
8905
|
+
const maxIndexY = Math.floor((bounds.maxY + epsilon) / this.CELL_SIZE);
|
|
8906
|
+
for (let ix = minIndexX; ix <= maxIndexX; ix++) {
|
|
8907
|
+
for (let iy = minIndexY; iy <= maxIndexY; iy++) {
|
|
8908
|
+
const bucketKey = `${ix}x${iy}`;
|
|
8909
|
+
let bucketList = this.segmentBuckets.get(bucketKey);
|
|
8910
|
+
if (!bucketList) {
|
|
8911
|
+
bucketList = [];
|
|
8912
|
+
this.segmentBuckets.set(bucketKey, bucketList);
|
|
8913
|
+
}
|
|
8914
|
+
bucketList.push(segmentInfo);
|
|
8915
|
+
}
|
|
8916
|
+
}
|
|
8917
|
+
}
|
|
8918
|
+
}
|
|
8919
|
+
if (route.vias && route.vias.length > 0) {
|
|
8920
|
+
for (let i = 0; i < route.vias.length; i++) {
|
|
8921
|
+
const via = route.vias[i];
|
|
8922
|
+
if (via === void 0 || via === null) continue;
|
|
8923
|
+
const storedVia = {
|
|
8924
|
+
viaId: `${route.connectionName}-via-${i}`,
|
|
8925
|
+
x: via.x,
|
|
8926
|
+
y: via.y,
|
|
8927
|
+
parentRoute: route
|
|
8928
|
+
};
|
|
8929
|
+
const ix = Math.floor(via.x / this.CELL_SIZE);
|
|
8930
|
+
const iy = Math.floor(via.y / this.CELL_SIZE);
|
|
8931
|
+
const bucketKey = `${ix}x${iy}`;
|
|
8932
|
+
let bucketList = this.viaBuckets.get(bucketKey);
|
|
8933
|
+
if (!bucketList) {
|
|
8934
|
+
bucketList = [];
|
|
8935
|
+
this.viaBuckets.set(bucketKey, bucketList);
|
|
8936
|
+
}
|
|
8937
|
+
bucketList.push(storedVia);
|
|
8938
|
+
}
|
|
8939
|
+
}
|
|
8940
|
+
}
|
|
8941
|
+
}
|
|
8942
|
+
/**
|
|
8943
|
+
* Finds routes that potentially conflict with a given line segment within a margin.
|
|
8944
|
+
* Checks both segments and vias.
|
|
8945
|
+
* @param segmentStart Start point of the query segment.
|
|
8946
|
+
* @param segmentEnd End point of the query segment.
|
|
8947
|
+
* @param margin The minimum required clearance distance from the query segment's centerline.
|
|
8948
|
+
* @returns An array of conflicting routes and their minimum distance to the segment.
|
|
8949
|
+
*/
|
|
8950
|
+
getConflictingRoutesForSegment(segmentStart, segmentEnd, margin) {
|
|
8951
|
+
const querySegment = [segmentStart, segmentEnd];
|
|
8952
|
+
const bounds = getSegmentBounds2(querySegment);
|
|
8953
|
+
const searchMinX = bounds.minX - margin;
|
|
8954
|
+
const searchMinY = bounds.minY - margin;
|
|
8955
|
+
const searchMaxX = bounds.maxX + margin;
|
|
8956
|
+
const searchMaxY = bounds.maxY + margin;
|
|
8957
|
+
const epsilon = 1e-9;
|
|
8958
|
+
const minIndexX = Math.floor(searchMinX / this.CELL_SIZE);
|
|
8959
|
+
const maxIndexX = Math.floor((searchMaxX + epsilon) / this.CELL_SIZE);
|
|
8960
|
+
const minIndexY = Math.floor(searchMinY / this.CELL_SIZE);
|
|
8961
|
+
const maxIndexY = Math.floor((searchMaxY + epsilon) / this.CELL_SIZE);
|
|
8962
|
+
const conflictingRouteData = /* @__PURE__ */ new Map();
|
|
8963
|
+
const checkedSegments = /* @__PURE__ */ new Set();
|
|
8964
|
+
const checkedVias = /* @__PURE__ */ new Set();
|
|
8965
|
+
const queryP1 = { x: segmentStart.x, y: segmentStart.y };
|
|
8966
|
+
const queryP2 = { x: segmentEnd.x, y: segmentEnd.y };
|
|
8967
|
+
for (let ix = minIndexX; ix <= maxIndexX; ix++) {
|
|
8968
|
+
for (let iy = minIndexY; iy <= maxIndexY; iy++) {
|
|
8969
|
+
const bucketKey = `${ix}x${iy}`;
|
|
8970
|
+
const segmentBucketList = this.segmentBuckets.get(bucketKey);
|
|
8971
|
+
if (segmentBucketList) {
|
|
8972
|
+
for (const segmentInfo of segmentBucketList) {
|
|
8973
|
+
if (checkedSegments.has(segmentInfo.segmentId)) continue;
|
|
8974
|
+
checkedSegments.add(segmentInfo.segmentId);
|
|
8975
|
+
const route = segmentInfo.parentRoute;
|
|
8976
|
+
const [p1, p2] = segmentInfo.segment;
|
|
8977
|
+
const requiredSeparation = margin + route.traceThickness / 2;
|
|
8978
|
+
const requiredSeparationSq = requiredSeparation * requiredSeparation;
|
|
8979
|
+
const distSq = segmentToSegmentDistanceSq(
|
|
8980
|
+
segmentStart,
|
|
8981
|
+
segmentEnd,
|
|
8982
|
+
p1,
|
|
8983
|
+
p2
|
|
8984
|
+
);
|
|
8985
|
+
if (distSq < requiredSeparationSq) {
|
|
8986
|
+
const routeName = route.connectionName;
|
|
8987
|
+
const existing = conflictingRouteData.get(routeName);
|
|
8988
|
+
if (!existing || distSq < existing.minDistSq) {
|
|
8989
|
+
conflictingRouteData.set(routeName, {
|
|
8990
|
+
route,
|
|
8991
|
+
minDistSq: distSq
|
|
8992
|
+
});
|
|
8993
|
+
}
|
|
8994
|
+
}
|
|
8995
|
+
}
|
|
8996
|
+
}
|
|
8997
|
+
const viaBucketList = this.viaBuckets.get(bucketKey);
|
|
8998
|
+
if (viaBucketList) {
|
|
8999
|
+
for (const viaInfo of viaBucketList) {
|
|
9000
|
+
if (checkedVias.has(viaInfo.viaId)) continue;
|
|
9001
|
+
checkedVias.add(viaInfo.viaId);
|
|
9002
|
+
const route = viaInfo.parentRoute;
|
|
9003
|
+
const viaPoint = { x: viaInfo.x, y: viaInfo.y };
|
|
9004
|
+
const requiredSeparation = margin + route.viaDiameter / 2;
|
|
9005
|
+
const requiredSeparationSq = requiredSeparation * requiredSeparation;
|
|
9006
|
+
const distSq = pointToSegmentDistanceSq(viaPoint, queryP1, queryP2);
|
|
9007
|
+
if (distSq < requiredSeparationSq) {
|
|
9008
|
+
const routeName = route.connectionName;
|
|
9009
|
+
const existing = conflictingRouteData.get(routeName);
|
|
9010
|
+
if (!existing || distSq < existing.minDistSq) {
|
|
9011
|
+
conflictingRouteData.set(routeName, {
|
|
9012
|
+
route,
|
|
9013
|
+
minDistSq: distSq
|
|
9014
|
+
});
|
|
9015
|
+
}
|
|
9016
|
+
}
|
|
9017
|
+
}
|
|
9018
|
+
}
|
|
9019
|
+
}
|
|
9020
|
+
}
|
|
9021
|
+
const results = [];
|
|
9022
|
+
for (const data of conflictingRouteData.values()) {
|
|
9023
|
+
results.push({
|
|
9024
|
+
conflictingRoute: data.route,
|
|
9025
|
+
distance: Math.sqrt(data.minDistSq)
|
|
9026
|
+
});
|
|
9027
|
+
}
|
|
9028
|
+
return results;
|
|
9029
|
+
}
|
|
9030
|
+
/**
|
|
9031
|
+
* Finds routes that pass near a given point within a margin.
|
|
9032
|
+
* Checks both segments and vias.
|
|
9033
|
+
* @param point The query point {x, y}. Z is ignored.
|
|
9034
|
+
* @param margin The minimum required clearance distance from the query point.
|
|
9035
|
+
* @returns An array of conflicting routes and their minimum distance to the point.
|
|
9036
|
+
*/
|
|
9037
|
+
getConflictingRoutesNearPoint(point, margin) {
|
|
9038
|
+
const searchMinX = point.x - margin;
|
|
9039
|
+
const searchMinY = point.y - margin;
|
|
9040
|
+
const searchMaxX = point.x + margin;
|
|
9041
|
+
const searchMaxY = point.y + margin;
|
|
9042
|
+
const epsilon = 1e-9;
|
|
9043
|
+
const minIndexX = Math.floor(searchMinX / this.CELL_SIZE);
|
|
9044
|
+
const maxIndexX = Math.floor((searchMaxX + epsilon) / this.CELL_SIZE);
|
|
9045
|
+
const minIndexY = Math.floor(searchMinY / this.CELL_SIZE);
|
|
9046
|
+
const maxIndexY = Math.floor((searchMaxY + epsilon) / this.CELL_SIZE);
|
|
9047
|
+
const conflictingRouteData = /* @__PURE__ */ new Map();
|
|
9048
|
+
const checkedSegments = /* @__PURE__ */ new Set();
|
|
9049
|
+
const checkedVias = /* @__PURE__ */ new Set();
|
|
9050
|
+
for (let ix = minIndexX; ix <= maxIndexX; ix++) {
|
|
9051
|
+
for (let iy = minIndexY; iy <= maxIndexY; iy++) {
|
|
9052
|
+
const bucketKey = `${ix}x${iy}`;
|
|
9053
|
+
const segmentBucketList = this.segmentBuckets.get(bucketKey);
|
|
9054
|
+
if (segmentBucketList) {
|
|
9055
|
+
for (const segmentInfo of segmentBucketList) {
|
|
9056
|
+
if (checkedSegments.has(segmentInfo.segmentId)) continue;
|
|
9057
|
+
checkedSegments.add(segmentInfo.segmentId);
|
|
9058
|
+
const route = segmentInfo.parentRoute;
|
|
9059
|
+
const p1 = {
|
|
9060
|
+
x: segmentInfo.segment[0].x,
|
|
9061
|
+
y: segmentInfo.segment[0].y
|
|
9062
|
+
};
|
|
9063
|
+
const p2 = {
|
|
9064
|
+
x: segmentInfo.segment[1].x,
|
|
9065
|
+
y: segmentInfo.segment[1].y
|
|
9066
|
+
};
|
|
9067
|
+
const requiredSeparation = margin + route.traceThickness / 2;
|
|
9068
|
+
const requiredSeparationSq = requiredSeparation * requiredSeparation;
|
|
9069
|
+
const distSq = pointToSegmentDistanceSq(point, p1, p2);
|
|
9070
|
+
if (distSq < requiredSeparationSq) {
|
|
9071
|
+
const routeName = route.connectionName;
|
|
9072
|
+
const existing = conflictingRouteData.get(routeName);
|
|
9073
|
+
if (!existing || distSq < existing.minDistSq) {
|
|
9074
|
+
conflictingRouteData.set(routeName, {
|
|
9075
|
+
route,
|
|
9076
|
+
minDistSq: distSq
|
|
9077
|
+
});
|
|
9078
|
+
}
|
|
9079
|
+
}
|
|
9080
|
+
}
|
|
9081
|
+
}
|
|
9082
|
+
const viaBucketList = this.viaBuckets.get(bucketKey);
|
|
9083
|
+
if (viaBucketList) {
|
|
9084
|
+
for (const viaInfo of viaBucketList) {
|
|
9085
|
+
if (checkedVias.has(viaInfo.viaId)) continue;
|
|
9086
|
+
checkedVias.add(viaInfo.viaId);
|
|
9087
|
+
const route = viaInfo.parentRoute;
|
|
9088
|
+
const viaPoint = { x: viaInfo.x, y: viaInfo.y };
|
|
9089
|
+
const requiredSeparation = margin + route.viaDiameter / 2;
|
|
9090
|
+
const requiredSeparationSq = requiredSeparation * requiredSeparation;
|
|
9091
|
+
const distSq = computeDistSq(point, viaPoint);
|
|
9092
|
+
if (distSq < requiredSeparationSq) {
|
|
9093
|
+
const routeName = route.connectionName;
|
|
9094
|
+
const existing = conflictingRouteData.get(routeName);
|
|
9095
|
+
if (!existing || distSq < existing.minDistSq) {
|
|
9096
|
+
conflictingRouteData.set(routeName, {
|
|
9097
|
+
route,
|
|
9098
|
+
minDistSq: distSq
|
|
9099
|
+
});
|
|
9100
|
+
}
|
|
9101
|
+
}
|
|
9102
|
+
}
|
|
9103
|
+
}
|
|
9104
|
+
}
|
|
9105
|
+
}
|
|
9106
|
+
const results = [];
|
|
9107
|
+
for (const data of conflictingRouteData.values()) {
|
|
9108
|
+
results.push({
|
|
9109
|
+
conflictingRoute: data.route,
|
|
9110
|
+
distance: Math.sqrt(data.minDistSq)
|
|
9111
|
+
});
|
|
9112
|
+
}
|
|
9113
|
+
return results;
|
|
9114
|
+
}
|
|
9115
|
+
};
|
|
9116
|
+
|
|
9117
|
+
// lib/solvers/UselessViaRemovalSolver/SingleRouteUselessViaRemovalSolver.ts
|
|
9118
|
+
var SingleRouteUselessViaRemovalSolver = class extends BaseSolver {
|
|
9119
|
+
obstacleSHI;
|
|
9120
|
+
hdRouteSHI;
|
|
9121
|
+
unsimplifiedRoute;
|
|
9122
|
+
routeSections;
|
|
9123
|
+
currentSectionIndex;
|
|
9124
|
+
TRACE_THICKNESS = 0.15;
|
|
9125
|
+
OBSTACLE_MARGIN = 0.1;
|
|
9126
|
+
constructor(params) {
|
|
9127
|
+
super();
|
|
9128
|
+
this.currentSectionIndex = 1;
|
|
9129
|
+
this.obstacleSHI = params.obstacleSHI;
|
|
9130
|
+
this.hdRouteSHI = params.hdRouteSHI;
|
|
9131
|
+
this.unsimplifiedRoute = params.unsimplifiedRoute;
|
|
9132
|
+
this.routeSections = this.breakRouteIntoSections(this.unsimplifiedRoute);
|
|
9133
|
+
}
|
|
9134
|
+
breakRouteIntoSections(route) {
|
|
9135
|
+
const routeSections = [];
|
|
9136
|
+
const routePoints = route.route;
|
|
9137
|
+
if (routePoints.length === 0) return [];
|
|
9138
|
+
let currentSection = {
|
|
9139
|
+
startIndex: 0,
|
|
9140
|
+
endIndex: -1,
|
|
9141
|
+
z: routePoints[0].z,
|
|
9142
|
+
points: [routePoints[0]]
|
|
9143
|
+
};
|
|
9144
|
+
for (let i = 1; i < routePoints.length; i++) {
|
|
9145
|
+
if (routePoints[i].z === currentSection.z) {
|
|
9146
|
+
currentSection.points.push(routePoints[i]);
|
|
9147
|
+
} else {
|
|
9148
|
+
currentSection.endIndex = i - 1;
|
|
9149
|
+
routeSections.push(currentSection);
|
|
9150
|
+
currentSection = {
|
|
9151
|
+
startIndex: i,
|
|
9152
|
+
endIndex: -1,
|
|
9153
|
+
z: routePoints[i].z,
|
|
9154
|
+
points: [routePoints[i]]
|
|
9155
|
+
};
|
|
9156
|
+
}
|
|
9157
|
+
}
|
|
9158
|
+
currentSection.endIndex = routePoints.length - 1;
|
|
9159
|
+
routeSections.push(currentSection);
|
|
9160
|
+
return routeSections;
|
|
9161
|
+
}
|
|
9162
|
+
_step() {
|
|
9163
|
+
if (this.currentSectionIndex >= this.routeSections.length - 1) {
|
|
9164
|
+
this.solved = true;
|
|
9165
|
+
return;
|
|
9166
|
+
}
|
|
9167
|
+
const prevSection = this.routeSections[this.currentSectionIndex - 1];
|
|
9168
|
+
const currentSection = this.routeSections[this.currentSectionIndex];
|
|
9169
|
+
const nextSection = this.routeSections[this.currentSectionIndex + 1];
|
|
9170
|
+
if (prevSection.z !== nextSection.z) {
|
|
9171
|
+
this.currentSectionIndex++;
|
|
9172
|
+
return;
|
|
9173
|
+
}
|
|
9174
|
+
const targetZ = prevSection.z;
|
|
9175
|
+
if (this.canSectionMoveToLayer({ currentSection, targetZ })) {
|
|
9176
|
+
currentSection.z = targetZ;
|
|
9177
|
+
currentSection.points = currentSection.points.map((p) => ({
|
|
9178
|
+
...p,
|
|
9179
|
+
z: targetZ
|
|
9180
|
+
}));
|
|
9181
|
+
this.currentSectionIndex += 2;
|
|
9182
|
+
return;
|
|
9183
|
+
}
|
|
9184
|
+
this.currentSectionIndex++;
|
|
9185
|
+
return;
|
|
9186
|
+
}
|
|
9187
|
+
canSectionMoveToLayer({
|
|
9188
|
+
currentSection,
|
|
9189
|
+
targetZ
|
|
9190
|
+
}) {
|
|
9191
|
+
for (let i = 0; i < currentSection.points.length - 1; i++) {
|
|
9192
|
+
const A = { ...currentSection.points[i], z: targetZ };
|
|
9193
|
+
const B = { ...currentSection.points[i + 1], z: targetZ };
|
|
9194
|
+
const conflictingRoutes = this.hdRouteSHI.getConflictingRoutesForSegment(
|
|
9195
|
+
A,
|
|
9196
|
+
B,
|
|
9197
|
+
this.TRACE_THICKNESS
|
|
9198
|
+
);
|
|
9199
|
+
for (const { conflictingRoute, distance: distance6 } of conflictingRoutes) {
|
|
9200
|
+
if (conflictingRoute.connectionName === this.unsimplifiedRoute.connectionName)
|
|
9201
|
+
continue;
|
|
9202
|
+
if (distance6 < this.TRACE_THICKNESS + conflictingRoute.traceThickness) {
|
|
9203
|
+
return false;
|
|
9204
|
+
}
|
|
9205
|
+
}
|
|
9206
|
+
const segmentBox = {
|
|
9207
|
+
centerX: (A.x + B.x) / 2,
|
|
9208
|
+
centerY: (A.y + B.y) / 2,
|
|
9209
|
+
width: Math.abs(A.x - B.x),
|
|
9210
|
+
height: Math.abs(A.y - B.y)
|
|
9211
|
+
};
|
|
9212
|
+
const obstacles = this.obstacleSHI.getNodesInArea(
|
|
9213
|
+
segmentBox.centerX,
|
|
9214
|
+
segmentBox.centerY,
|
|
9215
|
+
segmentBox.width,
|
|
9216
|
+
segmentBox.height
|
|
9217
|
+
);
|
|
9218
|
+
for (const obstacle of obstacles) {
|
|
9219
|
+
const distToObstacle = segmentToBoxMinDistance(A, B, obstacle);
|
|
9220
|
+
if (distToObstacle < this.TRACE_THICKNESS + this.OBSTACLE_MARGIN) {
|
|
9221
|
+
return false;
|
|
9222
|
+
}
|
|
9223
|
+
}
|
|
9224
|
+
}
|
|
9225
|
+
return true;
|
|
9226
|
+
}
|
|
9227
|
+
getOptimizedHdRoute() {
|
|
9228
|
+
const route = this.routeSections.flatMap((section) => section.points);
|
|
9229
|
+
const vias = [];
|
|
9230
|
+
for (let i = 0; i < route.length - 1; i++) {
|
|
9231
|
+
if (route[i].z !== route[i + 1].z) {
|
|
9232
|
+
vias.push({
|
|
9233
|
+
x: route[i].x,
|
|
9234
|
+
y: route[i].y
|
|
9235
|
+
});
|
|
9236
|
+
}
|
|
9237
|
+
}
|
|
9238
|
+
return {
|
|
9239
|
+
connectionName: this.unsimplifiedRoute.connectionName,
|
|
9240
|
+
route,
|
|
9241
|
+
traceThickness: this.unsimplifiedRoute.traceThickness,
|
|
9242
|
+
vias,
|
|
9243
|
+
viaDiameter: this.unsimplifiedRoute.viaDiameter
|
|
9244
|
+
};
|
|
9245
|
+
}
|
|
9246
|
+
visualize() {
|
|
9247
|
+
const graphics = {
|
|
9248
|
+
circles: [],
|
|
9249
|
+
lines: [],
|
|
9250
|
+
points: [],
|
|
9251
|
+
rects: [],
|
|
9252
|
+
coordinateSystem: "cartesian",
|
|
9253
|
+
title: "Single Route Useless Via Removal Solver"
|
|
9254
|
+
};
|
|
9255
|
+
for (let i = 0; i < this.routeSections.length; i++) {
|
|
9256
|
+
const section = this.routeSections[i];
|
|
9257
|
+
graphics.lines.push({
|
|
9258
|
+
points: section.points,
|
|
9259
|
+
strokeWidth: this.TRACE_THICKNESS,
|
|
9260
|
+
strokeColor: i === this.currentSectionIndex ? "orange" : section.z === 0 ? "red" : "blue"
|
|
9261
|
+
});
|
|
9262
|
+
}
|
|
9263
|
+
return graphics;
|
|
9264
|
+
}
|
|
9265
|
+
};
|
|
9266
|
+
|
|
9267
|
+
// lib/solvers/UselessViaRemovalSolver/UselessViaRemovalSolver.ts
|
|
9268
|
+
var UselessViaRemovalSolver = class extends BaseSolver {
|
|
9269
|
+
constructor(input) {
|
|
9270
|
+
super();
|
|
9271
|
+
this.input = input;
|
|
9272
|
+
this.unsimplifiedHdRoutes = input.unsimplifiedHdRoutes;
|
|
9273
|
+
this.optimizedHdRoutes = [];
|
|
9274
|
+
this.unprocessedRoutes = [...input.unsimplifiedHdRoutes];
|
|
9275
|
+
this.obstacleSHI = new ObstacleSpatialHashIndex(input.obstacles);
|
|
9276
|
+
this.hdRouteSHI = new HighDensityRouteSpatialIndex(
|
|
9277
|
+
this.unsimplifiedHdRoutes
|
|
9278
|
+
);
|
|
9279
|
+
}
|
|
9280
|
+
unsimplifiedHdRoutes;
|
|
9281
|
+
optimizedHdRoutes;
|
|
9282
|
+
unprocessedRoutes;
|
|
9283
|
+
activeSubSolver = null;
|
|
9284
|
+
obstacleSHI = null;
|
|
9285
|
+
hdRouteSHI = null;
|
|
9286
|
+
_step() {
|
|
9287
|
+
if (this.activeSubSolver) {
|
|
9288
|
+
this.activeSubSolver.step();
|
|
9289
|
+
if (this.activeSubSolver.solved) {
|
|
9290
|
+
this.optimizedHdRoutes.push(this.activeSubSolver.getOptimizedHdRoute());
|
|
9291
|
+
this.activeSubSolver = null;
|
|
9292
|
+
} else if (this.activeSubSolver.failed || this.activeSubSolver.error) {
|
|
9293
|
+
this.error = this.activeSubSolver.error;
|
|
9294
|
+
this.failed = true;
|
|
9295
|
+
}
|
|
9296
|
+
return;
|
|
9297
|
+
}
|
|
9298
|
+
const unprocessedRoute = this.unprocessedRoutes.shift();
|
|
9299
|
+
if (!unprocessedRoute) {
|
|
9300
|
+
this.solved = true;
|
|
9301
|
+
return;
|
|
9302
|
+
}
|
|
9303
|
+
this.activeSubSolver = new SingleRouteUselessViaRemovalSolver({
|
|
9304
|
+
hdRouteSHI: this.hdRouteSHI,
|
|
9305
|
+
obstacleSHI: this.obstacleSHI,
|
|
9306
|
+
unsimplifiedRoute: unprocessedRoute
|
|
9307
|
+
});
|
|
9308
|
+
}
|
|
9309
|
+
getOptimizedHdRoutes() {
|
|
9310
|
+
return this.optimizedHdRoutes;
|
|
9311
|
+
}
|
|
9312
|
+
visualize() {
|
|
9313
|
+
const visualization = {
|
|
9314
|
+
lines: [],
|
|
9315
|
+
points: [],
|
|
9316
|
+
rects: [],
|
|
9317
|
+
circles: [],
|
|
9318
|
+
coordinateSystem: "cartesian",
|
|
9319
|
+
title: "Useless Via Removal Solver"
|
|
9320
|
+
};
|
|
9321
|
+
for (const route of this.optimizedHdRoutes) {
|
|
9322
|
+
if (route.route.length === 0) continue;
|
|
9323
|
+
const color = this.input.colorMap[route.connectionName] || "#888888";
|
|
9324
|
+
for (let i = 0; i < route.route.length - 1; i++) {
|
|
9325
|
+
const current = route.route[i];
|
|
9326
|
+
const next = route.route[i + 1];
|
|
9327
|
+
if (current.z === next.z) {
|
|
9328
|
+
visualization.lines.push({
|
|
9329
|
+
points: [
|
|
9330
|
+
{ x: current.x, y: current.y },
|
|
9331
|
+
{ x: next.x, y: next.y }
|
|
9332
|
+
],
|
|
9333
|
+
strokeColor: current.z === 0 ? "red" : "blue",
|
|
9334
|
+
strokeWidth: route.traceThickness,
|
|
9335
|
+
label: `${route.connectionName} (z=${current.z})`
|
|
9336
|
+
});
|
|
9337
|
+
}
|
|
9338
|
+
}
|
|
9339
|
+
for (const via of route.vias) {
|
|
9340
|
+
visualization.circles.push({
|
|
9341
|
+
center: { x: via.x, y: via.y },
|
|
9342
|
+
radius: route.viaDiameter / 2,
|
|
9343
|
+
fill: "rgba(255, 0, 255, 0.5)",
|
|
9344
|
+
label: `${route.connectionName} via`
|
|
9345
|
+
});
|
|
9346
|
+
}
|
|
9347
|
+
}
|
|
9348
|
+
if (this.activeSubSolver) {
|
|
9349
|
+
visualization.lines.push(
|
|
9350
|
+
...this.activeSubSolver.visualize().lines ?? []
|
|
9351
|
+
);
|
|
9352
|
+
}
|
|
9353
|
+
return visualization;
|
|
9354
|
+
}
|
|
9355
|
+
};
|
|
9356
|
+
|
|
8809
9357
|
// lib/solvers/AutoroutingPipelineSolver.ts
|
|
8810
9358
|
function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
|
|
8811
9359
|
return {
|
|
@@ -8851,6 +9399,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
8851
9399
|
highDensityStitchSolver;
|
|
8852
9400
|
singleLayerNodeMerger;
|
|
8853
9401
|
strawSolver;
|
|
9402
|
+
uselessViaRemovalSolver;
|
|
8854
9403
|
multiSimplifiedPathSolver;
|
|
8855
9404
|
startTimeOfPhase;
|
|
8856
9405
|
endTimeOfPhase;
|
|
@@ -9006,12 +9555,24 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9006
9555
|
}
|
|
9007
9556
|
]
|
|
9008
9557
|
),
|
|
9558
|
+
definePipelineStep(
|
|
9559
|
+
"uselessViaRemovalSolver",
|
|
9560
|
+
UselessViaRemovalSolver,
|
|
9561
|
+
(cms) => [
|
|
9562
|
+
{
|
|
9563
|
+
unsimplifiedHdRoutes: cms.highDensityStitchSolver.mergedHdRoutes,
|
|
9564
|
+
obstacles: cms.srj.obstacles,
|
|
9565
|
+
colorMap: cms.colorMap,
|
|
9566
|
+
layerCount: cms.srj.layerCount
|
|
9567
|
+
}
|
|
9568
|
+
]
|
|
9569
|
+
),
|
|
9009
9570
|
definePipelineStep(
|
|
9010
9571
|
"multiSimplifiedPathSolver",
|
|
9011
9572
|
MultiSimplifiedPathSolver,
|
|
9012
9573
|
(cms) => [
|
|
9013
9574
|
{
|
|
9014
|
-
unsimplifiedHdRoutes: cms.highDensityStitchSolver.mergedHdRoutes,
|
|
9575
|
+
unsimplifiedHdRoutes: cms.uselessViaRemovalSolver?.getOptimizedHdRoutes() || cms.highDensityStitchSolver.mergedHdRoutes,
|
|
9015
9576
|
obstacles: cms.srj.obstacles,
|
|
9016
9577
|
connMap: cms.connMap,
|
|
9017
9578
|
colorMap: cms.colorMap
|
|
@@ -9070,6 +9631,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9070
9631
|
const segmentOptimizationViz = this.unravelMultiSectionSolver?.visualize() ?? this.segmentToPointOptimizer?.visualize();
|
|
9071
9632
|
const highDensityViz = this.highDensityRouteSolver?.visualize();
|
|
9072
9633
|
const highDensityStitchViz = this.highDensityStitchSolver?.visualize();
|
|
9634
|
+
const uselessViaRemovalViz = this.uselessViaRemovalSolver?.visualize();
|
|
9073
9635
|
const simplifiedPathSolverViz = this.multiSimplifiedPathSolver?.visualize();
|
|
9074
9636
|
const problemViz = {
|
|
9075
9637
|
points: [
|
|
@@ -9122,6 +9684,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9122
9684
|
segmentOptimizationViz,
|
|
9123
9685
|
highDensityViz ? combineVisualizations(problemViz, highDensityViz) : null,
|
|
9124
9686
|
highDensityStitchViz,
|
|
9687
|
+
uselessViaRemovalViz,
|
|
9125
9688
|
simplifiedPathSolverViz,
|
|
9126
9689
|
this.solved ? combineVisualizations(
|
|
9127
9690
|
problemViz,
|
|
@@ -9184,7 +9747,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9184
9747
|
return match ? match[1] : mstConnectionName;
|
|
9185
9748
|
}
|
|
9186
9749
|
_getOutputHdRoutes() {
|
|
9187
|
-
return this.multiSimplifiedPathSolver?.simplifiedHdRoutes ?? this.highDensityStitchSolver.mergedHdRoutes;
|
|
9750
|
+
return this.multiSimplifiedPathSolver?.simplifiedHdRoutes ?? this.uselessViaRemovalSolver?.getOptimizedHdRoutes() ?? this.highDensityStitchSolver.mergedHdRoutes;
|
|
9188
9751
|
}
|
|
9189
9752
|
/**
|
|
9190
9753
|
* Returns the SimpleRouteJson with routes converted to SimplifiedPcbTraces
|