@tscircuit/checks 0.0.144 → 0.0.146

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,50 +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;
1100
- const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${primaryObjId}`;
1101
- if (errorIds.has(pcb_trace_error_id)) continue;
1102
- errorIds.add(pcb_trace_error_id);
1103
- errors.push({
1104
- type: "pcb_trace_error",
1105
- error_type: "pcb_trace_error",
1106
- message: constructErrorMessage(
1107
- 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;
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;
1134
1350
  const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${primaryObjId}`;
1135
1351
  if (errorIds.has(pcb_trace_error_id)) continue;
1136
1352
  errorIds.add(pcb_trace_error_id);
@@ -1139,7 +1355,7 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1139
1355
  error_type: "pcb_trace_error",
1140
1356
  message: constructErrorMessage(
1141
1357
  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());
@@ -2272,6 +2491,68 @@ function checkPcbComponentOverlap(circuitJson) {
2272
2491
  return errors;
2273
2492
  }
2274
2493
 
2494
+ // lib/check-pcb-trace-lengths.ts
2495
+ var DEFAULT_VIA_LENGTH_MM = 1.6;
2496
+ var getRoutePointPosition = (routePoint) => routePoint.route_type === "through_pad" ? routePoint.start : { x: routePoint.x, y: routePoint.y };
2497
+ var getPcbTraceLength = (pcbTrace) => {
2498
+ if (pcbTrace.trace_length !== void 0) return pcbTrace.trace_length;
2499
+ let traceLength = 0;
2500
+ for (let routePointIndex = 0; routePointIndex < pcbTrace.route.length; routePointIndex++) {
2501
+ const routePoint = pcbTrace.route[routePointIndex];
2502
+ if (!routePoint) continue;
2503
+ if (routePoint.route_type === "via") {
2504
+ traceLength += DEFAULT_VIA_LENGTH_MM;
2505
+ continue;
2506
+ }
2507
+ const nextRoutePoint = pcbTrace.route[routePointIndex + 1];
2508
+ if (!nextRoutePoint) continue;
2509
+ const routePointPosition = getRoutePointPosition(routePoint);
2510
+ const nextRoutePointPosition = getRoutePointPosition(nextRoutePoint);
2511
+ traceLength += Math.hypot(
2512
+ nextRoutePointPosition.x - routePointPosition.x,
2513
+ nextRoutePointPosition.y - routePointPosition.y
2514
+ );
2515
+ }
2516
+ return traceLength;
2517
+ };
2518
+ var checkPcbTraceLengths = (circuitJson) => {
2519
+ const sourceTraces = circuitJson.filter(
2520
+ (element) => element.type === "source_trace"
2521
+ );
2522
+ const pcbTraces = circuitJson.filter(
2523
+ (element) => element.type === "pcb_trace"
2524
+ );
2525
+ const sourceTracesById = new Map(
2526
+ sourceTraces.map((sourceTrace) => [
2527
+ sourceTrace.source_trace_id,
2528
+ sourceTrace
2529
+ ])
2530
+ );
2531
+ const warnings = [];
2532
+ for (const pcbTrace of pcbTraces) {
2533
+ if (!pcbTrace.source_trace_id) continue;
2534
+ const sourceTrace = sourceTracesById.get(pcbTrace.source_trace_id);
2535
+ if (!sourceTrace) continue;
2536
+ const maximumTraceLength = sourceTrace.max_length;
2537
+ if (typeof maximumTraceLength !== "number") continue;
2538
+ const actualTraceLength = getPcbTraceLength(pcbTrace);
2539
+ if (actualTraceLength <= maximumTraceLength) continue;
2540
+ warnings.push({
2541
+ type: "pcb_trace_too_long_warning",
2542
+ pcb_trace_too_long_warning_id: `pcb_trace_too_long_warning_${pcbTrace.pcb_trace_id}`,
2543
+ warning_type: "pcb_trace_too_long_warning",
2544
+ message: `PCB trace is ${actualTraceLength.toFixed(2)}mm long, exceeding the ${maximumTraceLength}mm maximum`,
2545
+ pcb_trace_id: pcbTrace.pcb_trace_id,
2546
+ source_trace_id: sourceTrace.source_trace_id,
2547
+ source_net_id: sourceTrace.connected_source_net_ids[0],
2548
+ actual_trace_length: actualTraceLength,
2549
+ maximum_trace_length: maximumTraceLength,
2550
+ subcircuit_id: pcbTrace.subcircuit_id ?? sourceTrace.subcircuit_id
2551
+ });
2552
+ }
2553
+ return warnings;
2554
+ };
2555
+
2275
2556
  // lib/check-pad-pad-clearance.ts
