@tscircuit/schematic-trace-solver 0.0.44 → 0.0.46
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/.github/workflows/bun-formatcheck.yml +1 -1
- package/.github/workflows/bun-pver-release.yml +1 -1
- package/.github/workflows/bun-test.yml +1 -1
- package/.github/workflows/bun-typecheck.yml +1 -1
- package/dist/index.d.ts +43 -8
- package/dist/index.js +1109 -185
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +0 -1
- package/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +83 -36
- package/lib/solvers/TraceCleanupSolver/hasCollisions.ts +16 -4
- package/lib/solvers/TraceCleanupSolver/is4PointRectangle.ts +17 -0
- package/lib/solvers/TraceCleanupSolver/isSegmentAnEndpointSegment.ts +36 -0
- package/lib/solvers/TraceCleanupSolver/mergeGraphicsObjects.ts +28 -0
- package/lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +18 -1
- package/lib/solvers/TraceCleanupSolver/recognizeStairStepPattern.ts +56 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/UntangleTraceSubsolver.ts +370 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/findAllLShapedTurns.ts +48 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/findIntersectionsWithObstacles.ts +36 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts +107 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/generateRectangleCandidates.ts +55 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/getTraceObstacles.ts +25 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/isPathColliding.ts +58 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeCandidates.ts +33 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeCollision.ts +20 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeIntersectionPoints.ts +26 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeLSapes.ts +33 -0
- package/lib/solvers/TraceCleanupSolver/turnMinimization.ts +50 -56
- package/lib/solvers/TraceCleanupSolver/visualizeTightRectangle.ts +24 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +17 -3
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts +26 -11
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts +95 -75
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/filterLabelsAtTraceEdges.ts +74 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/groupLabelsByChipAndOrientation.ts +45 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/mergeLabelGroup.ts +71 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +150 -27
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/isPointInsideLabel.ts +23 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/visualizeDecomposition.ts +54 -0
- package/package.json +1 -1
- package/tests/examples/__snapshots__/example29.snap.svg +6 -6
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +8 -8
package/dist/index.js
CHANGED
|
@@ -271,19 +271,19 @@ var doesPairCrossRestrictedCenterLines = (params) => {
|
|
|
271
271
|
chipMap
|
|
272
272
|
});
|
|
273
273
|
if (restrictedCenterLines.size === 0) return false;
|
|
274
|
-
const
|
|
274
|
+
const EPS5 = 1e-9;
|
|
275
275
|
const crossesSegment = (a, b) => {
|
|
276
276
|
for (const [, rcl] of restrictedCenterLines) {
|
|
277
277
|
if (rcl.axes.has("x") && typeof rcl.x === "number") {
|
|
278
|
-
if ((a.x - rcl.x) * (b.x - rcl.x) < -
|
|
278
|
+
if ((a.x - rcl.x) * (b.x - rcl.x) < -EPS5) return true;
|
|
279
279
|
}
|
|
280
280
|
if (rcl.axes.has("y") && typeof rcl.y === "number") {
|
|
281
|
-
if ((a.y - rcl.y) * (b.y - rcl.y) < -
|
|
281
|
+
if ((a.y - rcl.y) * (b.y - rcl.y) < -EPS5) return true;
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
return false;
|
|
285
285
|
};
|
|
286
|
-
if (Math.abs(p1.x - p2.x) <
|
|
286
|
+
if (Math.abs(p1.x - p2.x) < EPS5 || Math.abs(p1.y - p2.y) < EPS5) {
|
|
287
287
|
return crossesSegment({ x: p1.x, y: p1.y }, { x: p2.x, y: p2.y });
|
|
288
288
|
}
|
|
289
289
|
const elbowHV = { x: p2.x, y: p1.y };
|
|
@@ -810,8 +810,8 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
|
|
|
810
810
|
if (!collision) {
|
|
811
811
|
const first = path[0];
|
|
812
812
|
const last = path[path.length - 1];
|
|
813
|
-
const
|
|
814
|
-
const samePoint = (p, q) => Math.abs(p.x - q.x) <
|
|
813
|
+
const EPS5 = 1e-9;
|
|
814
|
+
const samePoint = (p, q) => Math.abs(p.x - q.x) < EPS5 && Math.abs(p.y - q.y) < EPS5;
|
|
815
815
|
if (samePoint(first, { x: PA.x, y: PA.y }) && samePoint(last, { x: PB.x, y: PB.y })) {
|
|
816
816
|
this.solvedTracePath = path;
|
|
817
817
|
this.solved = true;
|
|
@@ -1010,17 +1010,17 @@ var applyJogToTerminalSegment = ({
|
|
|
1010
1010
|
segmentIndex: si,
|
|
1011
1011
|
offset,
|
|
1012
1012
|
JOG_SIZE,
|
|
1013
|
-
EPS:
|
|
1013
|
+
EPS: EPS5 = 1e-6
|
|
1014
1014
|
}) => {
|
|
1015
1015
|
if (si !== 0 && si !== pts.length - 2) return;
|
|
1016
1016
|
const start = pts[si];
|
|
1017
1017
|
const end = pts[si + 1];
|
|
1018
|
-
const
|
|
1019
|
-
const isHorizontal2 = Math.abs(start.y - end.y) <
|
|
1020
|
-
if (!
|
|
1021
|
-
const segDir =
|
|
1018
|
+
const isVertical3 = Math.abs(start.x - end.x) < EPS5;
|
|
1019
|
+
const isHorizontal2 = Math.abs(start.y - end.y) < EPS5;
|
|
1020
|
+
if (!isVertical3 && !isHorizontal2) return;
|
|
1021
|
+
const segDir = isVertical3 ? end.y > start.y ? 1 : -1 : end.x > start.x ? 1 : -1;
|
|
1022
1022
|
if (si === 0) {
|
|
1023
|
-
if (
|
|
1023
|
+
if (isVertical3) {
|
|
1024
1024
|
const jogY = start.y + segDir * JOG_SIZE;
|
|
1025
1025
|
pts.splice(
|
|
1026
1026
|
1,
|
|
@@ -1040,7 +1040,7 @@ var applyJogToTerminalSegment = ({
|
|
|
1040
1040
|
);
|
|
1041
1041
|
}
|
|
1042
1042
|
} else {
|
|
1043
|
-
if (
|
|
1043
|
+
if (isVertical3) {
|
|
1044
1044
|
const jogY = end.y - segDir * JOG_SIZE;
|
|
1045
1045
|
pts.splice(
|
|
1046
1046
|
si,
|
|
@@ -1083,13 +1083,13 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
|
|
|
1083
1083
|
}
|
|
1084
1084
|
}
|
|
1085
1085
|
_step() {
|
|
1086
|
-
const
|
|
1086
|
+
const EPS5 = 1e-6;
|
|
1087
1087
|
const offsets = this.overlappingTraceSegments.map((_, idx) => {
|
|
1088
1088
|
const n = Math.floor(idx / 2) + 1;
|
|
1089
1089
|
const signed = idx % 2 === 0 ? -n : n;
|
|
1090
1090
|
return signed * this.SHIFT_DISTANCE;
|
|
1091
1091
|
});
|
|
1092
|
-
const eq = (a, b) => Math.abs(a - b) <
|
|
1092
|
+
const eq = (a, b) => Math.abs(a - b) < EPS5;
|
|
1093
1093
|
const samePoint = (p, q) => !!p && !!q && eq(p.x, q.x) && eq(p.y, q.y);
|
|
1094
1094
|
this.overlappingTraceSegments.forEach((group, gidx) => {
|
|
1095
1095
|
const offset = offsets[gidx];
|
|
@@ -1115,15 +1115,15 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
|
|
|
1115
1115
|
segmentIndex: si,
|
|
1116
1116
|
offset,
|
|
1117
1117
|
JOG_SIZE,
|
|
1118
|
-
EPS:
|
|
1118
|
+
EPS: EPS5
|
|
1119
1119
|
});
|
|
1120
1120
|
} else {
|
|
1121
1121
|
const start = pts[si];
|
|
1122
1122
|
const end = pts[si + 1];
|
|
1123
|
-
const
|
|
1124
|
-
const isHorizontal2 = Math.abs(start.y - end.y) <
|
|
1125
|
-
if (!
|
|
1126
|
-
if (
|
|
1123
|
+
const isVertical3 = Math.abs(start.x - end.x) < EPS5;
|
|
1124
|
+
const isHorizontal2 = Math.abs(start.y - end.y) < EPS5;
|
|
1125
|
+
if (!isVertical3 && !isHorizontal2) continue;
|
|
1126
|
+
if (isVertical3) {
|
|
1127
1127
|
start.x += offset;
|
|
1128
1128
|
end.x += offset;
|
|
1129
1129
|
} else {
|
|
@@ -1218,7 +1218,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
|
1218
1218
|
return islands;
|
|
1219
1219
|
}
|
|
1220
1220
|
findNextOverlapIssue() {
|
|
1221
|
-
const
|
|
1221
|
+
const EPS5 = 2e-3;
|
|
1222
1222
|
const netIds = Object.keys(this.traceNetIslands);
|
|
1223
1223
|
for (let i = 0; i < netIds.length; i++) {
|
|
1224
1224
|
for (let j = i + 1; j < netIds.length; j++) {
|
|
@@ -1236,7 +1236,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
|
1236
1236
|
const minB = Math.min(b1, b2);
|
|
1237
1237
|
const maxB = Math.max(b1, b2);
|
|
1238
1238
|
const overlap = Math.min(maxA, maxB) - Math.max(minA, minB);
|
|
1239
|
-
return overlap >
|
|
1239
|
+
return overlap > EPS5;
|
|
1240
1240
|
};
|
|
1241
1241
|
for (let pa = 0; pa < pathsA.length; pa++) {
|
|
1242
1242
|
const pathA = pathsA[pa];
|
|
@@ -1244,8 +1244,8 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
|
1244
1244
|
for (let sa = 0; sa < ptsA.length - 1; sa++) {
|
|
1245
1245
|
const a1 = ptsA[sa];
|
|
1246
1246
|
const a2 = ptsA[sa + 1];
|
|
1247
|
-
const aVert = Math.abs(a1.x - a2.x) <
|
|
1248
|
-
const aHorz = Math.abs(a1.y - a2.y) <
|
|
1247
|
+
const aVert = Math.abs(a1.x - a2.x) < EPS5;
|
|
1248
|
+
const aHorz = Math.abs(a1.y - a2.y) < EPS5;
|
|
1249
1249
|
if (!aVert && !aHorz) continue;
|
|
1250
1250
|
for (let pb = 0; pb < pathsB.length; pb++) {
|
|
1251
1251
|
const pathB = pathsB[pb];
|
|
@@ -1253,11 +1253,11 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
|
1253
1253
|
for (let sb = 0; sb < ptsB.length - 1; sb++) {
|
|
1254
1254
|
const b1 = ptsB[sb];
|
|
1255
1255
|
const b2 = ptsB[sb + 1];
|
|
1256
|
-
const bVert = Math.abs(b1.x - b2.x) <
|
|
1257
|
-
const bHorz = Math.abs(b1.y - b2.y) <
|
|
1256
|
+
const bVert = Math.abs(b1.x - b2.x) < EPS5;
|
|
1257
|
+
const bHorz = Math.abs(b1.y - b2.y) < EPS5;
|
|
1258
1258
|
if (!bVert && !bHorz) continue;
|
|
1259
1259
|
if (aVert && bVert) {
|
|
1260
|
-
if (Math.abs(a1.x - b1.x) <
|
|
1260
|
+
if (Math.abs(a1.x - b1.x) < EPS5) {
|
|
1261
1261
|
if (overlaps1D(a1.y, a2.y, b1.y, b2.y)) {
|
|
1262
1262
|
const keyA = `${pa}:${sa}`;
|
|
1263
1263
|
const keyB = `${pb}:${sb}`;
|
|
@@ -1278,7 +1278,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
|
1278
1278
|
}
|
|
1279
1279
|
}
|
|
1280
1280
|
} else if (aHorz && bHorz) {
|
|
1281
|
-
if (Math.abs(a1.y - b1.y) <
|
|
1281
|
+
if (Math.abs(a1.y - b1.y) < EPS5) {
|
|
1282
1282
|
if (overlaps1D(a1.x, a2.x, b1.x, b2.x)) {
|
|
1283
1283
|
const keyA = `${pa}:${sa}`;
|
|
1284
1284
|
const keyB = `${pb}:${sb}`;
|
|
@@ -1316,15 +1316,15 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
|
1316
1316
|
return null;
|
|
1317
1317
|
}
|
|
1318
1318
|
findNextDiagonalSegment() {
|
|
1319
|
-
const
|
|
1319
|
+
const EPS5 = 2e-3;
|
|
1320
1320
|
for (const mspPairId in this.correctedTraceMap) {
|
|
1321
1321
|
const tracePath = this.correctedTraceMap[mspPairId].tracePath;
|
|
1322
1322
|
for (let i = 0; i < tracePath.length - 1; i++) {
|
|
1323
1323
|
const p1 = tracePath[i];
|
|
1324
1324
|
const p2 = tracePath[i + 1];
|
|
1325
|
-
const isHorizontal2 = Math.abs(p1.y - p2.y) <
|
|
1326
|
-
const
|
|
1327
|
-
if (!isHorizontal2 && !
|
|
1325
|
+
const isHorizontal2 = Math.abs(p1.y - p2.y) < EPS5;
|
|
1326
|
+
const isVertical3 = Math.abs(p1.x - p2.x) < EPS5;
|
|
1327
|
+
if (!isHorizontal2 && !isVertical3) {
|
|
1328
1328
|
return { mspPairId, tracePath, i, p1, p2 };
|
|
1329
1329
|
}
|
|
1330
1330
|
}
|
|
@@ -1337,13 +1337,13 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
|
1337
1337
|
return false;
|
|
1338
1338
|
}
|
|
1339
1339
|
const { mspPairId, tracePath, i, p1, p2 } = diagonalInfo;
|
|
1340
|
-
const
|
|
1340
|
+
const EPS5 = 2e-3;
|
|
1341
1341
|
const p0 = i > 0 ? tracePath[i - 1] : null;
|
|
1342
1342
|
const p3 = i + 2 < tracePath.length ? tracePath[i + 2] : null;
|
|
1343
|
-
const prevIsVertical = p0 ? Math.abs(p0.x - p1.x) <
|
|
1344
|
-
const prevIsHorizontal = p0 ? Math.abs(p0.y - p1.y) <
|
|
1345
|
-
const nextIsVertical = p3 ? Math.abs(p2.x - p3.x) <
|
|
1346
|
-
const nextIsHorizontal = p3 ? Math.abs(p2.y - p3.y) <
|
|
1343
|
+
const prevIsVertical = p0 ? Math.abs(p0.x - p1.x) < EPS5 : false;
|
|
1344
|
+
const prevIsHorizontal = p0 ? Math.abs(p0.y - p1.y) < EPS5 : false;
|
|
1345
|
+
const nextIsVertical = p3 ? Math.abs(p2.x - p3.x) < EPS5 : false;
|
|
1346
|
+
const nextIsHorizontal = p3 ? Math.abs(p2.y - p3.y) < EPS5 : false;
|
|
1347
1347
|
const elbow1 = { x: p1.x, y: p2.y };
|
|
1348
1348
|
const elbow2 = { x: p2.x, y: p1.y };
|
|
1349
1349
|
let score1 = 0;
|
|
@@ -1507,24 +1507,24 @@ function getRectBounds(center, w, h) {
|
|
|
1507
1507
|
}
|
|
1508
1508
|
|
|
1509
1509
|
// lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions.ts
|
|
1510
|
-
function segmentIntersectsRect2(p1, p2, rect,
|
|
1511
|
-
const isVert = Math.abs(p1.x - p2.x) <
|
|
1512
|
-
const isHorz = Math.abs(p1.y - p2.y) <
|
|
1510
|
+
function segmentIntersectsRect2(p1, p2, rect, EPS5 = 1e-9) {
|
|
1511
|
+
const isVert = Math.abs(p1.x - p2.x) < EPS5;
|
|
1512
|
+
const isHorz = Math.abs(p1.y - p2.y) < EPS5;
|
|
1513
1513
|
if (!isVert && !isHorz) return false;
|
|
1514
1514
|
if (isVert) {
|
|
1515
1515
|
const x = p1.x;
|
|
1516
|
-
if (x < rect.minX -
|
|
1516
|
+
if (x < rect.minX - EPS5 || x > rect.maxX + EPS5) return false;
|
|
1517
1517
|
const segMinY = Math.min(p1.y, p2.y);
|
|
1518
1518
|
const segMaxY = Math.max(p1.y, p2.y);
|
|
1519
1519
|
const overlap = Math.min(segMaxY, rect.maxY) - Math.max(segMinY, rect.minY);
|
|
1520
|
-
return overlap >
|
|
1520
|
+
return overlap > EPS5;
|
|
1521
1521
|
} else {
|
|
1522
1522
|
const y = p1.y;
|
|
1523
|
-
if (y < rect.minY -
|
|
1523
|
+
if (y < rect.minY - EPS5 || y > rect.maxY + EPS5) return false;
|
|
1524
1524
|
const segMinX = Math.min(p1.x, p2.x);
|
|
1525
1525
|
const segMaxX = Math.max(p1.x, p2.x);
|
|
1526
1526
|
const overlap = Math.min(segMaxX, rect.maxX) - Math.max(segMinX, rect.minX);
|
|
1527
|
-
return overlap >
|
|
1527
|
+
return overlap > EPS5;
|
|
1528
1528
|
}
|
|
1529
1529
|
}
|
|
1530
1530
|
function rectIntersectsAnyTrace(bounds, inputTraceMap, hostPathId, hostSegIndex) {
|
|
@@ -1893,14 +1893,14 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
|
|
|
1893
1893
|
};
|
|
1894
1894
|
let bestCandidate = null;
|
|
1895
1895
|
let bestScore = -Infinity;
|
|
1896
|
-
const
|
|
1896
|
+
const EPS5 = 1e-6;
|
|
1897
1897
|
for (const curr of tracesToScan) {
|
|
1898
1898
|
const pts = curr.tracePath.slice();
|
|
1899
1899
|
for (let si = 0; si < pts.length - 1; si++) {
|
|
1900
1900
|
const a = pts[si];
|
|
1901
1901
|
const b = pts[si + 1];
|
|
1902
|
-
const isH = Math.abs(a.y - b.y) <
|
|
1903
|
-
const isV = Math.abs(a.x - b.x) <
|
|
1902
|
+
const isH = Math.abs(a.y - b.y) < EPS5;
|
|
1903
|
+
const isV = Math.abs(a.x - b.x) < EPS5;
|
|
1904
1904
|
if (!isH && !isV) continue;
|
|
1905
1905
|
const segmentAllowed = isH ? ["y+", "y-"] : ["x+", "x-"];
|
|
1906
1906
|
const candidateOrients = orientations.filter(
|
|
@@ -2260,12 +2260,134 @@ var NetLabelPlacementSolver = class extends BaseSolver {
|
|
|
2260
2260
|
}
|
|
2261
2261
|
};
|
|
2262
2262
|
|
|
2263
|
+
// lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/groupLabelsByChipAndOrientation.ts
|
|
2264
|
+
var groupLabelsByChipAndOrientation = ({
|
|
2265
|
+
labels,
|
|
2266
|
+
chips
|
|
2267
|
+
}) => {
|
|
2268
|
+
const groupedLabels = {};
|
|
2269
|
+
for (const label of labels) {
|
|
2270
|
+
if (label.pinIds.length === 0) {
|
|
2271
|
+
continue;
|
|
2272
|
+
}
|
|
2273
|
+
const chipId = label.pinIds[0].split(".")[0];
|
|
2274
|
+
if (!chipId) {
|
|
2275
|
+
continue;
|
|
2276
|
+
}
|
|
2277
|
+
const key = `${chipId}-${label.orientation}`;
|
|
2278
|
+
if (!groupedLabels[key]) {
|
|
2279
|
+
groupedLabels[key] = [];
|
|
2280
|
+
}
|
|
2281
|
+
groupedLabels[key].push(label);
|
|
2282
|
+
}
|
|
2283
|
+
return groupedLabels;
|
|
2284
|
+
};
|
|
2285
|
+
|
|
2286
|
+
// lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/mergeLabelGroup.ts
|
|
2287
|
+
var mergeLabelGroup = (group, groupKey) => {
|
|
2288
|
+
if (group.length === 0) {
|
|
2289
|
+
throw new Error("Cannot merge an empty group of labels.");
|
|
2290
|
+
}
|
|
2291
|
+
let minX = Infinity;
|
|
2292
|
+
let minY = Infinity;
|
|
2293
|
+
let maxX = -Infinity;
|
|
2294
|
+
let maxY = -Infinity;
|
|
2295
|
+
const allPinIds = /* @__PURE__ */ new Set();
|
|
2296
|
+
const allMspConnectionPairIds = /* @__PURE__ */ new Set();
|
|
2297
|
+
const originalNetIds = /* @__PURE__ */ new Set();
|
|
2298
|
+
for (const label of group) {
|
|
2299
|
+
const bounds = getRectBounds(label.center, label.width, label.height);
|
|
2300
|
+
minX = Math.min(minX, bounds.minX);
|
|
2301
|
+
minY = Math.min(minY, bounds.minY);
|
|
2302
|
+
maxX = Math.max(maxX, bounds.maxX);
|
|
2303
|
+
maxY = Math.max(maxY, bounds.maxY);
|
|
2304
|
+
label.pinIds.forEach((id) => allPinIds.add(id));
|
|
2305
|
+
label.mspConnectionPairIds.forEach((id) => allMspConnectionPairIds.add(id));
|
|
2306
|
+
originalNetIds.add(label.globalConnNetId);
|
|
2307
|
+
}
|
|
2308
|
+
const newWidth = maxX - minX;
|
|
2309
|
+
const newHeight = maxY - minY;
|
|
2310
|
+
const newCenter = { x: minX + newWidth / 2, y: minY + newHeight / 2 };
|
|
2311
|
+
const template = group[0];
|
|
2312
|
+
const syntheticId = `merged-group-${groupKey}`;
|
|
2313
|
+
const mergedLabel = {
|
|
2314
|
+
...template,
|
|
2315
|
+
// Copy common properties
|
|
2316
|
+
globalConnNetId: syntheticId,
|
|
2317
|
+
center: newCenter,
|
|
2318
|
+
width: newWidth,
|
|
2319
|
+
height: newHeight,
|
|
2320
|
+
pinIds: Array.from(allPinIds),
|
|
2321
|
+
mspConnectionPairIds: Array.from(allMspConnectionPairIds)
|
|
2322
|
+
};
|
|
2323
|
+
return {
|
|
2324
|
+
mergedLabel,
|
|
2325
|
+
originalNetIds
|
|
2326
|
+
};
|
|
2327
|
+
};
|
|
2328
|
+
|
|
2329
|
+
// lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/filterLabelsAtTraceEdges.ts
|
|
2330
|
+
import { distance } from "@tscircuit/math-utils";
|
|
2331
|
+
var filterLabelsAtTraceEdges = ({
|
|
2332
|
+
labels,
|
|
2333
|
+
traces,
|
|
2334
|
+
distanceThreshold = 0.5
|
|
2335
|
+
// Example threshold
|
|
2336
|
+
}) => {
|
|
2337
|
+
const tracesByNetId = /* @__PURE__ */ new Map();
|
|
2338
|
+
if (!traces) {
|
|
2339
|
+
throw new Error("No traces provided to filterLabelsAtTraceEdges");
|
|
2340
|
+
}
|
|
2341
|
+
for (const trace of traces) {
|
|
2342
|
+
if (!trace.globalConnNetId) continue;
|
|
2343
|
+
if (!tracesByNetId.has(trace.globalConnNetId)) {
|
|
2344
|
+
tracesByNetId.set(trace.globalConnNetId, []);
|
|
2345
|
+
}
|
|
2346
|
+
tracesByNetId.get(trace.globalConnNetId).push(trace);
|
|
2347
|
+
}
|
|
2348
|
+
const filteredLabels = [];
|
|
2349
|
+
for (const label of labels) {
|
|
2350
|
+
if (label.mspConnectionPairIds.length === 0) {
|
|
2351
|
+
filteredLabels.push(label);
|
|
2352
|
+
continue;
|
|
2353
|
+
}
|
|
2354
|
+
const relevantTraces = tracesByNetId.get(label.globalConnNetId);
|
|
2355
|
+
let isNearTraceEdge = false;
|
|
2356
|
+
if (!relevantTraces || relevantTraces.length === 0) {
|
|
2357
|
+
continue;
|
|
2358
|
+
}
|
|
2359
|
+
for (const trace of relevantTraces) {
|
|
2360
|
+
if (trace.tracePath.length === 0) continue;
|
|
2361
|
+
const startPoint = trace.tracePath[0];
|
|
2362
|
+
const endPoint = trace.tracePath[trace.tracePath.length - 1];
|
|
2363
|
+
const startDist = distance(label.center, startPoint);
|
|
2364
|
+
const endDist = distance(label.center, endPoint);
|
|
2365
|
+
if (startDist <= distanceThreshold || endDist <= distanceThreshold) {
|
|
2366
|
+
isNearTraceEdge = true;
|
|
2367
|
+
break;
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
if (isNearTraceEdge) {
|
|
2371
|
+
filteredLabels.push(label);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
return filteredLabels;
|
|
2375
|
+
};
|
|
2376
|
+
|
|
2263
2377
|
// lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts
|
|
2264
2378
|
var MergedNetLabelObstacleSolver = class extends BaseSolver {
|
|
2265
2379
|
input;
|
|
2266
2380
|
output;
|
|
2267
2381
|
inputProblem;
|
|
2268
2382
|
traces;
|
|
2383
|
+
// State for the new pipeline
|
|
2384
|
+
pipelineStep = "filtering_labels";
|
|
2385
|
+
filteredLabels = [];
|
|
2386
|
+
labelGroups = {};
|
|
2387
|
+
groupKeysToProcess = [];
|
|
2388
|
+
finalPlacements = [];
|
|
2389
|
+
mergedLabelNetIdMap = {};
|
|
2390
|
+
activeMergingGroupKey = null;
|
|
2269
2391
|
constructor(solverInput) {
|
|
2270
2392
|
super();
|
|
2271
2393
|
this.input = solverInput;
|
|
@@ -2277,67 +2399,58 @@ var MergedNetLabelObstacleSolver = class extends BaseSolver {
|
|
|
2277
2399
|
};
|
|
2278
2400
|
}
|
|
2279
2401
|
_step() {
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
mspConnectionPairIds: [
|
|
2332
|
-
...new Set(group.flatMap((p) => p.mspConnectionPairIds))
|
|
2333
|
-
]
|
|
2334
|
-
});
|
|
2402
|
+
switch (this.pipelineStep) {
|
|
2403
|
+
case "filtering_labels":
|
|
2404
|
+
this.filteredLabels = filterLabelsAtTraceEdges({
|
|
2405
|
+
labels: this.input.netLabelPlacements,
|
|
2406
|
+
traces: this.traces
|
|
2407
|
+
});
|
|
2408
|
+
this.pipelineStep = "grouping_labels";
|
|
2409
|
+
break;
|
|
2410
|
+
case "grouping_labels":
|
|
2411
|
+
this.labelGroups = groupLabelsByChipAndOrientation({
|
|
2412
|
+
labels: this.filteredLabels,
|
|
2413
|
+
chips: this.inputProblem.chips
|
|
2414
|
+
});
|
|
2415
|
+
this.groupKeysToProcess = Object.keys(this.labelGroups);
|
|
2416
|
+
this.pipelineStep = "merging_groups";
|
|
2417
|
+
break;
|
|
2418
|
+
case "merging_groups":
|
|
2419
|
+
if (this.groupKeysToProcess.length === 0) {
|
|
2420
|
+
this.pipelineStep = "finalizing";
|
|
2421
|
+
this.activeMergingGroupKey = null;
|
|
2422
|
+
break;
|
|
2423
|
+
}
|
|
2424
|
+
const groupKey = this.groupKeysToProcess.pop();
|
|
2425
|
+
this.activeMergingGroupKey = groupKey;
|
|
2426
|
+
const group = this.labelGroups[groupKey];
|
|
2427
|
+
if (group.length > 1) {
|
|
2428
|
+
const { mergedLabel, originalNetIds } = mergeLabelGroup(
|
|
2429
|
+
group,
|
|
2430
|
+
groupKey
|
|
2431
|
+
);
|
|
2432
|
+
this.finalPlacements.push(mergedLabel);
|
|
2433
|
+
this.mergedLabelNetIdMap[mergedLabel.globalConnNetId] = originalNetIds;
|
|
2434
|
+
} else {
|
|
2435
|
+
this.finalPlacements.push(...group);
|
|
2436
|
+
}
|
|
2437
|
+
break;
|
|
2438
|
+
case "finalizing":
|
|
2439
|
+
const processedOriginalIds = new Set(
|
|
2440
|
+
this.finalPlacements.flatMap(
|
|
2441
|
+
(p) => this.mergedLabelNetIdMap[p.globalConnNetId] ? [...this.mergedLabelNetIdMap[p.globalConnNetId]] : [p.globalConnNetId]
|
|
2442
|
+
)
|
|
2443
|
+
);
|
|
2444
|
+
const unprocessedLabels = this.input.netLabelPlacements.filter(
|
|
2445
|
+
(l) => !processedOriginalIds.has(l.globalConnNetId)
|
|
2446
|
+
);
|
|
2447
|
+
this.output = {
|
|
2448
|
+
netLabelPlacements: [...this.finalPlacements, ...unprocessedLabels],
|
|
2449
|
+
mergedLabelNetIdMap: this.mergedLabelNetIdMap
|
|
2450
|
+
};
|
|
2451
|
+
this.solved = true;
|
|
2452
|
+
break;
|
|
2335
2453
|
}
|
|
2336
|
-
this.output = {
|
|
2337
|
-
netLabelPlacements: finalPlacements,
|
|
2338
|
-
mergedLabelNetIdMap
|
|
2339
|
-
};
|
|
2340
|
-
this.solved = true;
|
|
2341
2454
|
}
|
|
2342
2455
|
getOutput() {
|
|
2343
2456
|
return this.output;
|
|
@@ -2362,6 +2475,19 @@ var MergedNetLabelObstacleSolver = class extends BaseSolver {
|
|
|
2362
2475
|
};
|
|
2363
2476
|
graphics.lines.push(line);
|
|
2364
2477
|
}
|
|
2478
|
+
if (this.activeMergingGroupKey && this.labelGroups[this.activeMergingGroupKey]) {
|
|
2479
|
+
const activeGroup = this.labelGroups[this.activeMergingGroupKey];
|
|
2480
|
+
for (const label of activeGroup) {
|
|
2481
|
+
graphics.rects.push({
|
|
2482
|
+
center: label.center,
|
|
2483
|
+
width: label.width,
|
|
2484
|
+
height: label.height,
|
|
2485
|
+
fill: "rgba(255, 165, 0, 0.5)",
|
|
2486
|
+
// Orange highlight
|
|
2487
|
+
stroke: "orange"
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2365
2491
|
for (const finalLabel of this.output.netLabelPlacements) {
|
|
2366
2492
|
const isMerged = finalLabel.globalConnNetId.startsWith("merged-group-");
|
|
2367
2493
|
const color = getColorFromString(finalLabel.globalConnNetId);
|
|
@@ -2371,7 +2497,6 @@ var MergedNetLabelObstacleSolver = class extends BaseSolver {
|
|
|
2371
2497
|
width: finalLabel.width,
|
|
2372
2498
|
height: finalLabel.height,
|
|
2373
2499
|
fill: color.replace(/, 1\)/, ", 0.2)"),
|
|
2374
|
-
// semi-transparent
|
|
2375
2500
|
stroke: color,
|
|
2376
2501
|
label: finalLabel.globalConnNetId
|
|
2377
2502
|
});
|
|
@@ -2417,14 +2542,17 @@ var MergedNetLabelObstacleSolver = class extends BaseSolver {
|
|
|
2417
2542
|
};
|
|
2418
2543
|
|
|
2419
2544
|
// lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts
|
|
2420
|
-
var detectTraceLabelOverlap = (
|
|
2545
|
+
var detectTraceLabelOverlap = ({
|
|
2546
|
+
traces,
|
|
2547
|
+
netLabels = []
|
|
2548
|
+
}) => {
|
|
2421
2549
|
const overlaps = [];
|
|
2422
2550
|
for (const trace of traces) {
|
|
2423
2551
|
for (const label of netLabels) {
|
|
2424
2552
|
const labelBounds = getRectBounds(label.center, label.width, label.height);
|
|
2425
|
-
for (let
|
|
2426
|
-
const p1 = trace.tracePath[
|
|
2427
|
-
const p2 = trace.tracePath[
|
|
2553
|
+
for (let j = 0; j < trace.tracePath.length - 1; j++) {
|
|
2554
|
+
const p1 = trace.tracePath[j];
|
|
2555
|
+
const p2 = trace.tracePath[j + 1];
|
|
2428
2556
|
if (segmentIntersectsRect2(p1, p2, labelBounds)) {
|
|
2429
2557
|
if (trace.globalConnNetId === label.globalConnNetId) {
|
|
2430
2558
|
break;
|
|
@@ -2738,10 +2866,45 @@ var SingleOverlapSolver = class extends BaseSolver {
|
|
|
2738
2866
|
}
|
|
2739
2867
|
};
|
|
2740
2868
|
|
|
2869
|
+
// lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/isPointInsideLabel.ts
|
|
2870
|
+
var isPointInsideLabel = ({
|
|
2871
|
+
point,
|
|
2872
|
+
label
|
|
2873
|
+
}) => {
|
|
2874
|
+
const bounds = getRectBounds(label.center, label.width, label.height);
|
|
2875
|
+
return point.x >= bounds.minX && point.x <= bounds.maxX && point.y >= bounds.minY && point.y <= bounds.maxY;
|
|
2876
|
+
};
|
|
2877
|
+
|
|
2878
|
+
// lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/visualizeDecomposition.ts
|
|
2879
|
+
var visualizeDecomposition = (params) => {
|
|
2880
|
+
const { decomposedChildLabels, collidingTrace, mergedLabel, graphics } = params;
|
|
2881
|
+
if (!graphics.rects) graphics.rects = [];
|
|
2882
|
+
if (!graphics.texts) graphics.texts = [];
|
|
2883
|
+
for (const childLabel of decomposedChildLabels) {
|
|
2884
|
+
const isOwnLabel = childLabel.globalConnNetId === collidingTrace.globalConnNetId;
|
|
2885
|
+
graphics.rects.push({
|
|
2886
|
+
center: childLabel.center,
|
|
2887
|
+
width: childLabel.width,
|
|
2888
|
+
height: childLabel.height,
|
|
2889
|
+
fill: isOwnLabel ? "green" : "red"
|
|
2890
|
+
// Green for own label, red for others
|
|
2891
|
+
});
|
|
2892
|
+
}
|
|
2893
|
+
graphics.texts.push({
|
|
2894
|
+
x: mergedLabel.center.x,
|
|
2895
|
+
y: mergedLabel.center.y + mergedLabel.height / 2 + 0.5,
|
|
2896
|
+
text: `DECOMPOSITION: Trace ${collidingTrace.mspPairId} vs Merged Label ${mergedLabel.globalConnNetId}`,
|
|
2897
|
+
fontSize: 0.3,
|
|
2898
|
+
color: "blue"
|
|
2899
|
+
});
|
|
2900
|
+
return graphics;
|
|
2901
|
+
};
|
|
2902
|
+
|
|
2741
2903
|
// lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts
|
|
2742
2904
|
var OverlapAvoidanceStepSolver = class extends BaseSolver {
|
|
2743
2905
|
inputProblem;
|
|
2744
|
-
|
|
2906
|
+
initialNetLabelPlacements;
|
|
2907
|
+
mergedNetLabelPlacements;
|
|
2745
2908
|
mergedLabelNetIdMap;
|
|
2746
2909
|
allTraces;
|
|
2747
2910
|
modifiedTraces = [];
|
|
@@ -2750,14 +2913,19 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
|
|
|
2750
2913
|
activeSubSolver = null;
|
|
2751
2914
|
overlapQueue = [];
|
|
2752
2915
|
recentlyFailed = /* @__PURE__ */ new Set();
|
|
2916
|
+
currentlyProcessingOverlap = null;
|
|
2917
|
+
decomposedChildLabels = null;
|
|
2753
2918
|
constructor(solverInput) {
|
|
2754
2919
|
super();
|
|
2755
2920
|
this.inputProblem = solverInput.inputProblem;
|
|
2756
|
-
this.
|
|
2921
|
+
this.initialNetLabelPlacements = solverInput.initialNetLabelPlacements;
|
|
2922
|
+
this.mergedNetLabelPlacements = solverInput.mergedNetLabelPlacements;
|
|
2757
2923
|
this.mergedLabelNetIdMap = solverInput.mergedLabelNetIdMap;
|
|
2758
2924
|
this.allTraces = [...solverInput.traces];
|
|
2759
2925
|
}
|
|
2760
2926
|
_step() {
|
|
2927
|
+
this.currentlyProcessingOverlap = null;
|
|
2928
|
+
this.decomposedChildLabels = null;
|
|
2761
2929
|
if (this.activeSubSolver) {
|
|
2762
2930
|
this.activeSubSolver.step();
|
|
2763
2931
|
if (this.activeSubSolver.solved) {
|
|
@@ -2777,18 +2945,13 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
|
|
|
2777
2945
|
const overlapId = `${this.activeSubSolver.initialTrace.mspPairId}-${this.activeSubSolver.label.globalConnNetId}`;
|
|
2778
2946
|
this.recentlyFailed.add(overlapId);
|
|
2779
2947
|
this.activeSubSolver = null;
|
|
2948
|
+
} else {
|
|
2780
2949
|
}
|
|
2781
2950
|
return;
|
|
2782
2951
|
}
|
|
2783
|
-
const overlaps = detectTraceLabelOverlap(
|
|
2784
|
-
this.allTraces,
|
|
2785
|
-
this.
|
|
2786
|
-
).filter((o) => {
|
|
2787
|
-
const originalNetIds = this.mergedLabelNetIdMap[o.label.globalConnNetId];
|
|
2788
|
-
if (originalNetIds) {
|
|
2789
|
-
return !originalNetIds.has(o.trace.globalConnNetId);
|
|
2790
|
-
}
|
|
2791
|
-
return o.trace.globalConnNetId !== o.label.globalConnNetId;
|
|
2952
|
+
const overlaps = detectTraceLabelOverlap({
|
|
2953
|
+
traces: this.allTraces,
|
|
2954
|
+
netLabels: this.mergedNetLabelPlacements
|
|
2792
2955
|
});
|
|
2793
2956
|
if (overlaps.length === 0) {
|
|
2794
2957
|
this.solved = true;
|
|
@@ -2804,22 +2967,91 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
|
|
|
2804
2967
|
}
|
|
2805
2968
|
this.overlapQueue = nonFailedOverlaps;
|
|
2806
2969
|
const nextOverlap = this.overlapQueue.shift();
|
|
2970
|
+
this.currentlyProcessingOverlap = nextOverlap ?? null;
|
|
2807
2971
|
if (nextOverlap) {
|
|
2808
2972
|
const traceToFix = this.allTraces.find(
|
|
2809
2973
|
(t) => t.mspPairId === nextOverlap.trace.mspPairId
|
|
2810
2974
|
);
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2975
|
+
const labelToAvoid = nextOverlap.label;
|
|
2976
|
+
const traceStartPoint = traceToFix.tracePath[0];
|
|
2977
|
+
const originalNetIds = this.mergedLabelNetIdMap[labelToAvoid.globalConnNetId];
|
|
2978
|
+
const isSelfOverlap = originalNetIds?.has(traceToFix.globalConnNetId);
|
|
2979
|
+
if (isSelfOverlap) {
|
|
2980
|
+
const childLabels = this.initialNetLabelPlacements.filter(
|
|
2981
|
+
(l) => originalNetIds.has(l.globalConnNetId)
|
|
2982
|
+
);
|
|
2983
|
+
this.decomposedChildLabels = childLabels;
|
|
2984
|
+
let actualOverlapLabel = null;
|
|
2985
|
+
for (const childLabel of childLabels) {
|
|
2986
|
+
const overlapsWithChild = detectTraceLabelOverlap({
|
|
2987
|
+
traces: [traceToFix],
|
|
2988
|
+
netLabels: [childLabel]
|
|
2989
|
+
});
|
|
2990
|
+
if (overlapsWithChild.length > 0) {
|
|
2991
|
+
actualOverlapLabel = childLabel;
|
|
2992
|
+
break;
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
if (actualOverlapLabel) {
|
|
2996
|
+
const labelId2 = actualOverlapLabel.globalConnNetId;
|
|
2997
|
+
const detourCount2 = this.detourCountByLabel[labelId2] || 0;
|
|
2998
|
+
this.detourCountByLabel[labelId2] = detourCount2 + 1;
|
|
2999
|
+
this.activeSubSolver = new SingleOverlapSolver({
|
|
3000
|
+
trace: traceToFix,
|
|
3001
|
+
label: actualOverlapLabel,
|
|
3002
|
+
problem: this.inputProblem,
|
|
3003
|
+
paddingBuffer: this.PADDING_BUFFER,
|
|
3004
|
+
detourCount: detourCount2
|
|
3005
|
+
});
|
|
3006
|
+
} else {
|
|
3007
|
+
const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`;
|
|
3008
|
+
this.recentlyFailed.add(overlapId);
|
|
3009
|
+
}
|
|
3010
|
+
return;
|
|
2822
3011
|
}
|
|
3012
|
+
if (originalNetIds && isPointInsideLabel({ point: traceStartPoint, label: labelToAvoid })) {
|
|
3013
|
+
const childLabels = this.initialNetLabelPlacements.filter(
|
|
3014
|
+
(l) => originalNetIds.has(l.globalConnNetId)
|
|
3015
|
+
);
|
|
3016
|
+
this.decomposedChildLabels = childLabels;
|
|
3017
|
+
let actualOverlapLabel = null;
|
|
3018
|
+
for (const childLabel of childLabels) {
|
|
3019
|
+
const overlapsWithChild = detectTraceLabelOverlap({
|
|
3020
|
+
traces: [traceToFix],
|
|
3021
|
+
netLabels: [childLabel]
|
|
3022
|
+
});
|
|
3023
|
+
if (overlapsWithChild.length > 0) {
|
|
3024
|
+
actualOverlapLabel = childLabel;
|
|
3025
|
+
break;
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
if (actualOverlapLabel) {
|
|
3029
|
+
const labelId2 = actualOverlapLabel.globalConnNetId;
|
|
3030
|
+
const detourCount2 = this.detourCountByLabel[labelId2] || 0;
|
|
3031
|
+
this.detourCountByLabel[labelId2] = detourCount2 + 1;
|
|
3032
|
+
this.activeSubSolver = new SingleOverlapSolver({
|
|
3033
|
+
trace: traceToFix,
|
|
3034
|
+
label: actualOverlapLabel,
|
|
3035
|
+
problem: this.inputProblem,
|
|
3036
|
+
paddingBuffer: this.PADDING_BUFFER,
|
|
3037
|
+
detourCount: detourCount2
|
|
3038
|
+
});
|
|
3039
|
+
} else {
|
|
3040
|
+
const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`;
|
|
3041
|
+
this.recentlyFailed.add(overlapId);
|
|
3042
|
+
}
|
|
3043
|
+
return;
|
|
3044
|
+
}
|
|
3045
|
+
const labelId = labelToAvoid.globalConnNetId;
|
|
3046
|
+
const detourCount = this.detourCountByLabel[labelId] || 0;
|
|
3047
|
+
this.detourCountByLabel[labelId] = detourCount + 1;
|
|
3048
|
+
this.activeSubSolver = new SingleOverlapSolver({
|
|
3049
|
+
trace: traceToFix,
|
|
3050
|
+
label: labelToAvoid,
|
|
3051
|
+
problem: this.inputProblem,
|
|
3052
|
+
paddingBuffer: this.PADDING_BUFFER,
|
|
3053
|
+
detourCount
|
|
3054
|
+
});
|
|
2823
3055
|
}
|
|
2824
3056
|
}
|
|
2825
3057
|
getOutput() {
|
|
@@ -2840,6 +3072,37 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
|
|
|
2840
3072
|
strokeColor: "purple"
|
|
2841
3073
|
});
|
|
2842
3074
|
}
|
|
3075
|
+
if (this.currentlyProcessingOverlap) {
|
|
3076
|
+
const { trace, label } = this.currentlyProcessingOverlap;
|
|
3077
|
+
graphics.lines.push({
|
|
3078
|
+
points: trace.tracePath,
|
|
3079
|
+
strokeColor: "red"
|
|
3080
|
+
});
|
|
3081
|
+
if (this.decomposedChildLabels) {
|
|
3082
|
+
visualizeDecomposition({
|
|
3083
|
+
decomposedChildLabels: this.decomposedChildLabels,
|
|
3084
|
+
collidingTrace: trace,
|
|
3085
|
+
mergedLabel: label,
|
|
3086
|
+
graphics
|
|
3087
|
+
});
|
|
3088
|
+
} else {
|
|
3089
|
+
if (!graphics.rects) graphics.rects = [];
|
|
3090
|
+
graphics.rects.push({
|
|
3091
|
+
center: label.center,
|
|
3092
|
+
width: label.width,
|
|
3093
|
+
height: label.height,
|
|
3094
|
+
fill: "yellow"
|
|
3095
|
+
});
|
|
3096
|
+
if (!graphics.texts) graphics.texts = [];
|
|
3097
|
+
graphics.texts.push({
|
|
3098
|
+
x: label.center.x,
|
|
3099
|
+
y: label.center.y + label.height / 2 + 0.5,
|
|
3100
|
+
text: `COLLISION: Trace ${trace.mspPairId} vs Label ${label.globalConnNetId}`,
|
|
3101
|
+
fontSize: 0.3,
|
|
3102
|
+
color: "red"
|
|
3103
|
+
});
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
2843
3106
|
return graphics;
|
|
2844
3107
|
}
|
|
2845
3108
|
};
|
|
@@ -2884,7 +3147,9 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
|
|
|
2884
3147
|
this.overlapAvoidanceSolver = new OverlapAvoidanceStepSolver({
|
|
2885
3148
|
inputProblem: this.inputProblem,
|
|
2886
3149
|
traces: this.traces,
|
|
2887
|
-
|
|
3150
|
+
initialNetLabelPlacements: this.netLabelPlacements,
|
|
3151
|
+
// The original, unfiltered list
|
|
3152
|
+
mergedNetLabelPlacements: this.labelMergingSolver.getOutput().netLabelPlacements,
|
|
2888
3153
|
mergedLabelNetIdMap: this.labelMergingSolver.getOutput().mergedLabelNetIdMap
|
|
2889
3154
|
});
|
|
2890
3155
|
this.activeSubSolver = this.overlapAvoidanceSolver;
|
|
@@ -3006,7 +3271,7 @@ function doesTraceOverlapWithExistingTraces(newTracePath, existingTraces) {
|
|
|
3006
3271
|
|
|
3007
3272
|
// lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts
|
|
3008
3273
|
var NEAREST_NEIGHBOR_COUNT = 3;
|
|
3009
|
-
var
|
|
3274
|
+
var distance2 = (p1, p2) => {
|
|
3010
3275
|
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
|
|
3011
3276
|
};
|
|
3012
3277
|
var LongDistancePairSolver = class extends BaseSolver {
|
|
@@ -3047,7 +3312,7 @@ var LongDistancePairSolver = class extends BaseSolver {
|
|
|
3047
3312
|
return [
|
|
3048
3313
|
{
|
|
3049
3314
|
pin: targetPin,
|
|
3050
|
-
distance:
|
|
3315
|
+
distance: distance2(sourcePin, targetPin)
|
|
3051
3316
|
}
|
|
3052
3317
|
];
|
|
3053
3318
|
}).sort((a, b) => a.distance - b.distance).slice(0, NEAREST_NEIGHBOR_COUNT);
|
|
@@ -3163,12 +3428,21 @@ var LongDistancePairSolver = class extends BaseSolver {
|
|
|
3163
3428
|
};
|
|
3164
3429
|
|
|
3165
3430
|
// lib/solvers/TraceCleanupSolver/hasCollisions.ts
|
|
3431
|
+
import { segmentToBoxMinDistance } from "@tscircuit/math-utils";
|
|
3166
3432
|
var hasCollisions = (pathSegments, obstacles) => {
|
|
3167
3433
|
for (let i = 0; i < pathSegments.length - 1; i++) {
|
|
3168
3434
|
const p1 = pathSegments[i];
|
|
3169
3435
|
const p2 = pathSegments[i + 1];
|
|
3170
3436
|
for (const obstacle of obstacles) {
|
|
3171
|
-
|
|
3437
|
+
const box = {
|
|
3438
|
+
center: {
|
|
3439
|
+
x: obstacle.minX + (obstacle.maxX - obstacle.minX) / 2,
|
|
3440
|
+
y: obstacle.minY + (obstacle.maxY - obstacle.minY) / 2
|
|
3441
|
+
},
|
|
3442
|
+
width: obstacle.maxX - obstacle.minX,
|
|
3443
|
+
height: obstacle.maxY - obstacle.minY
|
|
3444
|
+
};
|
|
3445
|
+
if (segmentToBoxMinDistance(p1, p2, box) <= 0) {
|
|
3172
3446
|
return true;
|
|
3173
3447
|
}
|
|
3174
3448
|
}
|
|
@@ -3218,43 +3492,60 @@ var hasCollisionsWithLabels = (pathSegments, labels) => {
|
|
|
3218
3492
|
return false;
|
|
3219
3493
|
};
|
|
3220
3494
|
|
|
3495
|
+
// lib/solvers/TraceCleanupSolver/recognizeStairStepPattern.ts
|
|
3496
|
+
var recognizeStairStepPattern = (pathToCheck, startIdx) => {
|
|
3497
|
+
if (startIdx >= pathToCheck.length - 3) return -1;
|
|
3498
|
+
let endIdx = startIdx;
|
|
3499
|
+
let isStairStep = true;
|
|
3500
|
+
for (let i = startIdx; i < pathToCheck.length - 2 && i < startIdx + 10; i++) {
|
|
3501
|
+
if (i + 2 >= pathToCheck.length) break;
|
|
3502
|
+
const p1 = pathToCheck[i];
|
|
3503
|
+
const p2 = pathToCheck[i + 1];
|
|
3504
|
+
const p3 = pathToCheck[i + 2];
|
|
3505
|
+
const seg1Vertical = p1.x === p2.x;
|
|
3506
|
+
const seg2Vertical = p2.x === p3.x;
|
|
3507
|
+
if (seg1Vertical === seg2Vertical) {
|
|
3508
|
+
break;
|
|
3509
|
+
}
|
|
3510
|
+
const seg1Direction = seg1Vertical ? Math.sign(p2.y - p1.y) : Math.sign(p2.x - p1.x);
|
|
3511
|
+
if (i > startIdx) {
|
|
3512
|
+
const prevP = pathToCheck[i - 1];
|
|
3513
|
+
const prevSegVertical = prevP.x === p1.x;
|
|
3514
|
+
const prevDirection = prevSegVertical ? Math.sign(p1.y - prevP.y) : Math.sign(p1.x - prevP.x);
|
|
3515
|
+
if (seg1Vertical && prevSegVertical && seg1Direction !== prevDirection || !seg1Vertical && !prevSegVertical && seg1Direction !== prevDirection) {
|
|
3516
|
+
isStairStep = false;
|
|
3517
|
+
break;
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
endIdx = i + 2;
|
|
3521
|
+
}
|
|
3522
|
+
return isStairStep && endIdx - startIdx >= 3 ? endIdx : -1;
|
|
3523
|
+
};
|
|
3524
|
+
|
|
3525
|
+
// lib/solvers/TraceCleanupSolver/isSegmentAnEndpointSegment.ts
|
|
3526
|
+
var isSegmentAnEndpointSegment = (p1, p2, originalPath) => {
|
|
3527
|
+
if (originalPath.length < 2) return false;
|
|
3528
|
+
const originalStart = originalPath[0];
|
|
3529
|
+
const originalEnd = originalPath[originalPath.length - 1];
|
|
3530
|
+
if (p1.x === originalStart.x && p1.y === originalStart.y && p2.x === originalPath[1].x && p2.y === originalPath[1].y) {
|
|
3531
|
+
return true;
|
|
3532
|
+
}
|
|
3533
|
+
if (p1.x === originalPath[originalPath.length - 2].x && p1.y === originalPath[originalPath.length - 2].y && p2.x === originalEnd.x && p2.y === originalEnd.y) {
|
|
3534
|
+
return true;
|
|
3535
|
+
}
|
|
3536
|
+
return false;
|
|
3537
|
+
};
|
|
3538
|
+
|
|
3221
3539
|
// lib/solvers/TraceCleanupSolver/turnMinimization.ts
|
|
3222
3540
|
var minimizeTurns = ({
|
|
3223
3541
|
path,
|
|
3224
3542
|
obstacles,
|
|
3225
|
-
labelBounds
|
|
3543
|
+
labelBounds,
|
|
3544
|
+
originalPath
|
|
3226
3545
|
}) => {
|
|
3227
3546
|
if (path.length <= 2) {
|
|
3228
3547
|
return path;
|
|
3229
3548
|
}
|
|
3230
|
-
const recognizeStairStepPattern = (pathToCheck, startIdx) => {
|
|
3231
|
-
if (startIdx >= pathToCheck.length - 3) return -1;
|
|
3232
|
-
let endIdx = startIdx;
|
|
3233
|
-
let isStairStep = true;
|
|
3234
|
-
for (let i = startIdx; i < pathToCheck.length - 2 && i < startIdx + 10; i++) {
|
|
3235
|
-
if (i + 2 >= pathToCheck.length) break;
|
|
3236
|
-
const p1 = pathToCheck[i];
|
|
3237
|
-
const p2 = pathToCheck[i + 1];
|
|
3238
|
-
const p3 = pathToCheck[i + 2];
|
|
3239
|
-
const seg1Vertical = p1.x === p2.x;
|
|
3240
|
-
const seg2Vertical = p2.x === p3.x;
|
|
3241
|
-
if (seg1Vertical === seg2Vertical) {
|
|
3242
|
-
break;
|
|
3243
|
-
}
|
|
3244
|
-
const seg1Direction = seg1Vertical ? Math.sign(p2.y - p1.y) : Math.sign(p2.x - p1.x);
|
|
3245
|
-
if (i > startIdx) {
|
|
3246
|
-
const prevP = pathToCheck[i - 1];
|
|
3247
|
-
const prevSegVertical = prevP.x === p1.x;
|
|
3248
|
-
const prevDirection = prevSegVertical ? Math.sign(p1.y - prevP.y) : Math.sign(p1.x - prevP.x);
|
|
3249
|
-
if (seg1Vertical && prevSegVertical && seg1Direction !== prevDirection || !seg1Vertical && !prevSegVertical && seg1Direction !== prevDirection) {
|
|
3250
|
-
isStairStep = false;
|
|
3251
|
-
break;
|
|
3252
|
-
}
|
|
3253
|
-
}
|
|
3254
|
-
endIdx = i + 2;
|
|
3255
|
-
}
|
|
3256
|
-
return isStairStep && endIdx - startIdx >= 3 ? endIdx : -1;
|
|
3257
|
-
};
|
|
3258
3549
|
let optimizedPath = [...path];
|
|
3259
3550
|
let currentTurns = countTurns(optimizedPath);
|
|
3260
3551
|
let improved = true;
|
|
@@ -3263,6 +3554,17 @@ var minimizeTurns = ({
|
|
|
3263
3554
|
for (let startIdx = 0; startIdx < optimizedPath.length - 3; startIdx++) {
|
|
3264
3555
|
const stairEndIdx = recognizeStairStepPattern(optimizedPath, startIdx);
|
|
3265
3556
|
if (stairEndIdx > 0) {
|
|
3557
|
+
if (isSegmentAnEndpointSegment(
|
|
3558
|
+
optimizedPath[startIdx],
|
|
3559
|
+
optimizedPath[startIdx + 1],
|
|
3560
|
+
originalPath
|
|
3561
|
+
) || isSegmentAnEndpointSegment(
|
|
3562
|
+
optimizedPath[stairEndIdx - 1],
|
|
3563
|
+
optimizedPath[stairEndIdx],
|
|
3564
|
+
originalPath
|
|
3565
|
+
)) {
|
|
3566
|
+
continue;
|
|
3567
|
+
}
|
|
3266
3568
|
const startPoint = optimizedPath[startIdx];
|
|
3267
3569
|
const endPoint = optimizedPath[stairEndIdx];
|
|
3268
3570
|
const connectionOptions = tryConnectPoints(startPoint, endPoint);
|
|
@@ -3297,6 +3599,17 @@ var minimizeTurns = ({
|
|
|
3297
3599
|
for (let removeCount = 1; removeCount <= maxRemove; removeCount++) {
|
|
3298
3600
|
const endIdx = startIdx + removeCount + 1;
|
|
3299
3601
|
if (endIdx >= optimizedPath.length) continue;
|
|
3602
|
+
if (isSegmentAnEndpointSegment(
|
|
3603
|
+
optimizedPath[startIdx],
|
|
3604
|
+
optimizedPath[startIdx + 1],
|
|
3605
|
+
originalPath
|
|
3606
|
+
) || isSegmentAnEndpointSegment(
|
|
3607
|
+
optimizedPath[endIdx - 1],
|
|
3608
|
+
optimizedPath[endIdx],
|
|
3609
|
+
originalPath
|
|
3610
|
+
)) {
|
|
3611
|
+
continue;
|
|
3612
|
+
}
|
|
3300
3613
|
const startPoint = optimizedPath[startIdx];
|
|
3301
3614
|
const endPoint = optimizedPath[endIdx];
|
|
3302
3615
|
const connectionOptions = tryConnectPoints(startPoint, endPoint);
|
|
@@ -3335,6 +3648,9 @@ var minimizeTurns = ({
|
|
|
3335
3648
|
const p1 = optimizedPath[i];
|
|
3336
3649
|
const p2 = optimizedPath[i + 1];
|
|
3337
3650
|
const p3 = optimizedPath[i + 2];
|
|
3651
|
+
if (isSegmentAnEndpointSegment(p1, p2, originalPath) || isSegmentAnEndpointSegment(p2, p3, originalPath)) {
|
|
3652
|
+
continue;
|
|
3653
|
+
}
|
|
3338
3654
|
const allVertical = p1.x === p2.x && p2.x === p3.x;
|
|
3339
3655
|
const allHorizontal = p1.y === p2.y && p2.y === p3.y;
|
|
3340
3656
|
if (allVertical || allHorizontal) {
|
|
@@ -3391,7 +3707,15 @@ var minimizeTurnsWithFilteredLabels = ({
|
|
|
3391
3707
|
};
|
|
3392
3708
|
})
|
|
3393
3709
|
);
|
|
3394
|
-
const
|
|
3710
|
+
const staticObstaclesRaw = getObstacleRects(inputProblem);
|
|
3711
|
+
const PADDING = 0.01;
|
|
3712
|
+
const staticObstacles = staticObstaclesRaw.map((obs) => ({
|
|
3713
|
+
...obs,
|
|
3714
|
+
minX: obs.minX - PADDING,
|
|
3715
|
+
minY: obs.minY - PADDING,
|
|
3716
|
+
maxX: obs.maxX + PADDING,
|
|
3717
|
+
maxY: obs.maxY + PADDING
|
|
3718
|
+
}));
|
|
3395
3719
|
const combinedObstacles = [...staticObstacles, ...traceObstacles];
|
|
3396
3720
|
const originalPath = targetTrace.tracePath;
|
|
3397
3721
|
const filteredLabels = allLabelPlacements.filter((label) => {
|
|
@@ -3410,7 +3734,8 @@ var minimizeTurnsWithFilteredLabels = ({
|
|
|
3410
3734
|
const newPath = minimizeTurns({
|
|
3411
3735
|
path: originalPath,
|
|
3412
3736
|
obstacles: combinedObstacles,
|
|
3413
|
-
labelBounds
|
|
3737
|
+
labelBounds,
|
|
3738
|
+
originalPath
|
|
3414
3739
|
});
|
|
3415
3740
|
return {
|
|
3416
3741
|
...targetTrace,
|
|
@@ -3554,48 +3879,645 @@ var balanceZShapes = ({
|
|
|
3554
3879
|
};
|
|
3555
3880
|
};
|
|
3556
3881
|
|
|
3882
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/findAllLShapedTurns.ts
|
|
3883
|
+
var findAllLShapedTurns = (tracePath) => {
|
|
3884
|
+
const lShapes = [];
|
|
3885
|
+
if (tracePath.length < 3) {
|
|
3886
|
+
return lShapes;
|
|
3887
|
+
}
|
|
3888
|
+
for (let i = 0; i < tracePath.length - 2; i++) {
|
|
3889
|
+
const p1 = tracePath[i];
|
|
3890
|
+
const p2 = tracePath[i + 1];
|
|
3891
|
+
const p3 = tracePath[i + 2];
|
|
3892
|
+
const dx1 = p2.x - p1.x;
|
|
3893
|
+
const dy1 = p2.y - p1.y;
|
|
3894
|
+
const dx2 = p3.x - p2.x;
|
|
3895
|
+
const dy2 = p3.y - p2.y;
|
|
3896
|
+
if ((dx1 === 0 && dy2 === 0 && dy1 !== 0 && dx2 !== 0 || // Vertical then Horizontal
|
|
3897
|
+
dy1 === 0 && dx2 === 0 && dx1 !== 0 && dy2 !== 0) && // Horizontal then Vertical
|
|
3898
|
+
dx1 * dx1 + dy1 * dy1 >= 0.25 && // p1-p2 arm length >= 0.5
|
|
3899
|
+
dx2 * dx2 + dy2 * dy2 >= 0.25) {
|
|
3900
|
+
lShapes.push({ p1, p2, p3 });
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
return lShapes;
|
|
3904
|
+
};
|
|
3905
|
+
|
|
3906
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/getTraceObstacles.ts
|
|
3907
|
+
var getTraceObstacles = (allTraces, excludeTraceId) => {
|
|
3908
|
+
const obstacles = [];
|
|
3909
|
+
for (const trace of allTraces) {
|
|
3910
|
+
if (trace.mspPairId !== excludeTraceId) {
|
|
3911
|
+
obstacles.push({ points: trace.tracePath });
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
return obstacles;
|
|
3915
|
+
};
|
|
3916
|
+
|
|
3917
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/findIntersectionsWithObstacles.ts
|
|
3918
|
+
import { getSegmentIntersection } from "@tscircuit/math-utils/line-intersections";
|
|
3919
|
+
var findIntersectionsWithObstacles = (p1, p2, obstacles) => {
|
|
3920
|
+
const intersections = [];
|
|
3921
|
+
for (const obstacle of obstacles) {
|
|
3922
|
+
const obstaclePath = obstacle.points;
|
|
3923
|
+
for (let i = 0; i < obstaclePath.length - 1; i++) {
|
|
3924
|
+
const o1 = obstaclePath[i];
|
|
3925
|
+
const o2 = obstaclePath[i + 1];
|
|
3926
|
+
if (!o1 || !o2) {
|
|
3927
|
+
continue;
|
|
3928
|
+
}
|
|
3929
|
+
const intersection = getSegmentIntersection(p1, p2, o1, o2);
|
|
3930
|
+
if (intersection) {
|
|
3931
|
+
intersections.push(intersection);
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3935
|
+
return intersections;
|
|
3936
|
+
};
|
|
3937
|
+
|
|
3938
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts
|
|
3939
|
+
var EPS4 = 1e-6;
|
|
3940
|
+
var isVertical2 = (a, b, eps = EPS4) => Math.abs(a.x - b.x) < eps;
|
|
3941
|
+
var generateLShapeRerouteCandidates = ({
|
|
3942
|
+
lShape,
|
|
3943
|
+
rectangle,
|
|
3944
|
+
padding = 0.5,
|
|
3945
|
+
interactionPoint1,
|
|
3946
|
+
interactionPoint2
|
|
3947
|
+
}) => {
|
|
3948
|
+
const { p1, p2, p3 } = lShape;
|
|
3949
|
+
const { x, y, width, height } = rectangle;
|
|
3950
|
+
let c2;
|
|
3951
|
+
let i1_padded = interactionPoint1;
|
|
3952
|
+
let i2_padded = interactionPoint2;
|
|
3953
|
+
if (Math.abs(p2.x - x) < EPS4 && Math.abs(p2.y - (y + height)) < EPS4) {
|
|
3954
|
+
c2 = { x: x + width + padding, y: y - padding };
|
|
3955
|
+
if (isVertical2(p1, p2)) {
|
|
3956
|
+
i1_padded = { x: interactionPoint1.x, y: interactionPoint1.y - padding };
|
|
3957
|
+
} else {
|
|
3958
|
+
i1_padded = { x: interactionPoint1.x + padding, y: interactionPoint1.y };
|
|
3959
|
+
}
|
|
3960
|
+
if (isVertical2(p2, p3)) {
|
|
3961
|
+
i2_padded = { x: interactionPoint2.x, y: interactionPoint2.y - padding };
|
|
3962
|
+
} else {
|
|
3963
|
+
i2_padded = { x: interactionPoint2.x + padding, y: interactionPoint2.y };
|
|
3964
|
+
}
|
|
3965
|
+
} else if (Math.abs(p2.x - (x + width)) < EPS4 && Math.abs(p2.y - (y + height)) < EPS4) {
|
|
3966
|
+
c2 = { x: x - padding, y: y - padding };
|
|
3967
|
+
if (isVertical2(p1, p2)) {
|
|
3968
|
+
i1_padded = { x: interactionPoint1.x, y: interactionPoint1.y - padding };
|
|
3969
|
+
} else {
|
|
3970
|
+
i1_padded = { x: interactionPoint1.x - padding, y: interactionPoint1.y };
|
|
3971
|
+
}
|
|
3972
|
+
if (isVertical2(p2, p3)) {
|
|
3973
|
+
i2_padded = { x: interactionPoint2.x, y: interactionPoint2.y - padding };
|
|
3974
|
+
} else {
|
|
3975
|
+
i2_padded = { x: interactionPoint2.x - padding, y: interactionPoint2.y };
|
|
3976
|
+
}
|
|
3977
|
+
} else if (Math.abs(p2.x - x) < EPS4 && Math.abs(p2.y - y) < EPS4) {
|
|
3978
|
+
c2 = { x: x + width + padding, y: y + height + padding };
|
|
3979
|
+
if (isVertical2(p1, p2)) {
|
|
3980
|
+
i1_padded = { x: interactionPoint1.x, y: interactionPoint1.y + padding };
|
|
3981
|
+
} else {
|
|
3982
|
+
i1_padded = { x: interactionPoint1.x + padding, y: interactionPoint1.y };
|
|
3983
|
+
}
|
|
3984
|
+
if (isVertical2(p2, p3)) {
|
|
3985
|
+
i2_padded = { x: interactionPoint2.x, y: interactionPoint2.y + padding };
|
|
3986
|
+
} else {
|
|
3987
|
+
i2_padded = { x: interactionPoint2.x + padding, y: interactionPoint2.y };
|
|
3988
|
+
}
|
|
3989
|
+
} else if (Math.abs(p2.x - (x + width)) < EPS4 && Math.abs(p2.y - y) < EPS4) {
|
|
3990
|
+
c2 = { x: x - padding, y: y + height + padding };
|
|
3991
|
+
if (isVertical2(p1, p2)) {
|
|
3992
|
+
i1_padded = { x: interactionPoint1.x, y: interactionPoint1.y + padding };
|
|
3993
|
+
} else {
|
|
3994
|
+
i1_padded = { x: interactionPoint1.x - padding, y: interactionPoint1.y };
|
|
3995
|
+
}
|
|
3996
|
+
if (isVertical2(p2, p3)) {
|
|
3997
|
+
i2_padded = { x: interactionPoint2.x, y: interactionPoint2.y + padding };
|
|
3998
|
+
} else {
|
|
3999
|
+
i2_padded = { x: interactionPoint2.x - padding, y: interactionPoint2.y };
|
|
4000
|
+
}
|
|
4001
|
+
} else {
|
|
4002
|
+
return [];
|
|
4003
|
+
}
|
|
4004
|
+
return [[i1_padded, c2, i2_padded]];
|
|
4005
|
+
};
|
|
4006
|
+
|
|
4007
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/isPathColliding.ts
|
|
4008
|
+
import { getSegmentIntersection as getSegmentIntersection2 } from "@tscircuit/math-utils/line-intersections";
|
|
4009
|
+
var isPathColliding = (path, allTraces, traceIdToExclude) => {
|
|
4010
|
+
if (path.length < 2) {
|
|
4011
|
+
return { isColliding: false };
|
|
4012
|
+
}
|
|
4013
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
4014
|
+
const pathSegP1 = path[i];
|
|
4015
|
+
const pathSegQ1 = path[i + 1];
|
|
4016
|
+
for (const existingTrace of allTraces) {
|
|
4017
|
+
if (existingTrace.mspPairId === traceIdToExclude) {
|
|
4018
|
+
continue;
|
|
4019
|
+
}
|
|
4020
|
+
for (let j = 0; j < existingTrace.tracePath.length - 1; j++) {
|
|
4021
|
+
const existingSegP2 = existingTrace.tracePath[j];
|
|
4022
|
+
const existingSegQ2 = existingTrace.tracePath[j + 1];
|
|
4023
|
+
const intersectionPoint = getSegmentIntersection2(
|
|
4024
|
+
pathSegP1,
|
|
4025
|
+
pathSegQ1,
|
|
4026
|
+
existingSegP2,
|
|
4027
|
+
existingSegQ2
|
|
4028
|
+
);
|
|
4029
|
+
if (intersectionPoint) {
|
|
4030
|
+
return {
|
|
4031
|
+
isColliding: true,
|
|
4032
|
+
collidingTraceId: existingTrace.mspPairId,
|
|
4033
|
+
collisionPoint: intersectionPoint
|
|
4034
|
+
};
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
return { isColliding: false };
|
|
4040
|
+
};
|
|
4041
|
+
|
|
4042
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/generateRectangleCandidates.ts
|
|
4043
|
+
var generateRectangleCandidates = (intersections1, intersections2) => {
|
|
4044
|
+
const rectangleCandidates = [];
|
|
4045
|
+
for (const p1 of intersections1) {
|
|
4046
|
+
for (const p2 of intersections2) {
|
|
4047
|
+
const minX = Math.min(p1.x, p2.x);
|
|
4048
|
+
const minY = Math.min(p1.y, p2.y);
|
|
4049
|
+
const maxX = Math.max(p1.x, p2.x);
|
|
4050
|
+
const maxY = Math.max(p1.y, p2.y);
|
|
4051
|
+
const width = maxX - minX;
|
|
4052
|
+
const height = maxY - minY;
|
|
4053
|
+
if (width > 1e-6 && height > 1e-6) {
|
|
4054
|
+
rectangleCandidates.push({
|
|
4055
|
+
rect: {
|
|
4056
|
+
x: minX,
|
|
4057
|
+
y: minY,
|
|
4058
|
+
width,
|
|
4059
|
+
height
|
|
4060
|
+
},
|
|
4061
|
+
i1: p1,
|
|
4062
|
+
i2: p2
|
|
4063
|
+
});
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
return rectangleCandidates;
|
|
4068
|
+
};
|
|
4069
|
+
|
|
4070
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/visualizeLSapes.ts
|
|
4071
|
+
var visualizeLSapes = (lShapes) => {
|
|
4072
|
+
const graphics = { circles: [], lines: [] };
|
|
4073
|
+
const lShapesArray = Array.isArray(lShapes) ? lShapes : [lShapes];
|
|
4074
|
+
for (const lShape of lShapesArray) {
|
|
4075
|
+
graphics.circles.push({
|
|
4076
|
+
center: {
|
|
4077
|
+
x: lShape.p2.x,
|
|
4078
|
+
y: lShape.p2.y
|
|
4079
|
+
},
|
|
4080
|
+
radius: 0.01,
|
|
4081
|
+
fill: "blue"
|
|
4082
|
+
});
|
|
4083
|
+
graphics.lines.push({
|
|
4084
|
+
points: [lShape.p1, lShape.p2, lShape.p3],
|
|
4085
|
+
strokeColor: "lightblue"
|
|
4086
|
+
});
|
|
4087
|
+
}
|
|
4088
|
+
return graphics;
|
|
4089
|
+
};
|
|
4090
|
+
|
|
4091
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/visualizeIntersectionPoints.ts
|
|
4092
|
+
var visualizeIntersectionPoints = (points, color = "red") => {
|
|
4093
|
+
const graphics = { circles: [] };
|
|
4094
|
+
for (const point of points) {
|
|
4095
|
+
graphics.circles.push({
|
|
4096
|
+
center: {
|
|
4097
|
+
x: point.x,
|
|
4098
|
+
y: point.y
|
|
4099
|
+
},
|
|
4100
|
+
radius: 0.01,
|
|
4101
|
+
fill: color
|
|
4102
|
+
});
|
|
4103
|
+
}
|
|
4104
|
+
return graphics;
|
|
4105
|
+
};
|
|
4106
|
+
|
|
4107
|
+
// lib/solvers/TraceCleanupSolver/visualizeTightRectangle.ts
|
|
4108
|
+
var visualizeTightRectangle = (rectangle) => {
|
|
4109
|
+
const graphics = { rects: [] };
|
|
4110
|
+
graphics.rects.push({
|
|
4111
|
+
center: {
|
|
4112
|
+
x: rectangle.x + rectangle.width / 2,
|
|
4113
|
+
y: rectangle.y + rectangle.height / 2
|
|
4114
|
+
},
|
|
4115
|
+
width: rectangle.width,
|
|
4116
|
+
height: rectangle.height,
|
|
4117
|
+
stroke: "green"
|
|
4118
|
+
});
|
|
4119
|
+
return graphics;
|
|
4120
|
+
};
|
|
4121
|
+
|
|
4122
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/visualizeCandidates.ts
|
|
4123
|
+
var visualizeCandidates = (candidates, color = "gray", intersectionPoints = []) => {
|
|
4124
|
+
const graphics = { lines: [], circles: [] };
|
|
4125
|
+
for (const candidate of candidates) {
|
|
4126
|
+
graphics.lines.push({
|
|
4127
|
+
points: candidate,
|
|
4128
|
+
strokeColor: color
|
|
4129
|
+
});
|
|
4130
|
+
}
|
|
4131
|
+
for (const point of intersectionPoints) {
|
|
4132
|
+
graphics.circles.push({
|
|
4133
|
+
center: point,
|
|
4134
|
+
radius: 0.01,
|
|
4135
|
+
// Larger radius for intersection points
|
|
4136
|
+
fill: "green"
|
|
4137
|
+
});
|
|
4138
|
+
}
|
|
4139
|
+
return graphics;
|
|
4140
|
+
};
|
|
4141
|
+
|
|
4142
|
+
// lib/solvers/TraceCleanupSolver/mergeGraphicsObjects.ts
|
|
4143
|
+
var mergeGraphicsObjects = (objects) => {
|
|
4144
|
+
const merged = {
|
|
4145
|
+
lines: [],
|
|
4146
|
+
points: [],
|
|
4147
|
+
rects: [],
|
|
4148
|
+
circles: [],
|
|
4149
|
+
texts: []
|
|
4150
|
+
};
|
|
4151
|
+
for (const obj of objects) {
|
|
4152
|
+
if (!obj) continue;
|
|
4153
|
+
if (obj.lines) merged.lines.push(...obj.lines);
|
|
4154
|
+
if (obj.points) merged.points.push(...obj.points);
|
|
4155
|
+
if (obj.rects) merged.rects.push(...obj.rects);
|
|
4156
|
+
if (obj.circles) merged.circles.push(...obj.circles);
|
|
4157
|
+
if (obj.texts) merged.texts.push(...obj.texts);
|
|
4158
|
+
}
|
|
4159
|
+
return merged;
|
|
4160
|
+
};
|
|
4161
|
+
|
|
4162
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/visualizeCollision.ts
|
|
4163
|
+
var visualizeCollision = (collisionInfo) => {
|
|
4164
|
+
const collisionGraphics = { circles: [] };
|
|
4165
|
+
if (collisionInfo?.isColliding && collisionInfo.collisionPoint) {
|
|
4166
|
+
collisionGraphics.circles.push({
|
|
4167
|
+
center: collisionInfo.collisionPoint,
|
|
4168
|
+
radius: 0.01,
|
|
4169
|
+
fill: "red"
|
|
4170
|
+
});
|
|
4171
|
+
}
|
|
4172
|
+
return collisionGraphics;
|
|
4173
|
+
};
|
|
4174
|
+
|
|
4175
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/UntangleTraceSubsolver.ts
|
|
4176
|
+
var UntangleTraceSubsolver = class extends BaseSolver {
|
|
4177
|
+
input;
|
|
4178
|
+
lShapesToProcess = [];
|
|
4179
|
+
visualizationMode = "l_shapes";
|
|
4180
|
+
currentLShape = null;
|
|
4181
|
+
intersectionPoints = [];
|
|
4182
|
+
tightRectangle = null;
|
|
4183
|
+
candidates = [];
|
|
4184
|
+
bestRoute = null;
|
|
4185
|
+
lastCollision = null;
|
|
4186
|
+
collidingCandidate = null;
|
|
4187
|
+
rectangleCandidates = [];
|
|
4188
|
+
currentRectangleIndex = 0;
|
|
4189
|
+
isInitialStep = true;
|
|
4190
|
+
currentCandidateIndex = 0;
|
|
4191
|
+
lShapeProcessingStep = "idle";
|
|
4192
|
+
lShapeJustProcessed = false;
|
|
4193
|
+
bestRouteFound = null;
|
|
4194
|
+
constructor(solverInput) {
|
|
4195
|
+
super();
|
|
4196
|
+
this.input = solverInput;
|
|
4197
|
+
this.visualizationMode = "l_shapes";
|
|
4198
|
+
for (const trace of this.input.allTraces) {
|
|
4199
|
+
const lShapes = findAllLShapedTurns(trace.tracePath);
|
|
4200
|
+
this.lShapesToProcess.push(
|
|
4201
|
+
...lShapes.map((l) => ({ ...l, traceId: trace.mspPairId }))
|
|
4202
|
+
);
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
_step() {
|
|
4206
|
+
if (this.isInitialStep) {
|
|
4207
|
+
this.isInitialStep = false;
|
|
4208
|
+
return;
|
|
4209
|
+
}
|
|
4210
|
+
if (this.lShapeJustProcessed) {
|
|
4211
|
+
this._resetAfterLShapProcessing();
|
|
4212
|
+
return;
|
|
4213
|
+
}
|
|
4214
|
+
if (this.lShapesToProcess.length === 0 && this.currentLShape === null) {
|
|
4215
|
+
this.solved = true;
|
|
4216
|
+
return;
|
|
4217
|
+
}
|
|
4218
|
+
switch (this.lShapeProcessingStep) {
|
|
4219
|
+
case "idle":
|
|
4220
|
+
this._handleIdleStep();
|
|
4221
|
+
break;
|
|
4222
|
+
case "intersections":
|
|
4223
|
+
this._handleIntersectionsStep();
|
|
4224
|
+
break;
|
|
4225
|
+
case "rectangle_selection":
|
|
4226
|
+
this._handleRectangleSelectionStep();
|
|
4227
|
+
break;
|
|
4228
|
+
case "candidate_evaluation":
|
|
4229
|
+
this._handleCandidateEvaluationStep();
|
|
4230
|
+
break;
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
_resetAfterLShapProcessing() {
|
|
4234
|
+
this.lShapeProcessingStep = "idle";
|
|
4235
|
+
this.currentLShape = null;
|
|
4236
|
+
this.currentCandidateIndex = 0;
|
|
4237
|
+
this.lShapeJustProcessed = false;
|
|
4238
|
+
this.visualizationMode = "l_shapes";
|
|
4239
|
+
this.intersectionPoints = [];
|
|
4240
|
+
this.tightRectangle = null;
|
|
4241
|
+
this.candidates = [];
|
|
4242
|
+
this.bestRoute = null;
|
|
4243
|
+
this.lastCollision = null;
|
|
4244
|
+
this.collidingCandidate = null;
|
|
4245
|
+
}
|
|
4246
|
+
_handleIdleStep() {
|
|
4247
|
+
this.currentLShape = this.lShapesToProcess.shift();
|
|
4248
|
+
if (!this.currentLShape) {
|
|
4249
|
+
this.solved = true;
|
|
4250
|
+
return;
|
|
4251
|
+
}
|
|
4252
|
+
this.lShapeProcessingStep = "intersections";
|
|
4253
|
+
this.visualizationMode = "l_shapes";
|
|
4254
|
+
}
|
|
4255
|
+
_handleIntersectionsStep() {
|
|
4256
|
+
if (!this.currentLShape.traceId) {
|
|
4257
|
+
this.lShapeProcessingStep = "idle";
|
|
4258
|
+
return;
|
|
4259
|
+
}
|
|
4260
|
+
const allObstacles = getTraceObstacles(
|
|
4261
|
+
this.input.allTraces,
|
|
4262
|
+
this.currentLShape.traceId
|
|
4263
|
+
);
|
|
4264
|
+
const intersections1 = findIntersectionsWithObstacles(
|
|
4265
|
+
this.currentLShape.p1,
|
|
4266
|
+
this.currentLShape.p2,
|
|
4267
|
+
allObstacles
|
|
4268
|
+
);
|
|
4269
|
+
const intersections2 = findIntersectionsWithObstacles(
|
|
4270
|
+
this.currentLShape.p2,
|
|
4271
|
+
this.currentLShape.p3,
|
|
4272
|
+
allObstacles
|
|
4273
|
+
);
|
|
4274
|
+
this.intersectionPoints = [...intersections1, ...intersections2];
|
|
4275
|
+
if (intersections1.length === 0 || intersections2.length === 0) {
|
|
4276
|
+
this.lShapeProcessingStep = "idle";
|
|
4277
|
+
return;
|
|
4278
|
+
}
|
|
4279
|
+
this.rectangleCandidates = generateRectangleCandidates(
|
|
4280
|
+
intersections1,
|
|
4281
|
+
intersections2
|
|
4282
|
+
);
|
|
4283
|
+
this.currentRectangleIndex = 0;
|
|
4284
|
+
this.lShapeProcessingStep = "rectangle_selection";
|
|
4285
|
+
}
|
|
4286
|
+
_handleRectangleSelectionStep() {
|
|
4287
|
+
if (this.currentRectangleIndex >= this.rectangleCandidates.length) {
|
|
4288
|
+
this.lShapeProcessingStep = "idle";
|
|
4289
|
+
return;
|
|
4290
|
+
}
|
|
4291
|
+
const { rect, i1, i2 } = this.rectangleCandidates[this.currentRectangleIndex];
|
|
4292
|
+
this.tightRectangle = rect;
|
|
4293
|
+
this.candidates = generateLShapeRerouteCandidates({
|
|
4294
|
+
lShape: this.currentLShape,
|
|
4295
|
+
rectangle: this.tightRectangle,
|
|
4296
|
+
padding: 2 * this.input.paddingBuffer,
|
|
4297
|
+
interactionPoint1: i1,
|
|
4298
|
+
interactionPoint2: i2
|
|
4299
|
+
});
|
|
4300
|
+
this.currentCandidateIndex = 0;
|
|
4301
|
+
this.lastCollision = null;
|
|
4302
|
+
this.collidingCandidate = null;
|
|
4303
|
+
this.visualizationMode = "candidates";
|
|
4304
|
+
this.lShapeProcessingStep = "candidate_evaluation";
|
|
4305
|
+
}
|
|
4306
|
+
_handleCandidateEvaluationStep() {
|
|
4307
|
+
this.visualizationMode = "candidates";
|
|
4308
|
+
if (this.bestRouteFound) {
|
|
4309
|
+
this._applyBestRoute(this.bestRouteFound);
|
|
4310
|
+
this.bestRouteFound = null;
|
|
4311
|
+
return;
|
|
4312
|
+
}
|
|
4313
|
+
if (this.currentCandidateIndex >= this.candidates.length) {
|
|
4314
|
+
this.currentRectangleIndex++;
|
|
4315
|
+
this.lShapeProcessingStep = "rectangle_selection";
|
|
4316
|
+
return;
|
|
4317
|
+
}
|
|
4318
|
+
const currentCandidate = this.candidates[this.currentCandidateIndex];
|
|
4319
|
+
const collisionResult = isPathColliding(
|
|
4320
|
+
currentCandidate,
|
|
4321
|
+
this.input.allTraces,
|
|
4322
|
+
this.currentLShape.traceId
|
|
4323
|
+
);
|
|
4324
|
+
if (!collisionResult?.isColliding) {
|
|
4325
|
+
this.bestRouteFound = currentCandidate;
|
|
4326
|
+
this.lastCollision = null;
|
|
4327
|
+
this.collidingCandidate = null;
|
|
4328
|
+
} else {
|
|
4329
|
+
this.lastCollision = collisionResult;
|
|
4330
|
+
this.collidingCandidate = currentCandidate;
|
|
4331
|
+
this.currentCandidateIndex++;
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
_applyBestRoute(bestRoute) {
|
|
4335
|
+
this.bestRoute = bestRoute;
|
|
4336
|
+
this.collidingCandidate = null;
|
|
4337
|
+
this.lastCollision = null;
|
|
4338
|
+
const traceIndex = this.input.allTraces.findIndex(
|
|
4339
|
+
(trace) => trace.mspPairId === this.currentLShape.traceId
|
|
4340
|
+
);
|
|
4341
|
+
if (traceIndex !== -1) {
|
|
4342
|
+
const originalTrace = this.input.allTraces[traceIndex];
|
|
4343
|
+
const p2Index = originalTrace.tracePath.findIndex(
|
|
4344
|
+
(p) => p.x === this.currentLShape.p2.x && p.y === this.currentLShape.p2.y
|
|
4345
|
+
);
|
|
4346
|
+
if (p2Index !== -1) {
|
|
4347
|
+
const newTracePath = [
|
|
4348
|
+
...originalTrace.tracePath.slice(0, p2Index),
|
|
4349
|
+
...bestRoute,
|
|
4350
|
+
...originalTrace.tracePath.slice(p2Index + 1)
|
|
4351
|
+
];
|
|
4352
|
+
this.input.allTraces[traceIndex] = {
|
|
4353
|
+
...originalTrace,
|
|
4354
|
+
tracePath: newTracePath
|
|
4355
|
+
};
|
|
4356
|
+
this.lShapesToProcess = this.lShapesToProcess.filter(
|
|
4357
|
+
(l) => l.traceId !== this.currentLShape.traceId
|
|
4358
|
+
);
|
|
4359
|
+
}
|
|
4360
|
+
}
|
|
4361
|
+
this.lShapeJustProcessed = true;
|
|
4362
|
+
}
|
|
4363
|
+
getOutput() {
|
|
4364
|
+
return { traces: this.input.allTraces };
|
|
4365
|
+
}
|
|
4366
|
+
visualize() {
|
|
4367
|
+
switch (this.visualizationMode) {
|
|
4368
|
+
case "l_shapes":
|
|
4369
|
+
return visualizeLSapes(this.lShapesToProcess);
|
|
4370
|
+
case "intersection_points":
|
|
4371
|
+
return mergeGraphicsObjects([
|
|
4372
|
+
this.currentLShape ? visualizeLSapes(this.currentLShape) : void 0,
|
|
4373
|
+
visualizeIntersectionPoints(this.intersectionPoints)
|
|
4374
|
+
]);
|
|
4375
|
+
case "tight_rectangle":
|
|
4376
|
+
return mergeGraphicsObjects([
|
|
4377
|
+
this.currentLShape ? visualizeLSapes(this.currentLShape) : void 0,
|
|
4378
|
+
visualizeIntersectionPoints(this.intersectionPoints),
|
|
4379
|
+
this.tightRectangle ? visualizeTightRectangle(this.tightRectangle) : void 0
|
|
4380
|
+
]);
|
|
4381
|
+
case "candidates": {
|
|
4382
|
+
if (this.lShapeJustProcessed) {
|
|
4383
|
+
const allTracesGraphics2 = { lines: [] };
|
|
4384
|
+
for (const trace of this.input.allTraces) {
|
|
4385
|
+
const isUpdatedTrace = trace.mspPairId === this.currentLShape?.traceId;
|
|
4386
|
+
for (let i = 0; i < trace.tracePath.length - 1; i++) {
|
|
4387
|
+
allTracesGraphics2.lines.push({
|
|
4388
|
+
points: [trace.tracePath[i], trace.tracePath[i + 1]],
|
|
4389
|
+
strokeColor: isUpdatedTrace ? "green" : "#ccc"
|
|
4390
|
+
});
|
|
4391
|
+
}
|
|
4392
|
+
}
|
|
4393
|
+
return allTracesGraphics2;
|
|
4394
|
+
}
|
|
4395
|
+
const allTracesGraphics = { lines: [] };
|
|
4396
|
+
for (const trace of this.input.allTraces) {
|
|
4397
|
+
for (let i = 0; i < trace.tracePath.length - 1; i++) {
|
|
4398
|
+
allTracesGraphics.lines.push({
|
|
4399
|
+
points: [trace.tracePath[i], trace.tracePath[i + 1]],
|
|
4400
|
+
strokeColor: "#ccc"
|
|
4401
|
+
// Light gray for other traces
|
|
4402
|
+
});
|
|
4403
|
+
}
|
|
4404
|
+
}
|
|
4405
|
+
let candidateToDraw;
|
|
4406
|
+
if (this.bestRouteFound) {
|
|
4407
|
+
candidateToDraw = this.bestRouteFound;
|
|
4408
|
+
} else if (this.lastCollision?.isColliding) {
|
|
4409
|
+
candidateToDraw = this.collidingCandidate ?? void 0;
|
|
4410
|
+
} else {
|
|
4411
|
+
if (this.currentCandidateIndex < this.candidates.length) {
|
|
4412
|
+
candidateToDraw = this.candidates[this.currentCandidateIndex];
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
return mergeGraphicsObjects([
|
|
4416
|
+
allTracesGraphics,
|
|
4417
|
+
this.currentLShape ? visualizeLSapes(this.currentLShape) : void 0,
|
|
4418
|
+
this.tightRectangle ? visualizeTightRectangle(this.tightRectangle) : void 0,
|
|
4419
|
+
candidateToDraw ? visualizeCandidates(
|
|
4420
|
+
[candidateToDraw],
|
|
4421
|
+
this.bestRouteFound ? "green" : "blue",
|
|
4422
|
+
this.intersectionPoints
|
|
4423
|
+
) : void 0,
|
|
4424
|
+
this.lastCollision ? visualizeCollision(this.lastCollision) : void 0
|
|
4425
|
+
]);
|
|
4426
|
+
}
|
|
4427
|
+
default:
|
|
4428
|
+
return {};
|
|
4429
|
+
}
|
|
4430
|
+
}
|
|
4431
|
+
};
|
|
4432
|
+
|
|
4433
|
+
// lib/solvers/TraceCleanupSolver/is4PointRectangle.ts
|
|
4434
|
+
var is4PointRectangle = (path) => {
|
|
4435
|
+
if (path.length !== 4) return false;
|
|
4436
|
+
const [p0, p1, p2, p3] = path;
|
|
4437
|
+
const isHVHC = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y && p0.x === p3.x;
|
|
4438
|
+
const isVHVC = p0.x === p1.x && p1.y === p2.y && p2.x === p3.x && p0.y === p3.y;
|
|
4439
|
+
return isHVHC || isVHVC;
|
|
4440
|
+
};
|
|
4441
|
+
|
|
3557
4442
|
// lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts
|
|
3558
4443
|
var TraceCleanupSolver = class extends BaseSolver {
|
|
3559
4444
|
input;
|
|
3560
4445
|
outputTraces;
|
|
3561
4446
|
traceIdQueue;
|
|
3562
4447
|
tracesMap;
|
|
3563
|
-
pipelineStep = "
|
|
4448
|
+
pipelineStep = "untangling_traces";
|
|
3564
4449
|
activeTraceId = null;
|
|
3565
4450
|
// New property
|
|
4451
|
+
activeSubSolver = null;
|
|
3566
4452
|
constructor(solverInput) {
|
|
3567
4453
|
super();
|
|
3568
4454
|
this.input = solverInput;
|
|
3569
4455
|
this.outputTraces = [...solverInput.allTraces];
|
|
3570
4456
|
this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]));
|
|
3571
|
-
this.traceIdQueue = Array.from(
|
|
4457
|
+
this.traceIdQueue = Array.from(
|
|
4458
|
+
solverInput.allTraces.map((e) => e.mspPairId)
|
|
4459
|
+
);
|
|
3572
4460
|
}
|
|
3573
4461
|
_step() {
|
|
3574
|
-
if (this.
|
|
4462
|
+
if (this.activeSubSolver) {
|
|
4463
|
+
this.activeSubSolver.step();
|
|
4464
|
+
if (this.activeSubSolver.solved) {
|
|
4465
|
+
const output = this.activeSubSolver.getOutput();
|
|
4466
|
+
this.outputTraces = output.traces;
|
|
4467
|
+
this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]));
|
|
4468
|
+
this.activeSubSolver = null;
|
|
4469
|
+
this.pipelineStep = "minimizing_turns";
|
|
4470
|
+
} else if (this.activeSubSolver.failed) {
|
|
4471
|
+
this.activeSubSolver = null;
|
|
4472
|
+
this.pipelineStep = "minimizing_turns";
|
|
4473
|
+
}
|
|
4474
|
+
return;
|
|
4475
|
+
}
|
|
4476
|
+
switch (this.pipelineStep) {
|
|
4477
|
+
case "untangling_traces":
|
|
4478
|
+
this._runUntangleTracesStep();
|
|
4479
|
+
break;
|
|
4480
|
+
case "minimizing_turns":
|
|
4481
|
+
this._runMinimizeTurnsStep();
|
|
4482
|
+
break;
|
|
4483
|
+
case "balancing_l_shapes":
|
|
4484
|
+
this._runBalanceLShapesStep();
|
|
4485
|
+
break;
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
_runUntangleTracesStep() {
|
|
4489
|
+
this.activeSubSolver = new UntangleTraceSubsolver({
|
|
4490
|
+
...this.input,
|
|
4491
|
+
allTraces: Array.from(this.tracesMap.values())
|
|
4492
|
+
});
|
|
4493
|
+
}
|
|
4494
|
+
_runMinimizeTurnsStep() {
|
|
4495
|
+
if (this.traceIdQueue.length === 0) {
|
|
3575
4496
|
this.pipelineStep = "balancing_l_shapes";
|
|
3576
|
-
this.traceIdQueue = Array.from(
|
|
4497
|
+
this.traceIdQueue = Array.from(
|
|
4498
|
+
this.input.allTraces.map((e) => e.mspPairId)
|
|
4499
|
+
);
|
|
4500
|
+
return;
|
|
3577
4501
|
}
|
|
3578
|
-
|
|
4502
|
+
this._processTrace("minimizing_turns");
|
|
4503
|
+
}
|
|
4504
|
+
_runBalanceLShapesStep() {
|
|
4505
|
+
if (this.traceIdQueue.length === 0) {
|
|
3579
4506
|
this.solved = true;
|
|
3580
4507
|
return;
|
|
3581
4508
|
}
|
|
4509
|
+
this._processTrace("balancing_l_shapes");
|
|
4510
|
+
}
|
|
4511
|
+
_processTrace(step) {
|
|
3582
4512
|
const targetMspConnectionPairId = this.traceIdQueue.shift();
|
|
3583
4513
|
this.activeTraceId = targetMspConnectionPairId;
|
|
3584
4514
|
const originalTrace = this.tracesMap.get(targetMspConnectionPairId);
|
|
3585
|
-
|
|
3586
|
-
const is4PointRectangle = (path) => {
|
|
3587
|
-
if (path.length !== 4) return false;
|
|
3588
|
-
const [p0, p1, p2, p3] = path;
|
|
3589
|
-
const isHVHC = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y && p0.x === p3.x;
|
|
3590
|
-
const isVHVC = p0.x === p1.x && p1.y === p2.y && p2.x === p3.x && p0.y === p3.y;
|
|
3591
|
-
return isHVHC || isVHVC;
|
|
3592
|
-
};
|
|
3593
|
-
if (is4PointRectangle(tracePath)) {
|
|
4515
|
+
if (is4PointRectangle(originalTrace.tracePath)) {
|
|
3594
4516
|
return;
|
|
3595
4517
|
}
|
|
3596
4518
|
const allTraces = Array.from(this.tracesMap.values());
|
|
3597
4519
|
let updatedTrace;
|
|
3598
|
-
if (
|
|
4520
|
+
if (step === "minimizing_turns") {
|
|
3599
4521
|
updatedTrace = minimizeTurnsWithFilteredLabels({
|
|
3600
4522
|
...this.input,
|
|
3601
4523
|
targetMspConnectionPairId,
|
|
@@ -3617,6 +4539,9 @@ var TraceCleanupSolver = class extends BaseSolver {
|
|
|
3617
4539
|
};
|
|
3618
4540
|
}
|
|
3619
4541
|
visualize() {
|
|
4542
|
+
if (this.activeSubSolver) {
|
|
4543
|
+
return this.activeSubSolver.visualize();
|
|
4544
|
+
}
|
|
3620
4545
|
const graphics = visualizeInputProblem(this.input.inputProblem, {
|
|
3621
4546
|
chipAlpha: 0.1,
|
|
3622
4547
|
connectionAlpha: 0.1
|
|
@@ -3773,7 +4698,6 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
3773
4698
|
{
|
|
3774
4699
|
inputProblem: instance.inputProblem,
|
|
3775
4700
|
allTraces: traces,
|
|
3776
|
-
targetTraceIds: new Set(traces.map((t) => t.mspPairId)),
|
|
3777
4701
|
allLabelPlacements: labelMergingOutput.netLabelPlacements,
|
|
3778
4702
|
mergedLabelNetIdMap: labelMergingOutput.mergedLabelNetIdMap,
|
|
3779
4703
|
paddingBuffer: 0.1
|