@tscircuit/schematic-trace-solver 0.0.42 → 0.0.44

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 (38) hide show
  1. package/dist/index.d.ts +31 -29
  2. package/dist/index.js +981 -926
  3. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +20 -1
  4. package/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +133 -0
  5. package/lib/solvers/TraceCleanupSolver/balanceZShapes.ts +200 -0
  6. package/lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +76 -0
  7. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +1 -21
  8. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +1 -1
  9. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +1 -1
  10. package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts +83 -0
  11. package/package.json +1 -1
  12. package/site/examples/example26.page.tsx +4 -0
  13. package/tests/assets/example26.json +1206 -0
  14. package/tests/examples/__snapshots__/example02.snap.svg +1 -1
  15. package/tests/examples/__snapshots__/example09.snap.svg +34 -34
  16. package/tests/examples/__snapshots__/example11.snap.svg +13 -13
  17. package/tests/examples/__snapshots__/example12.snap.svg +3 -3
  18. package/tests/examples/__snapshots__/example13.snap.svg +12 -12
  19. package/tests/examples/__snapshots__/example16.snap.svg +4 -4
  20. package/tests/examples/__snapshots__/example19.snap.svg +9 -9
  21. package/tests/examples/__snapshots__/example25.snap.svg +3 -3
  22. package/tests/examples/__snapshots__/example26.snap.svg +3 -3
  23. package/tests/examples/__snapshots__/example28.snap.svg +3 -3
  24. package/tests/examples/__snapshots__/example29.snap.svg +1208 -0
  25. package/tests/examples/example29.test.ts +13 -0
  26. package/tests/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solver → TraceCleanupSolver}/TraceCleanupSolver.test.ts +2 -2
  27. package/tests/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solver → TraceCleanupSolver}/__snapshots__/TraceCleanupSolver.snap.svg +1 -1
  28. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView03.snap.svg +1 -1
  29. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView03.test.ts +1 -1
  30. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/TraceCleanupSolver.ts +0 -132
  31. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/balanceLShapes.ts +0 -209
  32. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +0 -66
  33. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/countTurns.ts +0 -0
  34. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/hasCollisions.ts +0 -0
  35. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/hasCollisionsWithLabels.ts +0 -0
  36. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/simplifyPath.ts +0 -0
  37. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/tryConnectPoints.ts +0 -0
  38. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/turnMinimization.ts +0 -0
package/dist/index.js CHANGED
@@ -1188,6 +1188,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1188
1188
  */
1189
1189
  traceNetIslands = {};
1190
1190
  correctedTraceMap = {};
1191
+ cleanupPhase = null;
1191
1192
  constructor(params) {
1192
1193
  super();
1193
1194
  this.inputProblem = params.inputProblem;
@@ -1314,6 +1315,47 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1314
1315
  }
1315
1316
  return null;
1316
1317
  }
1318
+ findNextDiagonalSegment() {
1319
+ const EPS4 = 2e-3;
1320
+ for (const mspPairId in this.correctedTraceMap) {
1321
+ const tracePath = this.correctedTraceMap[mspPairId].tracePath;
1322
+ for (let i = 0; i < tracePath.length - 1; i++) {
1323
+ const p1 = tracePath[i];
1324
+ const p2 = tracePath[i + 1];
1325
+ const isHorizontal2 = Math.abs(p1.y - p2.y) < EPS4;
1326
+ const isVertical2 = Math.abs(p1.x - p2.x) < EPS4;
1327
+ if (!isHorizontal2 && !isVertical2) {
1328
+ return { mspPairId, tracePath, i, p1, p2 };
1329
+ }
1330
+ }
1331
+ }
1332
+ return null;
1333
+ }
1334
+ findAndFixNextDiagonalSegment() {
1335
+ const diagonalInfo = this.findNextDiagonalSegment();
1336
+ if (!diagonalInfo) {
1337
+ return false;
1338
+ }
1339
+ const { mspPairId, tracePath, i, p1, p2 } = diagonalInfo;
1340
+ const EPS4 = 2e-3;
1341
+ const p0 = i > 0 ? tracePath[i - 1] : null;
1342
+ const p3 = i + 2 < tracePath.length ? tracePath[i + 2] : null;
1343
+ const prevIsVertical = p0 ? Math.abs(p0.x - p1.x) < 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;
1347
+ const elbow1 = { x: p1.x, y: p2.y };
1348
+ const elbow2 = { x: p2.x, y: p1.y };
1349
+ let score1 = 0;
1350
+ if (prevIsVertical) score1++;
1351
+ if (nextIsHorizontal) score1++;
1352
+ let score2 = 0;
1353
+ if (prevIsHorizontal) score2++;
1354
+ if (nextIsVertical) score2++;
1355
+ const elbowPoint = score1 < score2 ? elbow1 : elbow2;
1356
+ tracePath.splice(i + 1, 0, elbowPoint);
1357
+ return true;
1358
+ }
1317
1359
  _step() {
1318
1360
  if (this.activeSubSolver?.solved) {
1319
1361
  for (const [mspPairId, newTrace] of Object.entries(
@@ -1330,6 +1372,17 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1330
1372
  }
1331
1373
  const overlapIssue = this.findNextOverlapIssue();
1332
1374
  if (overlapIssue === null) {
1375
+ if (this.cleanupPhase === null) {
1376
+ this.cleanupPhase = "diagonals";
1377
+ }
1378
+ if (this.cleanupPhase === "diagonals") {
1379
+ const fixedDiagonal = this.findAndFixNextDiagonalSegment();
1380
+ if (!fixedDiagonal) {
1381
+ this.cleanupPhase = "done";
1382
+ this.solved = true;
1383
+ }
1384
+ return;
1385
+ }
1333
1386
  this.solved = true;
1334
1387
  return;
1335
1388
  }
@@ -1344,12 +1397,23 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1344
1397
  return this.activeSubSolver.visualize();
1345
1398
  }
1346
1399
  const graphics = visualizeInputProblem(this.inputProblem);
1400
+ graphics.circles = graphics.circles || [];
1347
1401
  for (const trace of Object.values(this.correctedTraceMap)) {
1348
1402
  graphics.lines.push({
1349
1403
  points: trace.tracePath,
1350
1404
  strokeColor: "purple"
1351
1405
  });
1352
1406
  }
1407
+ if (this.cleanupPhase === "diagonals") {
1408
+ const diagonalInfo = this.findNextDiagonalSegment();
1409
+ if (diagonalInfo) {
1410
+ graphics.lines.push({
1411
+ points: [diagonalInfo.p1, diagonalInfo.p2],
1412
+ strokeColor: "red",
1413
+ strokeWidth: 0.05
1414
+ });
1415
+ }
1416
+ }
1353
1417
  return graphics;
1354
1418
  }
1355
1419
  };
@@ -2352,37 +2416,182 @@ var MergedNetLabelObstacleSolver = class extends BaseSolver {
2352
2416
  }
2353
2417
  };
2354
2418
 
