@tscircuit/checks 0.0.126 → 0.0.128

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
@@ -572,7 +572,7 @@ var getClosestPointBetweenSegments = (segmentA, segmentB) => {
572
572
  };
573
573
  const dx = closestOnA.x - closestOnB.x;
574
574
  const dy = closestOnA.y - closestOnB.y;
575
- const distance4 = Math.sqrt(dx * dx + dy * dy);
575
+ const distance3 = Math.sqrt(dx * dx + dy * dy);
576
576
  const averagePoint = {
577
577
  x: (closestOnA.x + closestOnB.x) / 2,
578
578
  y: (closestOnA.y + closestOnB.y) / 2
@@ -811,14 +811,17 @@ var getCollidableBounds = (collidable) => {
811
811
  };
812
812
 
813
813
  // lib/check-each-pcb-trace-non-overlapping/getPcbPortIdsConnectedToTraces.ts
814
+ function getPcbPortIdsConnectedToRoutePoint(routePoint) {
815
+ if (routePoint.route_type !== "wire") return [];
816
+ return [routePoint.start_pcb_port_id, routePoint.end_pcb_port_id].filter(
817
+ (portId) => Boolean(portId)
818
+ );
819
+ }
814
820
  function getPcbPortIdsConnectedToTrace(trace) {
815
821
  const connectedPcbPorts = /* @__PURE__ */ new Set();
816
822
  for (const segment of trace.route) {
817
- if (segment.route_type === "wire") {
818
- if (segment.start_pcb_port_id)
819
- connectedPcbPorts.add(segment.start_pcb_port_id);
820
- if (segment.end_pcb_port_id)
821
- connectedPcbPorts.add(segment.end_pcb_port_id);
823
+ for (const portId of getPcbPortIdsConnectedToRoutePoint(segment)) {
824
+ connectedPcbPorts.add(portId);
822
825
  }
823
826
  }
824
827
  return Array.from(connectedPcbPorts);
@@ -966,12 +969,12 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
966
969
  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";
967
970
  if (isCircular) {
968
971
  const radius = getRadiusOfCircuitJsonElement(obj);
969
- const distance4 = segmentToCircleMinDistance(
972
+ const distance3 = segmentToCircleMinDistance(
970
973
  { x: segmentA.x1, y: segmentA.y1 },
971
974
  { x: segmentA.x2, y: segmentA.y2 },
972
975
  { x: obj.x, y: obj.y, radius }
973
976
  );
974
- const gap2 = distance4 - segmentA.thickness / 2;
977
+ const gap2 = distance3 - segmentA.thickness / 2;
975
978
  if (gap2 > minClearance - EPSILON) continue;
976
979
  const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${primaryObjId}`;
977
980
  if (errorIds.has(pcb_trace_error_id)) continue;
@@ -1003,11 +1006,11 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1003
1006
  const isPolygon = obj.type === "pcb_smtpad" && (obj.shape === "rotated_rect" || obj.shape === "polygon") || obj.type === "pcb_plated_hole" && "rect_pad_width" in obj && "rect_pad_height" in obj;
1004
1007
  if (isPolygon) {
1005
1008
  const padOutline = getPolygonPointsForPad(obj);
1006
- const { distance: distance4, center } = getSegmentToPolygonClearance(
1009
+ const { distance: distance3, center } = getSegmentToPolygonClearance(
1007
1010
  segmentA,
1008
1011
  padOutline
1009
1012
  );
1010
- const gap2 = distance4 - segmentA.thickness / 2;
1013
+ const gap2 = distance3 - segmentA.thickness / 2;
1011
1014
  if (gap2 > minClearance - EPSILON) continue;
1012
1015
  const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${primaryObjId}`;
1013
1016
  if (errorIds.has(pcb_trace_error_id)) continue;
@@ -1660,14 +1663,14 @@ function checkPcbTracesOutOfBoard(circuitJson, config = {}) {
1660
1663
  for (let j = 0; j < boardPoints.length; j++) {
1661
1664
  const edgeStart = boardPoints[j];
1662
1665
  const edgeEnd = boardPoints[(j + 1) % boardPoints.length];
1663
- const distance4 = segmentToSegmentMinDistance3(
1666
+ const distance3 = segmentToSegmentMinDistance3(
1664
1667
  segmentStart,
1665
1668
  segmentEnd,
1666
1669
  edgeStart,
1667
1670
  edgeEnd
1668
1671
  );
1669
- if (distance4 < minDistance) {
1670
- minDistance = distance4;
1672
+ if (distance3 < minDistance) {
1673
+ minDistance = distance3;
1671
1674
  }
1672
1675
  }
1673
1676
  const minimumDistance = traceWidth / 2 + margin;
@@ -2545,13 +2548,22 @@ function checkCourtyardOverlap(circuitJson) {
2545
2548
  }
2546
2549
 
2547
2550
  // lib/check-traces-are-contiguous/is-point-in-pad.ts
2548
- function distance3(x1, y1, x2, y2) {
2549
- return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
2551
+ function getDistanceBetweenPoints(pointA, pointB) {
2552
+ return Math.sqrt((pointB.x - pointA.x) ** 2 + (pointB.y - pointA.y) ** 2);
2553
+ }
2554
+ var POINT_ON_SEGMENT_TOLERANCE_MM = 1e-9;
2555
+ function isPointOnSegment(point, segment) {
2556
+ const crossProduct = (point.y - segment.start.y) * (segment.end.x - segment.start.x) - (point.x - segment.start.x) * (segment.end.y - segment.start.y);
2557
+ if (Math.abs(crossProduct) > POINT_ON_SEGMENT_TOLERANCE_MM) return false;
2558
+ const dotProduct = (point.x - segment.start.x) * (segment.end.x - segment.start.x) + (point.y - segment.start.y) * (segment.end.y - segment.start.y);
2559
+ if (dotProduct < -POINT_ON_SEGMENT_TOLERANCE_MM) return false;
2560
+ const squaredLength = (segment.end.x - segment.start.x) ** 2 + (segment.end.y - segment.start.y) ** 2;
2561
+ return dotProduct <= squaredLength + POINT_ON_SEGMENT_TOLERANCE_MM;
2550
2562
  }
2551
2563
  function isPointInPad(point, pad) {
2552
2564
  if (pad.type === "pcb_smtpad") {
2553
2565
  if (pad.shape === "circle") {
2554
- return distance3(point.x, point.y, pad.x, pad.y) <= pad.radius;
2566
+ return getDistanceBetweenPoints(point, pad) <= pad.radius;
2555
2567
  }
2556
2568
  if (pad.shape === "rect") {
2557
2569
  const halfWidth = pad.width / 2;
@@ -2583,10 +2595,21 @@ function isPointInPad(point, pad) {
2583
2595
  );
2584
2596
  return cornerX * cornerX + cornerY * cornerY <= radius * radius;
2585
2597
  }
2598
+ if (pad.shape === "polygon") {
2599
+ let inside = false;
2600
+ for (let i = 0, j = pad.points.length - 1; i < pad.points.length; j = i++) {
2601
+ const pi = pad.points[i];
2602
+ const pj = pad.points[j];
2603
+ if (isPointOnSegment(point, { start: pi, end: pj })) return true;
2604
+ 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;
2605
+ if (intersects) inside = !inside;
2606
+ }
2607
+ return inside;
2608
+ }
2586
2609
  }
2587
2610
  if (pad.type === "pcb_plated_hole") {
2588
2611
  if (pad.shape === "circle") {
2589
- return distance3(point.x, point.y, pad.x, pad.y) <= pad.outer_diameter / 2;
2612
+ return getDistanceBetweenPoints(point, pad) <= pad.outer_diameter / 2;
2590
2613
  }
2591
2614
  if (pad.shape === "oval" || pad.shape === "pill") {
2592
2615
  return Math.abs(point.x - pad.x) <= pad.outer_width / 2 && Math.abs(point.y - pad.y) <= pad.outer_height / 2;
@@ -2606,6 +2629,64 @@ import {
2606
2629
  getReadableNameForPcbPort as getReadableNameForPcbPort2,
2607
2630
  getReadableNameForPcbTrace
2608
2631
  } from "@tscircuit/circuit-json-util";
2632
+ function routePointConnectsToAnotherExpectedPort(point, expectedPorts, missingPcbPortId, padMap) {
2633
+ if (point.route_type !== "wire") return false;
2634
+ return expectedPorts.some((expectedPort) => {
2635
+ if (!expectedPort.pcb_port_id || expectedPort.pcb_port_id === missingPcbPortId) {
2636
+ return false;
2637
+ }
2638
+ const expectedPads = padMap.get(expectedPort.pcb_port_id);
2639
+ return expectedPads?.some(
2640
+ (pad) => isPointInPad({ x: point.x, y: point.y }, pad)
2641
+ ) ?? false;
2642
+ });
2643
+ }
2644
+ function getMissingConnectionErrorCenter({
2645
+ firstPoint,
2646
+ lastPoint,
2647
+ port,
2648
+ expectedPorts,
2649
+ padMap
2650
+ }) {
2651
+ let errorLocation;
2652
+ const firstWirePoint = firstPoint.route_type === "wire" ? firstPoint : void 0;
2653
+ const lastWirePoint = lastPoint.route_type === "wire" ? lastPoint : void 0;
2654
+ const firstWirePointReferencesPort = getPcbPortIdsConnectedToRoutePoint(
2655
+ firstPoint
2656
+ ).includes(port.pcb_port_id);
2657
+ const lastWirePointReferencesPort = getPcbPortIdsConnectedToRoutePoint(
2658
+ lastPoint
2659
+ ).includes(port.pcb_port_id);
2660
+ if (firstWirePointReferencesPort && firstWirePoint) {
2661
+ errorLocation = firstWirePoint;
2662
+ } else if (lastWirePointReferencesPort && lastWirePoint) {
2663
+ errorLocation = lastWirePoint;
2664
+ } else if (routePointConnectsToAnotherExpectedPort(
2665
+ firstPoint,
2666
+ expectedPorts,
2667
+ port.pcb_port_id,
2668
+ padMap
2669
+ ) && lastWirePoint) {
2670
+ errorLocation = lastWirePoint;
2671
+ } else if (routePointConnectsToAnotherExpectedPort(
2672
+ lastPoint,
2673
+ expectedPorts,
2674
+ port.pcb_port_id,
2675
+ padMap
2676
+ ) && firstWirePoint) {
2677
+ errorLocation = firstWirePoint;
2678
+ } else if (firstWirePoint && lastWirePoint) {
2679
+ errorLocation = distance2(firstWirePoint, port) <= distance2(lastWirePoint, port) ? firstWirePoint : lastWirePoint;
2680
+ } else if (firstWirePoint) {
2681
+ errorLocation = firstWirePoint;
2682
+ } else if (lastWirePoint) {
2683
+ errorLocation = lastWirePoint;
2684
+ }
2685
+ return errorLocation ? { x: errorLocation.x, y: errorLocation.y } : {
2686
+ x: (firstPoint.x + lastPoint.x) / 2,
2687
+ y: (firstPoint.y + lastPoint.y) / 2
2688
+ };
2689
+ }
2609
2690
  function checkTracesAreContiguous(circuitJson) {
2610
2691
  const errors = [];
2611
2692
  const pcbPorts = circuitJson.filter(
@@ -2626,12 +2707,15 @@ function checkTracesAreContiguous(circuitJson) {
2626
2707
  const padMap = /* @__PURE__ */ new Map();
2627
2708
  for (const pad of pcbSmtPads) {
2628
2709
  if (pad.pcb_port_id) {
2629
- padMap.set(pad.pcb_port_id, pad);
2710
+ padMap.set(pad.pcb_port_id, [...padMap.get(pad.pcb_port_id) ?? [], pad]);
2630
2711
  }
2631
2712
  }
2632
2713
  for (const hole of pcbPlatedHoles) {
2633
2714
  if (hole.pcb_port_id) {
2634
- padMap.set(hole.pcb_port_id, hole);
2715
+ padMap.set(hole.pcb_port_id, [
2716
+ ...padMap.get(hole.pcb_port_id) ?? [],
2717
+ hole
2718
+ ]);
2635
2719
  }
2636
2720
  }
2637
2721
  for (const trace of pcbTraces) {
@@ -2679,20 +2763,27 @@ function checkTracesAreContiguous(circuitJson) {
2679
2763
  );
2680
2764
  for (const port of expectedPorts) {
2681
2765
  if (!port.pcb_port_id) continue;
2682
- const pad = padMap.get(port.pcb_port_id);
2683
- if (!pad) continue;
2684
- const isFirstPointConnected = firstPoint.route_type === "wire" && isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad);
2685
- const isLastPointConnected = lastPoint.route_type === "wire" && isPointInPad({ x: lastPoint.x, y: lastPoint.y }, pad);
2766
+ const pads = padMap.get(port.pcb_port_id);
2767
+ if (!pads?.length) continue;
2768
+ const isFirstPointConnected = firstPoint.route_type === "wire" && pads.some(
2769
+ (pad) => isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad)
2770
+ );
2771
+ const isLastPointConnected = lastPoint.route_type === "wire" && pads.some(
2772
+ (pad) => isPointInPad({ x: lastPoint.x, y: lastPoint.y }, pad)
2773
+ );
2686
2774
  if (!isFirstPointConnected && !isLastPointConnected) {
2687
2775
  const portName = getReadableNameForPcbPort2(
2688
2776
  circuitJson,
2689
2777
  port.pcb_port_id
2690
2778
  ).replace("pcb_port", "");
2691
- const padType = pad.type.replace(/pcb_/, "");
2692
- const errorCenter = {
2693
- x: (firstPoint.x + lastPoint.x) / 2,
2694
- y: (firstPoint.y + lastPoint.y) / 2
2695
- };
2779
+ const padType = pads[0].type.replace(/pcb_/, "");
2780
+ const errorCenter = getMissingConnectionErrorCenter({
2781
+ firstPoint,
2782
+ lastPoint,
2783
+ port,
2784
+ expectedPorts,
2785
+ padMap
2786
+ });
2696
2787
  errors.push({
2697
2788
  type: "pcb_trace_error",
2698
2789
  message: `Trace [${traceName}] is missing a connection to ${padType}${portName}`,
@@ -2710,11 +2801,15 @@ function checkTracesAreContiguous(circuitJson) {
2710
2801
  let firstConnectsToAnyPad = false;
2711
2802
  let lastConnectsToAnyPad = false;
2712
2803
  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;
2713
- for (const [portId, pad] of padMap) {
2714
- if (firstPoint.route_type === "wire" && isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad)) {
2804
+ for (const pads of padMap.values()) {
2805
+ if (firstPoint.route_type === "wire" && pads.some(
2806
+ (pad) => isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad)
2807
+ )) {
2715
2808
  firstConnectsToAnyPad = true;
2716
2809
  }
2717
- if (lastPoint.route_type === "wire" && isPointInPad({ x: lastPoint.x, y: lastPoint.y }, pad)) {
2810
+ if (lastPoint.route_type === "wire" && pads.some(
2811
+ (pad) => isPointInPad({ x: lastPoint.x, y: lastPoint.y }, pad)
2812
+ )) {
2718
2813
  lastConnectsToAnyPad = true;
2719
2814
  }
2720
2815
  }