@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/README.md +5 -4
- package/dist/index.d.ts +11 -5
- package/dist/index.js +427 -341
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
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,50 +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;
|
|
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(
|
|
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());
|
|
@@ -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
|
|
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
|
|
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,
|