@tscircuit/schematic-trace-solver 0.0.95 → 0.0.97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. package/dist/index.d.ts +7 -6
  2. package/dist/index.js +271 -221
  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 +11 -7
  6. package/lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts +47 -40
  7. package/lib/types/InputProblem.ts +1 -0
  8. package/package.json +1 -1
  9. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +1 -1
  10. package/tests/bug-reports/bug-report-20260706T213649Z/bug-report-20260706T213649Z.test.ts +3 -1
  11. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +1 -1
  12. package/tests/bug-reports/bug-report-20260707T092615Z/bug-report-20260707T092615Z.test.ts +3 -1
  13. package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +178 -0
  14. package/tests/repros/__snapshots__/repro-rp2040-zero-crystal-fallback-netlabels.snap.svg +54 -0
  15. package/tests/repros/assets/repro-rp2040-gamepad-trace-alignment.input.json +237 -0
  16. package/tests/repros/assets/repro-rp2040-zero-crystal-fallback-netlabels.input.json +121 -0
  17. package/tests/repros/repro-rp2040-gamepad-trace-alignment.test.ts +14 -0
  18. package/tests/repros/repro-rp2040-zero-crystal-fallback-netlabels.test.ts +16 -0
  19. package/tests/solvers/SchematicTraceSingleLineSolver2/segment-intersects-rect-interior.test.ts +15 -0
  20. package/tests/solvers/SchematicTraceSingleLineSolver2/segment-overlaps-rect-boundary-boundary.test.ts +18 -0
  21. package/tests/solvers/SchematicTraceSingleLineSolver2/segment-overlaps-rect-boundary-interior.test.ts +18 -0
package/dist/index.d.ts CHANGED
@@ -103,6 +103,7 @@ interface InputProblem {
103
103
  availableNetLabelOrientations: Record<NetId, FacingDirection[]>;
104
104
  maxMspPairDistance?: number;
105
105
  _chipObstacleSpatialIndex?: ChipObstacleSpatialIndex;
106
+ _hideRatsNet?: boolean;
106
107
  }
107
108
 
108
109
  type MspConnectionPairId = string;
@@ -1012,9 +1013,8 @@ type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
1012
1013
  onSolved?: (instance: SchematicTracePipelineSolver) => void;
1013
1014
  shouldSkip?: (instance: SchematicTracePipelineSolver) => boolean;
1014
1015
  };
