@tscircuit/schematic-trace-solver 0.0.43 → 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.
Files changed (27) hide show
  1. package/dist/index.d.ts +17 -3
  2. package/dist/index.js +809 -86
  3. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +0 -1
  4. package/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +83 -36
  5. package/lib/solvers/TraceCleanupSolver/hasCollisions.ts +16 -4
  6. package/lib/solvers/TraceCleanupSolver/is4PointRectangle.ts +17 -0
  7. package/lib/solvers/TraceCleanupSolver/isSegmentAnEndpointSegment.ts +36 -0
  8. package/lib/solvers/TraceCleanupSolver/mergeGraphicsObjects.ts +28 -0
  9. package/lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +18 -1
  10. package/lib/solvers/TraceCleanupSolver/recognizeStairStepPattern.ts +56 -0
  11. package/lib/solvers/TraceCleanupSolver/sub-solver/UntangleTraceSubsolver.ts +370 -0
  12. package/lib/solvers/TraceCleanupSolver/sub-solver/findAllLShapedTurns.ts +48 -0
  13. package/lib/solvers/TraceCleanupSolver/sub-solver/findIntersectionsWithObstacles.ts +36 -0
  14. package/lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts +107 -0
  15. package/lib/solvers/TraceCleanupSolver/sub-solver/generateRectangleCandidates.ts +55 -0
  16. package/lib/solvers/TraceCleanupSolver/sub-solver/getTraceObstacles.ts +25 -0
  17. package/lib/solvers/TraceCleanupSolver/sub-solver/isPathColliding.ts +58 -0
  18. package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeCandidates.ts +33 -0
  19. package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeCollision.ts +20 -0
  20. package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeIntersectionPoints.ts +26 -0
  21. package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeLSapes.ts +33 -0
  22. package/lib/solvers/TraceCleanupSolver/turnMinimization.ts +50 -56
  23. package/lib/solvers/TraceCleanupSolver/visualizeTightRectangle.ts +24 -0
  24. package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts +83 -0
  25. package/package.json +1 -1
  26. package/tests/examples/__snapshots__/example16.snap.svg +4 -4
  27. package/tests/examples/__snapshots__/example29.snap.svg +7 -7
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 EPS4 = 1e-9;
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) < -EPS4) return true;
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) < -EPS4) return true;
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) < EPS4 || Math.abs(p1.y - p2.y) < EPS4) {
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 EPS4 = 1e-9;
814
- const samePoint = (p, q) => Math.abs(p.x - q.x) < EPS4 && Math.abs(p.y - q.y) < EPS4;
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: EPS4 = 1e-6
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 isVertical2 = Math.abs(start.x - end.x) < EPS4;
1019
- const isHorizontal2 = Math.abs(start.y - end.y) < EPS4;
1020
- if (!isVertical2 && !isHorizontal2) return;
1021
- const segDir = isVertical2 ? end.y > start.y ? 1 : -1 : end.x > start.x ? 1 : -1;
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 (isVertical2) {
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 (isVertical2) {
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 EPS4 = 1e-6;
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) < EPS4;
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: EPS4
1118
+ EPS: EPS5
1119
1119
  });
