@tscircuit/checks 0.0.138 → 0.0.139

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
@@ -1,3 +1,266 @@
1
+ // lib/check-traces-are-contiguous/is-point-in-pad.ts
2
+ import { pointToSegmentDistance } from "@tscircuit/math-utils";
3
+
4
+ // lib/check-each-pcb-trace-non-overlapping/segment-to-polygon-clearance.ts
5
+ import {
6
+ distSq,
7
+ getSegmentIntersection,
8
+ isPointInsidePolygon,
9
+ pointToSegmentClosestPoint,
10
+ segmentToSegmentMinDistance
11
+ } from "@tscircuit/math-utils";
12
+ var rotatePoint = (point, angleDegrees) => {
13
+ const angle = angleDegrees * Math.PI / 180;
14
+ return {
15
+ x: point.x * Math.cos(angle) - point.y * Math.sin(angle),
16
+ y: point.x * Math.sin(angle) + point.y * Math.cos(angle)
17
+ };
18
+ };
19
+ var getRotatedRectPoints = ({
20
+ x,
21
+ y,
22
+ width,
23
+ height,
24
+ ccwRotation
25
+ }) => {
26
+ const halfWidth = width / 2;
27
+ const halfHeight = height / 2;
28
+ return [
29
+ { x: -halfWidth, y: -halfHeight },
30
+ { x: halfWidth, y: -halfHeight },
31
+ { x: halfWidth, y: halfHeight },
32
+ { x: -halfWidth, y: halfHeight }
33
+ ].map((point) => {
34
+ const rotated = rotatePoint(point, ccwRotation);
35
+ return { x: x + rotated.x, y: y + rotated.y };
36
+ });
37
+ };
38
+ var getPillCenterLineForPad = (pad) => {
39
+ const ccwRotation = pad.shape === "rotated_pill" ? pad.ccw_rotation : 0;
40
+ const halfLineLength = Math.max(
41
+ Math.max(pad.width, pad.height) / 2 - pad.radius,
42
+ 0
43
+ );
44
+ const axis = pad.width >= pad.height ? { x: halfLineLength, y: 0 } : { x: 0, y: halfLineLength };
45
+ const rotatedAxis = rotatePoint(axis, ccwRotation);
46
+ return {
47
+ start: { x: pad.x - rotatedAxis.x, y: pad.y - rotatedAxis.y },
48
+ end: { x: pad.x + rotatedAxis.x, y: pad.y + rotatedAxis.y },
49
+ radius: pad.radius
50
+ };
51
+ };
52
+ var getPolygonPointsForPad = (pad) => {
53
+ if (pad.type === "pcb_smtpad") {
54
+ if (pad.shape === "polygon") return pad.points;
55
+ if (pad.shape === "rotated_rect") {
56
+ return getRotatedRectPoints({
57
+ x: pad.x,
58
+ y: pad.y,
59
+ width: pad.width,
60
+ height: pad.height,
61
+ ccwRotation: pad.ccw_rotation
62
+ });
63
+ }
64
+ }
65
+ if (pad.type === "pcb_plated_hole" && "rect_pad_width" in pad && "rect_pad_height" in pad) {
66
+ return getRotatedRectPoints({
67
+ x: pad.x,
68
+ y: pad.y,
69
+ width: pad.rect_pad_width,
70
+ height: pad.rect_pad_height,
71
+ ccwRotation: "rect_ccw_rotation" in pad && typeof pad.rect_ccw_rotation === "number" ? pad.rect_ccw_rotation : 0
72
+ });
73
+ }
74
+ throw new Error(
75
+ `Expected polygonal pad geometry, got ${pad.type} with shape "${pad.shape}"`
76
+ );
77
+ };
78
+ var getPolygonEdges = (points) => points.map(
79
+ (point, index) => [point, points[(index + 1) % points.length]]
80
+ );
81
+ var getClosestPointsBetweenSegments = (a1, a2, b1, b2) => {
82
+ const intersection = getSegmentIntersection(a1, a2, b1, b2);
83
+ if (intersection) {
84
+ return {
85
+ distance: 0,
86
+ pointOnA: intersection,
87
+ pointOnB: intersection,
88
+ center: intersection
89
+ };
90
+ }
91
+ const candidates = [
92
+ { pointOnA: a1, pointOnB: pointToSegmentClosestPoint(a1, b1, b2) },
93
+ { pointOnA: a2, pointOnB: pointToSegmentClosestPoint(a2, b1, b2) },
94
+ { pointOnA: pointToSegmentClosestPoint(b1, a1, a2), pointOnB: b1 },
95
+ { pointOnA: pointToSegmentClosestPoint(b2, a1, a2), pointOnB: b2 }
96
+ ];
97
+ let best = candidates[0];
98
+ let bestDistanceSquared = distSq(best.pointOnA, best.pointOnB);
99
+ for (const candidate of candidates.slice(1)) {
100
+ const candidateDistanceSquared = distSq(
101
+ candidate.pointOnA,
102
+ candidate.pointOnB
103
+ );
104
+ if (candidateDistanceSquared < bestDistanceSquared) {
105
+ best = candidate;
106
+ bestDistanceSquared = candidateDistanceSquared;
107
+ }
108
+ }
109
+ return {
110
+ distance: segmentToSegmentMinDistance(a1, a2, b1, b2),
111
+ pointOnA: best.pointOnA,
112
+ pointOnB: best.pointOnB,
113
+ center: {
114
+ x: (best.pointOnA.x + best.pointOnB.x) / 2,
115
+ y: (best.pointOnA.y + best.pointOnB.y) / 2
116
+ }
117
+ };
118
+ };
119
+ var getSegmentToPolygonClearanceFromPoints = (start, end, polygon) => {
120
+ if (polygon.length < 3) {
121
+ return { distance: Number.POSITIVE_INFINITY, center: start };
122
+ }
123
+ const intersections = getPolygonEdges(polygon).map(
124
+ ([edgeStart, edgeEnd]) => getSegmentIntersection(start, end, edgeStart, edgeEnd)
125
+ ).filter((point) => point !== null);
126
+ if (intersections.length > 0) {
127
+ const dx = end.x - start.x;
128
+ const dy = end.y - start.y;
129
+ const lengthSquared = dx * dx + dy * dy;
130
+ intersections.sort((a, b) => {
131
+ const ta = ((a.x - start.x) * dx + (a.y - start.y) * dy) / lengthSquared;
132
+ const tb = ((b.x - start.x) * dx + (b.y - start.y) * dy) / lengthSquared;
133
+ return ta - tb;
134
+ });
135
+ return { distance: 0, center: intersections[0] };
136
+ }
137
+ if (isPointInsidePolygon(start, polygon) || isPointInsidePolygon(end, polygon)) {
138
+ return {
139
+ distance: 0,
140
+ center: {
141
+ x: (start.x + end.x) / 2,
142
+ y: (start.y + end.y) / 2
143
+ }
144
+ };
145
+ }
146
+ let best = getClosestPointsBetweenSegments(
147
+ start,
148
+ end,
149
+ polygon[0],
150
+ polygon[1]
151
+ );
152
+ for (const [edgeStart, edgeEnd] of getPolygonEdges(polygon).slice(1)) {
153
+ const candidate = getClosestPointsBetweenSegments(
154
+ start,
155
+ end,
156
+ edgeStart,
157
+ edgeEnd
158
+ );
159
+ if (candidate.distance < best.distance) best = candidate;
160
+ }
161
+ return { distance: best.distance, center: best.center };
162
+ };
163
+ var getSegmentToPolygonClearance = (segment, polygon) => getSegmentToPolygonClearanceFromPoints(
164
+ { x: segment.x1, y: segment.y1 },
165
+ { x: segment.x2, y: segment.y2 },
166
+ polygon
167
+ );
168
+ var getSegmentToPillClearance = (segment, pad) => {
169
+ const pill = getPillCenterLineForPad(pad);
170
+ const closest = getClosestPointsBetweenSegments(
171
+ { x: segment.x1, y: segment.y1 },
172
+ { x: segment.x2, y: segment.y2 },
173
+ pill.start,
174
+ pill.end
175
+ );
176
+ return {
177
+ distance: closest.distance,
178
+ center: closest.center,
179
+ radius: pill.radius
180
+ };
181
+ };
182
+
183
+ // lib/check-traces-are-contiguous/is-point-in-pad.ts
184
+ function getDistanceBetweenPoints(pointA, pointB) {
185
+ return Math.sqrt((pointB.x - pointA.x) ** 2 + (pointB.y - pointA.y) ** 2);
186
+ }
187
+ var POINT_ON_SEGMENT_TOLERANCE_MM = 1e-9;
188
+ function isPointOnSegment(point, segment) {
189
+ const crossProduct = (point.y - segment.start.y) * (segment.end.x - segment.start.x) - (point.x - segment.start.x) * (segment.end.y - segment.start.y);
190
+ if (Math.abs(crossProduct) > POINT_ON_SEGMENT_TOLERANCE_MM) return false;
191
+ const dotProduct = (point.x - segment.start.x) * (segment.end.x - segment.start.x) + (point.y - segment.start.y) * (segment.end.y - segment.start.y);
192
+ if (dotProduct < -POINT_ON_SEGMENT_TOLERANCE_MM) return false;
193
+ const squaredLength = (segment.end.x - segment.start.x) ** 2 + (segment.end.y - segment.start.y) ** 2;
194
+ return dotProduct <= squaredLength + POINT_ON_SEGMENT_TOLERANCE_MM;
195
+ }
196
+ function isPointInPad(point, pad) {
197
+ if (pad.type === "pcb_smtpad") {
198
+ if (pad.shape === "circle") {
199
+ return getDistanceBetweenPoints(point, pad) <= pad.radius;
200
+ }
201
+ if (pad.shape === "rect") {
202
+ const halfWidth = pad.width / 2;
203
+ const halfHeight = pad.height / 2;
204
+ return Math.abs(point.x - pad.x) <= halfWidth && Math.abs(point.y - pad.y) <= halfHeight;
205
+ }
206
+ if (pad.shape === "rotated_rect") {
207
+ const dx = point.x - pad.x;
208
+ const dy = point.y - pad.y;
209
+ const angle = -pad.ccw_rotation;
210
+ const rotatedX = dx * Math.cos(angle) - dy * Math.sin(angle);
211
+ const rotatedY = dx * Math.sin(angle) + dy * Math.cos(angle);
212
+ return Math.abs(rotatedX) <= pad.width / 2 && Math.abs(rotatedY) <= pad.height / 2;
213
+ }
214
+ if (pad.shape === "pill" || pad.shape === "rotated_pill") {
215
+ if (pad.shape === "rotated_pill") {
216
+ const pill = getPillCenterLineForPad(pad);
217
+ return pointToSegmentDistance(point, pill.start, pill.end) <= pill.radius;
218
+ }
219
+ const halfWidth = pad.width / 2;
220
+ const halfHeight = pad.height / 2;
221
+ const radius = pad.radius;
222
+ if (Math.abs(point.x - pad.x) <= halfWidth - radius && Math.abs(point.y - pad.y) <= halfHeight) {
223
+ return true;
224
+ }
225
+ const cornerX = Math.max(
226
+ Math.abs(point.x - pad.x) - (halfWidth - radius),
227
+ 0
228
+ );
229
+ const cornerY = Math.max(
230
+ Math.abs(point.y - pad.y) - (halfHeight - radius),
231
+ 0
232
+ );
233
+ return cornerX * cornerX + cornerY * cornerY <= radius * radius;
234
+ }
235
+ if (pad.shape === "polygon") {
236
+ let inside = false;
237
+ for (let i = 0, j = pad.points.length - 1; i < pad.points.length; j = i++) {
238
+ const pi = pad.points[i];
239
+ const pj = pad.points[j];
240
+ if (isPointOnSegment(point, { start: pi, end: pj })) return true;
241
+ const intersects = pi.y > point.y !== pj.y > point.y && point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x;
242
+ if (intersects) inside = !inside;
243
+ }
244
+ return inside;
245
+ }
246
+ }
247
+ if (pad.type === "pcb_plated_hole") {
248
+ if (pad.shape === "circle") {
249
+ return getDistanceBetweenPoints(point, pad) <= pad.outer_diameter / 2;
250
+ }
251
+ if (pad.shape === "oval" || pad.shape === "pill") {
252
+ return Math.abs(point.x - pad.x) <= pad.outer_width / 2 && Math.abs(point.y - pad.y) <= pad.outer_height / 2;
253
+ }
254
+ if (pad.shape === "circular_hole_with_rect_pad") {
255
+ return Math.abs(point.x - pad.x) <= pad.rect_pad_width / 2 && Math.abs(point.y - pad.y) <= pad.rect_pad_height / 2;
256
+ }
257
+ if (pad.shape === "pill_hole_with_rect_pad") {
258
+ return Math.abs(point.x - pad.x) <= pad.rect_pad_width / 2 && Math.abs(point.y - pad.y) <= pad.rect_pad_height / 2;
259
+ }
260
+ }
261
+ return false;
262
+ }
263
+
1
264
  // lib/add-start-and-end-port-ids-if-missing.ts
