@tscircuit/schematic-trace-solver 0.0.46 → 0.0.48

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 (21) hide show
  1. package/dist/index.d.ts +18 -16
  2. package/dist/index.js +228 -122
  3. package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +2 -0
  4. package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver_visualize.ts +12 -0
  5. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +89 -61
  6. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +16 -5
  7. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +27 -16
  8. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/doesTraceStartOrEndInLabel.ts +58 -0
  9. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +13 -1
  10. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts +110 -60
  11. package/package.json +1 -1
  12. package/site/examples/example27.page.tsx +4 -0
  13. package/site/examples/example28.page.tsx +61 -0
  14. package/tests/assets/example27.json +168 -0
  15. package/tests/examples/__snapshots__/example03.snap.svg +67 -67
  16. package/tests/examples/__snapshots__/example16.snap.svg +3 -3
  17. package/tests/examples/__snapshots__/example28.snap.svg +23 -189
  18. package/tests/examples/__snapshots__/example30.snap.svg +258 -0
  19. package/tests/examples/example28.test.ts +2 -3
  20. package/tests/examples/example30.test.ts +12 -0
  21. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +9 -6
package/dist/index.d.ts CHANGED
@@ -435,6 +435,7 @@ declare class SingleOverlapSolver extends BaseSolver {
435
435
  problem: InputProblem;
436
436
  obstacles: ReturnType<typeof getObstacleRects>;
437
437
  label: NetLabelPlacement;
438
+ _tried: number;
438
439
  constructor(solverInput: SingleOverlapSolverInput);
439
440
  _step(): void;
440
441
  visualize(): GraphicsObject;
@@ -446,6 +447,7 @@ interface OverlapCollectionSolverInput {
446
447
  initialNetLabelPlacements: NetLabelPlacement[];
447
448
  mergedNetLabelPlacements: NetLabelPlacement[];
448
449
  mergedLabelNetIdMap: Record<string, Set<string>>;
450
+ detourCounts: Map<string, number>;
449
451
  }
450
452
  /**
451
453
  * This is an internal solver that manages the step-by-step process of avoiding
@@ -458,8 +460,8 @@ declare class OverlapAvoidanceStepSolver extends BaseSolver {
458
460
  mergedLabelNetIdMap: Record<string, Set<string>>;
459
461
  allTraces: SolvedTracePath[];
460
462
  modifiedTraces: SolvedTracePath[];
461
- private detourCountByLabel;
462
463
  private readonly PADDING_BUFFER;
464
+ private detourCounts;
463
465
  activeSubSolver: SingleOverlapSolver | null;
464
466
  private overlapQueue;
465
467
  private recentlyFailed;
@@ -470,6 +472,7 @@ declare class OverlapAvoidanceStepSolver extends BaseSolver {
470
472
  getOutput(): {
471
473
  allTraces: SolvedTracePath[];
472
474
  modifiedTraces: SolvedTracePath[];
475
+ detourCounts: Map<string, number>;
473
476
  };
474
477
  visualize(): GraphicsObject;
475
478
  }
@@ -480,29 +483,28 @@ interface TraceLabelOverlapAvoidanceSolverInput {
480
483
  netLabelPlacements: NetLabelPlacement[];
481
484
  }
482
485
  /**
483
- * A pipeline solver responsible for resolving overlaps between schematic traces and net labels.
486
+ * Resolves overlaps between schematic traces and net labels using a two-phase,
487
+ * "fire-and-forget" dispatching strategy.
484
488
  *
485
- * This solver orchestrates a sequence of sub-solvers to achieve its goal:
486
- * 1. **MergedNetLabelObstacleSolver**: This solver first merges labels that are
487
- * close to each other to form larger "obstacle" groups. This simplifies the
488
- * problem by reducing the number of individual obstacles the traces need to avoid.
489
- * 2. **OverlapAvoidanceStepSolver**: This solver then takes the output of the merging
490
- * step and iteratively attempts to reroute traces to avoid the merged label obstacles.
491
- * It handles one overlap at a time, making it a step-by-step process.
489
+ * This solver operates in two distinct phases:
492
490
  *
493
- * The final output is a set of modified traces that have been rerouted to avoid
494
- * labels, and the set of merged labels that were used as obstacles.
491
+ * 1. **Dispatch Phase**: Iterates through traces, identifying clean ones and dispatching
492
+ * colliding ones to dedicated `OverlapAvoidanceStepSolver` instances.
495
493
  *
496
- * @param {TraceLabelOverlapAvoidanceSolverInput} solverInput - The input for the solver,
497
- * containing the initial traces, label placements, and the input problem definition.
494
+ * 2. **Execution Phase**: Steps through all dispatched sub-solvers until they complete.
495
+ *
496
+ * The final output combines clean traces with results from sub-solvers. A final
497
+ * `MergedNetLabelObstacleSolver` ensures pipeline compatibility.
498
498
  */
499
499
  declare class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
500
500
  inputProblem: InputProblem;
501
- traces: SolvedTracePath[];
502
501
  netLabelPlacements: NetLabelPlacement[];
502
+ unprocessedTraces: SolvedTracePath[];
503
+ cleanTraces: SolvedTracePath[];
504
+ subSolvers: OverlapAvoidanceStepSolver[];
505
+ private phase;
506
+ detourCounts: Map<string, number>;
503
507
  labelMergingSolver?: MergedNetLabelObstacleSolver;
504
- overlapAvoidanceSolver?: OverlapAvoidanceStepSolver;
505
- pipelineStepIndex: number;
506
508
  constructor(solverInput: TraceLabelOverlapAvoidanceSolverInput);
507
509
  _step(): void;
508
510
  getOutput(): {
package/dist/index.js CHANGED
@@ -1767,6 +1767,7 @@ function visualizeSingleNetLabelPlacementSolver(solver) {
1767
1767
  for (const c of solver.testedCandidates) {
1768
1768
  const fill = c.status === "ok" ? "rgba(0, 180, 0, 0.25)" : c.status === "chip-collision" ? "rgba(220, 0, 0, 0.25)" : c.status === "trace-collision" ? "rgba(220, 140, 0, 0.25)" : "rgba(120, 120, 120, 0.15)";
1769
1769
  const stroke = c.status === "ok" ? "green" : c.status === "chip-collision" ? "red" : c.status === "trace-collision" ? "orange" : "gray";
1770
+ const candidateLabel = c.status === "ok" ? "status: ok(valid net label candidate)" : c.status === "chip-collision" ? "status: chip-collision" : c.status === "trace-collision" ? "status: trace-collision" : "status: parallel-to-segment";
1770
1771
  graphics.rects.push({
1771
1772
  center: {
1772
1773
  x: (c.bounds.minX + c.bounds.maxX) / 2,
@@ -1775,12 +1776,16 @@ function visualizeSingleNetLabelPlacementSolver(solver) {
1775
1776
  width: c.width,
1776
1777
  height: c.height,
1777
1778
  fill,
1778
- strokeColor: stroke
1779
+ strokeColor: stroke,
1780
+ label: `${candidateLabel}
1781
+ orientation: ${c.orientation}`
1779
1782
  });
1780
1783
  graphics.points.push({
1781
1784
  x: c.anchor.x,
1782
1785
  y: c.anchor.y,
1783
- color: stroke
1786
+ color: stroke,
1787
+ label: `anchor
1788
+ orientation: ${c.orientation}`
1784
1789
  });
1785
1790
  }
1786
1791
  if (solver.netLabelPlacement) {
@@ -1790,12 +1795,16 @@ function visualizeSingleNetLabelPlacementSolver(solver) {
1790
1795
  width: p.width,
1791
1796
  height: p.height,
1792
1797
  fill: "rgba(0, 128, 255, 0.35)",
1793
- strokeColor: "blue"
1798
+ strokeColor: "blue",
1799
+ label: `netId: ${p.netId}
1800
+ globalConnNetId: ${p.globalConnNetId}`
1794
1801
  });
1795
1802
  graphics.points.push({
1796
1803
  x: p.anchorPoint.x,
1797
1804
  y: p.anchorPoint.y,
1798
- color: "blue"
1805
+ color: "blue",
1806
+ label: `anchor
1807
+ orientation: ${p.orientation}`
1799
1808
  });
1800
1809
  }
1801
1810
  return graphics;
@@ -2248,12 +2257,16 @@ var NetLabelPlacementSolver = class extends BaseSolver {
2248
2257
  width: p.width,
2249
2258
  height: p.height,
2250
2259
  fill: getColorFromString(p.globalConnNetId, 0.35),
2251
- strokeColor: getColorFromString(p.globalConnNetId, 0.9)
2260
+ strokeColor: getColorFromString(p.globalConnNetId, 0.9),
2261
+ label: `netId: ${p.netId}
2262
+ globalConnNetId: ${p.globalConnNetId}`
2252
2263
  });
2253
2264
  graphics.points.push({
2254
2265
  x: p.anchorPoint.x,
2255
2266
  y: p.anchorPoint.y,
2256
- color: getColorFromString(p.globalConnNetId, 0.9)
2267
+ color: getColorFromString(p.globalConnNetId, 0.9),
2268
+ label: `anchorPoint
2269
+ orientation: ${p.orientation}`
2257
2270
  });
2258
2271
  }
2259
2272
  return graphics;
@@ -2649,73 +2662,106 @@ var generateFourPointDetourCandidates = ({
2649
2662
  paddingBuffer,
2650
2663
  detourCount
2651
2664
  }) => {
2652
- let collidingSegIndex = -1;
2653
- for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
2654
- if (segmentIntersectsRect(
2655
- initialTrace.tracePath[i],
2656
- initialTrace.tracePath[i + 1],
2657
- labelBounds
2658
- )) {
2659
- collidingSegIndex = i;
2660
- break;
2665
+ const isMergedLabel = label.globalConnNetId.startsWith("merged-group-");
2666
+ const effectivePadding = paddingBuffer + detourCount * paddingBuffer;
2667
+ const paddedLabelBounds = {
2668
+ minX: labelBounds.minX - effectivePadding,
2669
+ maxX: labelBounds.maxX + effectivePadding,
2670
+ minY: labelBounds.minY - effectivePadding,
2671
+ maxY: labelBounds.maxY + effectivePadding
2672
+ };
2673
+ let entryPoint, exitPoint;
2674
+ let entryIndex, exitIndex;
2675
+ const isPointInRect = (p) => p.x >= paddedLabelBounds.minX && p.x <= paddedLabelBounds.maxX && p.y >= paddedLabelBounds.minY && p.y <= paddedLabelBounds.maxY;
2676
+ if (isMergedLabel) {
2677
+ let firstInsideIndex = -1;
2678
+ for (let i = 0; i < initialTrace.tracePath.length; i++) {
2679
+ if (isPointInRect(initialTrace.tracePath[i])) {
2680
+ firstInsideIndex = i;
2681
+ break;
2682
+ }
2683
+ }
2684
+ if (firstInsideIndex === -1) return [];
2685
+ entryIndex = Math.max(0, firstInsideIndex - 1);
2686
+ entryPoint = initialTrace.tracePath[entryIndex];
2687
+ let firstOutsideIndex = -1;
2688
+ for (let i = firstInsideIndex; i < initialTrace.tracePath.length; i++) {
2689
+ if (!isPointInRect(initialTrace.tracePath[i])) {
2690
+ firstOutsideIndex = i;
2691
+ break;
2692
+ }
2693
+ }
2694
+ if (firstOutsideIndex === -1) {
2695
+ exitIndex = initialTrace.tracePath.length - 1;
2696
+ } else {
2697
+ exitIndex = firstOutsideIndex;
2661
2698
  }
2699
+ exitPoint = initialTrace.tracePath[exitIndex];
2700
+ } else {
2701
+ let collidingSegIndex = -1;
2702
+ for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
2703
+ if (segmentIntersectsRect(
2704
+ initialTrace.tracePath[i],
2705
+ initialTrace.tracePath[i + 1],
2706
+ { ...paddedLabelBounds, chipId: "temp-obstacle" }
2707
+ )) {
2708
+ collidingSegIndex = i;
2709
+ break;
2710
+ }
2711
+ }
2712
+ if (collidingSegIndex === -1) return [];
2713
+ entryIndex = collidingSegIndex;
2714
+ exitIndex = collidingSegIndex + 1;
2715
+ entryPoint = initialTrace.tracePath[entryIndex];
2716
+ exitPoint = initialTrace.tracePath[exitIndex];
2662
2717
  }
2663
- if (collidingSegIndex === -1) return [];
2664
- const pA = initialTrace.tracePath[collidingSegIndex];
2665
- const pB = initialTrace.tracePath[collidingSegIndex + 1];
2666
- if (!pA || !pB) return [];
2718
+ if (!entryPoint || !exitPoint || entryIndex >= exitIndex) return [];
2667
2719
  const candidateDetours = [];
2668
- const paddedLabelBounds = getRectBounds(
2669
- label.center,
2670
- label.width,
2671
- label.height
2672
- );
2673
- const effectivePadding = paddingBuffer + detourCount * paddingBuffer;
2674
- if (isVertical(pA, pB)) {
2675
- const xCandidates = [
2676
- paddedLabelBounds.maxX + effectivePadding,
2677
- paddedLabelBounds.minX - effectivePadding
2678
- ];
2679
- for (const newX of xCandidates) {
2720
+ const dx = exitPoint.x - entryPoint.x;
2721
+ const dy = exitPoint.y - entryPoint.y;
2722
+ if (Math.abs(dx) > Math.abs(dy)) {
2723
+ const yCandidates = [paddedLabelBounds.maxY, paddedLabelBounds.minY];
2724
+ for (const newY of yCandidates) {
2680
2725
  candidateDetours.push(
2681
- pB.y > pA.y ? [
2682
- { x: pA.x, y: paddedLabelBounds.minY - effectivePadding },
2683
- { x: newX, y: paddedLabelBounds.minY - effectivePadding },
2684
- { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
2685
- { x: pB.x, y: paddedLabelBounds.maxY + effectivePadding }
2726
+ dx > 0 ? [
2727
+ { x: paddedLabelBounds.minX, y: entryPoint.y },
2728
+ { x: paddedLabelBounds.minX, y: newY },
2729
+ { x: paddedLabelBounds.maxX, y: newY },
2730
+ { x: paddedLabelBounds.maxX, y: exitPoint.y }
2686
2731
  ] : [
2687
- { x: pA.x, y: paddedLabelBounds.maxY + effectivePadding },
2688
- { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
2689
- { x: newX, y: paddedLabelBounds.minY - effectivePadding },
2690
- { x: pB.x, y: paddedLabelBounds.minY - effectivePadding }
2732
+ // Right-to-left
2733
+ { x: paddedLabelBounds.maxX, y: entryPoint.y },
2734
+ { x: paddedLabelBounds.maxX, y: newY },
2735
+ { x: paddedLabelBounds.minX, y: newY },
2736
+ { x: paddedLabelBounds.minX, y: exitPoint.y }
2691
2737
  ]
2692
2738
  );
2693
2739
  }
2694
2740
  } else {
2695
- const yCandidates = [
2696
- paddedLabelBounds.maxY + effectivePadding,
2697
- paddedLabelBounds.minY - effectivePadding
2698
- ];
2699
- for (const newY of yCandidates) {
2741
+ const xCandidates = [paddedLabelBounds.maxX, paddedLabelBounds.minX];
2742
+ for (const newX of xCandidates) {
2700
2743
  candidateDetours.push(
2701
- pB.x > pA.x ? [
2702
- { x: paddedLabelBounds.minX - effectivePadding, y: pA.y },
2703
- { x: paddedLabelBounds.minX - effectivePadding, y: newY },
2704
- { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
2705
- { x: paddedLabelBounds.maxX + effectivePadding, y: pB.y }
2744
+ dy > 0 ? [
2745
+ { x: entryPoint.x, y: paddedLabelBounds.minY },
2746
+ { x: newX, y: paddedLabelBounds.minY },
2747
+ { x: newX, y: paddedLabelBounds.maxY },
2748
+ { x: exitPoint.x, y: paddedLabelBounds.maxY }
2706
2749
  ] : [
2707
- { x: paddedLabelBounds.maxX + effectivePadding, y: pA.y },
2708
- { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
2709
- { x: paddedLabelBounds.minX - effectivePadding, y: newY },
2710
- { x: paddedLabelBounds.minX - effectivePadding, y: pB.y }
2750
+ // Bottom-to-top
2751
+ { x: entryPoint.x, y: paddedLabelBounds.maxY },
2752
+ { x: newX, y: paddedLabelBounds.maxY },
2753
+ { x: newX, y: paddedLabelBounds.minY },
2754
+ { x: exitPoint.x, y: paddedLabelBounds.minY }
2711
2755
  ]
2712
2756
  );
2713
2757
  }
2714
2758
  }
2715
2759
  return candidateDetours.map((detourPoints) => [
2716
- ...initialTrace.tracePath.slice(0, collidingSegIndex + 1),
2760
+ ...initialTrace.tracePath.slice(0, entryIndex),
2761
+ entryPoint,
2717
2762
  ...detourPoints,
2718
- ...initialTrace.tracePath.slice(collidingSegIndex + 1)
2763
+ exitPoint,
2764
+ ...initialTrace.tracePath.slice(exitIndex + 1)
2719
2765
  ]);
2720
2766
  };
2721
2767
 
@@ -2759,13 +2805,12 @@ var generateRerouteCandidates = ({
2759
2805
  if (trace.globalConnNetId === label.globalConnNetId) {
2760
2806
  return [initialTrace.tracePath];
2761
2807
  }
2762
- const labelPadding = paddingBuffer;
2763
2808
  const labelBoundsRaw = getRectBounds(label.center, label.width, label.height);
2764
2809
  const labelBounds = {
2765
- minX: labelBoundsRaw.minX - labelPadding,
2766
- minY: labelBoundsRaw.minY - labelPadding,
2767
- maxX: labelBoundsRaw.maxX + labelPadding,
2768
- maxY: labelBoundsRaw.maxY + labelPadding,
2810
+ minX: labelBoundsRaw.minX,
2811
+ minY: labelBoundsRaw.minY,
2812
+ maxX: labelBoundsRaw.maxX,
2813
+ maxY: labelBoundsRaw.maxY,
2769
2814
  chipId: `netlabel-${label.netId}`
2770
2815
  };
2771
2816
  const fourPointCandidates = generateFourPointDetourCandidates({
@@ -2791,6 +2836,7 @@ var generateRerouteCandidates = ({
2791
2836
  };
2792
2837
 
2793
2838
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts
2839
+ var MAX_TRIES = 5;
2794
2840
  var SingleOverlapSolver = class extends BaseSolver {
2795
2841
  queuedCandidatePaths;
2796
2842
  solvedTracePath = null;
@@ -2798,13 +2844,17 @@ var SingleOverlapSolver = class extends BaseSolver {
2798
2844
  problem;
2799
2845
  obstacles;
2800
2846
  label;
2847
+ _tried = 0;
2801
2848
  constructor(solverInput) {
2802
2849
  super();
2803
2850
  this.initialTrace = solverInput.trace;
2804
2851
  this.problem = solverInput.problem;
2805
2852
  this.label = solverInput.label;
2853
+ const effectivePadding = solverInput.paddingBuffer + solverInput.detourCount * solverInput.paddingBuffer;
2806
2854
  const candidates = generateRerouteCandidates({
2807
- ...solverInput
2855
+ ...solverInput,
2856
+ paddingBuffer: effectivePadding
2857
+ // Use the calculated, larger padding
2808
2858
  });
2809
2859
  const getPathLength = (pts) => {
2810
2860
  let len = 0;
@@ -2821,10 +2871,11 @@ var SingleOverlapSolver = class extends BaseSolver {
2821
2871
  this.obstacles = getObstacleRects(this.problem);
2822
2872
  }
2823
2873
  _step() {
2824
- if (this.queuedCandidatePaths.length === 0) {
2874
+ if (this.queuedCandidatePaths.length === 0 || this._tried >= MAX_TRIES) {
2825
2875
  this.failed = true;
2826
2876
  return;
2827
2877
  }
2878
+ this._tried++;
2828
2879
  const nextCandidatePath = this.queuedCandidatePaths.shift();
2829
2880
  const simplifiedPath = simplifyPath(nextCandidatePath);
2830
2881
  if (!isPathCollidingWithObstacles(simplifiedPath, this.obstacles)) {
@@ -2866,13 +2917,38 @@ var SingleOverlapSolver = class extends BaseSolver {
2866
2917
  }
2867
2918
  };
2868
2919
 
2869
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/isPointInsideLabel.ts
2870
- var isPointInsideLabel = ({
2871
- point,
2872
- label
2873
- }) => {
2874
- const bounds = getRectBounds(label.center, label.width, label.height);
2875
- return point.x >= bounds.minX && point.x <= bounds.maxX && point.y >= bounds.minY && point.y <= bounds.maxY;
2920
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/doesTraceStartOrEndInLabel.ts
2921
+ var doesTraceStartOrEndInLabel = ({ trace, label }) => {
2922
+ if (trace.tracePath.length < 2) {
2923
+ return false;
2924
+ }
2925
+ const firstSegmentTrace = {
2926
+ ...trace,
2927
+ tracePath: [trace.tracePath[0], trace.tracePath[1]]
2928
+ };
2929
+ const firstSegmentOverlap = detectTraceLabelOverlap({
2930
+ traces: [firstSegmentTrace],
2931
+ netLabels: [label]
2932
+ });
2933
+ if (firstSegmentOverlap.length > 0) {
2934
+ return true;
2935
+ }
2936
+ if (trace.tracePath.length > 2) {
2937
+ const lastPoint = trace.tracePath[trace.tracePath.length - 1];
2938
+ const secondToLastPoint = trace.tracePath[trace.tracePath.length - 2];
2939
+ const lastSegmentTrace = {
2940
+ ...trace,
2941
+ tracePath: [secondToLastPoint, lastPoint]
2942
+ };
2943
+ const lastSegmentOverlap = detectTraceLabelOverlap({
2944
+ traces: [lastSegmentTrace],
2945
+ netLabels: [label]
2946
+ });
2947
+ if (lastSegmentOverlap.length > 0) {
2948
+ return true;
2949
+ }
2950
+ }
2951
+ return false;
2876
2952
  };
2877
2953
 
2878
2954
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/visualizeDecomposition.ts
@@ -2908,8 +2984,8 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
2908
2984
  mergedLabelNetIdMap;
2909
2985
  allTraces;
2910
2986
  modifiedTraces = [];
2911
- detourCountByLabel = {};
2912
2987
  PADDING_BUFFER = 0.1;
2988
+ detourCounts = /* @__PURE__ */ new Map();
2913
2989
  activeSubSolver = null;
2914
2990
  overlapQueue = [];
2915
2991
  recentlyFailed = /* @__PURE__ */ new Set();
@@ -2922,6 +2998,7 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
2922
2998
  this.mergedNetLabelPlacements = solverInput.mergedNetLabelPlacements;
2923
2999
  this.mergedLabelNetIdMap = solverInput.mergedLabelNetIdMap;
2924
3000
  this.allTraces = [...solverInput.traces];
3001
+ this.detourCounts = solverInput.detourCounts;
2925
3002
  }
2926
3003
  _step() {
2927
3004
  this.currentlyProcessingOverlap = null;
@@ -2973,7 +3050,6 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
2973
3050
  (t) => t.mspPairId === nextOverlap.trace.mspPairId
2974
3051
  );
2975
3052
  const labelToAvoid = nextOverlap.label;
2976
- const traceStartPoint = traceToFix.tracePath[0];
2977
3053
  const originalNetIds = this.mergedLabelNetIdMap[labelToAvoid.globalConnNetId];
2978
3054
  const isSelfOverlap = originalNetIds?.has(traceToFix.globalConnNetId);
2979
3055
  if (isSelfOverlap) {
@@ -2993,9 +3069,11 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
2993
3069
  }
2994
3070
  }
2995
3071
  if (actualOverlapLabel) {
2996
- const labelId2 = actualOverlapLabel.globalConnNetId;
2997
- const detourCount2 = this.detourCountByLabel[labelId2] || 0;
2998
- this.detourCountByLabel[labelId2] = detourCount2 + 1;
3072
+ const detourCount2 = this.detourCounts.get(actualOverlapLabel.globalConnNetId) ?? 0;
3073
+ this.detourCounts.set(
3074
+ actualOverlapLabel.globalConnNetId,
3075
+ detourCount2 + 1
3076
+ );
2999
3077
  this.activeSubSolver = new SingleOverlapSolver({
3000
3078
  trace: traceToFix,
3001
3079
  label: actualOverlapLabel,
@@ -3009,7 +3087,7 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
3009
3087
  }
3010
3088
  return;
3011
3089
  }
3012
- if (originalNetIds && isPointInsideLabel({ point: traceStartPoint, label: labelToAvoid })) {
3090
+ if (originalNetIds && doesTraceStartOrEndInLabel({ trace: traceToFix, label: labelToAvoid })) {
3013
3091
  const childLabels = this.initialNetLabelPlacements.filter(
3014
3092
  (l) => originalNetIds.has(l.globalConnNetId)
3015
3093
  );
@@ -3026,9 +3104,11 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
3026
3104
  }
3027
3105
  }
3028
3106
  if (actualOverlapLabel) {
3029
- const labelId2 = actualOverlapLabel.globalConnNetId;
3030
- const detourCount2 = this.detourCountByLabel[labelId2] || 0;
3031
- this.detourCountByLabel[labelId2] = detourCount2 + 1;
3107
+ const detourCount2 = this.detourCounts.get(actualOverlapLabel.globalConnNetId) ?? 0;
3108
+ this.detourCounts.set(
3109
+ actualOverlapLabel.globalConnNetId,
3110
+ detourCount2 + 1
3111
+ );
3032
3112
  this.activeSubSolver = new SingleOverlapSolver({
3033
3113
  trace: traceToFix,
3034
3114
  label: actualOverlapLabel,
@@ -3042,9 +3122,8 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
3042
3122
  }
3043
3123
  return;
3044
3124
  }
3045
- const labelId = labelToAvoid.globalConnNetId;
3046
- const detourCount = this.detourCountByLabel[labelId] || 0;
3047
- this.detourCountByLabel[labelId] = detourCount + 1;
3125
+ const detourCount = this.detourCounts.get(labelToAvoid.globalConnNetId) ?? 0;
3126
+ this.detourCounts.set(labelToAvoid.globalConnNetId, detourCount + 1);
3048
3127
  this.activeSubSolver = new SingleOverlapSolver({
3049
3128
  trace: traceToFix,
3050
3129
  label: labelToAvoid,
@@ -3057,7 +3136,8 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
3057
3136
  getOutput() {
3058
3137
  return {
3059
3138
  allTraces: this.allTraces,
3060
- modifiedTraces: this.modifiedTraces
3139
+ modifiedTraces: this.modifiedTraces,
3140
+ detourCounts: this.detourCounts
3061
3141
  };
3062
3142
  }
3063
3143
  visualize() {
@@ -3110,76 +3190,101 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
3110
3190
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts
3111
3191
  var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
3112
3192
  inputProblem;
3113
- traces;
3114
3193
  netLabelPlacements;
3115
- // sub-solver instances
3194
+ unprocessedTraces = [];
3195
+ cleanTraces = [];
3196
+ subSolvers = [];
3197
+ phase = "searching_for_overlaps";
3198
+ detourCounts = /* @__PURE__ */ new Map();
3116
3199
  labelMergingSolver;
3117
- overlapAvoidanceSolver;
3118
- pipelineStepIndex = 0;
3119
3200
  constructor(solverInput) {
3120
3201
  super();
3121
3202
  this.inputProblem = solverInput.inputProblem;
3122
- this.traces = solverInput.traces;
3203
+ this.unprocessedTraces = [...solverInput.traces];
3123
3204
  this.netLabelPlacements = solverInput.netLabelPlacements;
3205
+ this.cleanTraces = [];
3206
+ this.subSolvers = [];
3124
3207
  }
3125
3208
  _step() {
3126
- if (this.activeSubSolver) {
3127
- this.activeSubSolver.step();
3128
- if (this.activeSubSolver.solved) {
3129
- this.activeSubSolver = null;
3130
- this.pipelineStepIndex++;
3131
- } else if (this.activeSubSolver.failed) {
3132
- this.failed = true;
3133
- this.activeSubSolver = null;
3209
+ if (this.phase === "searching_for_overlaps") {
3210
+ if (this.unprocessedTraces.length === 0) {
3211
+ console.log(
3212
+ `Dispatch phase complete. Created ${this.subSolvers.length} sub-solvers.`
3213
+ );
3214
+ this.phase = "fixing_overlaps";
3215
+ return;
3134
3216
  }
3135
- return;
3136
- }
3137
- switch (this.pipelineStepIndex) {
3138
- case 0:
3139
- this.labelMergingSolver = new MergedNetLabelObstacleSolver({
3140
- netLabelPlacements: this.netLabelPlacements,
3217
+ const currentTargetTrace = this.unprocessedTraces.shift();
3218
+ const localOverlaps = detectTraceLabelOverlap({
3219
+ traces: [currentTargetTrace],
3220
+ netLabels: this.netLabelPlacements
3221
+ });
3222
+ if (localOverlaps.length === 0) {
3223
+ this.cleanTraces.push(currentTargetTrace);
3224
+ } else {
3225
+ const collidingLabels = localOverlaps.map((o) => o.label);
3226
+ const labelMerger = new MergedNetLabelObstacleSolver({
3227
+ netLabelPlacements: collidingLabels,
3141
3228
  inputProblem: this.inputProblem,
3142
- traces: this.traces
3229
+ traces: [currentTargetTrace]
3143
3230
  });
3144
- this.activeSubSolver = this.labelMergingSolver;
3145
- break;
3146
- case 1:
3147
- this.overlapAvoidanceSolver = new OverlapAvoidanceStepSolver({
3231
+ labelMerger.solve();
3232
+ const mergingOutput = labelMerger.getOutput();
3233
+ const subSolver = new OverlapAvoidanceStepSolver({
3148
3234
  inputProblem: this.inputProblem,
3149
- traces: this.traces,
3235
+ traces: [currentTargetTrace],
3150
3236
  initialNetLabelPlacements: this.netLabelPlacements,
3151
- // The original, unfiltered list
3152
- mergedNetLabelPlacements: this.labelMergingSolver.getOutput().netLabelPlacements,
3153
- mergedLabelNetIdMap: this.labelMergingSolver.getOutput().mergedLabelNetIdMap
3237
+ mergedNetLabelPlacements: mergingOutput.netLabelPlacements,
3238
+ mergedLabelNetIdMap: mergingOutput.mergedLabelNetIdMap,
3239
+ detourCounts: this.detourCounts
3154
3240
  });
3155
- this.activeSubSolver = this.overlapAvoidanceSolver;
3156
- break;
3157
- default:
3241
+ this.subSolvers.push(subSolver);
3242
+ }
3243
+ } else if (this.phase === "fixing_overlaps") {
3244
+ if (this.subSolvers.every((s) => s.solved || s.failed)) {
3245
+ console.log("All sub-solvers finished.");
3246
+ if (!this.labelMergingSolver) {
3247
+ this.labelMergingSolver = new MergedNetLabelObstacleSolver({
3248
+ netLabelPlacements: this.netLabelPlacements,
3249
+ inputProblem: this.inputProblem,
3250
+ traces: this.getOutput().traces
3251
+ });
3252
+ this.labelMergingSolver.solve();
3253
+ }
3158
3254
  this.solved = true;
3159
- break;
3255
+ return;
3256
+ }
3257
+ for (const solver of this.subSolvers) {
3258
+ if (!solver.solved && !solver.failed) {
3259
+ solver.step();
3260
+ }
3261
+ }
3160
3262
  }
3161
3263
  }
3162
3264
  getOutput() {
3265
+ const solvedTraces = this.subSolvers.flatMap((s) => s.getOutput().allTraces);
3163
3266
  return {
3164
- traces: this.overlapAvoidanceSolver?.getOutput().allTraces ?? this.traces,
3267
+ traces: [...this.cleanTraces, ...solvedTraces],
3165
3268
  netLabelPlacements: this.labelMergingSolver?.getOutput().netLabelPlacements ?? this.netLabelPlacements
3166
3269
  };
3167
3270
  }
3168
3271
  visualize() {
3169
- if (this.activeSubSolver) {
3170
- return this.activeSubSolver.visualize();
3171
- }
3172
3272
  const graphics = visualizeInputProblem(this.inputProblem);
3173
3273
  if (!graphics.lines) graphics.lines = [];
3174
3274
  if (!graphics.rects) graphics.rects = [];
3175
- const output = this.getOutput();
3176
- for (const trace of output.traces) {
3275
+ for (const trace of this.cleanTraces) {
3177
3276
  graphics.lines.push({
3178
3277
  points: trace.tracePath,
3179
3278
  strokeColor: "purple"
3180
3279
  });
3181
3280
  }
3182
- for (const label of output.netLabelPlacements) {
3281
+ for (const solver of this.subSolvers) {
3282
+ const solverGraphics = solver.visualize();
3283
+ graphics.lines.push(...solverGraphics.lines ?? []);
3284
+ graphics.rects.push(...solverGraphics.rects ?? []);
3285
+ graphics.points.push(...solverGraphics.points ?? []);
3286
+ }
3287
+ for (const label of this.netLabelPlacements) {
3183
3288
  const color = getColorFromString(label.globalConnNetId, 0.3);
3184
3289
  graphics.rects.push({
3185
3290
  center: label.center,
@@ -3328,6 +3433,7 @@ var LongDistancePairSolver = class extends BaseSolver {
3328
3433
  }
3329
3434
  this.queuedCandidatePairs = candidatePairs;
3330
3435
  }
3436
+ params;
3331
3437
  solvedLongDistanceTraces = [];
3332
3438
  queuedCandidatePairs = [];
3333
3439
  currentCandidatePair = null;
@@ -350,11 +350,13 @@ export class NetLabelPlacementSolver extends BaseSolver {
350
350
  height: p.height,
351
351
  fill: getColorFromString(p.globalConnNetId, 0.35),
352
352
  strokeColor: getColorFromString(p.globalConnNetId, 0.9),
353
+ label: `netId: ${p.netId}\nglobalConnNetId: ${p.globalConnNetId}`,
353
354
  } as any)
354
355
  graphics.points!.push({
355
356
  x: p.anchorPoint.x,
356
357
  y: p.anchorPoint.y,
357
358
  color: getColorFromString(p.globalConnNetId, 0.9),
359
+ label: `anchorPoint\norientation: ${p.orientation}`,
358
360
  } as any)
359
361
  }
360
362