@tscircuit/schematic-trace-solver 0.0.44 → 0.0.46

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