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