@tscircuit/checks 0.0.161 → 0.0.163
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 +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +92 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ and output an array of arrays for any issues found.
|
|
|
28
28
|
| [`checkSourceTracesHavePcbTraces`](./lib/check-source-traces-have-pcb-traces.ts) | Returns `pcb_trace_error` when source traces are missing corresponding `pcb_trace` routes. |
|
|
29
29
|
| [`checkTracesAreContiguous`](./lib/check-traces-are-contiguous/check-traces-are-contiguous.ts) | Returns `pcb_trace_error` when trace endpoints are floating or do not connect as expected. |
|
|
30
30
|
| [`checkViasOffBoard`](./lib/check-pcb-components-out-of-board/checkViasOffBoard.ts) | Returns `pcb_placement_error` if any PCB via lies outside or crosses the board boundary. |
|
|
31
|
-
| [`checkCopperToBoardEdgeClearance`](./lib/check-copper-to-board-edge-clearance.ts) | Checks via, SMT-pad,
|
|
31
|
+
| [`checkCopperToBoardEdgeClearance`](./lib/check-copper-to-board-edge-clearance.ts) | Checks via, SMT-pad, plated-hole, and copper-pour geometry against the polygon board outline and required edge clearance. |
|
|
32
32
|
|
|
33
33
|
## Aggregate check runner functions
|
|
34
34
|
|
package/dist/index.d.ts
CHANGED
|
@@ -18,8 +18,9 @@ declare class NetManager {
|
|
|
18
18
|
declare function checkViasOffBoard(circuitJson: AnyCircuitElement[]): PcbPlacementError[];
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* Checks the finished copper geometry of every via, SMT pad,
|
|
22
|
-
* against the real board outline and its configured edge-clearance
|
|
21
|
+
* Checks the finished copper geometry of every via, SMT pad, plated hole, and
|
|
22
|
+
* copper pour against the real board outline and its configured edge-clearance
|
|
23
|
+
* rule.
|
|
23
24
|
*
|
|
24
25
|
* Component and footprint rotations are normally composed into Circuit JSON's
|
|
25
26
|
* absolute coordinates and `ccw_rotation` fields. Polygon-pad plated holes are
|
package/dist/index.js
CHANGED
|
@@ -200,6 +200,7 @@ function getDistanceBetweenPoints(pointA, pointB) {
|
|
|
200
200
|
return Math.sqrt((pointB.x - pointA.x) ** 2 + (pointB.y - pointA.y) ** 2);
|
|
201
201
|
}
|
|
202
202
|
var POINT_ON_SEGMENT_TOLERANCE_MM = 1e-9;
|
|
203
|
+
var POINT_IN_PAD_TOLERANCE_MM = 1e-9;
|
|
203
204
|
function isPointOnSegment(point, segment) {
|
|
204
205
|
const crossProduct = (point.y - segment.start.y) * (segment.end.x - segment.start.x) - (point.x - segment.start.x) * (segment.end.y - segment.start.y);
|
|
205
206
|
if (Math.abs(crossProduct) > POINT_ON_SEGMENT_TOLERANCE_MM) return false;
|
|
@@ -222,12 +223,12 @@ function isPointInPolygon(point, polygon) {
|
|
|
222
223
|
function isPointInPad(point, pad) {
|
|
223
224
|
if (pad.type === "pcb_smtpad") {
|
|
224
225
|
if (pad.shape === "circle") {
|
|
225
|
-
return getDistanceBetweenPoints(point, pad) <= pad.radius;
|
|
226
|
+
return getDistanceBetweenPoints(point, pad) <= pad.radius + POINT_IN_PAD_TOLERANCE_MM;
|
|
226
227
|
}
|
|
227
228
|
if (pad.shape === "rect") {
|
|
228
229
|
const halfWidth = pad.width / 2;
|
|
229
230
|
const halfHeight = pad.height / 2;
|
|
230
|
-
return Math.abs(point.x - pad.x) <= halfWidth && Math.abs(point.y - pad.y) <= halfHeight;
|
|
231
|
+
return Math.abs(point.x - pad.x) <= halfWidth + POINT_IN_PAD_TOLERANCE_MM && Math.abs(point.y - pad.y) <= halfHeight + POINT_IN_PAD_TOLERANCE_MM;
|
|
231
232
|
}
|
|
232
233
|
if (pad.shape === "rotated_rect") {
|
|
233
234
|
return isPointInPolygon(point, getPolygonPointsForPad(pad));
|
|
@@ -235,12 +236,12 @@ function isPointInPad(point, pad) {
|
|
|
235
236
|
if (pad.shape === "pill" || pad.shape === "rotated_pill") {
|
|
236
237
|
if (pad.shape === "rotated_pill") {
|
|
237
238
|
const pill2 = getPillCenterLineForPad(pad);
|
|
238
|
-
return pointToSegmentDistance(point, pill2.start, pill2.end) <= pill2.radius;
|
|
239
|
+
return pointToSegmentDistance(point, pill2.start, pill2.end) <= pill2.radius + POINT_IN_PAD_TOLERANCE_MM;
|
|
239
240
|
}
|
|
240
241
|
const halfWidth = pad.width / 2;
|
|
241
242
|
const halfHeight = pad.height / 2;
|
|
242
243
|
const radius = pad.radius;
|
|
243
|
-
if (Math.abs(point.x - pad.x) <= halfWidth - radius && Math.abs(point.y - pad.y) <= halfHeight) {
|
|
244
|
+
if (Math.abs(point.x - pad.x) <= halfWidth - radius + POINT_IN_PAD_TOLERANCE_MM && Math.abs(point.y - pad.y) <= halfHeight + POINT_IN_PAD_TOLERANCE_MM) {
|
|
244
245
|
return true;
|
|
245
246
|
}
|
|
246
247
|
const cornerX = Math.max(
|
|
@@ -251,7 +252,8 @@ function isPointInPad(point, pad) {
|
|
|
251
252
|
Math.abs(point.y - pad.y) - (halfHeight - radius),
|
|
252
253
|
0
|
|
253
254
|
);
|
|
254
|
-
|
|
255
|
+
const radiusWithTolerance = radius + POINT_IN_PAD_TOLERANCE_MM;
|
|
256
|
+
return cornerX * cornerX + cornerY * cornerY <= radiusWithTolerance * radiusWithTolerance;
|
|
255
257
|
}
|
|
256
258
|
if (pad.shape === "polygon") {
|
|
257
259
|
return isPointInPolygon(point, pad.points);
|
|
@@ -259,13 +261,13 @@ function isPointInPad(point, pad) {
|
|
|
259
261
|
}
|
|
260
262
|
if (pad.type === "pcb_plated_hole") {
|
|
261
263
|
if (pad.shape === "circle") {
|
|
262
|
-
return getDistanceBetweenPoints(point, pad) <= pad.outer_diameter / 2;
|
|
264
|
+
return getDistanceBetweenPoints(point, pad) <= pad.outer_diameter / 2 + POINT_IN_PAD_TOLERANCE_MM;
|
|
263
265
|
}
|
|
264
266
|
if ("rect_pad_width" in pad && "rect_pad_height" in pad) {
|
|
265
267
|
return isPointInPolygon(point, getPolygonPointsForPad(pad));
|
|
266
268
|
}
|
|
267
269
|
if (pad.shape === "oval" || pad.shape === "pill") {
|
|
268
|
-
return Math.abs(point.x - pad.x) <= pad.outer_width / 2 && Math.abs(point.y - pad.y) <= pad.outer_height / 2;
|
|
270
|
+
return Math.abs(point.x - pad.x) <= pad.outer_width / 2 + POINT_IN_PAD_TOLERANCE_MM && Math.abs(point.y - pad.y) <= pad.outer_height / 2 + POINT_IN_PAD_TOLERANCE_MM;
|
|
269
271
|
}
|
|
270
272
|
}
|
|
271
273
|
return false;
|
|
@@ -1536,6 +1538,57 @@ var pointsToPolygon = (points) => {
|
|
|
1536
1538
|
if (points.length < 3) return null;
|
|
1537
1539
|
return new Flatten.Polygon(points.map(({ x, y }) => new Flatten.Point(x, y)));
|
|
1538
1540
|
};
|
|
1541
|
+
var brepRingToPolygon = (vertices) => {
|
|
1542
|
+
const ring = vertices.filter((vertex, index) => {
|
|
1543
|
+
const previous = vertices[index - 1];
|
|
1544
|
+
return !previous || Math.abs(previous.x - vertex.x) > GEOMETRY_EPSILON || Math.abs(previous.y - vertex.y) > GEOMETRY_EPSILON;
|
|
1545
|
+
});
|
|
1546
|
+
if (ring.length > 1 && Math.abs(ring[0].x - ring.at(-1).x) <= GEOMETRY_EPSILON && Math.abs(ring[0].y - ring.at(-1).y) <= GEOMETRY_EPSILON) {
|
|
1547
|
+
ring.pop();
|
|
1548
|
+
}
|
|
1549
|
+
if (ring.length < 3) return null;
|
|
1550
|
+
const edges = [];
|
|
1551
|
+
for (let index = 0; index < ring.length; index++) {
|
|
1552
|
+
const start = ring[index];
|
|
1553
|
+
const end = ring[(index + 1) % ring.length];
|
|
1554
|
+
const startPoint = new Flatten.Point(start.x, start.y);
|
|
1555
|
+
const endPoint = new Flatten.Point(end.x, end.y);
|
|
1556
|
+
const bulge = start.bulge ?? 0;
|
|
1557
|
+
if (Math.abs(bulge) <= GEOMETRY_EPSILON) {
|
|
1558
|
+
edges.push(new Flatten.Segment(startPoint, endPoint));
|
|
1559
|
+
continue;
|
|
1560
|
+
}
|
|
1561
|
+
const chordLength = startPoint.distanceTo(endPoint)[0];
|
|
1562
|
+
if (chordLength <= GEOMETRY_EPSILON) continue;
|
|
1563
|
+
const midpoint2 = {
|
|
1564
|
+
x: (start.x + end.x) / 2,
|
|
1565
|
+
y: (start.y + end.y) / 2
|
|
1566
|
+
};
|
|
1567
|
+
const leftNormal = {
|
|
1568
|
+
x: -(end.y - start.y) / chordLength,
|
|
1569
|
+
y: (end.x - start.x) / chordLength
|
|
1570
|
+
};
|
|
1571
|
+
const centerOffset = chordLength * (1 - bulge * bulge) / (4 * bulge);
|
|
1572
|
+
const center = new Flatten.Point(
|
|
1573
|
+
midpoint2.x + leftNormal.x * centerOffset,
|
|
1574
|
+
midpoint2.y + leftNormal.y * centerOffset
|
|
1575
|
+
);
|
|
1576
|
+
const radius = chordLength * (1 + bulge * bulge) / (4 * Math.abs(bulge));
|
|
1577
|
+
edges.push(
|
|
1578
|
+
new Flatten.Arc(
|
|
1579
|
+
center,
|
|
1580
|
+
radius,
|
|
1581
|
+
Math.atan2(start.y - center.y, start.x - center.x),
|
|
1582
|
+
Math.atan2(end.y - center.y, end.x - center.x),
|
|
1583
|
+
bulge > 0
|
|
1584
|
+
)
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1587
|
+
if (edges.length < 3) return null;
|
|
1588
|
+
const polygon = new Flatten.Polygon();
|
|
1589
|
+
polygon.addFace(edges);
|
|
1590
|
+
return polygon;
|
|
1591
|
+
};
|
|
1539
1592
|
var boardToPolygon = (board) => {
|
|
1540
1593
|
if (board.outline && board.outline.length >= 3) {
|
|
1541
1594
|
return pointsToPolygon(board.outline);
|
|
@@ -1773,22 +1826,45 @@ var getCopperGeometry = (element, componentCcwRotationsById) => {
|
|
|
1773
1826
|
};
|
|
1774
1827
|
}
|
|
1775
1828
|
if (element.type === "pcb_smtpad") return getSmtPadGeometry(element);
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1829
|
+
if (element.type === "pcb_plated_hole") {
|
|
1830
|
+
return getPlatedHoleGeometry(
|
|
1831
|
+
element,
|
|
1832
|
+
element.pcb_component_id ? componentCcwRotationsById.get(
|
|
1833
|
+
toPcbComponentId(element.pcb_component_id)
|
|
1834
|
+
) ?? 0 : 0
|
|
1835
|
+
);
|
|
1836
|
+
}
|
|
1837
|
+
let polygon;
|
|
1838
|
+
switch (element.shape) {
|
|
1839
|
+
case "rect":
|
|
1840
|
+
polygon = getRectanglePolygon({
|
|
1841
|
+
x: element.center.x,
|
|
1842
|
+
y: element.center.y,
|
|
1843
|
+
width: element.width,
|
|
1844
|
+
height: element.height,
|
|
1845
|
+
ccwRotationDegrees: element.rotation ?? 0
|
|
1846
|
+
});
|
|
1847
|
+
break;
|
|
1848
|
+
case "polygon":
|
|
1849
|
+
polygon = pointsToPolygon(element.points);
|
|
1850
|
+
break;
|
|
1851
|
+
case "brep":
|
|
1852
|
+
polygon = brepRingToPolygon(element.brep_shape.outer_ring.vertices);
|
|
1853
|
+
break;
|
|
1854
|
+
}
|
|
1855
|
+
return polygon ? { kind: "shapes", shapes: [polygon] } : null;
|
|
1782
1856
|
};
|
|
1783
1857
|
var getCopperElementId = (element) => {
|
|
1784
1858
|
if (element.type === "pcb_via") return element.pcb_via_id;
|
|
1785
1859
|
if (element.type === "pcb_smtpad") return element.pcb_smtpad_id;
|
|
1786
|
-
return element.pcb_plated_hole_id;
|
|
1860
|
+
if (element.type === "pcb_plated_hole") return element.pcb_plated_hole_id;
|
|
1861
|
+
return element.pcb_copper_pour_id;
|
|
1787
1862
|
};
|
|
1788
1863
|
var getCopperElementLabel = (element) => {
|
|
1789
1864
|
if (element.type === "pcb_via") return "Via";
|
|
1790
1865
|
if (element.type === "pcb_smtpad") return "SMT pad";
|
|
1791
|
-
return "Plated hole";
|
|
1866
|
+
if (element.type === "pcb_plated_hole") return "Plated hole";
|
|
1867
|
+
return "Copper pour";
|
|
1792
1868
|
};
|
|
1793
1869
|
var measureClearance = (board, geometry) => {
|
|
1794
1870
|
if (geometry.kind === "shapes") {
|
|
@@ -1816,7 +1892,7 @@ function checkCopperToBoardEdgeClearance(circuitJson) {
|
|
|
1816
1892
|
const requiredClearance = getBoardDrcValue(board, "min_board_edge_clearance") ?? jlcMinTolerances.min_board_edge_clearance;
|
|
1817
1893
|
if (requiredClearance === void 0) return [];
|
|
1818
1894
|
const copperElements = circuitJson.filter(
|
|
1819
|
-
(element) => element.type === "pcb_via" || element.type === "pcb_smtpad" || element.type === "pcb_plated_hole"
|
|
1895
|
+
(element) => element.type === "pcb_via" || element.type === "pcb_smtpad" || element.type === "pcb_plated_hole" || element.type === "pcb_copper_pour"
|
|
1820
1896
|
);
|
|
1821
1897
|
const componentCcwRotationsById = new Map(
|
|
1822
1898
|
circuitJson.filter(
|