@tscircuit/checks 0.0.143 → 0.0.145

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
@@ -118,7 +118,12 @@ var getClosestPointsBetweenSegments = (a1, a2, b1, b2) => {
118
118
  };
119
119
  var getSegmentToPolygonClearanceFromPoints = (start, end, polygon) => {
120
120
  if (polygon.length < 3) {
121
- return { distance: Number.POSITIVE_INFINITY, center: start };
121
+ return {
122
+ distance: Number.POSITIVE_INFINITY,
123
+ center: start,
124
+ tracePoint: start,
125
+ obstaclePoint: start
126
+ };
122
127
  }
123
128
  const intersections = getPolygonEdges(polygon).map(
124
129
  ([edgeStart, edgeEnd]) => getSegmentIntersection(start, end, edgeStart, edgeEnd)
@@ -132,15 +137,23 @@ var getSegmentToPolygonClearanceFromPoints = (start, end, polygon) => {
132
137
  const tb = ((b.x - start.x) * dx + (b.y - start.y) * dy) / lengthSquared;
133
138
  return ta - tb;
134
139
  });
135
- return { distance: 0, center: intersections[0] };
140
+ return {
141
+ distance: 0,
142
+ center: intersections[0],
143
+ tracePoint: intersections[0],
144
+ obstaclePoint: intersections[0]
145
+ };
136
146
  }
137
147
  if (isPointInsidePolygon(start, polygon) || isPointInsidePolygon(end, polygon)) {
148
+ const center = {
149
+ x: (start.x + end.x) / 2,
150
+ y: (start.y + end.y) / 2
151
+ };
138
152
  return {
139
153
  distance: 0,
140
- center: {
141
- x: (start.x + end.x) / 2,
142
- y: (start.y + end.y) / 2
143
- }
154
+ center,
155
+ tracePoint: center,
156
+ obstaclePoint: center
144
157
  };
145
158
  }
146
159
  let best = getClosestPointsBetweenSegments(
@@ -158,13 +171,13 @@ var getSegmentToPolygonClearanceFromPoints = (start, end, polygon) => {
158
171
  );
159
172
  if (candidate.distance < best.distance) best = candidate;
160
173
  }
161
- return { distance: best.distance, center: best.center };
174
+ return {
175
+ distance: best.distance,
176
+ center: best.center,
177
+ tracePoint: best.pointOnA,
178
+ obstaclePoint: best.pointOnB
179
+ };
162
180
  };
