@tscircuit/schematic-trace-solver 0.0.44 → 0.0.45
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 +14 -3
- package/dist/index.js +754 -95
- 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/package.json +1 -1
- package/tests/examples/__snapshots__/example29.snap.svg +6 -6
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(
|
|
@@ -3163,12 +3163,21 @@ var LongDistancePairSolver = class extends BaseSolver {
|
|
|
3163
3163
|
};
|
|
3164
3164
|
|
|
3165
3165
|
// lib/solvers/TraceCleanupSolver/hasCollisions.ts
|
|
3166
|
+
import { segmentToBoxMinDistance } from "@tscircuit/math-utils";
|
|
3166
3167
|
var hasCollisions = (pathSegments, obstacles) => {
|
|
3167
3168
|
for (let i = 0; i < pathSegments.length - 1; i++) {
|
|
3168
3169
|
const p1 = pathSegments[i];
|
|
3169
3170
|
const p2 = pathSegments[i + 1];
|
|
3170
3171
|
for (const obstacle of obstacles) {
|
|
3171
|
-
|
|
3172
|
+
const box = {
|
|
3173
|
+
center: {
|
|
3174
|
+
x: obstacle.minX + (obstacle.maxX - obstacle.minX) / 2,
|
|
3175
|
+
y: obstacle.minY + (obstacle.maxY - obstacle.minY) / 2
|
|
3176
|
+
},
|
|
3177
|
+
width: obstacle.maxX - obstacle.minX,
|
|
3178
|
+
height: obstacle.maxY - obstacle.minY
|
|
3179
|
+
};
|
|
3180
|
+
if (segmentToBoxMinDistance(p1, p2, box) <= 0) {
|
|
3172
3181
|
return true;
|
|
3173
3182
|
}
|
|
3174
3183
|
}
|
|
@@ -3218,43 +3227,60 @@ var hasCollisionsWithLabels = (pathSegments, labels) => {
|
|
|
3218
3227
|
return false;
|
|
3219
3228
|
};
|
|
3220
3229
|
|
|
3230
|
+
// lib/solvers/TraceCleanupSolver/recognizeStairStepPattern.ts
|
|
3231
|
+
var recognizeStairStepPattern = (pathToCheck, startIdx) => {
|
|
3232
|
+
if (startIdx >= pathToCheck.length - 3) return -1;
|
|
3233
|
+
let endIdx = startIdx;
|
|
3234
|
+
let isStairStep = true;
|
|
3235
|
+
for (let i = startIdx; i < pathToCheck.length - 2 && i < startIdx + 10; i++) {
|
|
3236
|
+
if (i + 2 >= pathToCheck.length) break;
|
|
3237
|
+
const p1 = pathToCheck[i];
|
|
3238
|
+
const p2 = pathToCheck[i + 1];
|
|
3239
|
+
const p3 = pathToCheck[i + 2];
|
|
3240
|
+
const seg1Vertical = p1.x === p2.x;
|
|
3241
|
+
const seg2Vertical = p2.x === p3.x;
|
|
3242
|
+
if (seg1Vertical === seg2Vertical) {
|
|
3243
|
+
break;
|
|
3244
|
+
}
|
|
3245
|
+
const seg1Direction = seg1Vertical ? Math.sign(p2.y - p1.y) : Math.sign(p2.x - p1.x);
|
|
3246
|
+
if (i > startIdx) {
|
|
3247
|
+
const prevP = pathToCheck[i - 1];
|
|
3248
|
+
const prevSegVertical = prevP.x === p1.x;
|
|
3249
|
+
const prevDirection = prevSegVertical ? Math.sign(p1.y - prevP.y) : Math.sign(p1.x - prevP.x);
|
|
3250
|
+
if (seg1Vertical && prevSegVertical && seg1Direction !== prevDirection || !seg1Vertical && !prevSegVertical && seg1Direction !== prevDirection) {
|
|
3251
|
+
isStairStep = false;
|
|
3252
|
+
break;
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
endIdx = i + 2;
|
|
3256
|
+
}
|
|
3257
|
+
return isStairStep && endIdx - startIdx >= 3 ? endIdx : -1;
|
|
3258
|
+
};
|
|
3259
|
+
|
|
3260
|
+
// lib/solvers/TraceCleanupSolver/isSegmentAnEndpointSegment.ts
|
|
3261
|
+
var isSegmentAnEndpointSegment = (p1, p2, originalPath) => {
|
|
3262
|
+
if (originalPath.length < 2) return false;
|
|
3263
|
+
const originalStart = originalPath[0];
|
|
3264
|
+
const originalEnd = originalPath[originalPath.length - 1];
|
|
3265
|
+
if (p1.x === originalStart.x && p1.y === originalStart.y && p2.x === originalPath[1].x && p2.y === originalPath[1].y) {
|
|
3266
|
+
return true;
|
|
3267
|
+
}
|
|
3268
|
+
if (p1.x === originalPath[originalPath.length - 2].x && p1.y === originalPath[originalPath.length - 2].y && p2.x === originalEnd.x && p2.y === originalEnd.y) {
|
|
3269
|
+
return true;
|
|
3270
|
+
}
|
|
3271
|
+
return false;
|
|
3272
|
+
};
|
|
3273
|
+
|
|
3221
3274
|
// lib/solvers/TraceCleanupSolver/turnMinimization.ts
|
|
3222
3275
|
var minimizeTurns = ({
|
|
3223
3276
|
path,
|
|
3224
3277
|
obstacles,
|
|
3225
|
-
labelBounds
|
|
3278
|
+
labelBounds,
|
|
3279
|
+
originalPath
|
|
3226
3280
|
}) => {
|
|
3227
3281
|
if (path.length <= 2) {
|
|
3228
3282
|
return path;
|
|
3229
3283
|
}
|
|
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
3284
|
let optimizedPath = [...path];
|
|
3259
3285
|
let currentTurns = countTurns(optimizedPath);
|
|
3260
3286
|
let improved = true;
|
|
@@ -3263,6 +3289,17 @@ var minimizeTurns = ({
|
|
|
3263
3289
|
for (let startIdx = 0; startIdx < optimizedPath.length - 3; startIdx++) {
|
|
3264
3290
|
const stairEndIdx = recognizeStairStepPattern(optimizedPath, startIdx);
|
|
3265
3291
|
if (stairEndIdx > 0) {
|
|
3292
|
+
if (isSegmentAnEndpointSegment(
|
|
3293
|
+
optimizedPath[startIdx],
|
|
3294
|
+
optimizedPath[startIdx + 1],
|
|
3295
|
+
originalPath
|
|
3296
|
+
) || isSegmentAnEndpointSegment(
|
|
3297
|
+
optimizedPath[stairEndIdx - 1],
|
|
3298
|
+
optimizedPath[stairEndIdx],
|
|
3299
|
+
originalPath
|
|
3300
|
+
)) {
|
|
3301
|
+
continue;
|
|
3302
|
+
}
|
|
3266
3303
|
const startPoint = optimizedPath[startIdx];
|
|
3267
3304
|
const endPoint = optimizedPath[stairEndIdx];
|
|
3268
3305
|
const connectionOptions = tryConnectPoints(startPoint, endPoint);
|
|
@@ -3297,6 +3334,17 @@ var minimizeTurns = ({
|
|
|
3297
3334
|
for (let removeCount = 1; removeCount <= maxRemove; removeCount++) {
|
|
3298
3335
|
const endIdx = startIdx + removeCount + 1;
|
|
3299
3336
|
if (endIdx >= optimizedPath.length) continue;
|
|
3337
|
+
if (isSegmentAnEndpointSegment(
|
|
3338
|
+
optimizedPath[startIdx],
|
|
3339
|
+
optimizedPath[startIdx + 1],
|
|
3340
|
+
originalPath
|
|
3341
|
+
) || isSegmentAnEndpointSegment(
|
|
3342
|
+
optimizedPath[endIdx - 1],
|
|
3343
|
+
optimizedPath[endIdx],
|
|
3344
|
+
originalPath
|
|
3345
|
+
)) {
|
|
3346
|
+
continue;
|
|
3347
|
+
}
|
|
3300
3348
|
const startPoint = optimizedPath[startIdx];
|
|
3301
3349
|
const endPoint = optimizedPath[endIdx];
|
|
3302
3350
|
const connectionOptions = tryConnectPoints(startPoint, endPoint);
|
|
@@ -3335,6 +3383,9 @@ var minimizeTurns = ({
|
|
|
3335
3383
|
const p1 = optimizedPath[i];
|
|
3336
3384
|
const p2 = optimizedPath[i + 1];
|
|
3337
3385
|
const p3 = optimizedPath[i + 2];
|
|
3386
|
+
if (isSegmentAnEndpointSegment(p1, p2, originalPath) || isSegmentAnEndpointSegment(p2, p3, originalPath)) {
|
|
3387
|
+
continue;
|
|
3388
|
+
}
|
|
3338
3389
|
const allVertical = p1.x === p2.x && p2.x === p3.x;
|
|
3339
3390
|
const allHorizontal = p1.y === p2.y && p2.y === p3.y;
|
|
3340
3391
|
if (allVertical || allHorizontal) {
|
|
@@ -3391,7 +3442,15 @@ var minimizeTurnsWithFilteredLabels = ({
|
|
|
3391
3442
|
};
|
|
3392
3443
|
})
|
|
3393
3444
|
);
|
|
3394
|
-
const
|
|
3445
|
+
const staticObstaclesRaw = getObstacleRects(inputProblem);
|
|
3446
|
+
const PADDING = 0.01;
|
|
3447
|
+
const staticObstacles = staticObstaclesRaw.map((obs) => ({
|
|
3448
|
+
...obs,
|
|
3449
|
+
minX: obs.minX - PADDING,
|
|
3450
|
+
minY: obs.minY - PADDING,
|
|
3451
|
+
maxX: obs.maxX + PADDING,
|
|
3452
|
+
maxY: obs.maxY + PADDING
|
|
3453
|
+
}));
|
|
3395
3454
|
const combinedObstacles = [...staticObstacles, ...traceObstacles];
|
|
3396
3455
|
const originalPath = targetTrace.tracePath;
|
|
3397
3456
|
const filteredLabels = allLabelPlacements.filter((label) => {
|
|
@@ -3410,7 +3469,8 @@ var minimizeTurnsWithFilteredLabels = ({
|
|
|
3410
3469
|
const newPath = minimizeTurns({
|
|
3411
3470
|
path: originalPath,
|
|
3412
3471
|
obstacles: combinedObstacles,
|
|
3413
|
-
labelBounds
|
|
3472
|
+
labelBounds,
|
|
3473
|
+
originalPath
|
|
3414
3474
|
});
|
|
3415
3475
|
return {
|
|
3416
3476
|
...targetTrace,
|
|
@@ -3554,48 +3614,645 @@ var balanceZShapes = ({
|
|
|
3554
3614
|
};
|
|
3555
3615
|
};
|
|
3556
3616
|
|
|
3617
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/findAllLShapedTurns.ts
|
|
3618
|
+
var findAllLShapedTurns = (tracePath) => {
|
|
3619
|
+
const lShapes = [];
|
|
3620
|
+
if (tracePath.length < 3) {
|
|
3621
|
+
return lShapes;
|
|
3622
|
+
}
|
|
3623
|
+
for (let i = 0; i < tracePath.length - 2; i++) {
|
|
3624
|
+
const p1 = tracePath[i];
|
|
3625
|
+
const p2 = tracePath[i + 1];
|
|
3626
|
+
const p3 = tracePath[i + 2];
|
|
3627
|
+
const dx1 = p2.x - p1.x;
|
|
3628
|
+
const dy1 = p2.y - p1.y;
|
|
3629
|
+
const dx2 = p3.x - p2.x;
|
|
3630
|
+
const dy2 = p3.y - p2.y;
|
|
3631
|
+
if ((dx1 === 0 && dy2 === 0 && dy1 !== 0 && dx2 !== 0 || // Vertical then Horizontal
|
|
3632
|
+
dy1 === 0 && dx2 === 0 && dx1 !== 0 && dy2 !== 0) && // Horizontal then Vertical
|
|
3633
|
+
dx1 * dx1 + dy1 * dy1 >= 0.25 && // p1-p2 arm length >= 0.5
|
|
3634
|
+
dx2 * dx2 + dy2 * dy2 >= 0.25) {
|
|
3635
|
+
lShapes.push({ p1, p2, p3 });
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
return lShapes;
|
|
3639
|
+
};
|
|
3640
|
+
|
|
3641
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/getTraceObstacles.ts
|
|
3642
|
+
var getTraceObstacles = (allTraces, excludeTraceId) => {
|
|
3643
|
+
const obstacles = [];
|
|
3644
|
+
for (const trace of allTraces) {
|
|
3645
|
+
if (trace.mspPairId !== excludeTraceId) {
|
|
3646
|
+
obstacles.push({ points: trace.tracePath });
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
return obstacles;
|
|
3650
|
+
};
|
|
3651
|
+
|
|
3652
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/findIntersectionsWithObstacles.ts
|
|
3653
|
+
import { getSegmentIntersection } from "@tscircuit/math-utils/line-intersections";
|
|
3654
|
+
var findIntersectionsWithObstacles = (p1, p2, obstacles) => {
|
|
3655
|
+
const intersections = [];
|
|
3656
|
+
for (const obstacle of obstacles) {
|
|
3657
|
+
const obstaclePath = obstacle.points;
|
|
3658
|
+
for (let i = 0; i < obstaclePath.length - 1; i++) {
|
|
3659
|
+
const o1 = obstaclePath[i];
|
|
3660
|
+
const o2 = obstaclePath[i + 1];
|
|
3661
|
+
if (!o1 || !o2) {
|
|
3662
|
+
continue;
|
|
3663
|
+
}
|
|
3664
|
+
const intersection = getSegmentIntersection(p1, p2, o1, o2);
|
|
3665
|
+
if (intersection) {
|
|
3666
|
+
intersections.push(intersection);
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
return intersections;
|
|
3671
|
+
};
|
|
3672
|
+
|
|
3673
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts
|
|
3674
|
+
var EPS4 = 1e-6;
|
|
3675
|
+
var isVertical2 = (a, b, eps = EPS4) => Math.abs(a.x - b.x) < eps;
|
|
3676
|
+
var generateLShapeRerouteCandidates = ({
|
|
3677
|
+
lShape,
|
|
3678
|
+
rectangle,
|
|
3679
|
+
padding = 0.5,
|
|
3680
|
+
interactionPoint1,
|
|
3681
|
+
interactionPoint2
|
|
3682
|
+
}) => {
|
|
3683
|
+
const { p1, p2, p3 } = lShape;
|
|
3684
|
+
const { x, y, width, height } = rectangle;
|
|
3685
|
+
let c2;
|
|
3686
|
+
let i1_padded = interactionPoint1;
|
|
3687
|
+
let i2_padded = interactionPoint2;
|
|
3688
|
+
if (Math.abs(p2.x - x) < EPS4 && Math.abs(p2.y - (y + height)) < EPS4) {
|
|
3689
|
+
c2 = { x: x + width + padding, y: y - padding };
|
|
3690
|
+
if (isVertical2(p1, p2)) {
|
|
3691
|
+
i1_padded = { x: interactionPoint1.x, y: interactionPoint1.y - padding };
|
|
3692
|
+
} else {
|
|
3693
|
+
i1_padded = { x: interactionPoint1.x + padding, y: interactionPoint1.y };
|
|
3694
|
+
}
|
|
3695
|
+
if (isVertical2(p2, p3)) {
|
|
3696
|
+
i2_padded = { x: interactionPoint2.x, y: interactionPoint2.y - padding };
|
|
3697
|
+
} else {
|
|
3698
|
+
i2_padded = { x: interactionPoint2.x + padding, y: interactionPoint2.y };
|
|
3699
|
+
}
|
|
3700
|
+
} else if (Math.abs(p2.x - (x + width)) < EPS4 && Math.abs(p2.y - (y + height)) < EPS4) {
|
|
3701
|
+
c2 = { x: x - padding, y: y - padding };
|
|
3702
|
+
if (isVertical2(p1, p2)) {
|
|
3703
|
+
i1_padded = { x: interactionPoint1.x, y: interactionPoint1.y - padding };
|
|
3704
|
+
} else {
|
|
3705
|
+
i1_padded = { x: interactionPoint1.x - padding, y: interactionPoint1.y };
|
|
3706
|
+
}
|
|
3707
|
+
if (isVertical2(p2, p3)) {
|
|
3708
|
+
i2_padded = { x: interactionPoint2.x, y: interactionPoint2.y - padding };
|
|
3709
|
+
} else {
|
|
3710
|
+
i2_padded = { x: interactionPoint2.x - padding, y: interactionPoint2.y };
|
|
3711
|
+
}
|
|
3712
|
+
} else if (Math.abs(p2.x - x) < EPS4 && Math.abs(p2.y - y) < EPS4) {
|
|
3713
|
+
c2 = { x: x + width + padding, y: y + height + padding };
|
|
3714
|
+
if (isVertical2(p1, p2)) {
|
|
3715
|
+
i1_padded = { x: interactionPoint1.x, y: interactionPoint1.y + padding };
|
|
3716
|
+
} else {
|
|
3717
|
+
i1_padded = { x: interactionPoint1.x + padding, y: interactionPoint1.y };
|
|
3718
|
+
}
|
|
3719
|
+
if (isVertical2(p2, p3)) {
|
|
3720
|
+
i2_padded = { x: interactionPoint2.x, y: interactionPoint2.y + padding };
|
|
3721
|
+
} else {
|
|
3722
|
+
i2_padded = { x: interactionPoint2.x + padding, y: interactionPoint2.y };
|
|
3723
|
+
}
|
|
3724
|
+
} else if (Math.abs(p2.x - (x + width)) < EPS4 && Math.abs(p2.y - y) < EPS4) {
|
|
3725
|
+
c2 = { x: x - padding, y: y + height + padding };
|
|
3726
|
+
if (isVertical2(p1, p2)) {
|
|
3727
|
+
i1_padded = { x: interactionPoint1.x, y: interactionPoint1.y + padding };
|
|
3728
|
+
} else {
|
|
3729
|
+
i1_padded = { x: interactionPoint1.x - padding, y: interactionPoint1.y };
|
|
3730
|
+
}
|
|
3731
|
+
if (isVertical2(p2, p3)) {
|
|
3732
|
+
i2_padded = { x: interactionPoint2.x, y: interactionPoint2.y + padding };
|
|
3733
|
+
} else {
|
|
3734
|
+
i2_padded = { x: interactionPoint2.x - padding, y: interactionPoint2.y };
|
|
3735
|
+
}
|
|
3736
|
+
} else {
|
|
3737
|
+
return [];
|
|
3738
|
+
}
|
|
3739
|
+
return [[i1_padded, c2, i2_padded]];
|
|
3740
|
+
};
|
|
3741
|
+
|
|
3742
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/isPathColliding.ts
|
|
3743
|
+
import { getSegmentIntersection as getSegmentIntersection2 } from "@tscircuit/math-utils/line-intersections";
|
|
3744
|
+
var isPathColliding = (path, allTraces, traceIdToExclude) => {
|
|
3745
|
+
if (path.length < 2) {
|
|
3746
|
+
return { isColliding: false };
|
|
3747
|
+
}
|
|
3748
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
3749
|
+
const pathSegP1 = path[i];
|
|
3750
|
+
const pathSegQ1 = path[i + 1];
|
|
3751
|
+
for (const existingTrace of allTraces) {
|
|
3752
|
+
if (existingTrace.mspPairId === traceIdToExclude) {
|
|
3753
|
+
continue;
|
|
3754
|
+
}
|
|
3755
|
+
for (let j = 0; j < existingTrace.tracePath.length - 1; j++) {
|
|
3756
|
+
const existingSegP2 = existingTrace.tracePath[j];
|
|
3757
|
+
const existingSegQ2 = existingTrace.tracePath[j + 1];
|
|
3758
|
+
const intersectionPoint = getSegmentIntersection2(
|
|
3759
|
+
pathSegP1,
|
|
3760
|
+
pathSegQ1,
|
|
3761
|
+
existingSegP2,
|
|
3762
|
+
existingSegQ2
|
|
3763
|
+
);
|
|
3764
|
+
if (intersectionPoint) {
|
|
3765
|
+
return {
|
|
3766
|
+
isColliding: true,
|
|
3767
|
+
collidingTraceId: existingTrace.mspPairId,
|
|
3768
|
+
collisionPoint: intersectionPoint
|
|
3769
|
+
};
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
return { isColliding: false };
|
|
3775
|
+
};
|
|
3776
|
+
|
|
3777
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/generateRectangleCandidates.ts
|
|
3778
|
+
var generateRectangleCandidates = (intersections1, intersections2) => {
|
|
3779
|
+
const rectangleCandidates = [];
|
|
3780
|
+
for (const p1 of intersections1) {
|
|
3781
|
+
for (const p2 of intersections2) {
|
|
3782
|
+
const minX = Math.min(p1.x, p2.x);
|
|
3783
|
+
const minY = Math.min(p1.y, p2.y);
|
|
3784
|
+
const maxX = Math.max(p1.x, p2.x);
|
|
3785
|
+
const maxY = Math.max(p1.y, p2.y);
|
|
3786
|
+
const width = maxX - minX;
|
|
3787
|
+
const height = maxY - minY;
|
|
3788
|
+
if (width > 1e-6 && height > 1e-6) {
|
|
3789
|
+
rectangleCandidates.push({
|
|
3790
|
+
rect: {
|
|
3791
|
+
x: minX,
|
|
3792
|
+
y: minY,
|
|
3793
|
+
width,
|
|
3794
|
+
height
|
|
3795
|
+
},
|
|
3796
|
+
i1: p1,
|
|
3797
|
+
i2: p2
|
|
3798
|
+
});
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3802
|
+
return rectangleCandidates;
|
|
3803
|
+
};
|
|
3804
|
+
|
|
3805
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/visualizeLSapes.ts
|
|
3806
|
+
var visualizeLSapes = (lShapes) => {
|
|
3807
|
+
const graphics = { circles: [], lines: [] };
|
|
3808
|
+
const lShapesArray = Array.isArray(lShapes) ? lShapes : [lShapes];
|
|
3809
|
+
for (const lShape of lShapesArray) {
|
|
3810
|
+
graphics.circles.push({
|
|
3811
|
+
center: {
|
|
3812
|
+
x: lShape.p2.x,
|
|
3813
|
+
y: lShape.p2.y
|
|
3814
|
+
},
|
|
3815
|
+
radius: 0.01,
|
|
3816
|
+
fill: "blue"
|
|
3817
|
+
});
|
|
3818
|
+
graphics.lines.push({
|
|
3819
|
+
points: [lShape.p1, lShape.p2, lShape.p3],
|
|
3820
|
+
strokeColor: "lightblue"
|
|
3821
|
+
});
|
|
3822
|
+
}
|
|
3823
|
+
return graphics;
|
|
3824
|
+
};
|
|
3825
|
+
|
|
3826
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/visualizeIntersectionPoints.ts
|
|
3827
|
+
var visualizeIntersectionPoints = (points, color = "red") => {
|
|
3828
|
+
const graphics = { circles: [] };
|
|
3829
|
+
for (const point of points) {
|
|
3830
|
+
graphics.circles.push({
|
|
3831
|
+
center: {
|
|
3832
|
+
x: point.x,
|
|
3833
|
+
y: point.y
|
|
3834
|
+
},
|
|
3835
|
+
radius: 0.01,
|
|
3836
|
+
fill: color
|
|
3837
|
+
});
|
|
3838
|
+
}
|
|
3839
|
+
return graphics;
|
|
3840
|
+
};
|
|
3841
|
+
|
|
3842
|
+
// lib/solvers/TraceCleanupSolver/visualizeTightRectangle.ts
|
|
3843
|
+
var visualizeTightRectangle = (rectangle) => {
|
|
3844
|
+
const graphics = { rects: [] };
|
|
3845
|
+
graphics.rects.push({
|
|
3846
|
+
center: {
|
|
3847
|
+
x: rectangle.x + rectangle.width / 2,
|
|
3848
|
+
y: rectangle.y + rectangle.height / 2
|
|
3849
|
+
},
|
|
3850
|
+
width: rectangle.width,
|
|
3851
|
+
height: rectangle.height,
|
|
3852
|
+
stroke: "green"
|
|
3853
|
+
});
|
|
3854
|
+
return graphics;
|
|
3855
|
+
};
|
|
3856
|
+
|
|
3857
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/visualizeCandidates.ts
|
|
3858
|
+
var visualizeCandidates = (candidates, color = "gray", intersectionPoints = []) => {
|
|
3859
|
+
const graphics = { lines: [], circles: [] };
|
|
3860
|
+
for (const candidate of candidates) {
|
|
3861
|
+
graphics.lines.push({
|
|
3862
|
+
points: candidate,
|
|
3863
|
+
strokeColor: color
|
|
3864
|
+
});
|
|
3865
|
+
}
|
|
3866
|
+
for (const point of intersectionPoints) {
|
|
3867
|
+
graphics.circles.push({
|
|
3868
|
+
center: point,
|
|
3869
|
+
radius: 0.01,
|
|
3870
|
+
// Larger radius for intersection points
|
|
3871
|
+
fill: "green"
|
|
3872
|
+
});
|
|
3873
|
+
}
|
|
3874
|
+
return graphics;
|
|
3875
|
+
};
|
|
3876
|
+
|
|
3877
|
+
// lib/solvers/TraceCleanupSolver/mergeGraphicsObjects.ts
|
|
3878
|
+
var mergeGraphicsObjects = (objects) => {
|
|
3879
|
+
const merged = {
|
|
3880
|
+
lines: [],
|
|
3881
|
+
points: [],
|
|
3882
|
+
rects: [],
|
|
3883
|
+
circles: [],
|
|
3884
|
+
texts: []
|
|
3885
|
+
};
|
|
3886
|
+
for (const obj of objects) {
|
|
3887
|
+
if (!obj) continue;
|
|
3888
|
+
if (obj.lines) merged.lines.push(...obj.lines);
|
|
3889
|
+
if (obj.points) merged.points.push(...obj.points);
|
|
3890
|
+
if (obj.rects) merged.rects.push(...obj.rects);
|
|
3891
|
+
if (obj.circles) merged.circles.push(...obj.circles);
|
|
3892
|
+
if (obj.texts) merged.texts.push(...obj.texts);
|
|
3893
|
+
}
|
|
3894
|
+
return merged;
|
|
3895
|
+
};
|
|
3896
|
+
|
|
3897
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/visualizeCollision.ts
|
|
3898
|
+
var visualizeCollision = (collisionInfo) => {
|
|
3899
|
+
const collisionGraphics = { circles: [] };
|
|
3900
|
+
if (collisionInfo?.isColliding && collisionInfo.collisionPoint) {
|
|
3901
|
+
collisionGraphics.circles.push({
|
|
3902
|
+
center: collisionInfo.collisionPoint,
|
|
3903
|
+
radius: 0.01,
|
|
3904
|
+
fill: "red"
|
|
3905
|
+
});
|
|
3906
|
+
}
|
|
3907
|
+
return collisionGraphics;
|
|
3908
|
+
};
|
|
3909
|
+
|
|
3910
|
+
// lib/solvers/TraceCleanupSolver/sub-solver/UntangleTraceSubsolver.ts
|
|
3911
|
+
var UntangleTraceSubsolver = class extends BaseSolver {
|
|
3912
|
+
input;
|
|
3913
|
+
lShapesToProcess = [];
|
|
3914
|
+
visualizationMode = "l_shapes";
|
|
3915
|
+
currentLShape = null;
|
|
3916
|
+
intersectionPoints = [];
|
|
3917
|
+
tightRectangle = null;
|
|
3918
|
+
candidates = [];
|
|
3919
|
+
bestRoute = null;
|
|
3920
|
+
lastCollision = null;
|
|
3921
|
+
collidingCandidate = null;
|
|
3922
|
+
rectangleCandidates = [];
|
|
3923
|
+
currentRectangleIndex = 0;
|
|
3924
|
+
isInitialStep = true;
|
|
3925
|
+
currentCandidateIndex = 0;
|
|
3926
|
+
lShapeProcessingStep = "idle";
|
|
3927
|
+
lShapeJustProcessed = false;
|
|
3928
|
+
bestRouteFound = null;
|
|
3929
|
+
constructor(solverInput) {
|
|
3930
|
+
super();
|
|
3931
|
+
this.input = solverInput;
|
|
3932
|
+
this.visualizationMode = "l_shapes";
|
|
3933
|
+
for (const trace of this.input.allTraces) {
|
|
3934
|
+
const lShapes = findAllLShapedTurns(trace.tracePath);
|
|
3935
|
+
this.lShapesToProcess.push(
|
|
3936
|
+
...lShapes.map((l) => ({ ...l, traceId: trace.mspPairId }))
|
|
3937
|
+
);
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
_step() {
|
|
3941
|
+
if (this.isInitialStep) {
|
|
3942
|
+
this.isInitialStep = false;
|
|
3943
|
+
return;
|
|
3944
|
+
}
|
|
3945
|
+
if (this.lShapeJustProcessed) {
|
|
3946
|
+
this._resetAfterLShapProcessing();
|
|
3947
|
+
return;
|
|
3948
|
+
}
|
|
3949
|
+
if (this.lShapesToProcess.length === 0 && this.currentLShape === null) {
|
|
3950
|
+
this.solved = true;
|
|
3951
|
+
return;
|
|
3952
|
+
}
|
|
3953
|
+
switch (this.lShapeProcessingStep) {
|
|
3954
|
+
case "idle":
|
|
3955
|
+
this._handleIdleStep();
|
|
3956
|
+
break;
|
|
3957
|
+
case "intersections":
|
|
3958
|
+
this._handleIntersectionsStep();
|
|
3959
|
+
break;
|
|
3960
|
+
case "rectangle_selection":
|
|
3961
|
+
this._handleRectangleSelectionStep();
|
|
3962
|
+
break;
|
|
3963
|
+
case "candidate_evaluation":
|
|
3964
|
+
this._handleCandidateEvaluationStep();
|
|
3965
|
+
break;
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
_resetAfterLShapProcessing() {
|
|
3969
|
+
this.lShapeProcessingStep = "idle";
|
|
3970
|
+
this.currentLShape = null;
|
|
3971
|
+
this.currentCandidateIndex = 0;
|
|
3972
|
+
this.lShapeJustProcessed = false;
|
|
3973
|
+
this.visualizationMode = "l_shapes";
|
|
3974
|
+
this.intersectionPoints = [];
|
|
3975
|
+
this.tightRectangle = null;
|
|
3976
|
+
this.candidates = [];
|
|
3977
|
+
this.bestRoute = null;
|
|
3978
|
+
this.lastCollision = null;
|
|
3979
|
+
this.collidingCandidate = null;
|
|
3980
|
+
}
|
|
3981
|
+
_handleIdleStep() {
|
|
3982
|
+
this.currentLShape = this.lShapesToProcess.shift();
|
|
3983
|
+
if (!this.currentLShape) {
|
|
3984
|
+
this.solved = true;
|
|
3985
|
+
return;
|
|
3986
|
+
}
|
|
3987
|
+
this.lShapeProcessingStep = "intersections";
|
|
3988
|
+
this.visualizationMode = "l_shapes";
|
|
3989
|
+
}
|
|
3990
|
+
_handleIntersectionsStep() {
|
|
3991
|
+
if (!this.currentLShape.traceId) {
|
|
3992
|
+
this.lShapeProcessingStep = "idle";
|
|
3993
|
+
return;
|
|
3994
|
+
}
|
|
3995
|
+
const allObstacles = getTraceObstacles(
|
|
3996
|
+
this.input.allTraces,
|
|
3997
|
+
this.currentLShape.traceId
|
|
3998
|
+
);
|
|
3999
|
+
const intersections1 = findIntersectionsWithObstacles(
|
|
4000
|
+
this.currentLShape.p1,
|
|
4001
|
+
this.currentLShape.p2,
|
|
4002
|
+
allObstacles
|
|
4003
|
+
);
|
|
4004
|
+
const intersections2 = findIntersectionsWithObstacles(
|
|
4005
|
+
this.currentLShape.p2,
|
|
4006
|
+
this.currentLShape.p3,
|
|
4007
|
+
allObstacles
|
|
4008
|
+
);
|
|
4009
|
+
this.intersectionPoints = [...intersections1, ...intersections2];
|
|
4010
|
+
if (intersections1.length === 0 || intersections2.length === 0) {
|
|
4011
|
+
this.lShapeProcessingStep = "idle";
|
|
4012
|
+
return;
|
|
4013
|
+
}
|
|
4014
|
+
this.rectangleCandidates = generateRectangleCandidates(
|
|
4015
|
+
intersections1,
|
|
4016
|
+
intersections2
|
|
4017
|
+
);
|
|
4018
|
+
this.currentRectangleIndex = 0;
|
|
4019
|
+
this.lShapeProcessingStep = "rectangle_selection";
|
|
4020
|
+
}
|
|
4021
|
+
_handleRectangleSelectionStep() {
|
|
4022
|
+
if (this.currentRectangleIndex >= this.rectangleCandidates.length) {
|
|
4023
|
+
this.lShapeProcessingStep = "idle";
|
|
4024
|
+
return;
|
|
4025
|
+
}
|
|
4026
|
+
const { rect, i1, i2 } = this.rectangleCandidates[this.currentRectangleIndex];
|
|
4027
|
+
this.tightRectangle = rect;
|
|
4028
|
+
this.candidates = generateLShapeRerouteCandidates({
|
|
4029
|
+
lShape: this.currentLShape,
|
|
4030
|
+
rectangle: this.tightRectangle,
|
|
4031
|
+
padding: 2 * this.input.paddingBuffer,
|
|
4032
|
+
interactionPoint1: i1,
|
|
4033
|
+
interactionPoint2: i2
|
|
4034
|
+
});
|
|
4035
|
+
this.currentCandidateIndex = 0;
|
|
4036
|
+
this.lastCollision = null;
|
|
4037
|
+
this.collidingCandidate = null;
|
|
4038
|
+
this.visualizationMode = "candidates";
|
|
4039
|
+
this.lShapeProcessingStep = "candidate_evaluation";
|
|
4040
|
+
}
|
|
4041
|
+
_handleCandidateEvaluationStep() {
|
|
4042
|
+
this.visualizationMode = "candidates";
|
|
4043
|
+
if (this.bestRouteFound) {
|
|
4044
|
+
this._applyBestRoute(this.bestRouteFound);
|
|
4045
|
+
this.bestRouteFound = null;
|
|
4046
|
+
return;
|
|
4047
|
+
}
|
|
4048
|
+
if (this.currentCandidateIndex >= this.candidates.length) {
|
|
4049
|
+
this.currentRectangleIndex++;
|
|
4050
|
+
this.lShapeProcessingStep = "rectangle_selection";
|
|
4051
|
+
return;
|
|
4052
|
+
}
|
|
4053
|
+
const currentCandidate = this.candidates[this.currentCandidateIndex];
|
|
4054
|
+
const collisionResult = isPathColliding(
|
|
4055
|
+
currentCandidate,
|
|
4056
|
+
this.input.allTraces,
|
|
4057
|
+
this.currentLShape.traceId
|
|
4058
|
+
);
|
|
4059
|
+
if (!collisionResult?.isColliding) {
|
|
4060
|
+
this.bestRouteFound = currentCandidate;
|
|
4061
|
+
this.lastCollision = null;
|
|
4062
|
+
this.collidingCandidate = null;
|
|
4063
|
+
} else {
|
|
4064
|
+
this.lastCollision = collisionResult;
|
|
4065
|
+
this.collidingCandidate = currentCandidate;
|
|
4066
|
+
this.currentCandidateIndex++;
|
|
4067
|
+
}
|
|
4068
|
+
}
|
|
4069
|
+
_applyBestRoute(bestRoute) {
|
|
4070
|
+
this.bestRoute = bestRoute;
|
|
4071
|
+
this.collidingCandidate = null;
|
|
4072
|
+
this.lastCollision = null;
|
|
4073
|
+
const traceIndex = this.input.allTraces.findIndex(
|
|
4074
|
+
(trace) => trace.mspPairId === this.currentLShape.traceId
|
|
4075
|
+
);
|
|
4076
|
+
if (traceIndex !== -1) {
|
|
4077
|
+
const originalTrace = this.input.allTraces[traceIndex];
|
|
4078
|
+
const p2Index = originalTrace.tracePath.findIndex(
|
|
4079
|
+
(p) => p.x === this.currentLShape.p2.x && p.y === this.currentLShape.p2.y
|
|
4080
|
+
);
|
|
4081
|
+
if (p2Index !== -1) {
|
|
4082
|
+
const newTracePath = [
|
|
4083
|
+
...originalTrace.tracePath.slice(0, p2Index),
|
|
4084
|
+
...bestRoute,
|
|
4085
|
+
...originalTrace.tracePath.slice(p2Index + 1)
|
|
4086
|
+
];
|
|
4087
|
+
this.input.allTraces[traceIndex] = {
|
|
4088
|
+
...originalTrace,
|
|
4089
|
+
tracePath: newTracePath
|
|
4090
|
+
};
|
|
4091
|
+
this.lShapesToProcess = this.lShapesToProcess.filter(
|
|
4092
|
+
(l) => l.traceId !== this.currentLShape.traceId
|
|
4093
|
+
);
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
this.lShapeJustProcessed = true;
|
|
4097
|
+
}
|
|
4098
|
+
getOutput() {
|
|
4099
|
+
return { traces: this.input.allTraces };
|
|
4100
|
+
}
|
|
4101
|
+
visualize() {
|
|
4102
|
+
switch (this.visualizationMode) {
|
|
4103
|
+
case "l_shapes":
|
|
4104
|
+
return visualizeLSapes(this.lShapesToProcess);
|
|
4105
|
+
case "intersection_points":
|
|
4106
|
+
return mergeGraphicsObjects([
|
|
4107
|
+
this.currentLShape ? visualizeLSapes(this.currentLShape) : void 0,
|
|
4108
|
+
visualizeIntersectionPoints(this.intersectionPoints)
|
|
4109
|
+
]);
|
|
4110
|
+
case "tight_rectangle":
|
|
4111
|
+
return mergeGraphicsObjects([
|
|
4112
|
+
this.currentLShape ? visualizeLSapes(this.currentLShape) : void 0,
|
|
4113
|
+
visualizeIntersectionPoints(this.intersectionPoints),
|
|
4114
|
+
this.tightRectangle ? visualizeTightRectangle(this.tightRectangle) : void 0
|
|
4115
|
+
]);
|
|
4116
|
+
case "candidates": {
|
|
4117
|
+
if (this.lShapeJustProcessed) {
|
|
4118
|
+
const allTracesGraphics2 = { lines: [] };
|
|
4119
|
+
for (const trace of this.input.allTraces) {
|
|
4120
|
+
const isUpdatedTrace = trace.mspPairId === this.currentLShape?.traceId;
|
|
4121
|
+
for (let i = 0; i < trace.tracePath.length - 1; i++) {
|
|
4122
|
+
allTracesGraphics2.lines.push({
|
|
4123
|
+
points: [trace.tracePath[i], trace.tracePath[i + 1]],
|
|
4124
|
+
strokeColor: isUpdatedTrace ? "green" : "#ccc"
|
|
4125
|
+
});
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
return allTracesGraphics2;
|
|
4129
|
+
}
|
|
4130
|
+
const allTracesGraphics = { lines: [] };
|
|
4131
|
+
for (const trace of this.input.allTraces) {
|
|
4132
|
+
for (let i = 0; i < trace.tracePath.length - 1; i++) {
|
|
4133
|
+
allTracesGraphics.lines.push({
|
|
4134
|
+
points: [trace.tracePath[i], trace.tracePath[i + 1]],
|
|
4135
|
+
strokeColor: "#ccc"
|
|
4136
|
+
// Light gray for other traces
|
|
4137
|
+
});
|
|
4138
|
+
}
|
|
4139
|
+
}
|
|
4140
|
+
let candidateToDraw;
|
|
4141
|
+
if (this.bestRouteFound) {
|
|
4142
|
+
candidateToDraw = this.bestRouteFound;
|
|
4143
|
+
} else if (this.lastCollision?.isColliding) {
|
|
4144
|
+
candidateToDraw = this.collidingCandidate ?? void 0;
|
|
4145
|
+
} else {
|
|
4146
|
+
if (this.currentCandidateIndex < this.candidates.length) {
|
|
4147
|
+
candidateToDraw = this.candidates[this.currentCandidateIndex];
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
return mergeGraphicsObjects([
|
|
4151
|
+
allTracesGraphics,
|
|
4152
|
+
this.currentLShape ? visualizeLSapes(this.currentLShape) : void 0,
|
|
4153
|
+
this.tightRectangle ? visualizeTightRectangle(this.tightRectangle) : void 0,
|
|
4154
|
+
candidateToDraw ? visualizeCandidates(
|
|
4155
|
+
[candidateToDraw],
|
|
4156
|
+
this.bestRouteFound ? "green" : "blue",
|
|
4157
|
+
this.intersectionPoints
|
|
4158
|
+
) : void 0,
|
|
4159
|
+
this.lastCollision ? visualizeCollision(this.lastCollision) : void 0
|
|
4160
|
+
]);
|
|
4161
|
+
}
|
|
4162
|
+
default:
|
|
4163
|
+
return {};
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4166
|
+
};
|
|
4167
|
+
|
|
4168
|
+
// lib/solvers/TraceCleanupSolver/is4PointRectangle.ts
|
|
4169
|
+
var is4PointRectangle = (path) => {
|
|
4170
|
+
if (path.length !== 4) return false;
|
|
4171
|
+
const [p0, p1, p2, p3] = path;
|
|
4172
|
+
const isHVHC = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y && p0.x === p3.x;
|
|
4173
|
+
const isVHVC = p0.x === p1.x && p1.y === p2.y && p2.x === p3.x && p0.y === p3.y;
|
|
4174
|
+
return isHVHC || isVHVC;
|
|
4175
|
+
};
|
|
4176
|
+
|
|
3557
4177
|
// lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts
|
|
3558
4178
|
var TraceCleanupSolver = class extends BaseSolver {
|
|
3559
4179
|
input;
|
|
3560
4180
|
outputTraces;
|
|
3561
4181
|
traceIdQueue;
|
|
3562
4182
|
tracesMap;
|
|
3563
|
-
pipelineStep = "
|
|
4183
|
+
pipelineStep = "untangling_traces";
|
|
3564
4184
|
activeTraceId = null;
|
|
3565
4185
|
// New property
|
|
4186
|
+
activeSubSolver = null;
|
|
3566
4187
|
constructor(solverInput) {
|
|
3567
4188
|
super();
|
|
3568
4189
|
this.input = solverInput;
|
|
3569
4190
|
this.outputTraces = [...solverInput.allTraces];
|
|
3570
4191
|
this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]));
|
|
3571
|
-
this.traceIdQueue = Array.from(
|
|
4192
|
+
this.traceIdQueue = Array.from(
|
|
4193
|
+
solverInput.allTraces.map((e) => e.mspPairId)
|
|
4194
|
+
);
|
|
3572
4195
|
}
|
|
3573
4196
|
_step() {
|
|
3574
|
-
if (this.
|
|
4197
|
+
if (this.activeSubSolver) {
|
|
4198
|
+
this.activeSubSolver.step();
|
|
4199
|
+
if (this.activeSubSolver.solved) {
|
|
4200
|
+
const output = this.activeSubSolver.getOutput();
|
|
4201
|
+
this.outputTraces = output.traces;
|
|
4202
|
+
this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]));
|
|
4203
|
+
this.activeSubSolver = null;
|
|
4204
|
+
this.pipelineStep = "minimizing_turns";
|
|
4205
|
+
} else if (this.activeSubSolver.failed) {
|
|
4206
|
+
this.activeSubSolver = null;
|
|
4207
|
+
this.pipelineStep = "minimizing_turns";
|
|
4208
|
+
}
|
|
4209
|
+
return;
|
|
4210
|
+
}
|
|
4211
|
+
switch (this.pipelineStep) {
|
|
4212
|
+
case "untangling_traces":
|
|
4213
|
+
this._runUntangleTracesStep();
|
|
4214
|
+
break;
|
|
4215
|
+
case "minimizing_turns":
|
|
4216
|
+
this._runMinimizeTurnsStep();
|
|
4217
|
+
break;
|
|
4218
|
+
case "balancing_l_shapes":
|
|
4219
|
+
this._runBalanceLShapesStep();
|
|
4220
|
+
break;
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
_runUntangleTracesStep() {
|
|
4224
|
+
this.activeSubSolver = new UntangleTraceSubsolver({
|
|
4225
|
+
...this.input,
|
|
4226
|
+
allTraces: Array.from(this.tracesMap.values())
|
|
4227
|
+
});
|
|
4228
|
+
}
|
|
4229
|
+
_runMinimizeTurnsStep() {
|
|
4230
|
+
if (this.traceIdQueue.length === 0) {
|
|
3575
4231
|
this.pipelineStep = "balancing_l_shapes";
|
|
3576
|
-
this.traceIdQueue = Array.from(
|
|
4232
|
+
this.traceIdQueue = Array.from(
|
|
4233
|
+
this.input.allTraces.map((e) => e.mspPairId)
|
|
4234
|
+
);
|
|
4235
|
+
return;
|
|
3577
4236
|
}
|
|
3578
|
-
|
|
4237
|
+
this._processTrace("minimizing_turns");
|
|
4238
|
+
}
|
|
4239
|
+
_runBalanceLShapesStep() {
|
|
4240
|
+
if (this.traceIdQueue.length === 0) {
|
|
3579
4241
|
this.solved = true;
|
|
3580
4242
|
return;
|
|
3581
4243
|
}
|
|
4244
|
+
this._processTrace("balancing_l_shapes");
|
|
4245
|
+
}
|
|
4246
|
+
_processTrace(step) {
|
|
3582
4247
|
const targetMspConnectionPairId = this.traceIdQueue.shift();
|
|
3583
4248
|
this.activeTraceId = targetMspConnectionPairId;
|
|
3584
4249
|
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)) {
|
|
4250
|
+
if (is4PointRectangle(originalTrace.tracePath)) {
|
|
3594
4251
|
return;
|
|
3595
4252
|
}
|
|
3596
4253
|
const allTraces = Array.from(this.tracesMap.values());
|
|
3597
4254
|
let updatedTrace;
|
|
3598
|
-
if (
|
|
4255
|
+
if (step === "minimizing_turns") {
|
|
3599
4256
|
updatedTrace = minimizeTurnsWithFilteredLabels({
|
|
3600
4257
|
...this.input,
|
|
3601
4258
|
targetMspConnectionPairId,
|
|
@@ -3617,6 +4274,9 @@ var TraceCleanupSolver = class extends BaseSolver {
|
|
|
3617
4274
|
};
|
|
3618
4275
|
}
|
|
3619
4276
|
visualize() {
|
|
4277
|
+
if (this.activeSubSolver) {
|
|
4278
|
+
return this.activeSubSolver.visualize();
|
|
4279
|
+
}
|
|
3620
4280
|
const graphics = visualizeInputProblem(this.input.inputProblem, {
|
|
3621
4281
|
chipAlpha: 0.1,
|
|
3622
4282
|
connectionAlpha: 0.1
|
|
@@ -3773,7 +4433,6 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
3773
4433
|
{
|
|
3774
4434
|
inputProblem: instance.inputProblem,
|
|
3775
4435
|
allTraces: traces,
|
|
3776
|
-
targetTraceIds: new Set(traces.map((t) => t.mspPairId)),
|
|
3777
4436
|
allLabelPlacements: labelMergingOutput.netLabelPlacements,
|
|
3778
4437
|
mergedLabelNetIdMap: labelMergingOutput.mergedLabelNetIdMap,
|
|
3779
4438
|
paddingBuffer: 0.1
|