@tscircuit/capacity-autorouter 0.0.48 → 0.0.50
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 +145 -39
- package/dist/index.js +594 -15
- 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
|
}
|
|
@@ -8358,26 +8358,59 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
|
|
|
8358
8358
|
return;
|
|
8359
8359
|
}
|
|
8360
8360
|
if (layerChangeBtwHeadAndTail && layerChangeAtDistance > 0) {
|
|
8361
|
-
const
|
|
8361
|
+
const indexAfterLayerChange = this.getNearestIndexForDistance(layerChangeAtDistance) + 1;
|
|
8362
|
+
const pointAfterChange = this.inputRoute.route[indexAfterLayerChange];
|
|
8363
|
+
const viaLocation = { x: pointAfterChange.x, y: pointAfterChange.y };
|
|
8362
8364
|
if (this.lastValidPath) {
|
|
8363
8365
|
this.addPathToResult(this.lastValidPath);
|
|
8364
8366
|
this.lastValidPath = null;
|
|
8365
8367
|
}
|
|
8366
|
-
const
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8368
|
+
const lastPointInNewRoute = this.newRoute[this.newRoute.length - 1];
|
|
8369
|
+
if (lastPointInNewRoute.x !== viaLocation.x || lastPointInNewRoute.y !== viaLocation.y) {
|
|
8370
|
+
this.newRoute.push({
|
|
8371
|
+
x: viaLocation.x,
|
|
8372
|
+
y: viaLocation.y,
|
|
8373
|
+
z: lastPointInNewRoute.z
|
|
8374
|
+
// Use the Z of the layer we are leaving
|
|
8375
|
+
});
|
|
8376
|
+
}
|
|
8377
|
+
this.newVias.push(viaLocation);
|
|
8378
|
+
this.newRoute.push({
|
|
8379
|
+
x: viaLocation.x,
|
|
8380
|
+
y: viaLocation.y,
|
|
8381
|
+
z: pointAfterChange.z
|
|
8382
|
+
// Use the Z of the layer we are entering
|
|
8371
8383
|
});
|
|
8372
|
-
this.newRoute.push(pointAfterChange);
|
|
8373
8384
|
this.currentStepSize = this.maxStepSize;
|
|
8374
|
-
|
|
8375
|
-
|
|
8385
|
+
const segmentIndexAfterChange = this.pathSegments.findIndex(
|
|
8386
|
+
(seg) => seg.start === pointAfterChange
|
|
8387
|
+
);
|
|
8388
|
+
if (segmentIndexAfterChange !== -1) {
|
|
8389
|
+
this.tailDistanceAlongPath = this.pathSegments[segmentIndexAfterChange].startDistance;
|
|
8376
8390
|
this.headDistanceAlongPath = this.tailDistanceAlongPath;
|
|
8391
|
+
this.lastValidPath = null;
|
|
8392
|
+
this.lastValidPathHeadDistance = this.tailDistanceAlongPath;
|
|
8393
|
+
} else if (indexAfterLayerChange < this.inputRoute.route.length) {
|
|
8394
|
+
console.warn(
|
|
8395
|
+
"Fallback used for tailDistanceAlongPath after layer change"
|
|
8396
|
+
);
|
|
8397
|
+
const segment = this.pathSegments.find(
|
|
8398
|
+
(seg) => seg.start === this.inputRoute.route[indexAfterLayerChange]
|
|
8399
|
+
);
|
|
8400
|
+
if (segment) {
|
|
8401
|
+
this.tailDistanceAlongPath = segment.startDistance;
|
|
8402
|
+
this.headDistanceAlongPath = this.tailDistanceAlongPath;
|
|
8403
|
+
this.lastValidPath = null;
|
|
8404
|
+
this.lastValidPathHeadDistance = this.tailDistanceAlongPath;
|
|
8405
|
+
} else {
|
|
8406
|
+
console.error(
|
|
8407
|
+
"Could not find segment start after layer change, path might be incomplete."
|
|
8408
|
+
);
|
|
8409
|
+
this.solved = true;
|
|
8410
|
+
}
|
|
8377
8411
|
} else {
|
|
8378
|
-
console.
|
|
8412
|
+
console.warn("Layer change occurred at the end of the path.");
|
|
8379
8413
|
this.solved = true;
|
|
8380
|
-
return;
|
|
8381
8414
|
}
|
|
8382
8415
|
return;
|
|
8383
8416
|
}
|
|
@@ -8773,6 +8806,537 @@ var CapacityMeshEdgeSolver2_NodeTreeOptimization = class extends CapacityMeshEdg
|
|
|
8773
8806
|
}
|
|
8774
8807
|
};
|
|
8775
8808
|
|
|
8809
|
+
// lib/data-structures/HighDensityRouteSpatialIndex.ts
|
|
8810
|
+
var getSegmentBounds2 = (segment) => {
|
|
8811
|
+
return {
|
|
8812
|
+
minX: Math.min(segment[0].x, segment[1].x),
|
|
8813
|
+
maxX: Math.max(segment[0].x, segment[1].x),
|
|
8814
|
+
minY: Math.min(segment[0].y, segment[1].y),
|
|
8815
|
+
maxY: Math.max(segment[0].y, segment[1].y)
|
|
8816
|
+
};
|
|
8817
|
+
};
|
|
8818
|
+
function computeDistSq(p1, p2) {
|
|
8819
|
+
const dx = p1.x - p2.x;
|
|
8820
|
+
const dy = p1.y - p2.y;
|
|
8821
|
+
return dx * dx + dy * dy;
|
|
8822
|
+
}
|
|
8823
|
+
function pointToSegmentDistanceSq(p, a, b) {
|
|
8824
|
+
const l2 = computeDistSq(a, b);
|
|
8825
|
+
if (l2 === 0) return computeDistSq(p, a);
|
|
8826
|
+
let t = ((p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y)) / l2;
|
|
8827
|
+
t = Math.max(0, Math.min(1, t));
|
|
8828
|
+
const projection = {
|
|
8829
|
+
x: a.x + t * (b.x - a.x),
|
|
8830
|
+
y: a.y + t * (b.y - a.y)
|
|
8831
|
+
};
|
|
8832
|
+
return computeDistSq(p, projection);
|
|
8833
|
+
}
|
|
8834
|
+
function segmentToSegmentDistanceSq(a, b, c, d) {
|
|
8835
|
+
if (doSegmentsIntersect(a, b, c, d)) {
|
|
8836
|
+
return 0;
|
|
8837
|
+
}
|
|
8838
|
+
const pA = { x: a.x, y: a.y };
|
|
8839
|
+
const pB = { x: b.x, y: b.y };
|
|
8840
|
+
const pC = { x: c.x, y: c.y };
|
|
8841
|
+
const pD = { x: d.x, y: d.y };
|
|
8842
|
+
return Math.min(
|
|
8843
|
+
pointToSegmentDistanceSq(pA, pC, pD),
|
|
8844
|
+
pointToSegmentDistanceSq(pB, pC, pD),
|
|
8845
|
+
pointToSegmentDistanceSq(pC, pA, pB),
|
|
8846
|
+
pointToSegmentDistanceSq(pD, pA, pB)
|
|
8847
|
+
);
|
|
8848
|
+
}
|
|
8849
|
+
var HighDensityRouteSpatialIndex = class {
|
|
8850
|
+
segmentBuckets;
|
|
8851
|
+
viaBuckets;
|
|
8852
|
+
// New: Store vias
|
|
8853
|
+
routes;
|
|
8854
|
+
CELL_SIZE;
|
|
8855
|
+
constructor(routes, cellSize = 1) {
|
|
8856
|
+
this.segmentBuckets = /* @__PURE__ */ new Map();
|
|
8857
|
+
this.viaBuckets = /* @__PURE__ */ new Map();
|
|
8858
|
+
this.routes = /* @__PURE__ */ new Map();
|
|
8859
|
+
this.CELL_SIZE = cellSize;
|
|
8860
|
+
const epsilon = 1e-9;
|
|
8861
|
+
for (const route of routes) {
|
|
8862
|
+
if (!route || !route.connectionName) {
|
|
8863
|
+
console.warn("Skipping route with missing data:", route);
|
|
8864
|
+
continue;
|
|
8865
|
+
}
|
|
8866
|
+
if (this.routes.has(route.connectionName)) {
|
|
8867
|
+
console.warn(
|
|
8868
|
+
`Skipping duplicate route connectionName: ${route.connectionName}`
|
|
8869
|
+
);
|
|
8870
|
+
continue;
|
|
8871
|
+
}
|
|
8872
|
+
this.routes.set(route.connectionName, route);
|
|
8873
|
+
if (route.route && route.route.length >= 2) {
|
|
8874
|
+
for (let i = 0; i < route.route.length - 1; i++) {
|
|
8875
|
+
const p1 = route.route[i];
|
|
8876
|
+
const p2 = route.route[i + 1];
|
|
8877
|
+
if (p1.x === p2.x && p1.y === p2.y) continue;
|
|
8878
|
+
const segment = [p1, p2];
|
|
8879
|
+
const bounds = getSegmentBounds2(segment);
|
|
8880
|
+
const segmentInfo = {
|
|
8881
|
+
segmentId: `${route.connectionName}-seg-${i}`,
|
|
8882
|
+
segment,
|
|
8883
|
+
parentRoute: route
|
|
8884
|
+
};
|
|
8885
|
+
const minIndexX = Math.floor(bounds.minX / this.CELL_SIZE);
|
|
8886
|
+
const maxIndexX = Math.floor((bounds.maxX + epsilon) / this.CELL_SIZE);
|
|
8887
|
+
const minIndexY = Math.floor(bounds.minY / this.CELL_SIZE);
|
|
8888
|
+
const maxIndexY = Math.floor((bounds.maxY + epsilon) / this.CELL_SIZE);
|
|
8889
|
+
for (let ix = minIndexX; ix <= maxIndexX; ix++) {
|
|
8890
|
+
for (let iy = minIndexY; iy <= maxIndexY; iy++) {
|
|
8891
|
+
const bucketKey = `${ix}x${iy}`;
|
|
8892
|
+
let bucketList = this.segmentBuckets.get(bucketKey);
|
|
8893
|
+
if (!bucketList) {
|
|
8894
|
+
bucketList = [];
|
|
8895
|
+
this.segmentBuckets.set(bucketKey, bucketList);
|
|
8896
|
+
}
|
|
8897
|
+
bucketList.push(segmentInfo);
|
|
8898
|
+
}
|
|
8899
|
+
}
|
|
8900
|
+
}
|
|
8901
|
+
}
|
|
8902
|
+
if (route.vias && route.vias.length > 0) {
|
|
8903
|
+
for (let i = 0; i < route.vias.length; i++) {
|
|
8904
|
+
const via = route.vias[i];
|
|
8905
|
+
if (via === void 0 || via === null) continue;
|
|
8906
|
+
const storedVia = {
|
|
8907
|
+
viaId: `${route.connectionName}-via-${i}`,
|
|
8908
|
+
x: via.x,
|
|
8909
|
+
y: via.y,
|
|
8910
|
+
parentRoute: route
|
|
8911
|
+
};
|
|
8912
|
+
const ix = Math.floor(via.x / this.CELL_SIZE);
|
|
8913
|
+
const iy = Math.floor(via.y / this.CELL_SIZE);
|
|
8914
|
+
const bucketKey = `${ix}x${iy}`;
|
|
8915
|
+
let bucketList = this.viaBuckets.get(bucketKey);
|
|
8916
|
+
if (!bucketList) {
|
|
8917
|
+
bucketList = [];
|
|
8918
|
+
this.viaBuckets.set(bucketKey, bucketList);
|
|
8919
|
+
}
|
|
8920
|
+
bucketList.push(storedVia);
|
|
8921
|
+
}
|
|
8922
|
+
}
|
|
8923
|
+
}
|
|
8924
|
+
}
|
|
8925
|
+
/**
|
|
8926
|
+
* Finds routes that potentially conflict with a given line segment within a margin.
|
|
8927
|
+
* Checks both segments and vias.
|
|
8928
|
+
* @param segmentStart Start point of the query segment.
|
|
8929
|
+
* @param segmentEnd End point of the query segment.
|
|
8930
|
+
* @param margin The minimum required clearance distance from the query segment's centerline.
|
|
8931
|
+
* @returns An array of conflicting routes and their minimum distance to the segment.
|
|
8932
|
+
*/
|
|
8933
|
+
getConflictingRoutesForSegment(segmentStart, segmentEnd, margin) {
|
|
8934
|
+
const querySegment = [segmentStart, segmentEnd];
|
|
8935
|
+
const bounds = getSegmentBounds2(querySegment);
|
|
8936
|
+
const searchMinX = bounds.minX - margin;
|
|
8937
|
+
const searchMinY = bounds.minY - margin;
|
|
8938
|
+
const searchMaxX = bounds.maxX + margin;
|
|
8939
|
+
const searchMaxY = bounds.maxY + margin;
|
|
8940
|
+
const epsilon = 1e-9;
|
|
8941
|
+
const minIndexX = Math.floor(searchMinX / this.CELL_SIZE);
|
|
8942
|
+
const maxIndexX = Math.floor((searchMaxX + epsilon) / this.CELL_SIZE);
|
|
8943
|
+
const minIndexY = Math.floor(searchMinY / this.CELL_SIZE);
|
|
8944
|
+
const maxIndexY = Math.floor((searchMaxY + epsilon) / this.CELL_SIZE);
|
|
8945
|
+
const conflictingRouteData = /* @__PURE__ */ new Map();
|
|
8946
|
+
const checkedSegments = /* @__PURE__ */ new Set();
|
|
8947
|
+
const checkedVias = /* @__PURE__ */ new Set();
|
|
8948
|
+
const queryP1 = { x: segmentStart.x, y: segmentStart.y };
|
|
8949
|
+
const queryP2 = { x: segmentEnd.x, y: segmentEnd.y };
|
|
8950
|
+
for (let ix = minIndexX; ix <= maxIndexX; ix++) {
|
|
8951
|
+
for (let iy = minIndexY; iy <= maxIndexY; iy++) {
|
|
8952
|
+
const bucketKey = `${ix}x${iy}`;
|
|
8953
|
+
const segmentBucketList = this.segmentBuckets.get(bucketKey);
|
|
8954
|
+
if (segmentBucketList) {
|
|
8955
|
+
for (const segmentInfo of segmentBucketList) {
|
|
8956
|
+
if (checkedSegments.has(segmentInfo.segmentId)) continue;
|
|
8957
|
+
checkedSegments.add(segmentInfo.segmentId);
|
|
8958
|
+
const route = segmentInfo.parentRoute;
|
|
8959
|
+
const [p1, p2] = segmentInfo.segment;
|
|
8960
|
+
const requiredSeparation = margin + route.traceThickness / 2;
|
|
8961
|
+
const requiredSeparationSq = requiredSeparation * requiredSeparation;
|
|
8962
|
+
const distSq = segmentToSegmentDistanceSq(
|
|
8963
|
+
segmentStart,
|
|
8964
|
+
segmentEnd,
|
|
8965
|
+
p1,
|
|
8966
|
+
p2
|
|
8967
|
+
);
|
|
8968
|
+
if (distSq < requiredSeparationSq) {
|
|
8969
|
+
const routeName = route.connectionName;
|
|
8970
|
+
const existing = conflictingRouteData.get(routeName);
|
|
8971
|
+
if (!existing || distSq < existing.minDistSq) {
|
|
8972
|
+
conflictingRouteData.set(routeName, {
|
|
8973
|
+
route,
|
|
8974
|
+
minDistSq: distSq
|
|
8975
|
+
});
|
|
8976
|
+
}
|
|
8977
|
+
}
|
|
8978
|
+
}
|
|
8979
|
+
}
|
|
8980
|
+
const viaBucketList = this.viaBuckets.get(bucketKey);
|
|
8981
|
+
if (viaBucketList) {
|
|
8982
|
+
for (const viaInfo of viaBucketList) {
|
|
8983
|
+
if (checkedVias.has(viaInfo.viaId)) continue;
|
|
8984
|
+
checkedVias.add(viaInfo.viaId);
|
|
8985
|
+
const route = viaInfo.parentRoute;
|
|
8986
|
+
const viaPoint = { x: viaInfo.x, y: viaInfo.y };
|
|
8987
|
+
const requiredSeparation = margin + route.viaDiameter / 2;
|
|
8988
|
+
const requiredSeparationSq = requiredSeparation * requiredSeparation;
|
|
8989
|
+
const distSq = pointToSegmentDistanceSq(viaPoint, queryP1, queryP2);
|
|
8990
|
+
if (distSq < requiredSeparationSq) {
|
|
8991
|
+
const routeName = route.connectionName;
|
|
8992
|
+
const existing = conflictingRouteData.get(routeName);
|
|
8993
|
+
if (!existing || distSq < existing.minDistSq) {
|
|
8994
|
+
conflictingRouteData.set(routeName, {
|
|
8995
|
+
route,
|
|
8996
|
+
minDistSq: distSq
|
|
8997
|
+
});
|
|
8998
|
+
}
|
|
8999
|
+
}
|
|
9000
|
+
}
|
|
9001
|
+
}
|
|
9002
|
+
}
|
|
9003
|
+
}
|
|
9004
|
+
const results = [];
|
|
9005
|
+
for (const data of conflictingRouteData.values()) {
|
|
9006
|
+
results.push({
|
|
9007
|
+
conflictingRoute: data.route,
|
|
9008
|
+
distance: Math.sqrt(data.minDistSq)
|
|
9009
|
+
});
|
|
9010
|
+
}
|
|
9011
|
+
return results;
|
|
9012
|
+
}
|
|
9013
|
+
/**
|
|
9014
|
+
* Finds routes that pass near a given point within a margin.
|
|
9015
|
+
* Checks both segments and vias.
|
|
9016
|
+
* @param point The query point {x, y}. Z is ignored.
|
|
9017
|
+
* @param margin The minimum required clearance distance from the query point.
|
|
9018
|
+
* @returns An array of conflicting routes and their minimum distance to the point.
|
|
9019
|
+
*/
|
|
9020
|
+
getConflictingRoutesNearPoint(point, margin) {
|
|
9021
|
+
const searchMinX = point.x - margin;
|
|
9022
|
+
const searchMinY = point.y - margin;
|
|
9023
|
+
const searchMaxX = point.x + margin;
|
|
9024
|
+
const searchMaxY = point.y + margin;
|
|
9025
|
+
const epsilon = 1e-9;
|
|
9026
|
+
const minIndexX = Math.floor(searchMinX / this.CELL_SIZE);
|
|
9027
|
+
const maxIndexX = Math.floor((searchMaxX + epsilon) / this.CELL_SIZE);
|
|
9028
|
+
const minIndexY = Math.floor(searchMinY / this.CELL_SIZE);
|
|
9029
|
+
const maxIndexY = Math.floor((searchMaxY + epsilon) / this.CELL_SIZE);
|
|
9030
|
+
const conflictingRouteData = /* @__PURE__ */ new Map();
|
|
9031
|
+
const checkedSegments = /* @__PURE__ */ new Set();
|
|
9032
|
+
const checkedVias = /* @__PURE__ */ new Set();
|
|
9033
|
+
for (let ix = minIndexX; ix <= maxIndexX; ix++) {
|
|
9034
|
+
for (let iy = minIndexY; iy <= maxIndexY; iy++) {
|
|
9035
|
+
const bucketKey = `${ix}x${iy}`;
|
|
9036
|
+
const segmentBucketList = this.segmentBuckets.get(bucketKey);
|
|
9037
|
+
if (segmentBucketList) {
|
|
9038
|
+
for (const segmentInfo of segmentBucketList) {
|
|
9039
|
+
if (checkedSegments.has(segmentInfo.segmentId)) continue;
|
|
9040
|
+
checkedSegments.add(segmentInfo.segmentId);
|
|
9041
|
+
const route = segmentInfo.parentRoute;
|
|
9042
|
+
const p1 = {
|
|
9043
|
+
x: segmentInfo.segment[0].x,
|
|
9044
|
+
y: segmentInfo.segment[0].y
|
|
9045
|
+
};
|
|
9046
|
+
const p2 = {
|
|
9047
|
+
x: segmentInfo.segment[1].x,
|
|
9048
|
+
y: segmentInfo.segment[1].y
|
|
9049
|
+
};
|
|
9050
|
+
const requiredSeparation = margin + route.traceThickness / 2;
|
|
9051
|
+
const requiredSeparationSq = requiredSeparation * requiredSeparation;
|
|
9052
|
+
const distSq = pointToSegmentDistanceSq(point, p1, p2);
|
|
9053
|
+
if (distSq < requiredSeparationSq) {
|
|
9054
|
+
const routeName = route.connectionName;
|
|
9055
|
+
const existing = conflictingRouteData.get(routeName);
|
|
9056
|
+
if (!existing || distSq < existing.minDistSq) {
|
|
9057
|
+
conflictingRouteData.set(routeName, {
|
|
9058
|
+
route,
|
|
9059
|
+
minDistSq: distSq
|
|
9060
|
+
});
|
|
9061
|
+
}
|
|
9062
|
+
}
|
|
9063
|
+
}
|
|
9064
|
+
}
|
|
9065
|
+
const viaBucketList = this.viaBuckets.get(bucketKey);
|
|
9066
|
+
if (viaBucketList) {
|
|
9067
|
+
for (const viaInfo of viaBucketList) {
|
|
9068
|
+
if (checkedVias.has(viaInfo.viaId)) continue;
|
|
9069
|
+
checkedVias.add(viaInfo.viaId);
|
|
9070
|
+
const route = viaInfo.parentRoute;
|
|
9071
|
+
const viaPoint = { x: viaInfo.x, y: viaInfo.y };
|
|
9072
|
+
const requiredSeparation = margin + route.viaDiameter / 2;
|
|
9073
|
+
const requiredSeparationSq = requiredSeparation * requiredSeparation;
|
|
9074
|
+
const distSq = computeDistSq(point, viaPoint);
|
|
9075
|
+
if (distSq < requiredSeparationSq) {
|
|
9076
|
+
const routeName = route.connectionName;
|
|
9077
|
+
const existing = conflictingRouteData.get(routeName);
|
|
9078
|
+
if (!existing || distSq < existing.minDistSq) {
|
|
9079
|
+
conflictingRouteData.set(routeName, {
|
|
9080
|
+
route,
|
|
9081
|
+
minDistSq: distSq
|
|
9082
|
+
});
|
|
9083
|
+
}
|
|
9084
|
+
}
|
|
9085
|
+
}
|
|
9086
|
+
}
|
|
9087
|
+
}
|
|
9088
|
+
}
|
|
9089
|
+
const results = [];
|
|
9090
|
+
for (const data of conflictingRouteData.values()) {
|
|
9091
|
+
results.push({
|
|
9092
|
+
conflictingRoute: data.route,
|
|
9093
|
+
distance: Math.sqrt(data.minDistSq)
|
|
9094
|
+
});
|
|
9095
|
+
}
|
|
9096
|
+
return results;
|
|
9097
|
+
}
|
|
9098
|
+
};
|
|
9099
|
+
|
|
9100
|
+
// lib/solvers/UselessViaRemovalSolver/SingleRouteUselessViaRemovalSolver.ts
|
|
9101
|
+
var SingleRouteUselessViaRemovalSolver = class extends BaseSolver {
|
|
9102
|
+
obstacleSHI;
|
|
9103
|
+
hdRouteSHI;
|
|
9104
|
+
unsimplifiedRoute;
|
|
9105
|
+
routeSections;
|
|
9106
|
+
currentSectionIndex;
|
|
9107
|
+
TRACE_THICKNESS = 0.15;
|
|
9108
|
+
OBSTACLE_MARGIN = 0.1;
|
|
9109
|
+
constructor(params) {
|
|
9110
|
+
super();
|
|
9111
|
+
this.currentSectionIndex = 1;
|
|
9112
|
+
this.obstacleSHI = params.obstacleSHI;
|
|
9113
|
+
this.hdRouteSHI = params.hdRouteSHI;
|
|
9114
|
+
this.unsimplifiedRoute = params.unsimplifiedRoute;
|
|
9115
|
+
this.routeSections = this.breakRouteIntoSections(this.unsimplifiedRoute);
|
|
9116
|
+
}
|
|
9117
|
+
breakRouteIntoSections(route) {
|
|
9118
|
+
const routeSections = [];
|
|
9119
|
+
const routePoints = route.route;
|
|
9120
|
+
if (routePoints.length === 0) return [];
|
|
9121
|
+
let currentSection = {
|
|
9122
|
+
startIndex: 0,
|
|
9123
|
+
endIndex: -1,
|
|
9124
|
+
z: routePoints[0].z,
|
|
9125
|
+
points: [routePoints[0]]
|
|
9126
|
+
};
|
|
9127
|
+
for (let i = 1; i < routePoints.length; i++) {
|
|
9128
|
+
if (routePoints[i].z === currentSection.z) {
|
|
9129
|
+
currentSection.points.push(routePoints[i]);
|
|
9130
|
+
} else {
|
|
9131
|
+
currentSection.endIndex = i - 1;
|
|
9132
|
+
routeSections.push(currentSection);
|
|
9133
|
+
currentSection = {
|
|
9134
|
+
startIndex: i,
|
|
9135
|
+
endIndex: -1,
|
|
9136
|
+
z: routePoints[i].z,
|
|
9137
|
+
points: [routePoints[i]]
|
|
9138
|
+
};
|
|
9139
|
+
}
|
|
9140
|
+
}
|
|
9141
|
+
currentSection.endIndex = routePoints.length - 1;
|
|
9142
|
+
routeSections.push(currentSection);
|
|
9143
|
+
return routeSections;
|
|
9144
|
+
}
|
|
9145
|
+
_step() {
|
|
9146
|
+
if (this.currentSectionIndex >= this.routeSections.length - 1) {
|
|
9147
|
+
this.solved = true;
|
|
9148
|
+
return;
|
|
9149
|
+
}
|
|
9150
|
+
const prevSection = this.routeSections[this.currentSectionIndex - 1];
|
|
9151
|
+
const currentSection = this.routeSections[this.currentSectionIndex];
|
|
9152
|
+
const nextSection = this.routeSections[this.currentSectionIndex + 1];
|
|
9153
|
+
if (prevSection.z !== nextSection.z) {
|
|
9154
|
+
this.currentSectionIndex++;
|
|
9155
|
+
return;
|
|
9156
|
+
}
|
|
9157
|
+
const targetZ = prevSection.z;
|
|
9158
|
+
if (this.canSectionMoveToLayer({ currentSection, targetZ })) {
|
|
9159
|
+
currentSection.z = targetZ;
|
|
9160
|
+
currentSection.points = currentSection.points.map((p) => ({
|
|
9161
|
+
...p,
|
|
9162
|
+
z: targetZ
|
|
9163
|
+
}));
|
|
9164
|
+
this.currentSectionIndex += 2;
|
|
9165
|
+
return;
|
|
9166
|
+
}
|
|
9167
|
+
this.currentSectionIndex++;
|
|
9168
|
+
return;
|
|
9169
|
+
}
|
|
9170
|
+
canSectionMoveToLayer({
|
|
9171
|
+
currentSection,
|
|
9172
|
+
targetZ
|
|
9173
|
+
}) {
|
|
9174
|
+
for (let i = 0; i < currentSection.points.length - 1; i++) {
|
|
9175
|
+
const A = { ...currentSection.points[i], z: targetZ };
|
|
9176
|
+
const B = { ...currentSection.points[i + 1], z: targetZ };
|
|
9177
|
+
const conflictingRoutes = this.hdRouteSHI.getConflictingRoutesForSegment(
|
|
9178
|
+
A,
|
|
9179
|
+
B,
|
|
9180
|
+
this.TRACE_THICKNESS
|
|
9181
|
+
);
|
|
9182
|
+
for (const { conflictingRoute, distance: distance6 } of conflictingRoutes) {
|
|
9183
|
+
if (conflictingRoute.connectionName === this.unsimplifiedRoute.connectionName)
|
|
9184
|
+
continue;
|
|
9185
|
+
if (distance6 < this.TRACE_THICKNESS + conflictingRoute.traceThickness) {
|
|
9186
|
+
return false;
|
|
9187
|
+
}
|
|
9188
|
+
}
|
|
9189
|
+
const segmentBox = {
|
|
9190
|
+
centerX: (A.x + B.x) / 2,
|
|
9191
|
+
centerY: (A.y + B.y) / 2,
|
|
9192
|
+
width: Math.abs(A.x - B.x),
|
|
9193
|
+
height: Math.abs(A.y - B.y)
|
|
9194
|
+
};
|
|
9195
|
+
const obstacles = this.obstacleSHI.getNodesInArea(
|
|
9196
|
+
segmentBox.centerX,
|
|
9197
|
+
segmentBox.centerY,
|
|
9198
|
+
segmentBox.width,
|
|
9199
|
+
segmentBox.height
|
|
9200
|
+
);
|
|
9201
|
+
for (const obstacle of obstacles) {
|
|
9202
|
+
const distToObstacle = segmentToBoxMinDistance(A, B, obstacle);
|
|
9203
|
+
if (distToObstacle < this.TRACE_THICKNESS + this.OBSTACLE_MARGIN) {
|
|
9204
|
+
return false;
|
|
9205
|
+
}
|
|
9206
|
+
}
|
|
9207
|
+
}
|
|
9208
|
+
return true;
|
|
9209
|
+
}
|
|
9210
|
+
getOptimizedHdRoute() {
|
|
9211
|
+
const route = this.routeSections.flatMap((section) => section.points);
|
|
9212
|
+
const vias = [];
|
|
9213
|
+
for (let i = 0; i < route.length - 1; i++) {
|
|
9214
|
+
if (route[i].z !== route[i + 1].z) {
|
|
9215
|
+
vias.push({
|
|
9216
|
+
x: route[i].x,
|
|
9217
|
+
y: route[i].y
|
|
9218
|
+
});
|
|
9219
|
+
}
|
|
9220
|
+
}
|
|
9221
|
+
return {
|
|
9222
|
+
connectionName: this.unsimplifiedRoute.connectionName,
|
|
9223
|
+
route,
|
|
9224
|
+
traceThickness: this.unsimplifiedRoute.traceThickness,
|
|
9225
|
+
vias,
|
|
9226
|
+
viaDiameter: this.unsimplifiedRoute.viaDiameter
|
|
9227
|
+
};
|
|
9228
|
+
}
|
|
9229
|
+
visualize() {
|
|
9230
|
+
const graphics = {
|
|
9231
|
+
circles: [],
|
|
9232
|
+
lines: [],
|
|
9233
|
+
points: [],
|
|
9234
|
+
rects: [],
|
|
9235
|
+
coordinateSystem: "cartesian",
|
|
9236
|
+
title: "Single Route Useless Via Removal Solver"
|
|
9237
|
+
};
|
|
9238
|
+
for (let i = 0; i < this.routeSections.length; i++) {
|
|
9239
|
+
const section = this.routeSections[i];
|
|
9240
|
+
graphics.lines.push({
|
|
9241
|
+
points: section.points,
|
|
9242
|
+
strokeWidth: this.TRACE_THICKNESS,
|
|
9243
|
+
strokeColor: i === this.currentSectionIndex ? "orange" : section.z === 0 ? "red" : "blue"
|
|
9244
|
+
});
|
|
9245
|
+
}
|
|
9246
|
+
return graphics;
|
|
9247
|
+
}
|
|
9248
|
+
};
|
|
9249
|
+
|
|
9250
|
+
// lib/solvers/UselessViaRemovalSolver/UselessViaRemovalSolver.ts
|
|
9251
|
+
var UselessViaRemovalSolver = class extends BaseSolver {
|
|
9252
|
+
constructor(input) {
|
|
9253
|
+
super();
|
|
9254
|
+
this.input = input;
|
|
9255
|
+
this.unsimplifiedHdRoutes = input.unsimplifiedHdRoutes;
|
|
9256
|
+
this.optimizedHdRoutes = [];
|
|
9257
|
+
this.unprocessedRoutes = [...input.unsimplifiedHdRoutes];
|
|
9258
|
+
this.obstacleSHI = new ObstacleSpatialHashIndex(input.obstacles);
|
|
9259
|
+
this.hdRouteSHI = new HighDensityRouteSpatialIndex(
|
|
9260
|
+
this.unsimplifiedHdRoutes
|
|
9261
|
+
);
|
|
9262
|
+
}
|
|
9263
|
+
unsimplifiedHdRoutes;
|
|
9264
|
+
optimizedHdRoutes;
|
|
9265
|
+
unprocessedRoutes;
|
|
9266
|
+
activeSubSolver = null;
|
|
9267
|
+
obstacleSHI = null;
|
|
9268
|
+
hdRouteSHI = null;
|
|
9269
|
+
_step() {
|
|
9270
|
+
if (this.activeSubSolver) {
|
|
9271
|
+
this.activeSubSolver.step();
|
|
9272
|
+
if (this.activeSubSolver.solved) {
|
|
9273
|
+
this.optimizedHdRoutes.push(this.activeSubSolver.getOptimizedHdRoute());
|
|
9274
|
+
this.activeSubSolver = null;
|
|
9275
|
+
} else if (this.activeSubSolver.failed || this.activeSubSolver.error) {
|
|
9276
|
+
this.error = this.activeSubSolver.error;
|
|
9277
|
+
this.failed = true;
|
|
9278
|
+
}
|
|
9279
|
+
return;
|
|
9280
|
+
}
|
|
9281
|
+
const unprocessedRoute = this.unprocessedRoutes.shift();
|
|
9282
|
+
if (!unprocessedRoute) {
|
|
9283
|
+
this.solved = true;
|
|
9284
|
+
return;
|
|
9285
|
+
}
|
|
9286
|
+
this.activeSubSolver = new SingleRouteUselessViaRemovalSolver({
|
|
9287
|
+
hdRouteSHI: this.hdRouteSHI,
|
|
9288
|
+
obstacleSHI: this.obstacleSHI,
|
|
9289
|
+
unsimplifiedRoute: unprocessedRoute
|
|
9290
|
+
});
|
|
9291
|
+
}
|
|
9292
|
+
getOptimizedHdRoutes() {
|
|
9293
|
+
return this.optimizedHdRoutes;
|
|
9294
|
+
}
|
|
9295
|
+
visualize() {
|
|
9296
|
+
const visualization = {
|
|
9297
|
+
lines: [],
|
|
9298
|
+
points: [],
|
|
9299
|
+
rects: [],
|
|
9300
|
+
circles: [],
|
|
9301
|
+
coordinateSystem: "cartesian",
|
|
9302
|
+
title: "Useless Via Removal Solver"
|
|
9303
|
+
};
|
|
9304
|
+
for (const route of this.optimizedHdRoutes) {
|
|
9305
|
+
if (route.route.length === 0) continue;
|
|
9306
|
+
const color = this.input.colorMap[route.connectionName] || "#888888";
|
|
9307
|
+
for (let i = 0; i < route.route.length - 1; i++) {
|
|
9308
|
+
const current = route.route[i];
|
|
9309
|
+
const next = route.route[i + 1];
|
|
9310
|
+
if (current.z === next.z) {
|
|
9311
|
+
visualization.lines.push({
|
|
9312
|
+
points: [
|
|
9313
|
+
{ x: current.x, y: current.y },
|
|
9314
|
+
{ x: next.x, y: next.y }
|
|
9315
|
+
],
|
|
9316
|
+
strokeColor: current.z === 0 ? "red" : "blue",
|
|
9317
|
+
strokeWidth: route.traceThickness,
|
|
9318
|
+
label: `${route.connectionName} (z=${current.z})`
|
|
9319
|
+
});
|
|
9320
|
+
}
|
|
9321
|
+
}
|
|
9322
|
+
for (const via of route.vias) {
|
|
9323
|
+
visualization.circles.push({
|
|
9324
|
+
center: { x: via.x, y: via.y },
|
|
9325
|
+
radius: route.viaDiameter / 2,
|
|
9326
|
+
fill: "rgba(255, 0, 255, 0.5)",
|
|
9327
|
+
label: `${route.connectionName} via`
|
|
9328
|
+
});
|
|
9329
|
+
}
|
|
9330
|
+
}
|
|
9331
|
+
if (this.activeSubSolver) {
|
|
9332
|
+
visualization.lines.push(
|
|
9333
|
+
...this.activeSubSolver.visualize().lines ?? []
|
|
9334
|
+
);
|
|
9335
|
+
}
|
|
9336
|
+
return visualization;
|
|
9337
|
+
}
|
|
9338
|
+
};
|
|
9339
|
+
|
|
8776
9340
|
// lib/solvers/AutoroutingPipelineSolver.ts
|
|
8777
9341
|
function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
|
|
8778
9342
|
return {
|
|
@@ -8818,6 +9382,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
8818
9382
|
highDensityStitchSolver;
|
|
8819
9383
|
singleLayerNodeMerger;
|
|
8820
9384
|
strawSolver;
|
|
9385
|
+
uselessViaRemovalSolver;
|
|
8821
9386
|
multiSimplifiedPathSolver;
|
|
8822
9387
|
startTimeOfPhase;
|
|
8823
9388
|
endTimeOfPhase;
|
|
@@ -8973,12 +9538,24 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
8973
9538
|
}
|
|
8974
9539
|
]
|
|
8975
9540
|
),
|
|
9541
|
+
definePipelineStep(
|
|
9542
|
+
"uselessViaRemovalSolver",
|
|
9543
|
+
UselessViaRemovalSolver,
|
|
9544
|
+
(cms) => [
|
|
9545
|
+
{
|
|
9546
|
+
unsimplifiedHdRoutes: cms.highDensityStitchSolver.mergedHdRoutes,
|
|
9547
|
+
obstacles: cms.srj.obstacles,
|
|
9548
|
+
colorMap: cms.colorMap,
|
|
9549
|
+
layerCount: cms.srj.layerCount
|
|
9550
|
+
}
|
|
9551
|
+
]
|
|
9552
|
+
),
|
|
8976
9553
|
definePipelineStep(
|
|
8977
9554
|
"multiSimplifiedPathSolver",
|
|
8978
9555
|
MultiSimplifiedPathSolver,
|
|
8979
9556
|
(cms) => [
|
|
8980
9557
|
{
|
|
8981
|
-
unsimplifiedHdRoutes: cms.highDensityStitchSolver.mergedHdRoutes,
|
|
9558
|
+
unsimplifiedHdRoutes: cms.uselessViaRemovalSolver?.getOptimizedHdRoutes() || cms.highDensityStitchSolver.mergedHdRoutes,
|
|
8982
9559
|
obstacles: cms.srj.obstacles,
|
|
8983
9560
|
connMap: cms.connMap,
|
|
8984
9561
|
colorMap: cms.colorMap
|
|
@@ -9037,6 +9614,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9037
9614
|
const segmentOptimizationViz = this.unravelMultiSectionSolver?.visualize() ?? this.segmentToPointOptimizer?.visualize();
|
|
9038
9615
|
const highDensityViz = this.highDensityRouteSolver?.visualize();
|
|
9039
9616
|
const highDensityStitchViz = this.highDensityStitchSolver?.visualize();
|
|
9617
|
+
const uselessViaRemovalViz = this.uselessViaRemovalSolver?.visualize();
|
|
9040
9618
|
const simplifiedPathSolverViz = this.multiSimplifiedPathSolver?.visualize();
|
|
9041
9619
|
const problemViz = {
|
|
9042
9620
|
points: [
|
|
@@ -9089,6 +9667,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9089
9667
|
segmentOptimizationViz,
|
|
9090
9668
|
highDensityViz ? combineVisualizations(problemViz, highDensityViz) : null,
|
|
9091
9669
|
highDensityStitchViz,
|
|
9670
|
+
uselessViaRemovalViz,
|
|
9092
9671
|
simplifiedPathSolverViz,
|
|
9093
9672
|
this.solved ? combineVisualizations(
|
|
9094
9673
|
problemViz,
|
|
@@ -9151,7 +9730,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9151
9730
|
return match ? match[1] : mstConnectionName;
|
|
9152
9731
|
}
|
|
9153
9732
|
_getOutputHdRoutes() {
|
|
9154
|
-
return this.multiSimplifiedPathSolver?.simplifiedHdRoutes ?? this.highDensityStitchSolver.mergedHdRoutes;
|
|
9733
|
+
return this.multiSimplifiedPathSolver?.simplifiedHdRoutes ?? this.uselessViaRemovalSolver?.getOptimizedHdRoutes() ?? this.highDensityStitchSolver.mergedHdRoutes;
|
|
9155
9734
|
}
|
|
9156
9735
|
/**
|
|
9157
9736
|
* Returns the SimpleRouteJson with routes converted to SimplifiedPcbTraces
|