@tscircuit/checks 0.0.144 → 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
 
@@ -497,17 +512,284 @@ function checkEachPcbPortConnectedToPcbTraces(circuitJson) {
497
512
  }
498
513
 
499
514
  // lib/check-each-pcb-trace-non-overlapping/check-each-pcb-trace-non-overlapping.ts
500
- import { cju, getReadableNameForElement as getReadableNameForElement2 } from "@tscircuit/circuit-json-util";
515
+ import { cju as cju2, getReadableNameForElement as getReadableNameForElement2 } from "@tscircuit/circuit-json-util";
501
516
  import { getPrimaryId } from "@tscircuit/circuit-json-util";
502
517
  import {
503
518
  segmentToBoundsMinDistance,
504
- segmentToCircleMinDistance
519
+ segmentToCircleMinDistance as segmentToCircleMinDistance2
505
520
  } from "@tscircuit/math-utils";
506
- import { segmentToSegmentMinDistance as segmentToSegmentMinDistance2 } from "@tscircuit/math-utils";
521
+ import { segmentToSegmentMinDistance as segmentToSegmentMinDistance3 } from "@tscircuit/math-utils";
507
522
  import {
508
523
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson2
509
524
  } from "circuit-json-to-connectivity-map";
510
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
+
511
793
  // lib/data-structures/SpatialIndex.ts
512
794
  var SpatialObjectIndex = class {
513
795
  buckets;
@@ -607,31 +889,6 @@ var SpatialObjectIndex = class {
607
889
  }
608
890
  };
609
891
 
610
- // node_modules/@tscircuit/jlcpcb-manufacturing-specs/lib/jlcpcb-manufacturing-specs.ts
611
- var jlcMinTolerances = {
612
- min_trace_width: 0.1,
613
- min_via_hole_edge_to_via_hole_edge_clearance: 0.1,
614
- min_plated_hole_drill_edge_to_drill_edge_clearance: 0.15,
615
- min_trace_to_pad_edge_clearance: 0.1,
616
- min_pad_edge_to_pad_edge_clearance: 0.1,
617
- min_board_edge_clearance: 0.2,
618
- min_via_hole_diameter: 0.2,
619
- min_via_pad_diameter: 0.3
620
- };
621
-
622
- // lib/drc-defaults.ts
623
- var DEFAULT_TRACE_MARGIN = 0.1;
624
- var DEFAULT_TRACE_THICKNESS = jlcMinTolerances.min_trace_width;
625
- var DEFAULT_VIA_DIAMETER = jlcMinTolerances.min_via_pad_diameter;
626
- var DEFAULT_VIA_BOARD_MARGIN = jlcMinTolerances.min_board_edge_clearance;
627
- var DEFAULT_SAME_NET_VIA_MARGIN = jlcMinTolerances.min_via_hole_edge_to_via_hole_edge_clearance;
628
- var DEFAULT_DIFFERENT_NET_VIA_MARGIN = jlcMinTolerances.min_via_hole_edge_to_via_hole_edge_clearance;
629
- var DEFAULT_PAD_PAD_CLEARANCE = jlcMinTolerances.min_pad_edge_to_pad_edge_clearance;
630
- var DEFAULT_PAD_TRACE_CLEARANCE = jlcMinTolerances.min_trace_to_pad_edge_clearance;
631
- var EPSILON = 5e-3;
632
- var getPcbBoard = (circuitJson) => circuitJson.find((el) => el.type === "pcb_board");
633
- var getBoardDrcValue = (board, key) => board?.[key];
634
-
635
892
  // lib/util/getLayersOfPcbElement.ts
636
893
  import { all_layers } from "circuit-json";
