@tscircuit/checks 0.0.142 → 0.0.144

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 CHANGED
@@ -78,6 +78,15 @@ declare function checkViaTraceClearance(circuitJson: AnyCircuitElement[], { conn
78
78
  minClearance?: number;
79
79
  }): PcbViaTraceClearanceError[];
80
80
 
81
+ /**
82
+ * Removes generic pcb_trace_error records when a more specific pad-trace or
83
+ * via-trace clearance error describes the same physical pair.
84
+ *
85
+ * The specific record is retained because it includes the measured and
86
+ * required clearance. Trace-trace and all other generic errors are untouched.
87
+ */
88
+ declare const dedupePcbDrcErrors: <T extends AnyCircuitElement>(errors: T[]) => T[];
89
+
81
90
  type SourcePinMustBeConnectedError = {
82
91
  type: "source_pin_must_be_connected_error";
83
92
  source_pin_must_be_connected_error_id: string;
@@ -137,4 +146,4 @@ declare function runAllChecks(circuitJson: AnyCircuitElement[]): Promise<(circui
137
146
 
138
147
  declare function checkConnectorAccessibleOrientation(circuitJson: AnyCircuitElement[]): PcbConnectorNotInAccessibleOrientationWarning[];
139
148
 
140
- export { NetManager, checkAllPinsInComponentAreUnderspecified, checkConnectorAccessibleOrientation, checkDifferentNetViaSpacing, checkEachPcbPortConnectedToPcbTraces, checkEachPcbTraceNonOverlapping, checkNoGroundPinDefined, checkNoPowerPinDefined, checkPadPadClearance, checkPadTraceClearance, checkPcbComponentOverlap, checkPcbComponentsOutOfBoard, checkPcbTracesOutOfBoard, checkPinMustBeConnected, checkSameNetViaSpacing, checkSourceTracesHavePcbTraces, checkSourceTracesMatchPcbTraceThickness, checkTracesAreContiguous, checkViaTraceClearance, checkViasOffBoard, runAllChecks, runAllNetlistChecks, runAllPinSpecificationChecks, runAllPlacementChecks, runAllRoutingChecks };
149
+ export { NetManager, checkAllPinsInComponentAreUnderspecified, checkConnectorAccessibleOrientation, checkDifferentNetViaSpacing, checkEachPcbPortConnectedToPcbTraces, checkEachPcbTraceNonOverlapping, checkNoGroundPinDefined, checkNoPowerPinDefined, checkPadPadClearance, checkPadTraceClearance, checkPcbComponentOverlap, checkPcbComponentsOutOfBoard, checkPcbTracesOutOfBoard, checkPinMustBeConnected, checkSameNetViaSpacing, checkSourceTracesHavePcbTraces, checkSourceTracesMatchPcbTraceThickness, checkTracesAreContiguous, checkViaTraceClearance, checkViasOffBoard, dedupePcbDrcErrors, runAllChecks, runAllNetlistChecks, runAllPinSpecificationChecks, runAllPlacementChecks, runAllRoutingChecks };
package/dist/index.js CHANGED
@@ -193,6 +193,17 @@ function isPointOnSegment(point, segment) {
193
193
  const squaredLength = (segment.end.x - segment.start.x) ** 2 + (segment.end.y - segment.start.y) ** 2;
194
194
  return dotProduct <= squaredLength + POINT_ON_SEGMENT_TOLERANCE_MM;
195
195
  }
196
+ function isPointInPolygon(point, polygon) {
197
+ let inside = false;
198
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
199
+ const pi = polygon[i];
200
+ const pj = polygon[j];
201
+ if (isPointOnSegment(point, { start: pi, end: pj })) return true;
202
+ const intersects = pi.y > point.y !== pj.y > point.y && point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x;
203
+ if (intersects) inside = !inside;
204
+ }
205
+ return inside;
206
+ }
196
207
  function isPointInPad(point, pad) {
197
208
  if (pad.type === "pcb_smtpad") {
198
209
  if (pad.shape === "circle") {
@@ -204,12 +215,7 @@ function isPointInPad(point, pad) {
204
215
  return Math.abs(point.x - pad.x) <= halfWidth && Math.abs(point.y - pad.y) <= halfHeight;
205
216
  }
206
217
  if (pad.shape === "rotated_rect") {
207
- const dx = point.x - pad.x;
208
- const dy = point.y - pad.y;
209
- const angle = -pad.ccw_rotation;
210
- const rotatedX = dx * Math.cos(angle) - dy * Math.sin(angle);
211
- const rotatedY = dx * Math.sin(angle) + dy * Math.cos(angle);
212
- return Math.abs(rotatedX) <= pad.width / 2 && Math.abs(rotatedY) <= pad.height / 2;
218
+ return isPointInPolygon(point, getPolygonPointsForPad(pad));
213
219
  }
214
220
  if (pad.shape === "pill" || pad.shape === "rotated_pill") {
215
221
  if (pad.shape === "rotated_pill") {
@@ -233,30 +239,19 @@ function isPointInPad(point, pad) {
233
239
  return cornerX * cornerX + cornerY * cornerY <= radius * radius;
234
240
  }
235
241
  if (pad.shape === "polygon") {
236
- let inside = false;
237
- for (let i = 0, j = pad.points.length - 1; i < pad.points.length; j = i++) {
238
- const pi = pad.points[i];
239
- const pj = pad.points[j];
240
- if (isPointOnSegment(point, { start: pi, end: pj })) return true;
241
- const intersects = pi.y > point.y !== pj.y > point.y && point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x;
242
- if (intersects) inside = !inside;
243
- }
244
- return inside;
242
+ return isPointInPolygon(point, pad.points);
245
243
  }
246
244
  }
247
245
  if (pad.type === "pcb_plated_hole") {
248
246
  if (pad.shape === "circle") {
249
247
  return getDistanceBetweenPoints(point, pad) <= pad.outer_diameter / 2;
250
248
  }
249
+ if ("rect_pad_width" in pad && "rect_pad_height" in pad) {
250
+ return isPointInPolygon(point, getPolygonPointsForPad(pad));
251
+ }
251
252
  if (pad.shape === "oval" || pad.shape === "pill") {
252
253
  return Math.abs(point.x - pad.x) <= pad.outer_width / 2 && Math.abs(point.y - pad.y) <= pad.outer_height / 2;
253
254
  }
254
- if (pad.shape === "circular_hole_with_rect_pad") {
255
- return Math.abs(point.x - pad.x) <= pad.rect_pad_width / 2 && Math.abs(point.y - pad.y) <= pad.rect_pad_height / 2;
256
- }
257
- if (pad.shape === "pill_hole_with_rect_pad") {
258
- return Math.abs(point.x - pad.x) <= pad.rect_pad_width / 2 && Math.abs(point.y - pad.y) <= pad.rect_pad_height / 2;
259
- }
260
255
  }
261
256
  return false;
262
257
  }
@@ -1871,6 +1866,7 @@ function checkTracesAreContiguous(circuitJson) {
1871
1866
  );
1872
1867
  const padMap = /* @__PURE__ */ new Map();
1873
1868
  const pcbConnectivityMap = new PcbConnectivityMap(circuitJson);
1869
+ const checkedSourceTraceIds = /* @__PURE__ */ new Set();
1874
1870
  for (const pad of pcbSmtPads) {
1875
1871
  if (pad.pcb_port_id) {
1876
1872
  padMap.set(pad.pcb_port_id, [...padMap.get(pad.pcb_port_id) ?? [], pad]);
@@ -1927,14 +1923,22 @@ function checkTracesAreContiguous(circuitJson) {
1927
1923
  circuitJson,
1928
1924
  trace.pcb_trace_id
1929
1925
  );
1926
+ if (sourceTrace && expectedPorts.length > 0) {
1927
+ if (checkedSourceTraceIds.has(sourceTrace.source_trace_id)) continue;
1928
+ checkedSourceTraceIds.add(sourceTrace.source_trace_id);
1929
+ }
1930
1930
  for (const port of expectedPorts) {
1931
1931
  if (!port.pcb_port_id) continue;
1932
1932
  const pads = padMap.get(port.pcb_port_id);
1933
1933
  if (!pads?.length) continue;
1934
1934
  let isConnectedByRoutedSourceTrace = false;
1935
- for (const candidateTrace of pcbConnectivityMap.getAllTracesConnectedToTrace(
1936
- trace.pcb_trace_id
1937
- )) {
1935
+ const candidateTraces = [
1936
+ ...pcbConnectivityMap.getAllTracesConnectedToTrace(trace.pcb_trace_id),
1937
+ ...pcbTraces.filter(
1938
+ (candidateTrace) => candidateTrace.source_trace_id === sourceTrace?.source_trace_id
1939
+ )
1940
+ ];
1941
+ for (const candidateTrace of candidateTraces) {
1938
1942
  if (candidateTrace.pcb_trace_id === trace.pcb_trace_id) continue;
1939
1943
  if (!candidateTrace.source_trace_id) continue;
1940
1944
  const candidateSourceTrace = sourceTraces.find(
@@ -1945,16 +1949,16 @@ function checkTracesAreContiguous(circuitJson) {
1945
1949
  )) {
1946
1950
  continue;
1947
1951
  }
1952
+ const candidateFirstPoint = candidateTrace.route[0];
1953
+ const candidateLastPoint = candidateTrace.route.at(-1);
1948
1954
  if (getPcbPortIdsConnectedToTrace(candidateTrace).includes(
1949
1955
  port.pcb_port_id
1950
- )) {
1956
+ ) || candidateFirstPoint?.route_type === "wire" && pads.some((pad) => isPointInPad(candidateFirstPoint, pad)) || candidateLastPoint?.route_type === "wire" && pads.some((pad) => isPointInPad(candidateLastPoint, pad))) {
1951
1957
  isConnectedByRoutedSourceTrace = true;
1952
1958
  break;
1953
1959
  }
1954
1960
  }
1955
- if (isConnectedByRoutedSourceTrace) {
1956
- continue;
1957
- }
1961
+ if (isConnectedByRoutedSourceTrace) continue;
1958
1962
  const isFirstPointConnected = firstPoint.route_type === "wire" && pads.some(
1959
1963
  (pad) => isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad)
1960
1964
  );
@@ -2643,6 +2647,27 @@ function checkViaTraceClearance(circuitJson, {
2643
2647
  return Array.from(errors.values()).map(({ error }) => error);
2644
2648
  }
2645
2649
 
2650
+ // lib/dedupe-pcb-drc-errors.ts
2651
+ var dedupePcbDrcErrors = (errors) => {
2652
+ const specificallyReportedPairIds = /* @__PURE__ */ new Set();
2653
+ for (const error of errors) {
2654
+ if (error.type === "pcb_pad_trace_clearance_error" && typeof error.pcb_trace_id === "string" && typeof error.pcb_pad_id === "string") {
2655
+ specificallyReportedPairIds.add(
2656
+ `overlap_${error.pcb_trace_id}_${error.pcb_pad_id}`
2657
+ );
2658
+ }
2659
+ if (error.type === "pcb_via_trace_clearance_error" && typeof error.pcb_trace_id === "string" && typeof error.pcb_via_id === "string") {
2660
+ specificallyReportedPairIds.add(
2661
+ `overlap_${error.pcb_trace_id}_${error.pcb_via_id}`
2662
+ );
2663
+ }
2664
+ }
2665
+ return errors.filter((element) => {
2666
+ const error = element;
2667
+ return !(error.type === "pcb_trace_error" && typeof error.pcb_trace_error_id === "string" && specificallyReportedPairIds.has(error.pcb_trace_error_id));
2668
+ });
2669
+ };
2670
+
2646
2671
  // lib/check-pin-must-be-connected.ts
2647
2672
  function checkPinMustBeConnected(circuitJson) {
2648
2673
  const errors = [];
@@ -3064,7 +3089,7 @@ async function runAllPinSpecificationChecks(circuitJson) {
3064
3089
  ];
3065
3090
  }
3066
3091
  async function runAllRoutingChecks(circuitJson) {
3067
- return [
3092
+ return dedupePcbDrcErrors([
3068
3093
  ...checkEachPcbPortConnectedToPcbTraces(circuitJson),
3069
3094
  ...checkSourceTracesHavePcbTraces(circuitJson),
3070
3095
  ...checkEachPcbTraceNonOverlapping(circuitJson),
@@ -3073,15 +3098,15 @@ async function runAllRoutingChecks(circuitJson) {
3073
3098
  ...checkDifferentNetViaSpacing(circuitJson),
3074
3099
  ...checkTracesAreContiguous(circuitJson),
3075
3100
  ...checkPcbTracesOutOfBoard(circuitJson)
3076
- ];
3101
+ ]);
3077
3102
  }
3078
3103
  async function runAllChecks(circuitJson) {
3079
- return [
3104
+ return dedupePcbDrcErrors([
3080
3105
  ...await runAllPlacementChecks(circuitJson),
3081
3106
  ...await runAllNetlistChecks(circuitJson),
3082
3107
  ...await runAllPinSpecificationChecks(circuitJson),
3083
3108
  ...await runAllRoutingChecks(circuitJson)
3084
- ];
3109
+ ]);
3085
3110
  }
3086
3111
  export {
3087
3112
  NetManager,
@@ -3104,6 +3129,7 @@ export {
3104
3129
  checkTracesAreContiguous,
3105
3130
  checkViaTraceClearance,
3106
3131
  checkViasOffBoard,
3132
+ dedupePcbDrcErrors,
3107
3133
  runAllChecks,
3108
3134
  runAllNetlistChecks,
3109
3135
  runAllPinSpecificationChecks,