@tscircuit/checks 0.0.117 → 0.0.119

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
@@ -19,6 +19,7 @@ and output an array of arrays for any issues found.
19
19
  | [`checkPcbComponentOverlap`](./lib/check-pcb-components-overlap/checkPcbComponentOverlap.ts) | Returns `pcb_footprint_overlap_error` when footprint elements from different components overlap in disallowed ways. |
20
20
  | [`checkPcbComponentsOutOfBoard`](./lib/check-pcb-components-out-of-board/checkPcbComponentsOutOfBoard.ts) | Returns `pcb_placement_error` when PCB components do not fit inside the board area. |
21
21
  | [`checkPcbTracesOutOfBoard`](./lib/check-trace-out-of-board/checkTraceOutOfBoard.ts) | Returns `pcb_trace_error` when any trace segment or via extends beyond the board boundary. |
22
+ | [`checkViaTraceClearance`](./lib/check-via-trace-clearance.ts) | Returns `pcb_via_trace_clearance_error` when a via and unrelated trace segment violate minimum clearance. |
22
23
  | [`checkPinMustBeConnected`](./lib/check-pin-must-be-connected.ts) | Returns `pcb_trace_error` when required source pins are not connected. |
23
24
  | [`checkSameNetViaSpacing`](./lib/check-same-net-via-spacing.ts) | Returns `pcb_via_clearance_error` if vias on the same net are closer than the allowed margin. |
24
25
  | [`checkSourceTracesHavePcbTraces`](./lib/check-source-traces-have-pcb-traces.ts) | Returns `pcb_trace_error` when source traces are missing corresponding `pcb_trace` routes. |
@@ -32,7 +33,7 @@ and output an array of arrays for any issues found.
32
33
  | [`runAllPlacementChecks`](./lib/run-all-checks.ts) | Runs all placement checks (`checkViasOffBoard`, `checkPcbComponentsOutOfBoard`, `checkPcbComponentOverlap`, and `checkConnectorAccessibleOrientation`). |
33
34
  | [`runAllNetlistChecks`](./lib/run-all-checks.ts) | Runs netlist connectivity checks (currently `checkPinMustBeConnected`). |
34
35
  | [`runAllPinSpecificationChecks`](./lib/run-all-checks.ts) | Runs pin specification checks (e.g. `checkAllPinsInComponentAreUnderspecified`, `checkNoPowerPinDefined`, and `checkNoGroundPinDefined`). |
35
- | [`runAllRoutingChecks`](./lib/run-all-checks.ts) | Runs all routing checks currently enabled (`checkEachPcbPortConnectedToPcbTraces`, `checkSourceTracesHavePcbTraces`, `checkEachPcbTraceNonOverlapping`, same/different net via spacing, and `checkPcbTracesOutOfBoard`). |
36
+ | [`runAllRoutingChecks`](./lib/run-all-checks.ts) | Runs all routing checks currently enabled (`checkEachPcbPortConnectedToPcbTraces`, `checkSourceTracesHavePcbTraces`, `checkEachPcbTraceNonOverlapping`, `checkViaTraceClearance`, same/different net via spacing, and `checkPcbTracesOutOfBoard`). |
36
37
  | [`runAllChecks`](./lib/run-all-checks.ts) | Runs placement, netlist, pin specification, and routing checks and returns a combined list of issues. |
37
38
 
38
39
  ## Implementation Details
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as circuit_json from 'circuit-json';
2
- import { AnyCircuitElement, PcbPortNotConnectedError, PcbTraceError, PcbPlacementError, PcbComponentOutsideBoardError, PcbViaClearanceError, PcbTraceWarning, PcbTraceMissingError, PcbFootprintOverlapError, SourceComponentPinsUnderspecifiedWarning, SourceNoPowerPinDefinedWarning, SourceNoGroundPinDefinedWarning, PcbConnectorNotInAccessibleOrientationWarning } from 'circuit-json';
2
+ import { AnyCircuitElement, PcbPortNotConnectedError, PcbTraceError, PcbPlacementError, PcbComponentOutsideBoardError, PcbViaClearanceError, PcbTraceWarning, PcbTraceMissingError, PcbFootprintOverlapError, PcbPadPadClearanceError, PcbPadTraceClearanceError, PcbViaTraceClearanceError, SourceComponentPinsUnderspecifiedWarning, SourceNoPowerPinDefinedWarning, SourceNoGroundPinDefinedWarning, PcbConnectorNotInAccessibleOrientationWarning } from 'circuit-json';
3
3
  import { ConnectivityMap } from 'circuit-json-to-connectivity-map';
4
4
 
5
5
  declare function checkEachPcbPortConnectedToPcbTraces(circuitJson: AnyCircuitElement[]): PcbPortNotConnectedError[];
