@tscircuit/schematic-trace-solver 0.0.76 → 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 (25) 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 +226 -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/SchematicTracePipelineSolver/visualizeInputProblem.ts +11 -0
  18. package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/TraceAnchoredNetLabelOverlapSolver.ts +3 -0
  19. package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/types.ts +1 -0
  20. package/lib/solvers/VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver.ts +3 -0
  21. package/lib/solvers/VccNetLabelCornerPlacementSolver/types.ts +1 -0
  22. package/lib/utils/textBoxBounds.ts +40 -0
  23. package/package.json +1 -1
  24. package/tests/repros/__snapshots__/manufacturePartNumber-text-box.snap.svg +88 -0
  25. package/tests/repros/manufacturePartNumber-text-box.test.ts +57 -0
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
@@ -357,6 +357,16 @@ ${pin._facingDirection ?? getPinDirection(pin, chip)}`,
357
357
  });
358
358
  }
359
359
  }
360
+ for (const textBox of inputProblem.textBoxes ?? []) {
361
+ graphics.rects.push({
362
+ label: textBox.text ?? "schematic_text",
363
+ center: textBox.center,
364
+ width: textBox.width,
365
+ height: textBox.height,
366
+ fill: "rgba(160, 0, 220, 0.14)",
367
+ strokeColor: "rgba(160, 0, 220, 0.9)"
368
+ });
369
+ }
360
370
  for (const directConn of inputProblem.directConnections) {
361
371
  const [pinId1, pinId2] = directConn.pinIds;
362
372
  const pin1 = pinIdMap.get(pinId1);
@@ -581,13 +591,41 @@ function getInputChipBounds(chip) {
581
591
  };
582
592
  }
583
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
+
584
613
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts
585
614
  var chipToRect = (chip) => {
586
615
  const b = getInputChipBounds(chip);
587
- return { chipId: chip.chipId, ...b };
616
+ return { kind: "chip", chipId: chip.chipId, ...b };
588
617
  };
589
- var getObstacleRects = (problem) => {
590
- 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];
591
629
  };
592
630
 
593
631
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts
@@ -618,9 +656,9 @@ var findFirstCollision = (pts, rects, opts = {}) => {
618
656
  for (let i = 0; i < pts.length - 1; i++) {
619
657
  const a = pts[i];
620
658
  const b = pts[i + 1];
621
- const excluded = opts.excludeRectIdsForSegment?.(i) ?? /* @__PURE__ */ new Set();
659
+ const excluded = opts.excludeRectsForSegment?.(i) ?? /* @__PURE__ */ new Set();
622
660
  for (const r of rects) {
623
- if (excluded.has(r.chipId)) continue;
661
+ if (excluded.has(r)) continue;
624
662
  if (segmentIntersectsRect(a, b, r)) {
625
663
  return { segIndex: i, rect: r };
626
664
  }
@@ -666,8 +704,8 @@ var midBetweenPointAndRect = (axis, p, r, eps = EPS2) => {
666
704
  return [r.minY - 0.2, r.maxY + 0.2];
667
705
  }
668
706
  };
669
- var candidateMidsFromSet = (axis, colliding, rectsById, collisionRectIds, aabb, eps = EPS2) => {
670
- const setRects = [...collisionRectIds].map((id) => rectsById.get(id)).filter((r) => !!r);
707
+ var candidateMidsFromSet = (axis, colliding, collisionRects, aabb, eps = EPS2) => {
708
+ const setRects = [...collisionRects];
671
709
  if (axis === "x") {
672
710
  const leftBoundaries = [aabb.minX, ...setRects.map((r) => r.maxX)].filter(
673
711
  (v) => v < colliding.minX - eps
@@ -768,13 +806,54 @@ var pathKey = (pts, decimals = 6) => {
768
806
  return key;
769
807
  };
770
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
+
771
849
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts
772
850
  var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
773
851
  pins;
852
+ connectionPair;
774
853
  inputProblem;
775
854
  chipMap;
776
855
  obstacles;
777
- rectById;
856
+ textObstacles;
778
857
  aabb;
779
858
  baseElbow;
780
859
  solvedTracePath = null;
@@ -783,6 +862,7 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
783
862
  constructor(params) {
784
863
  super();
785
864
  this.pins = params.pins;
865
+ this.connectionPair = params.connectionPair;
786
866
  this.inputProblem = params.inputProblem;
787
867
  this.chipMap = params.chipMap;
788
868
  for (const pin of this.pins) {
@@ -791,8 +871,12 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
791
871
  pin._facingDirection = getPinDirection(pin, chip);
792
872
  }
793
873
  }
794
- this.obstacles = getObstacleRects(this.inputProblem);
795
- 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
+ );
796
880
  const [pin1, pin2] = this.pins;
797
881
  this.baseElbow = calculateElbow(
798
882
  {
@@ -811,16 +895,84 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
811
895
  { x: pin1.x, y: pin1.y },
812
896
  { x: pin2.x, y: pin2.y }
813
897
  );
814
- this.queue.push({ path: this.baseElbow, collisionChipIds: /* @__PURE__ */ new Set() });
898
+ this.queue.push({ path: this.baseElbow, collisionRects: /* @__PURE__ */ new Set() });
815
899
  this.visited.add(pathKey(this.baseElbow));
816
900
  }
817
901
  getConstructorParams() {
818
902
  return {
819
903
  chipMap: this.chipMap,
820
904
  pins: this.pins,
905
+ connectionPair: this.connectionPair,
821
906
  inputProblem: this.inputProblem
822
907
  };
823
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
+ }
824
976
  axisOfSegment(a, b) {
825
977
  if (isVertical(a, b)) return "x";
826
978
  if (isHorizontal(a, b)) return "y";
@@ -844,9 +996,17 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
844
996
  this.error = "No collision-free path found";
845
997
  return;
846
998
  }
847
- const { path, collisionChipIds } = state;
999
+ const { path, collisionRects } = state;
848
1000
  const [PA, PB] = this.pins;
849
- 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
+ });
850
1010
  if (!collision) {
851
1011
  const first = path[0];
852
1012
  const last = path[path.length - 1];
@@ -879,20 +1039,14 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
879
1039
  return;
880
1040
  }
881
1041
  const candidates = [];
882
- if (collisionChipIds.size === 0) {
1042
+ if (collisionRects.size === 0) {
883
1043
  const m1 = midBetweenPointAndRect(axis, { x: PA.x, y: PA.y }, rect);
884
1044
  const m2 = midBetweenPointAndRect(axis, { x: PB.x, y: PB.y }, rect);
885
1045
  const allCandidates = [...m1, ...m2];
886
1046
  const uniqueCandidates = [...new Set(allCandidates)];
887
1047
  candidates.push(...uniqueCandidates);
888
1048
  } else {
889
- const mids = candidateMidsFromSet(
890
- axis,
891
- rect,
892
- this.rectById,
893
- collisionChipIds,
894
- this.aabb
895
- );
1049
+ const mids = candidateMidsFromSet(axis, rect, collisionRects, this.aabb);
896
1050
  candidates.push(...mids);
897
1051
  }
898
1052
  const newStates = [];
@@ -902,14 +1056,14 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
902
1056
  const key = pathKey(newPath);
903
1057
  if (this.visited.has(key)) continue;
904
1058
  this.visited.add(key);
905
- const nextSet = new Set(collisionChipIds);
906
- nextSet.add(rect.chipId);
1059
+ const nextSet = new Set(collisionRects);
1060
+ nextSet.add(rect);
907
1061
  const len = this.pathLength(newPath);
908
- newStates.push({ path: newPath, collisionRectIds: nextSet, len });
1062
+ newStates.push({ path: newPath, collisionRects: nextSet, len });
909
1063
  }
910
1064
  newStates.sort((a2, b2) => a2.len - b2.len);
911
1065
  for (const st of newStates) {
912
- this.queue.push({ path: st.path, collisionChipIds: st.collisionRectIds });
1066
+ this.queue.push({ path: st.path, collisionRects: st.collisionRects });
913
1067
  }
914
1068
  }
915
1069
  visualize() {
@@ -931,7 +1085,7 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
931
1085
  strokeColor: "blue",
932
1086
  strokeDash: "5 5"
933
1087
  });
934
- for (const { path, collisionChipIds: collisionRectIds } of this.queue) {
1088
+ for (const { path } of this.queue) {
935
1089
  g.lines.push({ points: path, strokeColor: "teal", strokeDash: "2 2" });
936
1090
  }
937
1091
  if (this.solvedTracePath) {
@@ -1013,6 +1167,7 @@ var SchematicTraceLinesSolver = class extends BaseSolver {
1013
1167
  this.activeSubSolver = new SchematicTraceSingleLineSolver2({
1014
1168
  inputProblem: this.inputProblem,
1015
1169
  pins,
1170
+ connectionPair,
1016
1171
  chipMap: this.chipMap
1017
1172
  });
1018
1173
  }
@@ -1628,46 +1783,6 @@ var ChipObstacleSpatialIndex = class {
1628
1783
  }
1629
1784
  };
1630
1785
 
1631
- // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry.ts
1632
- var NET_LABEL_HORIZONTAL_WIDTH = 0.45;
1633
- var NET_LABEL_HORIZONTAL_HEIGHT = 0.2;
1634
- function getDimsForOrientation(params) {
1635
- const { orientation, netLabelWidth, netLabelHeight } = params;
1636
- const horizWidth = typeof netLabelWidth === "number" ? netLabelWidth : NET_LABEL_HORIZONTAL_WIDTH;
1637
- const horizHeight = typeof netLabelHeight === "number" ? netLabelHeight : NET_LABEL_HORIZONTAL_HEIGHT;
1638
- if (orientation === "y+" || orientation === "y-") {
1639
- return {
1640
- // Rotated: horizontal length = netLabelHeight, vertical length = netLabelWidth
1641
- width: horizHeight,
1642
- height: horizWidth
1643
- };
1644
- }
1645
- return {
1646
- width: horizWidth,
1647
- height: horizHeight
1648
- };
1649
- }
1650
- function getCenterFromAnchor(anchor, orientation, width, height) {
1651
- switch (orientation) {
1652
- case "x+":
1653
- return { x: anchor.x + width / 2, y: anchor.y };
1654
- case "x-":
1655
- return { x: anchor.x - width / 2, y: anchor.y };
1656
- case "y+":
1657
- return { x: anchor.x, y: anchor.y + height / 2 };
1658
- case "y-":
1659
- return { x: anchor.x, y: anchor.y - height / 2 };
1660
- }
1661
- }
1662
- function getRectBounds(center, w, h) {
1663
- return {
1664
- minX: center.x - w / 2,
1665
- minY: center.y - h / 2,
1666
- maxX: center.x + w / 2,
1667
- maxY: center.y + h / 2
1668
- };
1669
- }
1670
-
1671
1786
  // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions.ts
1672
1787
  function segmentIntersectsRect2(p1, p2, rect, EPS11 = 1e-9) {
1673
1788
  const isVert = Math.abs(p1.x - p2.x) < EPS11;
@@ -1833,6 +1948,19 @@ function solveNetLabelPlacementForPortOnlyPin(params) {
1833
1948
  });
1834
1949
  continue;
1835
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
+ }
1836
1964
  const traceIntersectionResult = rectIntersectsAnyTrace(
1837
1965
  bounds,
1838
1966
  inputTraceMap,
@@ -1929,9 +2057,9 @@ function visualizeSingleNetLabelPlacementSolver(solver) {
1929
2057
  });
1930
2058
  }
1931
2059
  for (const c of solver.testedCandidates) {
1932
- 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)";
1933
- const stroke = c.status === "ok" ? "green" : c.status === "chip-collision" ? "red" : c.status === "trace-collision" ? "orange" : "gray";
1934
- 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";
1935
2063
  graphics.rects.push({
1936
2064
  center: {
1937
2065
  x: (c.bounds.minX + c.bounds.maxX) / 2,
@@ -2119,6 +2247,19 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
2119
2247
  });
2120
2248
  continue;
2121
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
+ }
2122
2263
  const traceIntersectionResult = rectIntersectsAnyTrace(
2123
2264
  bounds,
2124
2265
  this.inputTraceMap,
@@ -6466,6 +6607,9 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
6466
6607
  if (this.chipObstacleSpatialIndex.getChipsInBounds(bounds).length > 0) {
6467
6608
  return "chip-collision";
6468
6609
  }
6610
+ if (rectIntersectsAnyTextBox(bounds, this.inputProblem)) {
6611
+ return "text-collision";
6612
+ }
6469
6613
  if (this.sharesChipBoundary(bounds)) {
6470
6614
  return "chip-collision";
6471
6615
  }
@@ -6826,6 +6970,8 @@ var VccNetLabelCornerPlacementSolver = class extends BaseSolver {
6826
6970
  }
6827
6971
  getCandidateStatus(bounds, labelIndex) {
6828
6972
  if (this.intersectsAnyChip(bounds)) return "chip-collision";
6973
+ if (rectIntersectsAnyTextBox(bounds, this.inputProblem))
6974
+ return "text-collision";
6829
6975
  if (traceCrossesBoundsInterior(bounds, this.traceMap)) {
6830
6976
  return "trace-collision";
6831
6977
  }
@@ -7583,6 +7729,8 @@ var TraceAnchoredNetLabelOverlapSolver = class extends BaseSolver {
7583
7729
  getCandidateStatus(candidate, labelIndex) {
7584
7730
  const bounds = getLabelBounds(candidate);
7585
7731
  if (this.intersectsAnyChip(bounds)) return "chip-collision";
7732
+ if (rectIntersectsAnyTextBox(bounds, this.inputProblem))
7733
+ return "text-collision";
7586
7734
  if (traceCrossesBoundsInterior2(bounds, this.traces))
7587
7735
  return "trace-collision";
7588
7736
  if (this.intersectsAnyOtherLabel(bounds, labelIndex)) {
@@ -7886,15 +8034,17 @@ var CANDIDATE_STATUS_COLOR = {
7886
8034
  ok: "green",
7887
8035
  "label-collision": "orange",
7888
8036
  "trace-collision": "darkorange",
7889
- "chip-collision": "red"
8037
+ "chip-collision": "red",
8038
+ "text-collision": "purple"
7890
8039
  };
7891
8040
  var CANDIDATE_STATUS_FILL = {
7892
8041
  ok: "rgba(0, 200, 0, 0.25)",
7893
8042
  "label-collision": "rgba(255, 160, 0, 0.2)",
7894
8043
  "trace-collision": "rgba(200, 80, 0, 0.2)",
7895
- "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)"
7896
8046
  };
7897
- function boundsOverlap(a, b) {
8047
+ function boundsOverlap2(a, b) {
7898
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;
7899
8049
  }
7900
8050
  function sampleAnchorsAlongSegment(a, b) {
@@ -7958,7 +8108,7 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
7958
8108
  const b = labels[j];
7959
8109
  if (a.globalConnNetId === b.globalConnNetId) continue;
7960
8110
  if (this.skippedCollisionKeys.has(this.collisionKey(a, b))) continue;
7961
- if (boundsOverlap(this.labelBounds(a), this.labelBounds(b)))
8111
+ if (boundsOverlap2(this.labelBounds(a), this.labelBounds(b)))
7962
8112
  return [a, b];
7963
8113
  }
7964
8114
  }
@@ -8050,12 +8200,14 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
8050
8200
  const { bounds, hostPairId, hostSegIndex } = candidate;
8051
8201
  if (this.chipIndex.getChipsInBounds(bounds).length > 0)
8052
8202
  return "chip-collision";
8203
+ if (rectIntersectsAnyTextBox(bounds, this.inputProblem))
8204
+ return "text-collision";
8053
8205
  if (rectIntersectsAnyTrace(bounds, this.traceMap, hostPairId, hostSegIndex).hasIntersection) {
8054
8206
  return "trace-collision";
8055
8207
  }
8056
8208
  for (const obstacle of obstacleLabels) {
8057
8209
  if (obstacle.globalConnNetId === movingLabelNetId) continue;
8058
- if (boundsOverlap(bounds, this.labelBounds(obstacle)))
8210
+ if (boundsOverlap2(bounds, this.labelBounds(obstacle)))
8059
8211
  return "label-collision";
8060
8212
  }
8061
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)