1120
1120
  } else {
1121
1121
  const start = pts[si];
1122
1122
  const end = pts[si + 1];
1123
- const isVertical2 = Math.abs(start.x - end.x) < EPS4;
1124
- const isHorizontal2 = Math.abs(start.y - end.y) < EPS4;
1125
- if (!isVertical2 && !isHorizontal2) continue;
1126
- if (isVertical2) {
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 {
@@ -1188,6 +1188,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1188
1188
  */
1189
1189
  traceNetIslands = {};
1190
1190
  correctedTraceMap = {};
1191
+ cleanupPhase = null;
1191
1192
  constructor(params) {
1192
1193
  super();
1193
1194
  this.inputProblem = params.inputProblem;
@@ -1217,7 +1218,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1217
1218
  return islands;
1218
1219
  }
1219
1220
  findNextOverlapIssue() {
1220
- const EPS4 = 2e-3;
1221
+ const EPS5 = 2e-3;
1221
1222
  const netIds = Object.keys(this.traceNetIslands);
1222
1223
  for (let i = 0; i < netIds.length; i++) {
1223
1224
  for (let j = i + 1; j < netIds.length; j++) {
@@ -1235,7 +1236,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1235
1236
  const minB = Math.min(b1, b2);
1236
1237
  const maxB = Math.max(b1, b2);
1237
1238
  const overlap = Math.min(maxA, maxB) - Math.max(minA, minB);
1238
- return overlap > EPS4;
1239
+ return overlap > EPS5;
1239
1240
  };
1240
1241
  for (let pa = 0; pa < pathsA.length; pa++) {
1241
1242
  const pathA = pathsA[pa];
@@ -1243,8 +1244,8 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1243
1244
  for (let sa = 0; sa < ptsA.length - 1; sa++) {
1244
1245
  const a1 = ptsA[sa];
1245
1246
  const a2 = ptsA[sa + 1];
1246
- const aVert = Math.abs(a1.x - a2.x) < EPS4;
1247
- const aHorz = Math.abs(a1.y - a2.y) < EPS4;
1247
+ const aVert = Math.abs(a1.x - a2.x) < EPS5;
1248
+ const aHorz = Math.abs(a1.y - a2.y) < EPS5;
1248
1249
  if (!aVert && !aHorz) continue;
1249
1250
  for (let pb = 0; pb < pathsB.length; pb++) {
1250
1251
  const pathB = pathsB[pb];
@@ -1252,11 +1253,11 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1252
1253
  for (let sb = 0; sb < ptsB.length - 1; sb++) {
1253
1254
  const b1 = ptsB[sb];
1254
1255
  const b2 = ptsB[sb + 1];
1255
- const bVert = Math.abs(b1.x - b2.x) < EPS4;
1256
- const bHorz = Math.abs(b1.y - b2.y) < EPS4;
1256
+ const bVert = Math.abs(b1.x - b2.x) < EPS5;
1257
+ const bHorz = Math.abs(b1.y - b2.y) < EPS5;
1257
1258
  if (!bVert && !bHorz) continue;
1258
1259
  if (aVert && bVert) {
1259
- if (Math.abs(a1.x - b1.x) < EPS4) {
1260
+ if (Math.abs(a1.x - b1.x) < EPS5) {
1260
1261
  if (overlaps1D(a1.y, a2.y, b1.y, b2.y)) {
1261
1262
  const keyA = `${pa}:${sa}`;
1262
1263
  const keyB = `${pb}:${sb}`;
@@ -1277,7 +1278,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1277
1278
  }
1278
1279
  }
1279
1280
  } else if (aHorz && bHorz) {
1280
- if (Math.abs(a1.y - b1.y) < EPS4) {
1281
+ if (Math.abs(a1.y - b1.y) < EPS5) {
1281
1282
  if (overlaps1D(a1.x, a2.x, b1.x, b2.x)) {
1282
1283
  const keyA = `${pa}:${sa}`;
1283
1284
  const keyB = `${pb}:${sb}`;
@@ -1314,6 +1315,47 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1314
1315
  }
1315
1316
  return null;
1316
1317
  }
1318
+ findNextDiagonalSegment() {
1319
+ const EPS5 = 2e-3;
1320
+ for (const mspPairId in this.correctedTraceMap) {
1321
+ const tracePath = this.correctedTraceMap[mspPairId].tracePath;
1322
+ for (let i = 0; i < tracePath.length - 1; i++) {
1323
+ const p1 = tracePath[i];
1324
+ const p2 = tracePath[i + 1];
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
+ return { mspPairId, tracePath, i, p1, p2 };
1329
+ }
1330
+ }
1331
+ }
1332
+ return null;
1333
+ }
1334
+ findAndFixNextDiagonalSegment() {
1335
+ const diagonalInfo = this.findNextDiagonalSegment();
1336
+ if (!diagonalInfo) {
1337
+ return false;
1338
+ }
1339
+ const { mspPairId, tracePath, i, p1, p2 } = diagonalInfo;
1340
+ const EPS5 = 2e-3;
1341
+ const p0 = i > 0 ? tracePath[i - 1] : null;
1342
+ const p3 = i + 2 < tracePath.length ? tracePath[i + 2] : null;
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
+ const elbow1 = { x: p1.x, y: p2.y };
1348
+ const elbow2 = { x: p2.x, y: p1.y };
1349
+ let score1 = 0;
1350
+ if (prevIsVertical) score1++;
1351
+ if (nextIsHorizontal) score1++;
1352
+ let score2 = 0;
1353
+ if (prevIsHorizontal) score2++;
1354
+ if (nextIsVertical) score2++;
1355
+ const elbowPoint = score1 < score2 ? elbow1 : elbow2;
1356
+ tracePath.splice(i + 1, 0, elbowPoint);
1357
+ return true;
1358
+ }
1317
1359
  _step() {
1318
1360
  if (this.activeSubSolver?.solved) {
1319
1361
  for (const [mspPairId, newTrace] of Object.entries(
@@ -1330,6 +1372,17 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1330
1372
  }
1331
1373
  const overlapIssue = this.findNextOverlapIssue();
1332
1374
  if (overlapIssue === null) {
1375
+ if (this.cleanupPhase === null) {
1376
+ this.cleanupPhase = "diagonals";
1377
+ }
1378
+ if (this.cleanupPhase === "diagonals") {
1379
+ const fixedDiagonal = this.findAndFixNextDiagonalSegment();
1380
+ if (!fixedDiagonal) {
1381
+ this.cleanupPhase = "done";
1382
+ this.solved = true;
1383
+ }
1384
+ return;
1385
+ }
1333
1386
  this.solved = true;
1334
1387
  return;
1335
1388
  }
@@ -1344,12 +1397,23 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1344
1397
  return this.activeSubSolver.visualize();
1345
1398
  }
1346
1399
  const graphics = visualizeInputProblem(this.inputProblem);
1400
+ graphics.circles = graphics.circles || [];
1347
1401
  for (const trace of Object.values(this.correctedTraceMap)) {
1348
1402
  graphics.lines.push({
1349
1403
  points: trace.tracePath,
1350
1404
  strokeColor: "purple"
1351
1405
  });
1352
1406
  }
1407
+ if (this.cleanupPhase === "diagonals") {
1408
+ const diagonalInfo = this.findNextDiagonalSegment();
1409
+ if (diagonalInfo) {
1410
+ graphics.lines.push({
1411
+ points: [diagonalInfo.p1, diagonalInfo.p2],
1412
+ strokeColor: "red",
1413
+ strokeWidth: 0.05
1414
+ });
1415
+ }
1416
+ }
1353
1417
  return graphics;
1354
1418
  }
1355
1419
  };
@@ -1443,24 +1507,24 @@ function getRectBounds(center, w, h) {
1443
1507
  }
1444
1508
 
1445
1509
  // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions.ts
1446
- function segmentIntersectsRect2(p1, p2, rect, EPS4 = 1e-9) {
1447
- const isVert = Math.abs(p1.x - p2.x) < EPS4;
1448
- const isHorz = Math.abs(p1.y - p2.y) < EPS4;
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;
1449
1513
  if (!isVert && !isHorz) return false;
1450
1514
  if (isVert) {
1451
1515
  const x = p1.x;
1452
- if (x < rect.minX - EPS4 || x > rect.maxX + EPS4) return false;
1516
+ if (x < rect.minX - EPS5 || x > rect.maxX + EPS5) return false;
1453
1517
  const segMinY = Math.min(p1.y, p2.y);
1454
1518
  const segMaxY = Math.max(p1.y, p2.y);
1455
1519
  const overlap = Math.min(segMaxY, rect.maxY) - Math.max(segMinY, rect.minY);
1456
- return overlap > EPS4;
1520
+ return overlap > EPS5;
1457
1521
  } else {
1458
1522
  const y = p1.y;
1459
- if (y < rect.minY - EPS4 || y > rect.maxY + EPS4) return false;
1523
+ if (y < rect.minY - EPS5 || y > rect.maxY + EPS5) return false;
1460
1524
  const segMinX = Math.min(p1.x, p2.x);
1461
1525
  const segMaxX = Math.max(p1.x, p2.x);
1462
1526
  const overlap = Math.min(segMaxX, rect.maxX) - Math.max(segMinX, rect.minX);
1463
- return overlap > EPS4;
1527
+ return overlap > EPS5;
1464
1528
  }
1465
1529
  }
1466
1530
  function rectIntersectsAnyTrace(bounds, inputTraceMap, hostPathId, hostSegIndex) {
@@ -1829,14 +1893,14 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
1829
1893
  };
1830
1894
  let bestCandidate = null;
1831
1895
  let bestScore = -Infinity;
1832
- const EPS4 = 1e-6;
1896
+ const EPS5 = 1e-6;
1833
1897
  for (const curr of tracesToScan) {
1834
1898
  const pts = curr.tracePath.slice();
1835
1899
  for (let si = 0; si < pts.length - 1; si++) {
1836
1900
  const a = pts[si];
1837
1901
  const b = pts[si + 1];
1838
- const isH = Math.abs(a.y - b.y) < EPS4;
1839
- const isV = Math.abs(a.x - b.x) < EPS4;
1902
+ const isH = Math.abs(a.y - b.y) < EPS5;
1903
+ const isV = Math.abs(a.x - b.x) < EPS5;
1840
1904
  if (!isH && !isV) continue;
1841
1905
  const segmentAllowed = isH ? ["y+", "y-"] : ["x+", "x-"];
1842
1906
  const candidateOrients = orientations.filter(
@@ -3099,12 +3163,21 @@ var LongDistancePairSolver = class extends BaseSolver {
3099
3163
  };
3100
3164
 
3101
3165
  // lib/solvers/TraceCleanupSolver/hasCollisions.ts
3166
+ import { segmentToBoxMinDistance } from "@tscircuit/math-utils";
3102
3167
  var hasCollisions = (pathSegments, obstacles) => {
3103
3168
  for (let i = 0; i < pathSegments.length - 1; i++) {
3104
3169
  const p1 = pathSegments[i];
3105
3170
  const p2 = pathSegments[i + 1];
3106
3171
  for (const obstacle of obstacles) {
3107
- if (segmentIntersectsRect(p1, p2, obstacle)) {
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) {
3108
3181
  return true;
3109
3182
  }
3110
3183
  }
@@ -3154,43 +3227,60 @@ var hasCollisionsWithLabels = (pathSegments, labels) => {
3154
3227
  return false;
3155
3228
  };
3156
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
+
3157
3274
  // lib/solvers/TraceCleanupSolver/turnMinimization.ts
3158
3275
  var minimizeTurns = ({
3159
3276
  path,
3160
3277
  obstacles,
3161
- labelBounds
3278
+ labelBounds,
3279
+ originalPath
3162
3280
  }) => {
3163
3281
  if (path.length <= 2) {
3164
3282
  return path;
3165
3283
  }
3166
- const recognizeStairStepPattern = (pathToCheck, startIdx) => {
3167
- if (startIdx >= pathToCheck.length - 3) return -1;
3168
- let endIdx = startIdx;
3169
- let isStairStep = true;
3170
- for (let i = startIdx; i < pathToCheck.length - 2 && i < startIdx + 10; i++) {
3171
- if (i + 2 >= pathToCheck.length) break;
3172
- const p1 = pathToCheck[i];
3173
- const p2 = pathToCheck[i + 1];
3174
- const p3 = pathToCheck[i + 2];
3175
- const seg1Vertical = p1.x === p2.x;
3176
- const seg2Vertical = p2.x === p3.x;
3177
- if (seg1Vertical === seg2Vertical) {
3178
- break;
3179
- }
3180
- const seg1Direction = seg1Vertical ? Math.sign(p2.y - p1.y) : Math.sign(p2.x - p1.x);
3181
- if (i > startIdx) {
3182
- const prevP = pathToCheck[i - 1];
3183
- const prevSegVertical = prevP.x === p1.x;
3184
- const prevDirection = prevSegVertical ? Math.sign(p1.y - prevP.y) : Math.sign(p1.x - prevP.x);
3185
- if (seg1Vertical && prevSegVertical && seg1Direction !== prevDirection || !seg1Vertical && !prevSegVertical && seg1Direction !== prevDirection) {
3186
- isStairStep = false;
3187
- break;
3188
- }
3189
- }
3190
- endIdx = i + 2;
3191
- }
3192
- return isStairStep && endIdx - startIdx >= 3 ? endIdx : -1;
3193
- };
3194
3284
  let optimizedPath = [...path];
3195
3285
  let currentTurns = countTurns(optimizedPath);
3196
3286
  let improved = true;
@@ -3199,6 +3289,17 @@ var minimizeTurns = ({
3199
3289
  for (let startIdx = 0; startIdx < optimizedPath.length - 3; startIdx++) {
3200
3290
  const stairEndIdx = recognizeStairStepPattern(optimizedPath, startIdx);
3201
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
+ }
3202
3303
  const startPoint = optimizedPath[startIdx];
3203
3304
  const endPoint = optimizedPath[stairEndIdx];
3204
3305
  const connectionOptions = tryConnectPoints(startPoint, endPoint);
@@ -3233,6 +3334,17 @@ var minimizeTurns = ({
3233
3334
  for (let removeCount = 1; removeCount <= maxRemove; removeCount++) {
3234
3335
  const endIdx = startIdx + removeCount + 1;
3235
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
+ }
3236
3348
  const startPoint = optimizedPath[startIdx];
3237
3349
  const endPoint = optimizedPath[endIdx];
3238
3350
  const connectionOptions = tryConnectPoints(startPoint, endPoint);
@@ -3271,6 +3383,9 @@ var minimizeTurns = ({
3271
3383
  const p1 = optimizedPath[i];
3272
3384
  const p2 = optimizedPath[i + 1];
3273
3385
  const p3 = optimizedPath[i + 2];
3386
+ if (isSegmentAnEndpointSegment(p1, p2, originalPath) || isSegmentAnEndpointSegment(p2, p3, originalPath)) {
3387
+ continue;
3388
+ }
3274
3389
  const allVertical = p1.x === p2.x && p2.x === p3.x;
3275
3390
  const allHorizontal = p1.y === p2.y && p2.y === p3.y;
3276
3391
  if (allVertical || allHorizontal) {
@@ -3327,7 +3442,15 @@ var minimizeTurnsWithFilteredLabels = ({
3327
3442
  };
3328
3443
  })
3329
3444
  );
3330
- const staticObstacles = getObstacleRects(inputProblem);
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
+ }));
3331
3454
  const combinedObstacles = [...staticObstacles, ...traceObstacles];
3332
3455
  const originalPath = targetTrace.tracePath;
3333
3456
  const filteredLabels = allLabelPlacements.filter((label) => {
@@ -3346,7 +3469,8 @@ var minimizeTurnsWithFilteredLabels = ({
3346
3469
  const newPath = minimizeTurns({
3347
3470
  path: originalPath,
3348
3471
  obstacles: combinedObstacles,
3349
- labelBounds
3472
+ labelBounds,
3473
+ originalPath
3350
3474
  });
3351
3475
  return {
3352
3476
  ...targetTrace,
@@ -3490,48 +3614,645 @@ var balanceZShapes = ({
3490
3614
  };
3491
3615
  };
3492
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
+
3493
4177
  // lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts
3494
4178
  var TraceCleanupSolver = class extends BaseSolver {
3495
4179
  input;
3496
4180
  outputTraces;
3497
4181
  traceIdQueue;
3498
4182
  tracesMap;
3499
- pipelineStep = "minimizing_turns";
4183
+ pipelineStep = "untangling_traces";
3500
4184
  activeTraceId = null;
3501
4185
  // New property
4186
+ activeSubSolver = null;
3502
4187
  constructor(solverInput) {
3503
4188
  super();
3504
4189
  this.input = solverInput;
3505
4190
  this.outputTraces = [...solverInput.allTraces];
3506
4191
  this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]));
3507
- this.traceIdQueue = Array.from(solverInput.targetTraceIds);
4192
+ this.traceIdQueue = Array.from(
4193
+ solverInput.allTraces.map((e) => e.mspPairId)
4194
+ );
3508
4195
  }
3509
4196
  _step() {
3510
- if (this.pipelineStep === "minimizing_turns" && this.traceIdQueue.length === 0) {
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) {
3511
4231
  this.pipelineStep = "balancing_l_shapes";
3512
- this.traceIdQueue = Array.from(this.input.targetTraceIds);
4232
+ this.traceIdQueue = Array.from(
4233
+ this.input.allTraces.map((e) => e.mspPairId)
4234
+ );
4235
+ return;
3513
4236
  }
3514
- if (this.pipelineStep === "balancing_l_shapes" && this.traceIdQueue.length === 0) {
4237
+ this._processTrace("minimizing_turns");
4238
+ }
4239
+ _runBalanceLShapesStep() {
4240
+ if (this.traceIdQueue.length === 0) {
3515
4241
  this.solved = true;
3516
4242
  return;
3517
4243
  }
4244
+ this._processTrace("balancing_l_shapes");
4245
+ }
4246
+ _processTrace(step) {
3518
4247
  const targetMspConnectionPairId = this.traceIdQueue.shift();
3519
4248
  this.activeTraceId = targetMspConnectionPairId;
3520
4249
  const originalTrace = this.tracesMap.get(targetMspConnectionPairId);
3521
- const { tracePath } = originalTrace;
3522
- const is4PointRectangle = (path) => {
3523
- if (path.length !== 4) return false;
3524
- const [p0, p1, p2, p3] = path;
3525
- const isHVHC = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y && p0.x === p3.x;
3526
- const isVHVC = p0.x === p1.x && p1.y === p2.y && p2.x === p3.x && p0.y === p3.y;
3527
- return isHVHC || isVHVC;
3528
- };
3529
- if (is4PointRectangle(tracePath)) {
4250
+ if (is4PointRectangle(originalTrace.tracePath)) {
3530
4251
  return;
3531
4252
  }
3532
4253
  const allTraces = Array.from(this.tracesMap.values());
3533
4254
  let updatedTrace;
3534
- if (this.pipelineStep === "minimizing_turns") {
4255
+ if (step === "minimizing_turns") {
3535
4256
  updatedTrace = minimizeTurnsWithFilteredLabels({
3536
4257
  ...this.input,
3537
4258
  targetMspConnectionPairId,
@@ -3553,6 +4274,9 @@ var TraceCleanupSolver = class extends BaseSolver {
3553
4274
  };
3554
4275
  }
3555
4276
  visualize() {
4277
+ if (this.activeSubSolver) {
4278
+ return this.activeSubSolver.visualize();
4279
+ }
3556
4280
  const graphics = visualizeInputProblem(this.input.inputProblem, {
3557
4281
  chipAlpha: 0.1,
3558
4282
  connectionAlpha: 0.1
@@ -3709,7 +4433,6 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3709
4433
  {
3710
4434
  inputProblem: instance.inputProblem,
3711
4435
  allTraces: traces,
3712
- targetTraceIds: new Set(traces.map((t) => t.mspPairId)),
3713
4436
  allLabelPlacements: labelMergingOutput.netLabelPlacements,
3714
4437
  mergedLabelNetIdMap: labelMergingOutput.mergedLabelNetIdMap,
3715
4438
  paddingBuffer: 0.1