@@ -61,6 +61,21 @@ declare function checkPcbTracesOutOfBoard(circuitJson: AnyCircuitElement[], conf
61
61
  */
62
62
  declare function checkPcbComponentOverlap(circuitJson: AnyCircuitElement[]): PcbFootprintOverlapError[];
63
63
 
64
+ declare function checkPadPadClearance(circuitJson: AnyCircuitElement[], { connMap, minSpacing, }?: {
65
+ connMap?: ConnectivityMap;
66
+ minSpacing?: number;
67
+ }): PcbPadPadClearanceError[];
68
+
69
+ declare function checkPadTraceClearance(circuitJson: AnyCircuitElement[], { connMap, minSpacing, }?: {
70
+ connMap?: ConnectivityMap;
71
+ minSpacing?: number;
72
+ }): PcbPadTraceClearanceError[];
73
+
74
+ declare function checkViaTraceClearance(circuitJson: AnyCircuitElement[], { connMap, minSpacing, }?: {
75
+ connMap?: ConnectivityMap;
76
+ minSpacing?: number;
77
+ }): PcbViaTraceClearanceError[];
78
+
64
79
  type SourcePinMustBeConnectedError = {
65
80
  type: "source_pin_must_be_connected_error";
66
81
  source_pin_must_be_connected_error_id: string;
@@ -96,7 +111,7 @@ declare function checkNoPowerPinDefined(circuitJson: AnyCircuitElement[]): Sourc
96
111
  */
97
112
  declare function checkNoGroundPinDefined(circuitJson: AnyCircuitElement[]): SourceNoGroundPinDefinedWarning[];
98
113
 
99
- declare function runAllPlacementChecks(circuitJson: AnyCircuitElement[]): Promise<(circuit_json.PcbPlacementError | circuit_json.PcbComponentOutsideBoardError | circuit_json.PcbFootprintOverlapError | circuit_json.PcbCourtyardOverlapError | circuit_json.PcbConnectorNotInAccessibleOrientationWarning)[]>;
114
+ declare function runAllPlacementChecks(circuitJson: AnyCircuitElement[]): Promise<(circuit_json.PcbPlacementError | circuit_json.PcbComponentOutsideBoardError | circuit_json.PcbFootprintOverlapError | circuit_json.PcbPadPadClearanceError | circuit_json.PcbPadTraceClearanceError | circuit_json.PcbConnectorNotInAccessibleOrientationWarning | circuit_json.PcbCourtyardOverlapError)[]>;
100
115
  declare function runAllNetlistChecks(circuitJson: AnyCircuitElement[]): Promise<{
101
116
  type: "source_pin_must_be_connected_error";
102
117
  source_pin_must_be_connected_error_id: string;
@@ -107,8 +122,8 @@ declare function runAllNetlistChecks(circuitJson: AnyCircuitElement[]): Promise<
107
122
  subcircuit_id?: string;
108
123
  }[]>;
109
124
  declare function runAllPinSpecificationChecks(circuitJson: AnyCircuitElement[]): Promise<(circuit_json.SourceComponentPinsUnderspecifiedWarning | circuit_json.SourceNoPowerPinDefinedWarning | circuit_json.SourceNoGroundPinDefinedWarning)[]>;
110
- declare function runAllRoutingChecks(circuitJson: AnyCircuitElement[]): Promise<(circuit_json.PcbPortNotConnectedError | circuit_json.PcbTraceError | circuit_json.PcbViaClearanceError | circuit_json.PcbTraceMissingError)[]>;
111
- declare function runAllChecks(circuitJson: AnyCircuitElement[]): Promise<(circuit_json.PcbPortNotConnectedError | circuit_json.PcbTraceError | circuit_json.PcbPlacementError | circuit_json.PcbComponentOutsideBoardError | circuit_json.PcbViaClearanceError | circuit_json.PcbTraceMissingError | circuit_json.PcbFootprintOverlapError | {
125
+ declare function runAllRoutingChecks(circuitJson: AnyCircuitElement[]): Promise<(circuit_json.PcbPortNotConnectedError | circuit_json.PcbTraceError | circuit_json.PcbViaClearanceError | circuit_json.PcbTraceMissingError | circuit_json.PcbViaTraceClearanceError)[]>;
126
+ declare function runAllChecks(circuitJson: AnyCircuitElement[]): Promise<(circuit_json.PcbPortNotConnectedError | circuit_json.PcbTraceError | circuit_json.PcbPlacementError | circuit_json.PcbComponentOutsideBoardError | circuit_json.PcbViaClearanceError | circuit_json.PcbTraceMissingError | circuit_json.PcbFootprintOverlapError | circuit_json.PcbPadPadClearanceError | circuit_json.PcbPadTraceClearanceError | circuit_json.PcbViaTraceClearanceError | {
112
127
  type: "source_pin_must_be_connected_error";
113
128
  source_pin_must_be_connected_error_id: string;
114
129
  error_type: "source_pin_must_be_connected_error";
@@ -116,8 +131,8 @@ declare function runAllChecks(circuitJson: AnyCircuitElement[]): Promise<(circui
116
131
  source_component_id: string;
117
132
  source_port_id: string;
118
133
  subcircuit_id?: string;
119
- } | circuit_json.SourceComponentPinsUnderspecifiedWarning | circuit_json.SourceNoPowerPinDefinedWarning | circuit_json.SourceNoGroundPinDefinedWarning | circuit_json.PcbCourtyardOverlapError | circuit_json.PcbConnectorNotInAccessibleOrientationWarning)[]>;
134
+ } | circuit_json.SourceComponentPinsUnderspecifiedWarning | circuit_json.SourceNoPowerPinDefinedWarning | circuit_json.SourceNoGroundPinDefinedWarning | circuit_json.PcbConnectorNotInAccessibleOrientationWarning | circuit_json.PcbCourtyardOverlapError)[]>;
120
135
 
121
136
  declare function checkConnectorAccessibleOrientation(circuitJson: AnyCircuitElement[]): PcbConnectorNotInAccessibleOrientationWarning[];
122
137
 
123
- export { NetManager, checkAllPinsInComponentAreUnderspecified, checkConnectorAccessibleOrientation, checkDifferentNetViaSpacing, checkEachPcbPortConnectedToPcbTraces, checkEachPcbTraceNonOverlapping, checkNoGroundPinDefined, checkNoPowerPinDefined, checkPcbComponentOverlap, checkPcbComponentsOutOfBoard, checkPcbTracesOutOfBoard, checkPinMustBeConnected, checkSameNetViaSpacing, checkSourceTracesHavePcbTraces, checkSourceTracesMatchPcbTraceThickness, checkViasOffBoard, runAllChecks, runAllNetlistChecks, runAllPinSpecificationChecks, runAllPlacementChecks, runAllRoutingChecks };
138
+ export { NetManager, checkAllPinsInComponentAreUnderspecified, checkConnectorAccessibleOrientation, checkDifferentNetViaSpacing, checkEachPcbPortConnectedToPcbTraces, checkEachPcbTraceNonOverlapping, checkNoGroundPinDefined, checkNoPowerPinDefined, checkPadPadClearance, checkPadTraceClearance, checkPcbComponentOverlap, checkPcbComponentsOutOfBoard, checkPcbTracesOutOfBoard, checkPinMustBeConnected, checkSameNetViaSpacing, checkSourceTracesHavePcbTraces, checkSourceTracesMatchPcbTraceThickness, checkViaTraceClearance, checkViasOffBoard, runAllChecks, runAllNetlistChecks, runAllPinSpecificationChecks, runAllPlacementChecks, runAllRoutingChecks };
package/dist/index.js CHANGED
@@ -369,6 +369,8 @@ var DEFAULT_TRACE_THICKNESS = 0.15;
369
369
  var DEFAULT_VIA_BOARD_MARGIN = 0.3;
370
370
  var DEFAULT_SAME_NET_VIA_MARGIN = 0.2;
371
371
  var DEFAULT_DIFFERENT_NET_VIA_MARGIN = 0.3;
372
+ var DEFAULT_PAD_PAD_CLEARANCE = 0.1;
373
+ var DEFAULT_PAD_TRACE_CLEARANCE = 0.1;
372
374
  var EPSILON = 5e-3;
373
375
 
374
376
  // lib/check-each-pcb-trace-non-overlapping/getPcbPortIdsConnectedToTraces.ts
@@ -1601,6 +1603,318 @@ function checkPcbComponentOverlap(circuitJson) {
1601
1603
  return errors;
1602
1604
  }
1603
1605
 
1606
+ // lib/check-pad-pad-clearance.ts
1607
+ import {
1608
+ getPrimaryId as getPrimaryId3,
1609
+ getReadableNameForElement as getReadableNameForElement6
1610
+ } from "@tscircuit/circuit-json-util";
1611
+ import {
1612
+ getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson7
1613
+ } from "circuit-json-to-connectivity-map";
1614
+
1615
+ // lib/check-pad-clearance/common.ts
1616
+ import {
1617
+ cju as cju5,
1618
+ distanceBetweenCircleAndCircle,
1619
+ distanceBetweenCircleAndPolygon,
1620
+ distanceBetweenPolygonAndPolygon,
1621
+ getBoundsOfPcbElements as getBoundsOfPcbElements5
1622
+ } from "@tscircuit/circuit-json-util";
1623
+ import { midpoint } from "@tscircuit/math-utils";
1624
+ var formatMm = (value) => {
1625
+ const rounded = Math.round(value * 1e3) / 1e3;
1626
+ return `${Number(rounded.toFixed(3))}mm`;
1627
+ };
1628
+ var getPadBounds = (pad) => getBoundsOfPcbElements5([pad]);
1629
+ var getPadCenter = (pad) => {
1630
+ const bounds = getPadBounds(pad);
1631
+ return midpoint(
1632
+ { x: bounds.minX, y: bounds.minY },
1633
+ { x: bounds.maxX, y: bounds.maxY }
1634
+ );
1635
+ };
1636
+ var getPadRadius = (pad) => {
1637
+ const bounds = getPadBounds(pad);
1638
+ return Math.min(bounds.maxX - bounds.minX, bounds.maxY - bounds.minY) / 2;
1639
+ };
1640
+ var isCircularPad = (pad) => pad.shape === "circle";
1641
+ var getCircleShape = (pad) => {
1642
+ const center = getPadCenter(pad);
1643
+ return {
1644
+ kind: "circle",
1645
+ x: center.x,
1646
+ y: center.y,
1647
+ radius: getPadRadius(pad)
1648
+ };
1649
+ };
1650
+ var getPolygonShape = (pad) => {
1651
+ const bounds = getPadBounds(pad);
1652
+ return {
1653
+ kind: "polygon",
1654
+ points: [
1655
+ { x: bounds.minX, y: bounds.minY },
1656
+ { x: bounds.maxX, y: bounds.minY },
1657
+ { x: bounds.maxX, y: bounds.maxY },
1658
+ { x: bounds.minX, y: bounds.maxY }
1659
+ ]
1660
+ };
1661
+ };
1662
+ var getPadToPadGap = (padA, padB) => {
1663
+ if (isCircularPad(padA) && isCircularPad(padB)) {
1664
+ return distanceBetweenCircleAndCircle(
1665
+ getCircleShape(padA),
1666
+ getCircleShape(padB)
1667
+ );
1668
+ }
1669
+ if (isCircularPad(padA)) {
1670
+ return distanceBetweenCircleAndPolygon(
1671
+ getCircleShape(padA),
1672
+ getPolygonShape(padB)
1673
+ );
1674
+ }
1675
+ if (isCircularPad(padB)) {
1676
+ return distanceBetweenCircleAndPolygon(
1677
+ getCircleShape(padB),
1678
+ getPolygonShape(padA)
1679
+ );
1680
+ }
1681
+ return distanceBetweenPolygonAndPolygon(
1682
+ getPolygonShape(padA),
1683
+ getPolygonShape(padB)
1684
+ );
1685
+ };
1686
+ var getPads = (circuitJson) => [
1687
+ ...cju5(circuitJson).pcb_smtpad.list(),
1688
+ ...cju5(circuitJson).pcb_plated_hole.list()
1689
+ ];
1690
+ var getTraceSegments = (circuitJson) => {
1691
+ const pcbTraces = cju5(circuitJson).pcb_trace.list();
1692
+ return pcbTraces.flatMap((pcbTrace) => {
1693
+ const segments = [];
1694
+ for (let i = 0; i < pcbTrace.route.length - 1; i++) {
1695
+ const p1 = pcbTrace.route[i];
1696
+ const p2 = pcbTrace.route[i + 1];
1697
+ if (p1.route_type !== "wire") continue;
1698
+ if (p2.route_type !== "wire") continue;
1699
+ if (p1.layer !== p2.layer) continue;
1700
+ segments.push({
1701
+ type: "pcb_trace_segment",
1702
+ pcb_trace_id: pcbTrace.pcb_trace_id,
1703
+ _pcbTrace: pcbTrace,
1704
+ thickness: "width" in p1 ? p1.width : "width" in p2 ? p2.width : DEFAULT_TRACE_THICKNESS,
1705
+ layer: p1.layer,
1706
+ x1: p1.x,
1707
+ y1: p1.y,
1708
+ x2: p2.x,
1709
+ y2: p2.y
1710
+ });
1711
+ }
1712
+ return segments;
1713
+ });
1714
+ };
1715
+
1716
+ // lib/check-pad-pad-clearance.ts
1717
+ function checkPadPadClearance(circuitJson, {
1718
+ connMap,
1719
+ minSpacing = DEFAULT_PAD_PAD_CLEARANCE
1720
+ } = {}) {
1721
+ const pads = getPads(circuitJson);
1722
+ if (pads.length < 2) return [];
1723
+ connMap ??= getFullConnectivityMapFromCircuitJson7(circuitJson);
1724
+ const spatialIndex = new SpatialObjectIndex({
1725
+ objects: pads,
1726
+ getBounds: getPadBounds,
1727
+ getId: (pad) => getPrimaryId3(pad)
1728
+ });
1729
+ const errors = /* @__PURE__ */ new Map();
1730
+ for (const padA of pads) {
1731
+ const padAId = getPrimaryId3(padA);
1732
+ const nearbyPads = spatialIndex.getObjectsInBounds(
1733
+ getPadBounds(padA),
1734
+ minSpacing
1735
+ );
1736
+ for (const padB of nearbyPads) {
1737
+ const padBId = getPrimaryId3(padB);
1738
+ if (padAId === padBId) continue;
1739
+ if (!getLayersOfPcbElement(padA).some(
1740
+ (layer) => getLayersOfPcbElement(padB).includes(layer)
1741
+ )) {
1742
+ continue;
1743
+ }
1744
+ if (connMap.areIdsConnected(padAId, padBId)) continue;
1745
+ const pairId = [padAId, padBId].sort().join("_");
1746
+ const gap = getPadToPadGap(padA, padB);
1747
+ if (gap + EPSILON >= minSpacing) continue;
1748
+ const centerA = getPadCenter(padA);
1749
+ const centerB = getPadCenter(padB);
1750
+ const nextError = {
1751
+ type: "pcb_pad_pad_clearance_error",
1752
+ pcb_pad_pad_clearance_error_id: `pad_pad_clearance_${pairId}`,
1753
+ error_type: "pcb_pad_pad_clearance_error",
1754
+ message: `Pads ${getReadableNameForElement6(circuitJson, padAId)} and ${getReadableNameForElement6(circuitJson, padBId)} are too close (clearance: ${formatMm(gap)}, minimum: ${formatMm(minSpacing)})`,
1755
+ pcb_pad_ids: [padAId, padBId],
1756
+ minimum_clearance: minSpacing,
1757
+ actual_clearance: gap,
1758
+ center: {
1759
+ x: (centerA.x + centerB.x) / 2,
1760
+ y: (centerA.y + centerB.y) / 2
1761
+ }
1762
+ };
1763
+ if (!errors.has(pairId)) {
1764
+ errors.set(pairId, nextError);
1765
+ }
1766
+ }
1767
+ }
1768
+ return Array.from(errors.values());
1769
+ }
1770
+
1771
+ // lib/check-pad-trace-clearance.ts
1772
+ import {
1773
+ getPrimaryId as getPrimaryId4,
1774
+ getReadableNameForElement as getReadableNameForElement7
1775
+ } from "@tscircuit/circuit-json-util";
1776
+ import {
1777
+ segmentToBoundsMinDistance as segmentToBoundsMinDistance2,
1778
+ segmentToCircleMinDistance as segmentToCircleMinDistance2
1779
+ } from "@tscircuit/math-utils";
1780
+ import {
1781
+ getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson8
1782
+ } from "circuit-json-to-connectivity-map";
1783
+ function checkPadTraceClearance(circuitJson, {
1784
+ connMap,
1785
+ minSpacing = DEFAULT_PAD_TRACE_CLEARANCE
1786
+ } = {}) {
1787
+ const pads = getPads(circuitJson);
1788
+ const segments = getTraceSegments(circuitJson);
1789
+ if (pads.length === 0 || segments.length === 0) return [];
1790
+ connMap ??= getFullConnectivityMapFromCircuitJson8(circuitJson);
1791
+ const spatialIndex = new SpatialObjectIndex({
1792
+ objects: pads,
1793
+ getBounds: getPadBounds,
1794
+ getId: (pad) => getPrimaryId4(pad)
1795
+ });
1796
+ const errors = /* @__PURE__ */ new Map();
1797
+ for (const segment of segments) {
1798
+ const nearbyPads = spatialIndex.getObjectsInBounds(
1799
+ getCollidableBounds(segment),
1800
+ minSpacing + segment.thickness / 2
1801
+ );
1802
+ for (const pad of nearbyPads) {
1803
+ const padId = getPrimaryId4(pad);
1804
+ if (!getLayersOfPcbElement(pad).includes(segment.layer)) continue;
1805
+ if (connMap.areIdsConnected(segment.pcb_trace_id, padId)) continue;
1806
+ const center = getPadCenter(pad);
1807
+ const gap = isCircularPad(pad) ? segmentToCircleMinDistance2(
1808
+ { x: segment.x1, y: segment.y1 },
1809
+ { x: segment.x2, y: segment.y2 },
1810
+ { x: center.x, y: center.y, radius: getPadRadius(pad) }
1811
+ ) - segment.thickness / 2 : segmentToBoundsMinDistance2(
1812
+ { x: segment.x1, y: segment.y1 },
1813
+ { x: segment.x2, y: segment.y2 },
1814
+ getPadBounds(pad)
1815
+ ) - segment.thickness / 2;
1816
+ if (gap + EPSILON >= minSpacing) continue;
1817
+ const pairId = `${padId}_${segment.pcb_trace_id}`;
1818
+ const nextError = {
1819
+ type: "pcb_pad_trace_clearance_error",
1820
+ pcb_pad_trace_clearance_error_id: `pad_trace_clearance_${pairId}`,
1821
+ error_type: "pcb_pad_trace_clearance_error",
1822
+ message: `Pad ${getReadableNameForElement7(circuitJson, padId)} and trace ${getReadableNameForElement7(circuitJson, segment.pcb_trace_id)} are too close (clearance: ${formatMm(gap)}, minimum: ${formatMm(minSpacing)})`,
1823
+ pcb_pad_id: padId,
1824
+ pcb_trace_id: segment.pcb_trace_id,
1825
+ minimum_clearance: minSpacing,
1826
+ actual_clearance: gap,
1827
+ center: {
1828
+ x: (center.x + (segment.x1 + segment.x2) / 2) / 2,
1829
+ y: (center.y + (segment.y1 + segment.y2) / 2) / 2
1830
+ }
1831
+ };
1832
+ const current = errors.get(pairId);
1833
+ if (!current || gap < current.gap) {
1834
+ errors.set(pairId, { error: nextError, gap });
1835
+ }
1836
+ }
1837
+ }
1838
+ return Array.from(errors.values()).map(({ error }) => error);
1839
+ }
1840
+
1841
+ // lib/check-via-trace-clearance.ts
1842
+ import { getReadableNameForElement as getReadableNameForElement8 } from "@tscircuit/circuit-json-util";
1843
+ import { segmentToCircleMinDistance as segmentToCircleMinDistance3 } from "@tscircuit/math-utils";
1844
+ import {
1845
+ getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson9
1846
+ } from "circuit-json-to-connectivity-map";
1847
+ var getClosestPointOnSegment = (point, segmentStart, segmentEnd) => {
1848
+ const dx = segmentEnd.x - segmentStart.x;
1849
+ const dy = segmentEnd.y - segmentStart.y;
1850
+ const lengthSquared = dx * dx + dy * dy;
1851
+ if (lengthSquared === 0) return segmentStart;
1852
+ const t = Math.max(
1853
+ 0,
1854
+ Math.min(
1855
+ 1,
1856
+ ((point.x - segmentStart.x) * dx + (point.y - segmentStart.y) * dy) / lengthSquared
1857
+ )
1858
+ );
1859
+ return {
1860
+ x: segmentStart.x + t * dx,
1861
+ y: segmentStart.y + t * dy
1862
+ };
1863
+ };
1864
+ function checkViaTraceClearance(circuitJson, {
1865
+ connMap,
1866
+ minSpacing = DEFAULT_PAD_TRACE_CLEARANCE
1867
+ } = {}) {
1868
+ const vias = circuitJson.filter((el) => el.type === "pcb_via");
1869
+ const segments = getTraceSegments(circuitJson);
1870
+ if (vias.length === 0 || segments.length === 0) return [];
1871
+ connMap ??= getFullConnectivityMapFromCircuitJson9(circuitJson);
1872
+ const errors = /* @__PURE__ */ new Map();
1873
+ for (const via of vias) {
1874
+ const viaRadius = via.outer_diameter / 2;
1875
+ for (const segment of segments) {
1876
+ if (!getLayersOfPcbElement(via).includes(segment.layer)) continue;
1877
+ if (connMap.areIdsConnected(segment.pcb_trace_id, via.pcb_via_id))
1878
+ continue;
1879
+ const gap = segmentToCircleMinDistance3(
1880
+ { x: segment.x1, y: segment.y1 },
1881
+ { x: segment.x2, y: segment.y2 },
1882
+ {
1883
+ x: via.x,
1884
+ y: via.y,
1885
+ radius: viaRadius
1886
+ }
1887
+ ) - segment.thickness / 2;
1888
+ if (gap + EPSILON >= minSpacing) continue;
1889
+ const closestPoint = getClosestPointOnSegment(
1890
+ { x: via.x, y: via.y },
1891
+ { x: segment.x1, y: segment.y1 },
1892
+ { x: segment.x2, y: segment.y2 }
1893
+ );
1894
+ const pairId = `${via.pcb_via_id}_${segment.pcb_trace_id}`;
1895
+ const nextError = {
1896
+ type: "pcb_via_trace_clearance_error",
1897
+ pcb_via_trace_clearance_error_id: `via_trace_clearance_${pairId}`,
1898
+ error_type: "pcb_via_trace_clearance_error",
1899
+ message: `Via ${getReadableNameForElement8(circuitJson, via.pcb_via_id)} and trace ${getReadableNameForElement8(circuitJson, segment.pcb_trace_id)} are too close (clearance: ${formatMm(gap)}, minimum: ${formatMm(minSpacing)})`,
1900
+ pcb_via_id: via.pcb_via_id,
1901
+ pcb_trace_id: segment.pcb_trace_id,
1902
+ minimum_clearance: minSpacing,
1903
+ actual_clearance: gap,
1904
+ center: {
1905
+ x: (via.x + closestPoint.x) / 2,
1906
+ y: (via.y + closestPoint.y) / 2
1907
+ }
1908
+ };
1909
+ const current = errors.get(pairId);
1910
+ if (!current || gap < current.gap) {
1911
+ errors.set(pairId, { error: nextError, gap });
1912
+ }
1913
+ }
1914
+ }
1915
+ return Array.from(errors.values()).map(({ error }) => error);
1916
+ }
1917
+
1604
1918
  // lib/check-pin-must-be-connected.ts
1605
1919
  function checkPinMustBeConnected(circuitJson) {
1606
1920
  const errors = [];
@@ -1660,7 +1974,7 @@ function checkPinMustBeConnected(circuitJson) {
1660
1974
  }
1661
1975
 
1662
1976
  // lib/check-all-pins-in-component-are-underspecified.ts
1663
- import { cju as cju5 } from "@tscircuit/circuit-json-util";
1977
+ import { cju as cju6 } from "@tscircuit/circuit-json-util";
1664
1978
  var PIN_ATTRIBUTE_KEYS = [
1665
1979
  "must_be_connected",
1666
1980
  "provides_power",
@@ -1705,7 +2019,7 @@ function hasAnyPinAttribute(port) {
1705
2019
  }
1706
2020
  function checkAllPinsInComponentAreUnderspecified(circuitJson) {
1707
2021
  const warnings = [];
1708
- const db = cju5(circuitJson);
2022
+ const db = cju6(circuitJson);
1709
2023
  const sourceComponents = db.source_component.list();
1710
2024
  const sourcePorts = db.source_port.list();
1711
2025
  const portsByComponent = /* @__PURE__ */ new Map();
@@ -1737,10 +2051,10 @@ function checkAllPinsInComponentAreUnderspecified(circuitJson) {
1737
2051
  }
1738
2052
 
1739
2053
  // lib/check-no-power-pin-defined.ts
1740
- import { cju as cju6 } from "@tscircuit/circuit-json-util";
2054
+ import { cju as cju7 } from "@tscircuit/circuit-json-util";
1741
2055
  function checkNoPowerPinDefined(circuitJson) {
1742
2056
  const warnings = [];
1743
- const db = cju6(circuitJson);
2057
+ const db = cju7(circuitJson);
1744
2058
  const sourceComponents = db.source_component.list();
1745
2059
  const sourcePorts = db.source_port.list();
1746
2060
  const portsByComponent = /* @__PURE__ */ new Map();
@@ -1772,10 +2086,10 @@ function checkNoPowerPinDefined(circuitJson) {
1772
2086
  }
1773
2087
 
1774
2088
  // lib/check-no-ground-pin-defined.ts
1775
- import { cju as cju7 } from "@tscircuit/circuit-json-util";
2089
+ import { cju as cju8 } from "@tscircuit/circuit-json-util";
1776
2090
  function checkNoGroundPinDefined(circuitJson) {
1777
2091
  const warnings = [];
1778
- const db = cju7(circuitJson);
2092
+ const db = cju8(circuitJson);
1779
2093
  const sourceComponents = db.source_component.list();
1780
2094
  const sourcePorts = db.source_port.list();
1781
2095
  const portsByComponent = /* @__PURE__ */ new Map();
@@ -1806,6 +2120,75 @@ function checkNoGroundPinDefined(circuitJson) {
1806
2120
  return warnings;
1807
2121
  }
1808
2122
 
2123
+ // lib/check-connector-accessible-orientation.ts
2124
+ import { getBoardBounds } from "@tscircuit/circuit-json-util";
2125
+ function getFacingDirection(component) {
2126
+ if (!component.center || !component.cable_insertion_center) return null;
2127
+ const dx = component.cable_insertion_center.x - component.center.x;
2128
+ const dy = component.cable_insertion_center.y - component.center.y;
2129
+ if (Math.abs(dx) < 1e-6 && Math.abs(dy) < 1e-6) return null;
2130
+ if (Math.abs(dx) >= Math.abs(dy)) {
2131
+ return dx >= 0 ? "x+" : "x-";
2132
+ }
2133
+ return dy >= 0 ? "y+" : "y-";
2134
+ }
2135
+ function getRecommendedFacingDirection(component, bounds) {
2136
+ if (!component.center) return null;
2137
+ const distances = [
2138
+ { direction: "x-", distance: component.center.x - bounds.minX },
2139
+ { direction: "x+", distance: bounds.maxX - component.center.x },
2140
+ { direction: "y-", distance: component.center.y - bounds.minY },
2141
+ { direction: "y+", distance: bounds.maxY - component.center.y }
2142
+ ];
2143
+ distances.sort((a, b) => a.distance - b.distance);
2144
+ return distances[0]?.direction ?? null;
2145
+ }
2146
+ function checkConnectorAccessibleOrientation(circuitJson) {
2147
+ const board = circuitJson.find(
2148
+ (el) => el.type === "pcb_board"
2149
+ );
2150
+ if (!board) return [];
2151
+ const bounds = (() => {
2152
+ try {
2153
+ return getBoardBounds(board);
2154
+ } catch {
2155
+ return null;
2156
+ }
2157
+ })();
2158
+ if (!bounds) return [];
2159
+ const warnings = [];
2160
+ const components = circuitJson.filter(
2161
+ (el) => el.type === "pcb_component"
2162
+ );
2163
+ for (const component of components) {
2164
+ if (!component.cable_insertion_center) continue;
2165
+ const facingDirection = getFacingDirection(component);
2166
+ const recommendedFacingDirection = getRecommendedFacingDirection(
2167
+ component,
2168
+ bounds
2169
+ );
2170
+ if (!facingDirection || !recommendedFacingDirection) continue;
2171
+ if (facingDirection === recommendedFacingDirection) continue;
2172
+ const componentName = getReadableNameForComponent(
2173
+ circuitJson,
2174
+ component.pcb_component_id
2175
+ );
2176
+ warnings.push({
2177
+ type: "pcb_connector_not_in_accessible_orientation_warning",
2178
+ warning_type: "pcb_connector_not_in_accessible_orientation_warning",
2179
+ pcb_connector_not_in_accessible_orientation_warning_id: `pcb_connector_not_in_accessible_orientation_warning_${component.pcb_component_id}`,
2180
+ message: `${componentName} is facing ${facingDirection} but should face ${recommendedFacingDirection} so the connector is accessible from the board edge`,
2181
+ pcb_component_id: component.pcb_component_id,
2182
+ source_component_id: component.source_component_id,
2183
+ pcb_board_id: board.pcb_board_id,
2184
+ facing_direction: facingDirection,
2185
+ recommended_facing_direction: recommendedFacingDirection,
2186
+ subcircuit_id: component.subcircuit_id
2187
+ });
2188
+ }
2189
+ return warnings;
2190
+ }
2191
+
1809
2192
  // lib/check-courtyard-overlap/checkCourtyardOverlap.ts
1810
2193
  import {
1811
2194
  doSegmentsIntersect,
@@ -1912,75 +2295,6 @@ function checkCourtyardOverlap(circuitJson) {
1912
2295
  return errors;
1913
2296
  }
1914
2297
 
1915
- // lib/check-connector-accessible-orientation.ts
1916
- import { getBoardBounds } from "@tscircuit/circuit-json-util";
1917
- function getFacingDirection(component) {
1918
- if (!component.center || !component.cable_insertion_center) return null;
1919
- const dx = component.cable_insertion_center.x - component.center.x;
1920
- const dy = component.cable_insertion_center.y - component.center.y;
1921
- if (Math.abs(dx) < 1e-6 && Math.abs(dy) < 1e-6) return null;
1922
- if (Math.abs(dx) >= Math.abs(dy)) {
1923
- return dx >= 0 ? "x+" : "x-";
1924
- }
1925
- return dy >= 0 ? "y+" : "y-";
1926
- }
1927
- function getRecommendedFacingDirection(component, bounds) {
1928
- if (!component.center) return null;
1929
- const distances = [
1930
- { direction: "x-", distance: component.center.x - bounds.minX },
1931
- { direction: "x+", distance: bounds.maxX - component.center.x },
1932
- { direction: "y-", distance: component.center.y - bounds.minY },
1933
- { direction: "y+", distance: bounds.maxY - component.center.y }
1934
- ];
1935
- distances.sort((a, b) => a.distance - b.distance);
1936
- return distances[0]?.direction ?? null;
1937
- }
1938
- function checkConnectorAccessibleOrientation(circuitJson) {
1939
- const board = circuitJson.find(
1940
- (el) => el.type === "pcb_board"
1941
- );
1942
- if (!board) return [];
1943
- const bounds = (() => {
1944
- try {
1945
- return getBoardBounds(board);
1946
- } catch {
1947
- return null;
1948
- }
1949
- })();
1950
- if (!bounds) return [];
1951
- const warnings = [];
1952
- const components = circuitJson.filter(
1953
- (el) => el.type === "pcb_component"
1954
- );
1955
- for (const component of components) {
1956
- if (!component.cable_insertion_center) continue;
1957
- const facingDirection = getFacingDirection(component);
1958
- const recommendedFacingDirection = getRecommendedFacingDirection(
1959
- component,
1960
- bounds
1961
- );
1962
- if (!facingDirection || !recommendedFacingDirection) continue;
1963
- if (facingDirection === recommendedFacingDirection) continue;
1964
- const componentName = getReadableNameForComponent(
1965
- circuitJson,
1966
- component.pcb_component_id
1967
- );
1968
- warnings.push({
1969
- type: "pcb_connector_not_in_accessible_orientation_warning",
1970
- warning_type: "pcb_connector_not_in_accessible_orientation_warning",
1971
- pcb_connector_not_in_accessible_orientation_warning_id: `pcb_connector_not_in_accessible_orientation_warning_${component.pcb_component_id}`,
1972
- message: `${componentName} is facing ${facingDirection} but should face ${recommendedFacingDirection} so the connector is accessible from the board edge`,
1973
- pcb_component_id: component.pcb_component_id,
1974
- source_component_id: component.source_component_id,
1975
- pcb_board_id: board.pcb_board_id,
1976
- facing_direction: facingDirection,
1977
- recommended_facing_direction: recommendedFacingDirection,
1978
- subcircuit_id: component.subcircuit_id
1979
- });
1980
- }
1981
- return warnings;
1982
- }
1983
-
1984
2298
  // lib/check-traces-are-contiguous/check-traces-are-contiguous.ts
1985
2299
  import { getReadableNameForPcbPort as getReadableNameForPcbPort2 } from "@tscircuit/circuit-json-util";
1986
2300
 
@@ -1990,6 +2304,8 @@ async function runAllPlacementChecks(circuitJson) {
1990
2304
  ...checkViasOffBoard(circuitJson),
1991
2305
  ...checkPcbComponentsOutOfBoard(circuitJson),
1992
2306
  ...checkPcbComponentOverlap(circuitJson),
2307
+ ...checkPadPadClearance(circuitJson),
2308
+ ...checkPadTraceClearance(circuitJson),
1993
2309
  ...checkCourtyardOverlap(circuitJson),
1994
2310
  ...checkConnectorAccessibleOrientation(circuitJson)
1995
2311
  ];
@@ -2009,6 +2325,7 @@ async function runAllRoutingChecks(circuitJson) {
2009
2325
  ...checkEachPcbPortConnectedToPcbTraces(circuitJson),
2010
2326
  ...checkSourceTracesHavePcbTraces(circuitJson),
2011
2327
  ...checkEachPcbTraceNonOverlapping(circuitJson),
2328
+ ...checkViaTraceClearance(circuitJson),
2012
2329
  ...checkSameNetViaSpacing(circuitJson),
2013
2330
  ...checkDifferentNetViaSpacing(circuitJson),
2014
2331
  // ...checkTracesAreContiguous(circuitJson),
@@ -2032,6 +2349,8 @@ export {
2032
2349
  checkEachPcbTraceNonOverlapping,
2033
2350
  checkNoGroundPinDefined,
2034
2351
  checkNoPowerPinDefined,
2352
+ checkPadPadClearance,
2353
+ checkPadTraceClearance,
2035
2354
  checkPcbComponentOverlap,
2036
2355
  checkPcbComponentsOutOfBoard,
2037
2356
  checkPcbTracesOutOfBoard,
@@ -2039,6 +2358,7 @@ export {
2039
2358
  checkSameNetViaSpacing,
2040
2359
  checkSourceTracesHavePcbTraces,
2041
2360
  checkSourceTracesMatchPcbTraceThickness,
2361
+ checkViaTraceClearance,
2042
2362
  checkViasOffBoard,
2043
2363
  runAllChecks,
2044
2364
  runAllNetlistChecks,