@tscircuit/schematic-trace-solver 0.0.97 → 0.0.98

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 (36) hide show
  1. package/dist/index.d.ts +9 -0
  2. package/dist/index.js +753 -285
  3. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +45 -1
  4. package/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +55 -13
  5. package/lib/solvers/TraceCleanupSolver/alignSameNetRails.ts +66 -0
  6. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/evaluateRailGroup.ts +124 -0
  7. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry.ts +108 -0
  8. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getComponentSideRailSegments.ts +121 -0
  9. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getRailGroups.ts +114 -0
  10. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/moveRailSegments.ts +27 -0
  11. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/preservesLabelAnchors.ts +30 -0
  12. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/scoreRailAlignment.ts +67 -0
  13. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/types.ts +34 -0
  14. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +1 -60
  15. package/lib/utils/doesPathCoincideWithTraces.ts +61 -0
  16. package/package.json +1 -1
  17. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +1 -1
  18. package/tests/bug-reports/bug-report-20260707T141025Z/__snapshots__/bug-report-20260707T141025Z.snap.svg +1 -1
  19. package/tests/examples/__snapshots__/example02.snap.svg +1 -1
  20. package/tests/examples/__snapshots__/example14.snap.svg +1 -1
  21. package/tests/examples/__snapshots__/example18.snap.svg +1 -1
  22. package/tests/examples/__snapshots__/example46.snap.svg +1 -1
  23. package/tests/repros/__snapshots__/repro-cc2340r5.snap.svg +1 -1
  24. package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +1 -1
  25. package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +1 -1
  26. package/tests/repros/repro-rp2040-gamepad-trace-alignment.test.ts +24 -0
  27. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-component-scope.test.ts +38 -0
  28. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-eligible-traces.test.ts +28 -0
  29. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-horizontal.test.ts +62 -0
  30. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-label-anchor.test.ts +36 -0
  31. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-label-junction.test.ts +42 -0
  32. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-obstacle.test.ts +28 -0
  33. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-pipeline-label-connector.test.ts +52 -0
  34. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-vertical.test.ts +25 -0
  35. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-visible-length.test.ts +40 -0
  36. package/tests/solvers/TraceCleanupSolver/fixtures/alignSameNetRails.ts +89 -0