637
894
  function getLayersOfPcbElement(obj) {
@@ -895,7 +1152,7 @@ var clamp = (value, min, max) => {
895
1152
  };
896
1153
 
897
1154
  // lib/check-each-pcb-trace-non-overlapping/getCollidableBounds.ts
898
- import { getBoundsOfPcbElements as getBoundsOfPcbElements2 } from "@tscircuit/circuit-json-util";
1155
+ import { getBoundsOfPcbElements as getBoundsOfPcbElements3 } from "@tscircuit/circuit-json-util";
899
1156
  var getCollidableBounds = (collidable) => {
900
1157
  if (collidable.type === "pcb_trace_segment") {
901
1158
  return {
@@ -926,7 +1183,7 @@ var getCollidableBounds = (collidable) => {
926
1183
  };
927
1184
  }
928
1185
  }
929
- return getBoundsOfPcbElements2([collidable]);
1186
+ return getBoundsOfPcbElements3([collidable]);
930
1187
  };
931
1188
 
932
1189
  // lib/check-each-pcb-trace-non-overlapping/getPcbPortIdsConnectedToTraces.ts
@@ -984,7 +1241,7 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
984
1241
  connMap ??= getFullConnectivityMapFromCircuitJson2(circuitJson);
985
1242
  const board = getPcbBoard(circuitJson);
986
1243
  minClearance ??= getBoardDrcValue(board, "min_trace_to_pad_edge_clearance") ?? DEFAULT_TRACE_MARGIN;
987
- const pcbTraces = cju(circuitJson).pcb_trace.list();
1244
+ const pcbTraces = cju2(circuitJson).pcb_trace.list();
988
1245
  const pcbTraceSegments = pcbTraces.flatMap((pcbTrace) => {
989
1246
  const segments = [];
990
1247
  for (let i = 0; i < pcbTrace.route.length - 1; i++) {
@@ -1007,11 +1264,11 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1007
1264
  }
1008
1265
  return segments;
1009
1266
  });
1010
- const pcbSmtPads = cju(circuitJson).pcb_smtpad.list();
1011
- const pcbPlatedHoles = cju(circuitJson).pcb_plated_hole.list();
1012
- const pcbHoles = cju(circuitJson).pcb_hole.list();
1013
- const pcbVias = cju(circuitJson).pcb_via.list();
1014
- 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();
1015
1272
  const allObjects = [
1016
1273
  ...pcbTraceSegments,
1017
1274
  ...pcbSmtPads,
@@ -1026,7 +1283,7 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1026
1283
  });
1027
1284
  const getReadableName = (id) => getReadableNameForElement2(circuitJson, id);