1015
- interface SchematicTracePipelineSolverParams {
1016
- inputProblem: InputProblem;
1017
- allowLongDistanceTraces?: boolean;
1016
+ interface Options {
1017
+ hideRatsNet?: boolean;
1018
1018
  }
1019
1019
  declare class SchematicTracePipelineSolver extends BaseSolver {
1020
1020
  mspConnectionPairSolver?: MspConnectionPairSolver;
@@ -1036,9 +1036,10 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
1036
1036
  timeSpentOnPhase: Record<string, number>;
1037
1037
  firstIterationOfPhase: Record<string, number>;
1038
1038
  inputProblem: InputProblem;
1039
+ hideRatsNet: boolean;
1039
1040
  pipelineDef: (PipelineStep<typeof MspConnectionPairSolver> | PipelineStep<typeof SchematicTraceLinesSolver> | PipelineStep<typeof LongDistancePairSolver> | PipelineStep<typeof TraceOverlapShiftSolver> | PipelineStep<typeof NetLabelPlacementSolver> | PipelineStep<typeof TraceLabelOverlapAvoidanceSolver> | PipelineStep<typeof TraceCleanupSolver> | PipelineStep<typeof Example28Solver> | PipelineStep<typeof AvailableNetOrientationSolver> | PipelineStep<typeof RailNetLabelCornerPlacementSolver> | PipelineStep<typeof TraceAnchoredNetLabelOverlapSolver> | PipelineStep<typeof NetLabelTraceCollisionSolver> | PipelineStep<typeof NetLabelNetLabelCollisionSolver>)[];
1040
- constructor(inputProblem: InputProblem);
1041
- getConstructorParams(): ConstructorParameters<typeof SchematicTracePipelineSolver>[0];
1041
+ constructor(inputProblem: InputProblem, opts?: Options);
1042
+ getConstructorParams(): ConstructorParameters<typeof SchematicTracePipelineSolver>;
1042
1043
  currentPipelineStepIndex: number;
1043
1044
  private cloneAndCorrectInputProblem;
1044
1045
  _step(): void;
@@ -1052,4 +1053,4 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
1052
1053
  preview(): GraphicsObject;
1053
1054
  }
1054
1055
 
1055
- export { type ChipId, type InputChip, type InputDirectConnection, type InputNetConnection, type InputPin, type InputProblem, type NetId, type PinId, SchematicTracePipelineSolver, type SchematicTracePipelineSolverParams, SchematicTraceSingleLineSolver2, type SectionId, type TextBoxes };
1056
+ export { type ChipId, type InputChip, type InputDirectConnection, type InputNetConnection, type InputPin, type InputProblem, type NetId, type PinId, SchematicTracePipelineSolver, SchematicTraceSingleLineSolver2, type SectionId, type TextBoxes };
package/dist/index.js CHANGED
@@ -327,7 +327,11 @@ var arePinsInDifferentSchematicSections = (inputProblem, p1, p2) => {
327
327
 
328
328
  // lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts
329
329
  var visualizeInputProblem = (inputProblem, opts = {}) => {
330
- const { connectionAlpha = 0.8, chipAlpha = 0.8 } = opts;
330
+ const {
331
+ connectionAlpha = 0.8,
332
+ chipAlpha = 0.8,
333
+ hideRatsNet = inputProblem._hideRatsNet ?? false
334
+ } = opts;
331
335
  const graphics = {
332
336
  lines: [],
333
337
  points: [],
@@ -367,47 +371,49 @@ ${pin._facingDirection ?? getPinDirection(pin, chip)}`,
367
371
  strokeColor: "rgba(160, 0, 220, 0.9)"
368
372
  });
369
373
  }
370
- for (const directConn of inputProblem.directConnections) {
371
- const [pinId1, pinId2] = directConn.pinIds;
372
- const pin1 = pinIdMap.get(pinId1);
373
- const pin2 = pinIdMap.get(pinId2);
374
- if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
375
- continue;
374
+ if (!hideRatsNet) {
375
+ for (const directConn of inputProblem.directConnections) {
376
+ const [pinId1, pinId2] = directConn.pinIds;
377
+ const pin1 = pinIdMap.get(pinId1);
378
+ const pin2 = pinIdMap.get(pinId2);
379
+ if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
380
+ continue;
381
+ }
382
+ graphics.lines.push({
383
+ points: [
384
+ {
385
+ x: pin1.x,
386
+ y: pin1.y
387
+ },
388
+ {
389
+ x: pin2.x,
390
+ y: pin2.y
391
+ }
392
+ ],
393
+ strokeColor: getColorFromString(
394
+ directConn.netId ?? `${pinId1}-${pinId2}`,
395
+ connectionAlpha
396
+ )
397
+ });
376
398
  }
377
- graphics.lines.push({
378
- points: [
379
- {
380
- x: pin1.x,
381
- y: pin1.y
382
- },
383
- {
384
- x: pin2.x,
385
- y: pin2.y
386
- }
387
- ],
388
- strokeColor: getColorFromString(
389
- directConn.netId ?? `${pinId1}-${pinId2}`,
390
- connectionAlpha
391
- )
392
- });
393
- }
394
- for (const netConn of inputProblem.netConnections) {
395
- const pins = netConn.pinIds.map((pinId) => pinIdMap.get(pinId));
396
- for (let i = 0; i < pins.length - 1; i++) {
397
- for (let j = i + 1; j < pins.length; j++) {
398
- const pin1 = pins[i];
399
- const pin2 = pins[j];
400
- if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
401
- continue;
399
+ for (const netConn of inputProblem.netConnections) {
400
+ const pins = netConn.pinIds.map((pinId) => pinIdMap.get(pinId));
401
+ for (let i = 0; i < pins.length - 1; i++) {
402
+ for (let j = i + 1; j < pins.length; j++) {
403
+ const pin1 = pins[i];
404
+ const pin2 = pins[j];
405
+ if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
406
+ continue;
407
+ }
408
+ graphics.lines.push({
409
+ points: [
410
+ { x: pin1.x, y: pin1.y },
411
+ { x: pin2.x, y: pin2.y }
412
+ ],
413
+ strokeColor: getColorFromString(netConn.netId, connectionAlpha),
414
+ strokeDash: "4 2"
415
+ });
402
416
  }
403
- graphics.lines.push({
404
- points: [
405
- { x: pin1.x, y: pin1.y },
406
- { x: pin2.x, y: pin2.y }
407
- ],
408
- strokeColor: getColorFromString(netConn.netId, connectionAlpha),
409
- strokeDash: "4 2"
410
- });
411
417
  }
412
418
  }
413
419
  }
@@ -579,54 +585,150 @@ import "graphics-debug";
579
585
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts
580
586
  import { calculateElbow as calculateElbow2 } from "calculate-elbow";
581
587
 
582
- // lib/solvers/GuidelinesSolver/getInputChipBounds.ts
583
- function getInputChipBounds(chip) {
584
- const halfWidth = chip.width / 2;
585
- 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
+ }
586
602
  return {
587
- minX: chip.center.x - halfWidth,
588
- maxX: chip.center.x + halfWidth,
589
- minY: chip.center.y - halfHeight,
590
- maxY: chip.center.y + halfHeight
603
+ width: horizWidth,
604
+ height: horizHeight
591
605
  };
592
606
  }
593
-
594
- // lib/utils/textBoxBounds.ts
595
- 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) {
596
620
  return {
597
- minX: textBox.center.x - textBox.width / 2 - (padding.minX ?? 0),
598
- minY: textBox.center.y - textBox.height / 2 - (padding.minY ?? 0),
599
- maxX: textBox.center.x + textBox.width / 2 + (padding.maxX ?? 0),
600
- 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
601
625
  };
602
626
  }
603
- function boundsOverlap(a, b, eps = 1e-9) {
604
- 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;
605
639
  }
606
- function rectIntersectsAnyTextBox(bounds, inputProblem) {
607
- for (const textBox of inputProblem.textBoxes ?? []) {
608
- 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
+ ];
609
687
  }
610
- 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
+ );
611
731
  }
612
-
613
- // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts
614
- var chipToRect = (chip) => {
615
- const b = getInputChipBounds(chip);
616
- return { kind: "chip", chipId: chip.chipId, ...b };
617
- };
618
- var getObstacleRects = (problem, opts = {}) => {
619
- const chipRects = problem.chips.map(chipToRect);
620
- const textBoxRects = (problem.textBoxes ?? []).map((textBox) => {
621
- const b = getTextBoxBounds(textBox, opts.textBoxPadding);
622
- return {
623
- kind: "text_box",
624
- textBox,
625
- ...b
626
- };
627
- });
628
- return [...chipRects, ...textBoxRects];
629
- };
630
732
 
631
733
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts
632
734
  var EPS = 1e-9;
@@ -652,6 +754,21 @@ var segmentIntersectsRect = (a, b, r, eps = EPS) => {
652
754
  return overlap > eps;
653
755
  }
654
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
+ };
655
772
  var findFirstCollision = (pts, rects, opts = {}) => {
656
773
  for (let i = 0; i < pts.length - 1; i++) {
657
774
  const a = pts[i];
@@ -806,151 +923,55 @@ var pathKey = (pts, decimals = 6) => {
806
923
  return key;
807
924
  };
808
925
 
809
- // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry.ts
810
- var NET_LABEL_HORIZONTAL_WIDTH = 0.45;
811
- var NET_LABEL_HORIZONTAL_HEIGHT = 0.2;
812
- function getDimsForOrientation(params) {
813
- const { orientation, netLabelWidth, netLabelHeight } = params;
814
- const horizWidth = typeof netLabelWidth === "number" ? netLabelWidth : NET_LABEL_HORIZONTAL_WIDTH;
815
- const horizHeight = typeof netLabelHeight === "number" ? netLabelHeight : NET_LABEL_HORIZONTAL_HEIGHT;
816
- if (orientation === "y+" || orientation === "y-") {
817
- return {
818
- // Rotated: horizontal length = netLabelHeight, vertical length = netLabelWidth
819
- width: horizHeight,
820
- height: horizWidth
821
- };
822
- }
926
+ // lib/solvers/GuidelinesSolver/getInputChipBounds.ts
927
+ function getInputChipBounds(chip) {
928
+ const halfWidth = chip.width / 2;
929
+ const halfHeight = chip.height / 2;
823
930
  return {
824
- width: horizWidth,
825
- 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
826
935
  };
827
936
  }
828
- function getCenterFromAnchor(anchor, orientation, width, height) {
829
- switch (orientation) {
830
- case "x+":
831
- return { x: anchor.x + width / 2, y: anchor.y };
832
- case "x-":
833
- return { x: anchor.x - width / 2, y: anchor.y };
834
- case "y+":
835
- return { x: anchor.x, y: anchor.y + height / 2 };
836
- case "y-":
837
- return { x: anchor.x, y: anchor.y - height / 2 };
838
- }
839
- }
840
- function getRectBounds(center, w, h) {
937
+
938
+ // lib/utils/textBoxBounds.ts
939
+ function getTextBoxBounds(textBox, padding = {}) {
841
940
  return {
842
- minX: center.x - w / 2,
843
- minY: center.y - h / 2,
844
- maxX: center.x + w / 2,
845
- 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)
846
945
  };
847
946
  }
848
-
849
- // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/calculateDirectShortPath.ts
850
- import { calculateElbow } from "calculate-elbow";
851
- var MAX_SHORT_TRACE_DISTANCE = 0.15;
852
- var SHORT_TRACE_OVERSHOOT = MAX_SHORT_TRACE_DISTANCE / 7.5;
853
- var FALLBACK_ELBOW_MAX_OVERSHOOT = 0.2;
854
- function segmentDirection(from, to) {
855
- if (to.x > from.x) return "x+";
856
- if (to.x < from.x) return "x-";
857
- if (to.y > from.y) return "y+";
858
- if (to.y < from.y) return "y-";
859
- return null;
860
- }
861
- function pathMatchesPinDirections({
862
- path,
863
- pin1,
864
- pin2
865
- }) {
866
- const firstDirection = segmentDirection(path[0], path[1]);
867
- const lastDirection = segmentDirection(
868
- path[path.length - 1],
869
- path[path.length - 2]
870
- );
871
- return firstDirection === pin1._facingDirection && lastDirection === pin2._facingDirection;
872
- }
873
- function calculateShortOrthogonalRoute(pin1, pin2) {
874
- if (pin1.x === pin2.x || pin1.y === pin2.y) return null;
875
- const firstDir = pin1._facingDirection;
876
- const lastDir = pin2._facingDirection;
877
- const start = { x: pin1.x, y: pin1.y };
878
- const end = { x: pin2.x, y: pin2.y };
879
- let path = null;
880
- if (firstDir?.startsWith("y") && lastDir?.startsWith("x")) {
881
- let yOffset = -SHORT_TRACE_OVERSHOOT;
882
- if (firstDir === "y+") {
883
- yOffset = SHORT_TRACE_OVERSHOOT;
884
- }
885
- const routeY = start.y + yOffset;
886
- const routeX = (start.x + end.x) / 2;
887
- path = [
888
- start,
889
- { x: start.x, y: routeY },
890
- { x: routeX, y: routeY },
891
- { x: routeX, y: end.y },
892
- end
893
- ];
894
- } else if (firstDir?.startsWith("x") && lastDir?.startsWith("y")) {
895
- let xOffset = -SHORT_TRACE_OVERSHOOT;
896
- if (firstDir === "x+") {
897
- xOffset = SHORT_TRACE_OVERSHOOT;
898
- }
899
- const routeX = start.x + xOffset;
900
- const routeY = (start.y + end.y) / 2;
901
- path = [
902
- start,
903
- { x: routeX, y: start.y },
904
- { x: routeX, y: routeY },
905
- { x: end.x, y: routeY },
906
- end
907
- ];
908
- }
909
- if (!path) return null;
910
- if (pathMatchesPinDirections({ path, pin1, pin2 })) {
911
- return path;
912
- }
913
- 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;
914
949
  }
915
- function calculateDirectShortPath(pin1, pin2) {
916
- const routingDistance = Math.abs(pin1.x - pin2.x) + Math.abs(pin1.y - pin2.y);
917
- if (routingDistance > MAX_SHORT_TRACE_DISTANCE) return null;
918
- const start = { x: pin1.x, y: pin1.y };
919
- const end = { x: pin2.x, y: pin2.y };
920
- const orthogonalRoute = calculateShortOrthogonalRoute(pin1, pin2);
921
- if (orthogonalRoute) return orthogonalRoute;
922
- let candidatePaths = [];
923
- if (pin1.x !== pin2.x && pin1.y !== pin2.y) {
924
- candidatePaths = [
925
- [start, { x: pin2.x, y: pin1.y }, end],
926
- [start, { x: pin1.x, y: pin2.y }, end]
927
- ];
928
- } else {
929
- candidatePaths = [[start, end]];
930
- }
931
- for (const path of candidatePaths) {
932
- 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;
933
953
  }
934
- return calculateElbow(
935
- {
936
- x: pin1.x,
937
- y: pin1.y,
938
- facingDirection: pin1._facingDirection
939
- },
940
- {
941
- x: pin2.x,
942
- y: pin2.y,
943
- facingDirection: pin2._facingDirection
944
- },
945
- {
946
- overshoot: Math.min(
947
- FALLBACK_ELBOW_MAX_OVERSHOOT,
948
- Math.max(SHORT_TRACE_OVERSHOOT, routingDistance / 4)
949
- )
950
- }
951
- );
954
+ return false;
952
955
  }
953
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
+
954
975
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts
955
976
  var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
956
977
  pins;
@@ -1137,17 +1158,41 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1137
1158
  const collision = findFirstCollision(path, this.obstacles, {
1138
1159
  excludeRectsForSegment: (segIndex2) => {
1139
1160
  const lastSegIndex2 = path.length - 2;
1161
+ const excludedRects = /* @__PURE__ */ new Set();
1140
1162
  if (segIndex2 === 0 || segIndex2 === lastSegIndex2) {
1141
- 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
+ }
1142
1185
  }
1143
1186
  const adjacentEndpointSegIndex = segIndex2 === 1 ? 2 : segIndex2 === lastSegIndex2 - 1 ? lastSegIndex2 - 2 : null;
1144
1187
  if (adjacentEndpointSegIndex !== null && adjacentEndpointSegIndex >= 0 && adjacentEndpointSegIndex < lastSegIndex2 && this.isSegmentOutsidePinBand(
1145
1188
  path[adjacentEndpointSegIndex],
1146
1189
  path[adjacentEndpointSegIndex + 1]
1147
1190
  )) {
1148
- return this.endpointTextObstacles;
1191
+ for (const textObstacle of this.endpointTextObstacles) {
1192
+ excludedRects.add(textObstacle);
1193
+ }
1149
1194
  }
1150
- return /* @__PURE__ */ new Set();
1195
+ return excludedRects;
1151
1196
  }
1152
1197
  });
1153
1198
  if (!collision) {
@@ -9034,6 +9079,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9034
9079
  timeSpentOnPhase;
9035
9080
  firstIterationOfPhase;
9036
9081
  inputProblem;
9082
+ hideRatsNet;
9037
9083
  pipelineDef = [
9038
9084
  definePipelineStep(
9039
9085
  "mspConnectionPairSolver",
@@ -9233,8 +9279,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9233
9279
  ]
9234
9280
  )
9235
9281
  ];
9236
- constructor(inputProblem) {
9282
+ constructor(inputProblem, opts) {
9237
9283
  super();
9284
+ this.hideRatsNet = opts?.hideRatsNet ?? false;
9238
9285
  this.inputProblem = this.cloneAndCorrectInputProblem(inputProblem);
9239
9286
  this.MAX_ITERATIONS = 1e6;
9240
9287
  this.startTimeOfPhase = {};
@@ -9243,13 +9290,14 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9243
9290
  this.firstIterationOfPhase = {};
9244
9291
  }
9245
9292
  getConstructorParams() {
9246
- return this.inputProblem;
9293
+ return [this.inputProblem, { hideRatsNet: this.hideRatsNet }];
9247
9294
  }
9248
9295
  currentPipelineStepIndex = 0;
9249
9296
  cloneAndCorrectInputProblem(original) {
9250
9297
  const cloned = structuredClone({
9251
9298
  ...original,
9252
- _chipObstacleSpatialIndex: void 0
9299
+ _chipObstacleSpatialIndex: void 0,
9300
+ _hideRatsNet: this.hideRatsNet
9253
9301
  });
9254
9302
  expandChipsToFitPins(cloned);
9255
9303
  correctPinsInsideChips(cloned);
@@ -9295,7 +9343,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9295
9343
  if (!this.solved && this.activeSubSolver)
9296
9344
  return this.activeSubSolver.visualize();
9297
9345
  const visualizations = [
9298
- visualizeInputProblem(this.inputProblem),
9346
+ visualizeInputProblem(this.inputProblem, {
9347
+ hideRatsNet: this.hideRatsNet
9348
+ }),
9299
9349
  ...this.pipelineDef.map((p) => this[p.solverName]?.visualize()).filter(Boolean).map((viz, stepIndex) => {
9300
9350
  for (const rect of viz.rects ?? []) {
9301
9351
  rect.step = stepIndex;