@tscircuit/checks 0.0.123 → 0.0.125

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
@@ -363,12 +363,14 @@ var jlcMinTolerances = {
363
363
  var DEFAULT_TRACE_MARGIN = 0.1;
364
364
  var DEFAULT_TRACE_THICKNESS = jlcMinTolerances.min_trace_width;
365
365
  var DEFAULT_VIA_DIAMETER = jlcMinTolerances.min_via_pad_diameter;
366
- var DEFAULT_VIA_BOARD_MARGIN = 0.3;
366
+ var DEFAULT_VIA_BOARD_MARGIN = jlcMinTolerances.min_board_edge_clearance;
367
367
  var DEFAULT_SAME_NET_VIA_MARGIN = jlcMinTolerances.min_via_hole_edge_to_via_hole_edge_clearance;
368
368
  var DEFAULT_DIFFERENT_NET_VIA_MARGIN = jlcMinTolerances.min_via_hole_edge_to_via_hole_edge_clearance;
369
369
  var DEFAULT_PAD_PAD_CLEARANCE = jlcMinTolerances.min_pad_edge_to_pad_edge_clearance;
370
370
  var DEFAULT_PAD_TRACE_CLEARANCE = jlcMinTolerances.min_trace_to_pad_edge_clearance;
371
371
  var EPSILON = 5e-3;
372
+ var getPcbBoard = (circuitJson) => circuitJson.find((el) => el.type === "pcb_board");
373
+ var getBoardDrcValue = (board, key) => board?.[key];
372
374
 
373
375
  // lib/util/getLayersOfPcbElement.ts
374
376
  import { all_layers } from "circuit-json";
@@ -570,7 +572,7 @@ var getClosestPointBetweenSegments = (segmentA, segmentB) => {
570
572
  };
571
573
  const dx = closestOnA.x - closestOnB.x;
572
574
  const dy = closestOnA.y - closestOnB.y;
573
- const distance3 = Math.sqrt(dx * dx + dy * dy);
575
+ const distance4 = Math.sqrt(dx * dx + dy * dy);
574
576
  const averagePoint = {
575
577
  x: (closestOnA.x + closestOnB.x) / 2,
576
578
  y: (closestOnA.y + closestOnB.y) / 2
@@ -802,12 +804,12 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
802
804
  const isCircular = obj.type === "pcb_via" || obj.type === "pcb_plated_hole" && obj.shape === "circle" || obj.type === "pcb_hole" || obj.type === "pcb_smtpad" && obj.shape === "circle";
803
805
  if (isCircular) {
804
806
  const radius = getRadiusOfCircuitJsonElement(obj);
805
- const distance3 = segmentToCircleMinDistance(
807
+ const distance4 = segmentToCircleMinDistance(
806
808
  { x: segmentA.x1, y: segmentA.y1 },
807
809
  { x: segmentA.x2, y: segmentA.y2 },
808
810
  { x: obj.x, y: obj.y, radius }
809
811
  );
810
- const gap2 = distance3 - segmentA.thickness / 2;
812
+ const gap2 = distance4 - segmentA.thickness / 2;
811
813
  if (gap2 > minClearance - EPSILON) continue;
812
814
  const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${primaryObjId}`;
813
815
  if (errorIds.has(pcb_trace_error_id)) continue;
@@ -919,11 +921,12 @@ var NetManager = class {
919
921
  // lib/check-pcb-components-out-of-board/checkViasOffBoard.ts
920
922
  import { getReadableNameForElement as getReadableNameForElement3 } from "@tscircuit/circuit-json-util";
921
923
  function checkViasOffBoard(circuitJson) {
922
- const board = circuitJson.find((el) => el.type === "pcb_board");
924
+ const board = getPcbBoard(circuitJson);
923
925
  if (!board) return [];
924
926
  const vias = circuitJson.filter((el) => el.type === "pcb_via");
925
927
  if (vias.length === 0) return [];
926
928
  if (board.width === void 0 || board.height === void 0) return [];
929
+ const boardEdgeClearance = getBoardDrcValue(board, "min_board_edge_clearance") ?? jlcMinTolerances.min_board_edge_clearance;
927
930
  const boardMinX = board.center.x - board.width / 2;
928
931
  const boardMaxX = board.center.x + board.width / 2;
929
932
  const boardMinY = board.center.y - board.height / 2;
@@ -935,7 +938,7 @@ function checkViasOffBoard(circuitJson) {
935
938
  const viaMaxX = via.x + viaRadius;
936
939
  const viaMinY = via.y - viaRadius;
937
940
  const viaMaxY = via.y + viaRadius;
938
- if (viaMinX < boardMinX + DEFAULT_VIA_BOARD_MARGIN || viaMaxX > boardMaxX - DEFAULT_VIA_BOARD_MARGIN || viaMinY < boardMinY + DEFAULT_VIA_BOARD_MARGIN || viaMaxY > boardMaxY - DEFAULT_VIA_BOARD_MARGIN) {
941
+ if (viaMinX < boardMinX + boardEdgeClearance || viaMaxX > boardMaxX - boardEdgeClearance || viaMinY < boardMinY + boardEdgeClearance || viaMaxY > boardMaxY - boardEdgeClearance) {
939
942
  const viaName = getReadableNameForElement3(circuitJson, via.pcb_via_id);
940
943
  errors.push({
941
944
  type: "pcb_placement_error",
@@ -1199,10 +1202,12 @@ function viasAreAtSameLocation(a, b) {
1199
1202
  // lib/check-same-net-via-spacing.ts
1200
1203
  function checkSameNetViaSpacing(circuitJson, {
1201
1204
  connMap,
1202
- minClearance = DEFAULT_SAME_NET_VIA_MARGIN
1205
+ minClearance
1203
1206
  } = {}) {
1204
1207
  const vias = circuitJson.filter((el) => el.type === "pcb_via");
1205
1208
  if (vias.length < 2) return [];
1209
+ const board = getPcbBoard(circuitJson);
1210
+ minClearance ??= getBoardDrcValue(board, "min_via_hole_edge_to_via_hole_edge_clearance") ?? jlcMinTolerances.min_via_hole_edge_to_via_hole_edge_clearance;
1206
1211
  connMap ??= getFullConnectivityMapFromCircuitJson3(circuitJson);
1207
1212
  const errors = [];
1208
1213
  const reported = /* @__PURE__ */ new Set();
@@ -1212,7 +1217,7 @@ function checkSameNetViaSpacing(circuitJson, {
1212
1217
  const viaB = vias[j];
1213
1218
  if (viasAreAtSameLocation(viaA, viaB)) continue;
1214
1219
  if (!connMap.areIdsConnected(viaA.pcb_via_id, viaB.pcb_via_id)) continue;
1215
- const gap = distance2(viaA, viaB) - viaA.outer_diameter / 2 - viaB.outer_diameter / 2;
1220
+ const gap = distance2(viaA, viaB) - viaA.hole_diameter / 2 - viaB.hole_diameter / 2;
1216
1221
  if (gap + EPSILON >= minClearance) continue;
1217
1222
  const pairId = [viaA.pcb_via_id, viaB.pcb_via_id].sort().join("_");
1218
1223
  if (reported.has(pairId)) continue;
@@ -1248,10 +1253,12 @@ import {
1248
1253
  } from "circuit-json-to-connectivity-map";
1249
1254
  function checkDifferentNetViaSpacing(circuitJson, {
1250
1255
  connMap,
1251
- minClearance = DEFAULT_DIFFERENT_NET_VIA_MARGIN
1256
+ minClearance
1252
1257
  } = {}) {
1253
1258
  const vias = circuitJson.filter((el) => el.type === "pcb_via");
1254
1259
  if (vias.length < 2) return [];
1260
+ const board = getPcbBoard(circuitJson);
1261
+ minClearance ??= getBoardDrcValue(board, "min_via_hole_edge_to_via_hole_edge_clearance") ?? jlcMinTolerances.min_via_hole_edge_to_via_hole_edge_clearance;
1255
1262
  connMap ??= getFullConnectivityMapFromCircuitJson4(circuitJson);
1256
1263
  const errors = [];
1257
1264
  const reported = /* @__PURE__ */ new Set();
@@ -1261,7 +1268,7 @@ function checkDifferentNetViaSpacing(circuitJson, {
1261
1268
  const viaB = vias[j];
1262
1269
  if (viasAreAtSameLocation(viaA, viaB)) continue;
1263
1270
  if (connMap.areIdsConnected(viaA.pcb_via_id, viaB.pcb_via_id)) continue;
1264
- const gap = distance2(viaA, viaB) - viaA.outer_diameter / 2 - viaB.outer_diameter / 2;
1271
+ const gap = distance2(viaA, viaB) - viaA.hole_diameter / 2 - viaB.hole_diameter / 2;
1265
1272
  if (gap + EPSILON >= minClearance) continue;
1266
1273
  const pairId = [viaA.pcb_via_id, viaB.pcb_via_id].sort().join("_");
1267
1274
  if (reported.has(pairId)) continue;
@@ -1414,7 +1421,6 @@ function checkSourceTracesHavePcbTraces(circuitJson) {
1414
1421
  // lib/check-trace-out-of-board/checkTraceOutOfBoard.ts
1415
1422
  import { cju as cju3 } from "@tscircuit/circuit-json-util";
1416
1423
  import { segmentToSegmentMinDistance as segmentToSegmentMinDistance2 } from "@tscircuit/math-utils";
1417
- var DEFAULT_BOARD_MARGIN = 0.2;
1418
1424
  function getBoardPolygonPoints(board) {
1419
1425
  if (board.outline && board.outline.length > 0) {
1420
1426
  return board.outline.map((p) => ({ x: p.x, y: p.y }));
@@ -1439,11 +1445,9 @@ function getBoardPolygonPoints(board) {
1439
1445
  }
1440
1446
  function checkPcbTracesOutOfBoard(circuitJson, config = {}) {
1441
1447
  const errors = [];
1442
- const margin = config.margin ?? DEFAULT_BOARD_MARGIN;
1443
- const board = circuitJson.find(
1444
- (el) => el.type === "pcb_board"
1445
- );
1448
+ const board = getPcbBoard(circuitJson);
1446
1449
  if (!board) return errors;
1450
+ const margin = config.margin ?? getBoardDrcValue(board, "min_board_edge_clearance") ?? jlcMinTolerances.min_board_edge_clearance;
1447
1451
  const boardPoints = getBoardPolygonPoints(board);
1448
1452
  if (!boardPoints) return errors;
1449
1453
  const pcbTraces = cju3(circuitJson).pcb_trace.list();
@@ -1456,18 +1460,18 @@ function checkPcbTracesOutOfBoard(circuitJson, config = {}) {
1456
1460
  const traceWidth = "width" in p1 ? p1.width : "width" in p2 ? p2.width : 0.1;
1457
1461
  const segmentStart = { x: p1.x, y: p1.y };
1458
1462
  const segmentEnd = { x: p2.x, y: p2.y };
1459
- let minDistance = Infinity;
1463
+ let minDistance = Number.POSITIVE_INFINITY;
1460
1464
  for (let j = 0; j < boardPoints.length; j++) {
1461
1465
  const edgeStart = boardPoints[j];
1462
1466
  const edgeEnd = boardPoints[(j + 1) % boardPoints.length];
1463
- const distance3 = segmentToSegmentMinDistance2(
1467
+ const distance4 = segmentToSegmentMinDistance2(
1464
1468
  segmentStart,
1465
1469
  segmentEnd,
1466
1470
  edgeStart,
1467
1471
  edgeEnd
1468
1472
  );
1469
- if (distance3 < minDistance) {
1470
- minDistance = distance3;
1473
+ if (distance4 < minDistance) {
1474
+ minDistance = distance4;
1471
1475
  }
1472
1476
  }
1473
1477
  const minimumDistance = traceWidth / 2 + margin;
@@ -1741,10 +1745,12 @@ var getTraceSegments = (circuitJson) => {
1741
1745
  // lib/check-pad-pad-clearance.ts
1742
1746
  function checkPadPadClearance(circuitJson, {
1743
1747
  connMap,
1744
- minClearance = DEFAULT_PAD_PAD_CLEARANCE
1748
+ minClearance
1745
1749
  } = {}) {
1746
1750
  const pads = getPads(circuitJson);
1747
1751
  if (pads.length < 2) return [];
1752
+ const board = getPcbBoard(circuitJson);
1753
+ minClearance ??= getBoardDrcValue(board, "min_pad_edge_to_pad_edge_clearance") ?? jlcMinTolerances.min_pad_edge_to_pad_edge_clearance;
1748
1754
  connMap ??= getFullConnectivityMapFromCircuitJson7(circuitJson);
1749
1755
  const spatialIndex = new SpatialObjectIndex({
1750
1756
  objects: pads,
@@ -1807,11 +1813,13 @@ import {
1807
1813
  } from "circuit-json-to-connectivity-map";
1808
1814
  function checkPadTraceClearance(circuitJson, {
1809
1815
  connMap,
1810
- minClearance = DEFAULT_PAD_TRACE_CLEARANCE
1816
+ minClearance
1811
1817
  } = {}) {
1812
1818
  const pads = getPads(circuitJson);
1813
1819
  const segments = getTraceSegments(circuitJson);
1814
1820
  if (pads.length === 0 || segments.length === 0) return [];
1821
+ const board = getPcbBoard(circuitJson);
1822
+ minClearance ??= getBoardDrcValue(board, "min_trace_to_pad_edge_clearance") ?? jlcMinTolerances.min_trace_to_pad_edge_clearance;
1815
1823
  connMap ??= getFullConnectivityMapFromCircuitJson8(circuitJson);
1816
1824
  const spatialIndex = new SpatialObjectIndex({
1817
1825
  objects: pads,
@@ -1888,11 +1896,13 @@ var getClosestPointOnSegment = (point, segmentStart, segmentEnd) => {
1888
1896
  };
1889
1897
  function checkViaTraceClearance(circuitJson, {
1890
1898
  connMap,
1891
- minClearance = DEFAULT_PAD_TRACE_CLEARANCE
1899
+ minClearance
1892
1900
  } = {}) {
1893
1901
  const vias = circuitJson.filter((el) => el.type === "pcb_via");
1894
1902
  const segments = getTraceSegments(circuitJson);
1895
1903
  if (vias.length === 0 || segments.length === 0) return [];
1904
+ const board = getPcbBoard(circuitJson);
1905
+ minClearance ??= getBoardDrcValue(board, "min_trace_to_pad_edge_clearance") ?? jlcMinTolerances.min_trace_to_pad_edge_clearance;
1896
1906
  connMap ??= getFullConnectivityMapFromCircuitJson9(circuitJson);
1897
1907
  const errors = /* @__PURE__ */ new Map();
1898
1908
  for (const via of vias) {
@@ -2338,8 +2348,210 @@ function checkCourtyardOverlap(circuitJson) {
2338
2348
  return errors;
2339
2349
  }
2340
2350
 
2351
+ // lib/check-traces-are-contiguous/is-point-in-pad.ts
2352
+ function distance3(x1, y1, x2, y2) {
2353
+ return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
2354
+ }
2355
+ function isPointInPad(point, pad) {
2356
+ if (pad.type === "pcb_smtpad") {
2357
+ if (pad.shape === "circle") {
2358
+ return distance3(point.x, point.y, pad.x, pad.y) <= pad.radius;
2359
+ }
2360
+ if (pad.shape === "rect") {
2361
+ const halfWidth = pad.width / 2;
2362
+ const halfHeight = pad.height / 2;
2363
+ return Math.abs(point.x - pad.x) <= halfWidth && Math.abs(point.y - pad.y) <= halfHeight;
2364
+ }
2365
+ if (pad.shape === "rotated_rect") {
2366
+ const dx = point.x - pad.x;
2367
+ const dy = point.y - pad.y;
2368
+ const angle = -pad.ccw_rotation;
2369
+ const rotatedX = dx * Math.cos(angle) - dy * Math.sin(angle);
2370
+ const rotatedY = dx * Math.sin(angle) + dy * Math.cos(angle);
2371
+ return Math.abs(rotatedX) <= pad.width / 2 && Math.abs(rotatedY) <= pad.height / 2;
2372
+ }
2373
+ if (pad.shape === "pill") {
2374
+ const halfWidth = pad.width / 2;
2375
+ const halfHeight = pad.height / 2;
2376
+ const radius = pad.radius;
2377
+ if (Math.abs(point.x - pad.x) <= halfWidth - radius && Math.abs(point.y - pad.y) <= halfHeight) {
2378
+ return true;
2379
+ }
2380
+ const cornerX = Math.max(
2381
+ Math.abs(point.x - pad.x) - (halfWidth - radius),
2382
+ 0
2383
+ );
2384
+ const cornerY = Math.max(
2385
+ Math.abs(point.y - pad.y) - (halfHeight - radius),
2386
+ 0
2387
+ );
2388
+ return cornerX * cornerX + cornerY * cornerY <= radius * radius;
2389
+ }
2390
+ }
2391
+ if (pad.type === "pcb_plated_hole") {
2392
+ if (pad.shape === "circle") {
2393
+ return distance3(point.x, point.y, pad.x, pad.y) <= pad.outer_diameter / 2;
2394
+ }
2395
+ if (pad.shape === "oval" || pad.shape === "pill") {
2396
+ return Math.abs(point.x - pad.x) <= pad.outer_width / 2 && Math.abs(point.y - pad.y) <= pad.outer_height / 2;
2397
+ }
2398
+ if (pad.shape === "circular_hole_with_rect_pad") {
2399
+ return Math.abs(point.x - pad.x) <= pad.rect_pad_width / 2 && Math.abs(point.y - pad.y) <= pad.rect_pad_height / 2;
2400
+ }
2401
+ if (pad.shape === "pill_hole_with_rect_pad") {
2402
+ return Math.abs(point.x - pad.x) <= pad.rect_pad_width / 2 && Math.abs(point.y - pad.y) <= pad.rect_pad_height / 2;
2403
+ }
2404
+ }
2405
+ return false;
2406
+ }
2407
+
2341
2408
  // lib/check-traces-are-contiguous/check-traces-are-contiguous.ts
2342
- import { getReadableNameForPcbPort as getReadableNameForPcbPort2 } from "@tscircuit/circuit-json-util";
2409
+ import {
2410
+ getReadableNameForPcbPort as getReadableNameForPcbPort2,
2411
+ getReadableNameForPcbTrace
2412
+ } from "@tscircuit/circuit-json-util";
2413
+ function checkTracesAreContiguous(circuitJson) {
2414
+ const errors = [];
2415
+ const pcbPorts = circuitJson.filter(
2416
+ (el) => el.type === "pcb_port"
2417
+ );
2418
+ const pcbTraces = circuitJson.filter(
2419
+ (el) => el.type === "pcb_trace"
2420
+ );
2421
+ const sourceTraces = circuitJson.filter(
2422
+ (el) => el.type === "source_trace"
2423
+ );
2424
+ const pcbSmtPads = circuitJson.filter(
2425
+ (el) => el.type === "pcb_smtpad"
2426
+ );
2427
+ const pcbPlatedHoles = circuitJson.filter(
2428
+ (el) => el.type === "pcb_plated_hole"
2429
+ );
2430
+ const padMap = /* @__PURE__ */ new Map();
2431
+ for (const pad of pcbSmtPads) {
2432
+ if (pad.pcb_port_id) {
2433
+ padMap.set(pad.pcb_port_id, pad);
2434
+ }
2435
+ }
2436
+ for (const hole of pcbPlatedHoles) {
2437
+ if (hole.pcb_port_id) {
2438
+ padMap.set(hole.pcb_port_id, hole);
2439
+ }
2440
+ }
2441
+ for (const trace of pcbTraces) {
2442
+ if (trace.route.length === 0) continue;
2443
+ const firstPoint = trace.route[0];
2444
+ const lastPoint = trace.route[trace.route.length - 1];
2445
+ const sourceTrace = sourceTraces.find(
2446
+ (st) => st.source_trace_id === trace.source_trace_id
2447
+ );
2448
+ const expectedPorts = sourceTrace ? pcbPorts.filter(
2449
+ (port) => sourceTrace.connected_source_port_ids?.includes(port.source_port_id)
2450
+ ) : [];
2451
+ for (let i = 1; i < trace.route.length - 1; i++) {
2452
+ const prevPoint = trace.route[i - 1];
2453
+ const currentPoint = trace.route[i];
2454
+ const nextPoint = trace.route[i + 1];
2455
+ if (currentPoint.route_type === "via") {
2456
+ const prevIsWire = prevPoint.route_type === "wire";
2457
+ const nextIsWire = nextPoint.route_type === "wire";
2458
+ if (prevIsWire && nextIsWire) {
2459
+ const prevAligned = Math.abs(prevPoint.x - currentPoint.x) < 0.01 && Math.abs(prevPoint.y - currentPoint.y) < 0.01;
2460
+ const nextAligned = Math.abs(nextPoint.x - currentPoint.x) < 0.01 && Math.abs(nextPoint.y - currentPoint.y) < 0.01;
2461
+ if (!prevAligned || !nextAligned) {
2462
+ const traceName2 = getReadableNameForPcbTrace(
2463
+ circuitJson,
2464
+ trace.pcb_trace_id
2465
+ );
2466
+ errors.push({
2467
+ type: "pcb_trace_error",
2468
+ message: `Via in trace [${traceName2}] is misaligned at position {x: ${currentPoint.x}, y: ${currentPoint.y}}.`,
2469
+ source_trace_id: sourceTrace?.source_trace_id || trace.source_trace_id || `!${trace.pcb_trace_id}`,
2470
+ error_type: "pcb_trace_error",
2471
+ pcb_trace_id: trace.pcb_trace_id,
2472
+ pcb_trace_error_id: `misaligned_via_${trace.pcb_trace_id}_${i}`,
2473
+ pcb_component_ids: [],
2474
+ pcb_port_ids: []
2475
+ });
2476
+ }
2477
+ }
2478
+ }
2479
+ }
2480
+ const traceName = getReadableNameForPcbTrace(
2481
+ circuitJson,
2482
+ trace.pcb_trace_id
2483
+ );
2484
+ for (const port of expectedPorts) {
2485
+ if (!port.pcb_port_id) continue;
2486
+ const pad = padMap.get(port.pcb_port_id);
2487
+ if (!pad) continue;
2488
+ const isFirstPointConnected = firstPoint.route_type === "wire" && isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad);
2489
+ const isLastPointConnected = lastPoint.route_type === "wire" && isPointInPad({ x: lastPoint.x, y: lastPoint.y }, pad);
2490
+ if (!isFirstPointConnected && !isLastPointConnected) {
2491
+ const portName = getReadableNameForPcbPort2(
2492
+ circuitJson,
2493
+ port.pcb_port_id
2494
+ ).replace("pcb_port", "");
2495
+ const padType = pad.type.replace(/pcb_/, "");
2496
+ const errorCenter = {
2497
+ x: (firstPoint.x + lastPoint.x) / 2,
2498
+ y: (firstPoint.y + lastPoint.y) / 2
2499
+ };
2500
+ errors.push({
2501
+ type: "pcb_trace_error",
2502
+ message: `Trace [${traceName}] is missing a connection to ${padType}${portName}`,
2503
+ source_trace_id: sourceTrace?.source_trace_id || trace.source_trace_id || `!${trace.pcb_trace_id}`,
2504
+ error_type: "pcb_trace_error",
2505
+ pcb_trace_id: trace.pcb_trace_id,
2506
+ pcb_trace_error_id: `missing_connection_${trace.pcb_trace_id}_${port.pcb_port_id}`,
2507
+ center: errorCenter,
2508
+ pcb_component_ids: [],
2509
+ pcb_port_ids: [port.pcb_port_id]
2510
+ });
2511
+ }
2512
+ }
2513
+ if (expectedPorts.length === 0) {
2514
+ let firstConnectsToAnyPad = false;
2515
+ let lastConnectsToAnyPad = false;
2516
+ 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;
2517
+ for (const [portId, pad] of padMap) {
2518
+ if (firstPoint.route_type === "wire" && isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad)) {
2519
+ firstConnectsToAnyPad = true;
2520
+ }
2521
+ if (lastPoint.route_type === "wire" && isPointInPad({ x: lastPoint.x, y: lastPoint.y }, pad)) {
2522
+ lastConnectsToAnyPad = true;
2523
+ }
2524
+ }
2525
+ if (!firstConnectsToAnyPad && firstPoint.route_type === "wire") {
2526
+ errors.push({
2527
+ type: "pcb_trace_error",
2528
+ message: `Trace [${traceName}] has disconnected endpoint at (${firstPoint.x.toFixed(2)}, ${firstPoint.y.toFixed(2)})`,
2529
+ source_trace_id: sourceTrace?.source_trace_id || trace.source_trace_id || `!${trace.pcb_trace_id}`,
2530
+ error_type: "pcb_trace_error",
2531
+ pcb_trace_id: trace.pcb_trace_id,
2532
+ pcb_trace_error_id: `disconnected_endpoint_${trace.pcb_trace_id}_start`,
2533
+ center: { x: firstPoint.x, y: firstPoint.y },
2534
+ pcb_component_ids: [],
2535
+ pcb_port_ids: []
2536
+ });
2537
+ }
2538
+ if (!lastConnectsToAnyPad && lastPoint.route_type === "wire" && !(endpointsAreSame && !firstConnectsToAnyPad)) {
2539
+ errors.push({
2540
+ type: "pcb_trace_error",
2541
+ message: `Trace [${traceName}] has disconnected endpoint at (${lastPoint.x.toFixed(2)}, ${lastPoint.y.toFixed(2)})`,
2542
+ source_trace_id: sourceTrace?.source_trace_id || trace.source_trace_id || `!${trace.pcb_trace_id}`,
2543
+ error_type: "pcb_trace_error",
2544
+ pcb_trace_id: trace.pcb_trace_id,
2545
+ pcb_trace_error_id: `disconnected_endpoint_${trace.pcb_trace_id}_end`,
2546
+ center: { x: lastPoint.x, y: lastPoint.y },
2547
+ pcb_component_ids: [],
2548
+ pcb_port_ids: []
2549
+ });
2550
+ }
2551
+ }
2552
+ }
2553
+ return errors;
2554
+ }
2343
2555
 
2344
2556
  // lib/run-all-checks.ts
2345
2557
  async function runAllPlacementChecks(circuitJson) {
@@ -2371,7 +2583,7 @@ async function runAllRoutingChecks(circuitJson) {
2371
2583
  ...checkViaTraceClearance(circuitJson),
2372
2584
  ...checkSameNetViaSpacing(circuitJson),
2373
2585
  ...checkDifferentNetViaSpacing(circuitJson),
2374
- // ...checkTracesAreContiguous(circuitJson),
2586
+ ...checkTracesAreContiguous(circuitJson),
2375
2587
  ...checkPcbTracesOutOfBoard(circuitJson)
2376
2588
  ];
2377
2589
  }