@tscircuit/schematic-trace-solver 0.0.42 → 0.0.43

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 (37) hide show
  1. package/dist/index.d.ts +28 -29
  2. package/dist/index.js +917 -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/package.json +1 -1
  11. package/site/examples/example26.page.tsx +4 -0
  12. package/tests/assets/example26.json +1206 -0
  13. package/tests/examples/__snapshots__/example02.snap.svg +1 -1
  14. package/tests/examples/__snapshots__/example09.snap.svg +34 -34
  15. package/tests/examples/__snapshots__/example11.snap.svg +13 -13
  16. package/tests/examples/__snapshots__/example12.snap.svg +3 -3
  17. package/tests/examples/__snapshots__/example13.snap.svg +12 -12
  18. package/tests/examples/__snapshots__/example16.snap.svg +4 -4
  19. package/tests/examples/__snapshots__/example19.snap.svg +9 -9
  20. package/tests/examples/__snapshots__/example25.snap.svg +3 -3
  21. package/tests/examples/__snapshots__/example26.snap.svg +3 -3
  22. package/tests/examples/__snapshots__/example28.snap.svg +3 -3
  23. package/tests/examples/__snapshots__/example29.snap.svg +1208 -0
  24. package/tests/examples/example29.test.ts +13 -0
  25. package/tests/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solver → TraceCleanupSolver}/TraceCleanupSolver.test.ts +2 -2
  26. package/tests/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solver → TraceCleanupSolver}/__snapshots__/TraceCleanupSolver.snap.svg +1 -1
  27. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView03.snap.svg +1 -1
  28. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView03.test.ts +1 -1
  29. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/TraceCleanupSolver.ts +0 -132
  30. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/balanceLShapes.ts +0 -209
  31. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +0 -66
  32. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/countTurns.ts +0 -0
  33. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/hasCollisions.ts +0 -0
  34. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/hasCollisionsWithLabels.ts +0 -0
  35. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/simplifyPath.ts +0 -0
  36. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/tryConnectPoints.ts +0 -0
  37. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/turnMinimization.ts +0 -0
package/dist/index.js CHANGED
@@ -2352,37 +2352,182 @@ var MergedNetLabelObstacleSolver = class extends BaseSolver {
2352
2352
  }
2353
2353
  };
2354
2354
 
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;
2355
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts
2356
+ var detectTraceLabelOverlap = (traces, netLabels) => {
2357
+ const overlaps = [];
2358
+ for (const trace of traces) {
2359
+ for (const label of netLabels) {
2360
+ const labelBounds = getRectBounds(label.center, label.width, label.height);
2361
+ for (let i = 0; i < trace.tracePath.length - 1; i++) {
2362
+ const p1 = trace.tracePath[i];
2363
+ const p2 = trace.tracePath[i + 1];
2364
+ if (segmentIntersectsRect2(p1, p2, labelBounds)) {
2365
+ if (trace.globalConnNetId === label.globalConnNetId) {
2366
+ break;
2367
+ }
2368
+ overlaps.push({ trace, label });
2369
+ break;
2370
+ }
2363
2371
  }
2364
2372
  }
2365
2373
  }
2366
- return false;
2374
+ return overlaps;
2367
2375
  };
