@tscircuit/checks 0.0.116 → 0.0.118
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/dist/index.d.ts +17 -5
- package/dist/index.js +445 -83
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -120,6 +120,53 @@ var getReadableNameForPort = (circuitJson, pcbPortId) => {
|
|
|
120
120
|
"port"
|
|
121
121
|
);
|
|
122
122
|
};
|
|
123
|
+
var getReadableNameForSourceTrace = (circuitJson, sourceTrace) => {
|
|
124
|
+
const displayName = sanitizeReadableName(
|
|
125
|
+
sourceTrace.display_name,
|
|
126
|
+
sourceTrace.source_trace_id,
|
|
127
|
+
""
|
|
128
|
+
);
|
|
129
|
+
if (displayName) return displayName;
|
|
130
|
+
const connectedPortNames = (sourceTrace.connected_source_port_ids ?? []).map((sourcePortId) => {
|
|
131
|
+
const pcbPort = circuitJson.find(
|
|
132
|
+
(element) => element.type === "pcb_port" && element.source_port_id === sourcePortId
|
|
133
|
+
);
|
|
134
|
+
if (pcbPort?.type === "pcb_port") {
|
|
135
|
+
return getReadableNameForPort(circuitJson, pcbPort.pcb_port_id);
|
|
136
|
+
}
|
|
137
|
+
const sourcePort = circuitJson.find(
|
|
138
|
+
(element) => element.type === "source_port" && element.source_port_id === sourcePortId
|
|
139
|
+
);
|
|
140
|
+
if (sourcePort?.type !== "source_port") return null;
|
|
141
|
+
const sourceComponent = circuitJson.find(
|
|
142
|
+
(element) => element.type === "source_component" && element.source_component_id === sourcePort.source_component_id
|
|
143
|
+
);
|
|
144
|
+
const sourceComponentName = sourceComponent?.type === "source_component" ? sanitizeReadableName(
|
|
145
|
+
sourceComponent.name,
|
|
146
|
+
sourceComponent.source_component_id,
|
|
147
|
+
""
|
|
148
|
+
) : "";
|
|
149
|
+
const sourcePortName = firstReadableName(
|
|
150
|
+
[
|
|
151
|
+
sourcePort.name,
|
|
152
|
+
sourcePort.pin_number?.toString(),
|
|
153
|
+
sourcePort.port_hints?.[0]
|
|
154
|
+
],
|
|
155
|
+
sourcePort.source_port_id
|
|
156
|
+
);
|
|
157
|
+
if (sourceComponentName && sourcePortName) {
|
|
158
|
+
return `${sourceComponentName}.${sourcePortName}`;
|
|
159
|
+
}
|
|
160
|
+
return sourcePortName || null;
|
|
161
|
+
}).filter((name) => Boolean(name));
|
|
162
|
+
if (connectedPortNames.length >= 2) {
|
|
163
|
+
return `${connectedPortNames[0]} to ${connectedPortNames[1]}`;
|
|
164
|
+
}
|
|
165
|
+
if (connectedPortNames.length === 1) {
|
|
166
|
+
return `trace connected to ${connectedPortNames[0]}`;
|
|
167
|
+
}
|
|
168
|
+
return `trace ${sourceTrace.source_trace_id}`;
|
|
169
|
+
};
|
|
123
170
|
var getReadableNameForElementId = (circuitJson, elementId) => sanitizeReadableName(
|
|
124
171
|
getReadableNameForElement(circuitJson, elementId),
|
|
125
172
|
elementId,
|
|
@@ -322,6 +369,8 @@ var DEFAULT_TRACE_THICKNESS = 0.15;
|
|
|
322
369
|
var DEFAULT_VIA_BOARD_MARGIN = 0.3;
|
|
323
370
|
var DEFAULT_SAME_NET_VIA_MARGIN = 0.2;
|
|
324
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;
|
|
325
374
|
var EPSILON = 5e-3;
|
|
326
375
|
|
|
327
376
|
// lib/check-each-pcb-trace-non-overlapping/getPcbPortIdsConnectedToTraces.ts
|
|
@@ -1218,6 +1267,79 @@ function checkDifferentNetViaSpacing(circuitJson, {
|
|
|
1218
1267
|
return errors;
|
|
1219
1268
|
}
|
|
1220
1269
|
|
|
1270
|
+
// lib/check-source-traces-match-pcb-trace-thickness.ts
|
|
1271
|
+
import { cju as cju2 } from "@tscircuit/circuit-json-util";
|
|
1272
|
+
import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson5 } from "circuit-json-to-connectivity-map";
|
|
1273
|
+
function checkSourceTracesMatchPcbTraceThickness(circuitJson) {
|
|
1274
|
+
const warnings = [];
|
|
1275
|
+
const db = cju2(circuitJson);
|
|
1276
|
+
const sourceTraces = db.source_trace.list();
|
|
1277
|
+
const pcbTraces = db.pcb_trace.list();
|
|
1278
|
+
const pcbPorts = db.pcb_port.list();
|
|
1279
|
+
const connectivityMap = getFullConnectivityMapFromCircuitJson5(circuitJson);
|
|
1280
|
+
for (const sourceTrace of sourceTraces) {
|
|
1281
|
+
const requestedThickness = sourceTrace.min_trace_thickness;
|
|
1282
|
+
if (requestedThickness === void 0) continue;
|
|
1283
|
+
const connectedPcbPorts = pcbPorts.filter(
|
|
1284
|
+
(pcbPort) => sourceTrace.connected_source_port_ids?.includes(pcbPort.source_port_id)
|
|
1285
|
+
);
|
|
1286
|
+
if (connectedPcbPorts.length < 2) continue;
|
|
1287
|
+
const referenceNetId = connectivityMap.getNetConnectedToId(
|
|
1288
|
+
connectedPcbPorts[0].pcb_port_id
|
|
1289
|
+
);
|
|
1290
|
+
if (!referenceNetId) continue;
|
|
1291
|
+
const netElementIds = connectivityMap.getIdsConnectedToNet(referenceNetId);
|
|
1292
|
+
const relatedPcbTraces = pcbTraces.filter(
|
|
1293
|
+
(pcbTrace) => netElementIds.includes(pcbTrace.pcb_trace_id)
|
|
1294
|
+
);
|
|
1295
|
+
if (relatedPcbTraces.length === 0) continue;
|
|
1296
|
+
const actualWireWidths = relatedPcbTraces.flatMap(
|
|
1297
|
+
(pcbTrace) => pcbTrace.route.filter((point) => point.route_type === "wire").map((point) => point.width)
|
|
1298
|
+
);
|
|
1299
|
+
if (actualWireWidths.length === 0) continue;
|
|
1300
|
+
const actualThickness = Math.min(...actualWireWidths);
|
|
1301
|
+
if (actualThickness >= requestedThickness) continue;
|
|
1302
|
+
let undersizedSegment;
|
|
1303
|
+
for (const relatedPcbTrace of relatedPcbTraces) {
|
|
1304
|
+
for (let i = 0; i < relatedPcbTrace.route.length - 1; i++) {
|
|
1305
|
+
const point = relatedPcbTrace.route[i];
|
|
1306
|
+
const nextPoint = relatedPcbTrace.route[i + 1];
|
|
1307
|
+
if (!point || !nextPoint) continue;
|
|
1308
|
+
if (point.route_type !== "wire" || nextPoint.route_type !== "wire") {
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
1311
|
+
if (point.width !== actualThickness) continue;
|
|
1312
|
+
undersizedSegment = {
|
|
1313
|
+
pcb_trace_id: relatedPcbTrace.pcb_trace_id,
|
|
1314
|
+
center: {
|
|
1315
|
+
x: (point.x + nextPoint.x) / 2,
|
|
1316
|
+
y: (point.y + nextPoint.y) / 2
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
1319
|
+
break;
|
|
1320
|
+
}
|
|
1321
|
+
if (undersizedSegment) break;
|
|
1322
|
+
}
|
|
1323
|
+
if (!undersizedSegment) continue;
|
|
1324
|
+
warnings.push({
|
|
1325
|
+
type: "pcb_trace_warning",
|
|
1326
|
+
pcb_trace_warning_id: `pcb_trace_warning_${sourceTrace.source_trace_id}`,
|
|
1327
|
+
warning_type: "pcb_trace_warning",
|
|
1328
|
+
message: `Trace [${getReadableNameForSourceTrace(circuitJson, sourceTrace)}] is routed thinner than requested (requested: ${requestedThickness}mm, actual: ${actualThickness}mm).`,
|
|
1329
|
+
center: undersizedSegment.center,
|
|
1330
|
+
source_trace_id: sourceTrace.source_trace_id,
|
|
1331
|
+
pcb_trace_id: undersizedSegment.pcb_trace_id,
|
|
1332
|
+
pcb_component_ids: Array.from(
|
|
1333
|
+
new Set(
|
|
1334
|
+
connectedPcbPorts.map((pcbPort) => pcbPort.pcb_component_id).filter((id) => id !== void 0)
|
|
1335
|
+
)
|
|
1336
|
+
),
|
|
1337
|
+
pcb_port_ids: connectedPcbPorts.map((pcbPort) => pcbPort.pcb_port_id)
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
return warnings;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1221
1343
|
// lib/check-source-traces-have-pcb-traces.ts
|
|
1222
1344
|
var getSourceTraceIdsFromPcbTrace = (pcbTraceSourceTraceId) => {
|
|
1223
1345
|
if (!pcbTraceSourceTraceId) return [];
|
|
@@ -1265,7 +1387,7 @@ function checkSourceTracesHavePcbTraces(circuitJson) {
|
|
|
1265
1387
|
}
|
|
1266
1388
|
|
|
1267
1389
|
// lib/check-trace-out-of-board/checkTraceOutOfBoard.ts
|
|
1268
|
-
import { cju as
|
|
1390
|
+
import { cju as cju3 } from "@tscircuit/circuit-json-util";
|
|
1269
1391
|
import { segmentToSegmentMinDistance as segmentToSegmentMinDistance2 } from "@tscircuit/math-utils";
|
|
1270
1392
|
var DEFAULT_BOARD_MARGIN = 0.2;
|
|
1271
1393
|
function getBoardPolygonPoints(board) {
|
|
@@ -1299,7 +1421,7 @@ function checkPcbTracesOutOfBoard(circuitJson, config = {}) {
|
|
|
1299
1421
|
if (!board) return errors;
|
|
1300
1422
|
const boardPoints = getBoardPolygonPoints(board);
|
|
1301
1423
|
if (!boardPoints) return errors;
|
|
1302
|
-
const pcbTraces =
|
|
1424
|
+
const pcbTraces = cju3(circuitJson).pcb_trace.list();
|
|
1303
1425
|
for (const trace of pcbTraces) {
|
|
1304
1426
|
if (trace.route.length < 2) continue;
|
|
1305
1427
|
for (let i = 0; i < trace.route.length - 1; i++) {
|
|
@@ -1348,12 +1470,12 @@ function checkPcbTracesOutOfBoard(circuitJson, config = {}) {
|
|
|
1348
1470
|
|
|
1349
1471
|
// lib/check-pcb-components-overlap/checkPcbComponentOverlap.ts
|
|
1350
1472
|
import {
|
|
1351
|
-
cju as
|
|
1473
|
+
cju as cju4,
|
|
1352
1474
|
getBoundsOfPcbElements as getBoundsOfPcbElements4,
|
|
1353
1475
|
getPrimaryId as getPrimaryId2
|
|
1354
1476
|
} from "@tscircuit/circuit-json-util";
|
|
1355
1477
|
import { doBoundsOverlap as doBoundsOverlap2 } from "@tscircuit/math-utils";
|
|
1356
|
-
import { getFullConnectivityMapFromCircuitJson as
|
|
1478
|
+
import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson6 } from "circuit-json-to-connectivity-map";
|
|
1357
1479
|
|
|
1358
1480
|
// lib/check-pcb-components-overlap/doPcbElementsOverlap.ts
|
|
1359
1481
|
import { getBoundsOfPcbElements as getBoundsOfPcbElements3 } from "@tscircuit/circuit-json-util";
|
|
@@ -1385,10 +1507,10 @@ var formatOverlapElementDescription = (circuitJson, element) => {
|
|
|
1385
1507
|
};
|
|
1386
1508
|
function checkPcbComponentOverlap(circuitJson) {
|
|
1387
1509
|
const errors = [];
|
|
1388
|
-
const connMap =
|
|
1389
|
-
const smtPads =
|
|
1390
|
-
const platedHoles =
|
|
1391
|
-
const holes =
|
|
1510
|
+
const connMap = getFullConnectivityMapFromCircuitJson6(circuitJson);
|
|
1511
|
+
const smtPads = cju4(circuitJson).pcb_smtpad.list();
|
|
1512
|
+
const platedHoles = cju4(circuitJson).pcb_plated_hole.list();
|
|
1513
|
+
const holes = cju4(circuitJson).pcb_hole.list();
|
|
1392
1514
|
const componentMap = /* @__PURE__ */ new Map();
|
|
1393
1515
|
for (const pad of smtPads) {
|
|
1394
1516
|
const componentId = pad.pcb_component_id || `standalone_pad_${getPrimaryId2(pad)}`;
|
|
@@ -1481,6 +1603,241 @@ function checkPcbComponentOverlap(circuitJson) {
|
|
|
1481
1603
|
return errors;
|
|
1482
1604
|
}
|
|
1483
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
|
+
|
|
1484
1841
|
// lib/check-pin-must-be-connected.ts
|
|
1485
1842
|
function checkPinMustBeConnected(circuitJson) {
|
|
1486
1843
|
const errors = [];
|
|
@@ -1540,7 +1897,7 @@ function checkPinMustBeConnected(circuitJson) {
|
|
|
1540
1897
|
}
|
|
1541
1898
|
|
|
1542
1899
|
// lib/check-all-pins-in-component-are-underspecified.ts
|
|
1543
|
-
import { cju as
|
|
1900
|
+
import { cju as cju6 } from "@tscircuit/circuit-json-util";
|
|
1544
1901
|
var PIN_ATTRIBUTE_KEYS = [
|
|
1545
1902
|
"must_be_connected",
|
|
1546
1903
|
"provides_power",
|
|
@@ -1585,7 +1942,7 @@ function hasAnyPinAttribute(port) {
|
|
|
1585
1942
|
}
|
|
1586
1943
|
function checkAllPinsInComponentAreUnderspecified(circuitJson) {
|
|
1587
1944
|
const warnings = [];
|
|
1588
|
-
const db =
|
|
1945
|
+
const db = cju6(circuitJson);
|
|
1589
1946
|
const sourceComponents = db.source_component.list();
|
|
1590
1947
|
const sourcePorts = db.source_port.list();
|
|
1591
1948
|
const portsByComponent = /* @__PURE__ */ new Map();
|
|
@@ -1617,10 +1974,10 @@ function checkAllPinsInComponentAreUnderspecified(circuitJson) {
|
|
|
1617
1974
|
}
|
|
1618
1975
|
|
|
1619
1976
|
// lib/check-no-power-pin-defined.ts
|
|
1620
|
-
import { cju as
|
|
1977
|
+
import { cju as cju7 } from "@tscircuit/circuit-json-util";
|
|
1621
1978
|
function checkNoPowerPinDefined(circuitJson) {
|
|
1622
1979
|
const warnings = [];
|
|
1623
|
-
const db =
|
|
1980
|
+
const db = cju7(circuitJson);
|
|
1624
1981
|
const sourceComponents = db.source_component.list();
|
|
1625
1982
|
const sourcePorts = db.source_port.list();
|
|
1626
1983
|
const portsByComponent = /* @__PURE__ */ new Map();
|
|
@@ -1652,10 +2009,10 @@ function checkNoPowerPinDefined(circuitJson) {
|
|
|
1652
2009
|
}
|
|
1653
2010
|
|
|
1654
2011
|
// lib/check-no-ground-pin-defined.ts
|
|
1655
|
-
import { cju as
|
|
2012
|
+
import { cju as cju8 } from "@tscircuit/circuit-json-util";
|
|
1656
2013
|
function checkNoGroundPinDefined(circuitJson) {
|
|
1657
2014
|
const warnings = [];
|
|
1658
|
-
const db =
|
|
2015
|
+
const db = cju8(circuitJson);
|
|
1659
2016
|
const sourceComponents = db.source_component.list();
|
|
1660
2017
|
const sourcePorts = db.source_port.list();
|
|
1661
2018
|
const portsByComponent = /* @__PURE__ */ new Map();
|
|
@@ -1686,6 +2043,75 @@ function checkNoGroundPinDefined(circuitJson) {
|
|
|
1686
2043
|
return warnings;
|
|
1687
2044
|
}
|
|
1688
2045
|
|
|
2046
|
+
// lib/check-connector-accessible-orientation.ts
|
|
2047
|
+
import { getBoardBounds } from "@tscircuit/circuit-json-util";
|
|
2048
|
+
function getFacingDirection(component) {
|
|
2049
|
+
if (!component.center || !component.cable_insertion_center) return null;
|
|
2050
|
+
const dx = component.cable_insertion_center.x - component.center.x;
|
|
2051
|
+
const dy = component.cable_insertion_center.y - component.center.y;
|
|
2052
|
+
if (Math.abs(dx) < 1e-6 && Math.abs(dy) < 1e-6) return null;
|
|
2053
|
+
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
2054
|
+
return dx >= 0 ? "x+" : "x-";
|
|
2055
|
+
}
|
|
2056
|
+
return dy >= 0 ? "y+" : "y-";
|
|
2057
|
+
}
|
|
2058
|
+
function getRecommendedFacingDirection(component, bounds) {
|
|
2059
|
+
if (!component.center) return null;
|
|
2060
|
+
const distances = [
|
|
2061
|
+
{ direction: "x-", distance: component.center.x - bounds.minX },
|
|
2062
|
+
{ direction: "x+", distance: bounds.maxX - component.center.x },
|
|
2063
|
+
{ direction: "y-", distance: component.center.y - bounds.minY },
|
|
2064
|
+
{ direction: "y+", distance: bounds.maxY - component.center.y }
|
|
2065
|
+
];
|
|
2066
|
+
distances.sort((a, b) => a.distance - b.distance);
|
|
2067
|
+
return distances[0]?.direction ?? null;
|
|
2068
|
+
}
|
|
2069
|
+
function checkConnectorAccessibleOrientation(circuitJson) {
|
|
2070
|
+
const board = circuitJson.find(
|
|
2071
|
+
(el) => el.type === "pcb_board"
|
|
2072
|
+
);
|
|
2073
|
+
if (!board) return [];
|
|
2074
|
+
const bounds = (() => {
|
|
2075
|
+
try {
|
|
2076
|
+
return getBoardBounds(board);
|
|
2077
|
+
} catch {
|
|
2078
|
+
return null;
|
|
2079
|
+
}
|
|
2080
|
+
})();
|
|
2081
|
+
if (!bounds) return [];
|
|
2082
|
+
const warnings = [];
|
|
2083
|
+
const components = circuitJson.filter(
|
|
2084
|
+
(el) => el.type === "pcb_component"
|
|
2085
|
+
);
|
|
2086
|
+
for (const component of components) {
|
|
2087
|
+
if (!component.cable_insertion_center) continue;
|
|
2088
|
+
const facingDirection = getFacingDirection(component);
|
|
2089
|
+
const recommendedFacingDirection = getRecommendedFacingDirection(
|
|
2090
|
+
component,
|
|
2091
|
+
bounds
|
|
2092
|
+
);
|
|
2093
|
+
if (!facingDirection || !recommendedFacingDirection) continue;
|
|
2094
|
+
if (facingDirection === recommendedFacingDirection) continue;
|
|
2095
|
+
const componentName = getReadableNameForComponent(
|
|
2096
|
+
circuitJson,
|
|
2097
|
+
component.pcb_component_id
|
|
2098
|
+
);
|
|
2099
|
+
warnings.push({
|
|
2100
|
+
type: "pcb_connector_not_in_accessible_orientation_warning",
|
|
2101
|
+
warning_type: "pcb_connector_not_in_accessible_orientation_warning",
|
|
2102
|
+
pcb_connector_not_in_accessible_orientation_warning_id: `pcb_connector_not_in_accessible_orientation_warning_${component.pcb_component_id}`,
|
|
2103
|
+
message: `${componentName} is facing ${facingDirection} but should face ${recommendedFacingDirection} so the connector is accessible from the board edge`,
|
|
2104
|
+
pcb_component_id: component.pcb_component_id,
|
|
2105
|
+
source_component_id: component.source_component_id,
|
|
2106
|
+
pcb_board_id: board.pcb_board_id,
|
|
2107
|
+
facing_direction: facingDirection,
|
|
2108
|
+
recommended_facing_direction: recommendedFacingDirection,
|
|
2109
|
+
subcircuit_id: component.subcircuit_id
|
|
2110
|
+
});
|
|
2111
|
+
}
|
|
2112
|
+
return warnings;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
1689
2115
|
// lib/check-courtyard-overlap/checkCourtyardOverlap.ts
|
|
1690
2116
|
import {
|
|
1691
2117
|
doSegmentsIntersect,
|
|
@@ -1792,75 +2218,6 @@ function checkCourtyardOverlap(circuitJson) {
|
|
|
1792
2218
|
return errors;
|
|
1793
2219
|
}
|
|
1794
2220
|
|
|
1795
|
-
// lib/check-connector-accessible-orientation.ts
|
|
1796
|
-
import { getBoardBounds } from "@tscircuit/circuit-json-util";
|
|
1797
|
-
function getFacingDirection(component) {
|
|
1798
|
-
if (!component.center || !component.cable_insertion_center) return null;
|
|
1799
|
-
const dx = component.cable_insertion_center.x - component.center.x;
|
|
1800
|
-
const dy = component.cable_insertion_center.y - component.center.y;
|
|
1801
|
-
if (Math.abs(dx) < 1e-6 && Math.abs(dy) < 1e-6) return null;
|
|
1802
|
-
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
1803
|
-
return dx >= 0 ? "x+" : "x-";
|
|
1804
|
-
}
|
|
1805
|
-
return dy >= 0 ? "y+" : "y-";
|
|
1806
|
-
}
|
|
1807
|
-
function getRecommendedFacingDirection(component, bounds) {
|
|
1808
|
-
if (!component.center) return null;
|
|
1809
|
-
const distances = [
|
|
1810
|
-
{ direction: "x-", distance: component.center.x - bounds.minX },
|
|
1811
|
-
{ direction: "x+", distance: bounds.maxX - component.center.x },
|
|
1812
|
-
{ direction: "y-", distance: component.center.y - bounds.minY },
|
|
1813
|
-
{ direction: "y+", distance: bounds.maxY - component.center.y }
|
|
1814
|
-
];
|
|
1815
|
-
distances.sort((a, b) => a.distance - b.distance);
|
|
1816
|
-
return distances[0]?.direction ?? null;
|
|
1817
|
-
}
|
|
1818
|
-
function checkConnectorAccessibleOrientation(circuitJson) {
|
|
1819
|
-
const board = circuitJson.find(
|
|
1820
|
-
(el) => el.type === "pcb_board"
|
|
1821
|
-
);
|
|
1822
|
-
if (!board) return [];
|
|
1823
|
-
const bounds = (() => {
|
|
1824
|
-
try {
|
|
1825
|
-
return getBoardBounds(board);
|
|
1826
|
-
} catch {
|
|
1827
|
-
return null;
|
|
1828
|
-
}
|
|
1829
|
-
})();
|
|
1830
|
-
if (!bounds) return [];
|
|
1831
|
-
const warnings = [];
|
|
1832
|
-
const components = circuitJson.filter(
|
|
1833
|
-
(el) => el.type === "pcb_component"
|
|
1834
|
-
);
|
|
1835
|
-
for (const component of components) {
|
|
1836
|
-
if (!component.cable_insertion_center) continue;
|
|
1837
|
-
const facingDirection = getFacingDirection(component);
|
|
1838
|
-
const recommendedFacingDirection = getRecommendedFacingDirection(
|
|
1839
|
-
component,
|
|
1840
|
-
bounds
|
|
1841
|
-
);
|
|
1842
|
-
if (!facingDirection || !recommendedFacingDirection) continue;
|
|
1843
|
-
if (facingDirection === recommendedFacingDirection) continue;
|
|
1844
|
-
const componentName = getReadableNameForComponent(
|
|
1845
|
-
circuitJson,
|
|
1846
|
-
component.pcb_component_id
|
|
1847
|
-
);
|
|
1848
|
-
warnings.push({
|
|
1849
|
-
type: "pcb_connector_not_in_accessible_orientation_warning",
|
|
1850
|
-
warning_type: "pcb_connector_not_in_accessible_orientation_warning",
|
|
1851
|
-
pcb_connector_not_in_accessible_orientation_warning_id: `pcb_connector_not_in_accessible_orientation_warning_${component.pcb_component_id}`,
|
|
1852
|
-
message: `${componentName} is facing ${facingDirection} but should face ${recommendedFacingDirection} so the connector is accessible from the board edge`,
|
|
1853
|
-
pcb_component_id: component.pcb_component_id,
|
|
1854
|
-
source_component_id: component.source_component_id,
|
|
1855
|
-
pcb_board_id: board.pcb_board_id,
|
|
1856
|
-
facing_direction: facingDirection,
|
|
1857
|
-
recommended_facing_direction: recommendedFacingDirection,
|
|
1858
|
-
subcircuit_id: component.subcircuit_id
|
|
1859
|
-
});
|
|
1860
|
-
}
|
|
1861
|
-
return warnings;
|
|
1862
|
-
}
|
|
1863
|
-
|
|
1864
2221
|
// lib/check-traces-are-contiguous/check-traces-are-contiguous.ts
|
|
1865
2222
|
import { getReadableNameForPcbPort as getReadableNameForPcbPort2 } from "@tscircuit/circuit-json-util";
|
|
1866
2223
|
|
|
@@ -1870,6 +2227,8 @@ async function runAllPlacementChecks(circuitJson) {
|
|
|
1870
2227
|
...checkViasOffBoard(circuitJson),
|
|
1871
2228
|
...checkPcbComponentsOutOfBoard(circuitJson),
|
|
1872
2229
|
...checkPcbComponentOverlap(circuitJson),
|
|
2230
|
+
...checkPadPadClearance(circuitJson),
|
|
2231
|
+
...checkPadTraceClearance(circuitJson),
|
|
1873
2232
|
...checkCourtyardOverlap(circuitJson),
|
|
1874
2233
|
...checkConnectorAccessibleOrientation(circuitJson)
|
|
1875
2234
|
];
|
|
@@ -1912,12 +2271,15 @@ export {
|
|
|
1912
2271
|
checkEachPcbTraceNonOverlapping,
|
|
1913
2272
|
checkNoGroundPinDefined,
|
|
1914
2273
|
checkNoPowerPinDefined,
|
|
2274
|
+
checkPadPadClearance,
|
|
2275
|
+
checkPadTraceClearance,
|
|
1915
2276
|
checkPcbComponentOverlap,
|
|
1916
2277
|
checkPcbComponentsOutOfBoard,
|
|
1917
2278
|
checkPcbTracesOutOfBoard,
|
|
1918
2279
|
checkPinMustBeConnected,
|
|
1919
2280
|
checkSameNetViaSpacing,
|
|
1920
2281
|
checkSourceTracesHavePcbTraces,
|
|
2282
|
+
checkSourceTracesMatchPcbTraceThickness,
|
|
1921
2283
|
checkViasOffBoard,
|
|
1922
2284
|
runAllChecks,
|
|
1923
2285
|
runAllNetlistChecks,
|