@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 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) {
@@ -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 type { InputChip, InputProblem, PinId } from "lib/types/InputProblem"
6
- import type { Point } from "@tscircuit/math-utils"
7
- import { calculateElbow } from "calculate-elbow"
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 { getObstacleRects, type ObstacleRect } from "./rect"
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 { FacingDirection } from "lib/utils/dir"
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
- return this.textObstacles
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
- return this.endpointTextObstacles
327
+ for (const textObstacle of this.endpointTextObstacles) {
328
+ excludedRects.add(textObstacle)
329
+ }
290
330
  }
291
- return new Set<ObstacleRect>()
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[],
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.96",
4
+ "version": "0.0.97",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",