2368
2376
 
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++;
2377
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/violation.ts
2378
+ var findTraceViolationZone = (path, labelBounds) => {
2379
+ const isPointInside = (p) => p.x > labelBounds.minX && p.x < labelBounds.maxX && p.y > labelBounds.minY && p.y < labelBounds.maxY;
2380
+ let firstInsideIndex = -1;
2381
+ let lastInsideIndex = -1;
2382
+ for (let i = 0; i < path.length; i++) {
2383
+ if (isPointInside(path[i])) {
2384
+ if (firstInsideIndex === -1) {
2385
+ firstInsideIndex = i;
2386
+ }
2387
+ lastInsideIndex = i;
2380
2388
  }
2381
2389
  }
2382
- return turns;
2390
+ return { firstInsideIndex, lastInsideIndex };
2391
+ };
2392
+
2393
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts
2394
+ var generateSnipAndReconnectCandidates = ({
2395
+ initialTrace,
2396
+ firstInsideIndex,
2397
+ lastInsideIndex,
2398
+ labelBounds,
2399
+ paddingBuffer,
2400
+ detourCount
2401
+ }) => {
2402
+ if (firstInsideIndex <= 0 || lastInsideIndex >= initialTrace.tracePath.length - 1) {
2403
+ return [];
2404
+ }
2405
+ const entryPoint = initialTrace.tracePath[firstInsideIndex - 1];
2406
+ const exitPoint = initialTrace.tracePath[lastInsideIndex + 1];
2407
+ const pathToEntry = initialTrace.tracePath.slice(0, firstInsideIndex);
2408
+ const pathFromExit = initialTrace.tracePath.slice(lastInsideIndex + 1);
2409
+ const allCandidateDetours = [];
2410
+ if (entryPoint.x !== exitPoint.x && entryPoint.y !== exitPoint.y) {
2411
+ allCandidateDetours.push([{ x: exitPoint.x, y: entryPoint.y }]);
2412
+ allCandidateDetours.push([{ x: entryPoint.x, y: exitPoint.y }]);
2413
+ } else if (entryPoint.x === exitPoint.x || entryPoint.y === exitPoint.y) {
2414
+ allCandidateDetours.push([]);
2415
+ }
2416
+ const buffer = paddingBuffer + detourCount * paddingBuffer;
2417
+ const leftX = labelBounds.minX - buffer;
2418
+ const rightX = labelBounds.maxX + buffer;
2419
+ const topY = labelBounds.maxY + buffer;
2420
+ const bottomY = labelBounds.minY - buffer;
2421
+ if ((entryPoint.x <= labelBounds.minX || exitPoint.x <= labelBounds.minX) && entryPoint.x < labelBounds.maxX && exitPoint.x < labelBounds.maxX) {
2422
+ allCandidateDetours.push([
2423
+ { x: leftX, y: entryPoint.y },
2424
+ { x: leftX, y: exitPoint.y }
2425
+ ]);
2426
+ }
2427
+ if ((entryPoint.x >= labelBounds.maxX || exitPoint.x >= labelBounds.maxX) && entryPoint.x > labelBounds.minX && exitPoint.x > labelBounds.minX) {
2428
+ allCandidateDetours.push([
2429
+ { x: rightX, y: entryPoint.y },
2430
+ { x: rightX, y: exitPoint.y }
2431
+ ]);
2432
+ }
2433
+ if ((entryPoint.y >= labelBounds.maxY || exitPoint.y >= labelBounds.maxY) && entryPoint.y > labelBounds.minY && exitPoint.y > labelBounds.minY) {
2434
+ allCandidateDetours.push([
2435
+ { x: entryPoint.x, y: topY },
2436
+ { x: exitPoint.x, y: topY }
2437
+ ]);
2438
+ }
2439
+ if ((entryPoint.y <= labelBounds.minY || exitPoint.y <= labelBounds.minY) && entryPoint.y < labelBounds.maxY && exitPoint.y < labelBounds.maxY) {
2440
+ allCandidateDetours.push([
2441
+ { x: entryPoint.x, y: bottomY },
2442
+ { x: exitPoint.x, y: bottomY }
2443
+ ]);
2444
+ }
2445
+ return allCandidateDetours.map((detour) => [
2446
+ ...pathToEntry,
2447
+ ...detour,
2448
+ ...pathFromExit
2449
+ ]);
2450
+ };
2451
+
2452
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts
2453
+ var generateFourPointDetourCandidates = ({
2454
+ initialTrace,
2455
+ label,
2456
+ labelBounds,
2457
+ paddingBuffer,
2458
+ detourCount
2459
+ }) => {
2460
+ let collidingSegIndex = -1;
2461
+ for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
2462
+ if (segmentIntersectsRect(
2463
+ initialTrace.tracePath[i],
2464
+ initialTrace.tracePath[i + 1],
2465
+ labelBounds
2466
+ )) {
2467
+ collidingSegIndex = i;
2468
+ break;
2469
+ }
2470
+ }
2471
+ if (collidingSegIndex === -1) return [];
2472
+ const pA = initialTrace.tracePath[collidingSegIndex];
2473
+ const pB = initialTrace.tracePath[collidingSegIndex + 1];
2474
+ if (!pA || !pB) return [];
2475
+ const candidateDetours = [];
2476
+ const paddedLabelBounds = getRectBounds(
2477
+ label.center,
2478
+ label.width,
2479
+ label.height
2480
+ );
2481
+ const effectivePadding = paddingBuffer + detourCount * paddingBuffer;
2482
+ if (isVertical(pA, pB)) {
2483
+ const xCandidates = [
2484
+ paddedLabelBounds.maxX + effectivePadding,
2485
+ paddedLabelBounds.minX - effectivePadding
2486
+ ];
2487
+ for (const newX of xCandidates) {
2488
+ candidateDetours.push(
2489
+ pB.y > pA.y ? [
2490
+ { x: pA.x, y: paddedLabelBounds.minY - effectivePadding },
2491
+ { x: newX, y: paddedLabelBounds.minY - effectivePadding },
2492
+ { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
2493
+ { x: pB.x, y: paddedLabelBounds.maxY + effectivePadding }
2494
+ ] : [
2495
+ { x: pA.x, y: paddedLabelBounds.maxY + effectivePadding },
2496
+ { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
2497
+ { x: newX, y: paddedLabelBounds.minY - effectivePadding },
2498
+ { x: pB.x, y: paddedLabelBounds.minY - effectivePadding }
2499
+ ]
2500
+ );
2501
+ }
2502
+ } else {
2503
+ const yCandidates = [
2504
+ paddedLabelBounds.maxY + effectivePadding,
2505
+ paddedLabelBounds.minY - effectivePadding
2506
+ ];
2507
+ for (const newY of yCandidates) {
2508
+ candidateDetours.push(
2509
+ pB.x > pA.x ? [
2510
+ { x: paddedLabelBounds.minX - effectivePadding, y: pA.y },
2511
+ { x: paddedLabelBounds.minX - effectivePadding, y: newY },
2512
+ { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
2513
+ { x: paddedLabelBounds.maxX + effectivePadding, y: pB.y }
2514
+ ] : [
2515
+ { x: paddedLabelBounds.maxX + effectivePadding, y: pA.y },
2516
+ { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
2517
+ { x: paddedLabelBounds.minX - effectivePadding, y: newY },
2518
+ { x: paddedLabelBounds.minX - effectivePadding, y: pB.y }
2519
+ ]
2520
+ );
2521
+ }
2522
+ }
2523
+ return candidateDetours.map((detourPoints) => [
2524
+ ...initialTrace.tracePath.slice(0, collidingSegIndex + 1),
2525
+ ...detourPoints,
2526
+ ...initialTrace.tracePath.slice(collidingSegIndex + 1)
2527
+ ]);
2383
2528
  };
2384
2529
 
2385
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/simplifyPath.ts
2530
+ // lib/solvers/TraceCleanupSolver/simplifyPath.ts
2386
2531
  var simplifyPath = (path) => {
2387
2532
  if (path.length < 3) return path;
2388
2533
  const newPath = [path[0]];
@@ -2411,675 +2556,46 @@ var simplifyPath = (path) => {
2411
2556
  return finalPath;
2412
2557
  };
2413
2558
 
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]);
2422
- }
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
- }
2559
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts
2560
+ var generateRerouteCandidates = ({
2561
+ trace,
2562
+ label,
2563
+ paddingBuffer,
2564
+ detourCount
2565
+ }) => {
2566
+ const initialTrace = { ...trace, tracePath: simplifyPath(trace.tracePath) };
2567
+ if (trace.globalConnNetId === label.globalConnNetId) {
2568
+ return [initialTrace.tracePath];
2436
2569
  }
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];
2570
+ const labelPadding = paddingBuffer;
2571
+ const labelBoundsRaw = getRectBounds(label.center, label.width, label.height);
2572
+ const labelBounds = {
2573
+ minX: labelBoundsRaw.minX - labelPadding,
2574
+ minY: labelBoundsRaw.minY - labelPadding,
2575
+ maxX: labelBoundsRaw.maxX + labelPadding,
2576
+ maxY: labelBoundsRaw.maxY + labelPadding,
2577
+ chipId: `netlabel-${label.netId}`
2578
+ };
2579
+ const fourPointCandidates = generateFourPointDetourCandidates({
2580
+ initialTrace,
2581
+ label,
2582
+ labelBounds,
2583
+ paddingBuffer,
2584
+ detourCount
2585
+ });
2586
+ const { firstInsideIndex, lastInsideIndex } = findTraceViolationZone(
2587
+ initialTrace.tracePath,
2588
+ labelBounds
2589
+ );
2590
+ const snipReconnectCandidates = generateSnipAndReconnectCandidates({
2591
+ initialTrace,
2592
+ firstInsideIndex,
2593
+ lastInsideIndex,
2594
+ labelBounds,
2595
+ paddingBuffer,
2596
+ detourCount
2597
+ });
2598
+ return [...fourPointCandidates, ...snipReconnectCandidates];
3083
2599
  };