2
265
  function distance(x1, y1, x2, y2) {
3
266
  return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
@@ -20,6 +283,8 @@ var addStartAndEndPortIdsIfMissing = (soup) => {
20
283
  return Math.abs(point.x - pad.x) < pad.width / 2 + traceWidth / 2 && Math.abs(point.y - pad.y) < pad.height / 2 + traceWidth / 2;
21
284
  } else if (pad.shape === "circle") {
22
285
  return distance(point.x, point.y, pad.x, pad.y) < pad.radius;
286
+ } else if (pad.shape === "pill" || pad.shape === "rotated_pill") {
287
+ return isPointInPad(point, pad);
23
288
  }
24
289
  });
25
290
  if (smtPad) return smtPad.pcb_port_id ?? null;
@@ -636,156 +901,6 @@ var clamp = (value, min, max) => {
636
901
 
637
902
  // lib/check-each-pcb-trace-non-overlapping/getCollidableBounds.ts
638
903
  import { getBoundsOfPcbElements as getBoundsOfPcbElements2 } from "@tscircuit/circuit-json-util";
639
-
640
- // lib/check-each-pcb-trace-non-overlapping/segment-to-polygon-clearance.ts
641
- import {
642
- distSq,
643
- getSegmentIntersection,
644
- isPointInsidePolygon,
645
- pointToSegmentClosestPoint,
646
- segmentToSegmentMinDistance
647
- } from "@tscircuit/math-utils";
648
- var rotatePoint = (point, angleDegrees) => {
649
- const angle = angleDegrees * Math.PI / 180;
650
- return {
651
- x: point.x * Math.cos(angle) - point.y * Math.sin(angle),
652
- y: point.x * Math.sin(angle) + point.y * Math.cos(angle)
653
- };
654
- };
655
- var getRotatedRectPoints = ({
656
- x,
657
- y,
658
- width,
659
- height,
660
- ccwRotation
661
- }) => {
662
- const halfWidth = width / 2;
663
- const halfHeight = height / 2;
664
- return [
665
- { x: -halfWidth, y: -halfHeight },
666
- { x: halfWidth, y: -halfHeight },
667
- { x: halfWidth, y: halfHeight },
668
- { x: -halfWidth, y: halfHeight }
669
- ].map((point) => {
670
- const rotated = rotatePoint(point, ccwRotation);
671
- return { x: x + rotated.x, y: y + rotated.y };
672
- });
673
- };
674
- var getPolygonPointsForPad = (pad) => {
675
- if (pad.type === "pcb_smtpad") {
676
- if (pad.shape === "polygon") return pad.points;
677
- if (pad.shape === "rotated_rect") {
678
- return getRotatedRectPoints({
679
- x: pad.x,
680
- y: pad.y,
681
- width: pad.width,
682
- height: pad.height,
683
- ccwRotation: pad.ccw_rotation
684
- });
685
- }
686
- }
687
- if (pad.type === "pcb_plated_hole" && "rect_pad_width" in pad && "rect_pad_height" in pad) {
688
- return getRotatedRectPoints({
689
- x: pad.x,
690
- y: pad.y,
691
- width: pad.rect_pad_width,
692
- height: pad.rect_pad_height,
693
- ccwRotation: "rect_ccw_rotation" in pad && typeof pad.rect_ccw_rotation === "number" ? pad.rect_ccw_rotation : 0
694
- });
695
- }
696
- throw new Error(
697
- `Expected polygonal pad geometry, got ${pad.type} with shape "${pad.shape}"`
698
- );
699
- };
700
- var getPolygonEdges = (points) => points.map(
701
- (point, index) => [point, points[(index + 1) % points.length]]
702
- );
703
- var getClosestPointsBetweenSegments = (a1, a2, b1, b2) => {
704
- const intersection = getSegmentIntersection(a1, a2, b1, b2);
705
- if (intersection) {
706
- return {
707
- distance: 0,
708
- pointOnA: intersection,
709
- pointOnB: intersection,
710
- center: intersection
711
- };
712
- }
713
- const candidates = [
714
- { pointOnA: a1, pointOnB: pointToSegmentClosestPoint(a1, b1, b2) },
715
- { pointOnA: a2, pointOnB: pointToSegmentClosestPoint(a2, b1, b2) },
716
- { pointOnA: pointToSegmentClosestPoint(b1, a1, a2), pointOnB: b1 },
717
- { pointOnA: pointToSegmentClosestPoint(b2, a1, a2), pointOnB: b2 }
718
- ];
719
- let best = candidates[0];
720
- let bestDistanceSquared = distSq(best.pointOnA, best.pointOnB);
721
- for (const candidate of candidates.slice(1)) {
722
- const candidateDistanceSquared = distSq(
723
- candidate.pointOnA,
724
- candidate.pointOnB
725
- );
726
- if (candidateDistanceSquared < bestDistanceSquared) {
727
- best = candidate;
728
- bestDistanceSquared = candidateDistanceSquared;
729
- }
730
- }
731
- return {
732
- distance: segmentToSegmentMinDistance(a1, a2, b1, b2),
733
- pointOnA: best.pointOnA,
734
- pointOnB: best.pointOnB,
735
- center: {
736
- x: (best.pointOnA.x + best.pointOnB.x) / 2,
737
- y: (best.pointOnA.y + best.pointOnB.y) / 2
738
- }
739
- };
740
- };
741
- var getSegmentToPolygonClearance = (segment, polygon) => {
742
- const start = { x: segment.x1, y: segment.y1 };
743
- const end = { x: segment.x2, y: segment.y2 };
744
- if (polygon.length < 3) {
745
- return { distance: Number.POSITIVE_INFINITY, center: start };
746
- }
747
- const intersections = getPolygonEdges(polygon).map(
748
- ([edgeStart, edgeEnd]) => getSegmentIntersection(start, end, edgeStart, edgeEnd)
749
- ).filter((point) => point !== null);
750
- if (intersections.length > 0) {
751
- const dx = end.x - start.x;
752
- const dy = end.y - start.y;
753
- const lengthSquared = dx * dx + dy * dy;
754
- intersections.sort((a, b) => {
755
- const ta = ((a.x - start.x) * dx + (a.y - start.y) * dy) / lengthSquared;
756
- const tb = ((b.x - start.x) * dx + (b.y - start.y) * dy) / lengthSquared;
757
- return ta - tb;
758
- });
759
- return { distance: 0, center: intersections[0] };
760
- }
761
- if (isPointInsidePolygon(start, polygon) || isPointInsidePolygon(end, polygon)) {
762
- return {
763
- distance: 0,
764
- center: {
765
- x: (start.x + end.x) / 2,
766
- y: (start.y + end.y) / 2
767
- }
768
- };
769
- }
770
- let best = getClosestPointsBetweenSegments(
771
- start,
772
- end,
773
- polygon[0],
774
- polygon[1]
775
- );
776
- for (const [edgeStart, edgeEnd] of getPolygonEdges(polygon).slice(1)) {
777
- const candidate = getClosestPointsBetweenSegments(
778
- start,
779
- end,
780
- edgeStart,
781
- edgeEnd
782
- );
783
- if (candidate.distance < best.distance) best = candidate;
784
- }
785
- return { distance: best.distance, center: best.center };
786
- };
787
-
788
- // lib/check-each-pcb-trace-non-overlapping/getCollidableBounds.ts
789
904
  var getCollidableBounds = (collidable) => {
790
905
  if (collidable.type === "pcb_trace_segment") {
791
906
  return {
@@ -806,6 +921,15 @@ var getCollidableBounds = (collidable) => {
806
921
  maxY: Math.max(...polygonPoints.map((point) => point.y))
807
922
  };
808
923
  }
924
+ if (collidable.type === "pcb_smtpad" && collidable.shape === "rotated_pill") {
925
+ const pill = getPillCenterLineForPad(collidable);
926
+ return {
927
+ minX: Math.min(pill.start.x, pill.end.x) - pill.radius,
928
+ minY: Math.min(pill.start.y, pill.end.y) - pill.radius,
929
+ maxX: Math.max(pill.start.x, pill.end.x) + pill.radius,
930
+ maxY: Math.max(pill.start.y, pill.end.y) + pill.radius
931
+ };
932
+ }
809
933
  }
810
934
  return getBoundsOfPcbElements2([collidable]);
811
935
  };
@@ -1005,6 +1129,38 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1005
1129
  ].filter(Boolean)
