@tscircuit/checks 0.0.140 → 0.0.142
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 +3 -1
- package/dist/index.js +320 -319
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1777,80 +1777,337 @@ function checkSourceTracesHavePcbTraces(circuitJson) {
|
|
|
1777
1777
|
return errors;
|
|
1778
1778
|
}
|
|
1779
1779
|
|
|
1780
|
-
// lib/check-
|
|
1781
|
-
import {
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1780
|
+
// lib/check-traces-are-contiguous/check-traces-are-contiguous.ts
|
|
1781
|
+
import {
|
|
1782
|
+
getReadableNameForPcbPort as getReadableNameForPcbPort2,
|
|
1783
|
+
getReadableNameForPcbTrace
|
|
1784
|
+
} from "@tscircuit/circuit-json-util";
|
|
1785
|
+
import { PcbConnectivityMap } from "circuit-json-to-connectivity-map";
|
|
1786
|
+
function getRoutePointCenter(point) {
|
|
1787
|
+
if (point.route_type === "through_pad") {
|
|
1788
|
+
return {
|
|
1789
|
+
x: (point.start.x + point.end.x) / 2,
|
|
1790
|
+
y: (point.start.y + point.end.y) / 2
|
|
1791
|
+
};
|
|
1786
1792
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
{ x:
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1793
|
+
return { x: point.x, y: point.y };
|
|
1794
|
+
}
|
|
1795
|
+
function routePointConnectsToAnotherExpectedPort(point, expectedPorts, missingPcbPortId, padMap) {
|
|
1796
|
+
if (point.route_type !== "wire") return false;
|
|
1797
|
+
return expectedPorts.some((expectedPort) => {
|
|
1798
|
+
if (!expectedPort.pcb_port_id || expectedPort.pcb_port_id === missingPcbPortId) {
|
|
1799
|
+
return false;
|
|
1800
|
+
}
|
|
1801
|
+
const expectedPads = padMap.get(expectedPort.pcb_port_id);
|
|
1802
|
+
return expectedPads?.some(
|
|
1803
|
+
(pad) => isPointInPad({ x: point.x, y: point.y }, pad)
|
|
1804
|
+
) ?? false;
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
function getMissingConnectionErrorCenter({
|
|
1808
|
+
firstPoint,
|
|
1809
|
+
lastPoint,
|
|
1810
|
+
port,
|
|
1811
|
+
expectedPorts,
|
|
1812
|
+
padMap
|
|
1813
|
+
}) {
|
|
1814
|
+
let errorLocation;
|
|
1815
|
+
const firstWirePoint = firstPoint.route_type === "wire" ? firstPoint : void 0;
|
|
1816
|
+
const lastWirePoint = lastPoint.route_type === "wire" ? lastPoint : void 0;
|
|
1817
|
+
const firstWirePointReferencesPort = getPcbPortIdsConnectedToRoutePoint(
|
|
1818
|
+
firstPoint
|
|
1819
|
+
).includes(port.pcb_port_id);
|
|
1820
|
+
const lastWirePointReferencesPort = getPcbPortIdsConnectedToRoutePoint(
|
|
1821
|
+
lastPoint
|
|
1822
|
+
).includes(port.pcb_port_id);
|
|
1823
|
+
if (firstWirePointReferencesPort && firstWirePoint) {
|
|
1824
|
+
errorLocation = firstWirePoint;
|
|
1825
|
+
} else if (lastWirePointReferencesPort && lastWirePoint) {
|
|
1826
|
+
errorLocation = lastWirePoint;
|
|
1827
|
+
} else if (routePointConnectsToAnotherExpectedPort(
|
|
1828
|
+
firstPoint,
|
|
1829
|
+
expectedPorts,
|
|
1830
|
+
port.pcb_port_id,
|
|
1831
|
+
padMap
|
|
1832
|
+
) && lastWirePoint) {
|
|
1833
|
+
errorLocation = lastWirePoint;
|
|
1834
|
+
} else if (routePointConnectsToAnotherExpectedPort(
|
|
1835
|
+
lastPoint,
|
|
1836
|
+
expectedPorts,
|
|
1837
|
+
port.pcb_port_id,
|
|
1838
|
+
padMap
|
|
1839
|
+
) && firstWirePoint) {
|
|
1840
|
+
errorLocation = firstWirePoint;
|
|
1841
|
+
} else if (firstWirePoint && lastWirePoint) {
|
|
1842
|
+
errorLocation = distance2(firstWirePoint, port) <= distance2(lastWirePoint, port) ? firstWirePoint : lastWirePoint;
|
|
1843
|
+
} else if (firstWirePoint) {
|
|
1844
|
+
errorLocation = firstWirePoint;
|
|
1845
|
+
} else if (lastWirePoint) {
|
|
1846
|
+
errorLocation = lastWirePoint;
|
|
1802
1847
|
}
|
|
1803
|
-
|
|
1848
|
+
const firstPointCenter = getRoutePointCenter(firstPoint);
|
|
1849
|
+
const lastPointCenter = getRoutePointCenter(lastPoint);
|
|
1850
|
+
return errorLocation ? { x: errorLocation.x, y: errorLocation.y } : {
|
|
1851
|
+
x: (firstPointCenter.x + lastPointCenter.x) / 2,
|
|
1852
|
+
y: (firstPointCenter.y + lastPointCenter.y) / 2
|
|
1853
|
+
};
|
|
1804
1854
|
}
|
|
1805
|
-
function
|
|
1855
|
+
function checkTracesAreContiguous(circuitJson) {
|
|
1806
1856
|
const errors = [];
|
|
1807
|
-
const
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
const
|
|
1811
|
-
|
|
1812
|
-
|
|
1857
|
+
const pcbPorts = circuitJson.filter(
|
|
1858
|
+
(el) => el.type === "pcb_port"
|
|
1859
|
+
);
|
|
1860
|
+
const pcbTraces = circuitJson.filter(
|
|
1861
|
+
(el) => el.type === "pcb_trace"
|
|
1862
|
+
);
|
|
1863
|
+
const sourceTraces = circuitJson.filter(
|
|
1864
|
+
(el) => el.type === "source_trace"
|
|
1865
|
+
);
|
|
1866
|
+
const pcbSmtPads = circuitJson.filter(
|
|
1867
|
+
(el) => el.type === "pcb_smtpad"
|
|
1868
|
+
);
|
|
1869
|
+
const pcbPlatedHoles = circuitJson.filter(
|
|
1870
|
+
(el) => el.type === "pcb_plated_hole"
|
|
1871
|
+
);
|
|
1872
|
+
const padMap = /* @__PURE__ */ new Map();
|
|
1873
|
+
const pcbConnectivityMap = new PcbConnectivityMap(circuitJson);
|
|
1874
|
+
for (const pad of pcbSmtPads) {
|
|
1875
|
+
if (pad.pcb_port_id) {
|
|
1876
|
+
padMap.set(pad.pcb_port_id, [...padMap.get(pad.pcb_port_id) ?? [], pad]);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
for (const hole of pcbPlatedHoles) {
|
|
1880
|
+
if (hole.pcb_port_id) {
|
|
1881
|
+
padMap.set(hole.pcb_port_id, [
|
|
1882
|
+
...padMap.get(hole.pcb_port_id) ?? [],
|
|
1883
|
+
hole
|
|
1884
|
+
]);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1813
1887
|
for (const trace of pcbTraces) {
|
|
1814
|
-
if (trace.route.length
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1888
|
+
if (trace.route.length === 0) continue;
|
|
1889
|
+
const firstPoint = trace.route[0];
|
|
1890
|
+
const lastPoint = trace.route[trace.route.length - 1];
|
|
1891
|
+
const sourceTrace = sourceTraces.find(
|
|
1892
|
+
(st) => st.source_trace_id === trace.source_trace_id
|
|
1893
|
+
);
|
|
1894
|
+
const expectedPorts = sourceTrace ? pcbPorts.filter(
|
|
1895
|
+
(port) => sourceTrace.connected_source_port_ids?.includes(port.source_port_id)
|
|
1896
|
+
) : [];
|
|
1897
|
+
for (let i = 1; i < trace.route.length - 1; i++) {
|
|
1898
|
+
const prevPoint = trace.route[i - 1];
|
|
1899
|
+
const currentPoint = trace.route[i];
|
|
1900
|
+
const nextPoint = trace.route[i + 1];
|
|
1901
|
+
if (currentPoint.route_type === "via") {
|
|
1902
|
+
const prevIsWire = prevPoint.route_type === "wire";
|
|
1903
|
+
const nextIsWire = nextPoint.route_type === "wire";
|
|
1904
|
+
if (prevIsWire && nextIsWire) {
|
|
1905
|
+
const prevAligned = Math.abs(prevPoint.x - currentPoint.x) < 0.01 && Math.abs(prevPoint.y - currentPoint.y) < 0.01;
|
|
1906
|
+
const nextAligned = Math.abs(nextPoint.x - currentPoint.x) < 0.01 && Math.abs(nextPoint.y - currentPoint.y) < 0.01;
|
|
1907
|
+
if (!prevAligned || !nextAligned) {
|
|
1908
|
+
const traceName2 = getReadableNameForPcbTrace(
|
|
1909
|
+
circuitJson,
|
|
1910
|
+
trace.pcb_trace_id
|
|
1911
|
+
);
|
|
1912
|
+
errors.push({
|
|
1913
|
+
type: "pcb_trace_error",
|
|
1914
|
+
message: `Via in trace [${traceName2}] is misaligned at position {x: ${currentPoint.x}, y: ${currentPoint.y}}.`,
|
|
1915
|
+
source_trace_id: sourceTrace?.source_trace_id || trace.source_trace_id || `!${trace.pcb_trace_id}`,
|
|
1916
|
+
error_type: "pcb_trace_error",
|
|
1917
|
+
pcb_trace_id: trace.pcb_trace_id,
|
|
1918
|
+
pcb_trace_error_id: `misaligned_via_${trace.pcb_trace_id}_${i}`,
|
|
1919
|
+
pcb_component_ids: [],
|
|
1920
|
+
pcb_port_ids: []
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
const traceName = getReadableNameForPcbTrace(
|
|
1927
|
+
circuitJson,
|
|
1928
|
+
trace.pcb_trace_id
|
|
1929
|
+
);
|
|
1930
|
+
for (const port of expectedPorts) {
|
|
1931
|
+
if (!port.pcb_port_id) continue;
|
|
1932
|
+
const pads = padMap.get(port.pcb_port_id);
|
|
1933
|
+
if (!pads?.length) continue;
|
|
1934
|
+
let isConnectedByRoutedSourceTrace = false;
|
|
1935
|
+
for (const candidateTrace of pcbConnectivityMap.getAllTracesConnectedToTrace(
|
|
1936
|
+
trace.pcb_trace_id
|
|
1937
|
+
)) {
|
|
1938
|
+
if (candidateTrace.pcb_trace_id === trace.pcb_trace_id) continue;
|
|
1939
|
+
if (!candidateTrace.source_trace_id) continue;
|
|
1940
|
+
const candidateSourceTrace = sourceTraces.find(
|
|
1941
|
+
(st) => st.source_trace_id === candidateTrace.source_trace_id
|
|
1831
1942
|
);
|
|
1832
|
-
if (
|
|
1833
|
-
|
|
1943
|
+
if (!sourceTrace || candidateTrace.source_trace_id !== sourceTrace.source_trace_id && !candidateSourceTrace?.connected_source_port_ids.includes(
|
|
1944
|
+
port.source_port_id
|
|
1945
|
+
)) {
|
|
1946
|
+
continue;
|
|
1947
|
+
}
|
|
1948
|
+
if (getPcbPortIdsConnectedToTrace(candidateTrace).includes(
|
|
1949
|
+
port.pcb_port_id
|
|
1950
|
+
)) {
|
|
1951
|
+
isConnectedByRoutedSourceTrace = true;
|
|
1952
|
+
break;
|
|
1834
1953
|
}
|
|
1835
1954
|
}
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1955
|
+
if (isConnectedByRoutedSourceTrace) {
|
|
1956
|
+
continue;
|
|
1957
|
+
}
|
|
1958
|
+
const isFirstPointConnected = firstPoint.route_type === "wire" && pads.some(
|
|
1959
|
+
(pad) => isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad)
|
|
1960
|
+
);
|
|
1961
|
+
const isLastPointConnected = lastPoint.route_type === "wire" && pads.some(
|
|
1962
|
+
(pad) => isPointInPad({ x: lastPoint.x, y: lastPoint.y }, pad)
|
|
1963
|
+
);
|
|
1964
|
+
if (!isFirstPointConnected && !isLastPointConnected) {
|
|
1965
|
+
const portName = getReadableNameForPcbPort2(
|
|
1966
|
+
circuitJson,
|
|
1967
|
+
port.pcb_port_id
|
|
1968
|
+
).replace("pcb_port", "");
|
|
1969
|
+
const padType = pads[0].type.replace(/pcb_/, "");
|
|
1970
|
+
const errorCenter = getMissingConnectionErrorCenter({
|
|
1971
|
+
firstPoint,
|
|
1972
|
+
lastPoint,
|
|
1973
|
+
port,
|
|
1974
|
+
expectedPorts,
|
|
1975
|
+
padMap
|
|
1976
|
+
});
|
|
1977
|
+
errors.push({
|
|
1839
1978
|
type: "pcb_trace_error",
|
|
1979
|
+
message: `Trace [${traceName}] is missing a connection to ${padType}${portName}`,
|
|
1980
|
+
source_trace_id: sourceTrace?.source_trace_id || trace.source_trace_id || `!${trace.pcb_trace_id}`,
|
|
1840
1981
|
error_type: "pcb_trace_error",
|
|
1841
|
-
pcb_trace_error_id: `trace_too_close_to_board_${trace.pcb_trace_id}_segment_${i}`,
|
|
1842
|
-
message: `Trace too close to board edge (${minDistance.toFixed(3)}mm < ${minimumDistance.toFixed(3)}mm required, margin: ${margin}mm)`,
|
|
1843
1982
|
pcb_trace_id: trace.pcb_trace_id,
|
|
1844
|
-
|
|
1845
|
-
center:
|
|
1846
|
-
x: (segmentStart.x + segmentEnd.x) / 2,
|
|
1847
|
-
y: (segmentStart.y + segmentEnd.y) / 2
|
|
1848
|
-
},
|
|
1983
|
+
pcb_trace_error_id: `missing_connection_${trace.pcb_trace_id}_${port.pcb_port_id}`,
|
|
1984
|
+
center: errorCenter,
|
|
1849
1985
|
pcb_component_ids: [],
|
|
1850
|
-
pcb_port_ids: []
|
|
1851
|
-
};
|
|
1852
|
-
|
|
1853
|
-
|
|
1986
|
+
pcb_port_ids: [port.pcb_port_id]
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
if (expectedPorts.length === 0) {
|
|
1991
|
+
let firstConnectsToAnyPad = false;
|
|
1992
|
+
let lastConnectsToAnyPad = false;
|
|
1993
|
+
const endpointsAreSame = firstPoint.route_type === "wire" && lastPoint.route_type === "wire" && Math.abs(firstPoint.x - lastPoint.x) < 0.01 && Math.abs(firstPoint.y - lastPoint.y) < 0.01;
|
|
1994
|
+
for (const pads of padMap.values()) {
|
|
1995
|
+
if (firstPoint.route_type === "wire" && pads.some(
|
|
1996
|
+
(pad) => isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad)
|
|
1997
|
+
)) {
|
|
1998
|
+
firstConnectsToAnyPad = true;
|
|
1999
|
+
}
|
|
2000
|
+
if (lastPoint.route_type === "wire" && pads.some(
|
|
2001
|
+
(pad) => isPointInPad({ x: lastPoint.x, y: lastPoint.y }, pad)
|
|
2002
|
+
)) {
|
|
2003
|
+
lastConnectsToAnyPad = true;
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
if (!firstConnectsToAnyPad && firstPoint.route_type === "wire") {
|
|
2007
|
+
errors.push({
|
|
2008
|
+
type: "pcb_trace_error",
|
|
2009
|
+
message: `Trace [${traceName}] has disconnected endpoint at (${firstPoint.x.toFixed(2)}, ${firstPoint.y.toFixed(2)})`,
|
|
2010
|
+
source_trace_id: sourceTrace?.source_trace_id || trace.source_trace_id || `!${trace.pcb_trace_id}`,
|
|
2011
|
+
error_type: "pcb_trace_error",
|
|
2012
|
+
pcb_trace_id: trace.pcb_trace_id,
|
|
2013
|
+
pcb_trace_error_id: `disconnected_endpoint_${trace.pcb_trace_id}_start`,
|
|
2014
|
+
center: { x: firstPoint.x, y: firstPoint.y },
|
|
2015
|
+
pcb_component_ids: [],
|
|
2016
|
+
pcb_port_ids: []
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
if (!lastConnectsToAnyPad && lastPoint.route_type === "wire" && !(endpointsAreSame && !firstConnectsToAnyPad)) {
|
|
2020
|
+
errors.push({
|
|
2021
|
+
type: "pcb_trace_error",
|
|
2022
|
+
message: `Trace [${traceName}] has disconnected endpoint at (${lastPoint.x.toFixed(2)}, ${lastPoint.y.toFixed(2)})`,
|
|
2023
|
+
source_trace_id: sourceTrace?.source_trace_id || trace.source_trace_id || `!${trace.pcb_trace_id}`,
|
|
2024
|
+
error_type: "pcb_trace_error",
|
|
2025
|
+
pcb_trace_id: trace.pcb_trace_id,
|
|
2026
|
+
pcb_trace_error_id: `disconnected_endpoint_${trace.pcb_trace_id}_end`,
|
|
2027
|
+
center: { x: lastPoint.x, y: lastPoint.y },
|
|
2028
|
+
pcb_component_ids: [],
|
|
2029
|
+
pcb_port_ids: []
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
return errors;
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
// lib/check-trace-out-of-board/checkTraceOutOfBoard.ts
|
|
2038
|
+
import { cju as cju3 } from "@tscircuit/circuit-json-util";
|
|
2039
|
+
import { segmentToSegmentMinDistance as segmentToSegmentMinDistance3 } from "@tscircuit/math-utils";
|
|
2040
|
+
function getBoardPolygonPoints(board) {
|
|
2041
|
+
if (board.outline && board.outline.length > 0) {
|
|
2042
|
+
return board.outline.map((p) => ({ x: p.x, y: p.y }));
|
|
2043
|
+
}
|
|
2044
|
+
if (board.center && typeof board.width === "number" && typeof board.height === "number") {
|
|
2045
|
+
const cx = board.center.x;
|
|
2046
|
+
const cy = board.center.y;
|
|
2047
|
+
const hw = board.width / 2;
|
|
2048
|
+
const hh = board.height / 2;
|
|
2049
|
+
return [
|
|
2050
|
+
{ x: cx - hw, y: cy - hh },
|
|
2051
|
+
// bottom-left
|
|
2052
|
+
{ x: cx + hw, y: cy - hh },
|
|
2053
|
+
// bottom-right
|
|
2054
|
+
{ x: cx + hw, y: cy + hh },
|
|
2055
|
+
// top-right
|
|
2056
|
+
{ x: cx - hw, y: cy + hh }
|
|
2057
|
+
// top-left
|
|
2058
|
+
];
|
|
2059
|
+
}
|
|
2060
|
+
return null;
|
|
2061
|
+
}
|
|
2062
|
+
function checkPcbTracesOutOfBoard(circuitJson, config = {}) {
|
|
2063
|
+
const errors = [];
|
|
2064
|
+
const board = getPcbBoard(circuitJson);
|
|
2065
|
+
if (!board) return errors;
|
|
2066
|
+
const margin = config.margin ?? getBoardDrcValue(board, "min_board_edge_clearance") ?? jlcMinTolerances.min_board_edge_clearance;
|
|
2067
|
+
const boardPoints = getBoardPolygonPoints(board);
|
|
2068
|
+
if (!boardPoints) return errors;
|
|
2069
|
+
const pcbTraces = cju3(circuitJson).pcb_trace.list();
|
|
2070
|
+
for (const trace of pcbTraces) {
|
|
2071
|
+
if (trace.route.length < 2) continue;
|
|
2072
|
+
for (let i = 0; i < trace.route.length - 1; i++) {
|
|
2073
|
+
const p1 = trace.route[i];
|
|
2074
|
+
const p2 = trace.route[i + 1];
|
|
2075
|
+
if (p1.route_type !== "wire" || p2.route_type !== "wire") continue;
|
|
2076
|
+
const traceWidth = "width" in p1 ? p1.width : "width" in p2 ? p2.width : 0.1;
|
|
2077
|
+
const segmentStart = { x: p1.x, y: p1.y };
|
|
2078
|
+
const segmentEnd = { x: p2.x, y: p2.y };
|
|
2079
|
+
let minDistance = Number.POSITIVE_INFINITY;
|
|
2080
|
+
for (let j = 0; j < boardPoints.length; j++) {
|
|
2081
|
+
const edgeStart = boardPoints[j];
|
|
2082
|
+
const edgeEnd = boardPoints[(j + 1) % boardPoints.length];
|
|
2083
|
+
const distance3 = segmentToSegmentMinDistance3(
|
|
2084
|
+
segmentStart,
|
|
2085
|
+
segmentEnd,
|
|
2086
|
+
edgeStart,
|
|
2087
|
+
edgeEnd
|
|
2088
|
+
);
|
|
2089
|
+
if (distance3 < minDistance) {
|
|
2090
|
+
minDistance = distance3;
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
const minimumDistance = traceWidth / 2 + margin;
|
|
2094
|
+
if (minDistance < minimumDistance) {
|
|
2095
|
+
const error = {
|
|
2096
|
+
type: "pcb_trace_error",
|
|
2097
|
+
error_type: "pcb_trace_error",
|
|
2098
|
+
pcb_trace_error_id: `trace_too_close_to_board_${trace.pcb_trace_id}_segment_${i}`,
|
|
2099
|
+
message: `Trace too close to board edge (${minDistance.toFixed(3)}mm < ${minimumDistance.toFixed(3)}mm required, margin: ${margin}mm)`,
|
|
2100
|
+
pcb_trace_id: trace.pcb_trace_id,
|
|
2101
|
+
source_trace_id: trace.source_trace_id || "",
|
|
2102
|
+
center: {
|
|
2103
|
+
x: (segmentStart.x + segmentEnd.x) / 2,
|
|
2104
|
+
y: (segmentStart.y + segmentEnd.y) / 2
|
|
2105
|
+
},
|
|
2106
|
+
pcb_component_ids: [],
|
|
2107
|
+
pcb_port_ids: []
|
|
2108
|
+
};
|
|
2109
|
+
errors.push(error);
|
|
2110
|
+
}
|
|
1854
2111
|
}
|
|
1855
2112
|
}
|
|
1856
2113
|
return errors;
|
|
@@ -2784,263 +3041,6 @@ function checkCourtyardOverlap(circuitJson) {
|
|
|
2784
3041
|
return errors;
|
|
2785
3042
|
}
|
|
2786
3043
|
|
|
2787
|
-
// lib/check-traces-are-contiguous/check-traces-are-contiguous.ts
|
|
2788
|
-
import {
|
|
2789
|
-
getReadableNameForPcbPort as getReadableNameForPcbPort2,
|
|
2790
|
-
getReadableNameForPcbTrace
|
|
2791
|
-
} from "@tscircuit/circuit-json-util";
|
|
2792
|
-
import { PcbConnectivityMap } from "circuit-json-to-connectivity-map";
|
|
2793
|
-
function getRoutePointCenter(point) {
|
|
2794
|
-
if (point.route_type === "through_pad") {
|
|
2795
|
-
return {
|
|
2796
|
-
x: (point.start.x + point.end.x) / 2,
|
|
2797
|
-
y: (point.start.y + point.end.y) / 2
|
|
2798
|
-
};
|
|
2799
|
-
}
|
|
2800
|
-
return { x: point.x, y: point.y };
|
|
2801
|
-
}
|
|
2802
|
-
function routePointConnectsToAnotherExpectedPort(point, expectedPorts, missingPcbPortId, padMap) {
|
|
2803
|
-
if (point.route_type !== "wire") return false;
|
|
2804
|
-
return expectedPorts.some((expectedPort) => {
|
|
2805
|
-
if (!expectedPort.pcb_port_id || expectedPort.pcb_port_id === missingPcbPortId) {
|
|
2806
|
-
return false;
|
|
2807
|
-
}
|
|
2808
|
-
const expectedPads = padMap.get(expectedPort.pcb_port_id);
|
|
2809
|
-
return expectedPads?.some(
|
|
2810
|
-
(pad) => isPointInPad({ x: point.x, y: point.y }, pad)
|
|
2811
|
-
) ?? false;
|
|
2812
|
-
});
|
|
2813
|
-
}
|
|
2814
|
-
function getMissingConnectionErrorCenter({
|
|
2815
|
-
firstPoint,
|
|
2816
|
-
lastPoint,
|
|
2817
|
-
port,
|
|
2818
|
-
expectedPorts,
|
|
2819
|
-
padMap
|
|
2820
|
-
}) {
|
|
2821
|
-
let errorLocation;
|
|
2822
|
-
const firstWirePoint = firstPoint.route_type === "wire" ? firstPoint : void 0;
|
|
2823
|
-
const lastWirePoint = lastPoint.route_type === "wire" ? lastPoint : void 0;
|
|
2824
|
-
const firstWirePointReferencesPort = getPcbPortIdsConnectedToRoutePoint(
|
|
2825
|
-
firstPoint
|
|
2826
|
-
).includes(port.pcb_port_id);
|
|
2827
|
-
const lastWirePointReferencesPort = getPcbPortIdsConnectedToRoutePoint(
|
|
2828
|
-
lastPoint
|
|
2829
|
-
).includes(port.pcb_port_id);
|
|
2830
|
-
if (firstWirePointReferencesPort && firstWirePoint) {
|
|
2831
|
-
errorLocation = firstWirePoint;
|
|
2832
|
-
} else if (lastWirePointReferencesPort && lastWirePoint) {
|
|
2833
|
-
errorLocation = lastWirePoint;
|
|
2834
|
-
} else if (routePointConnectsToAnotherExpectedPort(
|
|
2835
|
-
firstPoint,
|
|
2836
|
-
expectedPorts,
|
|
2837
|
-
port.pcb_port_id,
|
|
2838
|
-
padMap
|
|
2839
|
-
) && lastWirePoint) {
|
|
2840
|
-
errorLocation = lastWirePoint;
|
|
2841
|
-
} else if (routePointConnectsToAnotherExpectedPort(
|
|
2842
|
-
lastPoint,
|
|
2843
|
-
expectedPorts,
|
|
2844
|
-
port.pcb_port_id,
|
|
2845
|
-
padMap
|
|
2846
|
-
) && firstWirePoint) {
|
|
2847
|
-
errorLocation = firstWirePoint;
|
|
2848
|
-
} else if (firstWirePoint && lastWirePoint) {
|
|
2849
|
-
errorLocation = distance2(firstWirePoint, port) <= distance2(lastWirePoint, port) ? firstWirePoint : lastWirePoint;
|
|
2850
|
-
} else if (firstWirePoint) {
|
|
2851
|
-
errorLocation = firstWirePoint;
|
|
2852
|
-
} else if (lastWirePoint) {
|
|
2853
|
-
errorLocation = lastWirePoint;
|
|
2854
|
-
}
|
|
2855
|
-
const firstPointCenter = getRoutePointCenter(firstPoint);
|
|
2856
|
-
const lastPointCenter = getRoutePointCenter(lastPoint);
|
|
2857
|
-
return errorLocation ? { x: errorLocation.x, y: errorLocation.y } : {
|
|
2858
|
-
x: (firstPointCenter.x + lastPointCenter.x) / 2,
|
|
2859
|
-
y: (firstPointCenter.y + lastPointCenter.y) / 2
|
|
2860
|
-
};
|
|
2861
|
-
}
|
|
2862
|
-
function checkTracesAreContiguous(circuitJson) {
|
|
2863
|
-
const errors = [];
|
|
2864
|
-
const pcbPorts = circuitJson.filter(
|
|
2865
|
-
(el) => el.type === "pcb_port"
|
|
2866
|
-
);
|
|
2867
|
-
const pcbTraces = circuitJson.filter(
|
|
2868
|
-
(el) => el.type === "pcb_trace"
|
|
2869
|
-
);
|
|
2870
|
-
const sourceTraces = circuitJson.filter(
|
|
2871
|
-
(el) => el.type === "source_trace"
|
|
2872
|
-
);
|
|
2873
|
-
const pcbSmtPads = circuitJson.filter(
|
|
2874
|
-
(el) => el.type === "pcb_smtpad"
|
|
2875
|
-
);
|
|
2876
|
-
const pcbPlatedHoles = circuitJson.filter(
|
|
2877
|
-
(el) => el.type === "pcb_plated_hole"
|
|
2878
|
-
);
|
|
2879
|
-
const padMap = /* @__PURE__ */ new Map();
|
|
2880
|
-
const pcbConnectivityMap = new PcbConnectivityMap(circuitJson);
|
|
2881
|
-
for (const pad of pcbSmtPads) {
|
|
2882
|
-
if (pad.pcb_port_id) {
|
|
2883
|
-
padMap.set(pad.pcb_port_id, [...padMap.get(pad.pcb_port_id) ?? [], pad]);
|
|
2884
|
-
}
|
|
2885
|
-
}
|
|
2886
|
-
for (const hole of pcbPlatedHoles) {
|
|
2887
|
-
if (hole.pcb_port_id) {
|
|
2888
|
-
padMap.set(hole.pcb_port_id, [
|
|
2889
|
-
...padMap.get(hole.pcb_port_id) ?? [],
|
|
2890
|
-
hole
|
|
2891
|
-
]);
|
|
2892
|
-
}
|
|
2893
|
-
}
|
|
2894
|
-
for (const trace of pcbTraces) {
|
|
2895
|
-
if (trace.route.length === 0) continue;
|
|
2896
|
-
const firstPoint = trace.route[0];
|
|
2897
|
-
const lastPoint = trace.route[trace.route.length - 1];
|
|
2898
|
-
const sourceTrace = sourceTraces.find(
|
|
2899
|
-
(st) => st.source_trace_id === trace.source_trace_id
|
|
2900
|
-
);
|
|
2901
|
-
const expectedPorts = sourceTrace ? pcbPorts.filter(
|
|
2902
|
-
(port) => sourceTrace.connected_source_port_ids?.includes(port.source_port_id)
|
|
2903
|
-
) : [];
|
|
2904
|
-
for (let i = 1; i < trace.route.length - 1; i++) {
|
|
2905
|
-
const prevPoint = trace.route[i - 1];
|
|
2906
|
-
const currentPoint = trace.route[i];
|
|
2907
|
-
const nextPoint = trace.route[i + 1];
|
|
2908
|
-
if (currentPoint.route_type === "via") {
|
|
2909
|
-
const prevIsWire = prevPoint.route_type === "wire";
|
|
2910
|
-
const nextIsWire = nextPoint.route_type === "wire";
|
|
2911
|
-
if (prevIsWire && nextIsWire) {
|
|
2912
|
-
const prevAligned = Math.abs(prevPoint.x - currentPoint.x) < 0.01 && Math.abs(prevPoint.y - currentPoint.y) < 0.01;
|
|
2913
|
-
const nextAligned = Math.abs(nextPoint.x - currentPoint.x) < 0.01 && Math.abs(nextPoint.y - currentPoint.y) < 0.01;
|
|
2914
|
-
if (!prevAligned || !nextAligned) {
|
|
2915
|
-
const traceName2 = getReadableNameForPcbTrace(
|
|
2916
|
-
circuitJson,
|
|
2917
|
-
trace.pcb_trace_id
|
|
2918
|
-
);
|
|
2919
|
-
errors.push({
|
|
2920
|
-
type: "pcb_trace_error",
|
|
2921
|
-
message: `Via in trace [${traceName2}] is misaligned at position {x: ${currentPoint.x}, y: ${currentPoint.y}}.`,
|
|
2922
|
-
source_trace_id: sourceTrace?.source_trace_id || trace.source_trace_id || `!${trace.pcb_trace_id}`,
|
|
2923
|
-
error_type: "pcb_trace_error",
|
|
2924
|
-
pcb_trace_id: trace.pcb_trace_id,
|
|
2925
|
-
pcb_trace_error_id: `misaligned_via_${trace.pcb_trace_id}_${i}`,
|
|
2926
|
-
pcb_component_ids: [],
|
|
2927
|
-
pcb_port_ids: []
|
|
2928
|
-
});
|
|
2929
|
-
}
|
|
2930
|
-
}
|
|
2931
|
-
}
|
|
2932
|
-
}
|
|
2933
|
-
const traceName = getReadableNameForPcbTrace(
|
|
2934
|
-
circuitJson,
|
|
2935
|
-
trace.pcb_trace_id
|
|
2936
|
-
);
|
|
2937
|
-
for (const port of expectedPorts) {
|
|
2938
|
-
if (!port.pcb_port_id) continue;
|
|
2939
|
-
const pads = padMap.get(port.pcb_port_id);
|
|
2940
|
-
if (!pads?.length) continue;
|
|
2941
|
-
let isConnectedByRoutedSourceTrace = false;
|
|
2942
|
-
for (const candidateTrace of pcbConnectivityMap.getAllTracesConnectedToTrace(
|
|
2943
|
-
trace.pcb_trace_id
|
|
2944
|
-
)) {
|
|
2945
|
-
if (candidateTrace.pcb_trace_id === trace.pcb_trace_id) continue;
|
|
2946
|
-
if (!candidateTrace.source_trace_id) continue;
|
|
2947
|
-
const candidateSourceTrace = sourceTraces.find(
|
|
2948
|
-
(st) => st.source_trace_id === candidateTrace.source_trace_id
|
|
2949
|
-
);
|
|
2950
|
-
if (!sourceTrace || candidateTrace.source_trace_id !== sourceTrace.source_trace_id && !candidateSourceTrace?.connected_source_port_ids.includes(
|
|
2951
|
-
port.source_port_id
|
|
2952
|
-
)) {
|
|
2953
|
-
continue;
|
|
2954
|
-
}
|
|
2955
|
-
if (getPcbPortIdsConnectedToTrace(candidateTrace).includes(
|
|
2956
|
-
port.pcb_port_id
|
|
2957
|
-
)) {
|
|
2958
|
-
isConnectedByRoutedSourceTrace = true;
|
|
2959
|
-
break;
|
|
2960
|
-
}
|
|
2961
|
-
}
|
|
2962
|
-
if (isConnectedByRoutedSourceTrace) {
|
|
2963
|
-
continue;
|
|
2964
|
-
}
|
|
2965
|
-
const isFirstPointConnected = firstPoint.route_type === "wire" && pads.some(
|
|
2966
|
-
(pad) => isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad)
|
|
2967
|
-
);
|
|
2968
|
-
const isLastPointConnected = lastPoint.route_type === "wire" && pads.some(
|
|
2969
|
-
(pad) => isPointInPad({ x: lastPoint.x, y: lastPoint.y }, pad)
|
|
2970
|
-
);
|
|
2971
|
-
if (!isFirstPointConnected && !isLastPointConnected) {
|
|
2972
|
-
const portName = getReadableNameForPcbPort2(
|
|
2973
|
-
circuitJson,
|
|
2974
|
-
port.pcb_port_id
|
|
2975
|
-
).replace("pcb_port", "");
|
|
2976
|
-
const padType = pads[0].type.replace(/pcb_/, "");
|
|
2977
|
-
const errorCenter = getMissingConnectionErrorCenter({
|
|
2978
|
-
firstPoint,
|
|
2979
|
-
lastPoint,
|
|
2980
|
-
port,
|
|
2981
|
-
expectedPorts,
|
|
2982
|
-
padMap
|
|
2983
|
-
});
|
|
2984
|
-
errors.push({
|
|
2985
|
-
type: "pcb_trace_error",
|
|
2986
|
-
message: `Trace [${traceName}] is missing a connection to ${padType}${portName}`,
|
|
2987
|
-
source_trace_id: sourceTrace?.source_trace_id || trace.source_trace_id || `!${trace.pcb_trace_id}`,
|
|
2988
|
-
error_type: "pcb_trace_error",
|
|
2989
|
-
pcb_trace_id: trace.pcb_trace_id,
|
|
2990
|
-
pcb_trace_error_id: `missing_connection_${trace.pcb_trace_id}_${port.pcb_port_id}`,
|
|
2991
|
-
center: errorCenter,
|
|
2992
|
-
pcb_component_ids: [],
|
|
2993
|
-
pcb_port_ids: [port.pcb_port_id]
|
|
2994
|
-
});
|
|
2995
|
-
}
|
|
2996
|
-
}
|
|
2997
|
-
if (expectedPorts.length === 0) {
|
|
2998
|
-
let firstConnectsToAnyPad = false;
|
|
2999
|
-
let lastConnectsToAnyPad = false;
|
|
3000
|
-
const endpointsAreSame = firstPoint.route_type === "wire" && lastPoint.route_type === "wire" && Math.abs(firstPoint.x - lastPoint.x) < 0.01 && Math.abs(firstPoint.y - lastPoint.y) < 0.01;
|
|
3001
|
-
for (const pads of padMap.values()) {
|
|
3002
|
-
if (firstPoint.route_type === "wire" && pads.some(
|
|
3003
|
-
(pad) => isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad)
|
|
3004
|
-
)) {
|
|
3005
|
-
firstConnectsToAnyPad = true;
|
|
3006
|
-
}
|
|
3007
|
-
if (lastPoint.route_type === "wire" && pads.some(
|
|
3008
|
-
(pad) => isPointInPad({ x: lastPoint.x, y: lastPoint.y }, pad)
|
|
3009
|
-
)) {
|
|
3010
|
-
lastConnectsToAnyPad = true;
|
|
3011
|
-
}
|
|
3012
|
-
}
|
|
3013
|
-
if (!firstConnectsToAnyPad && firstPoint.route_type === "wire") {
|
|
3014
|
-
errors.push({
|
|
3015
|
-
type: "pcb_trace_error",
|
|
3016
|
-
message: `Trace [${traceName}] has disconnected endpoint at (${firstPoint.x.toFixed(2)}, ${firstPoint.y.toFixed(2)})`,
|
|
3017
|
-
source_trace_id: sourceTrace?.source_trace_id || trace.source_trace_id || `!${trace.pcb_trace_id}`,
|
|
3018
|
-
error_type: "pcb_trace_error",
|
|
3019
|
-
pcb_trace_id: trace.pcb_trace_id,
|
|
3020
|
-
pcb_trace_error_id: `disconnected_endpoint_${trace.pcb_trace_id}_start`,
|
|
3021
|
-
center: { x: firstPoint.x, y: firstPoint.y },
|
|
3022
|
-
pcb_component_ids: [],
|
|
3023
|
-
pcb_port_ids: []
|
|
3024
|
-
});
|
|
3025
|
-
}
|
|
3026
|
-
if (!lastConnectsToAnyPad && lastPoint.route_type === "wire" && !(endpointsAreSame && !firstConnectsToAnyPad)) {
|
|
3027
|
-
errors.push({
|
|
3028
|
-
type: "pcb_trace_error",
|
|
3029
|
-
message: `Trace [${traceName}] has disconnected endpoint at (${lastPoint.x.toFixed(2)}, ${lastPoint.y.toFixed(2)})`,
|
|
3030
|
-
source_trace_id: sourceTrace?.source_trace_id || trace.source_trace_id || `!${trace.pcb_trace_id}`,
|
|
3031
|
-
error_type: "pcb_trace_error",
|
|
3032
|
-
pcb_trace_id: trace.pcb_trace_id,
|
|
3033
|
-
pcb_trace_error_id: `disconnected_endpoint_${trace.pcb_trace_id}_end`,
|
|
3034
|
-
center: { x: lastPoint.x, y: lastPoint.y },
|
|
3035
|
-
pcb_component_ids: [],
|
|
3036
|
-
pcb_port_ids: []
|
|
3037
|
-
});
|
|
3038
|
-
}
|
|
3039
|
-
}
|
|
3040
|
-
}
|
|
3041
|
-
return errors;
|
|
3042
|
-
}
|
|
3043
|
-
|
|
3044
3044
|
// lib/run-all-checks.ts
|
|
3045
3045
|
async function runAllPlacementChecks(circuitJson) {
|
|
3046
3046
|
return [
|
|
@@ -3101,6 +3101,7 @@ export {
|
|
|
3101
3101
|
checkSameNetViaSpacing,
|
|
3102
3102
|
checkSourceTracesHavePcbTraces,
|
|
3103
3103
|
checkSourceTracesMatchPcbTraceThickness,
|
|
3104
|
+
checkTracesAreContiguous,
|
|
3104
3105
|
checkViaTraceClearance,
|
|
3105
3106
|
checkViasOffBoard,
|
|
3106
3107
|
runAllChecks,
|