3084
2600
 
3085
2601
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts
@@ -3272,7 +2788,6 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
3272
2788
  // sub-solver instances
3273
2789
  labelMergingSolver;
3274
2790
  overlapAvoidanceSolver;
3275
- traceCleanupSolver;
3276
2791
  pipelineStepIndex = 0;
3277
2792
  constructor(solverInput) {
3278
2793
  super();
@@ -3310,292 +2825,753 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
3310
2825
  });
3311
2826
  this.activeSubSolver = this.overlapAvoidanceSolver;
3312
2827
  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
2828
  default:
3329
2829
  this.solved = true;
3330
2830
  break;
3331
2831
  }
3332
- }
3333
- getOutput() {
3334
- return {
3335
- traces: this.traceCleanupSolver?.getOutput().traces ?? this.traces,
3336
- netLabelPlacements: this.labelMergingSolver?.getOutput().netLabelPlacements ?? this.netLabelPlacements
3337
- };
2832
+ }
2833
+ getOutput() {
2834
+ return {
2835
+ traces: this.overlapAvoidanceSolver?.getOutput().allTraces ?? this.traces,
2836
+ netLabelPlacements: this.labelMergingSolver?.getOutput().netLabelPlacements ?? this.netLabelPlacements
2837
+ };
2838
+ }
2839
+ visualize() {
2840
+ if (this.activeSubSolver) {
2841
+ return this.activeSubSolver.visualize();
2842
+ }
2843
+ const graphics = visualizeInputProblem(this.inputProblem);
2844
+ if (!graphics.lines) graphics.lines = [];
2845
+ if (!graphics.rects) graphics.rects = [];
2846
+ const output = this.getOutput();
2847
+ for (const trace of output.traces) {
2848
+ graphics.lines.push({
2849
+ points: trace.tracePath,
2850
+ strokeColor: "purple"
2851
+ });
2852
+ }
2853
+ for (const label of output.netLabelPlacements) {
2854
+ const color = getColorFromString(label.globalConnNetId, 0.3);
2855
+ graphics.rects.push({
2856
+ center: label.center,
2857
+ width: label.width,
2858
+ height: label.height,
2859
+ fill: color,
2860
+ stroke: color.replace("0.3", "1"),
2861
+ label: label.globalConnNetId
2862
+ });
2863
+ }
2864
+ return graphics;
2865
+ }
2866
+ };
2867
+
2868
+ // lib/solvers/SchematicTracePipelineSolver/correctPinsInsideChip.ts
2869
+ var correctPinsInsideChips = (problem) => {
2870
+ for (const chip of problem.chips) {
2871
+ const bounds = getInputChipBounds(chip);
2872
+ for (const pin of chip.pins) {
2873
+ const isInside = pin.x > bounds.minX && pin.x < bounds.maxX && pin.y > bounds.minY && pin.y < bounds.maxY;
2874
+ if (!isInside) continue;
2875
+ const distLeft = pin.x - bounds.minX;
2876
+ const distRight = bounds.maxX - pin.x;
2877
+ const distBottom = pin.y - bounds.minY;
2878
+ const distTop = bounds.maxY - pin.y;
2879
+ const minDist = Math.min(distLeft, distRight, distBottom, distTop);
2880
+ if (minDist === distLeft) {
2881
+ pin.x = bounds.minX;
2882
+ } else if (minDist === distRight) {
2883
+ pin.x = bounds.maxX;
2884
+ } else if (minDist === distBottom) {
2885
+ pin.y = bounds.minY;
2886
+ } else {
2887
+ pin.y = bounds.maxY;
2888
+ }
2889
+ pin._facingDirection = void 0;
2890
+ }
2891
+ }
2892
+ };
2893
+
2894
+ // lib/solvers/SchematicTracePipelineSolver/expandChipsToFitPins.ts
2895
+ var expandChipsToFitPins = (problem) => {
2896
+ for (const chip of problem.chips) {
2897
+ const halfWidth = chip.width / 2;
2898
+ const halfHeight = chip.height / 2;
2899
+ let maxDx = 0;
2900
+ let maxDy = 0;
2901
+ for (const pin of chip.pins) {
2902
+ const dx = Math.abs(pin.x - chip.center.x);
2903
+ const dy = Math.abs(pin.y - chip.center.y);
2904
+ if (dx > maxDx) maxDx = dx;
2905
+ if (dy > maxDy) maxDy = dy;
2906
+ }
2907
+ const newHalfWidth = Math.max(halfWidth, maxDx);
2908
+ const newHalfHeight = Math.max(halfHeight, maxDy);
2909
+ if (newHalfWidth > halfWidth || newHalfHeight > halfHeight) {
2910
+ chip.width = newHalfWidth * 2;
2911
+ chip.height = newHalfHeight * 2;
2912
+ for (const pin of chip.pins) {
2913
+ pin._facingDirection = void 0;
2914
+ }
2915
+ }
2916
+ }
2917
+ };
2918
+
2919
+ // lib/utils/does-trace-overlap-with-existing-traces.ts
2920
+ import { doSegmentsIntersect } from "@tscircuit/math-utils";
2921
+ function doesTraceOverlapWithExistingTraces(newTracePath, existingTraces) {
2922
+ for (let i = 0; i < newTracePath.length - 1; i++) {
2923
+ const newSegmentP1 = newTracePath[i];
2924
+ const newSegmentP2 = newTracePath[i + 1];
2925
+ for (const existingTrace of existingTraces) {
2926
+ for (let j = 0; j < existingTrace.tracePath.length - 1; j++) {
2927
+ const existingSegmentP1 = existingTrace.tracePath[j];
2928
+ const existingSegmentP2 = existingTrace.tracePath[j + 1];
2929
+ if (doSegmentsIntersect(
2930
+ newSegmentP1,
2931
+ newSegmentP2,
2932
+ existingSegmentP1,
2933
+ existingSegmentP2
2934
+ )) {
2935
+ return true;
2936
+ }
2937
+ }
2938
+ }
2939
+ }
2940
+ return false;
2941
+ }
2942
+
2943
+ // lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts
2944
+ var NEAREST_NEIGHBOR_COUNT = 3;
2945
+ var distance = (p1, p2) => {
2946
+ return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
2947
+ };
2948
+ var LongDistancePairSolver = class extends BaseSolver {
2949
+ constructor(params) {
2950
+ super();
2951
+ this.params = params;
2952
+ const { inputProblem, primaryMspConnectionPairs, alreadySolvedTraces } = this.params;
2953
+ this.inputProblem = inputProblem;
2954
+ this.allSolvedTraces = [...alreadySolvedTraces];
2955
+ const primaryConnectedPinIds = /* @__PURE__ */ new Set();
2956
+ for (const pair of primaryMspConnectionPairs) {
2957
+ primaryConnectedPinIds.add(pair.pins[0].pinId);
2958
+ primaryConnectedPinIds.add(pair.pins[1].pinId);
2959
+ }
2960
+ const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
2961
+ this.netConnMap = netConnMap;
2962
+ const pinMap = /* @__PURE__ */ new Map();
2963
+ for (const chip of inputProblem.chips) {
2964
+ this.chipMap[chip.chipId] = chip;
2965
+ for (const pin of chip.pins) {
2966
+ pinMap.set(pin.pinId, { ...pin, chipId: chip.chipId });
2967
+ }
2968
+ }
2969
+ const candidatePairs = [];
2970
+ const addedPairKeys = /* @__PURE__ */ new Set();
2971
+ for (const netId of Object.keys(netConnMap.netMap)) {
2972
+ const allPinIdsInNet = netConnMap.getIdsConnectedToNet(netId);
2973
+ if (allPinIdsInNet.length < 2) continue;
2974
+ const unconnectedPinIds = allPinIdsInNet.filter(
2975
+ (pinId) => !primaryConnectedPinIds.has(pinId)
2976
+ );
2977
+ for (const unconnectedPinId of unconnectedPinIds) {
2978
+ const sourcePin = pinMap.get(unconnectedPinId);
2979
+ if (!sourcePin) continue;
2980
+ const neighbors = allPinIdsInNet.filter((otherPinId) => otherPinId !== unconnectedPinId).flatMap((otherPinId) => {
2981
+ const targetPin = pinMap.get(otherPinId);
2982
+ if (!targetPin) return [];
2983
+ return [
2984
+ {
2985
+ pin: targetPin,
2986
+ distance: distance(sourcePin, targetPin)
2987
+ }
2988
+ ];
2989
+ }).sort((a, b) => a.distance - b.distance).slice(0, NEAREST_NEIGHBOR_COUNT);
2990
+ for (const neighbor of neighbors) {
2991
+ const pair = [sourcePin, neighbor.pin];
2992
+ const pairKey = pair.map((p) => p.pinId).sort().join("--");
2993
+ if (!addedPairKeys.has(pairKey)) {
2994
+ candidatePairs.push(pair);
2995
+ addedPairKeys.add(pairKey);
2996
+ }
2997
+ }
2998
+ }
2999
+ }
3000
+ this.queuedCandidatePairs = candidatePairs;
3001
+ }
3002
+ solvedLongDistanceTraces = [];
3003
+ queuedCandidatePairs = [];
3004
+ currentCandidatePair = null;
3005
+ subSolver = null;
3006
+ chipMap = {};
3007
+ inputProblem;
3008
+ netConnMap;
3009
+ newlyConnectedPinIds = /* @__PURE__ */ new Set();
3010
+ allSolvedTraces = [];
3011
+ getConstructorParams() {
3012
+ return this.params;
3013
+ }
3014
+ _step() {
3015
+ if (this.subSolver?.solved) {
3016
+ const newTracePath = this.subSolver.solvedTracePath;
3017
+ if (newTracePath && this.currentCandidatePair) {
3018
+ const isTraceClear = !doesTraceOverlapWithExistingTraces(
3019
+ newTracePath,
3020
+ this.allSolvedTraces
3021
+ );
3022
+ if (isTraceClear) {
3023
+ const [p1, p2] = this.currentCandidatePair;
3024
+ const globalConnNetId = this.netConnMap.getNetConnectedToId(p1.pinId);
3025
+ const mspPairId = `${p1.pinId}-${p2.pinId}`;
3026
+ const newSolvedTrace = {
3027
+ mspPairId,
3028
+ dcConnNetId: globalConnNetId,
3029
+ globalConnNetId,
3030
+ pins: [p1, p2],
3031
+ tracePath: newTracePath,
3032
+ mspConnectionPairIds: [mspPairId],
3033
+ pinIds: [p1.pinId, p2.pinId]
3034
+ };
3035
+ this.solvedLongDistanceTraces.push(newSolvedTrace);
3036
+ this.allSolvedTraces.push(newSolvedTrace);
3037
+ this.newlyConnectedPinIds.add(p1.pinId);
3038
+ this.newlyConnectedPinIds.add(p2.pinId);
3039
+ }
3040
+ }
3041
+ this.subSolver = null;
3042
+ this.currentCandidatePair = null;
3043
+ } else if (this.subSolver?.failed) {
3044
+ this.subSolver = null;
3045
+ this.currentCandidatePair = null;
3046
+ }
3047
+ if (this.subSolver) {
3048
+ this.subSolver.step();
3049
+ return;
3050
+ }
3051
+ while (this.queuedCandidatePairs.length > 0) {
3052
+ const nextPair = this.queuedCandidatePairs.shift();
3053
+ const [p1, p2] = nextPair;
3054
+ if (this.newlyConnectedPinIds.has(p1.pinId) || this.newlyConnectedPinIds.has(p2.pinId)) {
3055
+ continue;
3056
+ }
3057
+ this.currentCandidatePair = nextPair;
3058
+ this.subSolver = new SchematicTraceSingleLineSolver2({
3059
+ inputProblem: this.params.inputProblem,
3060
+ pins: this.currentCandidatePair,
3061
+ chipMap: this.chipMap
3062
+ });
3063
+ return;
3064
+ }
3065
+ this.solved = true;
3338
3066
  }
3339
3067
  visualize() {
3340
- if (this.activeSubSolver) {
3341
- return this.activeSubSolver.visualize();
3068
+ if (this.subSolver) {
3069
+ return this.subSolver.visualize();
3342
3070
  }
3343
3071
  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) {
3072
+ for (const trace of this.solvedLongDistanceTraces) {
3348
3073
  graphics.lines.push({
3349
3074
  points: trace.tracePath,
3350
3075
  strokeColor: "purple"
3351
3076
  });
3352
3077
  }
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
3078
+ for (const [p1, p2] of this.queuedCandidatePairs) {
3079
+ graphics.lines.push({
3080
+ points: [p1, p2],
3081
+ strokeColor: "gray",
3082
+ strokeDash: "4 4"
3362
3083
  });
3363
3084
  }
3364
3085
  return graphics;
3365
3086
  }
3087
+ getOutput() {
3088
+ if (!this.solved) {
3089
+ return { newTraces: [], allTracesMerged: this.params.alreadySolvedTraces };
3090
+ }
3091
+ return {
3092
+ newTraces: this.solvedLongDistanceTraces,
3093
+ allTracesMerged: [
3094
+ ...this.params.alreadySolvedTraces,
3095
+ ...this.solvedLongDistanceTraces
3096
+ ]
3097
+ };
3098
+ }
3366
3099
  };
3367
3100
 
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;
3101
+ // lib/solvers/TraceCleanupSolver/hasCollisions.ts
3102
+ var hasCollisions = (pathSegments, obstacles) => {
3103
+ for (let i = 0; i < pathSegments.length - 1; i++) {
3104
+ const p1 = pathSegments[i];
3105
+ const p2 = pathSegments[i + 1];
3106
+ for (const obstacle of obstacles) {
3107
+ if (segmentIntersectsRect(p1, p2, obstacle)) {
3108
+ return true;
3388
3109
  }
3389
- pin._facingDirection = void 0;
3390
3110
  }
3391
3111
  }
3112
+ return false;
3392
3113
  };
3393
3114
 
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;
3115
+ // lib/solvers/TraceCleanupSolver/countTurns.ts
3116
+ var countTurns = (points) => {
3117
+ let turns = 0;
3118
+ for (let i = 1; i < points.length - 1; i++) {
3119
+ const prev = points[i - 1];
3120
+ const curr = points[i];
3121
+ const next = points[i + 1];
3122
+ const prevVertical = prev.x === curr.x;
3123
+ const nextVertical = curr.x === next.x;
3124
+ if (prevVertical !== nextVertical) {
3125
+ turns++;
3406
3126
  }
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;
3127
+ }
3128
+ return turns;
3129
+ };
3130
+
3131
+ // lib/solvers/TraceCleanupSolver/tryConnectPoints.ts
3132
+ var tryConnectPoints = (start, end) => {
3133
+ const candidates = [];
3134
+ if (start.x === end.x || start.y === end.y) {
3135
+ candidates.push([start, end]);
3136
+ } else {
3137
+ candidates.push([start, { x: end.x, y: start.y }, end]);
3138
+ candidates.push([start, { x: start.x, y: end.y }, end]);
3139
+ }
3140
+ return candidates;
3141
+ };
3142
+
3143
+ // lib/solvers/TraceCleanupSolver/hasCollisionsWithLabels.ts
3144
+ var hasCollisionsWithLabels = (pathSegments, labels) => {
3145
+ for (let i = 0; i < pathSegments.length - 1; i++) {
3146
+ const p1 = pathSegments[i];
3147
+ const p2 = pathSegments[i + 1];
3148
+ for (const label of labels) {
3149
+ if (segmentIntersectsRect(p1, p2, label)) {
3150
+ return true;
3151
+ }
3152
+ }
3153
+ }
3154
+ return false;
3155
+ };
3156
+
3157
+ // lib/solvers/TraceCleanupSolver/turnMinimization.ts
3158
+ var minimizeTurns = ({
3159
+ path,
3160
+ obstacles,
3161
+ labelBounds
3162
+ }) => {
3163
+ if (path.length <= 2) {
3164
+ return path;
3165
+ }
3166
+ const recognizeStairStepPattern = (pathToCheck, startIdx) => {
3167
+ if (startIdx >= pathToCheck.length - 3) return -1;
3168
+ let endIdx = startIdx;
3169
+ let isStairStep = true;
3170
+ for (let i = startIdx; i < pathToCheck.length - 2 && i < startIdx + 10; i++) {
3171
+ if (i + 2 >= pathToCheck.length) break;
3172
+ const p1 = pathToCheck[i];
3173
+ const p2 = pathToCheck[i + 1];
3174
+ const p3 = pathToCheck[i + 2];
3175
+ const seg1Vertical = p1.x === p2.x;
3176
+ const seg2Vertical = p2.x === p3.x;
3177
+ if (seg1Vertical === seg2Vertical) {
3178
+ break;
3179
+ }
3180
+ const seg1Direction = seg1Vertical ? Math.sign(p2.y - p1.y) : Math.sign(p2.x - p1.x);
3181
+ if (i > startIdx) {
3182
+ const prevP = pathToCheck[i - 1];
3183
+ const prevSegVertical = prevP.x === p1.x;
3184
+ const prevDirection = prevSegVertical ? Math.sign(p1.y - prevP.y) : Math.sign(p1.x - prevP.x);
3185
+ if (seg1Vertical && prevSegVertical && seg1Direction !== prevDirection || !seg1Vertical && !prevSegVertical && seg1Direction !== prevDirection) {
3186
+ isStairStep = false;
3187
+ break;
3188
+ }
3189
+ }
3190
+ endIdx = i + 2;
3191
+ }
3192
+ return isStairStep && endIdx - startIdx >= 3 ? endIdx : -1;
3193
+ };
3194
+ let optimizedPath = [...path];
3195
+ let currentTurns = countTurns(optimizedPath);
3196
+ let improved = true;
3197
+ while (improved) {
3198
+ improved = false;
3199
+ for (let startIdx = 0; startIdx < optimizedPath.length - 3; startIdx++) {
3200
+ const stairEndIdx = recognizeStairStepPattern(optimizedPath, startIdx);
3201
+ if (stairEndIdx > 0) {
3202
+ const startPoint = optimizedPath[startIdx];
3203
+ const endPoint = optimizedPath[stairEndIdx];
3204
+ const connectionOptions = tryConnectPoints(startPoint, endPoint);
3205
+ for (const connection of connectionOptions) {
3206
+ const testPath = [
3207
+ ...optimizedPath.slice(0, startIdx + 1),
3208
+ ...connection.slice(1, -1),
3209
+ ...optimizedPath.slice(stairEndIdx)
3210
+ ];
3211
+ const collidesWithObstacles = hasCollisions(connection, obstacles);
3212
+ const collidesWithLabels = hasCollisionsWithLabels(
3213
+ connection,
3214
+ labelBounds
3215
+ );
3216
+ if (!collidesWithObstacles && !collidesWithLabels) {
3217
+ const newTurns = countTurns(testPath);
3218
+ optimizedPath = testPath;
3219
+ currentTurns = newTurns;
3220
+ improved = true;
3221
+ break;
3222
+ }
3223
+ }
3224
+ if (improved) break;
3225
+ }
3226
+ }
3227
+ if (!improved) {
3228
+ for (let startIdx = 0; startIdx < optimizedPath.length - 2; startIdx++) {
3229
+ const maxRemove = Math.min(
3230
+ optimizedPath.length - startIdx - 2,
3231
+ optimizedPath.length - 2
3232
+ );
3233
+ for (let removeCount = 1; removeCount <= maxRemove; removeCount++) {
3234
+ const endIdx = startIdx + removeCount + 1;
3235
+ if (endIdx >= optimizedPath.length) continue;
3236
+ const startPoint = optimizedPath[startIdx];
3237
+ const endPoint = optimizedPath[endIdx];
3238
+ const connectionOptions = tryConnectPoints(startPoint, endPoint);
3239
+ for (const connection of connectionOptions) {
3240
+ const testPath = [
3241
+ ...optimizedPath.slice(0, startIdx + 1),
3242
+ ...connection.slice(1, -1),
3243
+ ...optimizedPath.slice(endIdx)
3244
+ ];
3245
+ const connectionSegments = connection;
3246
+ const collidesWithObstacles = hasCollisions(
3247
+ connectionSegments,
3248
+ obstacles
3249
+ );
3250
+ const collidesWithLabels = hasCollisionsWithLabels(
3251
+ connectionSegments,
3252
+ labelBounds
3253
+ );
3254
+ if (!collidesWithObstacles && !collidesWithLabels) {
3255
+ const newTurns = countTurns(testPath);
3256
+ if (newTurns < currentTurns || newTurns === currentTurns && testPath.length < optimizedPath.length) {
3257
+ optimizedPath = testPath;
3258
+ currentTurns = newTurns;
3259
+ improved = true;
3260
+ break;
3261
+ }
3262
+ }
3263
+ }
3264
+ if (improved) break;
3265
+ }
3266
+ if (improved) break;
3267
+ }
3268
+ }
3269
+ if (!improved) {
3270
+ for (let i = 0; i < optimizedPath.length - 2; i++) {
3271
+ const p1 = optimizedPath[i];
3272
+ const p2 = optimizedPath[i + 1];
3273
+ const p3 = optimizedPath[i + 2];
3274
+ const allVertical = p1.x === p2.x && p2.x === p3.x;
3275
+ const allHorizontal = p1.y === p2.y && p2.y === p3.y;
3276
+ if (allVertical || allHorizontal) {
3277
+ const testPath = [
3278
+ ...optimizedPath.slice(0, i + 1),
3279
+ ...optimizedPath.slice(i + 2)
3280
+ ];
3281
+ const collidesWithObstacles = hasCollisions([p1, p3], obstacles);
3282
+ const collidesWithLabels = hasCollisionsWithLabels(
3283
+ [p1, p3],
3284
+ labelBounds
3285
+ );
3286
+ if (!collidesWithObstacles && !collidesWithLabels) {
3287
+ optimizedPath = testPath;
3288
+ improved = true;
3289
+ break;
3290
+ }
3291
+ }
3414
3292
  }
3415
3293
  }
3416
3294
  }
3295
+ const finalSimplifiedPath = simplifyPath(optimizedPath);
3296
+ return finalSimplifiedPath;
3417
3297
  };
3418
3298
 
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
- }
3299
+ // lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts
3300
+ var minimizeTurnsWithFilteredLabels = ({
3301
+ targetMspConnectionPairId,
3302
+ traces,
3303
+ inputProblem,
3304
+ allLabelPlacements,
3305
+ mergedLabelNetIdMap,
3306
+ paddingBuffer
3307
+ }) => {
3308
+ const targetTrace = traces.find(
3309
+ (t) => t.mspPairId === targetMspConnectionPairId
3310
+ );
3311
+ if (!targetTrace) {
3312
+ throw new Error(`Target trace ${targetMspConnectionPairId} not found`);
3439
3313
  }
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));
3314
+ const obstacleTraces = traces.filter(
3315
+ (t) => t.mspPairId !== targetMspConnectionPairId
3316
+ );
3317
+ const TRACE_WIDTH = 0.01;
3318
+ const traceObstacles = obstacleTraces.flatMap(
3319
+ (trace, i) => trace.tracePath.slice(0, -1).map((p1, pi) => {
3320
+ const p2 = trace.tracePath[pi + 1];
3321
+ return {
3322
+ chipId: `trace-obstacle-${i}-${pi}`,
3323
+ minX: Math.min(p1.x, p2.x) - TRACE_WIDTH / 2,
3324
+ minY: Math.min(p1.y, p2.y) - TRACE_WIDTH / 2,
3325
+ maxX: Math.max(p1.x, p2.x) + TRACE_WIDTH / 2,
3326
+ maxY: Math.max(p1.y, p2.y) + TRACE_WIDTH / 2
3327
+ };
3328
+ })
3329
+ );
3330
+ const staticObstacles = getObstacleRects(inputProblem);
3331
+ const combinedObstacles = [...staticObstacles, ...traceObstacles];
3332
+ const originalPath = targetTrace.tracePath;
3333
+ const filteredLabels = allLabelPlacements.filter((label) => {
3334
+ const originalNetIds = mergedLabelNetIdMap[label.globalConnNetId];
3335
+ if (originalNetIds) {
3336
+ return !originalNetIds.has(targetTrace.globalConnNetId);
3337
+ }
3338
+ return label.globalConnNetId !== targetTrace.globalConnNetId;
3339
+ });
3340
+ const labelBounds = filteredLabels.map((nl) => ({
3341
+ minX: nl.center.x - nl.width / 2 - paddingBuffer,
3342
+ maxX: nl.center.x + nl.width / 2 + paddingBuffer,
3343
+ minY: nl.center.y - nl.height / 2 - paddingBuffer,
3344
+ maxY: nl.center.y + nl.height / 2 + paddingBuffer
3345
+ }));
3346
+ const newPath = minimizeTurns({
3347
+ path: originalPath,
3348
+ obstacles: combinedObstacles,
3349
+ labelBounds
3350
+ });
3351
+ return {
3352
+ ...targetTrace,
3353
+ tracePath: newPath
3354
+ };
3447
3355
  };
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 });
3356
+
3357
+ // lib/solvers/TraceCleanupSolver/balanceZShapes.ts
3358
+ var balanceZShapes = ({
3359
+ targetMspConnectionPairId,
3360
+ traces,
3361
+ inputProblem,
3362
+ allLabelPlacements,
3363
+ mergedLabelNetIdMap,
3364
+ paddingBuffer
3365
+ }) => {
3366
+ const targetTrace = traces.find(
3367
+ (t) => t.mspPairId === targetMspConnectionPairId
3368
+ );
3369
+ if (!targetTrace) {
3370
+ throw new Error(`Target trace ${targetMspConnectionPairId} not found`);
3371
+ }
3372
+ const TOLERANCE = 1e-5;
3373
+ const obstacleTraces = traces.filter(
3374
+ (t) => t.mspPairId !== targetMspConnectionPairId
3375
+ );
3376
+ const TRACE_WIDTH = 0.01;
3377
+ const traceObstacles = obstacleTraces.flatMap(
3378
+ (trace, i) => trace.tracePath.slice(0, -1).map((p1, pi) => {
3379
+ const p2 = trace.tracePath[pi + 1];
3380
+ return {
3381
+ chipId: `trace-obstacle-${i}-${pi}`,
3382
+ minX: Math.min(p1.x, p2.x) - TRACE_WIDTH / 2,
3383
+ minY: Math.min(p1.y, p2.y) - TRACE_WIDTH / 2,
3384
+ maxX: Math.max(p1.x, p2.x) + TRACE_WIDTH / 2,
3385
+ maxY: Math.max(p1.y, p2.y) + TRACE_WIDTH / 2
3386
+ };
3387
+ })
3388
+ );
3389
+ const staticObstacles = getObstacleRects(inputProblem).map((obs) => ({
3390
+ ...obs,
3391
+ minX: obs.minX + TOLERANCE,
3392
+ maxX: obs.maxX - TOLERANCE,
3393
+ minY: obs.minY + TOLERANCE,
3394
+ maxY: obs.maxY - TOLERANCE
3395
+ }));
3396
+ const combinedObstacles = [...staticObstacles, ...traceObstacles];
3397
+ const segmentIntersectsAnyRect = (p1, p2, rects) => {
3398
+ for (const rect of rects) {
3399
+ if (segmentIntersectsRect(p1, p2, rect)) {
3400
+ return true;
3467
3401
  }
3468
3402
  }
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
- }
3403
+ return false;
3404
+ };
3405
+ const filteredLabels = allLabelPlacements.filter((label) => {
3406
+ const originalNetIds = mergedLabelNetIdMap[label.globalConnNetId];
3407
+ if (originalNetIds) {
3408
+ return !originalNetIds.has(targetTrace.globalConnNetId);
3409
+ }
3410
+ return label.globalConnNetId !== targetTrace.globalConnNetId;
3411
+ });
3412
+ const labelBounds = filteredLabels.map((nl) => ({
3413
+ minX: nl.center.x - nl.width / 2 + TOLERANCE,
3414
+ maxX: nl.center.x + nl.width / 2 - TOLERANCE,
3415
+ minY: nl.center.y - nl.height / 2 + TOLERANCE,
3416
+ maxY: nl.center.y + nl.height / 2 - TOLERANCE
3417
+ }));
3418
+ const newPath = [...targetTrace.tracePath];
3419
+ if (newPath.length < 4) {
3420
+ return { ...targetTrace };
3421
+ }
3422
+ if (newPath.length === 4) {
3423
+ const [p0, p1, p2, p3] = newPath;
3424
+ let p1New;
3425
+ let p2New;
3426
+ const isHVHShape = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y;
3427
+ if (isHVHShape) {
3428
+ const idealX = (p0.x + p3.x) / 2;
3429
+ p1New = { x: idealX, y: p1.y };
3430
+ p2New = { x: idealX, y: p2.y };
3431
+ } else {
3432
+ const idealY = (p0.y + p3.y) / 2;
3433
+ p1New = { x: p1.x, y: idealY };
3434
+ p2New = { x: p2.x, y: idealY };
3435
+ }
3436
+ 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);
3437
+ if (!collides) {
3438
+ newPath[1] = p1New;
3439
+ newPath[2] = p2New;
3440
+ }
3441
+ return { ...targetTrace, tracePath: simplifyPath(newPath) };
3442
+ }
3443
+ for (let i = 1; i < newPath.length - 4; i++) {
3444
+ const p1 = newPath[i];
3445
+ const p2 = newPath[i + 1];
3446
+ const p3 = newPath[i + 2];
3447
+ const p4 = newPath[i + 3];
3448
+ const isHVHZShape = p1.y === p2.y && p2.x === p3.x && p3.y === p4.y;
3449
+ const isVHVZShape = p1.x === p2.x && p2.y === p3.y && p3.x === p4.x;
3450
+ const isCollinearHorizontal = p1.y === p2.y && p2.y === p3.y && p3.y === p4.y;
3451
+ const isCollinearVertical = p1.x === p2.x && p2.x === p3.x && p3.x === p4.x;
3452
+ const isCollinear = isCollinearHorizontal || isCollinearVertical;
3453
+ let isSameDirection = false;
3454
+ if (isHVHZShape) {
3455
+ isSameDirection = Math.sign(p2.x - p1.x) === Math.sign(p4.x - p3.x);
3456
+ } else if (isVHVZShape) {
3457
+ isSameDirection = Math.sign(p2.y - p1.y) === Math.sign(p4.y - p3.y);
3458
+ }
3459
+ const isValidZShape = (isHVHZShape || isVHVZShape) && !isCollinear && isSameDirection;
3460
+ if (!isValidZShape) {
3461
+ continue;
3462
+ }
3463
+ let p2New;
3464
+ let p3New;
3465
+ const len1Original = isHVHZShape ? Math.abs(p1.x - p2.x) : Math.abs(p1.y - p2.y);
3466
+ const len2Original = isHVHZShape ? Math.abs(p3.x - p4.x) : Math.abs(p3.y - p4.y);
3467
+ if (Math.abs(len1Original - len2Original) < 1e-3) {
3468
+ continue;
3469
+ }
3470
+ if (isHVHZShape) {
3471
+ const idealX = (p1.x + p4.x) / 2;
3472
+ p2New = { x: idealX, y: p2.y };
3473
+ p3New = { x: idealX, y: p3.y };
3474
+ } else {
3475
+ const idealY = (p1.y + p4.y) / 2;
3476
+ p2New = { x: p2.x, y: idealY };
3477
+ p3New = { x: p3.x, y: idealY };
3478
+ }
3479
+ 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);
3480
+ if (!collides) {
3481
+ newPath[i + 1] = p2New;
3482
+ newPath[i + 2] = p3New;
3483
+ i = 0;
3499
3484
  }