1028
1285
  const constructErrorMessage = (traceName, otherName, gap) => {
1029
- if (gap < 0) {
1286
+ if (isTraceObstacleOverlap(gap)) {
1030
1287
  return `PCB trace ${traceName} overlaps with ${otherName} (accidental contact)`;
1031
1288
  }
1032
1289
  return `PCB trace ${traceName} is too close to ${otherName} (gap: ${gap.toFixed(3)}mm)`;
@@ -1049,7 +1306,7 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1049
1306
  if (segmentA.layer !== segmentB.layer) continue;
1050
1307
  if (connMap.areIdsConnected(segmentA.pcb_trace_id, segmentB.pcb_trace_id))
1051
1308
  continue;
1052
- const gap2 = segmentToSegmentMinDistance2(
1309
+ const gap2 = segmentToSegmentMinDistance3(
1053
1310
  { x: segmentA.x1, y: segmentA.y1 },
1054
1311
  { x: segmentA.x2, y: segmentA.y2 },
1055
1312
  { x: segmentB.x1, y: segmentB.y1 },
@@ -1087,16 +1344,9 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1087
1344
  "pcb_trace_id" in obj ? obj.pcb_trace_id : primaryObjId
1088
1345
  ))
1089
1346
  continue;
1090
- 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";
1091
- if (isCircular) {
1092
- const radius = getRadiusOfCircuitJsonElement(obj);
1093
- const distance3 = segmentToCircleMinDistance(
1094
- { x: segmentA.x1, y: segmentA.y1 },
1095
- { x: segmentA.x2, y: segmentA.y2 },
1096
- { x: obj.x, y: obj.y, radius }
1097
- );
1098
- const gap2 = distance3 - segmentA.thickness / 2;
1099
- 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;
1100
1350
  const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${primaryObjId}`;
1101
1351
  if (errorIds.has(pcb_trace_error_id)) continue;
1102
1352
  errorIds.add(pcb_trace_error_id);
@@ -1105,41 +1355,7 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1105
1355
  error_type: "pcb_trace_error",
1106
1356
  message: constructErrorMessage(
1107
1357
  getReadableName(segmentA.pcb_trace_id),
1108
- `${obj.type} "${getReadableName(getPrimaryId(obj))}"`,
1109
- gap2
1110
- ),
1111
- pcb_trace_id: segmentA.pcb_trace_id,
1112
- center: getClosestPointBetweenSegmentAndBounds(
1113
- segmentA,
1114
- getCollidableBounds(obj)
1115
- ),
1116
- source_trace_id: "",
1117
- pcb_trace_error_id,
1118
- pcb_component_ids: [
1119
- "pcb_component_id" in obj ? obj.pcb_component_id : void 0
1120
- ].filter(Boolean),
1121
- pcb_port_ids: [
1122
- ...getPcbPortIdsConnectedToTraces([segmentA._pcbTrace]),
1123
- "pcb_port_id" in obj ? obj.pcb_port_id : void 0
1124
- ].filter(Boolean)
1125
- });
1126
- }
1127
- if (obj.type === "pcb_smtpad" && (obj.shape === "pill" || obj.shape === "rotated_pill")) {
1128
- const { distance: distance3, center, radius } = getSegmentToPillClearance(
1129
- segmentA,
1130
- obj
1131
- );
1132
- const gap2 = distance3 - segmentA.thickness / 2 - radius;
1133
- if (gap2 > minClearance - EPSILON) continue;
1134
- const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${primaryObjId}`;
1135
- if (errorIds.has(pcb_trace_error_id)) continue;
1136
- errorIds.add(pcb_trace_error_id);
1137
- errors.push({
1138
- type: "pcb_trace_error",
1139
- error_type: "pcb_trace_error",
1140
- message: constructErrorMessage(
1141
- getReadableName(segmentA.pcb_trace_id),
1142
- `${obj.type} "${getReadableName(getPrimaryId(obj))}"`,
1358
+ `${obj.type} "${getReadableName(primaryObjId)}"`,
1143
1359
  gap2
1144
1360
  ),
1145
1361
  pcb_trace_id: segmentA.pcb_trace_id,
@@ -1156,12 +1372,13 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1156
1372
  });
1157
1373
  continue;
1158
1374
  }
1159
- 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;
1160
- if (isPolygon) {
1161
- const padOutline = getPolygonPointsForPad(obj);
1162
- const { distance: distance3, center } = getSegmentToPolygonClearance(
1163
- segmentA,
1164
- 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 }
1165
1382
  );
1166
1383
  const gap2 = distance3 - segmentA.thickness / 2;
1167
1384
  if (gap2 > minClearance - EPSILON) continue;
@@ -1177,7 +1394,10 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1177
1394
  gap2
1178
1395
  ),
1179
1396
  pcb_trace_id: segmentA.pcb_trace_id,
1180
- center,
1397
+ center: getClosestPointBetweenSegmentAndBounds(
1398
+ segmentA,
1399
+ getCollidableBounds(obj)
1400
+ ),
1181
1401
  source_trace_id: "",
1182
1402
  pcb_trace_error_id,
1183
1403
  pcb_component_ids: [
@@ -1188,7 +1408,6 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1188
1408
  "pcb_port_id" in obj ? obj.pcb_port_id : void 0
1189
1409
  ].filter(Boolean)
1190
1410
  });
1191
- continue;
1192
1411
  }