1006
1130
  });
1007
1131
  }
1132
+ if (obj.type === "pcb_smtpad" && obj.shape === "rotated_pill") {
1133
+ const { distance: distance3, center, radius } = getSegmentToPillClearance(
1134
+ segmentA,
1135
+ obj
1136
+ );
1137
+ const gap2 = distance3 - segmentA.thickness / 2 - radius;
1138
+ if (gap2 > minClearance - EPSILON) continue;
1139
+ const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${primaryObjId}`;
1140
+ if (errorIds.has(pcb_trace_error_id)) continue;
1141
+ errorIds.add(pcb_trace_error_id);
1142
+ errors.push({
1143
+ type: "pcb_trace_error",
1144
+ error_type: "pcb_trace_error",
1145
+ message: constructErrorMessage(
1146
+ getReadableName(segmentA.pcb_trace_id),
1147
+ `${obj.type} "${getReadableName(getPrimaryId(obj))}"`,
1148
+ gap2
1149
+ ),
1150
+ pcb_trace_id: segmentA.pcb_trace_id,
1151
+ center,
1152
+ source_trace_id: "",
1153
+ pcb_trace_error_id,
1154
+ pcb_component_ids: [
1155
+ "pcb_component_id" in obj ? obj.pcb_component_id : void 0
1156
+ ].filter(Boolean),
1157
+ pcb_port_ids: [
1158
+ ...getPcbPortIdsConnectedToTraces([segmentA._pcbTrace]),
1159
+ "pcb_port_id" in obj ? obj.pcb_port_id : void 0
1160
+ ].filter(Boolean)
1161
+ });
1162
+ continue;
1163
+ }
1008
1164
  const isPolygon = obj.type === "pcb_smtpad" && (obj.shape === "rotated_rect" || obj.shape === "polygon") || obj.type === "pcb_plated_hole" && "rect_pad_width" in obj && "rect_pad_height" in obj;
1009
1165
  if (isPolygon) {
1010
1166
  const padOutline = getPolygonPointsForPad(obj);
@@ -1872,7 +2028,11 @@ import {
1872
2028
  distanceBetweenPolygonAndPolygon,
1873
2029
  getBoundsOfPcbElements as getBoundsOfPcbElements5
1874
2030
  } from "@tscircuit/circuit-json-util";
1875
- import { midpoint } from "@tscircuit/math-utils";
2031
+ import {
2032
+ midpoint,
2033
+ segmentToCircleMinDistance as segmentToCircleMinDistance2,
2034
+ segmentToSegmentMinDistance as segmentToSegmentMinDistance4
2035
+ } from "@tscircuit/math-utils";
1876
2036
  var formatMm = (value) => {
1877
2037
  const rounded = Math.round(value * 1e3) / 1e3;
1878
2038
  return `${Number(rounded.toFixed(3))}mm`;
@@ -1890,6 +2050,7 @@ var getPadRadius = (pad) => {
1890
2050
  return Math.min(bounds.maxX - bounds.minX, bounds.maxY - bounds.minY) / 2;
1891
2051
  };
1892
2052
  var isCircularPad = (pad) => pad.shape === "circle";
2053
+ var isPillPad = (pad) => pad.type === "pcb_smtpad" && (pad.shape === "pill" || pad.shape === "rotated_pill");
1893
2054
  var getCircleShape = (pad) => {
1894
2055
  const center = getPadCenter(pad);
1895
2056
  return {
@@ -1900,6 +2061,18 @@ var getCircleShape = (pad) => {
1900
2061
  };
1901
2062
  };
1902
2063
  var getPolygonShape = (pad) => {
2064
+ if (pad.type === "pcb_smtpad" && (pad.shape === "polygon" || pad.shape === "rotated_rect")) {
2065
+ return {
2066
+ kind: "polygon",
2067
+ points: getPolygonPointsForPad(pad)
2068
+ };
2069
+ }
2070
+ if (pad.type === "pcb_plated_hole" && "rect_pad_width" in pad && "rect_pad_height" in pad) {
2071
+ return {
2072
+ kind: "polygon",
2073
+ points: getPolygonPointsForPad(pad)
2074
+ };
2075
+ }
1903
2076
  const bounds = getPadBounds(pad);
1904
2077
  return {
1905
2078
  kind: "polygon",
@@ -1912,6 +2085,40 @@ var getPolygonShape = (pad) => {
1912
2085
  };
1913
2086
  };
1914
2087
  var getPadToPadGap = (padA, padB) => {
2088
+ if (isPillPad(padA) && isPillPad(padB)) {
2089
+ const pillA = getPillCenterLineForPad(padA);
2090
+ const pillB = getPillCenterLineForPad(padB);
2091
+ return segmentToSegmentMinDistance4(
2092
+ pillA.start,
2093
+ pillA.end,
2094
+ pillB.start,
2095
+ pillB.end
2096
+ ) - pillA.radius - pillB.radius;
2097
+ }
2098
+ if (isPillPad(padA) && isCircularPad(padB)) {
2099
+ const pill = getPillCenterLineForPad(padA);
2100
+ return segmentToCircleMinDistance2(pill.start, pill.end, getCircleShape(padB)) - pill.radius;
2101
+ }
2102
+ if (isCircularPad(padA) && isPillPad(padB)) {
2103
+ const pill = getPillCenterLineForPad(padB);
2104
+ return segmentToCircleMinDistance2(pill.start, pill.end, getCircleShape(padA)) - pill.radius;
2105
+ }
2106
+ if (isPillPad(padA)) {
2107
+ const pill = getPillCenterLineForPad(padA);
2108
+ return getSegmentToPolygonClearanceFromPoints(
2109
+ pill.start,
2110
+ pill.end,
2111
+ getPolygonShape(padB).points
2112
+ ).distance - pill.radius;
2113
+ }
2114
+ if (isPillPad(padB)) {
2115
+ const pill = getPillCenterLineForPad(padB);
2116
+ return getSegmentToPolygonClearanceFromPoints(
2117
+ pill.start,
2118
+ pill.end,
2119
+ getPolygonShape(padA).points
2120
+ ).distance - pill.radius;
2121
+ }
1915
2122
  if (isCircularPad(padA) && isCircularPad(padB)) {
1916
2123
  return distanceBetweenCircleAndCircle(
1917
2124
  getCircleShape(padA),
@@ -2029,7 +2236,7 @@ import {
2029
2236
  } from "@tscircuit/circuit-json-util";
2030
2237
  import {
2031
2238
  segmentToBoundsMinDistance as segmentToBoundsMinDistance2,
2032
- segmentToCircleMinDistance as segmentToCircleMinDistance2
2239
+ segmentToCircleMinDistance as segmentToCircleMinDistance3
2033
2240
  } from "@tscircuit/math-utils";
2034
2241
  import {
2035
2242
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson9
@@ -2060,7 +2267,13 @@ function checkPadTraceClearance(circuitJson, {
2060
2267
  if (!getLayersOfPcbElement(pad).includes(segment.layer)) continue;
2061
2268
  if (connMap.areIdsConnected(segment.pcb_trace_id, padId)) continue;
2062
2269
  const center = getPadCenter(pad);
2063
- const gap = isCircularPad(pad) ? segmentToCircleMinDistance2(
2270
+ const gap = pad.type === "pcb_smtpad" && pad.shape === "rotated_pill" ? (() => {
2271
+ const { distance: distance3, radius } = getSegmentToPillClearance(
2272
+ segment,
2273
+ pad
2274
+ );
2275
+ return distance3 - segment.thickness / 2 - radius;
2276
+ })() : isCircularPad(pad) ? segmentToCircleMinDistance3(
2064
2277
  { x: segment.x1, y: segment.y1 },
2065
2278
  { x: segment.x2, y: segment.y2 },
2066
2279
  { x: center.x, y: center.y, radius: getPadRadius(pad) }
@@ -2096,7 +2309,7 @@ function checkPadTraceClearance(circuitJson, {
2096
2309
 
2097
2310
  // lib/check-via-trace-clearance.ts
2098
2311
  import { getReadableNameForElement as getReadableNameForElement8 } from "@tscircuit/circuit-json-util";
2099
- import { segmentToCircleMinDistance as segmentToCircleMinDistance3 } from "@tscircuit/math-utils";
2312
+ import { segmentToCircleMinDistance as segmentToCircleMinDistance4 } from "@tscircuit/math-utils";
2100
2313
  import {
2101
2314
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson10
2102
2315
  } from "circuit-json-to-connectivity-map";
@@ -2134,7 +2347,7 @@ function checkViaTraceClearance(circuitJson, {
2134
2347
  if (!getLayersOfPcbElement(via).includes(segment.layer)) continue;
2135
2348
  if (connMap.areIdsConnected(segment.pcb_trace_id, via.pcb_via_id))
2136
2349
  continue;
2137
- const gap = segmentToCircleMinDistance3(
2350
+ const gap = segmentToCircleMinDistance4(
2138
2351
  { x: segment.x1, y: segment.y1 },
2139
2352
  { x: segment.x2, y: segment.y2 },
2140
2353
  {
@@ -2571,83 +2784,6 @@ function checkCourtyardOverlap(circuitJson) {
2571
2784
  return errors;
2572
2785
  }
2573
2786
 
2574
- // lib/check-traces-are-contiguous/is-point-in-pad.ts
2575
- function getDistanceBetweenPoints(pointA, pointB) {
2576
- return Math.sqrt((pointB.x - pointA.x) ** 2 + (pointB.y - pointA.y) ** 2);
2577
- }
2578
- var POINT_ON_SEGMENT_TOLERANCE_MM = 1e-9;
2579
- function isPointOnSegment(point, segment) {
2580
- const crossProduct = (point.y - segment.start.y) * (segment.end.x - segment.start.x) - (point.x - segment.start.x) * (segment.end.y - segment.start.y);
2581
- if (Math.abs(crossProduct) > POINT_ON_SEGMENT_TOLERANCE_MM) return false;
2582
- const dotProduct = (point.x - segment.start.x) * (segment.end.x - segment.start.x) + (point.y - segment.start.y) * (segment.end.y - segment.start.y);
2583
- if (dotProduct < -POINT_ON_SEGMENT_TOLERANCE_MM) return false;
2584
- const squaredLength = (segment.end.x - segment.start.x) ** 2 + (segment.end.y - segment.start.y) ** 2;
2585
- return dotProduct <= squaredLength + POINT_ON_SEGMENT_TOLERANCE_MM;
2586
- }
2587
- function isPointInPad(point, pad) {
2588
- if (pad.type === "pcb_smtpad") {
2589
- if (pad.shape === "circle") {
2590
- return getDistanceBetweenPoints(point, pad) <= pad.radius;
2591
- }
2592
- if (pad.shape === "rect") {
2593
- const halfWidth = pad.width / 2;
2594
- const halfHeight = pad.height / 2;
2595
- return Math.abs(point.x - pad.x) <= halfWidth && Math.abs(point.y - pad.y) <= halfHeight;
2596
- }
2597
- if (pad.shape === "rotated_rect") {
2598
- const dx = point.x - pad.x;
2599
- const dy = point.y - pad.y;
2600
- const angle = -pad.ccw_rotation;
2601
- const rotatedX = dx * Math.cos(angle) - dy * Math.sin(angle);
2602
- const rotatedY = dx * Math.sin(angle) + dy * Math.cos(angle);
2603
- return Math.abs(rotatedX) <= pad.width / 2 && Math.abs(rotatedY) <= pad.height / 2;
2604
- }
2605
- if (pad.shape === "pill") {
2606
- const halfWidth = pad.width / 2;
2607
- const halfHeight = pad.height / 2;
2608
- const radius = pad.radius;
2609
- if (Math.abs(point.x - pad.x) <= halfWidth - radius && Math.abs(point.y - pad.y) <= halfHeight) {
2610
- return true;
2611
- }
2612
- const cornerX = Math.max(
2613
- Math.abs(point.x - pad.x) - (halfWidth - radius),
2614
- 0
2615
- );
2616
- const cornerY = Math.max(
2617
- Math.abs(point.y - pad.y) - (halfHeight - radius),
2618
- 0
2619
- );
2620
- return cornerX * cornerX + cornerY * cornerY <= radius * radius;
2621
- }
2622
- if (pad.shape === "polygon") {
2623
- let inside = false;
2624
- for (let i = 0, j = pad.points.length - 1; i < pad.points.length; j = i++) {
2625
- const pi = pad.points[i];
2626
- const pj = pad.points[j];
2627
- if (isPointOnSegment(point, { start: pi, end: pj })) return true;
2628
- const intersects = pi.y > point.y !== pj.y > point.y && point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x;
2629
- if (intersects) inside = !inside;
2630
- }
2631
- return inside;
2632
- }
2633
- }
2634
- if (pad.type === "pcb_plated_hole") {
2635
- if (pad.shape === "circle") {
2636
- return getDistanceBetweenPoints(point, pad) <= pad.outer_diameter / 2;
2637
- }
2638
- if (pad.shape === "oval" || pad.shape === "pill") {
2639
- return Math.abs(point.x - pad.x) <= pad.outer_width / 2 && Math.abs(point.y - pad.y) <= pad.outer_height / 2;
2640
- }
2641
- if (pad.shape === "circular_hole_with_rect_pad") {
2642
- return Math.abs(point.x - pad.x) <= pad.rect_pad_width / 2 && Math.abs(point.y - pad.y) <= pad.rect_pad_height / 2;
2643
- }
2644
- if (pad.shape === "pill_hole_with_rect_pad") {
2645
- return Math.abs(point.x - pad.x) <= pad.rect_pad_width / 2 && Math.abs(point.y - pad.y) <= pad.rect_pad_height / 2;
2646
- }
2647
- }
2648
- return false;
2649
- }
2650
-
2651
2787
  // lib/check-traces-are-contiguous/check-traces-are-contiguous.ts
2652
2788
  import {
2653
2789
  getReadableNameForPcbPort as getReadableNameForPcbPort2,