2355
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/hasCollisions.ts
2356
- var hasCollisions = (pathSegments, obstacles) => {
2357
- for (let i = 0; i < pathSegments.length - 1; i++) {
2358
- const p1 = pathSegments[i];
2359
- const p2 = pathSegments[i + 1];
2360
- for (const obstacle of obstacles) {
2361
- if (segmentIntersectsRect(p1, p2, obstacle)) {
2362
- return true;
2419
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts
2420
+ var detectTraceLabelOverlap = (traces, netLabels) => {
2421
+ const overlaps = [];
2422
+ for (const trace of traces) {
2423
+ for (const label of netLabels) {
2424
+ 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];
2428
+ if (segmentIntersectsRect2(p1, p2, labelBounds)) {
2429
+ if (trace.globalConnNetId === label.globalConnNetId) {
2430
+ break;
2431
+ }
2432
+ overlaps.push({ trace, label });
2433
+ break;
2434
+ }
2363
2435
  }
2364
2436
  }
2365
2437
  }
2366
- return false;
2438
+ return overlaps;
2367
2439
  };
2368
2440
 
2369
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/countTurns.ts
2370
- var countTurns = (points) => {
2371
- let turns = 0;
2372
- for (let i = 1; i < points.length - 1; i++) {
2373
- const prev = points[i - 1];
2374
- const curr = points[i];
2375
- const next = points[i + 1];
2376
- const prevVertical = prev.x === curr.x;
2377
- const nextVertical = curr.x === next.x;
2378
- if (prevVertical !== nextVertical) {
2379
- turns++;
2441
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/violation.ts
2442
+ var findTraceViolationZone = (path, labelBounds) => {
2443
+ const isPointInside = (p) => p.x > labelBounds.minX && p.x < labelBounds.maxX && p.y > labelBounds.minY && p.y < labelBounds.maxY;
2444
+ let firstInsideIndex = -1;
2445
+ let lastInsideIndex = -1;
2446
+ for (let i = 0; i < path.length; i++) {
2447
+ if (isPointInside(path[i])) {
2448
+ if (firstInsideIndex === -1) {
2449
+ firstInsideIndex = i;
2450
+ }
2451
+ lastInsideIndex = i;
2380
2452
  }
2381
2453
  }
2382
- return turns;
2454
+ return { firstInsideIndex, lastInsideIndex };
2455
+ };
2456
+
2457
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts
2458
+ var generateSnipAndReconnectCandidates = ({
2459
+ initialTrace,
2460
+ firstInsideIndex,
2461
+ lastInsideIndex,
2462
+ labelBounds,
2463
+ paddingBuffer,
2464
+ detourCount
2465
+ }) => {
2466
+ if (firstInsideIndex <= 0 || lastInsideIndex >= initialTrace.tracePath.length - 1) {
2467
+ return [];
2468
+ }
2469
+ const entryPoint = initialTrace.tracePath[firstInsideIndex - 1];
2470
+ const exitPoint = initialTrace.tracePath[lastInsideIndex + 1];
2471
+ const pathToEntry = initialTrace.tracePath.slice(0, firstInsideIndex);
2472
+ const pathFromExit = initialTrace.tracePath.slice(lastInsideIndex + 1);
2473
+ const allCandidateDetours = [];
2474
+ if (entryPoint.x !== exitPoint.x && entryPoint.y !== exitPoint.y) {
2475
+ allCandidateDetours.push([{ x: exitPoint.x, y: entryPoint.y }]);
2476
+ allCandidateDetours.push([{ x: entryPoint.x, y: exitPoint.y }]);
2477
+ } else if (entryPoint.x === exitPoint.x || entryPoint.y === exitPoint.y) {
2478
+ allCandidateDetours.push([]);
2479
+ }
2480
+ const buffer = paddingBuffer + detourCount * paddingBuffer;
2481
+ const leftX = labelBounds.minX - buffer;
2482
+ const rightX = labelBounds.maxX + buffer;
2483
+ const topY = labelBounds.maxY + buffer;
2484
+ const bottomY = labelBounds.minY - buffer;
2485
+ if ((entryPoint.x <= labelBounds.minX || exitPoint.x <= labelBounds.minX) && entryPoint.x < labelBounds.maxX && exitPoint.x < labelBounds.maxX) {
2486
+ allCandidateDetours.push([
2487
+ { x: leftX, y: entryPoint.y },
2488
+ { x: leftX, y: exitPoint.y }
2489
+ ]);
2490
+ }
2491
+ if ((entryPoint.x >= labelBounds.maxX || exitPoint.x >= labelBounds.maxX) && entryPoint.x > labelBounds.minX && exitPoint.x > labelBounds.minX) {
2492
+ allCandidateDetours.push([
2493
+ { x: rightX, y: entryPoint.y },
2494
+ { x: rightX, y: exitPoint.y }
2495
+ ]);
2496
+ }
2497
+ if ((entryPoint.y >= labelBounds.maxY || exitPoint.y >= labelBounds.maxY) && entryPoint.y > labelBounds.minY && exitPoint.y > labelBounds.minY) {
2498
+ allCandidateDetours.push([
2499
+ { x: entryPoint.x, y: topY },
2500
+ { x: exitPoint.x, y: topY }
2501
+ ]);
2502
+ }
2503
+ if ((entryPoint.y <= labelBounds.minY || exitPoint.y <= labelBounds.minY) && entryPoint.y < labelBounds.maxY && exitPoint.y < labelBounds.maxY) {
2504
+ allCandidateDetours.push([
2505
+ { x: entryPoint.x, y: bottomY },
2506
+ { x: exitPoint.x, y: bottomY }
2507
+ ]);
2508
+ }
2509
+ return allCandidateDetours.map((detour) => [
2510
+ ...pathToEntry,
2511
+ ...detour,
2512
+ ...pathFromExit
2513
+ ]);
2514
+ };
2515
+
2516
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts
2517
+ var generateFourPointDetourCandidates = ({
2518
+ initialTrace,
2519
+ label,
2520
+ labelBounds,
2521
+ paddingBuffer,
2522
+ detourCount
2523
+ }) => {
2524
+ let collidingSegIndex = -1;
2525
+ for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
2526
+ if (segmentIntersectsRect(
2527
+ initialTrace.tracePath[i],
2528
+ initialTrace.tracePath[i + 1],
2529
+ labelBounds
2530
+ )) {
2531
+ collidingSegIndex = i;
2532
+ break;
2533
+ }
2534
+ }
2535
+ if (collidingSegIndex === -1) return [];
2536
+ const pA = initialTrace.tracePath[collidingSegIndex];
2537
+ const pB = initialTrace.tracePath[collidingSegIndex + 1];
2538
+ if (!pA || !pB) return [];
2539
+ const candidateDetours = [];
2540
+ const paddedLabelBounds = getRectBounds(
2541
+ label.center,
2542
+ label.width,
2543
+ label.height
2544
+ );
2545
+ const effectivePadding = paddingBuffer + detourCount * paddingBuffer;
2546
+ if (isVertical(pA, pB)) {
2547
+ const xCandidates = [
2548
+ paddedLabelBounds.maxX + effectivePadding,
2549
+ paddedLabelBounds.minX - effectivePadding
2550
+ ];
2551
+ for (const newX of xCandidates) {
2552
+ candidateDetours.push(
2553
+ pB.y > pA.y ? [
2554
+ { x: pA.x, y: paddedLabelBounds.minY - effectivePadding },
2555
+ { x: newX, y: paddedLabelBounds.minY - effectivePadding },
2556
+ { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
2557
+ { x: pB.x, y: paddedLabelBounds.maxY + effectivePadding }
2558
+ ] : [
2559
+ { x: pA.x, y: paddedLabelBounds.maxY + effectivePadding },
2560
+ { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
2561
+ { x: newX, y: paddedLabelBounds.minY - effectivePadding },
2562
+ { x: pB.x, y: paddedLabelBounds.minY - effectivePadding }
2563
+ ]
2564
+ );
2565
+ }
2566
+ } else {
2567
+ const yCandidates = [
2568
+ paddedLabelBounds.maxY + effectivePadding,
2569
+ paddedLabelBounds.minY - effectivePadding
2570
+ ];
2571
+ for (const newY of yCandidates) {
2572
+ candidateDetours.push(
2573
+ pB.x > pA.x ? [
2574
+ { x: paddedLabelBounds.minX - effectivePadding, y: pA.y },
2575
+ { x: paddedLabelBounds.minX - effectivePadding, y: newY },
2576
+ { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
2577
+ { x: paddedLabelBounds.maxX + effectivePadding, y: pB.y }
2578
+ ] : [
2579
+ { x: paddedLabelBounds.maxX + effectivePadding, y: pA.y },
2580
+ { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
2581
+ { x: paddedLabelBounds.minX - effectivePadding, y: newY },
2582
+ { x: paddedLabelBounds.minX - effectivePadding, y: pB.y }
2583
+ ]
2584
+ );
2585
+ }
2586
+ }
2587
+ return candidateDetours.map((detourPoints) => [
2588
+ ...initialTrace.tracePath.slice(0, collidingSegIndex + 1),
2589
+ ...detourPoints,
2590
+ ...initialTrace.tracePath.slice(collidingSegIndex + 1)
2591
+ ]);
2383
2592
  };
2384
2593
 
2385
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/simplifyPath.ts
2594
+ // lib/solvers/TraceCleanupSolver/simplifyPath.ts
2386
2595
  var simplifyPath = (path) => {
2387
2596
  if (path.length < 3) return path;
2388
2597
  const newPath = [path[0]];
@@ -2411,675 +2620,46 @@ var simplifyPath = (path) => {
2411
2620
  return finalPath;
2412
2621
  };
2413
2622
 
2414
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/tryConnectPoints.ts
2415
- var tryConnectPoints = (start, end) => {
2416
- const candidates = [];
2417
- if (start.x === end.x || start.y === end.y) {
2418
- candidates.push([start, end]);
2419
- } else {
2420
- candidates.push([start, { x: end.x, y: start.y }, end]);
2421
- candidates.push([start, { x: start.x, y: end.y }, end]);
2623
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts
2624
+ var generateRerouteCandidates = ({
2625
+ trace,
2626
+ label,
2627
+ paddingBuffer,
2628
+ detourCount
2629
+ }) => {
2630
+ const initialTrace = { ...trace, tracePath: simplifyPath(trace.tracePath) };
2631
+ if (trace.globalConnNetId === label.globalConnNetId) {
2632
+ return [initialTrace.tracePath];
2422
2633
  }
2423
- return candidates;
2424
- };
2425
-
2426
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/hasCollisionsWithLabels.ts
2427
- var hasCollisionsWithLabels = (pathSegments, labels) => {
2428
- for (let i = 0; i < pathSegments.length - 1; i++) {
2429
- const p1 = pathSegments[i];
2430
- const p2 = pathSegments[i + 1];
2431
- for (const label of labels) {
2432
- if (segmentIntersectsRect(p1, p2, label)) {
2433
- return true;
2434
- }
2435
- }
2436
- }
2437
- return false;
2438
- };
2439
-
2440
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/turnMinimization.ts
2441
- var minimizeTurns = ({
2442
- path,
2443
- obstacles,
2444
- labelBounds
2445
- }) => {
2446
- if (path.length <= 2) {
2447
- return path;
2448
- }
2449
- const recognizeStairStepPattern = (pathToCheck, startIdx) => {
2450
- if (startIdx >= pathToCheck.length - 3) return -1;
2451
- let endIdx = startIdx;
2452
- let isStairStep = true;
2453
- for (let i = startIdx; i < pathToCheck.length - 2 && i < startIdx + 10; i++) {
2454
- if (i + 2 >= pathToCheck.length) break;
2455
- const p1 = pathToCheck[i];
2456
- const p2 = pathToCheck[i + 1];
2457
- const p3 = pathToCheck[i + 2];
2458
- const seg1Vertical = p1.x === p2.x;
2459
- const seg2Vertical = p2.x === p3.x;
2460
- if (seg1Vertical === seg2Vertical) {
2461
- break;
2462
- }
2463
- const seg1Direction = seg1Vertical ? Math.sign(p2.y - p1.y) : Math.sign(p2.x - p1.x);
2464
- if (i > startIdx) {
2465
- const prevP = pathToCheck[i - 1];
2466
- const prevSegVertical = prevP.x === p1.x;
2467
- const prevDirection = prevSegVertical ? Math.sign(p1.y - prevP.y) : Math.sign(p1.x - prevP.x);
2468
- if (seg1Vertical && prevSegVertical && seg1Direction !== prevDirection || !seg1Vertical && !prevSegVertical && seg1Direction !== prevDirection) {
2469
- isStairStep = false;
2470
- break;
2471
- }
2472
- }
2473
- endIdx = i + 2;
2474
- }
2475
- return isStairStep && endIdx - startIdx >= 3 ? endIdx : -1;
2476
- };
2477
- let optimizedPath = [...path];
2478
- let currentTurns = countTurns(optimizedPath);
2479
- let improved = true;
2480
- while (improved) {
2481
- improved = false;
2482
- for (let startIdx = 0; startIdx < optimizedPath.length - 3; startIdx++) {
2483
- const stairEndIdx = recognizeStairStepPattern(optimizedPath, startIdx);
2484
- if (stairEndIdx > 0) {
2485
- const startPoint = optimizedPath[startIdx];
2486
- const endPoint = optimizedPath[stairEndIdx];
2487
- const connectionOptions = tryConnectPoints(startPoint, endPoint);
2488
- for (const connection of connectionOptions) {
2489
- const testPath = [
2490
- ...optimizedPath.slice(0, startIdx + 1),
2491
- ...connection.slice(1, -1),
2492
- ...optimizedPath.slice(stairEndIdx)
2493
- ];
2494
- const collidesWithObstacles = hasCollisions(connection, obstacles);
2495
- const collidesWithLabels = hasCollisionsWithLabels(
2496
- connection,
2497
- labelBounds
2498
- );
2499
- if (!collidesWithObstacles && !collidesWithLabels) {
2500
- const newTurns = countTurns(testPath);
2501
- optimizedPath = testPath;
2502
- currentTurns = newTurns;
2503
- improved = true;
2504
- break;
2505
- }
2506
- }
2507
- if (improved) break;
2508
- }
2509
- }
2510
- if (!improved) {
2511
- for (let startIdx = 0; startIdx < optimizedPath.length - 2; startIdx++) {
2512
- const maxRemove = Math.min(
2513
- optimizedPath.length - startIdx - 2,
2514
- optimizedPath.length - 2
2515
- );
2516
- for (let removeCount = 1; removeCount <= maxRemove; removeCount++) {
2517
- const endIdx = startIdx + removeCount + 1;
2518
- if (endIdx >= optimizedPath.length) continue;
2519
- const startPoint = optimizedPath[startIdx];
2520
- const endPoint = optimizedPath[endIdx];
2521
- const connectionOptions = tryConnectPoints(startPoint, endPoint);
2522
- for (const connection of connectionOptions) {
2523
- const testPath = [
2524
- ...optimizedPath.slice(0, startIdx + 1),
2525
- ...connection.slice(1, -1),
2526
- ...optimizedPath.slice(endIdx)
2527
- ];
2528
- const connectionSegments = connection;
2529
- const collidesWithObstacles = hasCollisions(
2530
- connectionSegments,
2531
- obstacles
2532
- );
2533
- const collidesWithLabels = hasCollisionsWithLabels(
2534
- connectionSegments,
2535
- labelBounds
2536
- );
2537
- if (!collidesWithObstacles && !collidesWithLabels) {
2538
- const newTurns = countTurns(testPath);
2539
- if (newTurns < currentTurns || newTurns === currentTurns && testPath.length < optimizedPath.length) {
2540
- optimizedPath = testPath;
2541
- currentTurns = newTurns;
2542
- improved = true;
2543
- break;
2544
- }
2545
- }
2546
- }
2547
- if (improved) break;
2548
- }
2549
- if (improved) break;
2550
- }
2551
- }
2552
- if (!improved) {
2553
- for (let i = 0; i < optimizedPath.length - 2; i++) {
2554
- const p1 = optimizedPath[i];
2555
- const p2 = optimizedPath[i + 1];
2556
- const p3 = optimizedPath[i + 2];
2557
- const allVertical = p1.x === p2.x && p2.x === p3.x;
2558
- const allHorizontal = p1.y === p2.y && p2.y === p3.y;
2559
- if (allVertical || allHorizontal) {
2560
- const testPath = [
2561
- ...optimizedPath.slice(0, i + 1),
2562
- ...optimizedPath.slice(i + 2)
2563
- ];
2564
- const collidesWithObstacles = hasCollisions([p1, p3], obstacles);
2565
- const collidesWithLabels = hasCollisionsWithLabels(
2566
- [p1, p3],
2567
- labelBounds
2568
- );
2569
- if (!collidesWithObstacles && !collidesWithLabels) {
2570
- optimizedPath = testPath;
2571
- improved = true;
2572
- break;
2573
- }
2574
- }
2575
- }
2576
- }
2577
- }
2578
- const finalSimplifiedPath = simplifyPath(optimizedPath);
2579
- return finalSimplifiedPath;
2580
- };
2581
-
2582
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts
2583
- var minimizeTurnsWithFilteredLabels = ({
2584
- traces,
2585
- inputProblem,
2586
- allLabelPlacements,
2587
- mergedLabelNetIdMap,
2588
- paddingBuffer
2589
- }) => {
2590
- let changesMade = false;
2591
- const obstacles = getObstacleRects(inputProblem);
2592
- const newTraces = traces.map((trace) => {
2593
- const originalPath = trace.tracePath;
2594
- const filteredLabels = allLabelPlacements.filter((label) => {
2595
- const originalNetIds = mergedLabelNetIdMap[label.globalConnNetId];
2596
- if (originalNetIds) {
2597
- return !originalNetIds.has(trace.globalConnNetId);
2598
- }
2599
- return label.globalConnNetId !== trace.globalConnNetId;
2600
- });
2601
- const labelBounds = filteredLabels.map((nl) => ({
2602
- minX: nl.center.x - nl.width / 2 - paddingBuffer,
2603
- maxX: nl.center.x + nl.width / 2 + paddingBuffer,
2604
- minY: nl.center.y - nl.height / 2 - paddingBuffer,
2605
- maxY: nl.center.y + nl.height / 2 + paddingBuffer
2606
- }));
2607
- const newPath = minimizeTurns({
2608
- path: originalPath,
2609
- obstacles,
2610
- labelBounds
2611
- });
2612
- if (newPath.length !== originalPath.length || newPath.some(
2613
- (p, i) => p.x !== originalPath[i].x || p.y !== originalPath[i].y
2614
- )) {
2615
- changesMade = true;
2616
- }
2617
- return {
2618
- ...trace,
2619
- tracePath: newPath
2620
- };
2621
- });
2622
- if (changesMade) {
2623
- return newTraces;
2624
- } else {
2625
- return null;
2626
- }
2627
- };
2628
-
2629
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/balanceLShapes.ts
2630
- var balanceLShapes = ({
2631
- traces,
2632
- inputProblem,
2633
- allLabelPlacements
2634
- }) => {
2635
- const TOLERANCE = 1e-5;
2636
- let changesMade = false;
2637
- for (const trace of traces) {
2638
- if (trace.tracePath.length === 4) {
2639
- const [p0, p1, p2, p3] = trace.tracePath;
2640
- const isHVHShape = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y;
2641
- const isVHVShape = p0.x === p1.x && p1.y === p2.y && p2.x === p3.x;
2642
- const isCollinearHorizontal = p0.y === p1.y && p1.y === p2.y && p2.y === p3.y;
2643
- const isCollinearVertical = p0.x === p1.x && p1.x === p2.x && p2.x === p3.x;
2644
- const isCollinear = isCollinearHorizontal || isCollinearVertical;
2645
- let isSameDirection = false;
2646
- if (isHVHShape) {
2647
- isSameDirection = Math.sign(p1.x - p0.x) === Math.sign(p3.x - p2.x);
2648
- } else if (isVHVShape) {
2649
- isSameDirection = Math.sign(p1.y - p0.y) === Math.sign(p3.y - p2.y);
2650
- }
2651
- const isValidZShape = (isHVHShape || isVHVShape) && !isCollinear && isSameDirection;
2652
- if (!isValidZShape) {
2653
- return null;
2654
- }
2655
- }
2656
- }
2657
- const obstacles = getObstacleRects(inputProblem).map((obs) => ({
2658
- ...obs,
2659
- minX: obs.minX + TOLERANCE,
2660
- maxX: obs.maxX - TOLERANCE,
2661
- minY: obs.minY + TOLERANCE,
2662
- maxY: obs.maxY - TOLERANCE
2663
- }));
2664
- const segmentIntersectsAnyRect = (p1, p2, rects) => {
2665
- for (const rect of rects) {
2666
- if (segmentIntersectsRect(p1, p2, rect)) {
2667
- return true;
2668
- }
2669
- }
2670
- return false;
2671
- };
2672
- const getLabelBounds = (labels, traceNetId) => {
2673
- const filteredLabels = labels.filter(
2674
- (label) => label.globalConnNetId !== traceNetId
2675
- );
2676
- return filteredLabels.map((nl) => ({
2677
- minX: nl.center.x - nl.width / 2 + TOLERANCE,
2678
- maxX: nl.center.x + nl.width / 2 - TOLERANCE,
2679
- minY: nl.center.y - nl.height / 2 + TOLERANCE,
2680
- maxY: nl.center.y + nl.height / 2 - TOLERANCE
2681
- }));
2682
- };
2683
- const newTraces = traces.map((trace) => {
2684
- const newPath = [...trace.tracePath];
2685
- if (newPath.length < 4) {
2686
- return { ...trace };
2687
- }
2688
- const labelBounds = getLabelBounds(
2689
- allLabelPlacements,
2690
- trace.globalConnNetId
2691
- );
2692
- if (newPath.length === 4) {
2693
- const [p0, p1, p2, p3] = newPath;
2694
- let p1New;
2695
- let p2New;
2696
- const isHVHShape = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y;
2697
- if (isHVHShape) {
2698
- const idealX = (p0.x + p3.x) / 2;
2699
- p1New = { x: idealX, y: p1.y };
2700
- p2New = { x: idealX, y: p2.y };
2701
- } else {
2702
- const idealY = (p0.y + p3.y) / 2;
2703
- p1New = { x: p1.x, y: idealY };
2704
- p2New = { x: p2.x, y: idealY };
2705
- }
2706
- const collides = segmentIntersectsAnyRect(p0, p1New, obstacles) || segmentIntersectsAnyRect(p1New, p2New, obstacles) || segmentIntersectsAnyRect(p2New, p3, obstacles) || segmentIntersectsAnyRect(p0, p1New, labelBounds) || segmentIntersectsAnyRect(p1New, p2New, labelBounds) || segmentIntersectsAnyRect(p2New, p3, labelBounds);
2707
- if (!collides) {
2708
- newPath[1] = p1New;
2709
- newPath[2] = p2New;
2710
- changesMade = true;
2711
- }
2712
- return { ...trace, tracePath: simplifyPath(newPath) };
2713
- }
2714
- for (let i = 1; i < newPath.length - 4; i++) {
2715
- const p1 = newPath[i];
2716
- const p2 = newPath[i + 1];
2717
- const p3 = newPath[i + 2];
2718
- const p4 = newPath[i + 3];
2719
- const isHVHZShape = p1.y === p2.y && p2.x === p3.x && p3.y === p4.y;
2720
- const isVHVZShape = p1.x === p2.x && p2.y === p3.y && p3.x === p4.x;
2721
- const isCollinearHorizontal = p1.y === p2.y && p2.y === p3.y && p3.y === p4.y;
2722
- const isCollinearVertical = p1.x === p2.x && p2.x === p3.x && p3.x === p4.x;
2723
- const isCollinear = isCollinearHorizontal || isCollinearVertical;
2724
- let isSameDirection = false;
2725
- if (isHVHZShape) {
2726
- isSameDirection = Math.sign(p2.x - p1.x) === Math.sign(p4.x - p3.x);
2727
- } else if (isVHVZShape) {
2728
- isSameDirection = Math.sign(p2.y - p1.y) === Math.sign(p4.y - p3.y);
2729
- }
2730
- const isValidZShape = (isHVHZShape || isVHVZShape) && !isCollinear && isSameDirection;
2731
- if (!isValidZShape) {
2732
- continue;
2733
- }
2734
- let p2New;
2735
- let p3New;
2736
- const len1Original = isHVHZShape ? Math.abs(p1.x - p2.x) : Math.abs(p1.y - p2.y);
2737
- const len2Original = isHVHZShape ? Math.abs(p3.x - p4.x) : Math.abs(p3.y - p4.y);
2738
- if (Math.abs(len1Original - len2Original) < 1e-3) {
2739
- continue;
2740
- }
2741
- if (isHVHZShape) {
2742
- const idealX = (p1.x + p4.x) / 2;
2743
- p2New = { x: idealX, y: p2.y };
2744
- p3New = { x: idealX, y: p3.y };
2745
- } else {
2746
- const idealY = (p1.y + p4.y) / 2;
2747
- p2New = { x: p2.x, y: idealY };
2748
- p3New = { x: p3.x, y: idealY };
2749
- }
2750
- const collides = segmentIntersectsAnyRect(p1, p2New, obstacles) || segmentIntersectsAnyRect(p2New, p3New, obstacles) || segmentIntersectsAnyRect(p3New, p4, obstacles) || segmentIntersectsAnyRect(p1, p2New, labelBounds) || segmentIntersectsAnyRect(p2New, p3New, labelBounds) || segmentIntersectsAnyRect(p3New, p4, labelBounds);
2751
- if (!collides) {
2752
- newPath[i + 1] = p2New;
2753
- newPath[i + 2] = p3New;
2754
- changesMade = true;
2755
- i = 0;
2756
- }
2757
- }
2758
- const finalSimplifiedPath = simplifyPath(newPath);
2759
- return {
2760
- ...trace,
2761
- tracePath: finalSimplifiedPath
2762
- };
2763
- });
2764
- if (changesMade) {
2765
- return newTraces;
2766
- } else {
2767
- return null;
2768
- }
2769
- };
2770
-
2771
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/TraceCleanupSolver.ts
2772
- var TraceCleanupSolver = class extends BaseSolver {
2773
- input;
2774
- outputTraces;
2775
- pipelineStepIndex = 0;
2776
- tracesToProcess;
2777
- minimizedTraces = null;
2778
- balancedTraces = null;
2779
- constructor(solverInput) {
2780
- super();
2781
- this.input = solverInput;
2782
- this.outputTraces = [...solverInput.allTraces];
2783
- this.tracesToProcess = this.outputTraces.filter(
2784
- (t) => this.input.targetTraceIds.has(t.mspPairId)
2785
- );
2786
- }
2787
- _step() {
2788
- const {
2789
- targetTraceIds,
2790
- inputProblem,
2791
- allLabelPlacements,
2792
- mergedLabelNetIdMap,
2793
- paddingBuffer
2794
- } = this.input;
2795
- if (targetTraceIds.size === 0) {
2796
- this.solved = true;
2797
- return;
2798
- }
2799
- switch (this.pipelineStepIndex) {
2800
- case 0: {
2801
- const minimizedTracesResult = minimizeTurnsWithFilteredLabels({
2802
- traces: this.tracesToProcess,
2803
- inputProblem,
2804
- allLabelPlacements,
2805
- mergedLabelNetIdMap,
2806
- paddingBuffer
2807
- });
2808
- this.minimizedTraces = minimizedTracesResult ?? this.tracesToProcess;
2809
- const tracesMap = new Map(
2810
- this.input.allTraces.map((t) => [t.mspPairId, t])
2811
- );
2812
- for (const trace of this.minimizedTraces) {
2813
- tracesMap.set(trace.mspPairId, trace);
2814
- }
2815
- this.outputTraces = Array.from(tracesMap.values());
2816
- this.pipelineStepIndex++;
2817
- break;
2818
- }
2819
- case 1: {
2820
- const balancedTracesResult = balanceLShapes({
2821
- traces: this.minimizedTraces,
2822
- inputProblem,
2823
- allLabelPlacements
2824
- });
2825
- this.balancedTraces = balancedTracesResult ?? this.minimizedTraces;
2826
- const tracesMap = new Map(
2827
- this.input.allTraces.map((t) => [t.mspPairId, t])
2828
- );
2829
- for (const trace of this.balancedTraces) {
2830
- tracesMap.set(trace.mspPairId, trace);
2831
- }
2832
- this.outputTraces = Array.from(tracesMap.values());
2833
- this.pipelineStepIndex++;
2834
- break;
2835
- }
2836
- case 2: {
2837
- this.solved = true;
2838
- break;
2839
- }
2840
- }
2841
- }
2842
- getOutput() {
2843
- return {
2844
- traces: this.outputTraces
2845
- };
2846
- }
2847
- visualize() {
2848
- const graphics = visualizeInputProblem(this.input.inputProblem, {
2849
- chipAlpha: 0.1,
2850
- connectionAlpha: 0.1
2851
- });
2852
- if (!graphics.lines) graphics.lines = [];
2853
- if (!graphics.points) graphics.points = [];
2854
- if (!graphics.rects) graphics.rects = [];
2855
- if (!graphics.circles) graphics.circles = [];
2856
- if (!graphics.texts) graphics.texts = [];
2857
- for (const trace of this.outputTraces) {
2858
- const line = {
2859
- points: trace.tracePath.map((p) => ({ x: p.x, y: p.y })),
2860
- strokeColor: "blue"
2861
- };
2862
- graphics.lines.push(line);
2863
- }
2864
- return graphics;
2865
- }
2866
- };
2867
-
2868
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts
2869
- var detectTraceLabelOverlap = (traces, netLabels) => {
2870
- const overlaps = [];
2871
- for (const trace of traces) {
2872
- for (const label of netLabels) {
2873
- const labelBounds = getRectBounds(label.center, label.width, label.height);
2874
- for (let i = 0; i < trace.tracePath.length - 1; i++) {
2875
- const p1 = trace.tracePath[i];
2876
- const p2 = trace.tracePath[i + 1];
2877
- if (segmentIntersectsRect2(p1, p2, labelBounds)) {
2878
- if (trace.globalConnNetId === label.globalConnNetId) {
2879
- break;
2880
- }
2881
- overlaps.push({ trace, label });
2882
- break;
2883
- }
2884
- }
2885
- }
2886
- }
2887
- return overlaps;
2888
- };
2889
-
2890
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/violation.ts
2891
- var findTraceViolationZone = (path, labelBounds) => {
2892
- const isPointInside = (p) => p.x > labelBounds.minX && p.x < labelBounds.maxX && p.y > labelBounds.minY && p.y < labelBounds.maxY;
2893
- let firstInsideIndex = -1;
2894
- let lastInsideIndex = -1;
2895
- for (let i = 0; i < path.length; i++) {
2896
- if (isPointInside(path[i])) {
2897
- if (firstInsideIndex === -1) {
2898
- firstInsideIndex = i;
2899
- }
2900
- lastInsideIndex = i;
2901
- }
2902
- }
2903
- return { firstInsideIndex, lastInsideIndex };
2904
- };
2905
-
2906
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts
2907
- var generateSnipAndReconnectCandidates = ({
2908
- initialTrace,
2909
- firstInsideIndex,
2910
- lastInsideIndex,
2911
- labelBounds,
2912
- paddingBuffer,
2913
- detourCount
2914
- }) => {
2915
- if (firstInsideIndex <= 0 || lastInsideIndex >= initialTrace.tracePath.length - 1) {
2916
- return [];
2917
- }
2918
- const entryPoint = initialTrace.tracePath[firstInsideIndex - 1];
2919
- const exitPoint = initialTrace.tracePath[lastInsideIndex + 1];
2920
- const pathToEntry = initialTrace.tracePath.slice(0, firstInsideIndex);
2921
- const pathFromExit = initialTrace.tracePath.slice(lastInsideIndex + 1);
2922
- const allCandidateDetours = [];
2923
- if (entryPoint.x !== exitPoint.x && entryPoint.y !== exitPoint.y) {
2924
- allCandidateDetours.push([{ x: exitPoint.x, y: entryPoint.y }]);
2925
- allCandidateDetours.push([{ x: entryPoint.x, y: exitPoint.y }]);
2926
- } else if (entryPoint.x === exitPoint.x || entryPoint.y === exitPoint.y) {
2927
- allCandidateDetours.push([]);
2928
- }
2929
- const buffer = paddingBuffer + detourCount * paddingBuffer;
2930
- const leftX = labelBounds.minX - buffer;
2931
- const rightX = labelBounds.maxX + buffer;
2932
- const topY = labelBounds.maxY + buffer;
2933
- const bottomY = labelBounds.minY - buffer;
2934
- if ((entryPoint.x <= labelBounds.minX || exitPoint.x <= labelBounds.minX) && entryPoint.x < labelBounds.maxX && exitPoint.x < labelBounds.maxX) {
2935
- allCandidateDetours.push([
2936
- { x: leftX, y: entryPoint.y },
2937
- { x: leftX, y: exitPoint.y }
2938
- ]);
2939
- }
2940
- if ((entryPoint.x >= labelBounds.maxX || exitPoint.x >= labelBounds.maxX) && entryPoint.x > labelBounds.minX && exitPoint.x > labelBounds.minX) {
2941
- allCandidateDetours.push([
2942
- { x: rightX, y: entryPoint.y },
2943
- { x: rightX, y: exitPoint.y }
2944
- ]);
2945
- }
2946
- if ((entryPoint.y >= labelBounds.maxY || exitPoint.y >= labelBounds.maxY) && entryPoint.y > labelBounds.minY && exitPoint.y > labelBounds.minY) {
2947
- allCandidateDetours.push([
2948
- { x: entryPoint.x, y: topY },
2949
- { x: exitPoint.x, y: topY }
2950
- ]);
2951
- }
2952
- if ((entryPoint.y <= labelBounds.minY || exitPoint.y <= labelBounds.minY) && entryPoint.y < labelBounds.maxY && exitPoint.y < labelBounds.maxY) {
2953
- allCandidateDetours.push([
2954
- { x: entryPoint.x, y: bottomY },
2955
- { x: exitPoint.x, y: bottomY }
2956
- ]);
2957
- }
2958
- return allCandidateDetours.map((detour) => [
2959
- ...pathToEntry,
2960
- ...detour,
2961
- ...pathFromExit
2962
- ]);
2963
- };
2964
-
2965
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts
2966
- var generateFourPointDetourCandidates = ({
2967
- initialTrace,
2968
- label,
2969
- labelBounds,
2970
- paddingBuffer,
2971
- detourCount
2972
- }) => {
2973
- let collidingSegIndex = -1;
2974
- for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
2975
- if (segmentIntersectsRect(
2976
- initialTrace.tracePath[i],
2977
- initialTrace.tracePath[i + 1],
2978
- labelBounds
2979
- )) {
2980
- collidingSegIndex = i;
2981
- break;
2982
- }
2983
- }
2984
- if (collidingSegIndex === -1) return [];
2985
- const pA = initialTrace.tracePath[collidingSegIndex];
2986
- const pB = initialTrace.tracePath[collidingSegIndex + 1];
2987
- if (!pA || !pB) return [];
2988
- const candidateDetours = [];
2989
- const paddedLabelBounds = getRectBounds(
2990
- label.center,
2991
- label.width,
2992
- label.height
2993
- );
2994
- const effectivePadding = paddingBuffer + detourCount * paddingBuffer;
2995
- if (isVertical(pA, pB)) {
2996
- const xCandidates = [
2997
- paddedLabelBounds.maxX + effectivePadding,
2998
- paddedLabelBounds.minX - effectivePadding
2999
- ];
3000
- for (const newX of xCandidates) {
3001
- candidateDetours.push(
3002
- pB.y > pA.y ? [
3003
- { x: pA.x, y: paddedLabelBounds.minY - effectivePadding },
3004
- { x: newX, y: paddedLabelBounds.minY - effectivePadding },
3005
- { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
3006
- { x: pB.x, y: paddedLabelBounds.maxY + effectivePadding }
3007
- ] : [
3008
- { x: pA.x, y: paddedLabelBounds.maxY + effectivePadding },
3009
- { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
3010
- { x: newX, y: paddedLabelBounds.minY - effectivePadding },
3011
- { x: pB.x, y: paddedLabelBounds.minY - effectivePadding }
3012
- ]
3013
- );
3014
- }
3015
- } else {
3016
- const yCandidates = [
3017
- paddedLabelBounds.maxY + effectivePadding,
3018
- paddedLabelBounds.minY - effectivePadding
3019
- ];
3020
- for (const newY of yCandidates) {
3021
- candidateDetours.push(
3022
- pB.x > pA.x ? [
3023
- { x: paddedLabelBounds.minX - effectivePadding, y: pA.y },
3024
- { x: paddedLabelBounds.minX - effectivePadding, y: newY },
3025
- { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
3026
- { x: paddedLabelBounds.maxX + effectivePadding, y: pB.y }
3027
- ] : [
3028
- { x: paddedLabelBounds.maxX + effectivePadding, y: pA.y },
3029
- { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
3030
- { x: paddedLabelBounds.minX - effectivePadding, y: newY },
3031
- { x: paddedLabelBounds.minX - effectivePadding, y: pB.y }
3032
- ]
3033
- );
3034
- }
3035
- }
3036
- return candidateDetours.map((detourPoints) => [
3037
- ...initialTrace.tracePath.slice(0, collidingSegIndex + 1),
3038
- ...detourPoints,
3039
- ...initialTrace.tracePath.slice(collidingSegIndex + 1)
3040
- ]);
3041
- };
3042
-
3043
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts
3044
- var generateRerouteCandidates = ({
3045
- trace,
3046
- label,
3047
- paddingBuffer,
3048
- detourCount
3049
- }) => {
3050
- const initialTrace = { ...trace, tracePath: simplifyPath(trace.tracePath) };
3051
- if (trace.globalConnNetId === label.globalConnNetId) {
3052
- return [initialTrace.tracePath];
3053
- }
3054
- const labelPadding = paddingBuffer;
3055
- const labelBoundsRaw = getRectBounds(label.center, label.width, label.height);
3056
- const labelBounds = {
3057
- minX: labelBoundsRaw.minX - labelPadding,
3058
- minY: labelBoundsRaw.minY - labelPadding,
3059
- maxX: labelBoundsRaw.maxX + labelPadding,
3060
- maxY: labelBoundsRaw.maxY + labelPadding,
3061
- chipId: `netlabel-${label.netId}`
3062
- };
3063
- const fourPointCandidates = generateFourPointDetourCandidates({
3064
- initialTrace,
3065
- label,
3066
- labelBounds,
3067
- paddingBuffer,
3068
- detourCount
3069
- });
3070
- const { firstInsideIndex, lastInsideIndex } = findTraceViolationZone(
3071
- initialTrace.tracePath,
3072
- labelBounds
3073
- );
3074
- const snipReconnectCandidates = generateSnipAndReconnectCandidates({
3075
- initialTrace,
3076
- firstInsideIndex,
3077
- lastInsideIndex,
3078
- labelBounds,
3079
- paddingBuffer,
3080
- detourCount
3081
- });
3082
- return [...fourPointCandidates, ...snipReconnectCandidates];
2634
+ const labelPadding = paddingBuffer;
2635
+ const labelBoundsRaw = getRectBounds(label.center, label.width, label.height);
2636
+ const labelBounds = {
2637
+ minX: labelBoundsRaw.minX - labelPadding,
2638
+ minY: labelBoundsRaw.minY - labelPadding,
2639
+ maxX: labelBoundsRaw.maxX + labelPadding,
2640
+ maxY: labelBoundsRaw.maxY + labelPadding,
2641
+ chipId: `netlabel-${label.netId}`
2642
+ };
2643
+ const fourPointCandidates = generateFourPointDetourCandidates({
2644
+ initialTrace,
2645
+ label,
2646
+ labelBounds,
2647
+ paddingBuffer,
2648
+ detourCount
2649
+ });
2650
+ const { firstInsideIndex, lastInsideIndex } = findTraceViolationZone(
2651
+ initialTrace.tracePath,
2652
+ labelBounds
2653
+ );
2654
+ const snipReconnectCandidates = generateSnipAndReconnectCandidates({
2655
+ initialTrace,
2656
+ firstInsideIndex,
2657
+ lastInsideIndex,
2658
+ labelBounds,
2659
+ paddingBuffer,
2660
+ detourCount
2661
+ });
2662
+ return [...fourPointCandidates, ...snipReconnectCandidates];
3083
2663
  };
3084
2664
 
3085
2665
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts
@@ -3272,7 +2852,6 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
3272
2852
  // sub-solver instances
3273
2853
  labelMergingSolver;
3274
2854
  overlapAvoidanceSolver;
3275
- traceCleanupSolver;
3276
2855
  pipelineStepIndex = 0;
3277
2856
  constructor(solverInput) {
3278
2857
  super();
@@ -3310,292 +2889,753 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
3310
2889
  });
3311
2890
  this.activeSubSolver = this.overlapAvoidanceSolver;
3312
2891
  break;
3313
- case 2:
3314
- this.traceCleanupSolver = new TraceCleanupSolver({
3315
- inputProblem: this.inputProblem,
3316
- allTraces: this.overlapAvoidanceSolver.getOutput().allTraces,
3317
- targetTraceIds: new Set(
3318
- this.overlapAvoidanceSolver.getOutput().modifiedTraces.map(
3319
- (t) => t.mspPairId
3320
- )
3321
- ),
3322
- allLabelPlacements: this.labelMergingSolver.getOutput().netLabelPlacements,
3323
- mergedLabelNetIdMap: this.labelMergingSolver.getOutput().mergedLabelNetIdMap,
3324
- paddingBuffer: 0.01
3325
- });
3326
- this.activeSubSolver = this.traceCleanupSolver;
3327
- break;
3328
2892
  default:
3329
2893
  this.solved = true;
3330
2894
  break;
3331
2895
  }
3332
- }
3333
- getOutput() {
3334
- return {
3335
- traces: this.traceCleanupSolver?.getOutput().traces ?? this.traces,
3336
- netLabelPlacements: this.labelMergingSolver?.getOutput().netLabelPlacements ?? this.netLabelPlacements
3337
- };
2896
+ }
2897
+ getOutput() {
2898
+ return {
2899
+ traces: this.overlapAvoidanceSolver?.getOutput().allTraces ?? this.traces,
2900
+ netLabelPlacements: this.labelMergingSolver?.getOutput().netLabelPlacements ?? this.netLabelPlacements
2901
+ };
2902
+ }
2903
+ visualize() {
2904
+ if (this.activeSubSolver) {
2905
+ return this.activeSubSolver.visualize();
2906
+ }
2907
+ const graphics = visualizeInputProblem(this.inputProblem);
2908
+ if (!graphics.lines) graphics.lines = [];
2909
+ if (!graphics.rects) graphics.rects = [];
2910
+ const output = this.getOutput();
2911
+ for (const trace of output.traces) {
2912
+ graphics.lines.push({
2913
+ points: trace.tracePath,
2914
+ strokeColor: "purple"
2915
+ });
2916
+ }
2917
+ for (const label of output.netLabelPlacements) {
2918
+ const color = getColorFromString(label.globalConnNetId, 0.3);
2919
+ graphics.rects.push({
2920
+ center: label.center,
2921
+ width: label.width,
2922
+ height: label.height,
2923
+ fill: color,
2924
+ stroke: color.replace("0.3", "1"),
2925
+ label: label.globalConnNetId
2926
+ });
2927
+ }
2928
+ return graphics;
2929
+ }
2930
+ };
2931
+
2932
+ // lib/solvers/SchematicTracePipelineSolver/correctPinsInsideChip.ts
2933
+ var correctPinsInsideChips = (problem) => {
2934
+ for (const chip of problem.chips) {
2935
+ const bounds = getInputChipBounds(chip);
2936
+ for (const pin of chip.pins) {
2937
+ const isInside = pin.x > bounds.minX && pin.x < bounds.maxX && pin.y > bounds.minY && pin.y < bounds.maxY;
2938
+ if (!isInside) continue;
2939
+ const distLeft = pin.x - bounds.minX;
2940
+ const distRight = bounds.maxX - pin.x;
2941
+ const distBottom = pin.y - bounds.minY;
2942
+ const distTop = bounds.maxY - pin.y;
2943
+ const minDist = Math.min(distLeft, distRight, distBottom, distTop);
2944
+ if (minDist === distLeft) {
2945
+ pin.x = bounds.minX;
2946
+ } else if (minDist === distRight) {
2947
+ pin.x = bounds.maxX;
2948
+ } else if (minDist === distBottom) {
2949
+ pin.y = bounds.minY;
2950
+ } else {
2951
+ pin.y = bounds.maxY;
2952
+ }
2953
+ pin._facingDirection = void 0;
2954
+ }
2955
+ }
2956
+ };
2957
+
2958
+ // lib/solvers/SchematicTracePipelineSolver/expandChipsToFitPins.ts
2959
+ var expandChipsToFitPins = (problem) => {
2960
+ for (const chip of problem.chips) {
2961
+ const halfWidth = chip.width / 2;
2962
+ const halfHeight = chip.height / 2;
2963
+ let maxDx = 0;
2964
+ let maxDy = 0;
2965
+ for (const pin of chip.pins) {
2966
+ const dx = Math.abs(pin.x - chip.center.x);
2967
+ const dy = Math.abs(pin.y - chip.center.y);
2968
+ if (dx > maxDx) maxDx = dx;
2969
+ if (dy > maxDy) maxDy = dy;
2970
+ }
2971
+ const newHalfWidth = Math.max(halfWidth, maxDx);
2972
+ const newHalfHeight = Math.max(halfHeight, maxDy);
2973
+ if (newHalfWidth > halfWidth || newHalfHeight > halfHeight) {
2974
+ chip.width = newHalfWidth * 2;
2975
+ chip.height = newHalfHeight * 2;
2976
+ for (const pin of chip.pins) {
2977
+ pin._facingDirection = void 0;
2978
+ }
2979
+ }
2980
+ }
2981
+ };
2982
+
2983
+ // lib/utils/does-trace-overlap-with-existing-traces.ts
2984
+ import { doSegmentsIntersect } from "@tscircuit/math-utils";
2985
+ function doesTraceOverlapWithExistingTraces(newTracePath, existingTraces) {
2986
+ for (let i = 0; i < newTracePath.length - 1; i++) {
2987
+ const newSegmentP1 = newTracePath[i];
2988
+ const newSegmentP2 = newTracePath[i + 1];
2989
+ for (const existingTrace of existingTraces) {
2990
+ for (let j = 0; j < existingTrace.tracePath.length - 1; j++) {
2991
+ const existingSegmentP1 = existingTrace.tracePath[j];
2992
+ const existingSegmentP2 = existingTrace.tracePath[j + 1];
2993
+ if (doSegmentsIntersect(
2994
+ newSegmentP1,
2995
+ newSegmentP2,
2996
+ existingSegmentP1,
2997
+ existingSegmentP2
2998
+ )) {
2999
+ return true;
3000
+ }
3001
+ }
3002
+ }
3003
+ }
3004
+ return false;
3005
+ }
3006
+
3007
+ // lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts
3008
+ var NEAREST_NEIGHBOR_COUNT = 3;
3009
+ var distance = (p1, p2) => {
3010
+ return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
3011
+ };
3012
+ var LongDistancePairSolver = class extends BaseSolver {
3013
+ constructor(params) {
3014
+ super();
3015
+ this.params = params;
3016
+ const { inputProblem, primaryMspConnectionPairs, alreadySolvedTraces } = this.params;
3017
+ this.inputProblem = inputProblem;
3018
+ this.allSolvedTraces = [...alreadySolvedTraces];
3019
+ const primaryConnectedPinIds = /* @__PURE__ */ new Set();
3020
+ for (const pair of primaryMspConnectionPairs) {
3021
+ primaryConnectedPinIds.add(pair.pins[0].pinId);
3022
+ primaryConnectedPinIds.add(pair.pins[1].pinId);
3023
+ }
3024
+ const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
3025
+ this.netConnMap = netConnMap;
3026
+ const pinMap = /* @__PURE__ */ new Map();
3027
+ for (const chip of inputProblem.chips) {
3028
+ this.chipMap[chip.chipId] = chip;
3029
+ for (const pin of chip.pins) {
3030
+ pinMap.set(pin.pinId, { ...pin, chipId: chip.chipId });
3031
+ }
3032
+ }
3033
+ const candidatePairs = [];
3034
+ const addedPairKeys = /* @__PURE__ */ new Set();
3035
+ for (const netId of Object.keys(netConnMap.netMap)) {
3036
+ const allPinIdsInNet = netConnMap.getIdsConnectedToNet(netId);
3037
+ if (allPinIdsInNet.length < 2) continue;
3038
+ const unconnectedPinIds = allPinIdsInNet.filter(
3039
+ (pinId) => !primaryConnectedPinIds.has(pinId)
3040
+ );
3041
+ for (const unconnectedPinId of unconnectedPinIds) {
3042
+ const sourcePin = pinMap.get(unconnectedPinId);
3043
+ if (!sourcePin) continue;
3044
+ const neighbors = allPinIdsInNet.filter((otherPinId) => otherPinId !== unconnectedPinId).flatMap((otherPinId) => {
3045
+ const targetPin = pinMap.get(otherPinId);
3046
+ if (!targetPin) return [];
3047
+ return [
3048
+ {
3049
+ pin: targetPin,
3050
+ distance: distance(sourcePin, targetPin)
3051
+ }
3052
+ ];
3053
+ }).sort((a, b) => a.distance - b.distance).slice(0, NEAREST_NEIGHBOR_COUNT);
3054
+ for (const neighbor of neighbors) {
3055
+ const pair = [sourcePin, neighbor.pin];
3056
+ const pairKey = pair.map((p) => p.pinId).sort().join("--");
3057
+ if (!addedPairKeys.has(pairKey)) {
3058
+ candidatePairs.push(pair);
3059
+ addedPairKeys.add(pairKey);
3060
+ }
3061
+ }
3062
+ }
3063
+ }
3064
+ this.queuedCandidatePairs = candidatePairs;
3065
+ }
3066
+ solvedLongDistanceTraces = [];
3067
+ queuedCandidatePairs = [];
3068
+ currentCandidatePair = null;
3069
+ subSolver = null;
3070
+ chipMap = {};
3071
+ inputProblem;
3072
+ netConnMap;
3073
+ newlyConnectedPinIds = /* @__PURE__ */ new Set();
3074
+ allSolvedTraces = [];
3075
+ getConstructorParams() {
3076
+ return this.params;
3077
+ }
3078
+ _step() {
3079
+ if (this.subSolver?.solved) {
3080
+ const newTracePath = this.subSolver.solvedTracePath;
3081
+ if (newTracePath && this.currentCandidatePair) {
3082
+ const isTraceClear = !doesTraceOverlapWithExistingTraces(
3083
+ newTracePath,
3084
+ this.allSolvedTraces
3085
+ );
3086
+ if (isTraceClear) {
3087
+ const [p1, p2] = this.currentCandidatePair;
3088
+ const globalConnNetId = this.netConnMap.getNetConnectedToId(p1.pinId);
3089
+ const mspPairId = `${p1.pinId}-${p2.pinId}`;
3090
+ const newSolvedTrace = {
3091
+ mspPairId,
3092
+ dcConnNetId: globalConnNetId,
3093
+ globalConnNetId,
3094
+ pins: [p1, p2],
3095
+ tracePath: newTracePath,
3096
+ mspConnectionPairIds: [mspPairId],
3097
+ pinIds: [p1.pinId, p2.pinId]
3098
+ };
3099
+ this.solvedLongDistanceTraces.push(newSolvedTrace);
3100
+ this.allSolvedTraces.push(newSolvedTrace);
3101
+ this.newlyConnectedPinIds.add(p1.pinId);
3102
+ this.newlyConnectedPinIds.add(p2.pinId);
3103
+ }
3104
+ }
3105
+ this.subSolver = null;
3106
+ this.currentCandidatePair = null;
3107
+ } else if (this.subSolver?.failed) {
3108
+ this.subSolver = null;
3109
+ this.currentCandidatePair = null;
3110
+ }
3111
+ if (this.subSolver) {
3112
+ this.subSolver.step();
3113
+ return;
3114
+ }
3115
+ while (this.queuedCandidatePairs.length > 0) {
3116
+ const nextPair = this.queuedCandidatePairs.shift();
3117
+ const [p1, p2] = nextPair;
3118
+ if (this.newlyConnectedPinIds.has(p1.pinId) || this.newlyConnectedPinIds.has(p2.pinId)) {
3119
+ continue;
3120
+ }
3121
+ this.currentCandidatePair = nextPair;
3122
+ this.subSolver = new SchematicTraceSingleLineSolver2({
3123
+ inputProblem: this.params.inputProblem,
3124
+ pins: this.currentCandidatePair,
3125
+ chipMap: this.chipMap
3126
+ });
3127
+ return;
3128
+ }
3129
+ this.solved = true;
3338
3130
  }
3339
3131
  visualize() {
3340
- if (this.activeSubSolver) {
3341
- return this.activeSubSolver.visualize();
3132
+ if (this.subSolver) {
3133
+ return this.subSolver.visualize();
3342
3134
  }
3343
3135
  const graphics = visualizeInputProblem(this.inputProblem);
3344
- if (!graphics.lines) graphics.lines = [];
3345
- if (!graphics.rects) graphics.rects = [];
3346
- const output = this.getOutput();
3347
- for (const trace of output.traces) {
3136
+ for (const trace of this.solvedLongDistanceTraces) {
3348
3137
  graphics.lines.push({
3349
3138
  points: trace.tracePath,
3350
3139
  strokeColor: "purple"
3351
3140
  });
3352
3141
  }
3353
- for (const label of output.netLabelPlacements) {
3354
- const color = getColorFromString(label.globalConnNetId, 0.3);
3355
- graphics.rects.push({
3356
- center: label.center,
3357
- width: label.width,
3358
- height: label.height,
3359
- fill: color,
3360
- stroke: color.replace("0.3", "1"),
3361
- label: label.globalConnNetId
3142
+ for (const [p1, p2] of this.queuedCandidatePairs) {
3143
+ graphics.lines.push({
3144
+ points: [p1, p2],
3145
+ strokeColor: "gray",
3146
+ strokeDash: "4 4"
3362
3147
  });
3363
3148
  }
3364
3149
  return graphics;
3365
3150
  }
3151
+ getOutput() {
3152
+ if (!this.solved) {
3153
+ return { newTraces: [], allTracesMerged: this.params.alreadySolvedTraces };
3154
+ }
3155
+ return {
3156
+ newTraces: this.solvedLongDistanceTraces,
3157
+ allTracesMerged: [
3158
+ ...this.params.alreadySolvedTraces,
3159
+ ...this.solvedLongDistanceTraces
3160
+ ]
3161
+ };
3162
+ }
3366
3163
  };
3367
3164
 
3368
- // lib/solvers/SchematicTracePipelineSolver/correctPinsInsideChip.ts
3369
- var correctPinsInsideChips = (problem) => {
3370
- for (const chip of problem.chips) {
3371
- const bounds = getInputChipBounds(chip);
3372
- for (const pin of chip.pins) {
3373
- const isInside = pin.x > bounds.minX && pin.x < bounds.maxX && pin.y > bounds.minY && pin.y < bounds.maxY;
3374
- if (!isInside) continue;
3375
- const distLeft = pin.x - bounds.minX;
3376
- const distRight = bounds.maxX - pin.x;
3377
- const distBottom = pin.y - bounds.minY;
3378
- const distTop = bounds.maxY - pin.y;
3379
- const minDist = Math.min(distLeft, distRight, distBottom, distTop);
3380
- if (minDist === distLeft) {
3381
- pin.x = bounds.minX;
3382
- } else if (minDist === distRight) {
3383
- pin.x = bounds.maxX;
3384
- } else if (minDist === distBottom) {
3385
- pin.y = bounds.minY;
3386
- } else {
3387
- pin.y = bounds.maxY;
3165
+ // lib/solvers/TraceCleanupSolver/hasCollisions.ts
3166
+ var hasCollisions = (pathSegments, obstacles) => {
3167
+ for (let i = 0; i < pathSegments.length - 1; i++) {
3168
+ const p1 = pathSegments[i];
3169
+ const p2 = pathSegments[i + 1];
3170
+ for (const obstacle of obstacles) {
3171
+ if (segmentIntersectsRect(p1, p2, obstacle)) {
3172
+ return true;
3388
3173
  }
3389
- pin._facingDirection = void 0;
3390
3174
  }
3391
3175
  }
3176
+ return false;
3392
3177
  };
3393
3178
 
3394
- // lib/solvers/SchematicTracePipelineSolver/expandChipsToFitPins.ts
3395
- var expandChipsToFitPins = (problem) => {
3396
- for (const chip of problem.chips) {
3397
- const halfWidth = chip.width / 2;
3398
- const halfHeight = chip.height / 2;
3399
- let maxDx = 0;
3400
- let maxDy = 0;
3401
- for (const pin of chip.pins) {
3402
- const dx = Math.abs(pin.x - chip.center.x);
3403
- const dy = Math.abs(pin.y - chip.center.y);
3404
- if (dx > maxDx) maxDx = dx;
3405
- if (dy > maxDy) maxDy = dy;
3179
+ // lib/solvers/TraceCleanupSolver/countTurns.ts
3180
+ var countTurns = (points) => {
3181
+ let turns = 0;
3182
+ for (let i = 1; i < points.length - 1; i++) {
3183
+ const prev = points[i - 1];
3184
+ const curr = points[i];
3185
+ const next = points[i + 1];
3186
+ const prevVertical = prev.x === curr.x;
3187
+ const nextVertical = curr.x === next.x;
3188
+ if (prevVertical !== nextVertical) {
3189
+ turns++;
3406
3190
  }
3407
- const newHalfWidth = Math.max(halfWidth, maxDx);
3408
- const newHalfHeight = Math.max(halfHeight, maxDy);
3409
- if (newHalfWidth > halfWidth || newHalfHeight > halfHeight) {
3410
- chip.width = newHalfWidth * 2;
3411
- chip.height = newHalfHeight * 2;
3412
- for (const pin of chip.pins) {
3413
- pin._facingDirection = void 0;
3191
+ }
3192
+ return turns;
3193
+ };
3194
+
3195
+ // lib/solvers/TraceCleanupSolver/tryConnectPoints.ts
3196
+ var tryConnectPoints = (start, end) => {
3197
+ const candidates = [];
3198
+ if (start.x === end.x || start.y === end.y) {
3199
+ candidates.push([start, end]);
3200
+ } else {
3201
+ candidates.push([start, { x: end.x, y: start.y }, end]);
3202
+ candidates.push([start, { x: start.x, y: end.y }, end]);
3203
+ }
3204
+ return candidates;
3205
+ };
3206
+
3207
+ // lib/solvers/TraceCleanupSolver/hasCollisionsWithLabels.ts
3208
+ var hasCollisionsWithLabels = (pathSegments, labels) => {
3209
+ for (let i = 0; i < pathSegments.length - 1; i++) {
3210
+ const p1 = pathSegments[i];
3211
+ const p2 = pathSegments[i + 1];
3212
+ for (const label of labels) {
3213
+ if (segmentIntersectsRect(p1, p2, label)) {
3214
+ return true;
3215
+ }
3216
+ }
3217
+ }
3218
+ return false;
3219
+ };
3220
+
3221
+ // lib/solvers/TraceCleanupSolver/turnMinimization.ts
3222
+ var minimizeTurns = ({
3223
+ path,
3224
+ obstacles,
3225
+ labelBounds
3226
+ }) => {
3227
+ if (path.length <= 2) {
3228
+ return path;
3229
+ }
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
+ let optimizedPath = [...path];
3259
+ let currentTurns = countTurns(optimizedPath);
3260
+ let improved = true;
3261
+ while (improved) {
3262
+ improved = false;
3263
+ for (let startIdx = 0; startIdx < optimizedPath.length - 3; startIdx++) {
3264
+ const stairEndIdx = recognizeStairStepPattern(optimizedPath, startIdx);
3265
+ if (stairEndIdx > 0) {
3266
+ const startPoint = optimizedPath[startIdx];
3267
+ const endPoint = optimizedPath[stairEndIdx];
3268
+ const connectionOptions = tryConnectPoints(startPoint, endPoint);
3269
+ for (const connection of connectionOptions) {
3270
+ const testPath = [
3271
+ ...optimizedPath.slice(0, startIdx + 1),
3272
+ ...connection.slice(1, -1),
3273
+ ...optimizedPath.slice(stairEndIdx)
3274
+ ];
3275
+ const collidesWithObstacles = hasCollisions(connection, obstacles);
3276
+ const collidesWithLabels = hasCollisionsWithLabels(
3277
+ connection,
3278
+ labelBounds
3279
+ );
3280
+ if (!collidesWithObstacles && !collidesWithLabels) {
3281
+ const newTurns = countTurns(testPath);
3282
+ optimizedPath = testPath;
3283
+ currentTurns = newTurns;
3284
+ improved = true;
3285
+ break;
3286
+ }
3287
+ }
3288
+ if (improved) break;
3289
+ }
3290
+ }
3291
+ if (!improved) {
3292
+ for (let startIdx = 0; startIdx < optimizedPath.length - 2; startIdx++) {
3293
+ const maxRemove = Math.min(
3294
+ optimizedPath.length - startIdx - 2,
3295
+ optimizedPath.length - 2
3296
+ );
3297
+ for (let removeCount = 1; removeCount <= maxRemove; removeCount++) {
3298
+ const endIdx = startIdx + removeCount + 1;
3299
+ if (endIdx >= optimizedPath.length) continue;
3300
+ const startPoint = optimizedPath[startIdx];
3301
+ const endPoint = optimizedPath[endIdx];
3302
+ const connectionOptions = tryConnectPoints(startPoint, endPoint);
3303
+ for (const connection of connectionOptions) {
3304
+ const testPath = [
3305
+ ...optimizedPath.slice(0, startIdx + 1),
3306
+ ...connection.slice(1, -1),
3307
+ ...optimizedPath.slice(endIdx)
3308
+ ];
3309
+ const connectionSegments = connection;
3310
+ const collidesWithObstacles = hasCollisions(
3311
+ connectionSegments,
3312
+ obstacles
3313
+ );
3314
+ const collidesWithLabels = hasCollisionsWithLabels(
3315
+ connectionSegments,
3316
+ labelBounds
3317
+ );
3318
+ if (!collidesWithObstacles && !collidesWithLabels) {
3319
+ const newTurns = countTurns(testPath);
3320
+ if (newTurns < currentTurns || newTurns === currentTurns && testPath.length < optimizedPath.length) {
3321
+ optimizedPath = testPath;
3322
+ currentTurns = newTurns;
3323
+ improved = true;
3324
+ break;
3325
+ }
3326
+ }
3327
+ }
3328
+ if (improved) break;
3329
+ }
3330
+ if (improved) break;
3331
+ }
3332
+ }
3333
+ if (!improved) {
3334
+ for (let i = 0; i < optimizedPath.length - 2; i++) {
3335
+ const p1 = optimizedPath[i];
3336
+ const p2 = optimizedPath[i + 1];
3337
+ const p3 = optimizedPath[i + 2];
3338
+ const allVertical = p1.x === p2.x && p2.x === p3.x;
3339
+ const allHorizontal = p1.y === p2.y && p2.y === p3.y;
3340
+ if (allVertical || allHorizontal) {
3341
+ const testPath = [
3342
+ ...optimizedPath.slice(0, i + 1),
3343
+ ...optimizedPath.slice(i + 2)
3344
+ ];
3345
+ const collidesWithObstacles = hasCollisions([p1, p3], obstacles);
3346
+ const collidesWithLabels = hasCollisionsWithLabels(
3347
+ [p1, p3],
3348
+ labelBounds
3349
+ );
3350
+ if (!collidesWithObstacles && !collidesWithLabels) {
3351
+ optimizedPath = testPath;
3352
+ improved = true;
3353
+ break;
3354
+ }
3355
+ }
3414
3356
  }
3415
3357
  }
3416
3358
  }
3359
+ const finalSimplifiedPath = simplifyPath(optimizedPath);
3360
+ return finalSimplifiedPath;
3417
3361
  };
3418
3362
 
3419
- // lib/utils/does-trace-overlap-with-existing-traces.ts
3420
- import { doSegmentsIntersect } from "@tscircuit/math-utils";
3421
- function doesTraceOverlapWithExistingTraces(newTracePath, existingTraces) {
3422
- for (let i = 0; i < newTracePath.length - 1; i++) {
3423
- const newSegmentP1 = newTracePath[i];
3424
- const newSegmentP2 = newTracePath[i + 1];
3425
- for (const existingTrace of existingTraces) {
3426
- for (let j = 0; j < existingTrace.tracePath.length - 1; j++) {
3427
- const existingSegmentP1 = existingTrace.tracePath[j];
3428
- const existingSegmentP2 = existingTrace.tracePath[j + 1];
3429
- if (doSegmentsIntersect(
3430
- newSegmentP1,
3431
- newSegmentP2,
3432
- existingSegmentP1,
3433
- existingSegmentP2
3434
- )) {
3435
- return true;
3436
- }
3437
- }
3438
- }
3363
+ // lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts
3364
+ var minimizeTurnsWithFilteredLabels = ({
3365
+ targetMspConnectionPairId,
3366
+ traces,
3367
+ inputProblem,
3368
+ allLabelPlacements,
3369
+ mergedLabelNetIdMap,
3370
+ paddingBuffer
3371
+ }) => {
3372
+ const targetTrace = traces.find(
3373
+ (t) => t.mspPairId === targetMspConnectionPairId
3374
+ );
3375
+ if (!targetTrace) {
3376
+ throw new Error(`Target trace ${targetMspConnectionPairId} not found`);
3439
3377
  }
3440
- return false;
3441
- }
3442
-
3443
- // lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts
3444
- var NEAREST_NEIGHBOR_COUNT = 3;
3445
- var distance = (p1, p2) => {
3446
- return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
3378
+ const obstacleTraces = traces.filter(
3379
+ (t) => t.mspPairId !== targetMspConnectionPairId
3380
+ );
3381
+ const TRACE_WIDTH = 0.01;
3382
+ const traceObstacles = obstacleTraces.flatMap(
3383
+ (trace, i) => trace.tracePath.slice(0, -1).map((p1, pi) => {
3384
+ const p2 = trace.tracePath[pi + 1];
3385
+ return {
3386
+ chipId: `trace-obstacle-${i}-${pi}`,
3387
+ minX: Math.min(p1.x, p2.x) - TRACE_WIDTH / 2,
3388
+ minY: Math.min(p1.y, p2.y) - TRACE_WIDTH / 2,
3389
+ maxX: Math.max(p1.x, p2.x) + TRACE_WIDTH / 2,
3390
+ maxY: Math.max(p1.y, p2.y) + TRACE_WIDTH / 2
3391
+ };
3392
+ })
3393
+ );
3394
+ const staticObstacles = getObstacleRects(inputProblem);
3395
+ const combinedObstacles = [...staticObstacles, ...traceObstacles];
3396
+ const originalPath = targetTrace.tracePath;
3397
+ const filteredLabels = allLabelPlacements.filter((label) => {
3398
+ const originalNetIds = mergedLabelNetIdMap[label.globalConnNetId];
3399
+ if (originalNetIds) {
3400
+ return !originalNetIds.has(targetTrace.globalConnNetId);
3401
+ }
3402
+ return label.globalConnNetId !== targetTrace.globalConnNetId;
3403
+ });
3404
+ const labelBounds = filteredLabels.map((nl) => ({
3405
+ minX: nl.center.x - nl.width / 2 - paddingBuffer,
3406
+ maxX: nl.center.x + nl.width / 2 + paddingBuffer,
3407
+ minY: nl.center.y - nl.height / 2 - paddingBuffer,
3408
+ maxY: nl.center.y + nl.height / 2 + paddingBuffer
3409
+ }));
3410
+ const newPath = minimizeTurns({
3411
+ path: originalPath,
3412
+ obstacles: combinedObstacles,
3413
+ labelBounds
3414
+ });
3415
+ return {
3416
+ ...targetTrace,
3417
+ tracePath: newPath
3418
+ };
3447
3419
  };
3448
- var LongDistancePairSolver = class extends BaseSolver {
3449
- constructor(params) {
3450
- super();
3451
- this.params = params;
3452
- const { inputProblem, primaryMspConnectionPairs, alreadySolvedTraces } = this.params;
3453
- this.inputProblem = inputProblem;
3454
- this.allSolvedTraces = [...alreadySolvedTraces];
3455
- const primaryConnectedPinIds = /* @__PURE__ */ new Set();
3456
- for (const pair of primaryMspConnectionPairs) {
3457
- primaryConnectedPinIds.add(pair.pins[0].pinId);
3458
- primaryConnectedPinIds.add(pair.pins[1].pinId);
3459
- }
3460
- const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
3461
- this.netConnMap = netConnMap;
3462
- const pinMap = /* @__PURE__ */ new Map();
3463
- for (const chip of inputProblem.chips) {
3464
- this.chipMap[chip.chipId] = chip;
3465
- for (const pin of chip.pins) {
3466
- pinMap.set(pin.pinId, { ...pin, chipId: chip.chipId });
3420
+
3421
+ // lib/solvers/TraceCleanupSolver/balanceZShapes.ts
3422
+ var balanceZShapes = ({
3423
+ targetMspConnectionPairId,
3424
+ traces,
3425
+ inputProblem,
3426
+ allLabelPlacements,
3427
+ mergedLabelNetIdMap,
3428
+ paddingBuffer
3429
+ }) => {
3430
+ const targetTrace = traces.find(
3431
+ (t) => t.mspPairId === targetMspConnectionPairId
3432
+ );
3433
+ if (!targetTrace) {
3434
+ throw new Error(`Target trace ${targetMspConnectionPairId} not found`);
3435
+ }
3436
+ const TOLERANCE = 1e-5;
3437
+ const obstacleTraces = traces.filter(
3438
+ (t) => t.mspPairId !== targetMspConnectionPairId
3439
+ );
3440
+ const TRACE_WIDTH = 0.01;
3441
+ const traceObstacles = obstacleTraces.flatMap(
3442
+ (trace, i) => trace.tracePath.slice(0, -1).map((p1, pi) => {
3443
+ const p2 = trace.tracePath[pi + 1];
3444
+ return {
3445
+ chipId: `trace-obstacle-${i}-${pi}`,
3446
+ minX: Math.min(p1.x, p2.x) - TRACE_WIDTH / 2,
3447
+ minY: Math.min(p1.y, p2.y) - TRACE_WIDTH / 2,
3448
+ maxX: Math.max(p1.x, p2.x) + TRACE_WIDTH / 2,
3449
+ maxY: Math.max(p1.y, p2.y) + TRACE_WIDTH / 2
3450
+ };
3451
+ })
3452
+ );
3453
+ const staticObstacles = getObstacleRects(inputProblem).map((obs) => ({
3454
+ ...obs,
3455
+ minX: obs.minX + TOLERANCE,
3456
+ maxX: obs.maxX - TOLERANCE,
3457
+ minY: obs.minY + TOLERANCE,
3458
+ maxY: obs.maxY - TOLERANCE
3459
+ }));
3460
+ const combinedObstacles = [...staticObstacles, ...traceObstacles];
3461
+ const segmentIntersectsAnyRect = (p1, p2, rects) => {
3462
+ for (const rect of rects) {
3463
+ if (segmentIntersectsRect(p1, p2, rect)) {
3464
+ return true;
3467
3465
  }
3468
3466
  }
3469
- const candidatePairs = [];
3470
- const addedPairKeys = /* @__PURE__ */ new Set();
3471
- for (const netId of Object.keys(netConnMap.netMap)) {
3472
- const allPinIdsInNet = netConnMap.getIdsConnectedToNet(netId);
3473
- if (allPinIdsInNet.length < 2) continue;
3474
- const unconnectedPinIds = allPinIdsInNet.filter(
3475
- (pinId) => !primaryConnectedPinIds.has(pinId)
3476
- );
3477
- for (const unconnectedPinId of unconnectedPinIds) {
3478
- const sourcePin = pinMap.get(unconnectedPinId);
3479
- if (!sourcePin) continue;
3480
- const neighbors = allPinIdsInNet.filter((otherPinId) => otherPinId !== unconnectedPinId).flatMap((otherPinId) => {
3481
- const targetPin = pinMap.get(otherPinId);
3482
- if (!targetPin) return [];
3483
- return [
3484
- {
3485
- pin: targetPin,
3486
- distance: distance(sourcePin, targetPin)
3487
- }
3488
- ];
3489
- }).sort((a, b) => a.distance - b.distance).slice(0, NEAREST_NEIGHBOR_COUNT);
3490
- for (const neighbor of neighbors) {
3491
- const pair = [sourcePin, neighbor.pin];
3492
- const pairKey = pair.map((p) => p.pinId).sort().join("--");
3493
- if (!addedPairKeys.has(pairKey)) {
3494
- candidatePairs.push(pair);
3495
- addedPairKeys.add(pairKey);
3496
- }
3497
- }
3498
- }
3467
+ return false;
3468
+ };
3469
+ const filteredLabels = allLabelPlacements.filter((label) => {
3470
+ const originalNetIds = mergedLabelNetIdMap[label.globalConnNetId];
3471
+ if (originalNetIds) {
3472
+ return !originalNetIds.has(targetTrace.globalConnNetId);
3473
+ }
3474
+ return label.globalConnNetId !== targetTrace.globalConnNetId;
3475
+ });
3476
+ const labelBounds = filteredLabels.map((nl) => ({
3477
+ minX: nl.center.x - nl.width / 2 + TOLERANCE,
3478
+ maxX: nl.center.x + nl.width / 2 - TOLERANCE,
3479
+ minY: nl.center.y - nl.height / 2 + TOLERANCE,
3480
+ maxY: nl.center.y + nl.height / 2 - TOLERANCE
3481
+ }));
3482
+ const newPath = [...targetTrace.tracePath];
3483
+ if (newPath.length < 4) {
3484
+ return { ...targetTrace };
3485
+ }
3486
+ if (newPath.length === 4) {
3487
+ const [p0, p1, p2, p3] = newPath;
3488
+ let p1New;
3489
+ let p2New;
3490
+ const isHVHShape = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y;
3491
+ if (isHVHShape) {
3492
+ const idealX = (p0.x + p3.x) / 2;
3493
+ p1New = { x: idealX, y: p1.y };
3494
+ p2New = { x: idealX, y: p2.y };
3495
+ } else {
3496
+ const idealY = (p0.y + p3.y) / 2;
3497
+ p1New = { x: p1.x, y: idealY };
3498
+ p2New = { x: p2.x, y: idealY };
3499
+ }
3500
+ const collides = segmentIntersectsAnyRect(p0, p1New, combinedObstacles) || segmentIntersectsAnyRect(p1New, p2New, combinedObstacles) || segmentIntersectsAnyRect(p2New, p3, combinedObstacles) || segmentIntersectsAnyRect(p0, p1New, labelBounds) || segmentIntersectsAnyRect(p1New, p2New, labelBounds) || segmentIntersectsAnyRect(p2New, p3, labelBounds);
3501
+ if (!collides) {
3502
+ newPath[1] = p1New;
3503
+ newPath[2] = p2New;
3504
+ }
3505
+ return { ...targetTrace, tracePath: simplifyPath(newPath) };
3506
+ }
3507
+ for (let i = 1; i < newPath.length - 4; i++) {
3508
+ const p1 = newPath[i];
3509
+ const p2 = newPath[i + 1];
3510
+ const p3 = newPath[i + 2];
3511
+ const p4 = newPath[i + 3];
3512
+ const isHVHZShape = p1.y === p2.y && p2.x === p3.x && p3.y === p4.y;
3513
+ const isVHVZShape = p1.x === p2.x && p2.y === p3.y && p3.x === p4.x;
3514
+ const isCollinearHorizontal = p1.y === p2.y && p2.y === p3.y && p3.y === p4.y;
3515
+ const isCollinearVertical = p1.x === p2.x && p2.x === p3.x && p3.x === p4.x;
3516
+ const isCollinear = isCollinearHorizontal || isCollinearVertical;
3517
+ let isSameDirection = false;
3518
+ if (isHVHZShape) {
3519
+ isSameDirection = Math.sign(p2.x - p1.x) === Math.sign(p4.x - p3.x);
3520
+ } else if (isVHVZShape) {
3521
+ isSameDirection = Math.sign(p2.y - p1.y) === Math.sign(p4.y - p3.y);
3522
+ }
3523
+ const isValidZShape = (isHVHZShape || isVHVZShape) && !isCollinear && isSameDirection;
3524
+ if (!isValidZShape) {
3525
+ continue;
3526
+ }
3527
+ let p2New;
3528
+ let p3New;
3529
+ const len1Original = isHVHZShape ? Math.abs(p1.x - p2.x) : Math.abs(p1.y - p2.y);
3530
+ const len2Original = isHVHZShape ? Math.abs(p3.x - p4.x) : Math.abs(p3.y - p4.y);
3531
+ if (Math.abs(len1Original - len2Original) < 1e-3) {
3532
+ continue;
3533
+ }
3534
+ if (isHVHZShape) {
3535
+ const idealX = (p1.x + p4.x) / 2;
3536
+ p2New = { x: idealX, y: p2.y };
3537
+ p3New = { x: idealX, y: p3.y };
3538
+ } else {
3539
+ const idealY = (p1.y + p4.y) / 2;
3540
+ p2New = { x: p2.x, y: idealY };
3541
+ p3New = { x: p3.x, y: idealY };
3542
+ }
3543
+ const collides = segmentIntersectsAnyRect(p1, p2New, combinedObstacles) || segmentIntersectsAnyRect(p2New, p3New, combinedObstacles) || segmentIntersectsAnyRect(p3New, p4, combinedObstacles) || segmentIntersectsAnyRect(p1, p2New, labelBounds) || segmentIntersectsAnyRect(p2New, p3New, labelBounds) || segmentIntersectsAnyRect(p3New, p4, labelBounds);
3544
+ if (!collides) {
3545
+ newPath[i + 1] = p2New;
3546
+ newPath[i + 2] = p3New;
3547
+ i = 0;
3499
3548
  }
3500
- this.queuedCandidatePairs = candidatePairs;
3501
3549
  }
3502
- solvedLongDistanceTraces = [];
3503
- queuedCandidatePairs = [];
3504
- currentCandidatePair = null;
3505
- subSolver = null;
3506
- chipMap = {};
3507
- inputProblem;
3508
- netConnMap;
3509
- newlyConnectedPinIds = /* @__PURE__ */ new Set();
3510
- allSolvedTraces = [];
3511
- getConstructorParams() {
3512
- return this.params;
3550
+ const finalSimplifiedPath = simplifyPath(newPath);
3551
+ return {
3552
+ ...targetTrace,
3553
+ tracePath: finalSimplifiedPath
3554
+ };
3555
+ };
3556
+
3557
+ // lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts
3558
+ var TraceCleanupSolver = class extends BaseSolver {
3559
+ input;
3560
+ outputTraces;
3561
+ traceIdQueue;
3562
+ tracesMap;
3563
+ pipelineStep = "minimizing_turns";
3564
+ activeTraceId = null;
3565
+ // New property
3566
+ constructor(solverInput) {
3567
+ super();
3568
+ this.input = solverInput;
3569
+ this.outputTraces = [...solverInput.allTraces];
3570
+ this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]));
3571
+ this.traceIdQueue = Array.from(solverInput.targetTraceIds);
3513
3572
  }
3514
3573
  _step() {
3515
- if (this.subSolver?.solved) {
3516
- const newTracePath = this.subSolver.solvedTracePath;
3517
- if (newTracePath && this.currentCandidatePair) {
3518
- const isTraceClear = !doesTraceOverlapWithExistingTraces(
3519
- newTracePath,
3520
- this.allSolvedTraces
3521
- );
3522
- if (isTraceClear) {
3523
- const [p1, p2] = this.currentCandidatePair;
3524
- const globalConnNetId = this.netConnMap.getNetConnectedToId(p1.pinId);
3525
- const mspPairId = `${p1.pinId}-${p2.pinId}`;
3526
- const newSolvedTrace = {
3527
- mspPairId,
3528
- dcConnNetId: globalConnNetId,
3529
- globalConnNetId,
3530
- pins: [p1, p2],
3531
- tracePath: newTracePath,
3532
- mspConnectionPairIds: [mspPairId],
3533
- pinIds: [p1.pinId, p2.pinId]
3534
- };
3535
- this.solvedLongDistanceTraces.push(newSolvedTrace);
3536
- this.allSolvedTraces.push(newSolvedTrace);
3537
- this.newlyConnectedPinIds.add(p1.pinId);
3538
- this.newlyConnectedPinIds.add(p2.pinId);
3539
- }
3540
- }
3541
- this.subSolver = null;
3542
- this.currentCandidatePair = null;
3543
- } else if (this.subSolver?.failed) {
3544
- this.subSolver = null;
3545
- this.currentCandidatePair = null;
3574
+ if (this.pipelineStep === "minimizing_turns" && this.traceIdQueue.length === 0) {
3575
+ this.pipelineStep = "balancing_l_shapes";
3576
+ this.traceIdQueue = Array.from(this.input.targetTraceIds);
3546
3577
  }
3547
- if (this.subSolver) {
3548
- this.subSolver.step();
3578
+ if (this.pipelineStep === "balancing_l_shapes" && this.traceIdQueue.length === 0) {
3579
+ this.solved = true;
3549
3580
  return;
3550
3581
  }
3551
- while (this.queuedCandidatePairs.length > 0) {
3552
- const nextPair = this.queuedCandidatePairs.shift();
3553
- const [p1, p2] = nextPair;
3554
- if (this.newlyConnectedPinIds.has(p1.pinId) || this.newlyConnectedPinIds.has(p2.pinId)) {
3555
- continue;
3556
- }
3557
- this.currentCandidatePair = nextPair;
3558
- this.subSolver = new SchematicTraceSingleLineSolver2({
3559
- inputProblem: this.params.inputProblem,
3560
- pins: this.currentCandidatePair,
3561
- chipMap: this.chipMap
3562
- });
3582
+ const targetMspConnectionPairId = this.traceIdQueue.shift();
3583
+ this.activeTraceId = targetMspConnectionPairId;
3584
+ 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)) {
3563
3594
  return;
3564
3595
  }
3565
- this.solved = true;
3566
- }
3567
- visualize() {
3568
- if (this.subSolver) {
3569
- return this.subSolver.visualize();
3570
- }
3571
- const graphics = visualizeInputProblem(this.inputProblem);
3572
- for (const trace of this.solvedLongDistanceTraces) {
3573
- graphics.lines.push({
3574
- points: trace.tracePath,
3575
- strokeColor: "purple"
3596
+ const allTraces = Array.from(this.tracesMap.values());
3597
+ let updatedTrace;
3598
+ if (this.pipelineStep === "minimizing_turns") {
3599
+ updatedTrace = minimizeTurnsWithFilteredLabels({
3600
+ ...this.input,
3601
+ targetMspConnectionPairId,
3602
+ traces: allTraces
3576
3603
  });
3577
- }
3578
- for (const [p1, p2] of this.queuedCandidatePairs) {
3579
- graphics.lines.push({
3580
- points: [p1, p2],
3581
- strokeColor: "gray",
3582
- strokeDash: "4 4"
3604
+ } else {
3605
+ updatedTrace = balanceZShapes({
3606
+ ...this.input,
3607
+ targetMspConnectionPairId,
3608
+ traces: allTraces
3583
3609
  });
3584
3610
  }
3585
- return graphics;
3611
+ this.tracesMap.set(targetMspConnectionPairId, updatedTrace);
3612
+ this.outputTraces = Array.from(this.tracesMap.values());
3586
3613
  }
3587
3614
  getOutput() {
3588
- if (!this.solved) {
3589
- return { newTraces: [], allTracesMerged: this.params.alreadySolvedTraces };
3590
- }
3591
3615
  return {
3592
- newTraces: this.solvedLongDistanceTraces,
3593
- allTracesMerged: [
3594
- ...this.params.alreadySolvedTraces,
3595
- ...this.solvedLongDistanceTraces
3596
- ]
3616
+ traces: this.outputTraces
3597
3617
  };
3598
3618
  }
3619
+ visualize() {
3620
+ const graphics = visualizeInputProblem(this.input.inputProblem, {
3621
+ chipAlpha: 0.1,
3622
+ connectionAlpha: 0.1
3623
+ });
3624
+ if (!graphics.lines) graphics.lines = [];
3625
+ if (!graphics.points) graphics.points = [];
3626
+ if (!graphics.rects) graphics.rects = [];
3627
+ if (!graphics.circles) graphics.circles = [];
3628
+ if (!graphics.texts) graphics.texts = [];
3629
+ for (const trace of this.outputTraces) {
3630
+ const line = {
3631
+ points: trace.tracePath.map((p) => ({ x: p.x, y: p.y })),
3632
+ strokeColor: trace.mspPairId === this.activeTraceId ? "red" : "blue"
3633
+ // Highlight active trace
3634
+ };
3635
+ graphics.lines.push(line);
3636
+ }
3637
+ return graphics;
3638
+ }
3599
3639
  };
3600
3640
 
3601
3641
  // lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
@@ -3725,6 +3765,21 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3725
3765
  ];
3726
3766
  }
3727
3767
  ),
3768
+ definePipelineStep("traceCleanupSolver", TraceCleanupSolver, (instance) => {
3769
+ const prevSolverOutput = instance.traceLabelOverlapAvoidanceSolver.getOutput();
3770
+ const traces = prevSolverOutput.traces;
3771
+ const labelMergingOutput = instance.traceLabelOverlapAvoidanceSolver.labelMergingSolver.getOutput();
3772
+ return [
3773
+ {
3774
+ inputProblem: instance.inputProblem,
3775
+ allTraces: traces,
3776
+ targetTraceIds: new Set(traces.map((t) => t.mspPairId)),
3777
+ allLabelPlacements: labelMergingOutput.netLabelPlacements,
3778
+ mergedLabelNetIdMap: labelMergingOutput.mergedLabelNetIdMap,
3779
+ paddingBuffer: 0.1
3780
+ }
3781
+ ];
3782
+ }),
3728
3783
  definePipelineStep(
3729
3784
  "netLabelPlacementSolver",
3730
3785
  NetLabelPlacementSolver,