package/dist/index.js CHANGED
@@ -1440,12 +1440,12 @@ var applyJogToTerminalSegment = ({
1440
1440
  if (si !== 0 && si !== pts.length - 2) return;
1441
1441
  const start = pts[si];
1442
1442
  const end = pts[si + 1];
1443
- const isVertical4 = Math.abs(start.x - end.x) < EPS11;
1444
- const isHorizontal3 = Math.abs(start.y - end.y) < EPS11;
1445
- if (!isVertical4 && !isHorizontal3) return;
1446
- const segDir = isVertical4 ? end.y > start.y ? 1 : -1 : end.x > start.x ? 1 : -1;
1443
+ const isVertical5 = Math.abs(start.x - end.x) < EPS11;
1444
+ const isHorizontal4 = Math.abs(start.y - end.y) < EPS11;
1445
+ if (!isVertical5 && !isHorizontal4) return;
1446
+ const segDir = isVertical5 ? end.y > start.y ? 1 : -1 : end.x > start.x ? 1 : -1;
1447
1447
  if (pts.length === 2) {
1448
- if (isVertical4) {
1448
+ if (isVertical5) {
1449
1449
  const jogYNearStart = start.y + segDir * JOG_SIZE;
1450
1450
  const jogYNearEnd = end.y - segDir * JOG_SIZE;
1451
1451
  pts.splice(
@@ -1471,7 +1471,7 @@ var applyJogToTerminalSegment = ({
1471
1471
  return;
1472
1472
  }
1473
1473
  if (si === 0) {
1474
- if (isVertical4) {
1474
+ if (isVertical5) {
1475
1475
  const jogY = start.y + segDir * JOG_SIZE;
1476
1476
  pts.splice(
1477
1477
  1,
@@ -1491,7 +1491,7 @@ var applyJogToTerminalSegment = ({
1491
1491
  );
1492
1492
  }
1493
1493
  } else {
1494
- if (isVertical4) {
1494
+ if (isVertical5) {
1495
1495
  const jogY = end.y - segDir * JOG_SIZE;
1496
1496
  pts.splice(
1497
1497
  si,
@@ -1587,10 +1587,10 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1587
1587
  } else {
1588
1588
  const start = pts[si];
1589
1589
  const end = pts[si + 1];
1590
- const isVertical4 = Math.abs(start.x - end.x) < EPS4;
1591
- const isHorizontal3 = Math.abs(start.y - end.y) < EPS4;
1592
- if (!isVertical4 && !isHorizontal3) continue;
1593
- if (isVertical4) {
1590
+ const isVertical5 = Math.abs(start.x - end.x) < EPS4;
1591
+ const isHorizontal4 = Math.abs(start.y - end.y) < EPS4;
1592
+ if (!isVertical5 && !isHorizontal4) continue;
1593
+ if (isVertical5) {
1594
1594
  start.x += offset;
1595
1595
  end.x += offset;
1596
1596
  } else {
@@ -1619,11 +1619,11 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1619
1619
  }) {
1620
1620
  const blockingCollisions = this.getBlockingCollisions({ group, offset });
1621
1621
  if (blockingCollisions.length === 0) return offset;
1622
- const crossesThroughObstacle = (candidateOffset) => blockingCollisions.some(({ start, isVertical: isVertical4, obstacle }) => {
1622
+ const crossesThroughObstacle = (candidateOffset) => blockingCollisions.some(({ start, isVertical: isVertical5, obstacle }) => {
1623
1623
  let orig;
1624
1624
  let min;
1625
1625
  let max;
1626
- if (isVertical4) {
1626
+ if (isVertical5) {
1627
1627
  orig = start.x;
1628
1628
  min = obstacle.minX;
1629
1629
  max = obstacle.maxX;
@@ -1635,8 +1635,8 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1635
1635
  const next = orig + candidateOffset;
1636
1636
  return orig <= min + EPS4 && next >= max - EPS4 || orig >= max - EPS4 && next <= min + EPS4;
1637
1637
  });
1638
- const edges = blockingCollisions.map(({ start, isVertical: isVertical4, obstacle }) => {
1639
- if (isVertical4) {
1638
+ const edges = blockingCollisions.map(({ start, isVertical: isVertical5, obstacle }) => {
1639
+ if (isVertical5) {
1640
1640
  return {
1641
1641
  coord: start.x,
1642
1642
  lowEdge: obstacle.minX,
@@ -1670,10 +1670,10 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1670
1670
  const start = trace.tracePath[traceSegmentIndex];
1671
1671
  const end = trace.tracePath[traceSegmentIndex + 1];
1672
1672
  if (!start || !end) continue;
1673
- const isVertical4 = Math.abs(start.x - end.x) < EPS4;
1674
- const isHorizontal3 = Math.abs(start.y - end.y) < EPS4;
1675
- if (!isVertical4 && !isHorizontal3) continue;
1676
- const shiftedSegment = isVertical4 ? [
1673
+ const isVertical5 = Math.abs(start.x - end.x) < EPS4;
1674
+ const isHorizontal4 = Math.abs(start.y - end.y) < EPS4;
1675
+ if (!isVertical5 && !isHorizontal4) continue;
1676
+ const shiftedSegment = isVertical5 ? [
1677
1677
  { ...start, x: start.x + offset },
1678
1678
  { ...end, x: end.x + offset }
1679
1679
  ] : [
@@ -1684,7 +1684,7 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1684
1684
  if (collision) {
1685
1685
  blockingCollisions.push({
1686
1686
  start,
1687
- isVertical: isVertical4,
1687
+ isVertical: isVertical5,
1688
1688
  obstacle: collision.rect
1689
1689
  });
1690
1690
  }
@@ -1867,9 +1867,9 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1867
1867
  for (let i = 0; i < tracePath.length - 1; i++) {
1868
1868
  const p1 = tracePath[i];
1869
1869
  const p2 = tracePath[i + 1];
1870
- const isHorizontal3 = Math.abs(p1.y - p2.y) < EPS11;
1871
- const isVertical4 = Math.abs(p1.x - p2.x) < EPS11;
1872
- if (!isHorizontal3 && !isVertical4) {
1870
+ const isHorizontal4 = Math.abs(p1.y - p2.y) < EPS11;
1871
+ const isVertical5 = Math.abs(p1.x - p2.x) < EPS11;
1872
+ if (!isHorizontal4 && !isVertical5) {
1873
1873
  return { mspPairId, tracePath, i, p1, p2 };
1874
1874
  }
1875
1875
  }
@@ -3478,19 +3478,18 @@ var generateRerouteCandidates = ({
3478
3478
  return [...fourPointCandidates, ...snipReconnectCandidates];
3479
3479
  };
3480
3480
 
3481
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts
3482
- var MAX_TRIES = 5;
3481
+ // lib/utils/doesPathCoincideWithTraces.ts
3483
3482
  var COINCIDENT_EPS = 2e-3;
3484
3483
  var doesPathCoincideWithTraces = (path, traces) => {
3485
3484
  const rangesOverlap1D = (a1, a2, b1, b2) => Math.min(Math.max(a1, a2), Math.max(b1, b2)) - Math.max(Math.min(a1, a2), Math.min(b1, b2)) > COINCIDENT_EPS;
3486
3485
  for (let i = 0; i < path.length - 1; i++) {
3487
3486
  const pathSegStart = path[i];
3488
3487
  const pathSegEnd = path[i + 1];
3489
- const isVertical4 = Math.abs(pathSegStart.x - pathSegEnd.x) < COINCIDENT_EPS;
3490
- const isHorizontal3 = Math.abs(pathSegStart.y - pathSegEnd.y) < COINCIDENT_EPS;
3491
- if (!isVertical4 && !isHorizontal3) continue;
3492
- const crossAxis = isVertical4 ? "x" : "y";
3493
- const alongAxis = isVertical4 ? "y" : "x";
3488
+ const isVertical5 = Math.abs(pathSegStart.x - pathSegEnd.x) < COINCIDENT_EPS;
3489
+ const isHorizontal4 = Math.abs(pathSegStart.y - pathSegEnd.y) < COINCIDENT_EPS;
3490
+ if (!isVertical5 && !isHorizontal4) continue;
3491
+ const crossAxis = isVertical5 ? "x" : "y";
3492
+ const alongAxis = isVertical5 ? "y" : "x";
3494
3493
  for (const trace of traces) {
3495
3494
  for (let j = 0; j < trace.tracePath.length - 1; j++) {
3496
3495
  const traceSegStart = trace.tracePath[j];
@@ -3512,6 +3511,9 @@ var doesPathCoincideWithTraces = (path, traces) => {
3512
3511
  }
3513
3512
  return false;
3514
3513
  };
3514
+
3515
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts
3516
+ var MAX_TRIES = 5;
3515
3517
  var SingleOverlapSolver = class extends BaseSolver {
3516
3518
  queuedCandidatePaths;
3517
3519
  solvedTracePath = null;
@@ -4691,6 +4693,590 @@ var balanceZShapes = ({
4691
4693
  };
4692
4694
  };
4693
4695
 
4696
+ // lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry.ts
4697
+ var RAIL_ALIGNMENT_EPSILON = 2e-3;
4698
+ var nearlyEqual = (a, b) => Math.abs(a - b) < RAIL_ALIGNMENT_EPSILON;
4699
+ var isHorizontal2 = (a, b) => nearlyEqual(a.y, b.y);
4700
+ var isVertical2 = (a, b) => nearlyEqual(a.x, b.x);
4701
+ var isPositiveLength = (a, b) => Math.abs(a.x - b.x) >= RAIL_ALIGNMENT_EPSILON || Math.abs(a.y - b.y) >= RAIL_ALIGNMENT_EPSILON;
4702
+ var getRailOrientation = (a, b) => {
4703
+ if (!isPositiveLength(a, b)) return null;
4704
+ if (isVertical2(a, b)) return "vertical";
4705
+ if (isHorizontal2(a, b)) return "horizontal";
4706
+ return null;
4707
+ };
4708
+ var rangesTouchOrOverlap = (a, b) => Math.min(a.maxAlong, b.maxAlong) - Math.max(a.minAlong, b.minAlong) >= -RAIL_ALIGNMENT_EPSILON;
4709
+ var pointsEqual = (a, b) => nearlyEqual(a.x, b.x) && nearlyEqual(a.y, b.y);
4710
+ var getDistinctCoordinates = (coordinates) => {
4711
+ const distinct = [];
4712
+ for (const coordinate of coordinates) {
4713
+ if (!distinct.some((item) => nearlyEqual(item, coordinate))) {
4714
+ distinct.push(coordinate);
4715
+ }
4716
+ }
4717
+ return distinct;
4718
+ };
4719
+ var getMergedIntervalLength = (intervals) => {
4720
+ const groups = [];
4721
+ for (const interval of intervals) {
4722
+ const group = groups.find(
4723
+ (items) => nearlyEqual(items[0].coordinate, interval.coordinate)
4724
+ );
4725
+ if (group) group.push(interval);
4726
+ else groups.push([interval]);
4727
+ }
4728
+ return groups.reduce((total, group) => {
4729
+ const sorted = [...group].sort((a, b) => a.min - b.min);
4730
+ let groupLength = 0;
4731
+ let currentMin = sorted[0].min;
4732
+ let currentMax = sorted[0].max;
4733
+ for (const interval of sorted.slice(1)) {
4734
+ if (interval.min <= currentMax + RAIL_ALIGNMENT_EPSILON) {
4735
+ currentMax = Math.max(currentMax, interval.max);
4736
+ } else {
4737
+ groupLength += currentMax - currentMin;
4738
+ currentMin = interval.min;
4739
+ currentMax = interval.max;
4740
+ }
4741
+ }
4742
+ return total + groupLength + currentMax - currentMin;
4743
+ }, 0);
4744
+ };
4745
+ var getVisibleTraceLength = (traces) => {
4746
+ const horizontal = [];
4747
+ const vertical = [];
4748
+ for (const trace of traces) {
4749
+ for (let index = 0; index < trace.tracePath.length - 1; index++) {
4750
+ const start = trace.tracePath[index];
4751
+ const end = trace.tracePath[index + 1];
4752
+ if (isHorizontal2(start, end)) {
4753
+ horizontal.push({
4754
+ coordinate: start.y,
4755
+ min: Math.min(start.x, end.x),
4756
+ max: Math.max(start.x, end.x)
4757
+ });
4758
+ } else if (isVertical2(start, end)) {
4759
+ vertical.push({
4760
+ coordinate: start.x,
4761
+ min: Math.min(start.y, end.y),
4762
+ max: Math.max(start.y, end.y)
4763
+ });
4764
+ }
4765
+ }
4766
+ }
4767
+ return getMergedIntervalLength(horizontal) + getMergedIntervalLength(vertical);
4768
+ };
4769
+
4770
+ // lib/solvers/TraceCleanupSolver/sameNetRailAlignment/moveRailSegments.ts
4771
+ var moveRailSegments = (trace, segments, coordinate) => {
4772
+ const pointsToMove = /* @__PURE__ */ new Set();
4773
+ for (const segment of segments) {
4774
+ pointsToMove.add(segment.segmentIndex);
4775
+ pointsToMove.add(segment.segmentIndex + 1);
4776
+ }
4777
+ const orientation = segments[0].orientation;
4778
+ const tracePath = simplifyPath(
4779
+ trace.tracePath.map((point, index) => {
4780
+ if (!pointsToMove.has(index)) return point;
4781
+ return orientation === "vertical" ? { ...point, x: coordinate } : { ...point, y: coordinate };
4782
+ })
4783
+ );
4784
+ return { ...trace, tracePath };
4785
+ };
4786
+
4787
+ // lib/solvers/RailNetLabelCornerPlacementSolver/geometry.ts
4788
+ var EPS5 = 1e-6;
4789
+ var isTraceLine = (trace) => getUniquePinCount(trace.pinIds) >= 2;
4790
+ var rectsOverlap = (a, b) => Math.min(a.maxX, b.maxX) - Math.max(a.minX, b.minX) > EPS5 && Math.min(a.maxY, b.maxY) - Math.max(a.minY, b.minY) > EPS5;
4791
+ var getTraceCorners = (path) => {
4792
+ const corners = [];
4793
+ for (let i = 1; i < path.length - 1; i++) {
4794
+ const previousPoint = path[i - 1];
4795
+ const cornerPoint = path[i];
4796
+ const nextPoint = path[i + 1];
4797
+ if (isTraceCorner(previousPoint, cornerPoint, nextPoint)) {
4798
+ corners.push(cornerPoint);
4799
+ }
4800
+ }
4801
+ return corners;
4802
+ };
4803
+ var tracePathContainsPoint = (path, point) => {
4804
+ for (let i = 0; i < path.length - 1; i++) {
4805
+ if (isPointOnSegment(point, path[i], path[i + 1])) return true;
4806
+ }
4807
+ return false;
4808
+ };
4809
+ var getDistance = (a, b) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
4810
+ var getUniquePinCount = (pinIds) => new Set(pinIds).size;
4811
+ var isTraceCorner = (a, b, c) => getSegmentOrientation(a, b) !== getSegmentOrientation(b, c);
4812
+ var isPointOnSegment = (point, start, end) => {
4813
+ if (getSegmentOrientation(start, end) === "horizontal") {
4814
+ return Math.abs(point.y - start.y) <= EPS5 && point.x >= Math.min(start.x, end.x) - EPS5 && point.x <= Math.max(start.x, end.x) + EPS5;
4815
+ }
4816
+ return Math.abs(point.x - start.x) <= EPS5 && point.y >= Math.min(start.y, end.y) - EPS5 && point.y <= Math.max(start.y, end.y) + EPS5;
4817
+ };
4818
+ var getSegmentOrientation = (a, b) => Math.abs(a.y - b.y) <= EPS5 ? "horizontal" : "vertical";
4819
+
4820
+ // lib/solvers/TraceCleanupSolver/sameNetRailAlignment/preservesLabelAnchors.ts
4821
+ var getAnchoredTraceIds = (label, traces) => new Set(
4822
+ traces.filter(
4823
+ (trace) => trace.globalConnNetId === label.globalConnNetId && tracePathContainsPoint(trace.tracePath, label.anchorPoint)
4824
+ ).map((trace) => trace.mspPairId)
4825
+ );
4826
+ var preservesLabelAnchors = (labels, before, after) => labels.every((label) => {
4827
+ const anchoredBefore = getAnchoredTraceIds(label, before);
4828
+ if (anchoredBefore.size === 0) return true;
4829
+ const anchoredAfter = getAnchoredTraceIds(label, after);
4830
+ return [...anchoredBefore].every((traceId) => anchoredAfter.has(traceId));
4831
+ });
4832
+
4833
+ // lib/solvers/Example28Solver/types.ts
4834
+ var EPS6 = 1e-9;
4835
+
4836
+ // lib/solvers/Example28Solver/segmentRunsAlongRectBoundary.ts
4837
+ var segmentRunsAlongRectBoundary = (start, end, rect) => {
4838
+ const isVertical5 = Math.abs(start.x - end.x) < EPS6;
4839
+ const isHorizontal4 = Math.abs(start.y - end.y) < EPS6;
4840
+ if (isVertical5) {
4841
+ return Math.abs(start.x - rect.minX) < EPS6 || Math.abs(start.x - rect.maxX) < EPS6;
4842
+ }
4843
+ if (isHorizontal4) {
4844
+ return Math.abs(start.y - rect.minY) < EPS6 || Math.abs(start.y - rect.maxY) < EPS6;
4845
+ }
4846
+ return false;
4847
+ };
4848
+
4849
+ // lib/solvers/Example28Solver/geometry.ts
4850
+ var getPathKey = (path) => path.map((point) => `${point.x},${point.y}`).join(";");
4851
+ var getPathLength = (path) => {
4852
+ let length = 0;
4853
+ for (let i = 0; i < path.length - 1; i++) {
4854
+ length += Math.abs(path[i + 1].x - path[i].x) + Math.abs(path[i + 1].y - path[i].y);
4855
+ }
4856
+ return length;
4857
+ };
4858
+ var getDistance2 = (a, b) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
4859
+ var isAxisAlignedSegment = (start, end) => Math.abs(start.x - end.x) < EPS6 || Math.abs(start.y - end.y) < EPS6;
4860
+ var getSegmentOrientation2 = (start, end) => Math.abs(start.y - end.y) < EPS6 ? "horizontal" : "vertical";
4861
+ var projectPointToSegment = (point, start, end) => {
4862
+ if (Math.abs(start.x - end.x) < EPS6) {
4863
+ return {
4864
+ x: start.x,
4865
+ y: Math.min(
4866
+ Math.max(point.y, Math.min(start.y, end.y)),
4867
+ Math.max(start.y, end.y)
4868
+ )
4869
+ };
4870
+ }
4871
+ if (Math.abs(start.y - end.y) < EPS6) {
4872
+ return {
4873
+ x: Math.min(
4874
+ Math.max(point.x, Math.min(start.x, end.x)),
4875
+ Math.max(start.x, end.x)
4876
+ ),
4877
+ y: start.y
4878
+ };
4879
+ }
4880
+ return start;
4881
+ };
4882
+ var getPointToSegmentDistance = (point, start, end) => getDistance2(point, projectPointToSegment(point, start, end));
4883
+ var getSegments = (path) => {
4884
+ const segments = [];
4885
+ for (let i = 0; i < path.length - 1; i++) {
4886
+ const start = path[i];
4887
+ const end = path[i + 1];
4888
+ if (!isAxisAlignedSegment(start, end)) continue;
4889
+ segments.push({
4890
+ start,
4891
+ end,
4892
+ orientation: getSegmentOrientation2(start, end)
4893
+ });
4894
+ }
4895
+ return segments;
4896
+ };
4897
+ var findSegmentContainingPoint = (path, point) => {
4898
+ for (let i = 0; i < path.length - 1; i++) {
4899
+ const start = path[i];
4900
+ const end = path[i + 1];
4901
+ if (!isAxisAlignedSegment(start, end)) continue;
4902
+ if (getPointToSegmentDistance(point, start, end) > 1e-6) continue;
4903
+ return {
4904
+ index: i,
4905
+ start,
4906
+ end,
4907
+ orientation: getSegmentOrientation2(start, end)
4908
+ };
4909
+ }
4910
+ return null;
4911
+ };
4912
+ var isPointWithinSegmentPrimaryRange = (point, segment) => {
4913
+ if (segment.orientation === "horizontal") {
4914
+ return point.x >= Math.min(segment.start.x, segment.end.x) - EPS6 && point.x <= Math.max(segment.start.x, segment.end.x) + EPS6;
4915
+ }
4916
+ return point.y >= Math.min(segment.start.y, segment.end.y) - EPS6 && point.y <= Math.max(segment.start.y, segment.end.y) + EPS6;
4917
+ };
4918
+ var findPreferredReroutedSegment = (path, originalSegmentIndex, originalSegmentCount, orientation, anchorPoint) => {
4919
+ const matchingSegments = getSegments(path).filter(
4920
+ (segment) => segment.orientation === orientation
4921
+ );
4922
+ if (matchingSegments.length === 0) return null;
4923
+ const containingSegments = matchingSegments.filter(
4924
+ (segment) => isPointWithinSegmentPrimaryRange(anchorPoint, segment)
4925
+ );
4926
+ const candidateSegments = containingSegments.length > 0 ? containingSegments : matchingSegments;
4927
+ const isNearStart = originalSegmentIndex < originalSegmentCount / 2;
4928
+ return isNearStart ? candidateSegments[0] : candidateSegments[candidateSegments.length - 1];
4929
+ };
4930
+ var projectPointToPath = (point, path) => {
4931
+ let bestPoint = null;
4932
+ let bestDistance = Number.POSITIVE_INFINITY;
4933
+ for (let i = 0; i < path.length - 1; i++) {
4934
+ const projectedPoint = projectPointToSegment(point, path[i], path[i + 1]);
4935
+ const distance3 = getDistance2(point, projectedPoint);
4936
+ if (distance3 < bestDistance) {
4937
+ bestPoint = projectedPoint;
4938
+ bestDistance = distance3;
4939
+ }
4940
+ }
4941
+ return bestPoint;
4942
+ };
4943
+ var segmentsIntersect = (a1, a2, b1, b2) => {
4944
+ const aVertical = Math.abs(a1.x - a2.x) < EPS6;
4945
+ const bVertical = Math.abs(b1.x - b2.x) < EPS6;
4946
+ const between = (value, p1, p2) => value >= Math.min(p1, p2) - EPS6 && value <= Math.max(p1, p2) + EPS6;
4947
+ if (aVertical && bVertical) {
4948
+ if (Math.abs(a1.x - b1.x) > EPS6) return false;
4949
+ const overlap = Math.min(Math.max(a1.y, a2.y), Math.max(b1.y, b2.y)) - Math.max(Math.min(a1.y, a2.y), Math.min(b1.y, b2.y));
4950
+ return overlap > EPS6;
4951
+ }
4952
+ if (!aVertical && !bVertical) {
4953
+ if (Math.abs(a1.y - b1.y) > EPS6) return false;
4954
+ const overlap = Math.min(Math.max(a1.x, a2.x), Math.max(b1.x, b2.x)) - Math.max(Math.min(a1.x, a2.x), Math.min(b1.x, b2.x));
4955
+ return overlap > EPS6;
4956
+ }
4957
+ const verticalA = aVertical ? a1 : b1;
4958
+ const verticalB = aVertical ? a2 : b2;
4959
+ const horizontalA = aVertical ? b1 : a1;
4960
+ const horizontalB = aVertical ? b2 : a2;
4961
+ return between(verticalA.x, horizontalA.x, horizontalB.x) && between(horizontalA.y, verticalA.y, verticalB.y);
4962
+ };
4963
+ var countPathIntersections = (pathA, pathB) => {
4964
+ let count = 0;
4965
+ for (let i = 0; i < pathA.length - 1; i++) {
4966
+ for (let j = 0; j < pathB.length - 1; j++) {
4967
+ if (segmentsIntersect(pathA[i], pathA[i + 1], pathB[j], pathB[j + 1])) {
4968
+ count++;
4969
+ }
4970
+ }
4971
+ }
4972
+ return count;
4973
+ };
4974
+ var isPathCollidingWithChipInterior = (path, chipObstacles) => {
4975
+ for (let i = 0; i < path.length - 1; i++) {
4976
+ const start = path[i];
4977
+ const end = path[i + 1];
4978
+ for (const obstacle of chipObstacles) {
4979
+ if (!segmentIntersectsRect(start, end, obstacle)) continue;
4980
+ if (segmentRunsAlongRectBoundary(start, end, obstacle)) continue;
4981
+ return true;
4982
+ }
4983
+ }
4984
+ return false;
4985
+ };
4986
+
4987
+ // lib/solvers/TraceCleanupSolver/sameNetRailAlignment/scoreRailAlignment.ts
4988
+ var countOtherNetCrossings = (traces, allTraces) => {
4989
+ let crossings = 0;
4990
+ for (const trace of traces) {
4991
+ for (const otherTrace of allTraces) {
4992
+ if (trace.globalConnNetId === otherTrace.globalConnNetId) continue;
4993
+ crossings += countPathIntersections(trace.tracePath, otherTrace.tracePath);
4994
+ }
4995
+ }
4996
+ return crossings;
4997
+ };
4998
+ var getTraceGeometryMetrics = (traces, allTraces) => ({
4999
+ turnCount: traces.reduce(
5000
+ (total, trace) => total + countTurns(trace.tracePath),
5001
+ 0
5002
+ ),
5003
+ visibleLength: getVisibleTraceLength(traces),
5004
+ pathLength: traces.reduce(
5005
+ (sum, trace) => sum + getPathLength(trace.tracePath),
5006
+ 0
5007
+ ),
5008
+ otherNetCrossings: countOtherNetCrossings(traces, allTraces)
5009
+ });
5010
+ var scoreIsBetter = (candidate, best) => {
5011
+ if (candidate.turnCount !== best.turnCount)
5012
+ return candidate.turnCount < best.turnCount;
5013
+ if (!nearlyEqual(candidate.visibleLength, best.visibleLength))
5014
+ return candidate.visibleLength < best.visibleLength;
5015
+ if (!nearlyEqual(candidate.pathLength, best.pathLength))
5016
+ return candidate.pathLength < best.pathLength;
5017
+ if (candidate.otherNetCrossings !== best.otherNetCrossings)
5018
+ return candidate.otherNetCrossings < best.otherNetCrossings;
5019
+ if (!nearlyEqual(candidate.displacement, best.displacement))
5020
+ return candidate.displacement < best.displacement;
5021
+ return candidate.coordinate < best.coordinate;
5022
+ };
5023
+ var isReadabilityImprovement = (candidate, baseline) => candidate.turnCount <= baseline.turnCount && (candidate.turnCount < baseline.turnCount || candidate.visibleLength <= baseline.visibleLength + RAIL_ALIGNMENT_EPSILON);
5024
+
5025
+ // lib/solvers/TraceCleanupSolver/sameNetRailAlignment/evaluateRailGroup.ts
5026
+ var tracePathChanged = (original, candidate) => original.tracePath.length !== candidate.tracePath.length || candidate.tracePath.some(
5027
+ (point, index) => !pointsEqual(point, original.tracePath[index])
5028
+ );
5029
+ var evaluateRailGroup = ({
5030
+ group,
5031
+ traces,
5032
+ netLabelPlacements,
5033
+ obstacles,
5034
+ eligibleTraceIds
5035
+ }) => {
5036
+ const groupTraceIds = new Set(group.map((segment) => segment.traceId));
5037
+ const originalGroupTraces = traces.filter(
5038
+ (trace) => groupTraceIds.has(trace.mspPairId)
5039
+ );
5040
+ const baseline = getTraceGeometryMetrics(originalGroupTraces, traces);
5041
+ const coordinates = getDistinctCoordinates(
5042
+ group.map((segment) => segment.coordinate)
5043
+ );
5044
+ const otherNetTraces = traces.filter(
5045
+ (trace) => trace.globalConnNetId !== group[0].globalConnNetId
5046
+ );
5047
+ const immutableSameNetTraces = traces.filter(
5048
+ (trace) => trace.globalConnNetId === group[0].globalConnNetId && !eligibleTraceIds.has(trace.mspPairId)
5049
+ );
5050
+ let best = null;
5051
+ for (const coordinate of coordinates) {
5052
+ const candidateMap = /* @__PURE__ */ new Map();
5053
+ for (const trace of originalGroupTraces) {
5054
+ const candidateTrace = moveRailSegments(
5055
+ trace,
5056
+ group.filter((segment) => segment.traceId === trace.mspPairId),
5057
+ coordinate
5058
+ );
5059
+ candidateMap.set(trace.mspPairId, candidateTrace);
5060
+ }
5061
+ const candidateTraces = [...candidateMap.values()];
5062
+ const allCandidateTraces = traces.map(
5063
+ (trace) => candidateMap.get(trace.mspPairId) ?? trace
5064
+ );
5065
+ const candidatesAreClear = candidateTraces.every(
5066
+ (candidate2) => !isPathCollidingWithObstacles(candidate2.tracePath, obstacles) && detectTraceLabelOverlap({
5067
+ traces: [candidate2],
5068
+ netLabels: netLabelPlacements
5069
+ }).length === 0 && !doesPathCoincideWithTraces(candidate2.tracePath, otherNetTraces) && !doesPathCoincideWithTraces(
5070
+ candidate2.tracePath,
5071
+ immutableSameNetTraces.filter(
5072
+ (trace) => trace.mspPairId !== candidate2.mspPairId
5073
+ )
5074
+ )
5075
+ );
5076
+ if (!candidatesAreClear) continue;
5077
+ if (!preservesLabelAnchors(netLabelPlacements, traces, allCandidateTraces)) {
5078
+ continue;
5079
+ }
5080
+ const metrics = getTraceGeometryMetrics(candidateTraces, allCandidateTraces);
5081
+ if (metrics.otherNetCrossings > baseline.otherNetCrossings) continue;
5082
+ if (!isReadabilityImprovement(metrics, baseline)) continue;
5083
+ const score = {
5084
+ ...metrics,
5085
+ displacement: group.reduce(
5086
+ (sum, segment) => sum + Math.abs(segment.coordinate - coordinate),
5087
+ 0
5088
+ ),
5089
+ coordinate
5090
+ };
5091
+ const changedTraceIds = candidateTraces.filter((candidate2) => {
5092
+ const original = traces.find(
5093
+ (trace) => trace.mspPairId === candidate2.mspPairId
5094
+ );
5095
+ return tracePathChanged(original, candidate2);
5096
+ }).map((trace) => trace.mspPairId);
5097
+ if (changedTraceIds.length === 0) continue;
5098
+ const candidate = { traces: allCandidateTraces, changedTraceIds, score };
5099
+ if (!best || scoreIsBetter(candidate.score, best.score)) best = candidate;
5100
+ }
5101
+ return best;
5102
+ };
5103
+
5104
+ // lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getComponentSideRailSegments.ts
5105
+ var getMovableRailSegments = (trace) => {
5106
+ const segments = [];
5107
+ const path = trace.tracePath;
5108
+ for (let segmentIndex = 1; segmentIndex < path.length - 2; segmentIndex++) {
5109
+ const previous = path[segmentIndex - 1];
5110
+ const start = path[segmentIndex];
5111
+ const end = path[segmentIndex + 1];
5112
+ const next = path[segmentIndex + 2];
5113
+ const orientation = getRailOrientation(start, end);
5114
+ if (!orientation) continue;
5115
+ const previousOrientation = getRailOrientation(previous, start);
5116
+ const nextOrientation = getRailOrientation(end, next);
5117
+ if (!previousOrientation || !nextOrientation) continue;
5118
+ if (previousOrientation === orientation || nextOrientation === orientation)
5119
+ continue;
5120
+ const alongValues = orientation === "vertical" ? [start.y, end.y] : [start.x, end.x];
5121
+ segments.push({
5122
+ traceId: trace.mspPairId,
5123
+ segmentIndex,
5124
+ globalConnNetId: trace.globalConnNetId,
5125
+ orientation,
5126
+ coordinate: orientation === "vertical" ? start.x : start.y,
5127
+ minAlong: Math.min(...alongValues),
5128
+ maxAlong: Math.max(...alongValues)
5129
+ });
5130
+ }
5131
+ return segments;
5132
+ };
5133
+ var railIsOutsideComponent = (segment, chip, facingDirection) => {
5134
+ const minX = chip.center.x - chip.width / 2;
5135
+ const maxX = chip.center.x + chip.width / 2;
5136
+ const minY = chip.center.y - chip.height / 2;
5137
+ const maxY = chip.center.y + chip.height / 2;
5138
+ switch (facingDirection) {
5139
+ case "x-":
5140
+ return segment.orientation === "vertical" && segment.coordinate <= minX + RAIL_ALIGNMENT_EPSILON;
5141
+ case "x+":
5142
+ return segment.orientation === "vertical" && segment.coordinate >= maxX - RAIL_ALIGNMENT_EPSILON;
5143
+ case "y+":
5144
+ return segment.orientation === "horizontal" && segment.coordinate >= maxY - RAIL_ALIGNMENT_EPSILON;
5145
+ case "y-":
5146
+ return segment.orientation === "horizontal" && segment.coordinate <= minY + RAIL_ALIGNMENT_EPSILON;
5147
+ }
5148
+ };
5149
+ var getComponentSideRailSegments = (trace, chipMap) => {
5150
+ const segments = [];
5151
+ for (const segment of getMovableRailSegments(trace)) {
5152
+ const associations = trace.pins.flatMap((pin, pinIndex) => {
5153
+ const chip = chipMap.get(pin.chipId);
5154
+ if (!chip) return [];
5155
+ const componentFacingDirection = pin._facingDirection ?? getPinDirection(pin, chip);
5156
+ if (!railIsOutsideComponent(segment, chip, componentFacingDirection)) {
5157
+ return [];
5158
+ }
5159
+ return [
5160
+ {
5161
+ componentId: chip.chipId,
5162
+ componentFacingDirection,
5163
+ distanceFromEndpoint: pinIndex === 0 ? segment.segmentIndex : trace.tracePath.length - 2 - segment.segmentIndex
5164
+ }
5165
+ ];
5166
+ });
5167
+ associations.sort((a, b) => a.distanceFromEndpoint - b.distanceFromEndpoint);
5168
+ const association = associations[0];
5169
+ if (!association) continue;
5170
+ segments.push({ ...segment, ...association });
5171
+ }
5172
+ return segments;
5173
+ };
5174
+
5175
+ // lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getRailGroups.ts
5176
+ var getCorridor = (a, b) => {
5177
+ const overlapMin = Math.max(a.minAlong, b.minAlong);
5178
+ const overlapMax = Math.min(a.maxAlong, b.maxAlong);
5179
+ const along = (overlapMin + overlapMax) / 2;
5180
+ return a.orientation === "vertical" ? [
5181
+ { x: a.coordinate, y: along },
5182
+ { x: b.coordinate, y: along }
5183
+ ] : [
5184
+ { x: along, y: a.coordinate },
5185
+ { x: along, y: b.coordinate }
5186
+ ];
5187
+ };
5188
+ var corridorIsClear = (a, b, obstacles) => {
5189
+ if (!rangesTouchOrOverlap(a, b)) return true;
5190
+ const [start, end] = getCorridor(a, b);
5191
+ return !obstacles.some(
5192
+ (obstacle) => segmentIntersectsRect(start, end, obstacle)
5193
+ );
5194
+ };
5195
+ var tracesSharePin = (a, b, traceMap) => {
5196
+ if (a.traceId === b.traceId) return true;
5197
+ const aPinIds = new Set(traceMap.get(a.traceId).pins.map((pin) => pin.pinId));
5198
+ return traceMap.get(b.traceId).pins.some((pin) => aPinIds.has(pin.pinId));
5199
+ };
5200
+ var canJoinRailGroup = (start, current, candidate, traceMap, obstacles) => candidate.globalConnNetId === start.globalConnNetId && candidate.orientation === start.orientation && candidate.componentId === start.componentId && candidate.componentFacingDirection === start.componentFacingDirection && (rangesTouchOrOverlap(current, candidate) || tracesSharePin(current, candidate, traceMap)) && corridorIsClear(current, candidate, obstacles);
5201
+ var getRailGroups = (traces, eligibleTraceIds, inputProblem, obstacles) => {
5202
+ const chipMap = new Map(inputProblem.chips.map((chip) => [chip.chipId, chip]));
5203
+ const segments = traces.filter((trace) => eligibleTraceIds.has(trace.mspPairId)).flatMap((trace) => getComponentSideRailSegments(trace, chipMap));
5204
+ const traceMap = new Map(traces.map((trace) => [trace.mspPairId, trace]));
5205
+ const visited = /* @__PURE__ */ new Set();
5206
+ const groups = [];
5207
+ for (let startIndex = 0; startIndex < segments.length; startIndex++) {
5208
+ if (visited.has(startIndex)) continue;
5209
+ const start = segments[startIndex];
5210
+ const queue = [startIndex];
5211
+ const group = [];
5212
+ visited.add(startIndex);
5213
+ for (let queueIndex = 0; queueIndex < queue.length; queueIndex++) {
5214
+ const current = segments[queue[queueIndex]];
5215
+ group.push(current);
5216
+ for (let candidateIndex = 0; candidateIndex < segments.length; candidateIndex++) {
5217
+ if (visited.has(candidateIndex)) continue;
5218
+ const candidate = segments[candidateIndex];
5219
+ if (!canJoinRailGroup(start, current, candidate, traceMap, obstacles)) {
5220
+ continue;
5221
+ }
5222
+ visited.add(candidateIndex);
5223
+ queue.push(candidateIndex);
5224
+ }
5225
+ }
5226
+ const traceCount = new Set(group.map((segment) => segment.traceId)).size;
5227
+ const hasDifferentCoordinates = group.some(
5228
+ (segment) => !nearlyEqual(segment.coordinate, group[0].coordinate)
5229
+ );
5230
+ if (traceCount >= 2 && hasDifferentCoordinates) groups.push(group);
5231
+ }
5232
+ return groups;
5233
+ };
5234
+
5235
+ // lib/solvers/TraceCleanupSolver/alignSameNetRails.ts
5236
+ var alignSameNetRails = ({
5237
+ inputProblem,
5238
+ traces,
5239
+ netLabelPlacements,
5240
+ eligibleTraceIds
5241
+ }) => {
5242
+ let outputTraces = [...traces];
5243
+ const obstacles = getObstacleRects(inputProblem);
5244
+ const alignedTraceIds = /* @__PURE__ */ new Set();
5245
+ let alignedRailGroupCount = 0;
5246
+ const maximumPasses = Math.max(
5247
+ 1,
5248
+ traces.reduce((sum, trace) => sum + trace.tracePath.length, 0)
5249
+ );
5250
+ for (let pass = 0; pass < maximumPasses; pass++) {
5251
+ const groups = getRailGroups(
5252
+ outputTraces,
5253
+ eligibleTraceIds,
5254
+ inputProblem,
5255
+ obstacles
5256
+ );
5257
+ let applied = null;
5258
+ for (const group of groups) {
5259
+ applied = evaluateRailGroup({
5260
+ group,
5261
+ traces: outputTraces,
5262
+ netLabelPlacements,
5263
+ obstacles,
5264
+ eligibleTraceIds
5265
+ });
5266
+ if (applied) break;
5267
+ }
5268
+ if (!applied) break;
5269
+ outputTraces = applied.traces;
5270
+ alignedRailGroupCount++;
5271
+ for (const traceId of applied.changedTraceIds) alignedTraceIds.add(traceId);
5272
+ }
5273
+ return {
5274
+ traces: outputTraces,
5275
+ alignedRailGroupCount,
5276
+ alignedTraceCount: alignedTraceIds.size
5277
+ };
5278
+ };
5279
+
4694
5280
  // lib/solvers/TraceCleanupSolver/sub-solver/findAllLShapedTurns.ts
4695
5281
  var findAllLShapedTurns = (tracePath) => {
4696
5282
  const lShapes = [];
@@ -4748,8 +5334,8 @@ var findIntersectionsWithObstacles = (p1, p2, obstacles) => {
4748
5334
  };
4749
5335
 
4750
5336
  // lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts
4751
- var EPS5 = 1e-6;
4752
- var isVertical2 = (a, b, eps = EPS5) => Math.abs(a.x - b.x) < eps;
5337
+ var EPS7 = 1e-6;
5338
+ var isVertical3 = (a, b, eps = EPS7) => Math.abs(a.x - b.x) < eps;
4753
5339
  var generateLShapeRerouteCandidates = ({
4754
5340
  lShape,
4755
5341
  rectangle,
@@ -4762,50 +5348,50 @@ var generateLShapeRerouteCandidates = ({
4762
5348
  let c2;
4763
5349
  let i1_padded = interactionPoint1;
4764
5350
  let i2_padded = interactionPoint2;
4765
- if (Math.abs(p2.x - x) < EPS5 && Math.abs(p2.y - (y + height)) < EPS5) {
5351
+ if (Math.abs(p2.x - x) < EPS7 && Math.abs(p2.y - (y + height)) < EPS7) {
4766
5352
  c2 = { x: x + width + padding, y: y - padding };
4767
- if (isVertical2(p1, p2)) {
5353
+ if (isVertical3(p1, p2)) {
4768
5354
  i1_padded = { x: interactionPoint1.x, y: interactionPoint1.y - padding };
4769
5355
  } else {
4770
5356
  i1_padded = { x: interactionPoint1.x + padding, y: interactionPoint1.y };
4771
5357
  }
4772
- if (isVertical2(p2, p3)) {
5358
+ if (isVertical3(p2, p3)) {
4773
5359
  i2_padded = { x: interactionPoint2.x, y: interactionPoint2.y - padding };
4774
5360
  } else {
4775
5361
  i2_padded = { x: interactionPoint2.x + padding, y: interactionPoint2.y };
4776
5362
  }
4777
- } else if (Math.abs(p2.x - (x + width)) < EPS5 && Math.abs(p2.y - (y + height)) < EPS5) {
5363
+ } else if (Math.abs(p2.x - (x + width)) < EPS7 && Math.abs(p2.y - (y + height)) < EPS7) {
4778
5364
  c2 = { x: x - padding, y: y - padding };
4779
- if (isVertical2(p1, p2)) {
5365
+ if (isVertical3(p1, p2)) {
4780
5366
  i1_padded = { x: interactionPoint1.x, y: interactionPoint1.y - padding };
4781
5367
  } else {
4782
5368
  i1_padded = { x: interactionPoint1.x - padding, y: interactionPoint1.y };
4783
5369
  }
4784
- if (isVertical2(p2, p3)) {
5370
+ if (isVertical3(p2, p3)) {
4785
5371
  i2_padded = { x: interactionPoint2.x, y: interactionPoint2.y - padding };
4786
5372
  } else {
4787
5373
  i2_padded = { x: interactionPoint2.x - padding, y: interactionPoint2.y };
4788
5374
  }
4789
- } else if (Math.abs(p2.x - x) < EPS5 && Math.abs(p2.y - y) < EPS5) {
5375
+ } else if (Math.abs(p2.x - x) < EPS7 && Math.abs(p2.y - y) < EPS7) {
4790
5376
  c2 = { x: x + width + padding, y: y + height + padding };
4791
- if (isVertical2(p1, p2)) {
5377
+ if (isVertical3(p1, p2)) {
4792
5378
  i1_padded = { x: interactionPoint1.x, y: interactionPoint1.y + padding };
4793
5379
  } else {
4794
5380
  i1_padded = { x: interactionPoint1.x + padding, y: interactionPoint1.y };
4795
5381
  }
4796
- if (isVertical2(p2, p3)) {
5382
+ if (isVertical3(p2, p3)) {
4797
5383
  i2_padded = { x: interactionPoint2.x, y: interactionPoint2.y + padding };
4798
5384
  } else {
4799
5385
  i2_padded = { x: interactionPoint2.x + padding, y: interactionPoint2.y };
4800
5386
  }
4801
- } else if (Math.abs(p2.x - (x + width)) < EPS5 && Math.abs(p2.y - y) < EPS5) {
5387
+ } else if (Math.abs(p2.x - (x + width)) < EPS7 && Math.abs(p2.y - y) < EPS7) {
4802
5388
  c2 = { x: x - padding, y: y + height + padding };
4803
- if (isVertical2(p1, p2)) {
5389
+ if (isVertical3(p1, p2)) {
4804
5390
  i1_padded = { x: interactionPoint1.x, y: interactionPoint1.y + padding };
4805
5391
  } else {
4806
5392
  i1_padded = { x: interactionPoint1.x - padding, y: interactionPoint1.y };
4807
5393
  }
4808
- if (isVertical2(p2, p3)) {
5394
+ if (isVertical3(p2, p3)) {
4809
5395
  i2_padded = { x: interactionPoint2.x, y: interactionPoint2.y + padding };
4810
5396
  } else {
4811
5397
  i2_padded = { x: interactionPoint2.x - padding, y: interactionPoint2.y };
@@ -5269,9 +5855,9 @@ var UntangleTraceSubsolver = class extends BaseSolver {
5269
5855
  };
5270
5856
 
5271
5857
  // lib/solvers/TraceCleanupSolver/is4PointRectangle.ts
5272
- var EPS6 = 1e-6;
5273
- var sameX = (a, b) => Math.abs(a.x - b.x) <= EPS6;
5274
- var sameY = (a, b) => Math.abs(a.y - b.y) <= EPS6;
5858
+ var EPS8 = 1e-6;
5859
+ var sameX = (a, b) => Math.abs(a.x - b.x) <= EPS8;
5860
+ var sameY = (a, b) => Math.abs(a.y - b.y) <= EPS8;
5275
5861
  var is4PointRectangle = (path) => {
5276
5862
  if (path.length !== 4) return false;
5277
5863
  const [p0, p1, p2, p3] = path;
@@ -5281,12 +5867,19 @@ var is4PointRectangle = (path) => {
5281
5867
  };
5282
5868
 
5283
5869
  // lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts
5870
+ var DEFAULT_OPERATIONS = [
5871
+ "untangling_traces",
5872
+ "minimizing_turns",
5873
+ "balancing_l_shapes"
5874
+ ];
5284
5875
  var TraceCleanupSolver = class extends BaseSolver {
5285
5876
  input;
5286
5877
  outputTraces;
5287
5878
  traceIdQueue;
5288
5879
  tracesMap;
5289
- pipelineStep = "untangling_traces";
5880
+ operations;
5881
+ operationIndex = 0;
5882
+ pipelineStep;
5290
5883
  activeTraceId = null;
5291
5884
  // New property
5292
5885
  activeSubSolver = null;
@@ -5295,6 +5888,8 @@ var TraceCleanupSolver = class extends BaseSolver {
5295
5888
  this.input = solverInput;
5296
5889
  this.outputTraces = [...solverInput.allTraces];
5297
5890
  this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]));
5891
+ this.operations = solverInput.operations ?? DEFAULT_OPERATIONS;
5892
+ this.pipelineStep = this.operations[0] ?? null;
5298
5893
  this.traceIdQueue = Array.from(
5299
5894
  solverInput.allTraces.map((e) => e.mspPairId)
5300
5895
  );
@@ -5307,13 +5902,17 @@ var TraceCleanupSolver = class extends BaseSolver {
5307
5902
  this.outputTraces = output.traces;
5308
5903
  this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]));
5309
5904
  this.activeSubSolver = null;
5310
- this.pipelineStep = "minimizing_turns";
5905
+ this._advancePipeline();
5311
5906
  } else if (this.activeSubSolver.failed) {
5312
5907
  this.activeSubSolver = null;
5313
- this.pipelineStep = "minimizing_turns";
5908
+ this._advancePipeline();
5314
5909
  }
5315
5910
  return;
5316
5911
  }
5912
+ if (!this.pipelineStep) {
5913
+ this.solved = true;
5914
+ return;
5915
+ }
5317
5916
  switch (this.pipelineStep) {
5318
5917
  case "untangling_traces":
5319
5918
  this._runUntangleTracesStep();
@@ -5324,8 +5923,17 @@ var TraceCleanupSolver = class extends BaseSolver {
5324
5923
  case "balancing_l_shapes":
5325
5924
  this._runBalanceLShapesStep();
5326
5925
  break;
5926
+ case "aligning_same_net_rails":
5927
+ this._runAlignSameNetRailsStep();
5928
+ break;
5327
5929
  }
5328
5930
  }
5931
+ _advancePipeline() {
5932
+ this.operationIndex++;
5933
+ this.pipelineStep = this.operations[this.operationIndex] ?? null;
5934
+ this.traceIdQueue = this.outputTraces.map((trace) => trace.mspPairId);
5935
+ if (this.pipelineStep) this.activeTraceId = null;
5936
+ }
5329
5937
  _runUntangleTracesStep() {
5330
5938
  this.activeSubSolver = new UntangleTraceSubsolver({
5331
5939
  ...this.input,
@@ -5334,17 +5942,14 @@ var TraceCleanupSolver = class extends BaseSolver {
5334
5942
  }
5335
5943
  _runMinimizeTurnsStep() {
5336
5944
  if (this.traceIdQueue.length === 0) {
5337
- this.pipelineStep = "balancing_l_shapes";
5338
- this.traceIdQueue = Array.from(
5339
- this.input.allTraces.map((e) => e.mspPairId)
5340
- );
5945
+ this._advancePipeline();
5341
5946
  return;
5342
5947
  }
5343
5948
  this._processTrace("minimizing_turns");
5344
5949
  }
5345
5950
  _runBalanceLShapesStep() {
5346
5951
  if (this.traceIdQueue.length === 0) {
5347
- this.solved = true;
5952
+ this._advancePipeline();
5348
5953
  return;
5349
5954
  }
5350
5955
  this._processTrace("balancing_l_shapes");
@@ -5374,6 +5979,19 @@ var TraceCleanupSolver = class extends BaseSolver {
5374
5979
  this.tracesMap.set(targetMspConnectionPairId, updatedTrace);
5375
5980
  this.outputTraces = Array.from(this.tracesMap.values());
5376
5981
  }
5982
+ _runAlignSameNetRailsStep() {
5983
+ const alignment = alignSameNetRails({
5984
+ inputProblem: this.input.inputProblem,
5985
+ traces: this.outputTraces,
5986
+ netLabelPlacements: this.input.allLabelPlacements,
5987
+ eligibleTraceIds: this.input.eligibleTraceIds ?? new Set(this.outputTraces.map((trace) => trace.mspPairId))
5988
+ });
5989
+ this.outputTraces = alignment.traces;
5990
+ this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]));
5991
+ this.stats.alignedRailGroupCount = alignment.alignedRailGroupCount;
5992
+ this.stats.alignedTraceCount = alignment.alignedTraceCount;
5993
+ this._advancePipeline();
5994
+ }
5377
5995
  getOutput() {
5378
5996
  return {
5379
5997
  traces: this.outputTraces
@@ -5452,160 +6070,6 @@ var dir = (facingDirection) => {
5452
6070
  throw new Error(`Invalid facing direction: ${facingDirection}`);
5453
6071
  };
5454
6072
 
5455
- // lib/solvers/Example28Solver/types.ts
5456
- var EPS7 = 1e-9;
5457
-
5458
- // lib/solvers/Example28Solver/segmentRunsAlongRectBoundary.ts
5459
- var segmentRunsAlongRectBoundary = (start, end, rect) => {
5460
- const isVertical4 = Math.abs(start.x - end.x) < EPS7;
5461
- const isHorizontal3 = Math.abs(start.y - end.y) < EPS7;
5462
- if (isVertical4) {
5463
- return Math.abs(start.x - rect.minX) < EPS7 || Math.abs(start.x - rect.maxX) < EPS7;
5464
- }
5465
- if (isHorizontal3) {
5466
- return Math.abs(start.y - rect.minY) < EPS7 || Math.abs(start.y - rect.maxY) < EPS7;
5467
- }
5468
- return false;
5469
- };
5470
-
5471
- // lib/solvers/Example28Solver/geometry.ts
5472
- var getPathKey = (path) => path.map((point) => `${point.x},${point.y}`).join(";");
5473
- var getPathLength = (path) => {
5474
- let length = 0;
5475
- for (let i = 0; i < path.length - 1; i++) {
5476
- length += Math.abs(path[i + 1].x - path[i].x) + Math.abs(path[i + 1].y - path[i].y);
5477
- }
5478
- return length;
5479
- };
5480
- var getDistance = (a, b) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
5481
- var isAxisAlignedSegment = (start, end) => Math.abs(start.x - end.x) < EPS7 || Math.abs(start.y - end.y) < EPS7;
5482
- var getSegmentOrientation = (start, end) => Math.abs(start.y - end.y) < EPS7 ? "horizontal" : "vertical";
5483
- var projectPointToSegment = (point, start, end) => {
5484
- if (Math.abs(start.x - end.x) < EPS7) {
5485
- return {
5486
- x: start.x,
5487
- y: Math.min(
5488
- Math.max(point.y, Math.min(start.y, end.y)),
5489
- Math.max(start.y, end.y)
5490
- )
5491
- };
5492
- }
5493
- if (Math.abs(start.y - end.y) < EPS7) {
5494
- return {
5495
- x: Math.min(
5496
- Math.max(point.x, Math.min(start.x, end.x)),
5497
- Math.max(start.x, end.x)
5498
- ),
5499
- y: start.y
5500
- };
5501
- }
5502
- return start;
5503
- };
5504
- var getPointToSegmentDistance = (point, start, end) => getDistance(point, projectPointToSegment(point, start, end));
5505
- var getSegments = (path) => {
5506
- const segments = [];
5507
- for (let i = 0; i < path.length - 1; i++) {
5508
- const start = path[i];
5509
- const end = path[i + 1];
5510
- if (!isAxisAlignedSegment(start, end)) continue;
5511
- segments.push({
5512
- start,
5513
- end,
5514
- orientation: getSegmentOrientation(start, end)
5515
- });
5516
- }
5517
- return segments;
5518
- };
5519
- var findSegmentContainingPoint = (path, point) => {
5520
- for (let i = 0; i < path.length - 1; i++) {
5521
- const start = path[i];
5522
- const end = path[i + 1];
5523
- if (!isAxisAlignedSegment(start, end)) continue;
5524
- if (getPointToSegmentDistance(point, start, end) > 1e-6) continue;
5525
- return {
5526
- index: i,
5527
- start,
5528
- end,
5529
- orientation: getSegmentOrientation(start, end)
5530
- };
5531
- }
5532
- return null;
5533
- };
5534
- var isPointWithinSegmentPrimaryRange = (point, segment) => {
5535
- if (segment.orientation === "horizontal") {
5536
- return point.x >= Math.min(segment.start.x, segment.end.x) - EPS7 && point.x <= Math.max(segment.start.x, segment.end.x) + EPS7;
5537
- }
5538
- return point.y >= Math.min(segment.start.y, segment.end.y) - EPS7 && point.y <= Math.max(segment.start.y, segment.end.y) + EPS7;
5539
- };
5540
- var findPreferredReroutedSegment = (path, originalSegmentIndex, originalSegmentCount, orientation, anchorPoint) => {
5541
- const matchingSegments = getSegments(path).filter(
5542
- (segment) => segment.orientation === orientation
5543
- );
5544
- if (matchingSegments.length === 0) return null;
5545
- const containingSegments = matchingSegments.filter(
5546
- (segment) => isPointWithinSegmentPrimaryRange(anchorPoint, segment)
5547
- );
5548
- const candidateSegments = containingSegments.length > 0 ? containingSegments : matchingSegments;
5549
- const isNearStart = originalSegmentIndex < originalSegmentCount / 2;
5550
- return isNearStart ? candidateSegments[0] : candidateSegments[candidateSegments.length - 1];
5551
- };
5552
- var projectPointToPath = (point, path) => {
5553
- let bestPoint = null;
5554
- let bestDistance = Number.POSITIVE_INFINITY;
5555
- for (let i = 0; i < path.length - 1; i++) {
5556
- const projectedPoint = projectPointToSegment(point, path[i], path[i + 1]);
5557
- const distance3 = getDistance(point, projectedPoint);
5558
- if (distance3 < bestDistance) {
5559
- bestPoint = projectedPoint;
5560
- bestDistance = distance3;
5561
- }
5562
- }
5563
- return bestPoint;
5564
- };
5565
- var segmentsIntersect = (a1, a2, b1, b2) => {
5566
- const aVertical = Math.abs(a1.x - a2.x) < EPS7;
5567
- const bVertical = Math.abs(b1.x - b2.x) < EPS7;
5568
- const between = (value, p1, p2) => value >= Math.min(p1, p2) - EPS7 && value <= Math.max(p1, p2) + EPS7;
5569
- if (aVertical && bVertical) {
5570
- if (Math.abs(a1.x - b1.x) > EPS7) return false;
5571
- const overlap = Math.min(Math.max(a1.y, a2.y), Math.max(b1.y, b2.y)) - Math.max(Math.min(a1.y, a2.y), Math.min(b1.y, b2.y));
5572
- return overlap > EPS7;
5573
- }
5574
- if (!aVertical && !bVertical) {
5575
- if (Math.abs(a1.y - b1.y) > EPS7) return false;
5576
- const overlap = Math.min(Math.max(a1.x, a2.x), Math.max(b1.x, b2.x)) - Math.max(Math.min(a1.x, a2.x), Math.min(b1.x, b2.x));
5577
- return overlap > EPS7;
5578
- }
5579
- const verticalA = aVertical ? a1 : b1;
5580
- const verticalB = aVertical ? a2 : b2;
5581
- const horizontalA = aVertical ? b1 : a1;
5582
- const horizontalB = aVertical ? b2 : a2;
5583
- return between(verticalA.x, horizontalA.x, horizontalB.x) && between(horizontalA.y, verticalA.y, verticalB.y);
5584
- };
5585
- var countPathIntersections = (pathA, pathB) => {
5586
- let count = 0;
5587
- for (let i = 0; i < pathA.length - 1; i++) {
5588
- for (let j = 0; j < pathB.length - 1; j++) {
5589
- if (segmentsIntersect(pathA[i], pathA[i + 1], pathB[j], pathB[j + 1])) {
5590
- count++;
5591
- }
5592
- }
5593
- }
5594
- return count;
5595
- };
5596
- var isPathCollidingWithChipInterior = (path, chipObstacles) => {
5597
- for (let i = 0; i < path.length - 1; i++) {
5598
- const start = path[i];
5599
- const end = path[i + 1];
5600
- for (const obstacle of chipObstacles) {
5601
- if (!segmentIntersectsRect(start, end, obstacle)) continue;
5602
- if (segmentRunsAlongRectBoundary(start, end, obstacle)) continue;
5603
- return true;
5604
- }
5605
- }
5606
- return false;
5607
- };
5608
-
5609
6073
  // lib/solvers/Example28Solver/getMovedAnchorPointForReroute.ts
5610
6074
  var getMovedAnchorPointForReroute = (anchorPoint, originalTracePath, reroutedTracePath) => {
5611
6075
  const originalSegment = findSegmentContainingPoint(
@@ -5668,7 +6132,7 @@ var doesPathRunAlongChipBoundary = (path, chipObstacles) => {
5668
6132
  const end = path[i + 1];
5669
6133
  for (const obstacle of chipObstacles) {
5670
6134
  if (!segmentRunsAlongRectBoundary(start, end, obstacle)) continue;
5671
- if (getSegmentOverlapWithRectSpan(start, end, obstacle) > EPS7) {
6135
+ if (getSegmentOverlapWithRectSpan(start, end, obstacle) > EPS6) {
5672
6136
  return true;
5673
6137
  }
5674
6138
  }
@@ -5683,14 +6147,14 @@ var getChipBoundaryOverlap = (path, chipObstacles) => {
5683
6147
  for (const obstacle of chipObstacles) {
5684
6148
  if (!segmentRunsAlongRectBoundary(start, end, obstacle)) continue;
5685
6149
  const overlap = getSegmentOverlapWithRectSpan(start, end, obstacle);
5686
- if (overlap > EPS7) total += overlap;
6150
+ if (overlap > EPS6) total += overlap;
5687
6151
  }
5688
6152
  }
5689
6153
  return total;
5690
6154
  };
5691
6155
  var getSegmentOverlapWithRectSpan = (start, end, rect) => {
5692
- const isVertical4 = Math.abs(start.x - end.x) < EPS7;
5693
- if (isVertical4) {
6156
+ const isVertical5 = Math.abs(start.x - end.x) < EPS6;
6157
+ if (isVertical5) {
5694
6158
  return Math.min(Math.max(start.y, end.y), rect.maxY) - Math.max(Math.min(start.y, end.y), rect.minY);
5695
6159
  }
5696
6160
  return Math.min(Math.max(start.x, end.x), rect.maxX) - Math.max(Math.min(start.x, end.x), rect.minX);
@@ -6453,16 +6917,16 @@ var createPortOnlyLabelConnectorTrace = ({
6453
6917
  // lib/solvers/AvailableNetOrientationSolver/constants.ts
6454
6918
  var LABEL_SEARCH_STEP = 0.05;
6455
6919
  var WICK_CLEARANCE = 1e-3;
6456
- var EPS8 = 1e-9;
6457
- var TRACE_BOUNDARY_TOLERANCE = WICK_CLEARANCE + EPS8;
6920
+ var EPS9 = 1e-9;
6921
+ var TRACE_BOUNDARY_TOLERANCE = WICK_CLEARANCE + EPS9;
6458
6922
  var CANDIDATE_SELECTED_COLOR2 = "blue";
6459
6923
  var CANDIDATE_REJECTED_COLOR2 = "red";
6460
6924
 
6461
6925
  // lib/solvers/AvailableNetOrientationSolver/geometry.ts
6462
6926
  var isYOrientation = (orientation) => orientation === "y+" || orientation === "y-";
6463
6927
  var isXOrientation = (orientation) => orientation === "x+" || orientation === "x-";
6464
- var rectsOverlap = (a, b) => Math.min(a.maxX, b.maxX) - Math.max(a.minX, b.minX) > EPS8 && Math.min(a.maxY, b.maxY) - Math.max(a.minY, b.minY) > EPS8;
6465
- var rangesOverlap = (minA, maxA, minB, maxB) => Math.min(maxA, maxB) - Math.max(minA, minB) > EPS8;
6928
+ var rectsOverlap2 = (a, b) => Math.min(a.maxX, b.maxX) - Math.max(a.minX, b.minX) > EPS9 && Math.min(a.maxY, b.maxY) - Math.max(a.minY, b.minY) > EPS9;
6929
+ var rangesOverlap = (minA, maxA, minB, maxB) => Math.min(maxA, maxB) - Math.max(minA, minB) > EPS9;
6466
6930
  var traceCrossesBoundsInterior = (bounds, traceMap) => {
6467
6931
  for (const trace of Object.values(traceMap)) {
6468
6932
  const points = trace.tracePath;
@@ -6485,7 +6949,7 @@ var segmentCrossesBoundsInterior = (p1, p2, bounds) => {
6485
6949
  return false;
6486
6950
  }
6487
6951
  if (sameX2(p1, p2)) {
6488
- if (p1.x <= interiorBounds.minX + EPS8 || p1.x >= interiorBounds.maxX - EPS8) {
6952
+ if (p1.x <= interiorBounds.minX + EPS9 || p1.x >= interiorBounds.maxX - EPS9) {
6489
6953
  return false;
6490
6954
  }
6491
6955
  return rangesOverlap(
@@ -6496,7 +6960,7 @@ var segmentCrossesBoundsInterior = (p1, p2, bounds) => {
6496
6960
  );
6497
6961
  }
6498
6962
  if (sameY2(p1, p2)) {
6499
- if (p1.y <= interiorBounds.minY + EPS8 || p1.y >= interiorBounds.maxY - EPS8) {
6963
+ if (p1.y <= interiorBounds.minY + EPS9 || p1.y >= interiorBounds.maxY - EPS9) {
6500
6964
  return false;
6501
6965
  }
6502
6966
  return rangesOverlap(
@@ -6535,7 +6999,7 @@ var getConnectorTracePath = (source, target, orientation) => simplifyOrthogonalP
6535
6999
  );
6536
7000
  var simplifyOrthogonalPath = (path) => {
6537
7001
  const deduped = path.filter(
6538
- (point, index) => index === 0 || !pointsEqual(point, path[index - 1])
7002
+ (point, index) => index === 0 || !pointsEqual2(point, path[index - 1])
6539
7003
  );
6540
7004
  if (deduped.length < 3) return deduped;
6541
7005
  const simplified = [deduped[0]];
@@ -6551,9 +7015,9 @@ var simplifyOrthogonalPath = (path) => {
6551
7015
  simplified.push(deduped[deduped.length - 1]);
6552
7016
  return simplified;
6553
7017
  };
6554
- var pointsEqual = (a, b) => sameX2(a, b) && sameY2(a, b);
6555
- var sameX2 = (a, b) => Math.abs(a.x - b.x) <= EPS8;
6556
- var sameY2 = (a, b) => Math.abs(a.y - b.y) <= EPS8;
7018
+ var pointsEqual2 = (a, b) => sameX2(a, b) && sameY2(a, b);
7019
+ var sameX2 = (a, b) => Math.abs(a.x - b.x) <= EPS9;
7020
+ var sameY2 = (a, b) => Math.abs(a.y - b.y) <= EPS9;
6557
7021
  var getMaxSearchDistance = (inputProblem) => {
6558
7022
  const maxChipWidth = Math.max(
6559
7023
  ...inputProblem.chips.map((chip) => chip.width),
@@ -6856,7 +7320,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
6856
7320
  );
6857
7321
  const maxSearchDistance = this.getSearchDistanceLimit(label, orientation);
6858
7322
  const maxOutwardDistance = outwardDirection.x === 0 && outwardDirection.y === 0 ? 0 : this.maxSearchDistance;
6859
- for (let outwardDistance = 0; outwardDistance <= maxOutwardDistance + EPS8; outwardDistance += LABEL_SEARCH_STEP) {
7323
+ for (let outwardDistance = 0; outwardDistance <= maxOutwardDistance + EPS9; outwardDistance += LABEL_SEARCH_STEP) {
6860
7324
  const baseAnchor = {
6861
7325
  x: initialBaseAnchor.x + outwardDirection.x * outwardDistance,
6862
7326
  y: initialBaseAnchor.y + outwardDirection.y * outwardDistance
@@ -6929,7 +7393,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
6929
7393
  outwardDistance,
6930
7394
  phase = "shift"
6931
7395
  } = params;
6932
- for (let distance3 = LABEL_SEARCH_STEP; distance3 <= maxSearchDistance + EPS8; distance3 += LABEL_SEARCH_STEP) {
7396
+ for (let distance3 = LABEL_SEARCH_STEP; distance3 <= maxSearchDistance + EPS9; distance3 += LABEL_SEARCH_STEP) {
6933
7397
  const anchorPoint = {
6934
7398
  x: baseAnchor.x + direction.x * distance3,
6935
7399
  y: baseAnchor.y + direction.y * distance3
@@ -7166,9 +7630,9 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7166
7630
  const { anchorPoint, orientation } = candidate;
7167
7631
  const chipBounds = chip.bounds;
7168
7632
  if (isYOrientation(orientation)) {
7169
- return anchorPoint.x < chipBounds.minX - EPS8 || anchorPoint.x > chipBounds.maxX + EPS8;
7633
+ return anchorPoint.x < chipBounds.minX - EPS9 || anchorPoint.x > chipBounds.maxX + EPS9;
7170
7634
  }
7171
- return anchorPoint.y < chipBounds.minY - EPS8 || anchorPoint.y > chipBounds.maxY + EPS8;
7635
+ return anchorPoint.y < chipBounds.minY - EPS9 || anchorPoint.y > chipBounds.maxY + EPS9;
7172
7636
  });
7173
7637
  }
7174
7638
  getBoundsStatus(candidateBoundsCheck) {
@@ -7235,15 +7699,15 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7235
7699
  if (i === labelIndex) continue;
7236
7700
  const label = this.outputNetLabelPlacements[i];
7237
7701
  const otherBounds = getRectBounds(label.center, label.width, label.height);
7238
- if (rectsOverlap(bounds, otherBounds)) return true;
7702
+ if (rectsOverlap2(bounds, otherBounds)) return true;
7239
7703
  }
7240
7704
  return false;
7241
7705
  }
7242
7706
  sharesChipBoundary(bounds) {
7243
7707
  for (const chip of this.chipObstacleSpatialIndex.chips) {
7244
7708
  const chipBounds = chip.bounds;
7245
- const adjacentToVerticalSide = Math.abs(bounds.minX - chipBounds.maxX) <= WICK_CLEARANCE + EPS8 || Math.abs(bounds.maxX - chipBounds.minX) <= WICK_CLEARANCE + EPS8;
7246
- const adjacentToHorizontalSide = Math.abs(bounds.minY - chipBounds.maxY) <= WICK_CLEARANCE + EPS8 || Math.abs(bounds.maxY - chipBounds.minY) <= WICK_CLEARANCE + EPS8;
7709
+ const adjacentToVerticalSide = Math.abs(bounds.minX - chipBounds.maxX) <= WICK_CLEARANCE + EPS9 || Math.abs(bounds.maxX - chipBounds.minX) <= WICK_CLEARANCE + EPS9;
7710
+ const adjacentToHorizontalSide = Math.abs(bounds.minY - chipBounds.maxY) <= WICK_CLEARANCE + EPS9 || Math.abs(bounds.maxY - chipBounds.minY) <= WICK_CLEARANCE + EPS9;
7247
7711
  if (adjacentToVerticalSide && rangesOverlap(
7248
7712
  bounds.minY,
7249
7713
  bounds.maxY,
@@ -7291,7 +7755,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7291
7755
  let nearestDistance = Number.POSITIVE_INFINITY;
7292
7756
  for (const chip of this.chipObstacleSpatialIndex.chips) {
7293
7757
  const bounds = chip.bounds;
7294
- if (point.x < bounds.minX - EPS8 || point.x > bounds.maxX + EPS8 || point.y < bounds.minY - EPS8 || point.y > bounds.maxY + EPS8) {
7758
+ if (point.x < bounds.minX - EPS9 || point.x > bounds.maxX + EPS9 || point.y < bounds.minY - EPS9 || point.y > bounds.maxY + EPS9) {
7295
7759
  continue;
7296
7760
  }
7297
7761
  for (const [side, distance3] of getSideDistances(point, bounds)) {
@@ -7308,8 +7772,8 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7308
7772
  let nearestDistanceSq = Number.POSITIVE_INFINITY;
7309
7773
  for (const chip of this.chipObstacleSpatialIndex.chips) {
7310
7774
  const bounds = chip.bounds;
7311
- const dx = point.x < bounds.minX - EPS8 ? bounds.minX - point.x : point.x > bounds.maxX + EPS8 ? point.x - bounds.maxX : 0;
7312
- const dy = point.y < bounds.minY - EPS8 ? bounds.minY - point.y : point.y > bounds.maxY + EPS8 ? point.y - bounds.maxY : 0;
7775
+ const dx = point.x < bounds.minX - EPS9 ? bounds.minX - point.x : point.x > bounds.maxX + EPS9 ? point.x - bounds.maxX : 0;
7776
+ const dy = point.y < bounds.minY - EPS9 ? bounds.minY - point.y : point.y > bounds.maxY + EPS9 ? point.y - bounds.maxY : 0;
7313
7777
  if (dx === 0 && dy === 0) continue;
7314
7778
  const distanceSq = dx ** 2 + dy ** 2;
7315
7779
  if (distanceSq >= nearestDistanceSq) continue;
@@ -7347,39 +7811,6 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7347
7811
  }
7348
7812
  };
7349
7813
 
7350
- // lib/solvers/RailNetLabelCornerPlacementSolver/geometry.ts
7351
- var EPS9 = 1e-6;
7352
- var isTraceLine = (trace) => getUniquePinCount(trace.pinIds) >= 2;
7353
- var rectsOverlap2 = (a, b) => Math.min(a.maxX, b.maxX) - Math.max(a.minX, b.minX) > EPS9 && Math.min(a.maxY, b.maxY) - Math.max(a.minY, b.minY) > EPS9;
7354
- var getTraceCorners = (path) => {
7355
- const corners = [];
7356
- for (let i = 1; i < path.length - 1; i++) {
7357
- const previousPoint = path[i - 1];
7358
- const cornerPoint = path[i];
7359
- const nextPoint = path[i + 1];
7360
- if (isTraceCorner(previousPoint, cornerPoint, nextPoint)) {
7361
- corners.push(cornerPoint);
7362
- }
7363
- }
7364
- return corners;
7365
- };
7366
- var tracePathContainsPoint = (path, point) => {
7367
- for (let i = 0; i < path.length - 1; i++) {
7368
- if (isPointOnSegment(point, path[i], path[i + 1])) return true;
7369
- }
7370
- return false;
7371
- };
7372
- var getDistance2 = (a, b) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
7373
- var getUniquePinCount = (pinIds) => new Set(pinIds).size;
7374
- var isTraceCorner = (a, b, c) => getSegmentOrientation2(a, b) !== getSegmentOrientation2(b, c);
7375
- var isPointOnSegment = (point, start, end) => {
7376
- if (getSegmentOrientation2(start, end) === "horizontal") {
7377
- return Math.abs(point.y - start.y) <= EPS9 && point.x >= Math.min(start.x, end.x) - EPS9 && point.x <= Math.max(start.x, end.x) + EPS9;
7378
- }
7379
- return Math.abs(point.x - start.x) <= EPS9 && point.y >= Math.min(start.y, end.y) - EPS9 && point.y <= Math.max(start.y, end.y) + EPS9;
7380
- };
7381
- var getSegmentOrientation2 = (a, b) => Math.abs(a.y - b.y) <= EPS9 ? "horizontal" : "vertical";
7382
-
7383
7814
  // lib/solvers/RailNetLabelCornerPlacementSolver/visualize.ts
7384
7815
  var CANDIDATE_SELECTED_COLOR3 = "blue";
7385
7816
  var CANDIDATE_REJECTED_COLOR3 = "red";
@@ -7602,20 +8033,20 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
7602
8033
  }
7603
8034
  intersectsAnyChip(bounds) {
7604
8035
  return this.inputProblem.chips.some(
7605
- (chip) => rectsOverlap2(bounds, getRectBounds(chip.center, chip.width, chip.height))
8036
+ (chip) => rectsOverlap(bounds, getRectBounds(chip.center, chip.width, chip.height))
7606
8037
  );
7607
8038
  }
7608
8039
  intersectsAnyOtherNetLabel(bounds, labelIndex) {
7609
8040
  return this.outputNetLabelPlacements.some((label, index) => {
7610
8041
  if (index === labelIndex) return false;
7611
- return rectsOverlap2(
8042
+ return rectsOverlap(
7612
8043
  bounds,
7613
8044
  getRectBounds(label.center, label.width, label.height)
7614
8045
  );
7615
8046
  });
7616
8047
  }
7617
8048
  getCornerCandidatesForLabel(label) {
7618
- const isVertical4 = label.orientation === "y+" || label.orientation === "y-";
8049
+ const isVertical5 = label.orientation === "y+" || label.orientation === "y-";
7619
8050
  const candidates = [];
7620
8051
  const seenCornerKeys = /* @__PURE__ */ new Set();
7621
8052
  for (const trace of this.getTraceLinesForLabel(label)) {
@@ -7626,14 +8057,14 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
7626
8057
  if (seenCornerKeys.has(key)) continue;
7627
8058
  seenCornerKeys.add(key);
7628
8059
  const pinAligned = pins.some((pin) => {
7629
- if (isVertical4) return Math.abs(pin.x - anchorPoint.x) < EPS9;
7630
- return Math.abs(pin.y - anchorPoint.y) < EPS9;
8060
+ if (isVertical5) return Math.abs(pin.x - anchorPoint.x) < EPS5;
8061
+ return Math.abs(pin.y - anchorPoint.y) < EPS5;
7631
8062
  });
7632
8063
  if (!pinAligned) continue;
7633
8064
  candidates.push({
7634
8065
  anchorPoint,
7635
8066
  traceId: trace.mspPairId,
7636
- distance: getDistance2(anchorPoint, label.anchorPoint),
8067
+ distance: getDistance(anchorPoint, label.anchorPoint),
7637
8068
  pinAligned
7638
8069
  });
7639
8070
  }
@@ -7808,7 +8239,7 @@ var segmentCrossesBoundsInterior2 = (start, end, bounds) => {
7808
8239
  if (interior.minX >= interior.maxX || interior.minY >= interior.maxY) {
7809
8240
  return false;
7810
8241
  }
7811
- if (isVertical3(start, end)) {
8242
+ if (isVertical4(start, end)) {
7812
8243
  if (start.x <= interior.minX + EPS10 || start.x >= interior.maxX - EPS10) {
7813
8244
  return false;
7814
8245
  }
@@ -7819,7 +8250,7 @@ var segmentCrossesBoundsInterior2 = (start, end, bounds) => {
7819
8250
  interior.maxY
7820
8251
  );
7821
8252
  }
7822
- if (isHorizontal2(start, end)) {
8253
+ if (isHorizontal3(start, end)) {
7823
8254
  if (start.y <= interior.minY + EPS10 || start.y >= interior.maxY - EPS10) {
7824
8255
  return false;
7825
8256
  }
@@ -7833,20 +8264,20 @@ var segmentCrossesBoundsInterior2 = (start, end, bounds) => {
7833
8264
  return false;
7834
8265
  };
7835
8266
  var isPointOnSegment2 = (point, start, end) => {
7836
- if (isHorizontal2(start, end)) {
8267
+ if (isHorizontal3(start, end)) {
7837
8268
  return Math.abs(point.y - start.y) <= EPS10 && point.x >= Math.min(start.x, end.x) - EPS10 && point.x <= Math.max(start.x, end.x) + EPS10;
7838
8269
  }
7839
- if (isVertical3(start, end)) {
8270
+ if (isVertical4(start, end)) {
7840
8271
  return Math.abs(point.x - start.x) <= EPS10 && point.y >= Math.min(start.y, end.y) - EPS10 && point.y <= Math.max(start.y, end.y) + EPS10;
7841
8272
  }
7842
8273
  return false;
7843
8274
  };
7844
8275
  var getSegmentDirection = (start, end) => ({
7845
- x: isVertical3(start, end) ? 0 : Math.sign(end.x - start.x),
7846
- y: isHorizontal2(start, end) ? 0 : Math.sign(end.y - start.y)
8276
+ x: isVertical4(start, end) ? 0 : Math.sign(end.x - start.x),
8277
+ y: isHorizontal3(start, end) ? 0 : Math.sign(end.y - start.y)
7847
8278
  });
7848
- var isHorizontal2 = (start, end) => Math.abs(start.y - end.y) <= EPS10;
7849
- var isVertical3 = (start, end) => Math.abs(start.x - end.x) <= EPS10;
8279
+ var isHorizontal3 = (start, end) => Math.abs(start.y - end.y) <= EPS10;
8280
+ var isVertical4 = (start, end) => Math.abs(start.x - end.x) <= EPS10;
7850
8281
  var rangesOverlap2 = (minA, maxA, minB, maxB) => Math.min(maxA, maxB) - Math.max(minA, minB) > EPS10;
7851
8282
  var getUniquePinCount2 = (pinIds) => new Set(pinIds).size;
7852
8283
 
@@ -8855,11 +9286,11 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
8855
9286
  for (let si = 0; si < pts.length - 1; si++) {
8856
9287
  const segStart = pts[si];
8857
9288
  const segEnd = pts[si + 1];
8858
- const isHorizontal3 = Math.abs(segStart.y - segEnd.y) < SEGMENT_PARALLEL_EPS;
8859
- const isVertical4 = Math.abs(segStart.x - segEnd.x) < SEGMENT_PARALLEL_EPS;
8860
- if (!isHorizontal3 && !isVertical4) continue;
9289
+ const isHorizontal4 = Math.abs(segStart.y - segEnd.y) < SEGMENT_PARALLEL_EPS;
9290
+ const isVertical5 = Math.abs(segStart.x - segEnd.x) < SEGMENT_PARALLEL_EPS;
9291
+ if (!isHorizontal4 && !isVertical5) continue;
8861
9292
  let perpendicularOrientations;
8862
- if (isHorizontal3) {
9293
+ if (isHorizontal4) {
8863
9294
  perpendicularOrientations = ["y+", "y-"];
8864
9295
  } else {
8865
9296
  perpendicularOrientations = ["x+", "x-"];
@@ -9072,7 +9503,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9072
9503
  availableNetOrientationSolver;
9073
9504
  railNetLabelCornerPlacementSolver;
9074
9505
  traceAnchoredNetLabelOverlapSolver;
9506
+ preAlignmentNetLabelTraceCollisionSolver;
9075
9507
  netLabelTraceCollisionSolver;
9508
+ traceCleanupSolver2;
9076
9509
  netLabelNetLabelCollisionSolver;
9077
9510
  startTimeOfPhase;
9078
9511
  endTimeOfPhase;
@@ -9257,7 +9690,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9257
9690
  ]
9258
9691
  ),
9259
9692
  definePipelineStep(
9260
- "netLabelTraceCollisionSolver",
9693
+ "preAlignmentNetLabelTraceCollisionSolver",
9261
9694
  NetLabelTraceCollisionSolver,
9262
9695
  (instance) => [
9263
9696
  {
@@ -9267,6 +9700,41 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9267
9700
  }
9268
9701
  ]
9269
9702
  ),
9703
+ definePipelineStep(
9704
+ "traceCleanupSolver2",
9705
+ TraceCleanupSolver,
9706
+ (instance) => {
9707
+ const collisionOutput = instance.preAlignmentNetLabelTraceCollisionSolver.getOutput();
9708
+ const labelMergingOutput = instance.traceLabelOverlapAvoidanceSolver.labelMergingSolver.getOutput();
9709
+ return [
9710
+ {
9711
+ inputProblem: instance.inputProblem,
9712
+ allTraces: collisionOutput.traces,
9713
+ allLabelPlacements: collisionOutput.netLabelPlacements,
9714
+ mergedLabelNetIdMap: labelMergingOutput.mergedLabelNetIdMap,
9715
+ paddingBuffer: 0.1,
9716
+ operations: ["aligning_same_net_rails"],
9717
+ eligibleTraceIds: new Set(
9718
+ instance.traceCleanupSolver.getOutput().traces.map((trace) => trace.mspPairId)
9719
+ )
9720
+ }
9721
+ ];
9722
+ }
9723
+ ),
9724
+ definePipelineStep(
9725
+ "netLabelTraceCollisionSolver",
9726
+ NetLabelTraceCollisionSolver,
9727
+ (instance) => {
9728
+ const previousCollisionOutput = instance.preAlignmentNetLabelTraceCollisionSolver.getOutput();
9729
+ return [
9730
+ {
9731
+ inputProblem: instance.inputProblem,
9732
+ traces: instance.traceCleanupSolver2.getOutput().traces,
9733
+ netLabelPlacements: previousCollisionOutput.netLabelPlacements
9734
+ }
9735
+ ];
9736
+ }
9737
+ ),
9270
9738
  definePipelineStep(
9271
9739
  "netLabelNetLabelCollisionSolver",
9272
9740
  NetLabelNetLabelCollisionSolver,