1193
1412
  const gap = segmentToBoundsMinDistance(
1194
1413
  { x: segmentA.x1, y: segmentA.y1 },
@@ -1652,11 +1871,11 @@ function checkDifferentNetViaSpacing(circuitJson, {
1652
1871
  }
1653
1872
 
1654
1873
  // lib/check-source-traces-match-pcb-trace-thickness.ts
1655
- import { cju as cju2 } from "@tscircuit/circuit-json-util";
1874
+ import { cju as cju3 } from "@tscircuit/circuit-json-util";
1656
1875
  import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson5 } from "circuit-json-to-connectivity-map";
1657
1876
  function checkSourceTracesMatchPcbTraceThickness(circuitJson) {
1658
1877
  const warnings = [];
1659
- const db = cju2(circuitJson);
1878
+ const db = cju3(circuitJson);
1660
1879
  const sourceTraces = db.source_trace.list();
1661
1880
  const pcbTraces = db.pcb_trace.list();
1662
1881
  const pcbPorts = db.pcb_port.list();
@@ -2039,8 +2258,8 @@ function checkTracesAreContiguous(circuitJson) {
2039
2258
  }
2040
2259
 
2041
2260
  // lib/check-trace-out-of-board/checkTraceOutOfBoard.ts
2042
- import { cju as cju3 } from "@tscircuit/circuit-json-util";
2043
- 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";
2044
2263
  function getBoardPolygonPoints(board) {
2045
2264
  if (board.outline && board.outline.length > 0) {
2046
2265
  return board.outline.map((p) => ({ x: p.x, y: p.y }));
@@ -2070,7 +2289,7 @@ function checkPcbTracesOutOfBoard(circuitJson, config = {}) {
2070
2289
  const margin = config.margin ?? getBoardDrcValue(board, "min_board_edge_clearance") ?? jlcMinTolerances.min_board_edge_clearance;
2071
2290
  const boardPoints = getBoardPolygonPoints(board);
2072
2291
  if (!boardPoints) return errors;
2073
- const pcbTraces = cju3(circuitJson).pcb_trace.list();
2292
+ const pcbTraces = cju4(circuitJson).pcb_trace.list();
2074
2293
  for (const trace of pcbTraces) {
2075
2294
  if (trace.route.length < 2) continue;
2076
2295
  for (let i = 0; i < trace.route.length - 1; i++) {
@@ -2084,7 +2303,7 @@ function checkPcbTracesOutOfBoard(circuitJson, config = {}) {
2084
2303
  for (let j = 0; j < boardPoints.length; j++) {
2085
2304
  const edgeStart = boardPoints[j];
2086
2305
  const edgeEnd = boardPoints[(j + 1) % boardPoints.length];
2087
- const distance3 = segmentToSegmentMinDistance3(
2306
+ const distance3 = segmentToSegmentMinDistance4(
2088
2307
  segmentStart,
2089
2308
  segmentEnd,
2090
2309
  edgeStart,
@@ -2119,15 +2338,15 @@ function checkPcbTracesOutOfBoard(circuitJson, config = {}) {
2119
2338
 
2120
2339
  // lib/check-pcb-components-overlap/checkPcbComponentOverlap.ts
2121
2340
  import {
2122
- cju as cju4,
2123
- getBoundsOfPcbElements as getBoundsOfPcbElements4,
2341
+ cju as cju5,
2342
+ getBoundsOfPcbElements as getBoundsOfPcbElements5,
2124
2343
  getPrimaryId as getPrimaryId2
2125
2344
  } from "@tscircuit/circuit-json-util";
2126
2345
  import { doBoundsOverlap as doBoundsOverlap2 } from "@tscircuit/math-utils";
2127
2346
  import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson7 } from "circuit-json-to-connectivity-map";
2128
2347
 
2129
2348
  // lib/check-pcb-components-overlap/doPcbElementsOverlap.ts
2130
- import { getBoundsOfPcbElements as getBoundsOfPcbElements3 } from "@tscircuit/circuit-json-util";
2349
+ import { getBoundsOfPcbElements as getBoundsOfPcbElements4 } from "@tscircuit/circuit-json-util";
2131
2350
  import { doBoundsOverlap } from "@tscircuit/math-utils";
2132
2351
  function getElementLayers(elem) {
2133
2352
  if (elem.type === "pcb_courtyard_circle" || elem.type === "pcb_courtyard_outline" || elem.type === "pcb_courtyard_polygon" || elem.type === "pcb_courtyard_rect") {
@@ -2143,8 +2362,8 @@ function doPcbElementsOverlap(elem1, elem2) {
2143
2362
  const layers1 = getElementLayers(elem1);
2144
2363
  const layers2 = getElementLayers(elem2);
2145
2364
  if (!doLayersOverlap(layers1, layers2)) return false;
2146
- const bounds1 = getBoundsOfPcbElements3([elem1]);
2147
- const bounds2 = getBoundsOfPcbElements3([elem2]);
2365
+ const bounds1 = getBoundsOfPcbElements4([elem1]);
2366
+ const bounds2 = getBoundsOfPcbElements4([elem2]);
2148
2367
  return doBoundsOverlap(bounds1, bounds2);
2149
2368
  }
2150
2369
 
@@ -2161,9 +2380,9 @@ var formatOverlapElementDescription = (circuitJson, element) => {
2161
2380
  function checkPcbComponentOverlap(circuitJson) {
2162
2381
  const errors = [];
2163
2382
  const connMap = getFullConnectivityMapFromCircuitJson7(circuitJson);
2164
- const smtPads = cju4(circuitJson).pcb_smtpad.list();
2165
- const platedHoles = cju4(circuitJson).pcb_plated_hole.list();
2166
- 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();
2167
2386
  const courtyards = circuitJson.filter(isCourtyardElement);
2168
2387
  const componentMap = /* @__PURE__ */ new Map();
2169
2388
  for (const pad of smtPads) {
@@ -2207,7 +2426,7 @@ function checkPcbComponentOverlap(circuitJson) {
2207
2426
  }
2208
2427
  for (const [componentId, componentData] of componentMap) {
2209
2428
  if (componentData.elements.length > 0) {
2210
- componentData.bounds = getBoundsOfPcbElements4(componentData.elements);
2429
+ componentData.bounds = getBoundsOfPcbElements5(componentData.elements);
2211
2430
  }
2212
2431
  }
2213
2432
  const componentsWithElements = Array.from(componentMap.values());
@@ -2280,160 +2499,6 @@ import {
2280
2499
  import {
2281
2500
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson8
2282
2501
  } from "circuit-json-to-connectivity-map";
2283
-
2284
- // lib/check-pad-clearance/common.ts
2285
- import {
2286
- cju as cju5,
2287
- distanceBetweenCircleAndCircle,
2288
- distanceBetweenCircleAndPolygon,
2289
- distanceBetweenPolygonAndPolygon,
2290
- getBoundsOfPcbElements as getBoundsOfPcbElements5
2291
- } from "@tscircuit/circuit-json-util";
2292
- import {
2293
- midpoint,
2294
- segmentToCircleMinDistance as segmentToCircleMinDistance2,
2295
- segmentToSegmentMinDistance as segmentToSegmentMinDistance4
2296
- } from "@tscircuit/math-utils";
2297
- var formatMm = (value) => {
2298
- const rounded = Math.round(value * 1e3) / 1e3;
2299
- return `${Number(rounded.toFixed(3))}mm`;
2300
- };
2301
- var getPadBounds = (pad) => getBoundsOfPcbElements5([pad]);
2302
- var getPadCenter = (pad) => {
2303
- const bounds = getPadBounds(pad);
2304
- return midpoint(
2305
- { x: bounds.minX, y: bounds.minY },
2306
- { x: bounds.maxX, y: bounds.maxY }
2307
- );
2308
- };
2309
- var getPadRadius = (pad) => {
2310
- const bounds = getPadBounds(pad);
2311
- return Math.min(bounds.maxX - bounds.minX, bounds.maxY - bounds.minY) / 2;
2312
- };
2313
- var isCircularPad = (pad) => pad.shape === "circle";
2314
- var isPillPad = (pad) => pad.type === "pcb_smtpad" && (pad.shape === "pill" || pad.shape === "rotated_pill");
2315
- var getCircleShape = (pad) => {
2316
- const center = getPadCenter(pad);
2317
- return {
2318
- kind: "circle",
2319
- x: center.x,
2320
- y: center.y,
2321
- radius: getPadRadius(pad)
2322
- };
2323
- };
2324
- var getPolygonShape = (pad) => {
2325
- if (pad.type === "pcb_smtpad" && (pad.shape === "polygon" || pad.shape === "rotated_rect")) {
2326
- return {
2327
- kind: "polygon",
2328
- points: getPolygonPointsForPad(pad)
2329
- };
2330
- }
2331
- if (pad.type === "pcb_plated_hole" && "rect_pad_width" in pad && "rect_pad_height" in pad) {
2332
- return {
2333
- kind: "polygon",
2334
- points: getPolygonPointsForPad(pad)
2335
- };
2336
- }
2337
- const bounds = getPadBounds(pad);
2338
- return {
2339
- kind: "polygon",
2340
- points: [
2341
- { x: bounds.minX, y: bounds.minY },
2342
- { x: bounds.maxX, y: bounds.minY },
2343
- { x: bounds.maxX, y: bounds.maxY },
2344
- { x: bounds.minX, y: bounds.maxY }
2345
- ]
2346
- };
2347
- };
2348
- var getPadToPadGap = (padA, padB) => {
2349
- if (isPillPad(padA) && isPillPad(padB)) {
2350
- const pillA = getPillCenterLineForPad(padA);
2351
- const pillB = getPillCenterLineForPad(padB);
2352
- return segmentToSegmentMinDistance4(
2353
- pillA.start,
2354
- pillA.end,
2355
- pillB.start,
2356
- pillB.end
2357
- ) - pillA.radius - pillB.radius;
2358
- }
2359
- if (isPillPad(padA) && isCircularPad(padB)) {
2360
- const pill = getPillCenterLineForPad(padA);
2361
- return segmentToCircleMinDistance2(pill.start, pill.end, getCircleShape(padB)) - pill.radius;
2362
- }
2363
- if (isCircularPad(padA) && isPillPad(padB)) {
2364
- const pill = getPillCenterLineForPad(padB);
2365
- return segmentToCircleMinDistance2(pill.start, pill.end, getCircleShape(padA)) - pill.radius;
2366
- }
2367
- if (isPillPad(padA)) {
2368
- const pill = getPillCenterLineForPad(padA);
2369
- return getSegmentToPolygonClearanceFromPoints(
2370
- pill.start,
2371
- pill.end,
2372
- getPolygonShape(padB).points
2373
- ).distance - pill.radius;
2374
- }
2375
- if (isPillPad(padB)) {
2376
- const pill = getPillCenterLineForPad(padB);
2377
- return getSegmentToPolygonClearanceFromPoints(
2378
- pill.start,
2379
- pill.end,
2380
- getPolygonShape(padA).points
2381
- ).distance - pill.radius;
2382
- }
2383
- if (isCircularPad(padA) && isCircularPad(padB)) {
2384
- return distanceBetweenCircleAndCircle(
2385
- getCircleShape(padA),
2386
- getCircleShape(padB)
2387
- );
2388
- }
2389
- if (isCircularPad(padA)) {
2390
- return distanceBetweenCircleAndPolygon(
2391
- getCircleShape(padA),
2392
- getPolygonShape(padB)
2393
- );
2394
- }
2395
- if (isCircularPad(padB)) {
2396
- return distanceBetweenCircleAndPolygon(
2397
- getCircleShape(padB),
2398
- getPolygonShape(padA)
2399
- );
2400
- }
2401
- return distanceBetweenPolygonAndPolygon(
2402
- getPolygonShape(padA),
2403
- getPolygonShape(padB)
2404
- );
2405
- };
2406
- var getPads = (circuitJson) => [
2407
- ...cju5(circuitJson).pcb_smtpad.list(),
2408
- ...cju5(circuitJson).pcb_plated_hole.list()
2409
- ];
2410
- var getTraceSegments = (circuitJson) => {
2411
- const pcbTraces = cju5(circuitJson).pcb_trace.list();
2412
- return pcbTraces.flatMap((pcbTrace) => {
2413
- const segments = [];
2414
- for (let i = 0; i < pcbTrace.route.length - 1; i++) {
2415
- const p1 = pcbTrace.route[i];
2416
- const p2 = pcbTrace.route[i + 1];
2417
- if (p1.route_type !== "wire") continue;
2418
- if (p2.route_type !== "wire") continue;
2419
- if (p1.layer !== p2.layer) continue;
2420
- segments.push({
2421
- type: "pcb_trace_segment",
2422
- pcb_trace_id: pcbTrace.pcb_trace_id,
2423
- _pcbTrace: pcbTrace,
2424
- thickness: "width" in p1 ? p1.width : "width" in p2 ? p2.width : DEFAULT_TRACE_THICKNESS,
2425
- layer: p1.layer,
2426
- x1: p1.x,
2427
- y1: p1.y,
2428
- x2: p2.x,
2429
- y2: p2.y
2430
- });
2431
- }
2432
- return segments;
2433
- });
2434
- };
2435
-
2436
- // lib/check-pad-pad-clearance.ts
2437
2502
  function checkPadPadClearance(circuitJson, {
2438
2503
  connMap,
2439
2504
  minClearance
@@ -2495,10 +2560,6 @@ import {
2495
2560
  getPrimaryId as getPrimaryId4,
2496
2561
  getReadableNameForElement as getReadableNameForElement7
2497
2562
  } from "@tscircuit/circuit-json-util";
2498
- import {
2499
- segmentToBoundsMinDistance as segmentToBoundsMinDistance2,
2500
- segmentToCircleMinDistance as segmentToCircleMinDistance3
2501
- } from "@tscircuit/math-utils";
2502
2563
  import {
2503
2564
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson9
2504
2565
  } from "circuit-json-to-connectivity-map";
@@ -2518,6 +2579,7 @@ function checkPadTraceClearance(circuitJson, {
2518
2579
  getId: (pad) => getPrimaryId4(pad)
2519
2580
  });
2520
2581
  const errors = /* @__PURE__ */ new Map();
2582
+ const overlappingPairIds = /* @__PURE__ */ new Set();
2521
2583
  for (const segment of segments) {
2522
2584
  const nearbyPads = spatialIndex.getObjectsInBounds(
2523
2585
  getCollidableBounds(segment),
@@ -2527,24 +2589,15 @@ function checkPadTraceClearance(circuitJson, {
2527
2589
  const padId = getPrimaryId4(pad);
2528
2590
  if (!getLayersOfPcbElement(pad).includes(segment.layer)) continue;
2529
2591
  if (connMap.areIdsConnected(segment.pcb_trace_id, padId)) continue;
2530
- const center = getPadCenter(pad);
2531
- const gap = pad.type === "pcb_smtpad" && (pad.shape === "pill" || pad.shape === "rotated_pill") ? (() => {
2532
- const { distance: distance3, radius } = getSegmentToPillClearance(
2533
- segment,
2534
- pad
2535
- );
2536
- return distance3 - segment.thickness / 2 - radius;
2537
- })() : isCircularPad(pad) ? segmentToCircleMinDistance3(
2538
- { x: segment.x1, y: segment.y1 },
2539
- { x: segment.x2, y: segment.y2 },
2540
- { x: center.x, y: center.y, radius: getPadRadius(pad) }
2541
- ) - segment.thickness / 2 : segmentToBoundsMinDistance2(
2542
- { x: segment.x1, y: segment.y1 },
2543
- { x: segment.x2, y: segment.y2 },
2544
- getPadBounds(pad)
2545
- ) - segment.thickness / 2;
2546
- if (gap + EPSILON >= minClearance) continue;
2547
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;
2548
2601
  const nextError = {
2549
2602
  type: "pcb_pad_trace_clearance_error",
2550
2603
  pcb_pad_trace_clearance_error_id: `pad_trace_clearance_${pairId}`,
@@ -2554,10 +2607,7 @@ function checkPadTraceClearance(circuitJson, {
2554
2607
  pcb_trace_id: segment.pcb_trace_id,
2555
2608
  minimum_clearance: minClearance,
2556
2609
  actual_clearance: gap,
2557
- center: {
2558
- x: (center.x + (segment.x1 + segment.x2) / 2) / 2,
2559
- y: (center.y + (segment.y1 + segment.y2) / 2) / 2
2560
- }
2610
+ center: getTraceCenter(segment)
2561
2611
  };
2562
2612
  const current = errors.get(pairId);
2563
2613
  if (!current || gap < current.gap) {
@@ -2570,27 +2620,9 @@ function checkPadTraceClearance(circuitJson, {
2570
2620
 
2571
2621
  // lib/check-via-trace-clearance.ts
2572
2622
  import { getReadableNameForElement as getReadableNameForElement8 } from "@tscircuit/circuit-json-util";
2573
- import { segmentToCircleMinDistance as segmentToCircleMinDistance4 } from "@tscircuit/math-utils";
2574
2623
  import {
2575
2624
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson10
2576
2625
  } from "circuit-json-to-connectivity-map";
2577
- var getClosestPointOnSegment = (point, segmentStart, segmentEnd) => {
2578
- const dx = segmentEnd.x - segmentStart.x;
2579
- const dy = segmentEnd.y - segmentStart.y;
2580
- const lengthSquared = dx * dx + dy * dy;
2581
- if (lengthSquared === 0) return segmentStart;
2582
- const t = Math.max(
2583
- 0,
2584
- Math.min(
2585
- 1,
2586
- ((point.x - segmentStart.x) * dx + (point.y - segmentStart.y) * dy) / lengthSquared
2587
- )
2588
- );
2589
- return {
2590
- x: segmentStart.x + t * dx,
2591
- y: segmentStart.y + t * dy
2592
- };
2593
- };
2594
2626
  function checkViaTraceClearance(circuitJson, {
2595
2627
  connMap,
2596
2628
  minClearance
@@ -2602,28 +2634,21 @@ function checkViaTraceClearance(circuitJson, {
2602
2634
  minClearance ??= getBoardDrcValue(board, "min_trace_to_pad_edge_clearance") ?? jlcMinTolerances.min_trace_to_pad_edge_clearance;
2603
2635
  connMap ??= getFullConnectivityMapFromCircuitJson10(circuitJson);
2604
2636
  const errors = /* @__PURE__ */ new Map();
2637
+ const overlappingPairIds = /* @__PURE__ */ new Set();
2605
2638
  for (const via of vias) {
2606
- const viaRadius = via.outer_diameter / 2;
2607
2639
  for (const segment of segments) {
2608
2640
  if (!getLayersOfPcbElement(via).includes(segment.layer)) continue;
2609
2641
  if (connMap.areIdsConnected(segment.pcb_trace_id, via.pcb_via_id))
2610
2642
  continue;
2611
- const gap = segmentToCircleMinDistance4(
2612
- { x: segment.x1, y: segment.y1 },
2613
- { x: segment.x2, y: segment.y2 },
2614
- {
2615
- x: via.x,
2616
- y: via.y,
2617
- radius: viaRadius
2618
- }
2619
- ) - segment.thickness / 2;
2620
- if (gap + EPSILON >= minClearance) continue;
2621
- const closestPoint = getClosestPointOnSegment(
2622
- { x: via.x, y: via.y },
2623
- { x: segment.x1, y: segment.y1 },
2624
- { x: segment.x2, y: segment.y2 }
2625
- );
2626
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;
2627
2652
  const nextError = {
2628
2653
  type: "pcb_via_trace_clearance_error",
2629
2654
  pcb_via_trace_clearance_error_id: `via_trace_clearance_${pairId}`,
@@ -2633,10 +2658,7 @@ function checkViaTraceClearance(circuitJson, {
2633
2658
  pcb_trace_id: segment.pcb_trace_id,
2634
2659
  minimum_clearance: minClearance,
2635
2660
  actual_clearance: gap,
2636
- center: {
2637
- x: (via.x + closestPoint.x) / 2,
2638
- y: (via.y + closestPoint.y) / 2
2639
- }
2661
+ center: getTraceCenter(segment)
2640
2662
  };
2641
2663
  const current = errors.get(pairId);
2642
2664
  if (!current || gap < current.gap) {
@@ -3073,7 +3095,6 @@ async function runAllPlacementChecks(circuitJson) {
3073
3095
  ...checkPcbComponentsOutOfBoard(circuitJson),
3074
3096
  ...checkPcbComponentOverlap(circuitJson),
3075
3097
  ...checkPadPadClearance(circuitJson),
3076
- ...checkPadTraceClearance(circuitJson),
3077
3098
  ...checkCourtyardOverlap(circuitJson),
3078
3099
  ...checkConnectorAccessibleOrientation(circuitJson)
3079
3100
  ];
@@ -3089,24 +3110,25 @@ async function runAllPinSpecificationChecks(circuitJson) {
3089
3110
  ];
3090
3111
  }
3091
3112
  async function runAllRoutingChecks(circuitJson) {
3092
- return dedupePcbDrcErrors([
3113
+ return [
3093
3114
  ...checkEachPcbPortConnectedToPcbTraces(circuitJson),
3094
3115
  ...checkSourceTracesHavePcbTraces(circuitJson),
3095
3116
  ...checkEachPcbTraceNonOverlapping(circuitJson),
3117
+ ...checkPadTraceClearance(circuitJson),
3096
3118
  ...checkViaTraceClearance(circuitJson),
3097
3119
  ...checkSameNetViaSpacing(circuitJson),
3098
3120
  ...checkDifferentNetViaSpacing(circuitJson),
3099
3121
  ...checkTracesAreContiguous(circuitJson),
3100
3122
  ...checkPcbTracesOutOfBoard(circuitJson)
3101
- ]);
3123
+ ];
3102
3124
  }
3103
3125
  async function runAllChecks(circuitJson) {
3104
- return dedupePcbDrcErrors([
3126
+ return [
3105
3127
  ...await runAllPlacementChecks(circuitJson),
3106
3128
  ...await runAllNetlistChecks(circuitJson),
3107
3129
  ...await runAllPinSpecificationChecks(circuitJson),
3108
3130
  ...await runAllRoutingChecks(circuitJson)
3109
- ]);
3131
+ ];
3110
3132
  }
3111
3133
  export {
3112
3134
  NetManager,