@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 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, and plated-hole copper against the polygon board outline and required edge clearance. |
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, and plated hole
22
- * against the real board outline and its configured edge-clearance rule.
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
- return cornerX * cornerX + cornerY * cornerY <= radius * radius;
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
- return getPlatedHoleGeometry(
1777
- element,
1778
- element.pcb_component_id ? componentCcwRotationsById.get(
1779
- toPcbComponentId(element.pcb_component_id)
1780
- ) ?? 0 : 0
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(