163
- var getSegmentToPolygonClearance = (segment, polygon) => getSegmentToPolygonClearanceFromPoints(
164
- { x: segment.x1, y: segment.y1 },
165
- { x: segment.x2, y: segment.y2 },
166
- polygon
167
- );
168
181
  var getSegmentToPillClearance = (segment, pad) => {
169
182
  const pill = getPillCenterLineForPad(pad);
170
183
  const closest = getClosestPointsBetweenSegments(
@@ -176,7 +189,9 @@ var getSegmentToPillClearance = (segment, pad) => {
176
189
  return {
177
190
  distance: closest.distance,
178
191
  center: closest.center,
179
- radius: pill.radius
192
+ radius: pill.radius,
193
+ tracePoint: closest.pointOnA,
194
+ obstaclePoint: closest.pointOnB
180
195
  };
181
196
  };
182
197
 
@@ -193,6 +208,17 @@ function isPointOnSegment(point, segment) {
193
208
  const squaredLength = (segment.end.x - segment.start.x) ** 2 + (segment.end.y - segment.start.y) ** 2;
194
209
  return dotProduct <= squaredLength + POINT_ON_SEGMENT_TOLERANCE_MM;
195
210
  }
211
+ function isPointInPolygon(point, polygon) {
212
+ let inside = false;
213
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
214
+ const pi = polygon[i];
215
+ const pj = polygon[j];
216
+ if (isPointOnSegment(point, { start: pi, end: pj })) return true;
217
+ 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;
218
+ if (intersects) inside = !inside;
219
+ }
220
+ return inside;
221
+ }
196
222
  function isPointInPad(point, pad) {
197
223
  if (pad.type === "pcb_smtpad") {
198
224
  if (pad.shape === "circle") {
@@ -204,12 +230,7 @@ function isPointInPad(point, pad) {
204
230
  return Math.abs(point.x - pad.x) <= halfWidth && Math.abs(point.y - pad.y) <= halfHeight;
205
231
  }
206
232
  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;
233
+ return isPointInPolygon(point, getPolygonPointsForPad(pad));
213
234
  }
214
235
  if (pad.shape === "pill" || pad.shape === "rotated_pill") {
215
236
  if (pad.shape === "rotated_pill") {
@@ -233,30 +254,19 @@ function isPointInPad(point, pad) {
233
254
  return cornerX * cornerX + cornerY * cornerY <= radius * radius;
234
255
  }
235
256
  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;
257
+ return isPointInPolygon(point, pad.points);
245
258
  }
246
259
  }
247
260
  if (pad.type === "pcb_plated_hole") {
248
261
  if (pad.shape === "circle") {
249
262
  return getDistanceBetweenPoints(point, pad) <= pad.outer_diameter / 2;
250
263
  }
264
+ if ("rect_pad_width" in pad && "rect_pad_height" in pad) {
265
+ return isPointInPolygon(point, getPolygonPointsForPad(pad));
266
+ }
251
267
  if (pad.shape === "oval" || pad.shape === "pill") {
252
268
  return Math.abs(point.x - pad.x) <= pad.outer_width / 2 && Math.abs(point.y - pad.y) <= pad.outer_height / 2;
253
269
  }
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
270
  }
261
271
  return false;
262
272
  }
@@ -502,17 +512,284 @@ function checkEachPcbPortConnectedToPcbTraces(circuitJson) {
502
512
  }
503
513
 
504
514
  // lib/check-each-pcb-trace-non-overlapping/check-each-pcb-trace-non-overlapping.ts
505
- import { cju, getReadableNameForElement as getReadableNameForElement2 } from "@tscircuit/circuit-json-util";
515
+ import { cju as cju2, getReadableNameForElement as getReadableNameForElement2 } from "@tscircuit/circuit-json-util";
506
516
  import { getPrimaryId } from "@tscircuit/circuit-json-util";
507
517
  import {
508
518
  segmentToBoundsMinDistance,
509
- segmentToCircleMinDistance
519
+ segmentToCircleMinDistance as segmentToCircleMinDistance2
510
520
  } from "@tscircuit/math-utils";
511
- import { segmentToSegmentMinDistance as segmentToSegmentMinDistance2 } from "@tscircuit/math-utils";
521
+ import { segmentToSegmentMinDistance as segmentToSegmentMinDistance3 } from "@tscircuit/math-utils";
512
522
  import {
513
523
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson2
514
524
  } from "circuit-json-to-connectivity-map";
515
525
 
526
+ // lib/check-pad-clearance/common.ts
527
+ import {
528
+ cju,
529
+ distanceBetweenCircleAndCircle,
530
+ distanceBetweenCircleAndPolygon,
531
+ distanceBetweenPolygonAndPolygon,
532
+ getBoundsOfPcbElements as getBoundsOfPcbElements2
533
+ } from "@tscircuit/circuit-json-util";
534
+ import {
535
+ midpoint,
536
+ pointToSegmentClosestPoint as pointToSegmentClosestPoint2,
537
+ segmentToCircleMinDistance,
538
+ segmentToSegmentMinDistance as segmentToSegmentMinDistance2
539
+ } from "@tscircuit/math-utils";
540
+
541
+ // node_modules/@tscircuit/jlcpcb-manufacturing-specs/lib/jlcpcb-manufacturing-specs.ts
542
+ var jlcMinTolerances = {
543
+ min_trace_width: 0.1,
544
+ min_via_hole_edge_to_via_hole_edge_clearance: 0.1,
545
+ min_plated_hole_drill_edge_to_drill_edge_clearance: 0.15,
546
+ min_trace_to_pad_edge_clearance: 0.1,
547
+ min_pad_edge_to_pad_edge_clearance: 0.1,
548
+ min_board_edge_clearance: 0.2,
549
+ min_via_hole_diameter: 0.2,
550
+ min_via_pad_diameter: 0.3
551
+ };
552
+
553
+ // lib/drc-defaults.ts
554
+ var DEFAULT_TRACE_MARGIN = 0.1;
555
+ var DEFAULT_TRACE_THICKNESS = jlcMinTolerances.min_trace_width;
556
+ var DEFAULT_VIA_DIAMETER = jlcMinTolerances.min_via_pad_diameter;
557
+ var DEFAULT_VIA_BOARD_MARGIN = jlcMinTolerances.min_board_edge_clearance;
558
+ var DEFAULT_SAME_NET_VIA_MARGIN = jlcMinTolerances.min_via_hole_edge_to_via_hole_edge_clearance;
559
+ var DEFAULT_DIFFERENT_NET_VIA_MARGIN = jlcMinTolerances.min_via_hole_edge_to_via_hole_edge_clearance;
560
+ var DEFAULT_PAD_PAD_CLEARANCE = jlcMinTolerances.min_pad_edge_to_pad_edge_clearance;
561
+ var EPSILON = 5e-3;
562
+ var getPcbBoard = (circuitJson) => circuitJson.find((el) => el.type === "pcb_board");
563
+ var getBoardDrcValue = (board, key) => board?.[key];
564
+
565
+ // lib/check-pad-clearance/common.ts
566
+ var formatMm = (value) => {
567
+ const rounded = Math.round(value * 1e3) / 1e3;
568
+ return `${Number(rounded.toFixed(3))}mm`;
569
+ };
570
+ var getPadBounds = (pad) => getBoundsOfPcbElements2([pad]);
571
+ var getPadCenter = (pad) => {
572
+ const bounds = getPadBounds(pad);
573
+ return midpoint(
574
+ { x: bounds.minX, y: bounds.minY },
575
+ { x: bounds.maxX, y: bounds.maxY }
576
+ );
577
+ };
578
+ var getPadRadius = (pad) => {
579
+ const bounds = getPadBounds(pad);
580
+ return Math.min(bounds.maxX - bounds.minX, bounds.maxY - bounds.minY) / 2;
581
+ };
582
+ var isCircularPad = (pad) => pad.shape === "circle";
583
+ var isPillPad = (pad) => pad.type === "pcb_smtpad" && (pad.shape === "pill" || pad.shape === "rotated_pill");
584
+ var getCircleShape = (pad) => {
585
+ const center = getPadCenter(pad);
586
+ return {
587
+ kind: "circle",
588
+ x: center.x,
589
+ y: center.y,
590
+ radius: getPadRadius(pad)
591
+ };
592
+ };
593
+ var getPolygonShape = (pad) => {
594
+ if (pad.type === "pcb_smtpad" && (pad.shape === "polygon" || pad.shape === "rotated_rect")) {
595
+ return {
596
+ kind: "polygon",
597
+ points: getPolygonPointsForPad(pad)
598
+ };
599
+ }
600
+ if (pad.type === "pcb_plated_hole" && "rect_pad_width" in pad && "rect_pad_height" in pad) {
601
+ return {
602
+ kind: "polygon",
603
+ points: getPolygonPointsForPad(pad)
604
+ };
605
+ }
606
+ const bounds = getPadBounds(pad);
607
+ return {
608
+ kind: "polygon",
609
+ points: [
610
+ { x: bounds.minX, y: bounds.minY },
611
+ { x: bounds.maxX, y: bounds.minY },
612
+ { x: bounds.maxX, y: bounds.maxY },
613
+ { x: bounds.minX, y: bounds.maxY }
614
+ ]
615
+ };
616
+ };
617
+ var getPadToPadGap = (padA, padB) => {
618
+ if (isPillPad(padA) && isPillPad(padB)) {
619
+ const pillA = getPillCenterLineForPad(padA);
620
+ const pillB = getPillCenterLineForPad(padB);
621
+ return segmentToSegmentMinDistance2(
622
+ pillA.start,
623
+ pillA.end,
624
+ pillB.start,
625
+ pillB.end
626
+ ) - pillA.radius - pillB.radius;
627
+ }
628
+ if (isPillPad(padA) && isCircularPad(padB)) {
629
+ const pill = getPillCenterLineForPad(padA);
630
+ return segmentToCircleMinDistance(pill.start, pill.end, getCircleShape(padB)) - pill.radius;
631
+ }
632
+ if (isCircularPad(padA) && isPillPad(padB)) {
633
+ const pill = getPillCenterLineForPad(padB);
634
+ return segmentToCircleMinDistance(pill.start, pill.end, getCircleShape(padA)) - pill.radius;
635
+ }
636
+ if (isPillPad(padA)) {
637
+ const pill = getPillCenterLineForPad(padA);
638
+ return getSegmentToPolygonClearanceFromPoints(
639
+ pill.start,
640
+ pill.end,
641
+ getPolygonShape(padB).points
642
+ ).distance - pill.radius;
643
+ }
644
+ if (isPillPad(padB)) {
645
+ const pill = getPillCenterLineForPad(padB);
646
+ return getSegmentToPolygonClearanceFromPoints(
647
+ pill.start,
648
+ pill.end,
649
+ getPolygonShape(padA).points
650
+ ).distance - pill.radius;
651
+ }
652
+ if (isCircularPad(padA) && isCircularPad(padB)) {
653
+ return distanceBetweenCircleAndCircle(
654
+ getCircleShape(padA),
655
+ getCircleShape(padB)
656
+ );
657
+ }
658
+ if (isCircularPad(padA)) {
659
+ return distanceBetweenCircleAndPolygon(
660
+ getCircleShape(padA),
661
+ getPolygonShape(padB)
662
+ );
663
+ }
664
+ if (isCircularPad(padB)) {
665
+ return distanceBetweenCircleAndPolygon(
666
+ getCircleShape(padB),
667
+ getPolygonShape(padA)
668
+ );
669
+ }
670
+ return distanceBetweenPolygonAndPolygon(
671
+ getPolygonShape(padA),
672
+ getPolygonShape(padB)
673
+ );
674
+ };
675
+ var getPads = (circuitJson) => [
676
+ ...cju(circuitJson).pcb_smtpad.list(),
677
+ ...cju(circuitJson).pcb_plated_hole.list()
678
+ ];
679
+ var getTraceSegments = (circuitJson) => {
680
+ const pcbTraces = cju(circuitJson).pcb_trace.list();
681
+ return pcbTraces.flatMap((pcbTrace) => {
682
+ const segments = [];
683
+ for (let i = 0; i < pcbTrace.route.length - 1; i++) {
684
+ const p1 = pcbTrace.route[i];
685
+ const p2 = pcbTrace.route[i + 1];
686
+ if (p1.route_type !== "wire") continue;
687
+ if (p2.route_type !== "wire") continue;
688
+ if (p1.layer !== p2.layer) continue;
689
+ segments.push({
690
+ type: "pcb_trace_segment",
691
+ pcb_trace_id: pcbTrace.pcb_trace_id,
692
+ _pcbTrace: pcbTrace,
693
+ thickness: "width" in p1 ? p1.width : "width" in p2 ? p2.width : DEFAULT_TRACE_THICKNESS,
694
+ layer: p1.layer,
695
+ x1: p1.x,
696
+ y1: p1.y,
697
+ x2: p2.x,
698
+ y2: p2.y
699
+ });
700
+ }
701
+ return segments;
702
+ });
703
+ };
704
+ var getTraceCenter = (segment) => {
705
+ const routePoints = segment._pcbTrace.route.flatMap((routePoint) => {
706
+ if (routePoint.route_type === "through_pad") {
707
+ return [routePoint.start, routePoint.end];
708
+ }
709
+ return [{ x: routePoint.x, y: routePoint.y }];
710
+ });
711
+ const firstPoint = routePoints[0];
712
+ const lastPoint = routePoints[routePoints.length - 1];
713
+ if (!firstPoint || !lastPoint) {
714
+ return midpoint(
715
+ { x: segment.x1, y: segment.y1 },
716
+ { x: segment.x2, y: segment.y2 }
717
+ );
718
+ }
719
+ return midpoint(firstPoint, lastPoint);
720
+ };
721
+ var getCenterBetweenCopperEdges = ({
722
+ tracePoint,
723
+ obstaclePoint,
724
+ traceRadius,
725
+ obstacleRadius
726
+ }) => {
727
+ const dx = obstaclePoint.x - tracePoint.x;
728
+ const dy = obstaclePoint.y - tracePoint.y;
729
+ const distance3 = Math.hypot(dx, dy);
730
+ if (distance3 === 0) return midpoint(tracePoint, obstaclePoint);
731
+ const unitX = dx / distance3;
732
+ const unitY = dy / distance3;
733
+ const traceEdge = {
734
+ x: tracePoint.x + unitX * traceRadius,
735
+ y: tracePoint.y + unitY * traceRadius
736
+ };
737
+ const obstacleEdge = {
738
+ x: obstaclePoint.x - unitX * obstacleRadius,
739
+ y: obstaclePoint.y - unitY * obstacleRadius
740
+ };
741
+ return midpoint(traceEdge, obstacleEdge);
742
+ };
743
+ var getTraceObstacleClearance = (segment, obstacle) => {
744
+ const start = { x: segment.x1, y: segment.y1 };
745
+ const end = { x: segment.x2, y: segment.y2 };
746
+ const traceRadius = segment.thickness / 2;
747
+ if (obstacle.type === "pcb_via" || isCircularPad(obstacle)) {
748
+ const circle = obstacle.type === "pcb_via" ? {
749
+ x: obstacle.x,
750
+ y: obstacle.y,
751
+ radius: obstacle.outer_diameter / 2
752
+ } : getCircleShape(obstacle);
753
+ const closestPoint = pointToSegmentClosestPoint2(circle, start, end);
754
+ return {
755
+ gap: segmentToCircleMinDistance(start, end, circle) - traceRadius,
756
+ center: getCenterBetweenCopperEdges({
757
+ tracePoint: closestPoint,
758
+ obstaclePoint: circle,
759
+ traceRadius,
760
+ obstacleRadius: circle.radius
761
+ })
762
+ };
763
+ }
764
+ if (isPillPad(obstacle)) {
765
+ const clearance2 = getSegmentToPillClearance(segment, obstacle);
766
+ return {
767
+ gap: clearance2.distance - traceRadius - clearance2.radius,
768
+ center: getCenterBetweenCopperEdges({
769
+ tracePoint: clearance2.tracePoint,
770
+ obstaclePoint: clearance2.obstaclePoint,
771
+ traceRadius,
772
+ obstacleRadius: clearance2.radius
773
+ })
774
+ };
775
+ }
776
+ const clearance = getSegmentToPolygonClearanceFromPoints(
777
+ start,
778
+ end,
779
+ getPolygonShape(obstacle).points
780
+ );
781
+ return {
782
+ gap: clearance.distance - traceRadius,
783
+ center: getCenterBetweenCopperEdges({
784
+ tracePoint: clearance.tracePoint,
785
+ obstaclePoint: clearance.obstaclePoint,
786
+ traceRadius,
787
+ obstacleRadius: 0
788
+ })
789
+ };
790
+ };
791
+ var isTraceObstacleOverlap = (gap) => gap <= 0;
792
+
516
793
  // lib/data-structures/SpatialIndex.ts
517
794
  var SpatialObjectIndex = class {
518
795
  buckets;
@@ -612,31 +889,6 @@ var SpatialObjectIndex = class {
612
889
  }
613
890
  };
614
891
 
615
- // node_modules/@tscircuit/jlcpcb-manufacturing-specs/lib/jlcpcb-manufacturing-specs.ts
616
- var jlcMinTolerances = {
617
- min_trace_width: 0.1,
618
- min_via_hole_edge_to_via_hole_edge_clearance: 0.1,
619
- min_plated_hole_drill_edge_to_drill_edge_clearance: 0.15,
620
- min_trace_to_pad_edge_clearance: 0.1,
621
- min_pad_edge_to_pad_edge_clearance: 0.1,
622
- min_board_edge_clearance: 0.2,
623
- min_via_hole_diameter: 0.2,
624
- min_via_pad_diameter: 0.3
625
- };
626
-
627
- // lib/drc-defaults.ts
628
- var DEFAULT_TRACE_MARGIN = 0.1;
629
- var DEFAULT_TRACE_THICKNESS = jlcMinTolerances.min_trace_width;
630
- var DEFAULT_VIA_DIAMETER = jlcMinTolerances.min_via_pad_diameter;
631
- var DEFAULT_VIA_BOARD_MARGIN = jlcMinTolerances.min_board_edge_clearance;
632
- var DEFAULT_SAME_NET_VIA_MARGIN = jlcMinTolerances.min_via_hole_edge_to_via_hole_edge_clearance;
633
- var DEFAULT_DIFFERENT_NET_VIA_MARGIN = jlcMinTolerances.min_via_hole_edge_to_via_hole_edge_clearance;
634
- var DEFAULT_PAD_PAD_CLEARANCE = jlcMinTolerances.min_pad_edge_to_pad_edge_clearance;
635
- var DEFAULT_PAD_TRACE_CLEARANCE = jlcMinTolerances.min_trace_to_pad_edge_clearance;
636
- var EPSILON = 5e-3;
637
- var getPcbBoard = (circuitJson) => circuitJson.find((el) => el.type === "pcb_board");
638
- var getBoardDrcValue = (board, key) => board?.[key];
639
-
640
892
  // lib/util/getLayersOfPcbElement.ts
641
893
  import { all_layers } from "circuit-json";
642
894
  function getLayersOfPcbElement(obj) {
@@ -900,7 +1152,7 @@ var clamp = (value, min, max) => {
900
1152
  };
901
1153
 
902
1154
  // lib/check-each-pcb-trace-non-overlapping/getCollidableBounds.ts
903
- import { getBoundsOfPcbElements as getBoundsOfPcbElements2 } from "@tscircuit/circuit-json-util";
1155
+ import { getBoundsOfPcbElements as getBoundsOfPcbElements3 } from "@tscircuit/circuit-json-util";
904
1156
  var getCollidableBounds = (collidable) => {
905
1157
  if (collidable.type === "pcb_trace_segment") {
906
1158
  return {
@@ -931,7 +1183,7 @@ var getCollidableBounds = (collidable) => {
931
1183
  };
932
1184
  }
933
1185
  }
934
- return getBoundsOfPcbElements2([collidable]);
1186
+ return getBoundsOfPcbElements3([collidable]);
935
1187
  };
936
1188
 
937
1189
  // lib/check-each-pcb-trace-non-overlapping/getPcbPortIdsConnectedToTraces.ts
@@ -989,7 +1241,7 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
989
1241
  connMap ??= getFullConnectivityMapFromCircuitJson2(circuitJson);
990
1242
  const board = getPcbBoard(circuitJson);
991
1243
  minClearance ??= getBoardDrcValue(board, "min_trace_to_pad_edge_clearance") ?? DEFAULT_TRACE_MARGIN;
992
- const pcbTraces = cju(circuitJson).pcb_trace.list();
1244
+ const pcbTraces = cju2(circuitJson).pcb_trace.list();
993
1245
  const pcbTraceSegments = pcbTraces.flatMap((pcbTrace) => {
994
1246
  const segments = [];
995
1247
  for (let i = 0; i < pcbTrace.route.length - 1; i++) {
@@ -1012,11 +1264,11 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1012
1264
  }
1013
1265
  return segments;
1014
1266
  });
1015
- const pcbSmtPads = cju(circuitJson).pcb_smtpad.list();
1016
- const pcbPlatedHoles = cju(circuitJson).pcb_plated_hole.list();
1017
- const pcbHoles = cju(circuitJson).pcb_hole.list();
1018
- const pcbVias = cju(circuitJson).pcb_via.list();
1019
- const pcbKeepouts = cju(circuitJson).pcb_keepout.list();
1267
+ const pcbSmtPads = cju2(circuitJson).pcb_smtpad.list();
1268
+ const pcbPlatedHoles = cju2(circuitJson).pcb_plated_hole.list();
1269
+ const pcbHoles = cju2(circuitJson).pcb_hole.list();
1270
+ const pcbVias = cju2(circuitJson).pcb_via.list();
1271
+ const pcbKeepouts = cju2(circuitJson).pcb_keepout.list();
1020
1272
  const allObjects = [
1021
1273
  ...pcbTraceSegments,
1022
1274
  ...pcbSmtPads,
@@ -1031,7 +1283,7 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1031
1283
  });
1032
1284
  const getReadableName = (id) => getReadableNameForElement2(circuitJson, id);
1033
1285
  const constructErrorMessage = (traceName, otherName, gap) => {
1034
- if (gap < 0) {
1286
+ if (isTraceObstacleOverlap(gap)) {
1035
1287
  return `PCB trace ${traceName} overlaps with ${otherName} (accidental contact)`;
1036
1288
  }
1037
1289
  return `PCB trace ${traceName} is too close to ${otherName} (gap: ${gap.toFixed(3)}mm)`;
@@ -1054,7 +1306,7 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1054
1306
  if (segmentA.layer !== segmentB.layer) continue;
1055
1307
  if (connMap.areIdsConnected(segmentA.pcb_trace_id, segmentB.pcb_trace_id))
1056
1308
  continue;
1057
- const gap2 = segmentToSegmentMinDistance2(
1309
+ const gap2 = segmentToSegmentMinDistance3(
1058
1310
  { x: segmentA.x1, y: segmentA.y1 },
1059
1311
  { x: segmentA.x2, y: segmentA.y2 },
1060
1312
  { x: segmentB.x1, y: segmentB.y1 },
@@ -1092,50 +1344,9 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1092
1344
  "pcb_trace_id" in obj ? obj.pcb_trace_id : primaryObjId
1093
1345
  ))
1094
1346
  continue;
1095
- const isCircular = obj.type === "pcb_via" || obj.type === "pcb_plated_hole" && obj.shape === "circle" || obj.type === "pcb_hole" || obj.type === "pcb_smtpad" && obj.shape === "circle";
1096
- if (isCircular) {
1097
- const radius = getRadiusOfCircuitJsonElement(obj);
1098
- const distance3 = segmentToCircleMinDistance(
1099
- { x: segmentA.x1, y: segmentA.y1 },
1100
- { x: segmentA.x2, y: segmentA.y2 },
1101
- { x: obj.x, y: obj.y, radius }
1102
- );
1103
- const gap2 = distance3 - segmentA.thickness / 2;
1104
- if (gap2 > minClearance - EPSILON) continue;
1105
- const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${primaryObjId}`;
1106
- if (errorIds.has(pcb_trace_error_id)) continue;
1107
- errorIds.add(pcb_trace_error_id);
1108
- errors.push({
1109
- type: "pcb_trace_error",
1110
- error_type: "pcb_trace_error",
1111
- message: constructErrorMessage(
1112
- getReadableName(segmentA.pcb_trace_id),
1113
- `${obj.type} "${getReadableName(getPrimaryId(obj))}"`,
1114
- gap2
1115
- ),
1116
- pcb_trace_id: segmentA.pcb_trace_id,
1117
- center: getClosestPointBetweenSegmentAndBounds(
1118
- segmentA,
1119
- getCollidableBounds(obj)
1120
- ),
1121
- source_trace_id: "",
1122
- pcb_trace_error_id,
1123
- pcb_component_ids: [
1124
- "pcb_component_id" in obj ? obj.pcb_component_id : void 0
1125
- ].filter(Boolean),
1126
- pcb_port_ids: [
1127
- ...getPcbPortIdsConnectedToTraces([segmentA._pcbTrace]),
1128
- "pcb_port_id" in obj ? obj.pcb_port_id : void 0
1129
- ].filter(Boolean)
1130
- });
1131
- }
1132
- if (obj.type === "pcb_smtpad" && (obj.shape === "pill" || 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;
1347
+ if (obj.type === "pcb_smtpad" || obj.type === "pcb_plated_hole" || obj.type === "pcb_via") {
1348
+ const { gap: gap2, center } = getTraceObstacleClearance(segmentA, obj);
1349
+ if (!isTraceObstacleOverlap(gap2)) continue;
1139
1350
  const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${primaryObjId}`;
1140
1351
  if (errorIds.has(pcb_trace_error_id)) continue;
1141
1352
  errorIds.add(pcb_trace_error_id);
@@ -1144,7 +1355,7 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1144
1355
  error_type: "pcb_trace_error",
1145
1356
  message: constructErrorMessage(
1146
1357
  getReadableName(segmentA.pcb_trace_id),
1147
- `${obj.type} "${getReadableName(getPrimaryId(obj))}"`,
1358
+ `${obj.type} "${getReadableName(primaryObjId)}"`,
1148
1359
  gap2
1149
1360
  ),
1150
1361
  pcb_trace_id: segmentA.pcb_trace_id,
@@ -1161,12 +1372,13 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1161
1372
  });
1162
1373
  continue;
1163
1374
  }
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;
1165
- if (isPolygon) {
1166
- const padOutline = getPolygonPointsForPad(obj);
1167
- const { distance: distance3, center } = getSegmentToPolygonClearance(
1168
- segmentA,
1169
- padOutline
1375
+ const isCircular = obj.type === "pcb_hole";
1376
+ if (isCircular) {
1377
+ const radius = getRadiusOfCircuitJsonElement(obj);
1378
+ const distance3 = segmentToCircleMinDistance2(
1379
+ { x: segmentA.x1, y: segmentA.y1 },
1380
+ { x: segmentA.x2, y: segmentA.y2 },
1381
+ { x: obj.x, y: obj.y, radius }
1170
1382
  );
1171
1383
  const gap2 = distance3 - segmentA.thickness / 2;
1172
1384
  if (gap2 > minClearance - EPSILON) continue;
@@ -1182,7 +1394,10 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1182
1394
  gap2
1183
1395
  ),
1184
1396
  pcb_trace_id: segmentA.pcb_trace_id,
1185
- center,
1397
+ center: getClosestPointBetweenSegmentAndBounds(
1398
+ segmentA,
1399
+ getCollidableBounds(obj)
1400
+ ),
1186
1401
  source_trace_id: "",
1187
1402
  pcb_trace_error_id,
1188
1403
  pcb_component_ids: [
@@ -1193,7 +1408,6 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1193
1408
  "pcb_port_id" in obj ? obj.pcb_port_id : void 0
1194
1409
  ].filter(Boolean)
1195
1410
  });
1196
- continue;
1197
1411
  }
1198
1412
  const gap = segmentToBoundsMinDistance(
1199
1413
  { x: segmentA.x1, y: segmentA.y1 },
@@ -1657,11 +1871,11 @@ function checkDifferentNetViaSpacing(circuitJson, {
1657
1871
  }
1658
1872
 
1659
1873
  // lib/check-source-traces-match-pcb-trace-thickness.ts
1660
- import { cju as cju2 } from "@tscircuit/circuit-json-util";
1874
+ import { cju as cju3 } from "@tscircuit/circuit-json-util";
1661
1875
  import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson5 } from "circuit-json-to-connectivity-map";
1662
1876
  function checkSourceTracesMatchPcbTraceThickness(circuitJson) {
1663
1877
  const warnings = [];
1664
- const db = cju2(circuitJson);
1878
+ const db = cju3(circuitJson);
1665
1879
  const sourceTraces = db.source_trace.list();
1666
1880
  const pcbTraces = db.pcb_trace.list();
1667
1881
  const pcbPorts = db.pcb_port.list();
@@ -2044,8 +2258,8 @@ function checkTracesAreContiguous(circuitJson) {
2044
2258
  }
2045
2259
 
2046
2260
  // lib/check-trace-out-of-board/checkTraceOutOfBoard.ts
2047
- import { cju as cju3 } from "@tscircuit/circuit-json-util";
2048
- import { segmentToSegmentMinDistance as segmentToSegmentMinDistance3 } from "@tscircuit/math-utils";
2261
+ import { cju as cju4 } from "@tscircuit/circuit-json-util";
2262
+ import { segmentToSegmentMinDistance as segmentToSegmentMinDistance4 } from "@tscircuit/math-utils";
2049
2263
  function getBoardPolygonPoints(board) {
2050
2264
  if (board.outline && board.outline.length > 0) {
2051
2265
  return board.outline.map((p) => ({ x: p.x, y: p.y }));
@@ -2075,7 +2289,7 @@ function checkPcbTracesOutOfBoard(circuitJson, config = {}) {
2075
2289
  const margin = config.margin ?? getBoardDrcValue(board, "min_board_edge_clearance") ?? jlcMinTolerances.min_board_edge_clearance;
2076
2290
  const boardPoints = getBoardPolygonPoints(board);
2077
2291
  if (!boardPoints) return errors;
2078
- const pcbTraces = cju3(circuitJson).pcb_trace.list();
2292
+ const pcbTraces = cju4(circuitJson).pcb_trace.list();
2079
2293
  for (const trace of pcbTraces) {
2080
2294
  if (trace.route.length < 2) continue;
2081
2295
  for (let i = 0; i < trace.route.length - 1; i++) {
@@ -2089,7 +2303,7 @@ function checkPcbTracesOutOfBoard(circuitJson, config = {}) {
2089
2303
  for (let j = 0; j < boardPoints.length; j++) {
2090
2304
  const edgeStart = boardPoints[j];
2091
2305
  const edgeEnd = boardPoints[(j + 1) % boardPoints.length];
2092
- const distance3 = segmentToSegmentMinDistance3(
2306
+ const distance3 = segmentToSegmentMinDistance4(
2093
2307
  segmentStart,
2094
2308
  segmentEnd,
2095
2309
  edgeStart,
@@ -2124,15 +2338,15 @@ function checkPcbTracesOutOfBoard(circuitJson, config = {}) {
2124
2338
 
2125
2339
  // lib/check-pcb-components-overlap/checkPcbComponentOverlap.ts
2126
2340
  import {
2127
- cju as cju4,
2128
- getBoundsOfPcbElements as getBoundsOfPcbElements4,
2341
+ cju as cju5,
2342
+ getBoundsOfPcbElements as getBoundsOfPcbElements5,
2129
2343
  getPrimaryId as getPrimaryId2
2130
2344
  } from "@tscircuit/circuit-json-util";
2131
2345
  import { doBoundsOverlap as doBoundsOverlap2 } from "@tscircuit/math-utils";
2132
2346
  import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson7 } from "circuit-json-to-connectivity-map";
2133
2347
 
2134
2348
  // lib/check-pcb-components-overlap/doPcbElementsOverlap.ts
2135
- import { getBoundsOfPcbElements as getBoundsOfPcbElements3 } from "@tscircuit/circuit-json-util";
2349
+ import { getBoundsOfPcbElements as getBoundsOfPcbElements4 } from "@tscircuit/circuit-json-util";
2136
2350
  import { doBoundsOverlap } from "@tscircuit/math-utils";
2137
2351
  function getElementLayers(elem) {
2138
2352
  if (elem.type === "pcb_courtyard_circle" || elem.type === "pcb_courtyard_outline" || elem.type === "pcb_courtyard_polygon" || elem.type === "pcb_courtyard_rect") {
@@ -2148,8 +2362,8 @@ function doPcbElementsOverlap(elem1, elem2) {
2148
2362
  const layers1 = getElementLayers(elem1);
2149
2363
  const layers2 = getElementLayers(elem2);
2150
2364
  if (!doLayersOverlap(layers1, layers2)) return false;
2151
- const bounds1 = getBoundsOfPcbElements3([elem1]);
2152
- const bounds2 = getBoundsOfPcbElements3([elem2]);
2365
+ const bounds1 = getBoundsOfPcbElements4([elem1]);
2366
+ const bounds2 = getBoundsOfPcbElements4([elem2]);
2153
2367
  return doBoundsOverlap(bounds1, bounds2);
2154
2368
  }
2155
2369
 
@@ -2166,9 +2380,9 @@ var formatOverlapElementDescription = (circuitJson, element) => {
2166
2380
  function checkPcbComponentOverlap(circuitJson) {
2167
2381
  const errors = [];
2168
2382
  const connMap = getFullConnectivityMapFromCircuitJson7(circuitJson);
2169
- const smtPads = cju4(circuitJson).pcb_smtpad.list();
2170
- const platedHoles = cju4(circuitJson).pcb_plated_hole.list();
2171
- const holes = cju4(circuitJson).pcb_hole.list();
2383
+ const smtPads = cju5(circuitJson).pcb_smtpad.list();
2384
+ const platedHoles = cju5(circuitJson).pcb_plated_hole.list();
2385
+ const holes = cju5(circuitJson).pcb_hole.list();
2172
2386
  const courtyards = circuitJson.filter(isCourtyardElement);
2173
2387
  const componentMap = /* @__PURE__ */ new Map();
2174
2388
  for (const pad of smtPads) {
@@ -2212,7 +2426,7 @@ function checkPcbComponentOverlap(circuitJson) {
2212
2426
  }
2213
2427
  for (const [componentId, componentData] of componentMap) {
2214
2428
  if (componentData.elements.length > 0) {
2215
- componentData.bounds = getBoundsOfPcbElements4(componentData.elements);
2429
+ componentData.bounds = getBoundsOfPcbElements5(componentData.elements);
2216
2430
  }
2217
2431
  }
2218
2432
  const componentsWithElements = Array.from(componentMap.values());
@@ -2285,160 +2499,6 @@ import {
2285
2499
  import {
2286
2500
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson8
2287
2501
  } from "circuit-json-to-connectivity-map";
2288
-
2289
- // lib/check-pad-clearance/common.ts
2290
- import {
2291
- cju as cju5,
2292
- distanceBetweenCircleAndCircle,
2293
- distanceBetweenCircleAndPolygon,
2294
- distanceBetweenPolygonAndPolygon,
2295
- getBoundsOfPcbElements as getBoundsOfPcbElements5
2296
- } from "@tscircuit/circuit-json-util";
2297
- import {
2298
- midpoint,
2299
- segmentToCircleMinDistance as segmentToCircleMinDistance2,
2300
- segmentToSegmentMinDistance as segmentToSegmentMinDistance4
2301
- } from "@tscircuit/math-utils";
2302
- var formatMm = (value) => {
2303
- const rounded = Math.round(value * 1e3) / 1e3;
2304
- return `${Number(rounded.toFixed(3))}mm`;
2305
- };
2306
- var getPadBounds = (pad) => getBoundsOfPcbElements5([pad]);
2307
- var getPadCenter = (pad) => {
2308
- const bounds = getPadBounds(pad);
2309
- return midpoint(
2310
- { x: bounds.minX, y: bounds.minY },
2311
- { x: bounds.maxX, y: bounds.maxY }
2312
- );
2313
- };
2314
- var getPadRadius = (pad) => {
2315
- const bounds = getPadBounds(pad);
2316
- return Math.min(bounds.maxX - bounds.minX, bounds.maxY - bounds.minY) / 2;
2317
- };
2318
- var isCircularPad = (pad) => pad.shape === "circle";
2319
- var isPillPad = (pad) => pad.type === "pcb_smtpad" && (pad.shape === "pill" || pad.shape === "rotated_pill");
2320
- var getCircleShape = (pad) => {
2321
- const center = getPadCenter(pad);
2322
- return {
2323
- kind: "circle",
2324
- x: center.x,
2325
- y: center.y,
2326
- radius: getPadRadius(pad)
2327
- };
2328
- };
2329
- var getPolygonShape = (pad) => {
2330
- if (pad.type === "pcb_smtpad" && (pad.shape === "polygon" || pad.shape === "rotated_rect")) {
2331
- return {
2332
- kind: "polygon",
2333
- points: getPolygonPointsForPad(pad)
2334
- };
2335
- }
2336
- if (pad.type === "pcb_plated_hole" && "rect_pad_width" in pad && "rect_pad_height" in pad) {
2337
- return {
2338
- kind: "polygon",
2339
- points: getPolygonPointsForPad(pad)
2340
- };
2341
- }
2342
- const bounds = getPadBounds(pad);
2343
- return {
2344
- kind: "polygon",
2345
- points: [
2346
- { x: bounds.minX, y: bounds.minY },
2347
- { x: bounds.maxX, y: bounds.minY },
2348
- { x: bounds.maxX, y: bounds.maxY },
2349
- { x: bounds.minX, y: bounds.maxY }
2350
- ]
2351
- };
2352
- };
2353
- var getPadToPadGap = (padA, padB) => {
2354
- if (isPillPad(padA) && isPillPad(padB)) {
2355
- const pillA = getPillCenterLineForPad(padA);
2356
- const pillB = getPillCenterLineForPad(padB);
2357
- return segmentToSegmentMinDistance4(
2358
- pillA.start,
2359
- pillA.end,
2360
- pillB.start,
2361
- pillB.end
2362
- ) - pillA.radius - pillB.radius;
2363
- }
2364
- if (isPillPad(padA) && isCircularPad(padB)) {
2365
- const pill = getPillCenterLineForPad(padA);
2366
- return segmentToCircleMinDistance2(pill.start, pill.end, getCircleShape(padB)) - pill.radius;
2367
- }
2368
- if (isCircularPad(padA) && isPillPad(padB)) {
2369
- const pill = getPillCenterLineForPad(padB);
2370
- return segmentToCircleMinDistance2(pill.start, pill.end, getCircleShape(padA)) - pill.radius;
2371
- }
2372
- if (isPillPad(padA)) {
2373
- const pill = getPillCenterLineForPad(padA);
2374
- return getSegmentToPolygonClearanceFromPoints(
2375
- pill.start,
2376
- pill.end,
2377
- getPolygonShape(padB).points
2378
- ).distance - pill.radius;
2379
- }
2380
- if (isPillPad(padB)) {
2381
- const pill = getPillCenterLineForPad(padB);
2382
- return getSegmentToPolygonClearanceFromPoints(
2383
- pill.start,
2384
- pill.end,
2385
- getPolygonShape(padA).points
2386
- ).distance - pill.radius;
2387
- }
2388
- if (isCircularPad(padA) && isCircularPad(padB)) {
2389
- return distanceBetweenCircleAndCircle(
2390
- getCircleShape(padA),
2391
- getCircleShape(padB)
2392
- );
2393
- }
2394
- if (isCircularPad(padA)) {
2395
- return distanceBetweenCircleAndPolygon(
2396
- getCircleShape(padA),
2397
- getPolygonShape(padB)
2398
- );
2399
- }
2400
- if (isCircularPad(padB)) {
2401
- return distanceBetweenCircleAndPolygon(
2402
- getCircleShape(padB),
2403
- getPolygonShape(padA)
2404
- );
2405
- }
2406
- return distanceBetweenPolygonAndPolygon(
2407
- getPolygonShape(padA),
2408
- getPolygonShape(padB)
2409
- );
2410
- };
2411
- var getPads = (circuitJson) => [
2412
- ...cju5(circuitJson).pcb_smtpad.list(),
2413
- ...cju5(circuitJson).pcb_plated_hole.list()
2414
- ];
2415
- var getTraceSegments = (circuitJson) => {
2416
- const pcbTraces = cju5(circuitJson).pcb_trace.list();
2417
- return pcbTraces.flatMap((pcbTrace) => {
2418
- const segments = [];
2419
- for (let i = 0; i < pcbTrace.route.length - 1; i++) {
2420
- const p1 = pcbTrace.route[i];
2421
- const p2 = pcbTrace.route[i + 1];
2422
- if (p1.route_type !== "wire") continue;
2423
- if (p2.route_type !== "wire") continue;
2424
- if (p1.layer !== p2.layer) continue;
2425
- segments.push({
2426
- type: "pcb_trace_segment",
2427
- pcb_trace_id: pcbTrace.pcb_trace_id,
2428
- _pcbTrace: pcbTrace,
2429
- thickness: "width" in p1 ? p1.width : "width" in p2 ? p2.width : DEFAULT_TRACE_THICKNESS,
2430
- layer: p1.layer,
2431
- x1: p1.x,
2432
- y1: p1.y,
2433
- x2: p2.x,
2434
- y2: p2.y
2435
- });
2436
- }
2437
- return segments;
2438
- });
2439
- };
2440
-
2441
- // lib/check-pad-pad-clearance.ts
2442
2502
  function checkPadPadClearance(circuitJson, {
2443
2503
  connMap,
2444
2504
  minClearance
@@ -2500,10 +2560,6 @@ import {
2500
2560
  getPrimaryId as getPrimaryId4,
2501
2561
  getReadableNameForElement as getReadableNameForElement7
2502
2562
  } from "@tscircuit/circuit-json-util";
2503
- import {
2504
- segmentToBoundsMinDistance as segmentToBoundsMinDistance2,
2505
- segmentToCircleMinDistance as segmentToCircleMinDistance3
2506
- } from "@tscircuit/math-utils";
2507
2563
  import {
2508
2564
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson9
2509
2565
  } from "circuit-json-to-connectivity-map";
@@ -2523,6 +2579,7 @@ function checkPadTraceClearance(circuitJson, {
2523
2579
  getId: (pad) => getPrimaryId4(pad)
2524
2580
  });
2525
2581
  const errors = /* @__PURE__ */ new Map();
2582
+ const overlappingPairIds = /* @__PURE__ */ new Set();
2526
2583
  for (const segment of segments) {
2527
2584
  const nearbyPads = spatialIndex.getObjectsInBounds(
2528
2585
  getCollidableBounds(segment),
@@ -2532,24 +2589,15 @@ function checkPadTraceClearance(circuitJson, {
2532
2589
  const padId = getPrimaryId4(pad);
2533
2590
  if (!getLayersOfPcbElement(pad).includes(segment.layer)) continue;
2534
2591
  if (connMap.areIdsConnected(segment.pcb_trace_id, padId)) continue;
2535
- const center = getPadCenter(pad);
2536
- const gap = pad.type === "pcb_smtpad" && (pad.shape === "pill" || pad.shape === "rotated_pill") ? (() => {
2537
- const { distance: distance3, radius } = getSegmentToPillClearance(
2538
- segment,
2539
- pad
2540
- );
2541
- return distance3 - segment.thickness / 2 - radius;
2542
- })() : isCircularPad(pad) ? segmentToCircleMinDistance3(
2543
- { x: segment.x1, y: segment.y1 },
2544
- { x: segment.x2, y: segment.y2 },
2545
- { x: center.x, y: center.y, radius: getPadRadius(pad) }
2546
- ) - segment.thickness / 2 : segmentToBoundsMinDistance2(
2547
- { x: segment.x1, y: segment.y1 },
2548
- { x: segment.x2, y: segment.y2 },
2549
- getPadBounds(pad)
2550
- ) - segment.thickness / 2;
2551
- if (gap + EPSILON >= minClearance) continue;
2552
2592
  const pairId = `${padId}_${segment.pcb_trace_id}`;
2593
+ const { gap } = getTraceObstacleClearance(segment, pad);
2594
+ if (isTraceObstacleOverlap(gap)) {
2595
+ errors.delete(pairId);
2596
+ overlappingPairIds.add(pairId);
2597
+ continue;
2598
+ }
2599
+ if (overlappingPairIds.has(pairId)) continue;
2600
+ if (gap + EPSILON >= minClearance) continue;
2553
2601
  const nextError = {
2554
2602
  type: "pcb_pad_trace_clearance_error",
2555
2603
  pcb_pad_trace_clearance_error_id: `pad_trace_clearance_${pairId}`,
@@ -2559,10 +2607,7 @@ function checkPadTraceClearance(circuitJson, {
2559
2607
  pcb_trace_id: segment.pcb_trace_id,
2560
2608
  minimum_clearance: minClearance,
2561
2609
  actual_clearance: gap,
2562
- center: {
2563
- x: (center.x + (segment.x1 + segment.x2) / 2) / 2,
2564
- y: (center.y + (segment.y1 + segment.y2) / 2) / 2
2565
- }
2610
+ center: getTraceCenter(segment)
2566
2611
  };
2567
2612
  const current = errors.get(pairId);
2568
2613
  if (!current || gap < current.gap) {
@@ -2575,27 +2620,9 @@ function checkPadTraceClearance(circuitJson, {
2575
2620
 
2576
2621
  // lib/check-via-trace-clearance.ts
2577
2622
  import { getReadableNameForElement as getReadableNameForElement8 } from "@tscircuit/circuit-json-util";
2578
- import { segmentToCircleMinDistance as segmentToCircleMinDistance4 } from "@tscircuit/math-utils";
2579
2623
  import {
2580
2624
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson10
2581
2625
  } from "circuit-json-to-connectivity-map";
2582
- var getClosestPointOnSegment = (point, segmentStart, segmentEnd) => {
2583
- const dx = segmentEnd.x - segmentStart.x;
2584
- const dy = segmentEnd.y - segmentStart.y;
2585
- const lengthSquared = dx * dx + dy * dy;
2586
- if (lengthSquared === 0) return segmentStart;
2587
- const t = Math.max(
2588
- 0,
2589
- Math.min(
2590
- 1,
2591
- ((point.x - segmentStart.x) * dx + (point.y - segmentStart.y) * dy) / lengthSquared
2592
- )
2593
- );
2594
- return {
2595
- x: segmentStart.x + t * dx,
2596
- y: segmentStart.y + t * dy
2597
- };
2598
- };
2599
2626
  function checkViaTraceClearance(circuitJson, {
2600
2627
  connMap,
2601
2628
  minClearance
@@ -2607,28 +2634,21 @@ function checkViaTraceClearance(circuitJson, {
2607
2634
  minClearance ??= getBoardDrcValue(board, "min_trace_to_pad_edge_clearance") ?? jlcMinTolerances.min_trace_to_pad_edge_clearance;
2608
2635
  connMap ??= getFullConnectivityMapFromCircuitJson10(circuitJson);
2609
2636
  const errors = /* @__PURE__ */ new Map();
2637
+ const overlappingPairIds = /* @__PURE__ */ new Set();
2610
2638
  for (const via of vias) {
2611
- const viaRadius = via.outer_diameter / 2;
2612
2639
  for (const segment of segments) {
2613
2640
  if (!getLayersOfPcbElement(via).includes(segment.layer)) continue;
2614
2641
  if (connMap.areIdsConnected(segment.pcb_trace_id, via.pcb_via_id))
2615
2642
  continue;
2616
- const gap = segmentToCircleMinDistance4(
2617
- { x: segment.x1, y: segment.y1 },
2618
- { x: segment.x2, y: segment.y2 },
2619
- {
2620
- x: via.x,
2621
- y: via.y,
2622
- radius: viaRadius
2623
- }
2624
- ) - segment.thickness / 2;
2625
- if (gap + EPSILON >= minClearance) continue;
2626
- const closestPoint = getClosestPointOnSegment(
2627
- { x: via.x, y: via.y },
2628
- { x: segment.x1, y: segment.y1 },
2629
- { x: segment.x2, y: segment.y2 }
2630
- );
2631
2643
  const pairId = `${via.pcb_via_id}_${segment.pcb_trace_id}`;
2644
+ const { gap } = getTraceObstacleClearance(segment, via);
2645
+ if (isTraceObstacleOverlap(gap)) {
2646
+ errors.delete(pairId);
2647
+ overlappingPairIds.add(pairId);
2648
+ continue;
2649
+ }
2650
+ if (overlappingPairIds.has(pairId)) continue;
2651
+ if (gap + EPSILON >= minClearance) continue;
2632
2652
  const nextError = {
2633
2653
  type: "pcb_via_trace_clearance_error",
2634
2654
  pcb_via_trace_clearance_error_id: `via_trace_clearance_${pairId}`,
@@ -2638,10 +2658,7 @@ function checkViaTraceClearance(circuitJson, {
2638
2658
  pcb_trace_id: segment.pcb_trace_id,
2639
2659
  minimum_clearance: minClearance,
2640
2660
  actual_clearance: gap,
2641
- center: {
2642
- x: (via.x + closestPoint.x) / 2,
2643
- y: (via.y + closestPoint.y) / 2
2644
- }
2661
+ center: getTraceCenter(segment)
2645
2662
  };
2646
2663
  const current = errors.get(pairId);
2647
2664
  if (!current || gap < current.gap) {
@@ -2652,6 +2669,27 @@ function checkViaTraceClearance(circuitJson, {
2652
2669
  return Array.from(errors.values()).map(({ error }) => error);
2653
2670
  }
2654
2671
 
2672
+ // lib/dedupe-pcb-drc-errors.ts
2673
+ var dedupePcbDrcErrors = (errors) => {
2674
+ const specificallyReportedPairIds = /* @__PURE__ */ new Set();
2675
+ for (const error of errors) {
2676
+ if (error.type === "pcb_pad_trace_clearance_error" && typeof error.pcb_trace_id === "string" && typeof error.pcb_pad_id === "string") {
2677
+ specificallyReportedPairIds.add(
2678
+ `overlap_${error.pcb_trace_id}_${error.pcb_pad_id}`
2679
+ );
2680
+ }
2681
+ if (error.type === "pcb_via_trace_clearance_error" && typeof error.pcb_trace_id === "string" && typeof error.pcb_via_id === "string") {
2682
+ specificallyReportedPairIds.add(
2683
+ `overlap_${error.pcb_trace_id}_${error.pcb_via_id}`
2684
+ );
2685
+ }
2686
+ }
2687
+ return errors.filter((element) => {
2688
+ const error = element;
2689
+ return !(error.type === "pcb_trace_error" && typeof error.pcb_trace_error_id === "string" && specificallyReportedPairIds.has(error.pcb_trace_error_id));
2690
+ });
2691
+ };
2692
+
2655
2693
  // lib/check-pin-must-be-connected.ts
2656
2694
  function checkPinMustBeConnected(circuitJson) {
2657
2695
  const errors = [];
@@ -3057,7 +3095,6 @@ async function runAllPlacementChecks(circuitJson) {
3057
3095
  ...checkPcbComponentsOutOfBoard(circuitJson),
3058
3096
  ...checkPcbComponentOverlap(circuitJson),
3059
3097
  ...checkPadPadClearance(circuitJson),
3060
- ...checkPadTraceClearance(circuitJson),
3061
3098
  ...checkCourtyardOverlap(circuitJson),
3062
3099
  ...checkConnectorAccessibleOrientation(circuitJson)
3063
3100
  ];
@@ -3077,6 +3114,7 @@ async function runAllRoutingChecks(circuitJson) {
3077
3114
  ...checkEachPcbPortConnectedToPcbTraces(circuitJson),
3078
3115
  ...checkSourceTracesHavePcbTraces(circuitJson),
3079
3116
  ...checkEachPcbTraceNonOverlapping(circuitJson),
3117
+ ...checkPadTraceClearance(circuitJson),
3080
3118
  ...checkViaTraceClearance(circuitJson),
3081
3119
  ...checkSameNetViaSpacing(circuitJson),
3082
3120
  ...checkDifferentNetViaSpacing(circuitJson),
@@ -3113,6 +3151,7 @@ export {
3113
3151
  checkTracesAreContiguous,
3114
3152
  checkViaTraceClearance,
3115
3153
  checkViasOffBoard,
3154
+ dedupePcbDrcErrors,
3116
3155
  runAllChecks,
3117
3156
  runAllNetlistChecks,
3118
3157
  runAllPinSpecificationChecks,