@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/README.md +5 -4
- package/dist/index.d.ts +6 -2
- package/dist/index.js +363 -341
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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 {
|
|
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 {
|
|
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
|
-
|
|
142
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
1011
|
-
const pcbPlatedHoles =
|
|
1012
|
-
const pcbHoles =
|
|
1013
|
-
const pcbVias =
|
|
1014
|
-
const pcbKeepouts =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
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(
|
|
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
|
|
1160
|
-
if (
|
|
1161
|
-
const
|
|
1162
|
-
const
|
|
1163
|
-
segmentA,
|
|
1164
|
-
|
|
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
|
|
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 =
|
|
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
|
|
2043
|
-
import { segmentToSegmentMinDistance as
|
|
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 =
|
|
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 =
|
|
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
|
|
2123
|
-
getBoundsOfPcbElements as
|
|
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
|
|
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 =
|
|
2147
|
-
const bounds2 =
|
|
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 =
|
|
2165
|
-
const platedHoles =
|
|
2166
|
-
const holes =
|
|
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 =
|
|
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
|
|
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
|
|
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,
|