@tscircuit/schematic-trace-solver 0.0.96 → 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 (45) hide show
  1. package/dist/index.d.ts +9 -0
  2. package/dist/index.js +986 -479
  3. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +55 -14
  4. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts +31 -0
  5. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +45 -1
  6. package/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +55 -13
  7. package/lib/solvers/TraceCleanupSolver/alignSameNetRails.ts +66 -0
  8. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/evaluateRailGroup.ts +124 -0
  9. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry.ts +108 -0
  10. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getComponentSideRailSegments.ts +121 -0
  11. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getRailGroups.ts +114 -0
  12. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/moveRailSegments.ts +27 -0
  13. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/preservesLabelAnchors.ts +30 -0
  14. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/scoreRailAlignment.ts +67 -0
  15. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/types.ts +34 -0
  16. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +1 -60
  17. package/lib/utils/doesPathCoincideWithTraces.ts +61 -0
  18. package/package.json +1 -1
  19. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +1 -1
  20. package/tests/bug-reports/bug-report-20260707T141025Z/__snapshots__/bug-report-20260707T141025Z.snap.svg +1 -1
  21. package/tests/examples/__snapshots__/example02.snap.svg +1 -1
  22. package/tests/examples/__snapshots__/example14.snap.svg +1 -1
  23. package/tests/examples/__snapshots__/example18.snap.svg +1 -1
  24. package/tests/examples/__snapshots__/example46.snap.svg +1 -1
  25. package/tests/repros/__snapshots__/repro-cc2340r5.snap.svg +1 -1
  26. package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +178 -0
  27. package/tests/repros/__snapshots__/repro-rp2040-zero-crystal-fallback-netlabels.snap.svg +54 -0
  28. package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +1 -1
  29. package/tests/repros/assets/repro-rp2040-gamepad-trace-alignment.input.json +237 -0
  30. package/tests/repros/assets/repro-rp2040-zero-crystal-fallback-netlabels.input.json +121 -0
  31. package/tests/repros/repro-rp2040-gamepad-trace-alignment.test.ts +38 -0
  32. package/tests/repros/repro-rp2040-zero-crystal-fallback-netlabels.test.ts +16 -0
  33. package/tests/solvers/SchematicTraceSingleLineSolver2/segment-intersects-rect-interior.test.ts +15 -0
  34. package/tests/solvers/SchematicTraceSingleLineSolver2/segment-overlaps-rect-boundary-boundary.test.ts +18 -0
  35. package/tests/solvers/SchematicTraceSingleLineSolver2/segment-overlaps-rect-boundary-interior.test.ts +18 -0
  36. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-component-scope.test.ts +38 -0
  37. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-eligible-traces.test.ts +28 -0
  38. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-horizontal.test.ts +62 -0
  39. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-label-anchor.test.ts +36 -0
  40. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-label-junction.test.ts +42 -0
  41. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-obstacle.test.ts +28 -0
  42. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-pipeline-label-connector.test.ts +52 -0
  43. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-vertical.test.ts +25 -0
  44. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-visible-length.test.ts +40 -0
  45. package/tests/solvers/TraceCleanupSolver/fixtures/alignSameNetRails.ts +89 -0
package/dist/index.js CHANGED
@@ -585,54 +585,150 @@ import "graphics-debug";
585
585
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts
586
586
  import { calculateElbow as calculateElbow2 } from "calculate-elbow";
587
587
 