2276
2557
  import {
2277
2558
  getPrimaryId as getPrimaryId3,
@@ -2280,160 +2561,6 @@ import {
2280
2561
  import {
2281
2562
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson8
2282
2563
  } 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
2564
  function checkPadPadClearance(circuitJson, {
2438
2565
  connMap,
2439
2566
  minClearance
@@ -2495,10 +2622,6 @@ import {
2495
2622
  getPrimaryId as getPrimaryId4,
2496
2623
  getReadableNameForElement as getReadableNameForElement7
2497
2624
  } from "@tscircuit/circuit-json-util";
2498
- import {
2499
- segmentToBoundsMinDistance as segmentToBoundsMinDistance2,
2500
- segmentToCircleMinDistance as segmentToCircleMinDistance3
2501
- } from "@tscircuit/math-utils";
2502
2625
  import {
2503
2626
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson9
2504
2627
  } from "circuit-json-to-connectivity-map";
@@ -2518,6 +2641,7 @@ function checkPadTraceClearance(circuitJson, {
2518
2641
  getId: (pad) => getPrimaryId4(pad)
2519
2642
  });
2520
2643
  const errors = /* @__PURE__ */ new Map();
2644
+ const overlappingPairIds = /* @__PURE__ */ new Set();
2521
2645
  for (const segment of segments) {
2522
2646
  const nearbyPads = spatialIndex.getObjectsInBounds(
2523
2647
  getCollidableBounds(segment),
@@ -2527,24 +2651,15 @@ function checkPadTraceClearance(circuitJson, {
2527
2651
  const padId = getPrimaryId4(pad);
2528
2652
  if (!getLayersOfPcbElement(pad).includes(segment.layer)) continue;
2529
2653
  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
2654
  const pairId = `${padId}_${segment.pcb_trace_id}`;
2655
+ const { gap } = getTraceObstacleClearance(segment, pad);
2656
+ if (isTraceObstacleOverlap(gap)) {
2657
+ errors.delete(pairId);
2658
+ overlappingPairIds.add(pairId);
2659
+ continue;
2660
+ }
2661
+ if (overlappingPairIds.has(pairId)) continue;
2662
+ if (gap + EPSILON >= minClearance) continue;
2548
2663
  const nextError = {
2549
2664
  type: "pcb_pad_trace_clearance_error",
2550
2665
  pcb_pad_trace_clearance_error_id: `pad_trace_clearance_${pairId}`,
@@ -2554,10 +2669,7 @@ function checkPadTraceClearance(circuitJson, {
2554
2669
  pcb_trace_id: segment.pcb_trace_id,
2555
2670
  minimum_clearance: minClearance,
2556
2671
  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
- }
2672
+ center: getTraceCenter(segment)
2561
2673
  };
2562
2674
  const current = errors.get(pairId);
2563
2675
  if (!current || gap < current.gap) {
@@ -2570,27 +2682,9 @@ function checkPadTraceClearance(circuitJson, {
2570
2682
 
2571
2683
  // lib/check-via-trace-clearance.ts
2572
2684
  import { getReadableNameForElement as getReadableNameForElement8 } from "@tscircuit/circuit-json-util";
2573
- import { segmentToCircleMinDistance as segmentToCircleMinDistance4 } from "@tscircuit/math-utils";
2574
2685
  import {
2575
2686
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson10
2576
2687
  } 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
2688
  function checkViaTraceClearance(circuitJson, {
2595
2689
  connMap,
2596
2690
  minClearance
@@ -2602,28 +2696,21 @@ function checkViaTraceClearance(circuitJson, {
2602
2696
  minClearance ??= getBoardDrcValue(board, "min_trace_to_pad_edge_clearance") ?? jlcMinTolerances.min_trace_to_pad_edge_clearance;
2603
2697
  connMap ??= getFullConnectivityMapFromCircuitJson10(circuitJson);
2604
2698
  const errors = /* @__PURE__ */ new Map();
2699
+ const overlappingPairIds = /* @__PURE__ */ new Set();
2605
2700
  for (const via of vias) {
2606
- const viaRadius = via.outer_diameter / 2;
2607
2701
  for (const segment of segments) {
2608
2702
  if (!getLayersOfPcbElement(via).includes(segment.layer)) continue;
2609
2703
  if (connMap.areIdsConnected(segment.pcb_trace_id, via.pcb_via_id))
2610
2704
  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
2705
  const pairId = `${via.pcb_via_id}_${segment.pcb_trace_id}`;
2706
+ const { gap } = getTraceObstacleClearance(segment, via);
2707
+ if (isTraceObstacleOverlap(gap)) {
2708
+ errors.delete(pairId);
2709
+ overlappingPairIds.add(pairId);
2710
+ continue;
2711
+ }
2712
+ if (overlappingPairIds.has(pairId)) continue;
2713
+ if (gap + EPSILON >= minClearance) continue;
2627
2714
  const nextError = {
2628
2715
  type: "pcb_via_trace_clearance_error",
2629
2716
  pcb_via_trace_clearance_error_id: `via_trace_clearance_${pairId}`,
@@ -2633,10 +2720,7 @@ function checkViaTraceClearance(circuitJson, {
2633
2720
  pcb_trace_id: segment.pcb_trace_id,
2634
2721
  minimum_clearance: minClearance,
2635
2722
  actual_clearance: gap,
2636
- center: {
2637
- x: (via.x + closestPoint.x) / 2,
2638
- y: (via.y + closestPoint.y) / 2
2639
- }
2723
+ center: getTraceCenter(segment)
2640
2724
  };
2641
2725
  const current = errors.get(pairId);
2642
2726
  if (!current || gap < current.gap) {
@@ -3073,7 +3157,6 @@ async function runAllPlacementChecks(circuitJson) {
3073
3157
  ...checkPcbComponentsOutOfBoard(circuitJson),
3074
3158
  ...checkPcbComponentOverlap(circuitJson),
3075
3159
  ...checkPadPadClearance(circuitJson),
3076
- ...checkPadTraceClearance(circuitJson),
3077
3160
  ...checkCourtyardOverlap(circuitJson),
3078
3161
  ...checkConnectorAccessibleOrientation(circuitJson)
3079
3162
  ];
@@ -3089,24 +3172,26 @@ async function runAllPinSpecificationChecks(circuitJson) {
3089
3172
  ];
3090
3173
  }
3091
3174
  async function runAllRoutingChecks(circuitJson) {
3092
- return dedupePcbDrcErrors([
3175
+ return [
3093
3176
  ...checkEachPcbPortConnectedToPcbTraces(circuitJson),
3094
3177
  ...checkSourceTracesHavePcbTraces(circuitJson),
3178
+ ...checkPcbTraceLengths(circuitJson),
3095
3179
  ...checkEachPcbTraceNonOverlapping(circuitJson),
3180
+ ...checkPadTraceClearance(circuitJson),
3096
3181
  ...checkViaTraceClearance(circuitJson),
3097
3182
  ...checkSameNetViaSpacing(circuitJson),
3098
3183
  ...checkDifferentNetViaSpacing(circuitJson),
3099
3184
  ...checkTracesAreContiguous(circuitJson),
3100
3185
  ...checkPcbTracesOutOfBoard(circuitJson)
3101
- ]);
3186
+ ];
3102
3187
  }
3103
3188
  async function runAllChecks(circuitJson) {
3104
- return dedupePcbDrcErrors([
3189
+ return [
3105
3190
  ...await runAllPlacementChecks(circuitJson),
3106
3191
  ...await runAllNetlistChecks(circuitJson),
3107
3192
  ...await runAllPinSpecificationChecks(circuitJson),
3108
3193
  ...await runAllRoutingChecks(circuitJson)
3109
- ]);
3194
+ ];
3110
3195
  }
3111
3196
  export {
3112
3197
  NetManager,
@@ -3121,6 +3206,7 @@ export {
3121
3206
  checkPadTraceClearance,
3122
3207
  checkPcbComponentOverlap,
3123
3208
  checkPcbComponentsOutOfBoard,
3209
+ checkPcbTraceLengths,
3124
3210
  checkPcbTracesOutOfBoard,
3125
3211
  checkPinMustBeConnected,
3126
3212
  checkSameNetViaSpacing,