@tscircuit/schematic-trace-solver 0.0.77 → 0.0.78

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 (23) hide show
  1. package/.github/ISSUE_TEMPLATE/json-bug-report.yml +24 -0
  2. package/.github/scripts/import-json-bug-report.ts +483 -0
  3. package/.github/workflows/json-bug-report.yml +154 -0
  4. package/dist/index.d.ts +32 -10
  5. package/dist/index.js +216 -74
  6. package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +4 -0
  7. package/lib/solvers/AvailableNetOrientationSolver/types.ts +1 -0
  8. package/lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts +6 -0
  9. package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver.ts +21 -1
  10. package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver_visualize.ts +9 -3
  11. package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/solvePortOnlyPin.ts +27 -2
  12. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver.ts +1 -0
  13. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +121 -30
  14. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts +10 -10
  15. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/mid.ts +5 -8
  16. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts +31 -6
  17. package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/TraceAnchoredNetLabelOverlapSolver.ts +3 -0
  18. package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/types.ts +1 -0
  19. package/lib/solvers/VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver.ts +3 -0
  20. package/lib/solvers/VccNetLabelCornerPlacementSolver/types.ts +1 -0
  21. package/lib/utils/textBoxBounds.ts +40 -0
  22. package/package.json +1 -1
  23. package/tests/repros/__snapshots__/manufacturePartNumber-text-box.snap.svg +12 -12
package/dist/index.d.ts CHANGED
@@ -137,21 +137,39 @@ declare class MspConnectionPairSolver extends BaseSolver {
137
137
  visualize(): GraphicsObject;
138
138
  }
139
139
 