588
- // lib/solvers/GuidelinesSolver/getInputChipBounds.ts
589
- function getInputChipBounds(chip) {
590
- const halfWidth = chip.width / 2;
591
- const halfHeight = chip.height / 2;
588
+ // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry.ts
589
+ var NET_LABEL_HORIZONTAL_WIDTH = 0.45;
590
+ var NET_LABEL_HORIZONTAL_HEIGHT = 0.2;
591
+ function getDimsForOrientation(params) {
592
+ const { orientation, netLabelWidth, netLabelHeight } = params;
593
+ const horizWidth = typeof netLabelWidth === "number" ? netLabelWidth : NET_LABEL_HORIZONTAL_WIDTH;
594
+ const horizHeight = typeof netLabelHeight === "number" ? netLabelHeight : NET_LABEL_HORIZONTAL_HEIGHT;
595
+ if (orientation === "y+" || orientation === "y-") {
596
+ return {
597
+ // Rotated: horizontal length = netLabelHeight, vertical length = netLabelWidth
598
+ width: horizHeight,
599
+ height: horizWidth
600
+ };
601
+ }
592
602
  return {
593
- minX: chip.center.x - halfWidth,
594
- maxX: chip.center.x + halfWidth,
595
- minY: chip.center.y - halfHeight,
596
- maxY: chip.center.y + halfHeight
603
+ width: horizWidth,
604
+ height: horizHeight
597
605
  };
598
606
  }
599
-
600
- // lib/utils/textBoxBounds.ts
601
- function getTextBoxBounds(textBox, padding = {}) {
607
+ function getCenterFromAnchor(anchor, orientation, width, height) {
608
+ switch (orientation) {
609
+ case "x+":
610
+ return { x: anchor.x + width / 2, y: anchor.y };
611
+ case "x-":
612
+ return { x: anchor.x - width / 2, y: anchor.y };
613
+ case "y+":
614
+ return { x: anchor.x, y: anchor.y + height / 2 };
615
+ case "y-":
616
+ return { x: anchor.x, y: anchor.y - height / 2 };
617
+ }
618
+ }
619
+ function getRectBounds(center, w, h) {
602
620
  return {
603
- minX: textBox.center.x - textBox.width / 2 - (padding.minX ?? 0),
604
- minY: textBox.center.y - textBox.height / 2 - (padding.minY ?? 0),
605
- maxX: textBox.center.x + textBox.width / 2 + (padding.maxX ?? 0),
606
- maxY: textBox.center.y + textBox.height / 2 + (padding.maxY ?? 0)
621
+ minX: center.x - w / 2,
622
+ minY: center.y - h / 2,
623
+ maxX: center.x + w / 2,
624
+ maxY: center.y + h / 2
607
625
  };
608
626
  }
609
- function boundsOverlap(a, b, eps = 1e-9) {
610
- return a.minX < b.maxX - eps && a.maxX > b.minX + eps && a.minY < b.maxY - eps && a.maxY > b.minY + eps;
627
+
628
+ // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/calculateDirectShortPath.ts
629
+ import { calculateElbow } from "calculate-elbow";
630
+ var MAX_SHORT_TRACE_DISTANCE = 0.15;
631
+ var SHORT_TRACE_OVERSHOOT = MAX_SHORT_TRACE_DISTANCE / 7.5;
632
+ var FALLBACK_ELBOW_MAX_OVERSHOOT = 0.2;
633
+ function segmentDirection(from, to) {
634
+ if (to.x > from.x) return "x+";
635
+ if (to.x < from.x) return "x-";
636
+ if (to.y > from.y) return "y+";
637
+ if (to.y < from.y) return "y-";
638
+ return null;
611
639
  }
612
- function rectIntersectsAnyTextBox(bounds, inputProblem) {
613
- for (const textBox of inputProblem.textBoxes ?? []) {
614
- if (boundsOverlap(bounds, getTextBoxBounds(textBox))) return true;
640
+ function pathMatchesPinDirections({
641
+ path,
642
+ pin1,
643
+ pin2
644
+ }) {
645
+ const firstDirection = segmentDirection(path[0], path[1]);
646
+ const lastDirection = segmentDirection(
647
+ path[path.length - 1],
648
+ path[path.length - 2]
649
+ );
650
+ return firstDirection === pin1._facingDirection && lastDirection === pin2._facingDirection;
651
+ }
652
+ function calculateShortOrthogonalRoute(pin1, pin2) {
653
+ if (pin1.x === pin2.x || pin1.y === pin2.y) return null;
654
+ const firstDir = pin1._facingDirection;
655
+ const lastDir = pin2._facingDirection;
656
+ const start = { x: pin1.x, y: pin1.y };
657
+ const end = { x: pin2.x, y: pin2.y };
658
+ let path = null;
659
+ if (firstDir?.startsWith("y") && lastDir?.startsWith("x")) {
660
+ let yOffset = -SHORT_TRACE_OVERSHOOT;
661
+ if (firstDir === "y+") {
662
+ yOffset = SHORT_TRACE_OVERSHOOT;
663
+ }
664
+ const routeY = start.y + yOffset;
665
+ const routeX = (start.x + end.x) / 2;
666
+ path = [
667
+ start,
668
+ { x: start.x, y: routeY },
669
+ { x: routeX, y: routeY },
670
+ { x: routeX, y: end.y },
671
+ end
672
+ ];
673
+ } else if (firstDir?.startsWith("x") && lastDir?.startsWith("y")) {
674
+ let xOffset = -SHORT_TRACE_OVERSHOOT;
675
+ if (firstDir === "x+") {
676
+ xOffset = SHORT_TRACE_OVERSHOOT;
677
+ }
678
+ const routeX = start.x + xOffset;
679
+ const routeY = (start.y + end.y) / 2;
680
+ path = [
681
+ start,
682
+ { x: routeX, y: start.y },
683
+ { x: routeX, y: routeY },
684
+ { x: end.x, y: routeY },
685
+ end
686
+ ];
615
687
  }
616
- return false;
688
+ if (!path) return null;
689
+ if (pathMatchesPinDirections({ path, pin1, pin2 })) {
690
+ return path;
691
+ }
692
+ return null;
693
+ }
694
+ function calculateDirectShortPath(pin1, pin2) {
695
+ const routingDistance = Math.abs(pin1.x - pin2.x) + Math.abs(pin1.y - pin2.y);
696
+ if (routingDistance > MAX_SHORT_TRACE_DISTANCE) return null;
697
+ const start = { x: pin1.x, y: pin1.y };
698
+ const end = { x: pin2.x, y: pin2.y };
699
+ const orthogonalRoute = calculateShortOrthogonalRoute(pin1, pin2);
700
+ if (orthogonalRoute) return orthogonalRoute;
701
+ let candidatePaths = [];
702
+ if (pin1.x !== pin2.x && pin1.y !== pin2.y) {
703
+ candidatePaths = [
704
+ [start, { x: pin2.x, y: pin1.y }, end],
705
+ [start, { x: pin1.x, y: pin2.y }, end]
706
+ ];
707
+ } else {
708
+ candidatePaths = [[start, end]];
709
+ }
710
+ for (const path of candidatePaths) {
711
+ if (pathMatchesPinDirections({ path, pin1, pin2 })) return path;
712
+ }
713
+ return calculateElbow(
714
+ {
715
+ x: pin1.x,
716
+ y: pin1.y,
717
+ facingDirection: pin1._facingDirection
718
+ },
719
+ {
720
+ x: pin2.x,
721
+ y: pin2.y,
722
+ facingDirection: pin2._facingDirection
723
+ },
724
+ {
725
+ overshoot: Math.min(
726
+ FALLBACK_ELBOW_MAX_OVERSHOOT,
727
+ Math.max(SHORT_TRACE_OVERSHOOT, routingDistance / 4)
728
+ )
729
+ }
730
+ );
617
731
  }
618
-
619
- // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts
620
- var chipToRect = (chip) => {
621
- const b = getInputChipBounds(chip);
622
- return { kind: "chip", chipId: chip.chipId, ...b };
623
- };
624
- var getObstacleRects = (problem, opts = {}) => {
625
- const chipRects = problem.chips.map(chipToRect);
626
- const textBoxRects = (problem.textBoxes ?? []).map((textBox) => {
627
- const b = getTextBoxBounds(textBox, opts.textBoxPadding);
628
- return {
629
- kind: "text_box",
630
- textBox,
631
- ...b
632
- };
633
- });
634
- return [...chipRects, ...textBoxRects];
635
- };
636
732
 
637
733
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts
638
734
  var EPS = 1e-9;
@@ -658,6 +754,21 @@ var segmentIntersectsRect = (a, b, r, eps = EPS) => {
658
754
  return overlap > eps;
659
755
  }
660
756
  };
757
+ var segmentOverlapsRectBoundary = (a, b, r, eps = EPS) => {
758
+ if (isVertical(a, b, eps)) {
759
+ const onVerticalBoundary = Math.abs(a.x - r.minX) <= eps || Math.abs(a.x - r.maxX) <= eps;
760
+ if (!onVerticalBoundary) return false;
761
+ const overlap = Math.min(Math.max(a.y, b.y), r.maxY) - Math.max(Math.min(a.y, b.y), r.minY);
762
+ return overlap > eps;
763
+ }
764
+ if (isHorizontal(a, b, eps)) {
765
+ const onHorizontalBoundary = Math.abs(a.y - r.minY) <= eps || Math.abs(a.y - r.maxY) <= eps;
766
+ if (!onHorizontalBoundary) return false;
767
+ const overlap = Math.min(Math.max(a.x, b.x), r.maxX) - Math.max(Math.min(a.x, b.x), r.minX);
768
+ return overlap > eps;
769
+ }
770
+ return false;
771
+ };
661
772
  var findFirstCollision = (pts, rects, opts = {}) => {
662
773
  for (let i = 0; i < pts.length - 1; i++) {
663
774
  const a = pts[i];
@@ -812,151 +923,55 @@ var pathKey = (pts, decimals = 6) => {
812
923
  return key;
813
924
  };
814
925
 
815
- // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry.ts
816
- var NET_LABEL_HORIZONTAL_WIDTH = 0.45;
817
- var NET_LABEL_HORIZONTAL_HEIGHT = 0.2;
818
- function getDimsForOrientation(params) {
819
- const { orientation, netLabelWidth, netLabelHeight } = params;
820
- const horizWidth = typeof netLabelWidth === "number" ? netLabelWidth : NET_LABEL_HORIZONTAL_WIDTH;
821
- const horizHeight = typeof netLabelHeight === "number" ? netLabelHeight : NET_LABEL_HORIZONTAL_HEIGHT;
822
- if (orientation === "y+" || orientation === "y-") {
823
- return {
824
- // Rotated: horizontal length = netLabelHeight, vertical length = netLabelWidth
825
- width: horizHeight,
826
- height: horizWidth
827
- };
828
- }
926
+ // lib/solvers/GuidelinesSolver/getInputChipBounds.ts
927
+ function getInputChipBounds(chip) {
928
+ const halfWidth = chip.width / 2;
929
+ const halfHeight = chip.height / 2;
829
930
  return {
830
- width: horizWidth,
831
- height: horizHeight
931
+ minX: chip.center.x - halfWidth,
932
+ maxX: chip.center.x + halfWidth,
933
+ minY: chip.center.y - halfHeight,
934
+ maxY: chip.center.y + halfHeight
832
935
  };
833
936
  }
834
- function getCenterFromAnchor(anchor, orientation, width, height) {
835
- switch (orientation) {
836
- case "x+":
837
- return { x: anchor.x + width / 2, y: anchor.y };
838
- case "x-":
839
- return { x: anchor.x - width / 2, y: anchor.y };
840
- case "y+":
841
- return { x: anchor.x, y: anchor.y + height / 2 };
842
- case "y-":
843
- return { x: anchor.x, y: anchor.y - height / 2 };
844
- }
845
- }
846
- function getRectBounds(center, w, h) {
937
+
938
+ // lib/utils/textBoxBounds.ts
939
+ function getTextBoxBounds(textBox, padding = {}) {
847
940
  return {
848
- minX: center.x - w / 2,
849
- minY: center.y - h / 2,
850
- maxX: center.x + w / 2,
851
- maxY: center.y + h / 2
941
+ minX: textBox.center.x - textBox.width / 2 - (padding.minX ?? 0),
942
+ minY: textBox.center.y - textBox.height / 2 - (padding.minY ?? 0),
943
+ maxX: textBox.center.x + textBox.width / 2 + (padding.maxX ?? 0),
944
+ maxY: textBox.center.y + textBox.height / 2 + (padding.maxY ?? 0)
852
945
  };
853
946
  }
854
-
855
- // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/calculateDirectShortPath.ts
856
- import { calculateElbow } from "calculate-elbow";
857
- var MAX_SHORT_TRACE_DISTANCE = 0.15;
858
- var SHORT_TRACE_OVERSHOOT = MAX_SHORT_TRACE_DISTANCE / 7.5;
859
- var FALLBACK_ELBOW_MAX_OVERSHOOT = 0.2;
860
- function segmentDirection(from, to) {
861
- if (to.x > from.x) return "x+";
862
- if (to.x < from.x) return "x-";
863
- if (to.y > from.y) return "y+";
864
- if (to.y < from.y) return "y-";
865
- return null;
866
- }
867
- function pathMatchesPinDirections({
868
- path,
869
- pin1,
870
- pin2
871
- }) {
872
- const firstDirection = segmentDirection(path[0], path[1]);
873
- const lastDirection = segmentDirection(
874
- path[path.length - 1],
875
- path[path.length - 2]
876
- );
877
- return firstDirection === pin1._facingDirection && lastDirection === pin2._facingDirection;
878
- }
879
- function calculateShortOrthogonalRoute(pin1, pin2) {
880
- if (pin1.x === pin2.x || pin1.y === pin2.y) return null;
881
- const firstDir = pin1._facingDirection;
882
- const lastDir = pin2._facingDirection;
883
- const start = { x: pin1.x, y: pin1.y };
884
- const end = { x: pin2.x, y: pin2.y };
885
- let path = null;
886
- if (firstDir?.startsWith("y") && lastDir?.startsWith("x")) {
887
- let yOffset = -SHORT_TRACE_OVERSHOOT;
888
- if (firstDir === "y+") {
889
- yOffset = SHORT_TRACE_OVERSHOOT;
890
- }
891
- const routeY = start.y + yOffset;
892
- const routeX = (start.x + end.x) / 2;
893
- path = [
894
- start,
895
- { x: start.x, y: routeY },
896
- { x: routeX, y: routeY },
897
- { x: routeX, y: end.y },
898
- end
899
- ];
900
- } else if (firstDir?.startsWith("x") && lastDir?.startsWith("y")) {
901
- let xOffset = -SHORT_TRACE_OVERSHOOT;
902
- if (firstDir === "x+") {
903
- xOffset = SHORT_TRACE_OVERSHOOT;
904
- }
905
- const routeX = start.x + xOffset;
906
- const routeY = (start.y + end.y) / 2;
907
- path = [
908
- start,
909
- { x: routeX, y: start.y },
910
- { x: routeX, y: routeY },
911
- { x: end.x, y: routeY },
912
- end
913
- ];
914
- }
915
- if (!path) return null;
916
- if (pathMatchesPinDirections({ path, pin1, pin2 })) {
917
- return path;
918
- }
919
- return null;
947
+ function boundsOverlap(a, b, eps = 1e-9) {
948
+ return a.minX < b.maxX - eps && a.maxX > b.minX + eps && a.minY < b.maxY - eps && a.maxY > b.minY + eps;
920
949
  }
921
- function calculateDirectShortPath(pin1, pin2) {
922
- const routingDistance = Math.abs(pin1.x - pin2.x) + Math.abs(pin1.y - pin2.y);
923
- if (routingDistance > MAX_SHORT_TRACE_DISTANCE) return null;
924
- const start = { x: pin1.x, y: pin1.y };
925
- const end = { x: pin2.x, y: pin2.y };
926
- const orthogonalRoute = calculateShortOrthogonalRoute(pin1, pin2);
927
- if (orthogonalRoute) return orthogonalRoute;
928
- let candidatePaths = [];
929
- if (pin1.x !== pin2.x && pin1.y !== pin2.y) {
930
- candidatePaths = [
931
- [start, { x: pin2.x, y: pin1.y }, end],
932
- [start, { x: pin1.x, y: pin2.y }, end]
933
- ];
934
- } else {
935
- candidatePaths = [[start, end]];
936
- }
937
- for (const path of candidatePaths) {
938
- if (pathMatchesPinDirections({ path, pin1, pin2 })) return path;
950
+ function rectIntersectsAnyTextBox(bounds, inputProblem) {
951
+ for (const textBox of inputProblem.textBoxes ?? []) {
952
+ if (boundsOverlap(bounds, getTextBoxBounds(textBox))) return true;
939
953
  }
940
- return calculateElbow(
941
- {
942
- x: pin1.x,
943
- y: pin1.y,
944
- facingDirection: pin1._facingDirection
945
- },
946
- {
947
- x: pin2.x,
948
- y: pin2.y,
949
- facingDirection: pin2._facingDirection
950
- },
951
- {
952
- overshoot: Math.min(
953
- FALLBACK_ELBOW_MAX_OVERSHOOT,
954
- Math.max(SHORT_TRACE_OVERSHOOT, routingDistance / 4)
955
- )
956
- }
957
- );
954
+ return false;
958
955
  }
959
956
 
957
+ // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts
958
+ var chipToRect = (chip) => {
959
+ const b = getInputChipBounds(chip);
960
+ return { kind: "chip", chipId: chip.chipId, ...b };
961
+ };
962
+ var getObstacleRects = (problem, opts = {}) => {
963
+ const chipRects = problem.chips.map(chipToRect);
964
+ const textBoxRects = (problem.textBoxes ?? []).map((textBox) => {
965
+ const b = getTextBoxBounds(textBox, opts.textBoxPadding);
966
+ return {
967
+ kind: "text_box",
968
+ textBox,
969
+ ...b
970
+ };
971
+ });
972
+ return [...chipRects, ...textBoxRects];
973
+ };
974
+
960
975
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts
961
976
  var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
962
977
  pins;
@@ -1143,17 +1158,41 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1143
1158
  const collision = findFirstCollision(path, this.obstacles, {
1144
1159
  excludeRectsForSegment: (segIndex2) => {
1145
1160
  const lastSegIndex2 = path.length - 2;
1161
+ const excludedRects = /* @__PURE__ */ new Set();
1146
1162
  if (segIndex2 === 0 || segIndex2 === lastSegIndex2) {
1147
- return this.textObstacles;
1163
+ for (const textObstacle of this.textObstacles) {
1164
+ excludedRects.add(textObstacle);
1165
+ }
1166
+ }
1167
+ const segmentStart = path[segIndex2];
1168
+ const segmentEnd = path[segIndex2 + 1];
1169
+ const endpointEpsilon = 1e-9;
1170
+ const segmentTouchesPin = (pin) => [segmentStart, segmentEnd].some(
1171
+ (point) => Math.abs(point.x - pin.x) <= endpointEpsilon && Math.abs(point.y - pin.y) <= endpointEpsilon
1172
+ );
1173
+ for (const pin of this.pins) {
1174
+ if (!segmentTouchesPin(pin)) continue;
1175
+ const endpointChipObstacle = this.obstacles.find(
1176
+ (obstacle) => obstacle.kind === "chip" && obstacle.chipId === pin.chipId
1177
+ );
1178
+ if (endpointChipObstacle && segmentOverlapsRectBoundary(
1179
+ segmentStart,
1180
+ segmentEnd,
1181
+ endpointChipObstacle
1182
+ )) {
1183
+ excludedRects.add(endpointChipObstacle);
1184
+ }
1148
1185
  }
1149
1186
  const adjacentEndpointSegIndex = segIndex2 === 1 ? 2 : segIndex2 === lastSegIndex2 - 1 ? lastSegIndex2 - 2 : null;
1150
1187
  if (adjacentEndpointSegIndex !== null && adjacentEndpointSegIndex >= 0 && adjacentEndpointSegIndex < lastSegIndex2 && this.isSegmentOutsidePinBand(
1151
1188
  path[adjacentEndpointSegIndex],
1152
1189
  path[adjacentEndpointSegIndex + 1]
1153
1190
  )) {
1154
- return this.endpointTextObstacles;
1191
+ for (const textObstacle of this.endpointTextObstacles) {
1192
+ excludedRects.add(textObstacle);
1193
+ }
1155
1194
  }
1156
- return /* @__PURE__ */ new Set();
1195
+ return excludedRects;
1157
1196
  }
1158
1197
  });
1159
1198
  if (!collision) {
@@ -1401,12 +1440,12 @@ var applyJogToTerminalSegment = ({
1401
1440
  if (si !== 0 && si !== pts.length - 2) return;
1402
1441
  const start = pts[si];
1403
1442
  const end = pts[si + 1];
1404
- const isVertical4 = Math.abs(start.x - end.x) < EPS11;
1405
- const isHorizontal3 = Math.abs(start.y - end.y) < EPS11;
1406
- if (!isVertical4 && !isHorizontal3) return;
1407
- 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;
1408
1447
  if (pts.length === 2) {
1409
- if (isVertical4) {
1448
+ if (isVertical5) {
1410
1449
  const jogYNearStart = start.y + segDir * JOG_SIZE;
1411
1450
  const jogYNearEnd = end.y - segDir * JOG_SIZE;
1412
1451
  pts.splice(
@@ -1432,7 +1471,7 @@ var applyJogToTerminalSegment = ({
1432
1471
  return;
1433
1472
  }
1434
1473
  if (si === 0) {
1435
- if (isVertical4) {
1474
+ if (isVertical5) {
1436
1475
  const jogY = start.y + segDir * JOG_SIZE;
1437
1476
  pts.splice(
1438
1477
  1,
@@ -1452,7 +1491,7 @@ var applyJogToTerminalSegment = ({
1452
1491
  );
1453
1492
  }
1454
1493
  } else {
1455
- if (isVertical4) {
1494
+ if (isVertical5) {
1456
1495
  const jogY = end.y - segDir * JOG_SIZE;
1457
1496
  pts.splice(
1458
1497
  si,
@@ -1548,10 +1587,10 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1548
1587
  } else {
1549
1588
  const start = pts[si];
1550
1589
  const end = pts[si + 1];
1551
- const isVertical4 = Math.abs(start.x - end.x) < EPS4;
1552
- const isHorizontal3 = Math.abs(start.y - end.y) < EPS4;
1553
- if (!isVertical4 && !isHorizontal3) continue;
1554
- 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) {
1555
1594
  start.x += offset;
1556
1595
  end.x += offset;
1557
1596
  } else {
@@ -1580,11 +1619,11 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1580
1619
  }) {
1581
1620
  const blockingCollisions = this.getBlockingCollisions({ group, offset });
1582
1621
  if (blockingCollisions.length === 0) return offset;
1583
- const crossesThroughObstacle = (candidateOffset) => blockingCollisions.some(({ start, isVertical: isVertical4, obstacle }) => {
1622
+ const crossesThroughObstacle = (candidateOffset) => blockingCollisions.some(({ start, isVertical: isVertical5, obstacle }) => {
1584
1623
  let orig;
1585
1624
  let min;
1586
1625
  let max;
1587
- if (isVertical4) {
1626
+ if (isVertical5) {
1588
1627
  orig = start.x;
1589
1628
  min = obstacle.minX;
1590
1629
  max = obstacle.maxX;
@@ -1596,8 +1635,8 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1596
1635
  const next = orig + candidateOffset;
1597
1636
  return orig <= min + EPS4 && next >= max - EPS4 || orig >= max - EPS4 && next <= min + EPS4;
1598
1637
  });
1599
- const edges = blockingCollisions.map(({ start, isVertical: isVertical4, obstacle }) => {
1600
- if (isVertical4) {
1638
+ const edges = blockingCollisions.map(({ start, isVertical: isVertical5, obstacle }) => {
1639
+ if (isVertical5) {
1601
1640
  return {
1602
1641
  coord: start.x,
1603
1642
  lowEdge: obstacle.minX,
@@ -1631,10 +1670,10 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1631
1670
  const start = trace.tracePath[traceSegmentIndex];
1632
1671
  const end = trace.tracePath[traceSegmentIndex + 1];
1633
1672
  if (!start || !end) continue;
1634
- const isVertical4 = Math.abs(start.x - end.x) < EPS4;
1635
- const isHorizontal3 = Math.abs(start.y - end.y) < EPS4;
1636
- if (!isVertical4 && !isHorizontal3) continue;
1637
- 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 ? [
1638
1677
  { ...start, x: start.x + offset },
1639
1678
  { ...end, x: end.x + offset }
1640
1679
  ] : [
@@ -1645,7 +1684,7 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1645
1684
  if (collision) {
1646
1685
  blockingCollisions.push({
1647
1686
  start,
1648
- isVertical: isVertical4,
1687
+ isVertical: isVertical5,
1649
1688
  obstacle: collision.rect
1650
1689
  });
1651
1690
  }
@@ -1828,9 +1867,9 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1828
1867
  for (let i = 0; i < tracePath.length - 1; i++) {
1829
1868
  const p1 = tracePath[i];
1830
1869
  const p2 = tracePath[i + 1];
1831
- const isHorizontal3 = Math.abs(p1.y - p2.y) < EPS11;
1832
- const isVertical4 = Math.abs(p1.x - p2.x) < EPS11;
1833
- 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) {
1834
1873
  return { mspPairId, tracePath, i, p1, p2 };
1835
1874
  }
1836
1875
  }
@@ -3439,19 +3478,18 @@ var generateRerouteCandidates = ({
3439
3478
  return [...fourPointCandidates, ...snipReconnectCandidates];
3440
3479
  };
3441
3480
 
3442
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts
3443
- var MAX_TRIES = 5;
3481
+ // lib/utils/doesPathCoincideWithTraces.ts
3444
3482
  var COINCIDENT_EPS = 2e-3;
3445
3483
  var doesPathCoincideWithTraces = (path, traces) => {
3446
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;
3447
3485
  for (let i = 0; i < path.length - 1; i++) {
3448
3486
  const pathSegStart = path[i];
3449
3487
  const pathSegEnd = path[i + 1];
3450
- const isVertical4 = Math.abs(pathSegStart.x - pathSegEnd.x) < COINCIDENT_EPS;
3451
- const isHorizontal3 = Math.abs(pathSegStart.y - pathSegEnd.y) < COINCIDENT_EPS;
3452
- if (!isVertical4 && !isHorizontal3) continue;
3453
- const crossAxis = isVertical4 ? "x" : "y";
3454
- 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";
3455
3493
  for (const trace of traces) {
3456
3494
  for (let j = 0; j < trace.tracePath.length - 1; j++) {
3457
3495
  const traceSegStart = trace.tracePath[j];
@@ -3473,6 +3511,9 @@ var doesPathCoincideWithTraces = (path, traces) => {
3473
3511
  }
3474
3512
  return false;
3475
3513
  };
3514
+
3515
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts
3516
+ var MAX_TRIES = 5;
3476
3517
  var SingleOverlapSolver = class extends BaseSolver {
3477
3518
  queuedCandidatePaths;
3478
3519
  solvedTracePath = null;
@@ -4629,26 +4670,610 @@ var balanceZShapes = ({
4629
4670
  if (Math.abs(len1Original - len2Original) < 1e-3) {
4630
4671
  continue;
4631
4672
  }
4632
- if (isHVHZShape) {
4633
- const idealX = (p1.x + p4.x) / 2;
4634
- p2New = { x: idealX, y: p2.y };
4635
- p3New = { x: idealX, y: p3.y };
4636
- } else {
4637
- const idealY = (p1.y + p4.y) / 2;
4638
- p2New = { x: p2.x, y: idealY };
4639
- p3New = { x: p3.x, y: idealY };
4640
- }
4641
- const collides = segmentIntersectsAnyRect(p1, p2New, combinedObstacles) || segmentIntersectsAnyRect(p2New, p3New, combinedObstacles) || segmentIntersectsAnyRect(p3New, p4, combinedObstacles) || segmentIntersectsAnyRect(p1, p2New, labelBounds) || segmentIntersectsAnyRect(p2New, p3New, labelBounds) || segmentIntersectsAnyRect(p3New, p4, labelBounds);
4642
- if (!collides) {
4643
- newPath[i + 1] = p2New;
4644
- newPath[i + 2] = p3New;
4645
- i = 0;
4673
+ if (isHVHZShape) {
4674
+ const idealX = (p1.x + p4.x) / 2;
4675
+ p2New = { x: idealX, y: p2.y };
4676
+ p3New = { x: idealX, y: p3.y };
4677
+ } else {
4678
+ const idealY = (p1.y + p4.y) / 2;
4679
+ p2New = { x: p2.x, y: idealY };
4680
+ p3New = { x: p3.x, y: idealY };
4681
+ }
4682
+ const collides = segmentIntersectsAnyRect(p1, p2New, combinedObstacles) || segmentIntersectsAnyRect(p2New, p3New, combinedObstacles) || segmentIntersectsAnyRect(p3New, p4, combinedObstacles) || segmentIntersectsAnyRect(p1, p2New, labelBounds) || segmentIntersectsAnyRect(p2New, p3New, labelBounds) || segmentIntersectsAnyRect(p3New, p4, labelBounds);
4683
+ if (!collides) {
4684
+ newPath[i + 1] = p2New;
4685
+ newPath[i + 2] = p3New;
4686
+ i = 0;
4687
+ }
4688
+ }
4689
+ const finalSimplifiedPath = simplifyPath(newPath);
4690
+ return {
4691
+ ...targetTrace,
4692
+ tracePath: finalSimplifiedPath
4693
+ };
4694
+ };
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;
4646
5267
  }
5268
+ if (!applied) break;
5269
+ outputTraces = applied.traces;
5270
+ alignedRailGroupCount++;
5271
+ for (const traceId of applied.changedTraceIds) alignedTraceIds.add(traceId);
4647
5272
  }
4648
- const finalSimplifiedPath = simplifyPath(newPath);
4649
5273
  return {
4650
- ...targetTrace,
4651
- tracePath: finalSimplifiedPath
5274
+ traces: outputTraces,
5275
+ alignedRailGroupCount,
5276
+ alignedTraceCount: alignedTraceIds.size
4652
5277
  };
4653
5278
  };
4654
5279
 
@@ -4709,8 +5334,8 @@ var findIntersectionsWithObstacles = (p1, p2, obstacles) => {
4709
5334
  };
4710
5335
 
4711
5336
  // lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts
4712
- var EPS5 = 1e-6;
4713
- 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;
4714
5339
  var generateLShapeRerouteCandidates = ({
4715
5340
  lShape,
4716
5341
  rectangle,
@@ -4723,50 +5348,50 @@ var generateLShapeRerouteCandidates = ({
4723
5348
  let c2;
4724
5349
  let i1_padded = interactionPoint1;
4725
5350
  let i2_padded = interactionPoint2;
4726
- 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) {
4727
5352
  c2 = { x: x + width + padding, y: y - padding };
4728
- if (isVertical2(p1, p2)) {
5353
+ if (isVertical3(p1, p2)) {
4729
5354
  i1_padded = { x: interactionPoint1.x, y: interactionPoint1.y - padding };
4730
5355
  } else {
4731
5356
  i1_padded = { x: interactionPoint1.x + padding, y: interactionPoint1.y };
4732
5357
  }
4733
- if (isVertical2(p2, p3)) {
5358
+ if (isVertical3(p2, p3)) {
4734
5359
  i2_padded = { x: interactionPoint2.x, y: interactionPoint2.y - padding };
4735
5360
  } else {
4736
5361
  i2_padded = { x: interactionPoint2.x + padding, y: interactionPoint2.y };
4737
5362
  }
4738
- } 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) {
4739
5364
  c2 = { x: x - padding, y: y - padding };
4740
- if (isVertical2(p1, p2)) {
5365
+ if (isVertical3(p1, p2)) {
4741
5366
  i1_padded = { x: interactionPoint1.x, y: interactionPoint1.y - padding };
4742
5367
  } else {
4743
5368
  i1_padded = { x: interactionPoint1.x - padding, y: interactionPoint1.y };
4744
5369
  }
4745
- if (isVertical2(p2, p3)) {
5370
+ if (isVertical3(p2, p3)) {
4746
5371
  i2_padded = { x: interactionPoint2.x, y: interactionPoint2.y - padding };
4747
5372
  } else {
4748
5373
  i2_padded = { x: interactionPoint2.x - padding, y: interactionPoint2.y };
4749
5374
  }
4750
- } 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) {
4751
5376
  c2 = { x: x + width + padding, y: y + height + padding };
4752
- if (isVertical2(p1, p2)) {
5377
+ if (isVertical3(p1, p2)) {
4753
5378
  i1_padded = { x: interactionPoint1.x, y: interactionPoint1.y + padding };
4754
5379
  } else {
4755
5380
  i1_padded = { x: interactionPoint1.x + padding, y: interactionPoint1.y };
4756
5381
  }
4757
- if (isVertical2(p2, p3)) {
5382
+ if (isVertical3(p2, p3)) {
4758
5383
  i2_padded = { x: interactionPoint2.x, y: interactionPoint2.y + padding };
4759
5384
  } else {
4760
5385
  i2_padded = { x: interactionPoint2.x + padding, y: interactionPoint2.y };
4761
5386
  }
4762
- } 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) {
4763
5388
  c2 = { x: x - padding, y: y + height + padding };
4764
- if (isVertical2(p1, p2)) {
5389
+ if (isVertical3(p1, p2)) {
4765
5390
  i1_padded = { x: interactionPoint1.x, y: interactionPoint1.y + padding };
4766
5391
  } else {
4767
5392
  i1_padded = { x: interactionPoint1.x - padding, y: interactionPoint1.y };
4768
5393
  }
4769
- if (isVertical2(p2, p3)) {
5394
+ if (isVertical3(p2, p3)) {
4770
5395
  i2_padded = { x: interactionPoint2.x, y: interactionPoint2.y + padding };
4771
5396
  } else {
4772
5397
  i2_padded = { x: interactionPoint2.x - padding, y: interactionPoint2.y };
@@ -5230,9 +5855,9 @@ var UntangleTraceSubsolver = class extends BaseSolver {
5230
5855
  };
5231
5856
 
5232
5857
  // lib/solvers/TraceCleanupSolver/is4PointRectangle.ts
5233
- var EPS6 = 1e-6;
5234
- var sameX = (a, b) => Math.abs(a.x - b.x) <= EPS6;
5235
- 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;
5236
5861
  var is4PointRectangle = (path) => {
5237
5862
  if (path.length !== 4) return false;
5238
5863
  const [p0, p1, p2, p3] = path;
@@ -5242,12 +5867,19 @@ var is4PointRectangle = (path) => {
5242
5867
  };
5243
5868
 
5244
5869
  // lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts
5870
+ var DEFAULT_OPERATIONS = [
5871
+ "untangling_traces",
5872
+ "minimizing_turns",
5873
+ "balancing_l_shapes"
5874
+ ];
5245
5875
  var TraceCleanupSolver = class extends BaseSolver {
5246
5876
  input;
5247
5877
  outputTraces;
5248
5878
  traceIdQueue;
5249
5879
  tracesMap;
5250
- pipelineStep = "untangling_traces";
5880
+ operations;
5881
+ operationIndex = 0;
5882
+ pipelineStep;
5251
5883
  activeTraceId = null;
5252
5884
  // New property
5253
5885
  activeSubSolver = null;
@@ -5256,6 +5888,8 @@ var TraceCleanupSolver = class extends BaseSolver {
5256
5888
  this.input = solverInput;
5257
5889
  this.outputTraces = [...solverInput.allTraces];
5258
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;
5259
5893
  this.traceIdQueue = Array.from(
5260
5894
  solverInput.allTraces.map((e) => e.mspPairId)
5261
5895
  );
@@ -5268,13 +5902,17 @@ var TraceCleanupSolver = class extends BaseSolver {
5268
5902
  this.outputTraces = output.traces;
5269
5903
  this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]));
5270
5904
  this.activeSubSolver = null;
5271
- this.pipelineStep = "minimizing_turns";
5905
+ this._advancePipeline();
5272
5906
  } else if (this.activeSubSolver.failed) {
5273
5907
  this.activeSubSolver = null;
5274
- this.pipelineStep = "minimizing_turns";
5908
+ this._advancePipeline();
5275
5909
  }
5276
5910
  return;
5277
5911
  }
5912
+ if (!this.pipelineStep) {
5913
+ this.solved = true;
5914
+ return;
5915
+ }
5278
5916
  switch (this.pipelineStep) {
5279
5917
  case "untangling_traces":
5280
5918
  this._runUntangleTracesStep();
@@ -5285,8 +5923,17 @@ var TraceCleanupSolver = class extends BaseSolver {
5285
5923
  case "balancing_l_shapes":
5286
5924
  this._runBalanceLShapesStep();
5287
5925
  break;
5926
+ case "aligning_same_net_rails":
5927
+ this._runAlignSameNetRailsStep();
5928
+ break;
5288
5929
  }
5289
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
+ }
5290
5937
  _runUntangleTracesStep() {
5291
5938
  this.activeSubSolver = new UntangleTraceSubsolver({
5292
5939
  ...this.input,
@@ -5295,17 +5942,14 @@ var TraceCleanupSolver = class extends BaseSolver {
5295
5942
  }
5296
5943
  _runMinimizeTurnsStep() {
5297
5944
  if (this.traceIdQueue.length === 0) {
5298
- this.pipelineStep = "balancing_l_shapes";
5299
- this.traceIdQueue = Array.from(
5300
- this.input.allTraces.map((e) => e.mspPairId)
5301
- );
5945
+ this._advancePipeline();
5302
5946
  return;
5303
5947
  }
5304
5948
  this._processTrace("minimizing_turns");
5305
5949
  }
5306
5950
  _runBalanceLShapesStep() {
5307
5951
  if (this.traceIdQueue.length === 0) {
5308
- this.solved = true;
5952
+ this._advancePipeline();
5309
5953
  return;
5310
5954
  }
5311
5955
  this._processTrace("balancing_l_shapes");
@@ -5335,6 +5979,19 @@ var TraceCleanupSolver = class extends BaseSolver {
5335
5979
  this.tracesMap.set(targetMspConnectionPairId, updatedTrace);
5336
5980
  this.outputTraces = Array.from(this.tracesMap.values());
5337
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
+ }
5338
5995
  getOutput() {
5339
5996
  return {
5340
5997
  traces: this.outputTraces
@@ -5413,160 +6070,6 @@ var dir = (facingDirection) => {
5413
6070
  throw new Error(`Invalid facing direction: ${facingDirection}`);
5414
6071
  };
5415
6072
 
5416
- // lib/solvers/Example28Solver/types.ts
5417
- var EPS7 = 1e-9;
5418
-
5419
- // lib/solvers/Example28Solver/segmentRunsAlongRectBoundary.ts
5420
- var segmentRunsAlongRectBoundary = (start, end, rect) => {
5421
- const isVertical4 = Math.abs(start.x - end.x) < EPS7;
5422
- const isHorizontal3 = Math.abs(start.y - end.y) < EPS7;
5423
- if (isVertical4) {
5424
- return Math.abs(start.x - rect.minX) < EPS7 || Math.abs(start.x - rect.maxX) < EPS7;
5425
- }
5426
- if (isHorizontal3) {
5427
- return Math.abs(start.y - rect.minY) < EPS7 || Math.abs(start.y - rect.maxY) < EPS7;
5428
- }
5429
- return false;
5430
- };
5431
-
5432
- // lib/solvers/Example28Solver/geometry.ts
5433
- var getPathKey = (path) => path.map((point) => `${point.x},${point.y}`).join(";");
5434
- var getPathLength = (path) => {
5435
- let length = 0;
5436
- for (let i = 0; i < path.length - 1; i++) {
5437
- length += Math.abs(path[i + 1].x - path[i].x) + Math.abs(path[i + 1].y - path[i].y);
5438
- }
5439
- return length;
5440
- };
5441
- var getDistance = (a, b) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
5442
- var isAxisAlignedSegment = (start, end) => Math.abs(start.x - end.x) < EPS7 || Math.abs(start.y - end.y) < EPS7;
5443
- var getSegmentOrientation = (start, end) => Math.abs(start.y - end.y) < EPS7 ? "horizontal" : "vertical";
5444
- var projectPointToSegment = (point, start, end) => {
5445
- if (Math.abs(start.x - end.x) < EPS7) {
5446
- return {
5447
- x: start.x,
5448
- y: Math.min(
5449
- Math.max(point.y, Math.min(start.y, end.y)),
5450
- Math.max(start.y, end.y)
5451
- )
5452
- };
5453
- }
5454
- if (Math.abs(start.y - end.y) < EPS7) {
5455
- return {
5456
- x: Math.min(
5457
- Math.max(point.x, Math.min(start.x, end.x)),
5458
- Math.max(start.x, end.x)
5459
- ),
5460
- y: start.y
5461
- };
5462
- }
5463
- return start;
5464
- };
5465
- var getPointToSegmentDistance = (point, start, end) => getDistance(point, projectPointToSegment(point, start, end));
5466
- var getSegments = (path) => {
5467
- const segments = [];
5468
- for (let i = 0; i < path.length - 1; i++) {
5469
- const start = path[i];
5470
- const end = path[i + 1];
5471
- if (!isAxisAlignedSegment(start, end)) continue;
5472
- segments.push({
5473
- start,
5474
- end,
5475
- orientation: getSegmentOrientation(start, end)
5476
- });
5477
- }
5478
- return segments;
5479
- };
5480
- var findSegmentContainingPoint = (path, point) => {
5481
- for (let i = 0; i < path.length - 1; i++) {
5482
- const start = path[i];
5483
- const end = path[i + 1];
5484
- if (!isAxisAlignedSegment(start, end)) continue;
5485
- if (getPointToSegmentDistance(point, start, end) > 1e-6) continue;
5486
- return {
5487
- index: i,
5488
- start,
5489
- end,
5490
- orientation: getSegmentOrientation(start, end)
5491
- };
5492
- }
5493
- return null;
5494
- };
5495
- var isPointWithinSegmentPrimaryRange = (point, segment) => {
5496
- if (segment.orientation === "horizontal") {
5497
- return point.x >= Math.min(segment.start.x, segment.end.x) - EPS7 && point.x <= Math.max(segment.start.x, segment.end.x) + EPS7;
5498
- }
5499
- return point.y >= Math.min(segment.start.y, segment.end.y) - EPS7 && point.y <= Math.max(segment.start.y, segment.end.y) + EPS7;
5500
- };
5501
- var findPreferredReroutedSegment = (path, originalSegmentIndex, originalSegmentCount, orientation, anchorPoint) => {
5502
- const matchingSegments = getSegments(path).filter(
5503
- (segment) => segment.orientation === orientation
5504
- );
5505
- if (matchingSegments.length === 0) return null;
5506
- const containingSegments = matchingSegments.filter(
5507
- (segment) => isPointWithinSegmentPrimaryRange(anchorPoint, segment)
5508
- );
5509
- const candidateSegments = containingSegments.length > 0 ? containingSegments : matchingSegments;
5510
- const isNearStart = originalSegmentIndex < originalSegmentCount / 2;
5511
- return isNearStart ? candidateSegments[0] : candidateSegments[candidateSegments.length - 1];
5512
- };
5513
- var projectPointToPath = (point, path) => {
5514
- let bestPoint = null;
5515
- let bestDistance = Number.POSITIVE_INFINITY;
5516
- for (let i = 0; i < path.length - 1; i++) {
5517
- const projectedPoint = projectPointToSegment(point, path[i], path[i + 1]);
5518
- const distance3 = getDistance(point, projectedPoint);
5519
- if (distance3 < bestDistance) {
5520
- bestPoint = projectedPoint;
5521
- bestDistance = distance3;
5522
- }
5523
- }
5524
- return bestPoint;
5525
- };
5526
- var segmentsIntersect = (a1, a2, b1, b2) => {
5527
- const aVertical = Math.abs(a1.x - a2.x) < EPS7;
5528
- const bVertical = Math.abs(b1.x - b2.x) < EPS7;
5529
- const between = (value, p1, p2) => value >= Math.min(p1, p2) - EPS7 && value <= Math.max(p1, p2) + EPS7;
5530
- if (aVertical && bVertical) {
5531
- if (Math.abs(a1.x - b1.x) > EPS7) return false;
5532
- 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));
5533
- return overlap > EPS7;
5534
- }
5535
- if (!aVertical && !bVertical) {
5536
- if (Math.abs(a1.y - b1.y) > EPS7) return false;
5537
- 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));
5538
- return overlap > EPS7;
5539
- }
5540
- const verticalA = aVertical ? a1 : b1;
5541
- const verticalB = aVertical ? a2 : b2;
5542
- const horizontalA = aVertical ? b1 : a1;
5543
- const horizontalB = aVertical ? b2 : a2;
5544
- return between(verticalA.x, horizontalA.x, horizontalB.x) && between(horizontalA.y, verticalA.y, verticalB.y);
5545
- };
5546
- var countPathIntersections = (pathA, pathB) => {
5547
- let count = 0;
5548
- for (let i = 0; i < pathA.length - 1; i++) {
5549
- for (let j = 0; j < pathB.length - 1; j++) {
5550
- if (segmentsIntersect(pathA[i], pathA[i + 1], pathB[j], pathB[j + 1])) {
5551
- count++;
5552
- }
5553
- }
5554
- }
5555
- return count;
5556
- };
5557
- var isPathCollidingWithChipInterior = (path, chipObstacles) => {
5558
- for (let i = 0; i < path.length - 1; i++) {
5559
- const start = path[i];
5560
- const end = path[i + 1];
5561
- for (const obstacle of chipObstacles) {
5562
- if (!segmentIntersectsRect(start, end, obstacle)) continue;
5563
- if (segmentRunsAlongRectBoundary(start, end, obstacle)) continue;
5564
- return true;
5565
- }
5566
- }
5567
- return false;
5568
- };
5569
-
5570
6073
  // lib/solvers/Example28Solver/getMovedAnchorPointForReroute.ts
5571
6074
  var getMovedAnchorPointForReroute = (anchorPoint, originalTracePath, reroutedTracePath) => {
5572
6075
  const originalSegment = findSegmentContainingPoint(
@@ -5629,7 +6132,7 @@ var doesPathRunAlongChipBoundary = (path, chipObstacles) => {
5629
6132
  const end = path[i + 1];
5630
6133
  for (const obstacle of chipObstacles) {
5631
6134
  if (!segmentRunsAlongRectBoundary(start, end, obstacle)) continue;
5632
- if (getSegmentOverlapWithRectSpan(start, end, obstacle) > EPS7) {
6135
+ if (getSegmentOverlapWithRectSpan(start, end, obstacle) > EPS6) {
5633
6136
  return true;
5634
6137
  }
5635
6138
  }
@@ -5644,14 +6147,14 @@ var getChipBoundaryOverlap = (path, chipObstacles) => {
5644
6147
  for (const obstacle of chipObstacles) {
5645
6148
  if (!segmentRunsAlongRectBoundary(start, end, obstacle)) continue;
5646
6149
  const overlap = getSegmentOverlapWithRectSpan(start, end, obstacle);
5647
- if (overlap > EPS7) total += overlap;
6150
+ if (overlap > EPS6) total += overlap;
5648
6151
  }
5649
6152
  }
5650
6153
  return total;
5651
6154
  };
5652
6155
  var getSegmentOverlapWithRectSpan = (start, end, rect) => {
5653
- const isVertical4 = Math.abs(start.x - end.x) < EPS7;
5654
- if (isVertical4) {
6156
+ const isVertical5 = Math.abs(start.x - end.x) < EPS6;
6157
+ if (isVertical5) {
5655
6158
  return Math.min(Math.max(start.y, end.y), rect.maxY) - Math.max(Math.min(start.y, end.y), rect.minY);
5656
6159
  }
5657
6160
  return Math.min(Math.max(start.x, end.x), rect.maxX) - Math.max(Math.min(start.x, end.x), rect.minX);
@@ -6414,16 +6917,16 @@ var createPortOnlyLabelConnectorTrace = ({
6414
6917
  // lib/solvers/AvailableNetOrientationSolver/constants.ts
6415
6918
  var LABEL_SEARCH_STEP = 0.05;
6416
6919
  var WICK_CLEARANCE = 1e-3;
6417
- var EPS8 = 1e-9;
6418
- var TRACE_BOUNDARY_TOLERANCE = WICK_CLEARANCE + EPS8;
6920
+ var EPS9 = 1e-9;
6921
+ var TRACE_BOUNDARY_TOLERANCE = WICK_CLEARANCE + EPS9;
6419
6922
  var CANDIDATE_SELECTED_COLOR2 = "blue";
6420
6923
  var CANDIDATE_REJECTED_COLOR2 = "red";
6421
6924
 
6422
6925
  // lib/solvers/AvailableNetOrientationSolver/geometry.ts
6423
6926
  var isYOrientation = (orientation) => orientation === "y+" || orientation === "y-";
6424
6927
  var isXOrientation = (orientation) => orientation === "x+" || orientation === "x-";
6425
- 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;
6426
- 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;
6427
6930
  var traceCrossesBoundsInterior = (bounds, traceMap) => {
6428
6931
  for (const trace of Object.values(traceMap)) {
6429
6932
  const points = trace.tracePath;
@@ -6446,7 +6949,7 @@ var segmentCrossesBoundsInterior = (p1, p2, bounds) => {
6446
6949
  return false;
6447
6950
  }
6448
6951
  if (sameX2(p1, p2)) {
6449
- if (p1.x <= interiorBounds.minX + EPS8 || p1.x >= interiorBounds.maxX - EPS8) {
6952
+ if (p1.x <= interiorBounds.minX + EPS9 || p1.x >= interiorBounds.maxX - EPS9) {
6450
6953
  return false;
6451
6954
  }
6452
6955
  return rangesOverlap(
@@ -6457,7 +6960,7 @@ var segmentCrossesBoundsInterior = (p1, p2, bounds) => {
6457
6960
  );
6458
6961
  }
6459
6962
  if (sameY2(p1, p2)) {
6460
- if (p1.y <= interiorBounds.minY + EPS8 || p1.y >= interiorBounds.maxY - EPS8) {
6963
+ if (p1.y <= interiorBounds.minY + EPS9 || p1.y >= interiorBounds.maxY - EPS9) {
6461
6964
  return false;
6462
6965
  }
6463
6966
  return rangesOverlap(
@@ -6496,7 +6999,7 @@ var getConnectorTracePath = (source, target, orientation) => simplifyOrthogonalP
6496
6999
  );
6497
7000
  var simplifyOrthogonalPath = (path) => {
6498
7001
  const deduped = path.filter(
6499
- (point, index) => index === 0 || !pointsEqual(point, path[index - 1])
7002
+ (point, index) => index === 0 || !pointsEqual2(point, path[index - 1])
6500
7003
  );
6501
7004
  if (deduped.length < 3) return deduped;
6502
7005
  const simplified = [deduped[0]];
@@ -6512,9 +7015,9 @@ var simplifyOrthogonalPath = (path) => {
6512
7015
  simplified.push(deduped[deduped.length - 1]);
6513
7016
  return simplified;
6514
7017
  };
6515
- var pointsEqual = (a, b) => sameX2(a, b) && sameY2(a, b);
6516
- var sameX2 = (a, b) => Math.abs(a.x - b.x) <= EPS8;
6517
- 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;
6518
7021
  var getMaxSearchDistance = (inputProblem) => {
6519
7022
  const maxChipWidth = Math.max(
6520
7023
  ...inputProblem.chips.map((chip) => chip.width),
@@ -6817,7 +7320,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
6817
7320
  );
6818
7321
  const maxSearchDistance = this.getSearchDistanceLimit(label, orientation);
6819
7322
  const maxOutwardDistance = outwardDirection.x === 0 && outwardDirection.y === 0 ? 0 : this.maxSearchDistance;
6820
- for (let outwardDistance = 0; outwardDistance <= maxOutwardDistance + EPS8; outwardDistance += LABEL_SEARCH_STEP) {
7323
+ for (let outwardDistance = 0; outwardDistance <= maxOutwardDistance + EPS9; outwardDistance += LABEL_SEARCH_STEP) {
6821
7324
  const baseAnchor = {
6822
7325
  x: initialBaseAnchor.x + outwardDirection.x * outwardDistance,
6823
7326
  y: initialBaseAnchor.y + outwardDirection.y * outwardDistance
@@ -6890,7 +7393,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
6890
7393
  outwardDistance,
6891
7394
  phase = "shift"
6892
7395
  } = params;
6893
- 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) {
6894
7397
  const anchorPoint = {
6895
7398
  x: baseAnchor.x + direction.x * distance3,
6896
7399
  y: baseAnchor.y + direction.y * distance3
@@ -7127,9 +7630,9 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7127
7630
  const { anchorPoint, orientation } = candidate;
7128
7631
  const chipBounds = chip.bounds;
7129
7632
  if (isYOrientation(orientation)) {
7130
- return anchorPoint.x < chipBounds.minX - EPS8 || anchorPoint.x > chipBounds.maxX + EPS8;
7633
+ return anchorPoint.x < chipBounds.minX - EPS9 || anchorPoint.x > chipBounds.maxX + EPS9;
7131
7634
  }
7132
- return anchorPoint.y < chipBounds.minY - EPS8 || anchorPoint.y > chipBounds.maxY + EPS8;
7635
+ return anchorPoint.y < chipBounds.minY - EPS9 || anchorPoint.y > chipBounds.maxY + EPS9;
7133
7636
  });
7134
7637
  }
7135
7638
  getBoundsStatus(candidateBoundsCheck) {
@@ -7196,15 +7699,15 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7196
7699
  if (i === labelIndex) continue;
7197
7700
  const label = this.outputNetLabelPlacements[i];
7198
7701
  const otherBounds = getRectBounds(label.center, label.width, label.height);
7199
- if (rectsOverlap(bounds, otherBounds)) return true;
7702
+ if (rectsOverlap2(bounds, otherBounds)) return true;
7200
7703
  }
7201
7704
  return false;
7202
7705
  }
7203
7706
  sharesChipBoundary(bounds) {
7204
7707
  for (const chip of this.chipObstacleSpatialIndex.chips) {
7205
7708
  const chipBounds = chip.bounds;
7206
- const adjacentToVerticalSide = Math.abs(bounds.minX - chipBounds.maxX) <= WICK_CLEARANCE + EPS8 || Math.abs(bounds.maxX - chipBounds.minX) <= WICK_CLEARANCE + EPS8;
7207
- 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;
7208
7711
  if (adjacentToVerticalSide && rangesOverlap(
7209
7712
  bounds.minY,
7210
7713
  bounds.maxY,
@@ -7252,7 +7755,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7252
7755
  let nearestDistance = Number.POSITIVE_INFINITY;
7253
7756
  for (const chip of this.chipObstacleSpatialIndex.chips) {
7254
7757
  const bounds = chip.bounds;
7255
- 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) {
7256
7759
  continue;
7257
7760
  }
7258
7761
  for (const [side, distance3] of getSideDistances(point, bounds)) {
@@ -7269,8 +7772,8 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7269
7772
  let nearestDistanceSq = Number.POSITIVE_INFINITY;
7270
7773
  for (const chip of this.chipObstacleSpatialIndex.chips) {
7271
7774
  const bounds = chip.bounds;
7272
- const dx = point.x < bounds.minX - EPS8 ? bounds.minX - point.x : point.x > bounds.maxX + EPS8 ? point.x - bounds.maxX : 0;
7273
- 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;
7274
7777
  if (dx === 0 && dy === 0) continue;
7275
7778
  const distanceSq = dx ** 2 + dy ** 2;
7276
7779
  if (distanceSq >= nearestDistanceSq) continue;
@@ -7308,39 +7811,6 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7308
7811
  }
7309
7812
  };
7310
7813
 
7311
- // lib/solvers/RailNetLabelCornerPlacementSolver/geometry.ts
7312
- var EPS9 = 1e-6;
7313
- var isTraceLine = (trace) => getUniquePinCount(trace.pinIds) >= 2;
7314
- 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;
7315
- var getTraceCorners = (path) => {
7316
- const corners = [];
7317
- for (let i = 1; i < path.length - 1; i++) {
7318
- const previousPoint = path[i - 1];
7319
- const cornerPoint = path[i];
7320
- const nextPoint = path[i + 1];
7321
- if (isTraceCorner(previousPoint, cornerPoint, nextPoint)) {
7322
- corners.push(cornerPoint);
7323
- }
7324
- }
7325
- return corners;
7326
- };
7327
- var tracePathContainsPoint = (path, point) => {
7328
- for (let i = 0; i < path.length - 1; i++) {
7329
- if (isPointOnSegment(point, path[i], path[i + 1])) return true;
7330
- }
7331
- return false;
7332
- };
7333
- var getDistance2 = (a, b) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
7334
- var getUniquePinCount = (pinIds) => new Set(pinIds).size;
7335
- var isTraceCorner = (a, b, c) => getSegmentOrientation2(a, b) !== getSegmentOrientation2(b, c);
7336
- var isPointOnSegment = (point, start, end) => {
7337
- if (getSegmentOrientation2(start, end) === "horizontal") {
7338
- 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;
7339
- }
7340
- 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;
7341
- };
7342
- var getSegmentOrientation2 = (a, b) => Math.abs(a.y - b.y) <= EPS9 ? "horizontal" : "vertical";
7343
-
7344
7814
  // lib/solvers/RailNetLabelCornerPlacementSolver/visualize.ts
7345
7815
  var CANDIDATE_SELECTED_COLOR3 = "blue";
7346
7816
  var CANDIDATE_REJECTED_COLOR3 = "red";
@@ -7563,20 +8033,20 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
7563
8033
  }
7564
8034
  intersectsAnyChip(bounds) {
7565
8035
  return this.inputProblem.chips.some(
7566
- (chip) => rectsOverlap2(bounds, getRectBounds(chip.center, chip.width, chip.height))
8036
+ (chip) => rectsOverlap(bounds, getRectBounds(chip.center, chip.width, chip.height))
7567
8037
  );
7568
8038
  }
7569
8039
  intersectsAnyOtherNetLabel(bounds, labelIndex) {
7570
8040
  return this.outputNetLabelPlacements.some((label, index) => {
7571
8041
  if (index === labelIndex) return false;
7572
- return rectsOverlap2(
8042
+ return rectsOverlap(
7573
8043
  bounds,
7574
8044
  getRectBounds(label.center, label.width, label.height)
7575
8045
  );
7576
8046
  });
7577
8047
  }
7578
8048
  getCornerCandidatesForLabel(label) {
7579
- const isVertical4 = label.orientation === "y+" || label.orientation === "y-";
8049
+ const isVertical5 = label.orientation === "y+" || label.orientation === "y-";
7580
8050
  const candidates = [];
7581
8051
  const seenCornerKeys = /* @__PURE__ */ new Set();
7582
8052
  for (const trace of this.getTraceLinesForLabel(label)) {
@@ -7587,14 +8057,14 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
7587
8057
  if (seenCornerKeys.has(key)) continue;
7588
8058
  seenCornerKeys.add(key);
7589
8059
  const pinAligned = pins.some((pin) => {
7590
- if (isVertical4) return Math.abs(pin.x - anchorPoint.x) < EPS9;
7591
- 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;
7592
8062
  });
7593
8063
  if (!pinAligned) continue;
7594
8064
  candidates.push({
7595
8065
  anchorPoint,
7596
8066
  traceId: trace.mspPairId,
7597
- distance: getDistance2(anchorPoint, label.anchorPoint),
8067
+ distance: getDistance(anchorPoint, label.anchorPoint),
7598
8068
  pinAligned
7599
8069
  });
7600
8070
  }
@@ -7769,7 +8239,7 @@ var segmentCrossesBoundsInterior2 = (start, end, bounds) => {
7769
8239
  if (interior.minX >= interior.maxX || interior.minY >= interior.maxY) {
7770
8240
  return false;
7771
8241
  }
7772
- if (isVertical3(start, end)) {
8242
+ if (isVertical4(start, end)) {
7773
8243
  if (start.x <= interior.minX + EPS10 || start.x >= interior.maxX - EPS10) {
7774
8244
  return false;
7775
8245
  }
@@ -7780,7 +8250,7 @@ var segmentCrossesBoundsInterior2 = (start, end, bounds) => {
7780
8250
  interior.maxY
7781
8251
  );
7782
8252
  }
7783
- if (isHorizontal2(start, end)) {
8253
+ if (isHorizontal3(start, end)) {
7784
8254
  if (start.y <= interior.minY + EPS10 || start.y >= interior.maxY - EPS10) {
7785
8255
  return false;
7786
8256
  }
@@ -7794,20 +8264,20 @@ var segmentCrossesBoundsInterior2 = (start, end, bounds) => {
7794
8264
  return false;
7795
8265
  };
7796
8266
  var isPointOnSegment2 = (point, start, end) => {
7797
- if (isHorizontal2(start, end)) {
8267
+ if (isHorizontal3(start, end)) {
7798
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;
7799
8269
  }
7800
- if (isVertical3(start, end)) {
8270
+ if (isVertical4(start, end)) {
7801
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;
7802
8272
  }
7803
8273
  return false;
7804
8274
  };
7805
8275
  var getSegmentDirection = (start, end) => ({
7806
- x: isVertical3(start, end) ? 0 : Math.sign(end.x - start.x),
7807
- 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)
7808
8278
  });
7809
- var isHorizontal2 = (start, end) => Math.abs(start.y - end.y) <= EPS10;
7810
- 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;
7811
8281
  var rangesOverlap2 = (minA, maxA, minB, maxB) => Math.min(maxA, maxB) - Math.max(minA, minB) > EPS10;
7812
8282
  var getUniquePinCount2 = (pinIds) => new Set(pinIds).size;
7813
8283
 
@@ -8816,11 +9286,11 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
8816
9286
  for (let si = 0; si < pts.length - 1; si++) {
8817
9287
  const segStart = pts[si];
8818
9288
  const segEnd = pts[si + 1];
8819
- const isHorizontal3 = Math.abs(segStart.y - segEnd.y) < SEGMENT_PARALLEL_EPS;
8820
- const isVertical4 = Math.abs(segStart.x - segEnd.x) < SEGMENT_PARALLEL_EPS;
8821
- 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;
8822
9292
  let perpendicularOrientations;
8823
- if (isHorizontal3) {
9293
+ if (isHorizontal4) {
8824
9294
  perpendicularOrientations = ["y+", "y-"];
8825
9295
  } else {
8826
9296
  perpendicularOrientations = ["x+", "x-"];
@@ -9033,7 +9503,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9033
9503
  availableNetOrientationSolver;
9034
9504
  railNetLabelCornerPlacementSolver;
9035
9505
  traceAnchoredNetLabelOverlapSolver;
9506
+ preAlignmentNetLabelTraceCollisionSolver;
9036
9507
  netLabelTraceCollisionSolver;
9508
+ traceCleanupSolver2;
9037
9509
  netLabelNetLabelCollisionSolver;
9038
9510
  startTimeOfPhase;
9039
9511
  endTimeOfPhase;
@@ -9218,7 +9690,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9218
9690
  ]
9219
9691
  ),
9220
9692
  definePipelineStep(
9221
- "netLabelTraceCollisionSolver",
9693
+ "preAlignmentNetLabelTraceCollisionSolver",
9222
9694
  NetLabelTraceCollisionSolver,
9223
9695
  (instance) => [
9224
9696
  {
@@ -9228,6 +9700,41 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9228
9700
  }
9229
9701
  ]
9230
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
+ ),
9231
9738
  definePipelineStep(
9232
9739
  "netLabelNetLabelCollisionSolver",
9233
9740
  NetLabelNetLabelCollisionSolver,