@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.
Files changed (25) hide show
  1. package/dist/index.d.ts +14 -3
  2. package/dist/index.js +754 -95
  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/package.json +1 -1
  25. 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 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 {
@@ -1218,7 +1218,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1218
1218
  return islands;
1219
1219
  }
1220
1220
  findNextOverlapIssue() {
1221
- const EPS4 = 2e-3;
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 > EPS4;
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) < EPS4;
1248
- 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;
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) < EPS4;
1257
- 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;
1258
1258
  if (!bVert && !bHorz) continue;
1259
1259
  if (aVert && bVert) {
1260
- if (Math.abs(a1.x - b1.x) < EPS4) {
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) < EPS4) {
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 EPS4 = 2e-3;
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) < EPS4;
1326
- const isVertical2 = Math.abs(p1.x - p2.x) < EPS4;
1327
- if (!isHorizontal2 && !isVertical2) {
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 EPS4 = 2e-3;
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) < EPS4 : false;
1344
- const prevIsHorizontal = p0 ? Math.abs(p0.y - p1.y) < EPS4 : false;
1345
- const nextIsVertical = p3 ? Math.abs(p2.x - p3.x) < EPS4 : false;
1346
- const nextIsHorizontal = p3 ? Math.abs(p2.y - p3.y) < EPS4 : false;
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, EPS4 = 1e-9) {
1511
- const isVert = Math.abs(p1.x - p2.x) < EPS4;
1512
- 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;
1513
1513
  if (!isVert && !isHorz) return false;
1514
1514
  if (isVert) {
1515
1515
  const x = p1.x;
1516
- if (x < rect.minX - EPS4 || x > rect.maxX + EPS4) return false;
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 > EPS4;
1520
+ return overlap > EPS5;
1521
1521
  } else {
1522
1522
  const y = p1.y;
1523
- if (y < rect.minY - EPS4 || y > rect.maxY + EPS4) return false;
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 > EPS4;
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 EPS4 = 1e-6;
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) < EPS4;
1903
- 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;
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
- 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) {
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 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
+ }));
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 = "minimizing_turns";
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(solverInput.targetTraceIds);
4192
+ this.traceIdQueue = Array.from(
4193
+ solverInput.allTraces.map((e) => e.mspPairId)
4194
+ );
3572
4195
  }
3573
4196
  _step() {
3574
- 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) {
3575
4231
  this.pipelineStep = "balancing_l_shapes";
3576
- this.traceIdQueue = Array.from(this.input.targetTraceIds);
4232
+ this.traceIdQueue = Array.from(
4233
+ this.input.allTraces.map((e) => e.mspPairId)
4234
+ );
4235
+ return;
3577
4236
  }
3578
- if (this.pipelineStep === "balancing_l_shapes" && this.traceIdQueue.length === 0) {
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
- const { tracePath } = originalTrace;
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 (this.pipelineStep === "minimizing_turns") {
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