@tscircuit/schematic-trace-solver 0.0.96 → 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.
- package/dist/index.js +216 -177
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +55 -14
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts +31 -0
- package/package.json +1 -1
- package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +178 -0
- package/tests/repros/__snapshots__/repro-rp2040-zero-crystal-fallback-netlabels.snap.svg +54 -0
- package/tests/repros/assets/repro-rp2040-gamepad-trace-alignment.input.json +237 -0
- package/tests/repros/assets/repro-rp2040-zero-crystal-fallback-netlabels.input.json +121 -0
- package/tests/repros/repro-rp2040-gamepad-trace-alignment.test.ts +14 -0
- package/tests/repros/repro-rp2040-zero-crystal-fallback-netlabels.test.ts +16 -0
- package/tests/solvers/SchematicTraceSingleLineSolver2/segment-intersects-rect-interior.test.ts +15 -0
- package/tests/solvers/SchematicTraceSingleLineSolver2/segment-overlaps-rect-boundary-boundary.test.ts +18 -0
- package/tests/solvers/SchematicTraceSingleLineSolver2/segment-overlaps-rect-boundary-interior.test.ts +18 -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/
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
-
|
|
594
|
-
|
|
595
|
-
minY: chip.center.y - halfHeight,
|
|
596
|
-
maxY: chip.center.y + halfHeight
|
|
603
|
+
width: horizWidth,
|
|
604
|
+
height: horizHeight
|
|
597
605
|
};
|
|
598
606
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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:
|
|
604
|
-
minY:
|
|
605
|
-
maxX:
|
|
606
|
-
maxY:
|
|
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
|
-
|
|
610
|
-
|
|
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
|
|
613
|
-
|
|
614
|
-
|
|
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
|
|
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/
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
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
|
-
|
|
831
|
-
|
|
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
|
-
|
|
835
|
-
|
|
836
|
-
|
|
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 -
|
|
849
|
-
minY: center.y -
|
|
850
|
-
maxX: center.x +
|
|
851
|
-
maxY: center.y +
|
|
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
|
-
|
|
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
|
|
922
|
-
const
|
|
923
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1191
|
+
for (const textObstacle of this.endpointTextObstacles) {
|
|
1192
|
+
excludedRects.add(textObstacle);
|
|
1193
|
+
}
|
|
1155
1194
|
}
|
|
1156
|
-
return
|
|
1195
|
+
return excludedRects;
|
|
1157
1196
|
}
|
|
1158
1197
|
});
|
|
1159
1198
|
if (!collision) {
|
|
@@ -1,24 +1,29 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import { calculateElbow } from "calculate-elbow"
|
|
1
3
|
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
-
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
3
4
|
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
4
5
|
import type { MspConnectionPair } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
6
|
+
import { getDimsForOrientation } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
7
|
+
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
8
|
+
import type { InputChip, InputProblem } from "lib/types/InputProblem"
|
|
9
|
+
import type { FacingDirection } from "lib/utils/dir"
|
|
10
|
+
import type { RectPadding } from "lib/utils/textBoxBounds"
|
|
8
11
|
import { getPinDirection } from "../SchematicTraceSingleLineSolver/getPinDirection"
|
|
9
|
-
import {
|
|
10
|
-
import { findFirstCollision, isHorizontal, isVertical } from "./collisions"
|
|
12
|
+
import { calculateDirectShortPath } from "./calculateDirectShortPath"
|
|
11
13
|
import {
|
|
14
|
+
findFirstCollision,
|
|
15
|
+
isHorizontal,
|
|
16
|
+
isVertical,
|
|
17
|
+
segmentOverlapsRectBoundary,
|
|
18
|
+
} from "./collisions"
|
|
19
|
+
import {
|
|
20
|
+
type Axis,
|
|
12
21
|
aabbFromPoints,
|
|
13
22
|
candidateMidsFromSet,
|
|
14
23
|
midBetweenPointAndRect,
|
|
15
|
-
type Axis,
|
|
16
24
|
} from "./mid"
|
|
17
25
|
import { pathKey, shiftSegmentOrth } from "./pathOps"
|
|
18
|
-
import type
|
|
19
|
-
import { getDimsForOrientation } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
20
|
-
import type { RectPadding } from "lib/utils/textBoxBounds"
|
|
21
|
-
import { calculateDirectShortPath } from "./calculateDirectShortPath"
|
|
26
|
+
import { getObstacleRects, type ObstacleRect } from "./rect"
|
|
22
27
|
|
|
23
28
|
type PathKey = string
|
|
24
29
|
|
|
@@ -268,9 +273,42 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
|
|
|
268
273
|
const collision = findFirstCollision(path, this.obstacles, {
|
|
269
274
|
excludeRectsForSegment: (segIndex) => {
|
|
270
275
|
const lastSegIndex = path.length - 2
|
|
276
|
+
const excludedRects = new Set<ObstacleRect>()
|
|
277
|
+
|
|
271
278
|
if (segIndex === 0 || segIndex === lastSegIndex) {
|
|
272
|
-
|
|
279
|
+
for (const textObstacle of this.textObstacles) {
|
|
280
|
+
excludedRects.add(textObstacle)
|
|
281
|
+
}
|
|
273
282
|
}
|
|
283
|
+
|
|
284
|
+
const segmentStart = path[segIndex]!
|
|
285
|
+
const segmentEnd = path[segIndex + 1]!
|
|
286
|
+
const endpointEpsilon = 1e-9
|
|
287
|
+
const segmentTouchesPin = (pin: MspConnectionPair["pins"][number]) =>
|
|
288
|
+
[segmentStart, segmentEnd].some(
|
|
289
|
+
(point) =>
|
|
290
|
+
Math.abs(point.x - pin.x) <= endpointEpsilon &&
|
|
291
|
+
Math.abs(point.y - pin.y) <= endpointEpsilon,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
for (const pin of this.pins) {
|
|
295
|
+
if (!segmentTouchesPin(pin)) continue
|
|
296
|
+
const endpointChipObstacle = this.obstacles.find(
|
|
297
|
+
(obstacle) =>
|
|
298
|
+
obstacle.kind === "chip" && obstacle.chipId === pin.chipId,
|
|
299
|
+
)
|
|
300
|
+
if (
|
|
301
|
+
endpointChipObstacle &&
|
|
302
|
+
segmentOverlapsRectBoundary(
|
|
303
|
+
segmentStart,
|
|
304
|
+
segmentEnd,
|
|
305
|
+
endpointChipObstacle,
|
|
306
|
+
)
|
|
307
|
+
) {
|
|
308
|
+
excludedRects.add(endpointChipObstacle)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
274
312
|
const adjacentEndpointSegIndex =
|
|
275
313
|
segIndex === 1
|
|
276
314
|
? 2
|
|
@@ -286,9 +324,12 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
|
|
|
286
324
|
path[adjacentEndpointSegIndex + 1]!,
|
|
287
325
|
)
|
|
288
326
|
) {
|
|
289
|
-
|
|
327
|
+
for (const textObstacle of this.endpointTextObstacles) {
|
|
328
|
+
excludedRects.add(textObstacle)
|
|
329
|
+
}
|
|
290
330
|
}
|
|
291
|
-
|
|
331
|
+
|
|
332
|
+
return excludedRects
|
|
292
333
|
},
|
|
293
334
|
})
|
|
294
335
|
|
|
@@ -35,6 +35,37 @@ export const segmentIntersectsRect = <TRect extends RectBounds>(
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export const segmentOverlapsRectBoundary = <TRect extends RectBounds>(
|
|
39
|
+
a: Point,
|
|
40
|
+
b: Point,
|
|
41
|
+
r: TRect,
|
|
42
|
+
eps = EPS,
|
|
43
|
+
): boolean => {
|
|
44
|
+
if (isVertical(a, b, eps)) {
|
|
45
|
+
const onVerticalBoundary =
|
|
46
|
+
Math.abs(a.x - r.minX) <= eps || Math.abs(a.x - r.maxX) <= eps
|
|
47
|
+
if (!onVerticalBoundary) return false
|
|
48
|
+
|
|
49
|
+
const overlap =
|
|
50
|
+
Math.min(Math.max(a.y, b.y), r.maxY) -
|
|
51
|
+
Math.max(Math.min(a.y, b.y), r.minY)
|
|
52
|
+
return overlap > eps
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (isHorizontal(a, b, eps)) {
|
|
56
|
+
const onHorizontalBoundary =
|
|
57
|
+
Math.abs(a.y - r.minY) <= eps || Math.abs(a.y - r.maxY) <= eps
|
|
58
|
+
if (!onHorizontalBoundary) return false
|
|
59
|
+
|
|
60
|
+
const overlap =
|
|
61
|
+
Math.min(Math.max(a.x, b.x), r.maxX) -
|
|
62
|
+
Math.max(Math.min(a.x, b.x), r.minX)
|
|
63
|
+
return overlap > eps
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
|
|
38
69
|
export const findFirstCollision = <TRect extends RectBounds>(
|
|
39
70
|
pts: Point[],
|
|
40
71
|
rects: TRect[],
|