@tscircuit/checks 0.0.139 → 0.0.141

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.js CHANGED
@@ -1129,7 +1129,7 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1129
1129
  ].filter(Boolean)
1130
1130
  });
1131
1131
  }
1132
- if (obj.type === "pcb_smtpad" && obj.shape === "rotated_pill") {
1132
+ if (obj.type === "pcb_smtpad" && (obj.shape === "pill" || obj.shape === "rotated_pill")) {
1133
1133
  const { distance: distance3, center, radius } = getSegmentToPillClearance(
1134
1134
  segmentA,
1135
1135
  obj
@@ -1777,80 +1777,337 @@ function checkSourceTracesHavePcbTraces(circuitJson) {
1777
1777
  return errors;
1778
1778
  }
1779
1779
 
1780
- // lib/check-trace-out-of-board/checkTraceOutOfBoard.ts
1781
- import { cju as cju3 } from "@tscircuit/circuit-json-util";
1782
- import { segmentToSegmentMinDistance as segmentToSegmentMinDistance3 } from "@tscircuit/math-utils";
1783
- function getBoardPolygonPoints(board) {
1784
- if (board.outline && board.outline.length > 0) {
1785
- return board.outline.map((p) => ({ x: p.x, y: p.y }));
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
- if (board.center && typeof board.width === "number" && typeof board.height === "number") {
1788
- const cx = board.center.x;
1789
- const cy = board.center.y;
1790
- const hw = board.width / 2;
1791
- const hh = board.height / 2;
1792
- return [
1793
- { x: cx - hw, y: cy - hh },
1794
- // bottom-left
1795
- { x: cx + hw, y: cy - hh },
1796
- // bottom-right
1797
- { x: cx + hw, y: cy + hh },
1798
- // top-right
1799
- { x: cx - hw, y: cy + hh }
1800
- // top-left
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
- return null;
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 checkPcbTracesOutOfBoard(circuitJson, config = {}) {
1855
+ function checkTracesAreContiguous(circuitJson) {
1806
1856
  const errors = [];
1807
- const board = getPcbBoard(circuitJson);
1808
- if (!board) return errors;
1809
- const margin = config.margin ?? getBoardDrcValue(board, "min_board_edge_clearance") ?? jlcMinTolerances.min_board_edge_clearance;
1810
- const boardPoints = getBoardPolygonPoints(board);
1811
- if (!boardPoints) return errors;
1812
- const pcbTraces = cju3(circuitJson).pcb_trace.list();
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 < 2) continue;
1815
- for (let i = 0; i < trace.route.length - 1; i++) {
1816
- const p1 = trace.route[i];
1817
- const p2 = trace.route[i + 1];
1818
- if (p1.route_type !== "wire" || p2.route_type !== "wire") continue;
1819
- const traceWidth = "width" in p1 ? p1.width : "width" in p2 ? p2.width : 0.1;
1820
- const segmentStart = { x: p1.x, y: p1.y };
1821
- const segmentEnd = { x: p2.x, y: p2.y };
1822
- let minDistance = Number.POSITIVE_INFINITY;
1823
- for (let j = 0; j < boardPoints.length; j++) {
1824
- const edgeStart = boardPoints[j];
1825
- const edgeEnd = boardPoints[(j + 1) % boardPoints.length];
1826
- const distance3 = segmentToSegmentMinDistance3(
1827
- segmentStart,
1828
- segmentEnd,
1829
- edgeStart,
1830
- edgeEnd
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 (distance3 < minDistance) {
1833
- minDistance = distance3;
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
- const minimumDistance = traceWidth / 2 + margin;
1837
- if (minDistance < minimumDistance) {
1838
- const error = {
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
- source_trace_id: trace.source_trace_id || "",
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
- errors.push(error);
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;
@@ -2267,7 +2524,7 @@ function checkPadTraceClearance(circuitJson, {
2267
2524
  if (!getLayersOfPcbElement(pad).includes(segment.layer)) continue;
2268
2525
  if (connMap.areIdsConnected(segment.pcb_trace_id, padId)) continue;
2269
2526
  const center = getPadCenter(pad);
2270
- const gap = pad.type === "pcb_smtpad" && pad.shape === "rotated_pill" ? (() => {
2527
+ const gap = pad.type === "pcb_smtpad" && (pad.shape === "pill" || pad.shape === "rotated_pill") ? (() => {
2271
2528
  const { distance: distance3, radius } = getSegmentToPillClearance(
2272
2529
  segment,
2273
2530
  pad
@@ -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,