3500
- this.queuedCandidatePairs = candidatePairs;
3501
3485
  }
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;
3486
+ const finalSimplifiedPath = simplifyPath(newPath);
3487
+ return {
3488
+ ...targetTrace,
3489
+ tracePath: finalSimplifiedPath
3490
+ };
3491
+ };
3492
+
3493
+ // lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts
3494
+ var TraceCleanupSolver = class extends BaseSolver {
3495
+ input;
3496
+ outputTraces;
3497
+ traceIdQueue;
3498
+ tracesMap;
3499
+ pipelineStep = "minimizing_turns";
3500
+ activeTraceId = null;
3501
+ // New property
3502
+ constructor(solverInput) {
3503
+ super();
3504
+ this.input = solverInput;
3505
+ this.outputTraces = [...solverInput.allTraces];
3506
+ this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]));
3507
+ this.traceIdQueue = Array.from(solverInput.targetTraceIds);
3513
3508
  }
3514
3509
  _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;
3510
+ if (this.pipelineStep === "minimizing_turns" && this.traceIdQueue.length === 0) {
3511
+ this.pipelineStep = "balancing_l_shapes";
3512
+ this.traceIdQueue = Array.from(this.input.targetTraceIds);
3546
3513
  }
3547
- if (this.subSolver) {
3548
- this.subSolver.step();
3514
+ if (this.pipelineStep === "balancing_l_shapes" && this.traceIdQueue.length === 0) {
3515
+ this.solved = true;
3549
3516
  return;
3550
3517
  }
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
- });
3518
+ const targetMspConnectionPairId = this.traceIdQueue.shift();
3519
+ this.activeTraceId = targetMspConnectionPairId;
3520
+ const originalTrace = this.tracesMap.get(targetMspConnectionPairId);
3521
+ const { tracePath } = originalTrace;
3522
+ const is4PointRectangle = (path) => {
3523
+ if (path.length !== 4) return false;
3524
+ const [p0, p1, p2, p3] = path;
3525
+ const isHVHC = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y && p0.x === p3.x;
3526
+ const isVHVC = p0.x === p1.x && p1.y === p2.y && p2.x === p3.x && p0.y === p3.y;
3527
+ return isHVHC || isVHVC;
3528
+ };
3529
+ if (is4PointRectangle(tracePath)) {
3563
3530
  return;
3564
3531
  }
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"
3532
+ const allTraces = Array.from(this.tracesMap.values());
3533
+ let updatedTrace;
3534
+ if (this.pipelineStep === "minimizing_turns") {
3535
+ updatedTrace = minimizeTurnsWithFilteredLabels({
3536
+ ...this.input,
3537
+ targetMspConnectionPairId,
3538
+ traces: allTraces
3576
3539
  });
3577
- }
3578
- for (const [p1, p2] of this.queuedCandidatePairs) {
3579
- graphics.lines.push({
3580
- points: [p1, p2],
3581
- strokeColor: "gray",
3582
- strokeDash: "4 4"
3540
+ } else {
3541
+ updatedTrace = balanceZShapes({
3542
+ ...this.input,
3543
+ targetMspConnectionPairId,
3544
+ traces: allTraces
3583
3545
  });
3584
3546
  }
3585
- return graphics;
3547
+ this.tracesMap.set(targetMspConnectionPairId, updatedTrace);
3548
+ this.outputTraces = Array.from(this.tracesMap.values());
3586
3549
  }
3587
3550
  getOutput() {
3588
- if (!this.solved) {
3589
- return { newTraces: [], allTracesMerged: this.params.alreadySolvedTraces };
3590
- }
3591
3551
  return {
3592
- newTraces: this.solvedLongDistanceTraces,
3593
- allTracesMerged: [
3594
- ...this.params.alreadySolvedTraces,
3595
- ...this.solvedLongDistanceTraces
3596
- ]
3552
+ traces: this.outputTraces
3597
3553
  };
3598
3554
  }
3555
+ visualize() {
3556
+ const graphics = visualizeInputProblem(this.input.inputProblem, {
3557
+ chipAlpha: 0.1,
3558
+ connectionAlpha: 0.1
3559
+ });
3560
+ if (!graphics.lines) graphics.lines = [];
3561
+ if (!graphics.points) graphics.points = [];
3562
+ if (!graphics.rects) graphics.rects = [];
3563
+ if (!graphics.circles) graphics.circles = [];
3564
+ if (!graphics.texts) graphics.texts = [];
3565
+ for (const trace of this.outputTraces) {
3566
+ const line = {
3567
+ points: trace.tracePath.map((p) => ({ x: p.x, y: p.y })),
3568
+ strokeColor: trace.mspPairId === this.activeTraceId ? "red" : "blue"
3569
+ // Highlight active trace
3570
+ };
3571
+ graphics.lines.push(line);
3572
+ }
3573
+ return graphics;
3574
+ }
3599
3575
  };
3600
3576
 
3601
3577
  // lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
@@ -3725,6 +3701,21 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3725
3701
  ];
3726
3702
  }
3727
3703
  ),
3704
+ definePipelineStep("traceCleanupSolver", TraceCleanupSolver, (instance) => {
3705
+ const prevSolverOutput = instance.traceLabelOverlapAvoidanceSolver.getOutput();
3706
+ const traces = prevSolverOutput.traces;
3707
+ const labelMergingOutput = instance.traceLabelOverlapAvoidanceSolver.labelMergingSolver.getOutput();
3708
+ return [
3709
+ {
3710
+ inputProblem: instance.inputProblem,
3711
+ allTraces: traces,
3712
+ targetTraceIds: new Set(traces.map((t) => t.mspPairId)),
3713
+ allLabelPlacements: labelMergingOutput.netLabelPlacements,
3714
+ mergedLabelNetIdMap: labelMergingOutput.mergedLabelNetIdMap,
3715
+ paddingBuffer: 0.1
3716
+ }
3717
+ ];
3718
+ }),
3728
3719
  definePipelineStep(
3729
3720
  "netLabelPlacementSolver",
3730
3721
  NetLabelPlacementSolver,