140
- type ChipWithBounds = {
141
- chipId: string;
140
+ type RectPadding = {
141
+ minX?: number;
142
+ minY?: number;
143
+ maxX?: number;
144
+ maxY?: number;
145
+ };
146
+
147
+ type RectBounds = {
142
148
  minX: number;
143
149
  minY: number;
144
150
  maxX: number;
145
151
  maxY: number;
146
152
  };
147
- declare const getObstacleRects: (problem: InputProblem) => ChipWithBounds[];
153
+ type ChipObstacleRect = RectBounds & {
154
+ kind: "chip";
155
+ chipId: string;
156
+ };
157
+ type TextBoxObstacleRect = RectBounds & {
158
+ kind: "text_box";
159
+ textBox: NonNullable<InputProblem["textBoxes"]>[number];
160
+ };
161
+ type ObstacleRect = ChipObstacleRect | TextBoxObstacleRect;
162
+ declare const getObstacleRects: (problem: InputProblem, opts?: {
163
+ textBoxPadding?: RectPadding;
164
+ }) => ObstacleRect[];
148
165
 
149
166
  declare class SchematicTraceSingleLineSolver2 extends BaseSolver {
150
167
  pins: MspConnectionPair["pins"];
168
+ connectionPair?: MspConnectionPair;
151
169
  inputProblem: InputProblem;
152
170
  chipMap: Record<string, InputChip>;
153
- obstacles: ChipWithBounds[];
154
- rectById: Map<string, ChipWithBounds>;
171
+ obstacles: ObstacleRect[];
172
+ textObstacles: Set<ObstacleRect>;
155
173
  aabb: {
156
174
  minX: number;
157
175
  maxX: number;
@@ -164,10 +182,14 @@ declare class SchematicTraceSingleLineSolver2 extends BaseSolver {
164
182
  private visited;
165
183
  constructor(params: {
166
184
  pins: MspConnectionPair["pins"];
185
+ connectionPair?: MspConnectionPair;
167
186
  inputProblem: InputProblem;
168
187
  chipMap: Record<string, InputChip>;
169
188
  });
170
189
  getConstructorParams(): ConstructorParameters<typeof SchematicTraceSingleLineSolver2>[0];
190
+ private getTextBoxPaddingForConnectionPair;
191
+ private getNetLabelWidthForConnectionPair;
192
+ private getNetLabelHeightForConnectionPair;
171
193
  private axisOfSegment;
172
194
  private pathLength;
173
195
  _step(): void;
@@ -324,7 +346,7 @@ declare class SingleNetLabelPlacementSolver extends BaseSolver {
324
346
  y: number;
325
347
  };
326
348
  orientation: FacingDirection;
327
- status: "ok" | "chip-collision" | "trace-collision" | "parallel-to-segment";
349
+ status: "ok" | "chip-collision" | "trace-collision" | "text-collision" | "parallel-to-segment";
328
350
  hostSegIndex: number;
329
351
  }>;
330
352
  constructor(params: {
@@ -672,7 +694,7 @@ type CandidateLabel = {
672
694
  width: number;
673
695
  height: number;
674
696
  };
675
- type CandidateStatus$2 = "valid" | "chip-collision" | "trace-collision" | "netlabel-collision";
697
+ type CandidateStatus$2 = "valid" | "chip-collision" | "text-collision" | "trace-collision" | "netlabel-collision";
676
698
  type CandidatePhase = "rotate" | "shift" | "lateral-shift";
677
699
  type EvaluatedCandidate = CandidateLabel & {
678
700
  status: CandidateStatus$2;
@@ -756,7 +778,7 @@ type TraceCornerCandidate = {
756
778
  traceId: string;
757
779
  distance: number;
758
780
  };
759
- type CornerCandidateStatus = "valid" | "chip-collision" | "trace-collision" | "netlabel-collision";
781
+ type CornerCandidateStatus = "valid" | "chip-collision" | "text-collision" | "trace-collision" | "netlabel-collision";
760
782
  type EvaluatedCornerCandidate = TraceCornerCandidate & {
761
783
  center: Point;
762
784
  width: number;
@@ -809,7 +831,7 @@ type LabelOverlap = {
809
831
  firstLabelIndex: number;
810
832
  secondLabelIndex: number;
811
833
  };
812
- type CandidateStatus$1 = "valid" | "chip-collision" | "trace-collision" | "netlabel-collision";
834
+ type CandidateStatus$1 = "valid" | "chip-collision" | "text-collision" | "trace-collision" | "netlabel-collision";
813
835
  type LabelCandidate = {
814
836
  anchorPoint: Point;
815
837
  center: Point;
@@ -887,7 +909,7 @@ declare class NetLabelTraceCollisionSolver extends BaseSolver {
887
909
  visualize(): GraphicsObject;
888
910
  }
889
911
 
890
- type CandidateStatus = "ok" | "chip-collision" | "trace-collision" | "label-collision";
912
+ type CandidateStatus = "ok" | "chip-collision" | "text-collision" | "trace-collision" | "label-collision";
891
913
  type Candidate = {
892
914
  orientation: FacingDirection;
893
915
  anchor: {
package/dist/index.js CHANGED
@@ -591,13 +591,41 @@ function getInputChipBounds(chip) {
591
591
  };
592
592
  }
593
593
 
594
+ // lib/utils/textBoxBounds.ts
595
+ function getTextBoxBounds(textBox, padding = {}) {
596
+ 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)
601
+ };
602
+ }
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;
605
+ }
606
+ function rectIntersectsAnyTextBox(bounds, inputProblem) {
607
+ for (const textBox of inputProblem.textBoxes ?? []) {
608
+ if (boundsOverlap(bounds, getTextBoxBounds(textBox))) return true;
609
+ }
610
+ return false;
611
+ }
612
+
594
613
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts
595
614
  var chipToRect = (chip) => {
596
615
  const b = getInputChipBounds(chip);
597
- return { chipId: chip.chipId, ...b };
616
+ return { kind: "chip", chipId: chip.chipId, ...b };
598
617
  };
599
- var getObstacleRects = (problem) => {
600
- return problem.chips.map(chipToRect);
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];
601
629
  };
602
630
 
603
631
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts
@@ -628,9 +656,9 @@ var findFirstCollision = (pts, rects, opts = {}) => {
628
656
  for (let i = 0; i < pts.length - 1; i++) {
629
657
  const a = pts[i];
630
658
  const b = pts[i + 1];
631
- const excluded = opts.excludeRectIdsForSegment?.(i) ?? /* @__PURE__ */ new Set();
659
+ const excluded = opts.excludeRectsForSegment?.(i) ?? /* @__PURE__ */ new Set();
632
660
  for (const r of rects) {
633
- if (excluded.has(r.chipId)) continue;
661
+ if (excluded.has(r)) continue;
634
662
  if (segmentIntersectsRect(a, b, r)) {
635
663
  return { segIndex: i, rect: r };
636
664
  }
@@ -676,8 +704,8 @@ var midBetweenPointAndRect = (axis, p, r, eps = EPS2) => {
676
704
  return [r.minY - 0.2, r.maxY + 0.2];
677
705
  }
678
706
  };
679
- var candidateMidsFromSet = (axis, colliding, rectsById, collisionRectIds, aabb, eps = EPS2) => {
680
- const setRects = [...collisionRectIds].map((id) => rectsById.get(id)).filter((r) => !!r);
707
+ var candidateMidsFromSet = (axis, colliding, collisionRects, aabb, eps = EPS2) => {
708
+ const setRects = [...collisionRects];
681
709
  if (axis === "x") {
682
710
  const leftBoundaries = [aabb.minX, ...setRects.map((r) => r.maxX)].filter(
683
711
  (v) => v < colliding.minX - eps
@@ -778,13 +806,54 @@ var pathKey = (pts, decimals = 6) => {
778
806
  return key;
779
807
  };
780
808
 
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
+ }
823
+ return {
824
+ width: horizWidth,
825
+ height: horizHeight
826
+ };
827
+ }
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) {
841
+ 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
846
+ };
847
+ }
848
+
781
849
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts
782
850
  var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
783
851
  pins;
852
+ connectionPair;
784
853
  inputProblem;
785
854
  chipMap;
786
855
  obstacles;
787
- rectById;
856
+ textObstacles;
788
857
  aabb;
789
858
  baseElbow;
790
859
  solvedTracePath = null;
@@ -793,6 +862,7 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
793
862
  constructor(params) {
794
863
  super();
795
864
  this.pins = params.pins;
865
+ this.connectionPair = params.connectionPair;
796
866
  this.inputProblem = params.inputProblem;
797
867
  this.chipMap = params.chipMap;
798
868
  for (const pin of this.pins) {
@@ -801,8 +871,12 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
801
871
  pin._facingDirection = getPinDirection(pin, chip);
802
872
  }
803
873
  }
804
- this.obstacles = getObstacleRects(this.inputProblem);
805
- this.rectById = new Map(this.obstacles.map((r) => [r.chipId, r]));
874
+ this.obstacles = getObstacleRects(this.inputProblem, {
875
+ textBoxPadding: this.getTextBoxPaddingForConnectionPair()
876
+ });
877
+ this.textObstacles = new Set(
878
+ this.obstacles.filter((r) => r.kind === "text_box")
879
+ );
806
880
  const [pin1, pin2] = this.pins;
807
881
  this.baseElbow = calculateElbow(
808
882
  {
@@ -821,16 +895,84 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
821
895
  { x: pin1.x, y: pin1.y },
822
896
  { x: pin2.x, y: pin2.y }
823
897
  );
824
- this.queue.push({ path: this.baseElbow, collisionChipIds: /* @__PURE__ */ new Set() });
898
+ this.queue.push({ path: this.baseElbow, collisionRects: /* @__PURE__ */ new Set() });
825
899
  this.visited.add(pathKey(this.baseElbow));
826
900
  }
827
901
  getConstructorParams() {
828
902
  return {
829
903
  chipMap: this.chipMap,
830
904
  pins: this.pins,
905
+ connectionPair: this.connectionPair,
831
906
  inputProblem: this.inputProblem
832
907
  };
833
908
  }
909
+ getTextBoxPaddingForConnectionPair() {
910
+ if (!this.inputProblem.textBoxes?.length) return {};
911
+ const netId = this.connectionPair?.userNetId;
912
+ if (!netId) return {};
913
+ const orientations = this.inputProblem.availableNetLabelOrientations[netId] ?? ["x+", "x-", "y+", "y-"];
914
+ const netLabelWidth = this.getNetLabelWidthForConnectionPair(netId);
915
+ const netLabelHeight = this.getNetLabelHeightForConnectionPair(netId);
916
+ const padding = {
917
+ minX: 0,
918
+ minY: 0,
919
+ maxX: 0,
920
+ maxY: 0
921
+ };
922
+ for (const orientation of orientations) {
923
+ const { width, height } = getDimsForOrientation({
924
+ orientation,
925
+ netLabelWidth,
926
+ netLabelHeight
927
+ });
928
+ if (orientation === "y+" || orientation === "y-") {
929
+ padding.minX = Math.max(padding.minX, width / 2);
930
+ padding.maxX = Math.max(padding.maxX, width / 2);
931
+ if (orientation === "y+") {
932
+ padding.minY = Math.max(padding.minY, height);
933
+ } else {
934
+ padding.maxY = Math.max(padding.maxY, height);
935
+ }
936
+ } else {
937
+ padding.minY = Math.max(padding.minY, height / 2);
938
+ padding.maxY = Math.max(padding.maxY, height / 2);
939
+ if (orientation === "x+") {
940
+ padding.minX = Math.max(padding.minX, width);
941
+ } else {
942
+ padding.maxX = Math.max(padding.maxX, width);
943
+ }
944
+ }
945
+ }
946
+ return padding;
947
+ }
948
+ getNetLabelWidthForConnectionPair(netId) {
949
+ const ncWidth = this.inputProblem.netConnections.find(
950
+ (nc) => nc.netId === netId
951
+ )?.netLabelWidth;
952
+ if (ncWidth !== void 0) return ncWidth;
953
+ const dcWidthByNetId = this.inputProblem.directConnections.find(
954
+ (dc) => dc.netId === netId
955
+ )?.netLabelWidth;
956
+ if (dcWidthByNetId !== void 0) return dcWidthByNetId;
957
+ const pinIds = this.pins.map((p) => p.pinId);
958
+ const dcWidthByPinId = this.inputProblem.directConnections.find(
959
+ (dc) => dc.pinIds.some((pid) => pinIds.includes(pid))
960
+ )?.netLabelWidth;
961
+ if (dcWidthByPinId !== void 0) return dcWidthByPinId;
962
+ return this.inputProblem.netConnections.find(
963
+ (nc) => nc.pinIds.some((pid) => pinIds.includes(pid))
964
+ )?.netLabelWidth;
965
+ }
966
+ getNetLabelHeightForConnectionPair(netId) {
967
+ const ncHeight = this.inputProblem.netConnections.find(
968
+ (nc) => nc.netId === netId
969
+ )?.netLabelHeight;
970
+ if (ncHeight !== void 0) return ncHeight;
971
+ const pinIds = this.pins.map((p) => p.pinId);
972
+ return this.inputProblem.netConnections.find(
973
+ (nc) => nc.pinIds.some((pid) => pinIds.includes(pid))
974
+ )?.netLabelHeight;
975
+ }
834
976
  axisOfSegment(a, b) {
835
977
  if (isVertical(a, b)) return "x";
836
978
  if (isHorizontal(a, b)) return "y";
@@ -854,9 +996,17 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
854
996
  this.error = "No collision-free path found";
855
997
  return;
856
998
  }
857
- const { path, collisionChipIds } = state;
999
+ const { path, collisionRects } = state;
858
1000
  const [PA, PB] = this.pins;
859
- const collision = findFirstCollision(path, this.obstacles);
1001
+ const collision = findFirstCollision(path, this.obstacles, {
1002
+ excludeRectsForSegment: (segIndex2) => {
1003
+ const lastSegIndex = path.length - 2;
1004
+ if (segIndex2 === 0 || segIndex2 === lastSegIndex) {
1005
+ return this.textObstacles;
1006
+ }
1007
+ return /* @__PURE__ */ new Set();
1008
+ }
1009
+ });
860
1010
  if (!collision) {
861
1011
  const first = path[0];
862
1012
  const last = path[path.length - 1];
@@ -889,20 +1039,14 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
889
1039
  return;
890
1040
  }
891
1041
  const candidates = [];
892
- if (collisionChipIds.size === 0) {
1042
+ if (collisionRects.size === 0) {
893
1043
  const m1 = midBetweenPointAndRect(axis, { x: PA.x, y: PA.y }, rect);
894
1044
  const m2 = midBetweenPointAndRect(axis, { x: PB.x, y: PB.y }, rect);
895
1045
  const allCandidates = [...m1, ...m2];
896
1046
  const uniqueCandidates = [...new Set(allCandidates)];
897
1047
  candidates.push(...uniqueCandidates);
898
1048
  } else {
899
- const mids = candidateMidsFromSet(
900
- axis,
901
- rect,
902
- this.rectById,
903
- collisionChipIds,
904
- this.aabb
905
- );
1049
+ const mids = candidateMidsFromSet(axis, rect, collisionRects, this.aabb);
906
1050
  candidates.push(...mids);
907
1051
  }
908
1052
  const newStates = [];
@@ -912,14 +1056,14 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
912
1056
  const key = pathKey(newPath);
913
1057
  if (this.visited.has(key)) continue;
914
1058
  this.visited.add(key);
915
- const nextSet = new Set(collisionChipIds);
916
- nextSet.add(rect.chipId);
1059
+ const nextSet = new Set(collisionRects);
1060
+ nextSet.add(rect);
917
1061
  const len = this.pathLength(newPath);
918
- newStates.push({ path: newPath, collisionRectIds: nextSet, len });
1062
+ newStates.push({ path: newPath, collisionRects: nextSet, len });
919
1063
  }
920
1064
  newStates.sort((a2, b2) => a2.len - b2.len);
921
1065
  for (const st of newStates) {
922
- this.queue.push({ path: st.path, collisionChipIds: st.collisionRectIds });
1066
+ this.queue.push({ path: st.path, collisionRects: st.collisionRects });
923
1067
  }
924
1068
  }
925
1069
  visualize() {
@@ -941,7 +1085,7 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
941
1085
  strokeColor: "blue",
942
1086
  strokeDash: "5 5"
943
1087
  });
944
- for (const { path, collisionChipIds: collisionRectIds } of this.queue) {
1088
+ for (const { path } of this.queue) {
945
1089
  g.lines.push({ points: path, strokeColor: "teal", strokeDash: "2 2" });
946
1090
  }
947
1091
  if (this.solvedTracePath) {
@@ -1023,6 +1167,7 @@ var SchematicTraceLinesSolver = class extends BaseSolver {
1023
1167
  this.activeSubSolver = new SchematicTraceSingleLineSolver2({
1024
1168
  inputProblem: this.inputProblem,
1025
1169
  pins,
1170
+ connectionPair,
1026
1171
  chipMap: this.chipMap
1027
1172
  });
1028
1173
  }
@@ -1638,46 +1783,6 @@ var ChipObstacleSpatialIndex = class {
1638
1783
  }
1639
1784
  };
1640
1785
 
1641
- // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry.ts
1642
- var NET_LABEL_HORIZONTAL_WIDTH = 0.45;
1643
- var NET_LABEL_HORIZONTAL_HEIGHT = 0.2;
1644
- function getDimsForOrientation(params) {
1645
- const { orientation, netLabelWidth, netLabelHeight } = params;
1646
- const horizWidth = typeof netLabelWidth === "number" ? netLabelWidth : NET_LABEL_HORIZONTAL_WIDTH;
1647
- const horizHeight = typeof netLabelHeight === "number" ? netLabelHeight : NET_LABEL_HORIZONTAL_HEIGHT;
1648
- if (orientation === "y+" || orientation === "y-") {
1649
- return {
1650
- // Rotated: horizontal length = netLabelHeight, vertical length = netLabelWidth
1651
- width: horizHeight,
1652
- height: horizWidth
1653
- };
1654
- }
1655
- return {
1656
- width: horizWidth,
1657
- height: horizHeight
1658
- };
1659
- }
1660
- function getCenterFromAnchor(anchor, orientation, width, height) {
1661
- switch (orientation) {
1662
- case "x+":
1663
- return { x: anchor.x + width / 2, y: anchor.y };
1664
- case "x-":
1665
- return { x: anchor.x - width / 2, y: anchor.y };
1666
- case "y+":
1667
- return { x: anchor.x, y: anchor.y + height / 2 };
1668
- case "y-":
1669
- return { x: anchor.x, y: anchor.y - height / 2 };
1670
- }
1671
- }
1672
- function getRectBounds(center, w, h) {
1673
- return {
1674
- minX: center.x - w / 2,
1675
- minY: center.y - h / 2,
1676
- maxX: center.x + w / 2,
1677
- maxY: center.y + h / 2
1678
- };
1679
- }
1680
-
1681
1786
  // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions.ts
1682
1787
  function segmentIntersectsRect2(p1, p2, rect, EPS11 = 1e-9) {
1683
1788
  const isVert = Math.abs(p1.x - p2.x) < EPS11;
@@ -1843,6 +1948,19 @@ function solveNetLabelPlacementForPortOnlyPin(params) {
1843
1948
  });
1844
1949
  continue;
1845
1950
  }
1951
+ if (rectIntersectsAnyTextBox(bounds, inputProblem)) {
1952
+ testedCandidates.push({
1953
+ center,
1954
+ width: width2,
1955
+ height: height2,
1956
+ bounds,
1957
+ anchor,
1958
+ orientation,
1959
+ status: "text-collision",
1960
+ hostSegIndex: -1
1961
+ });
1962
+ continue;
1963
+ }
1846
1964
  const traceIntersectionResult = rectIntersectsAnyTrace(
1847
1965
  bounds,
1848
1966
  inputTraceMap,
@@ -1939,9 +2057,9 @@ function visualizeSingleNetLabelPlacementSolver(solver) {
1939
2057
  });
1940
2058
  }
1941
2059
  for (const c of solver.testedCandidates) {
1942
- const fill = c.status === "ok" ? "rgba(0, 180, 0, 0.25)" : c.status === "chip-collision" ? "rgba(220, 0, 0, 0.25)" : c.status === "trace-collision" ? "rgba(220, 140, 0, 0.25)" : "rgba(120, 120, 120, 0.15)";
1943
- const stroke = c.status === "ok" ? "green" : c.status === "chip-collision" ? "red" : c.status === "trace-collision" ? "orange" : "gray";
1944
- const candidateLabel = c.status === "ok" ? "status: ok(valid net label candidate)" : c.status === "chip-collision" ? "status: chip-collision" : c.status === "trace-collision" ? "status: trace-collision" : "status: parallel-to-segment";
2060
+ const fill = c.status === "ok" ? "rgba(0, 180, 0, 0.25)" : c.status === "chip-collision" ? "rgba(220, 0, 0, 0.25)" : c.status === "trace-collision" ? "rgba(220, 140, 0, 0.25)" : c.status === "text-collision" ? "rgba(160, 0, 220, 0.2)" : "rgba(120, 120, 120, 0.15)";
2061
+ const stroke = c.status === "ok" ? "green" : c.status === "chip-collision" ? "red" : c.status === "trace-collision" ? "orange" : c.status === "text-collision" ? "purple" : "gray";
2062
+ const candidateLabel = c.status === "ok" ? "status: ok(valid net label candidate)" : c.status === "chip-collision" ? "status: chip-collision" : c.status === "trace-collision" ? "status: trace-collision" : c.status === "text-collision" ? "status: text-collision" : "status: parallel-to-segment";
1945
2063
  graphics.rects.push({
1946
2064
  center: {
1947
2065
  x: (c.bounds.minX + c.bounds.maxX) / 2,
@@ -2129,6 +2247,19 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
2129
2247
  });
2130
2248
  continue;
2131
2249
  }
2250
+ if (rectIntersectsAnyTextBox(bounds, this.inputProblem)) {
2251
+ this.testedCandidates.push({
2252
+ center: testCenter,
2253
+ width,
2254
+ height,
2255
+ bounds,
2256
+ anchor,
2257
+ orientation,
2258
+ status: "text-collision",
2259
+ hostSegIndex: si
2260
+ });
2261
+ continue;
2262
+ }
2132
2263
  const traceIntersectionResult = rectIntersectsAnyTrace(
2133
2264
  bounds,
2134
2265
  this.inputTraceMap,
@@ -6476,6 +6607,9 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
6476
6607
  if (this.chipObstacleSpatialIndex.getChipsInBounds(bounds).length > 0) {
6477
6608
  return "chip-collision";
6478
6609
  }
6610
+ if (rectIntersectsAnyTextBox(bounds, this.inputProblem)) {
6611
+ return "text-collision";
6612
+ }
6479
6613
  if (this.sharesChipBoundary(bounds)) {
6480
6614
  return "chip-collision";
6481
6615
  }
@@ -6836,6 +6970,8 @@ var VccNetLabelCornerPlacementSolver = class extends BaseSolver {
6836
6970
  }
6837
6971
  getCandidateStatus(bounds, labelIndex) {
6838
6972
  if (this.intersectsAnyChip(bounds)) return "chip-collision";
6973
+ if (rectIntersectsAnyTextBox(bounds, this.inputProblem))
6974
+ return "text-collision";
6839
6975
  if (traceCrossesBoundsInterior(bounds, this.traceMap)) {
6840
6976
  return "trace-collision";
6841
6977
  }
@@ -7593,6 +7729,8 @@ var TraceAnchoredNetLabelOverlapSolver = class extends BaseSolver {
7593
7729
  getCandidateStatus(candidate, labelIndex) {
7594
7730
  const bounds = getLabelBounds(candidate);
7595
7731
  if (this.intersectsAnyChip(bounds)) return "chip-collision";
7732
+ if (rectIntersectsAnyTextBox(bounds, this.inputProblem))
7733
+ return "text-collision";
7596
7734
  if (traceCrossesBoundsInterior2(bounds, this.traces))
7597
7735
  return "trace-collision";
7598
7736
  if (this.intersectsAnyOtherLabel(bounds, labelIndex)) {
@@ -7896,15 +8034,17 @@ var CANDIDATE_STATUS_COLOR = {
7896
8034
  ok: "green",
7897
8035
  "label-collision": "orange",
7898
8036
  "trace-collision": "darkorange",
7899
- "chip-collision": "red"
8037
+ "chip-collision": "red",
8038
+ "text-collision": "purple"
7900
8039
  };
7901
8040
  var CANDIDATE_STATUS_FILL = {
7902
8041
  ok: "rgba(0, 200, 0, 0.25)",
7903
8042
  "label-collision": "rgba(255, 160, 0, 0.2)",
7904
8043
  "trace-collision": "rgba(200, 80, 0, 0.2)",
7905
- "chip-collision": "rgba(220, 0, 0, 0.15)"
8044
+ "chip-collision": "rgba(220, 0, 0, 0.15)",
8045
+ "text-collision": "rgba(128, 0, 128, 0.15)"
7906
8046
  };
7907
- function boundsOverlap(a, b) {
8047
+ function boundsOverlap2(a, b) {
7908
8048
  return a.minX < b.maxX - 1e-9 && a.maxX > b.minX + 1e-9 && a.minY < b.maxY - 1e-9 && a.maxY > b.minY + 1e-9;
7909
8049
  }
7910
8050
  function sampleAnchorsAlongSegment(a, b) {
@@ -7968,7 +8108,7 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
7968
8108
  const b = labels[j];
7969
8109
  if (a.globalConnNetId === b.globalConnNetId) continue;
7970
8110
  if (this.skippedCollisionKeys.has(this.collisionKey(a, b))) continue;
7971
- if (boundsOverlap(this.labelBounds(a), this.labelBounds(b)))
8111
+ if (boundsOverlap2(this.labelBounds(a), this.labelBounds(b)))
7972
8112
  return [a, b];
7973
8113
  }
7974
8114
  }
@@ -8060,12 +8200,14 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
8060
8200
  const { bounds, hostPairId, hostSegIndex } = candidate;
8061
8201
  if (this.chipIndex.getChipsInBounds(bounds).length > 0)
8062
8202
  return "chip-collision";
8203
+ if (rectIntersectsAnyTextBox(bounds, this.inputProblem))
8204
+ return "text-collision";
8063
8205
  if (rectIntersectsAnyTrace(bounds, this.traceMap, hostPairId, hostSegIndex).hasIntersection) {
8064
8206
  return "trace-collision";
8065
8207
  }
8066
8208
  for (const obstacle of obstacleLabels) {
8067
8209
  if (obstacle.globalConnNetId === movingLabelNetId) continue;
8068
- if (boundsOverlap(bounds, this.labelBounds(obstacle)))
8210
+ if (boundsOverlap2(bounds, this.labelBounds(obstacle)))
8069
8211
  return "label-collision";
8070
8212
  }
8071
8213
  return "ok";
@@ -36,6 +36,7 @@ import type {
36
36
  EvaluatedCandidate,
37
37
  } from "./types"
38
38
  import { visualizeAvailableNetOrientationSolver } from "./visualize"
39
+ import { rectIntersectsAnyTextBox } from "lib/utils/textBoxBounds"
39
40
 
40
41
  export class AvailableNetOrientationSolver extends BaseSolver {
41
42
  inputProblem: InputProblem
@@ -643,6 +644,9 @@ export class AvailableNetOrientationSolver extends BaseSolver {
643
644
  if (this.chipObstacleSpatialIndex.getChipsInBounds(bounds).length > 0) {
644
645
  return "chip-collision"
645
646
  }
647
+ if (rectIntersectsAnyTextBox(bounds, this.inputProblem)) {
648
+ return "text-collision"
649
+ }
646
650
  if (this.sharesChipBoundary(bounds)) {
647
651
  return "chip-collision"
648
652
  }
@@ -30,6 +30,7 @@ export type CandidateLabel = {
30
30
  export type CandidateStatus =
31
31
  | "valid"
32
32
  | "chip-collision"
33
+ | "text-collision"
33
34
  | "trace-collision"
34
35
  | "netlabel-collision"
35
36
 
@@ -14,10 +14,12 @@ import { rectIntersectsAnyTrace } from "lib/solvers/NetLabelPlacementSolver/Sing
14
14
  import { ChipObstacleSpatialIndex } from "lib/data-structures/ChipObstacleSpatialIndex"
15
15
  import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
16
16
  import { getColorFromString } from "lib/utils/getColorFromString"
17
+ import { rectIntersectsAnyTextBox } from "lib/utils/textBoxBounds"
17
18
 
18
19
  type CandidateStatus =
19
20
  | "ok"
20
21
  | "chip-collision"
22
+ | "text-collision"
21
23
  | "trace-collision"
22
24
  | "label-collision"
23
25
 
@@ -37,6 +39,7 @@ const CANDIDATE_STATUS_COLOR: Record<CandidateStatus, string> = {
37
39
  "label-collision": "orange",
38
40
  "trace-collision": "darkorange",
39
41
  "chip-collision": "red",
42
+ "text-collision": "purple",
40
43
  }
41
44
 
42
45
  const CANDIDATE_STATUS_FILL: Record<CandidateStatus, string> = {
@@ -44,6 +47,7 @@ const CANDIDATE_STATUS_FILL: Record<CandidateStatus, string> = {
44
47
  "label-collision": "rgba(255, 160, 0, 0.2)",
45
48
  "trace-collision": "rgba(200, 80, 0, 0.2)",
46
49
  "chip-collision": "rgba(220, 0, 0, 0.15)",
50
+ "text-collision": "rgba(128, 0, 128, 0.15)",
47
51
  }
48
52
 
49
53
  type Candidate = {
@@ -269,6 +273,8 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
269
273
 
270
274
  if (this.chipIndex.getChipsInBounds(bounds).length > 0)
271
275
  return "chip-collision"
276
+ if (rectIntersectsAnyTextBox(bounds, this.inputProblem))
277
+ return "text-collision"
272
278
 
273
279
  if (
274
280
  rectIntersectsAnyTrace(bounds, this.traceMap, hostPairId, hostSegIndex)