imrishabh18/corne-keyboard

The code defines and renders two surface-mount chip components with SMT pads, silkscreen outlines, and 3D CAD models (OBJ and STEP) for PCB assembly.

Version
2.0.21
License
unset
Stars
1

vendor/checks/index.js

// lib/check-traces-are-contiguous/is-point-in-pad.ts
import { pointToSegmentDistance } from "@tscircuit/math-utils";

// lib/check-each-pcb-trace-non-overlapping/segment-to-polygon-clearance.ts
import {
  distSq,
  getSegmentIntersection,
  isPointInsidePolygon,
  pointToSegmentClosestPoint,
  segmentToSegmentMinDistance
} from "@tscircuit/math-utils";
var rotatePoint = (point, angleDegrees) => {
  const angle = angleDegrees * Math.PI / 180;
  return {
    x: point.x * Math.cos(angle) - point.y * Math.sin(angle),
    y: point.x * Math.sin(angle) + point.y * Math.cos(angle)
  };
};
var getRotatedRectPoints = ({
  x,
  y,
  width,
  height,
  ccwRotation
}) => {
  const halfWidth = width / 2;
  const halfHeight = height / 2;
  return [
    { x: -halfWidth, y: -halfHeight },
    { x: halfWidth, y: -halfHeight },
    { x: halfWidth, y: halfHeight },
    { x: -halfWidth, y: halfHeight }
  ].map((point) => {
    const rotated = rotatePoint(point, ccwRotation);
    return { x: x + rotated.x, y: y + rotated.y };
  });
};
var getPillCenterLineForPad = (pad) => {
  const width = pad.type === "pcb_plated_hole" ? pad.outer_width : pad.width;
  const height = pad.type === "pcb_plated_hole" ? pad.outer_height : pad.height;
  const radius = pad.type === "pcb_plated_hole" ? Math.min(width, height) / 2 : pad.radius;
  const ccwRotation = pad.type === "pcb_plated_hole" ? pad.ccw_rotation : pad.shape === "rotated_pill" ? pad.ccw_rotation : 0;
  const halfLineLength = Math.max(Math.max(width, height) / 2 - radius, 0);
  const axis = width >= height ? { x: halfLineLength, y: 0 } : { x: 0, y: halfLineLength };
  const rotatedAxis = rotatePoint(axis, ccwRotation);
  return {
    start: { x: pad.x - rotatedAxis.x, y: pad.y - rotatedAxis.y },
    end: { x: pad.x + rotatedAxis.x, y: pad.y + rotatedAxis.y },
    radius
  };
};
var getPolygonPointsForPad = (pad) => {
  if (pad.type === "pcb_smtpad") {
    if (pad.shape === "polygon") return pad.points;
    if (pad.shape === "rotated_rect") {
      return getRotatedRectPoints({
        x: pad.x,
        y: pad.y,
        width: pad.width,
        height: pad.height,
        ccwRotation: pad.ccw_rotation
      });
    }
  }
  if (pad.type === "pcb_plated_hole" && "rect_pad_width" in pad && "rect_pad_height" in pad) {
    return getRotatedRectPoints({
      x: pad.x,
      y: pad.y,
      width: pad.rect_pad_width,
      height: pad.rect_pad_height,
      ccwRotation: "rect_ccw_rotation" in pad && typeof pad.rect_ccw_rotation === "number" ? pad.rect_ccw_rotation : 0
    });
  }
  throw new Error(
    `Expected polygonal pad geometry, got ${pad.type} with shape "${pad.shape}"`
  );
};
var getPolygonEdges = (points) => points.map(
  (point, index) => [point, points[(index + 1) % points.length]]
);
var getClosestPointsBetweenSegments = (a1, a2, b1, b2) => {
  const intersection = getSegmentIntersection(a1, a2, b1, b2);
  if (intersection) {
    return {
      distance: 0,
      pointOnA: intersection,
      pointOnB: intersection,
      center: intersection
    };
  }
  const candidates = [
    { pointOnA: a1, pointOnB: pointToSegmentClosestPoint(a1, b1, b2) },
    { pointOnA: a2, pointOnB: pointToSegmentClosestPoint(a2, b1, b2) },
    { pointOnA: pointToSegmentClosestPoint(b1, a1, a2), pointOnB: b1 },
    { pointOnA: pointToSegmentClosestPoint(b2, a1, a2), pointOnB: b2 }
  ];
  let best = candidates[0];
  let bestDistanceSquared = distSq(best.pointOnA, best.pointOnB);
  for (const candidate of candidates.slice(1)) {
    const candidateDistanceSquared = distSq(
      candidate.pointOnA,
      candidate.pointOnB
    );
    if (candidateDistanceSquared < bestDistanceSquared) {
      best = candidate;
      bestDistanceSquared = candidateDistanceSquared;
    }
  }
  return {
    distance: segmentToSegmentMinDistance(a1, a2, b1, b2),
    pointOnA: best.pointOnA,
    pointOnB: best.pointOnB,
    center: {
      x: (best.pointOnA.x + best.pointOnB.x) / 2,
      y: (best.pointOnA.y + best.pointOnB.y) / 2
    }
  };
};
var getSegmentToPolygonClearanceFromPoints = (start, end, polygon) => {
  if (polygon.length < 3) {
    return {
      distance: Number.POSITIVE_INFINITY,
      center: start,
      tracePoint: start,
      obstaclePoint: start
    };
  }
  const intersections = getPolygonEdges(polygon).map(
    ([edgeStart, edgeEnd]) => getSegmentIntersection(start, end, edgeStart, edgeEnd)
  ).filter((point) => point !== null);
  if (intersections.length > 0) {
    const dx = end.x - start.x;
    const dy = end.y - start.y;
    const lengthSquared = dx * dx + dy * dy;
    intersections.sort((a, b) => {
      const ta = ((a.x - start.x) * dx + (a.y - start.y) * dy) / lengthSquared;
      const tb = ((b.x - start.x) * dx + (b.y - start.y) * dy) / lengthSquared;
      return ta - tb;
    });
    return {
      distance: 0,
      center: intersections[0],
      tracePoint: intersections[0],
      obstaclePoint: intersections[0]
    };
  }
  if (isPointInsidePolygon(start, polygon) || isPointInsidePolygon(end, polygon)) {
    const center = {
      x: (start.x + end.x) / 2,
      y: (start.y + end.y) / 2
    };
    return {
      distance: 0,
      center,
      tracePoint: center,
      obstaclePoint: center
    };
  }
  let best = getClosestPointsBetweenSegments(
    start,
    end,
    polygon[0],
    polygon[1]
  );
  for (const [edgeStart, edgeEnd] of getPolygonEdges(polygon).slice(1)) {
    const candidate = getClosestPointsBetweenSegments(
      start,
      end,
      edgeStart,
      edgeEnd
    );
    if (candidate.distance < best.distance) best = candidate;
  }
  return {
    distance: best.distance,
    center: best.center,
    tracePoint: best.pointOnA,
    obstaclePoint: best.pointOnB
  };
};
var getSegmentToPillClearance = (segment, pad) => {
  const pill2 = getPillCenterLineForPad(pad);
  const closest = getClosestPointsBetweenSegments(
    { x: segment.x1, y: segment.y1 },
    { x: segment.x2, y: segment.y2 },
    pill2.start,
    pill2.end
  );
  return {
    distance: closest.distance,
    center: closest.center,
    radius: pill2.radius,
    tracePoint: closest.pointOnA,
    obstaclePoint: closest.pointOnB
  };
};

// lib/check-traces-are-contiguous/is-point-in-pad.ts
function getDistanceBetweenPoints(pointA, pointB) {
  return Math.sqrt((pointB.x - pointA.x) ** 2 + (pointB.y - pointA.y) ** 2);
}
var POINT_ON_SEGMENT_TOLERANCE_MM = 1e-9;
var POINT_IN_PAD_TOLERANCE_MM = 1e-9;
function isPointOnSegment(point, segment) {
  const crossProduct = (point.y - segment.start.y) * (segment.end.x - segment.start.x) - (point.x - segment.start.x) * (segment.end.y - segment.start.y);
  if (Math.abs(crossProduct) > POINT_ON_SEGMENT_TOLERANCE_MM) return false;
  const dotProduct = (point.x - segment.start.x) * (segment.end.x - segment.start.x) + (point.y - segment.start.y) * (segment.end.y - segment.start.y);
  if (dotProduct < -POINT_ON_SEGMENT_TOLERANCE_MM) return false;
  const squaredLength = (segment.end.x - segment.start.x) ** 2 + (segment.end.y - segment.start.y) ** 2;
  return dotProduct <= squaredLength + POINT_ON_SEGMENT_TOLERANCE_MM;
}
function isPointInPolygon(point, polygon) {
  let inside = false;
  for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
    const pi = polygon[i];
    const pj = polygon[j];
    if (isPointOnSegment(point, { start: pi, end: pj })) return true;
    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;
    if (intersects) inside = !inside;
  }
  return inside;
}
function isPointInPad(point, pad) {
  if (pad.type === "pcb_smtpad") {
    if (pad.shape === "circle") {
      return getDistanceBetweenPoints(point, pad) <= pad.radius + POINT_IN_PAD_TOLERANCE_MM;
    }
    if (pad.shape === "rect") {
      const halfWidth = pad.width / 2;
      const halfHeight = pad.height / 2;
      return Math.abs(point.x - pad.x) <= halfWidth + POINT_IN_PAD_TOLERANCE_MM && Math.abs(point.y - pad.y) <= halfHeight + POINT_IN_PAD_TOLERANCE_MM;
    }
    if (pad.shape === "rotated_rect") {
      return isPointInPolygon(point, getPolygonPointsForPad(pad));
    }
    if (pad.shape === "pill" || pad.shape === "rotated_pill") {
      if (pad.shape === "rotated_pill") {
        const pill2 = getPillCenterLineForPad(pad);
        return pointToSegmentDistance(point, pill2.start, pill2.end) <= pill2.radius + POINT_IN_PAD_TOLERANCE_MM;
      }
      const halfWidth = pad.width / 2;
      const halfHeight = pad.height / 2;
      const radius = pad.radius;
      if (Math.abs(point.x - pad.x) <= halfWidth - radius + POINT_IN_PAD_TOLERANCE_MM && Math.abs(point.y - pad.y) <= halfHeight + POINT_IN_PAD_TOLERANCE_MM) {
        return true;
      }
      const cornerX = Math.max(
        Math.abs(point.x - pad.x) - (halfWidth - radius),
        0
      );
      const cornerY = Math.max(
        Math.abs(point.y - pad.y) - (halfHeight - radius),
        0
      );
      const radiusWithTolerance = radius + POINT_IN_PAD_TOLERANCE_MM;
      return cornerX * cornerX + cornerY * cornerY <= radiusWithTolerance * radiusWithTolerance;
    }
    if (pad.shape === "polygon") {
      return isPointInPolygon(point, pad.points);
    }
  }
  if (pad.type === "pcb_plated_hole") {
    if (pad.shape === "circle") {
      return getDistanceBetweenPoints(point, pad) <= pad.outer_diameter / 2 + POINT_IN_PAD_TOLERANCE_MM;
    }
    if ("rect_pad_width" in pad && "rect_pad_height" in pad) {
      return isPointInPolygon(point, getPolygonPointsForPad(pad));
    }
    if (pad.shape === "oval" || pad.shape === "pill") {
      return Math.abs(point.x - pad.x) <= pad.outer_width / 2 + POINT_IN_PAD_TOLERANCE_MM && Math.abs(point.y - pad.y) <= pad.outer_height / 2 + POINT_IN_PAD_TOLERANCE_MM;
    }
  }
  return false;
}

// lib/add-start-and-end-port-ids-if-missing.ts
function distance(x1, y1, x2, y2) {
  return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}
var addStartAndEndPortIdsIfMissing = (soup) => {
  const pcbPorts = soup.filter((item) => item.type === "pcb_port");
  const pcbSmtPads = soup.filter(
    (item) => item.type === "pcb_smtpad"
  );
  const pcbTraces = soup.filter((item) => item.type === "pcb_trace");
  function findPortIdOverlappingPoint(point, options = {}) {
    const traceWidth = options.traceWidth || 0;
    const directPort = pcbPorts.find(
      (port) => distance(port.x, port.y, point.x, point.y) < 0.01
    );
    if (directPort) return directPort.pcb_port_id;
    if (options.isFirstOrLastPoint) {
      const smtPad = pcbSmtPads.find((pad) => {
        if (pad.shape === "rect") {
          return Math.abs(point.x - pad.x) < pad.width / 2 + traceWidth / 2 && Math.abs(point.y - pad.y) < pad.height / 2 + traceWidth / 2;
        } else if (pad.shape === "circle") {
          return distance(point.x, point.y, pad.x, pad.y) < pad.radius;
        } else if (pad.shape === "pill" || pad.shape === "rotated_pill") {
          return isPointInPad(point, pad);
        }
      });
      if (smtPad) return smtPad.pcb_port_id ?? null;
    }
    return null;
  }
  for (const trace of pcbTraces) {
    for (let index = 0; index < trace.route.length; index++) {
      const segment = trace.route[index];
      const isFirstOrLastPoint = index === 0 || index === trace.route.length - 1;
      if (segment.route_type === "wire") {
        if (!segment.start_pcb_port_id && index === 0) {
          const startPortId = findPortIdOverlappingPoint(segment, {
            isFirstOrLastPoint,
            traceWidth: segment.width
          });
          if (startPortId) {
            segment.start_pcb_port_id = startPortId;
          }
        }
        if (!segment.end_pcb_port_id && index === trace.route.length - 1) {
          const endPortId = findPortIdOverlappingPoint(segment, {
            isFirstOrLastPoint,
            traceWidth: segment.width
          });
          if (endPortId) {
            segment.end_pcb_port_id = endPortId;
          }
        }
      }
    }
  }
};

// lib/check-each-pcb-port-connected-to-pcb-trace.ts
import {
  getFullConnectivityMapFromCircuitJson,
  PcbConnectivityMap
} from "circuit-json-to-connectivity-map";

// lib/util/copper-pour-connectivity.ts
import * as Flatten2 from "@flatten-js/core";
import { getPrimaryId } from "@tscircuit/circuit-json-util";

// lib/check-copper-to-board-edge-clearance.ts
import * as Flatten from "@flatten-js/core";

// node_modules/@tscircuit/jlcpcb-manufacturing-specs/lib/jlcpcb-manufacturing-specs.ts
var jlcMinTolerances = {
  min_trace_width: 0.1,
  min_via_hole_edge_to_via_hole_edge_clearance: 0.1,
  min_plated_hole_drill_edge_to_drill_edge_clearance: 0.15,
  min_trace_to_pad_edge_clearance: 0.1,
  min_pad_edge_to_pad_edge_clearance: 0.1,
  min_board_edge_clearance: 0.2,
  min_via_hole_diameter: 0.2,
  min_via_pad_diameter: 0.3
};

// lib/drc-defaults.ts
var DEFAULT_TRACE_MARGIN = 0.1;
var DEFAULT_TRACE_THICKNESS = jlcMinTolerances.min_trace_width;
var DEFAULT_VIA_DIAMETER = jlcMinTolerances.min_via_pad_diameter;
var DEFAULT_VIA_BOARD_MARGIN = jlcMinTolerances.min_board_edge_clearance;
var DEFAULT_SAME_NET_VIA_MARGIN = jlcMinTolerances.min_via_hole_edge_to_via_hole_edge_clearance;
var DEFAULT_DIFFERENT_NET_VIA_MARGIN = jlcMinTolerances.min_via_hole_edge_to_via_hole_edge_clearance;
var DEFAULT_PAD_PAD_CLEARANCE = jlcMinTolerances.min_pad_edge_to_pad_edge_clearance;
var EPSILON = 5e-3;
var getPcbBoard = (circuitJson) => circuitJson.find((el) => el.type === "pcb_board");
var getBoardDrcValue = (board, key) => board?.[key];

// lib/check-copper-to-board-edge-clearance.ts
import { applyToPoint, rotateDEG } from "transformation-matrix";
var toPcbComponentId = (id) => id;
var GEOMETRY_EPSILON = 1e-9;
var pointsToPolygon = (points) => {
  if (points.length < 3) return null;
  return new Flatten.Polygon(points.map(({ x, y }) => new Flatten.Point(x, y)));
};
var brepRingToPolygon = (vertices) => {
  const ring = [];
  for (const vertex of vertices) {
    const previous = ring.at(-1);
    if (!previous || !new Flatten.Point(previous.x, previous.y).equalTo(
      new Flatten.Point(vertex.x, vertex.y)
    )) ring.push(vertex);
  }
  if (ring.length > 1 && new Flatten.Point(ring[0].x, ring[0].y).equalTo(
    new Flatten.Point(ring.at(-1).x, ring.at(-1).y)
  )) {
    ring.pop();
  }
  if (ring.length < 3) return null;
  const edges = [];
  for (let index = 0; index < ring.length; index++) {
    const start = ring[index];
    const end = ring[(index + 1) % ring.length];
    const startPoint = new Flatten.Point(start.x, start.y);
    const endPoint = new Flatten.Point(end.x, end.y);
    const bulge = start.bulge ?? 0;
    if (Math.abs(bulge) <= GEOMETRY_EPSILON) {
      edges.push(new Flatten.Segment(startPoint, endPoint));
      continue;
    }
    const chordLength = startPoint.distanceTo(endPoint)[0];
    if (chordLength <= GEOMETRY_EPSILON) continue;
    const midpoint2 = {
      x: (start.x + end.x) / 2,
      y: (start.y + end.y) / 2
    };
    const leftNormal = {
      x: -(end.y - start.y) / chordLength,
      y: (end.x - start.x) / chordLength
    };
    const centerOffset = chordLength * (1 - bulge * bulge) / (4 * bulge);
    const center = new Flatten.Point(
      midpoint2.x + leftNormal.x * centerOffset,
      midpoint2.y + leftNormal.y * centerOffset
    );
    const radius = chordLength * (1 + bulge * bulge) / (4 * Math.abs(bulge));
    edges.push(
      new Flatten.Arc(
        center,
        radius,
        Math.atan2(start.y - center.y, start.x - center.x),
        Math.atan2(end.y - center.y, end.x - center.x),
        bulge > 0
      )
    );
  }
  if (edges.length < 3) return null;
  const polygon = new Flatten.Polygon();
  polygon.addFace(edges);
  return polygon;
};
var boardToPolygon = (board) => {
  if (board.outline && board.outline.length >= 3) {
    return pointsToPolygon(board.outline);
  }
  if (!board.center || typeof board.width !== "number" || typeof board.height !== "number") {
    return null;
  }
  const halfWidth = board.width / 2;
  const halfHeight = board.height / 2;
  return pointsToPolygon([
    { x: board.center.x - halfWidth, y: board.center.y - halfHeight },
    { x: board.center.x + halfWidth, y: board.center.y - halfHeight },
    { x: board.center.x + halfWidth, y: board.center.y + halfHeight },
    { x: board.center.x - halfWidth, y: board.center.y + halfHeight }
  ]);
};
var getRectanglePolygon = ({
  x,
  y,
  width,
  height,
  ccwRotationDegrees = 0
}) => {
  const halfWidth = width / 2;
  const halfHeight = height / 2;
  const rotationMatrix = rotateDEG(ccwRotationDegrees, x, y);
  return pointsToPolygon(
    [
      { x: x - halfWidth, y: y - halfHeight },
      { x: x + halfWidth, y: y - halfHeight },
      { x: x + halfWidth, y: y + halfHeight },
      { x: x - halfWidth, y: y + halfHeight }
    ].map((point) => applyToPoint(rotationMatrix, point))
  );
};
var roundedRect = ({
  x,
  y,
  width,
  height,
  cornerRadius,
  ccwRotationDegrees = 0
}) => {
  const radius = Math.max(0, Math.min(cornerRadius, width / 2, height / 2));
  if (radius <= GEOMETRY_EPSILON) {
    const polygon = getRectanglePolygon({
      x,
      y,
      width,
      height,
      ccwRotationDegrees
    });
    return polygon ? { kind: "shapes", shapes: [polygon] } : null;
  }
  const shapes = [];
  const innerWidth = width - 2 * radius;
  const innerHeight = height - 2 * radius;
  if (innerWidth > GEOMETRY_EPSILON) {
    const verticalBand = getRectanglePolygon({
      x,
      y,
      width: innerWidth,
      height,
      ccwRotationDegrees
    });
    if (verticalBand) shapes.push(verticalBand);
  }
  if (innerHeight > GEOMETRY_EPSILON) {
    const horizontalBand = getRectanglePolygon({
      x,
      y,
      width,
      height: innerHeight,
      ccwRotationDegrees
    });
    if (horizontalBand) shapes.push(horizontalBand);
  }
  const halfInnerWidth = innerWidth / 2;
  const halfInnerHeight = innerHeight / 2;
  const rotationMatrix = rotateDEG(ccwRotationDegrees, x, y);
  const cornerCenters = [
    { x: x - halfInnerWidth, y: y - halfInnerHeight },
    { x: x + halfInnerWidth, y: y - halfInnerHeight },
    { x: x + halfInnerWidth, y: y + halfInnerHeight },
    { x: x - halfInnerWidth, y: y + halfInnerHeight }
  ].map((point) => applyToPoint(rotationMatrix, point)).filter(
    (point, index, points) => points.findIndex(
      (candidate) => Math.abs(candidate.x - point.x) <= GEOMETRY_EPSILON && Math.abs(candidate.y - point.y) <= GEOMETRY_EPSILON
    ) === index
  );
  shapes.push(
    ...cornerCenters.map(
      (center) => new Flatten.Circle(new Flatten.Point(center.x, center.y), radius)
    )
  );
  return shapes.length > 0 ? { kind: "shapes", shapes } : null;
};
var pill = ({
  x,
  y,
  width,
  height,
  radius,
  ccwRotationDegrees = 0
}) => {
  const halfLineLength = Math.max(Math.max(width, height) / 2 - radius, 0);
  const localAxis = width >= height ? { x: halfLineLength, y: 0 } : { x: 0, y: halfLineLength };
  const axis = applyToPoint(rotateDEG(ccwRotationDegrees), localAxis);
  if (halfLineLength <= GEOMETRY_EPSILON) {
    return {
      kind: "shapes",
      shapes: [new Flatten.Circle(new Flatten.Point(x, y), radius)]
    };
  }
  return {
    kind: "pill",
    centerLine: new Flatten.Segment(
      new Flatten.Point(x - axis.x, y - axis.y),
      new Flatten.Point(x + axis.x, y + axis.y)
    ),
    radius
  };
};
var getSmtPadGeometry = (pad) => {
  switch (pad.shape) {
    case "circle":
      return {
        kind: "shapes",
        shapes: [
          new Flatten.Circle(new Flatten.Point(pad.x, pad.y), pad.radius)
        ]
      };
    case "rect":
      return roundedRect({
        x: pad.x,
        y: pad.y,
        width: pad.width,
        height: pad.height,
        cornerRadius: pad.rect_border_radius ?? pad.corner_radius ?? 0
      });
    case "rotated_rect":
      return roundedRect({
        x: pad.x,
        y: pad.y,
        width: pad.width,
        height: pad.height,
        cornerRadius: pad.rect_border_radius ?? pad.corner_radius ?? 0,
        ccwRotationDegrees: pad.ccw_rotation
      });
    case "pill":
      return pill({
        x: pad.x,
        y: pad.y,
        width: pad.width,
        height: pad.height,
        radius: pad.radius
      });
    case "rotated_pill":
      return pill({
        x: pad.x,
        y: pad.y,
        width: pad.width,
        height: pad.height,
        radius: pad.radius,
        ccwRotationDegrees: pad.ccw_rotation
      });
    case "polygon": {
      const polygon = pointsToPolygon(pad.points);
      return polygon ? { kind: "shapes", shapes: [polygon] } : null;
    }
  }
};
var getPlatedHoleGeometry = (platedHole, componentCcwRotationDegrees) => {
  switch (platedHole.shape) {
    case "circle":
      return {
        kind: "shapes",
        shapes: [
          new Flatten.Circle(
            new Flatten.Point(platedHole.x, platedHole.y),
            platedHole.outer_diameter / 2
          )
        ]
      };
    case "oval":
    case "pill":
      return pill({
        x: platedHole.x,
        y: platedHole.y,
        width: platedHole.outer_width,
        height: platedHole.outer_height,
        radius: Math.min(
          platedHole.outer_width / 2,
          platedHole.outer_height / 2
        ),
        ccwRotationDegrees: platedHole.ccw_rotation
      });
    case "circular_hole_with_rect_pad":
    case "pill_hole_with_rect_pad":
    case "rotated_pill_hole_with_rect_pad":
      return roundedRect({
        x: platedHole.x,
        y: platedHole.y,
        width: platedHole.rect_pad_width,
        height: platedHole.rect_pad_height,
        cornerRadius: platedHole.rect_border_radius ?? 0,
        ccwRotationDegrees: "rect_ccw_rotation" in platedHole ? platedHole.rect_ccw_rotation ?? 0 : 0
      });
    case "hole_with_polygon_pad": {
      const ccwRotationDegrees = platedHole.ccw_rotation ?? componentCcwRotationDegrees;
      const rotationMatrix = rotateDEG(ccwRotationDegrees);
      const polygon = pointsToPolygon(
        platedHole.pad_outline.map((point) => {
          const rotatedPoint = applyToPoint(rotationMatrix, point);
          return {
            x: platedHole.x + rotatedPoint.x,
            y: platedHole.y + rotatedPoint.y
          };
        })
      );
      return polygon ? { kind: "shapes", shapes: [polygon] } : null;
    }
  }
};
var getCopperGeometry = (element, componentCcwRotationsById) => {
  if (element.type === "pcb_via") {
    return {
      kind: "shapes",
      shapes: [
        new Flatten.Circle(
          new Flatten.Point(element.x, element.y),
          element.outer_diameter / 2
        )
      ]
    };
  }
  if (element.type === "pcb_smtpad") return getSmtPadGeometry(element);
  if (element.type === "pcb_plated_hole") {
    return getPlatedHoleGeometry(
      element,
      element.pcb_component_id ? componentCcwRotationsById.get(
        toPcbComponentId(element.pcb_component_id)
      ) ?? 0 : 0
    );
  }
  let polygon;
  switch (element.shape) {
    case "rect":
      polygon = getRectanglePolygon({
        x: element.center.x,
        y: element.center.y,
        width: element.width,
        height: element.height,
        ccwRotationDegrees: element.rotation ?? 0
      });
      break;
    case "polygon":
      polygon = pointsToPolygon(element.points);
      break;
    case "brep":
      polygon = brepRingToPolygon(element.brep_shape.outer_ring.vertices);
      break;
  }
  return polygon ? { kind: "shapes", shapes: [polygon] } : null;
};
var getCopperElementId = (element) => {
  if (element.type === "pcb_via") return element.pcb_via_id;
  if (element.type === "pcb_smtpad") return element.pcb_smtpad_id;
  if (element.type === "pcb_plated_hole") return element.pcb_plated_hole_id;
  return element.pcb_copper_pour_id;
};
var getCopperElementLabel = (element) => {
  if (element.type === "pcb_via") return "Via";
  if (element.type === "pcb_smtpad") return "SMT pad";
  if (element.type === "pcb_plated_hole") return "Plated hole";
  return "Copper pour";
};
var measureClearance = (board, geometry) => {
  if (geometry.kind === "shapes") {
    const isInside2 = geometry.shapes.every((shape) => board.contains(shape));
    return {
      isInside: isInside2,
      clearance: isInside2 ? Math.min(
        ...geometry.shapes.map((shape) => board.distanceTo(shape)[0])
      ) : 0
    };
  }
  const centerLineClearance = board.distanceTo(geometry.centerLine)[0];
  const clearance = centerLineClearance - geometry.radius;
  const isInside = board.contains(geometry.centerLine) && clearance >= -GEOMETRY_EPSILON;
  return {
    isInside,
    clearance: isInside ? Math.max(0, clearance) : 0
  };
};
function checkCopperToBoardEdgeClearance(circuitJson) {
  const board = getPcbBoard(circuitJson);
  if (!board) return [];
  const boardPolygon = boardToPolygon(board);
  if (!boardPolygon) return [];
  const requiredClearance = getBoardDrcValue(board, "min_board_edge_clearance") ?? jlcMinTolerances.min_board_edge_clearance;
  if (requiredClearance === void 0) return [];
  const allowedOffBoardComponentIds = new Set(
    circuitJson.filter(
      (element) => element.type === "pcb_component"
    ).filter((component) => component.is_allowed_to_be_off_board).map((component) => toPcbComponentId(component.pcb_component_id))
  );
  const copperElements = circuitJson.filter(
    (element) => element.type === "pcb_via" || element.type === "pcb_smtpad" || element.type === "pcb_plated_hole" || element.type === "pcb_copper_pour"
  );
  const componentCcwRotationsById = new Map(
    circuitJson.filter(
      (element) => element.type === "pcb_component"
    ).map((component) => [
      toPcbComponentId(component.pcb_component_id),
      component.rotation
    ])
  );
  const errors = [];
  for (const element of copperElements) {
    if ((element.type === "pcb_smtpad" || element.type === "pcb_plated_hole") && element.pcb_component_id && allowedOffBoardComponentIds.has(
      toPcbComponentId(element.pcb_component_id)
    )) {
      continue;
    }
    const geometry = getCopperGeometry(element, componentCcwRotationsById);
    if (!geometry) continue;
    const { isInside, clearance } = measureClearance(boardPolygon, geometry);
    if (isInside && clearance + GEOMETRY_EPSILON >= requiredClearance) {
      continue;
    }
    const id = getCopperElementId(element);
    const label = getCopperElementLabel(element);
    errors.push({
      type: "pcb_placement_error",
      pcb_placement_error_id: `copper_too_close_to_board_edge_${id}`,
      error_type: "pcb_placement_error",
      message: `${label} ${id} violates copper-to-board-edge clearance (measured ${clearance.toFixed(3)}mm, required ${requiredClearance.toFixed(3)}mm)`
    });
  }
  return errors;
}

// lib/util/getLayersOfPcbElement.ts
import { all_layers } from "circuit-json";
function getLayersOfPcbElement(obj) {
  if (obj.type === "pcb_trace_segment") {
    return [obj.layer];
  }
  if (obj.type === "pcb_smtpad") {
    return [obj.layer];
  }
  if (obj.type === "pcb_plated_hole") {
    return Array.isArray(obj.layers) ? obj.layers : [...all_layers];
  }
  if (obj.type === "pcb_hole") {
    return [...all_layers];
  }
  if (obj.type === "pcb_via") {
    return Array.isArray(obj.layers) ? obj.layers : [...all_layers];
  }
  if (obj.type === "pcb_keepout") {
    return Array.isArray(obj.layers) ? obj.layers : [];
  }
  return [];
}

// lib/data-structures/SpatialIndex.ts
var SpatialObjectIndex = class {
  buckets;
  objectsById;
  getBounds;
  getId;
  CELL_SIZE = 0.4;
  constructor({
    objects,
    getBounds,
    getId,
    CELL_SIZE
  }) {
    this.buckets = /* @__PURE__ */ new Map();
    this.objectsById = /* @__PURE__ */ new Map();
    this.getBounds = getBounds;
    this.getId = getId ?? (() => this._getNextId());
    this.CELL_SIZE = CELL_SIZE ?? this.CELL_SIZE;
    for (const obj of objects) {
      this.addObject(obj);
    }
  }
  _idCounter = 0;
  _getNextId() {
    return `${this._idCounter++}`;
  }
  addObject(obj) {
    const bounds2 = this.getBounds(obj);
    const spatialIndexId = this.getId(obj);
    const objWithId = { ...obj, spatialIndexId };
    this.objectsById.set(spatialIndexId, objWithId);
    const minBucketX = Math.floor(bounds2.minX / this.CELL_SIZE);
    const minBucketY = Math.floor(bounds2.minY / this.CELL_SIZE);
    const maxBucketX = Math.floor(bounds2.maxX / this.CELL_SIZE);
    const maxBucketY = Math.floor(bounds2.maxY / this.CELL_SIZE);
    for (let bx = minBucketX; bx <= maxBucketX; bx++) {
      for (let by = minBucketY; by <= maxBucketY; by++) {
        const bucketKey = `${bx}x${by}`;
        const bucket = this.buckets.get(bucketKey);
        if (!bucket) {
          this.buckets.set(bucketKey, [objWithId]);
        } else {
          bucket.push(objWithId);
        }
      }
    }
  }
  removeObject(id) {
    const obj = this.objectsById.get(id);
    if (!obj) return false;
    this.objectsById.delete(id);
    const bounds2 = this.getBounds(obj);
    const minBucketX = Math.floor(bounds2.minX / this.CELL_SIZE);
    const minBucketY = Math.floor(bounds2.minY / this.CELL_SIZE);
    const maxBucketX = Math.floor(bounds2.maxX / this.CELL_SIZE);
    const maxBucketY = Math.floor(bounds2.maxY / this.CELL_SIZE);
    for (let bx = minBucketX; bx <= maxBucketX; bx++) {
      for (let by = minBucketY; by <= maxBucketY; by++) {
        const bucketKey = `${bx}x${by}`;
        const bucket = this.buckets.get(bucketKey);
        if (bucket) {
          const index = bucket.findIndex((item) => item.spatialIndexId === id);
          if (index !== -1) {
            bucket.splice(index, 1);
            if (bucket.length === 0) {
              this.buckets.delete(bucketKey);
            }
          }
        }
      }
    }
    return true;
  }
  getBucketKey(x, y) {
    return `${Math.floor(x / this.CELL_SIZE)}x${Math.floor(y / this.CELL_SIZE)}`;
  }
  getObjectsInBounds(bounds2, margin = 0) {
    const objects = [];
    const addedIds = /* @__PURE__ */ new Set();
    const minBucketX = Math.floor((bounds2.minX - margin) / this.CELL_SIZE);
    const minBucketY = Math.floor((bounds2.minY - margin) / this.CELL_SIZE);
    const maxBucketX = Math.floor((bounds2.maxX + margin) / this.CELL_SIZE);
    const maxBucketY = Math.floor((bounds2.maxY + margin) / this.CELL_SIZE);
    for (let bx = minBucketX; bx <= maxBucketX; bx++) {
      for (let by = minBucketY; by <= maxBucketY; by++) {
        const bucketKey = `${bx}x${by}`;
        const bucket = this.buckets.get(bucketKey) || [];
        for (const obj of bucket) {
          const id = obj.spatialIndexId;
          if (addedIds.has(id)) continue;
          addedIds.add(id);
          objects.push(obj);
        }
      }
    }
    return objects;
  }
};

// lib/util/copper-pour-connectivity.ts
var EPSILON2 = 1e-9;
var polygonFromPoints = (points) => new Flatten2.Polygon(points.map(({ x, y }) => new Flatten2.Point(x, y)));
function pourPolygon(pour) {
  if (pour.shape === "polygon") return polygonFromPoints(pour.points);
  if (pour.shape === "rect")
    return polygonFromPoints(
      getRotatedRectPoints({
        x: pour.center.x,
        y: pour.center.y,
        width: pour.width,
        height: pour.height,
        ccwRotation: pour.rotation ?? 0
      })
    );
  const polygon = brepRingToPolygon(pour.brep_shape.outer_ring.vertices);
  if (!polygon) return;
  for (const ring of pour.brep_shape.inner_rings) {
    const hole = brepRingToPolygon(ring.vertices);
    if (!hole) return;
    for (const face of hole.faces) polygon.addFace(face.shapes);
  }
  return polygon;
}
function capsulePolygon(start, end, radius) {
  if (start.distanceTo(end)[0] <= EPSILON2)
    return new Flatten2.Polygon(new Flatten2.Circle(start, radius));
  const angle = Math.atan2(end.y - start.y, end.x - start.x) + Math.PI / 2;
  const dx = radius * Math.cos(angle), dy = radius * Math.sin(angle);
  return new Flatten2.Polygon([
    new Flatten2.Segment(
      new Flatten2.Point(start.x + dx, start.y + dy),
      new Flatten2.Point(end.x + dx, end.y + dy)
    ),
    new Flatten2.Arc(end, radius, angle, angle - Math.PI, false),
    new Flatten2.Segment(
      new Flatten2.Point(end.x - dx, end.y - dy),
      new Flatten2.Point(start.x - dx, start.y - dy)
    ),
    new Flatten2.Arc(start, radius, angle - Math.PI, angle - 2 * Math.PI, false)
  ]);
}
function platedHoleDrill(pad) {
  const x = pad.x + ("hole_offset_x" in pad ? pad.hole_offset_x : 0);
  const y = pad.y + ("hole_offset_y" in pad ? pad.hole_offset_y : 0);
  if ("hole_diameter" in pad && pad.hole_diameter !== void 0)
    return pad.hole_diameter > 0 ? new Flatten2.Polygon(
      new Flatten2.Circle(new Flatten2.Point(x, y), pad.hole_diameter / 2)
    ) : void 0;
  if (!("hole_width" in pad && "hole_height" in pad) || pad.hole_width === void 0 || pad.hole_height === void 0)
    return void 0;
  const rotation = "hole_ccw_rotation" in pad ? pad.hole_ccw_rotation : "ccw_rotation" in pad ? pad.ccw_rotation ?? 0 : 0;
  const line = getPillCenterLineForPad({
    type: "pcb_smtpad",
    pcb_smtpad_id: "drill",
    shape: "rotated_pill",
    layer: "top",
    x,
    y,
    width: pad.hole_width,
    height: pad.hole_height,
    radius: Math.min(pad.hole_width, pad.hole_height) / 2,
    ccw_rotation: rotation
  });
  return capsulePolygon(
    new Flatten2.Point(line.start.x, line.start.y),
    new Flatten2.Point(line.end.x, line.end.y),
    line.radius
  );
}
function platedCopperPolygon(geometry, drill) {
  const shapes = geometry.kind === "pill" ? [
    capsulePolygon(
      geometry.centerLine.start,
      geometry.centerLine.end,
      geometry.radius
    )
  ] : geometry.shapes.map(
    (s) => s instanceof Flatten2.Circle ? new Flatten2.Polygon(s) : s
  );
  const outer = shapes.reduce((a, b) => Flatten2.BooleanOperations.unify(a, b));
  return drill ? Flatten2.BooleanOperations.subtract(outer, drill) : outer;
}
function representativePoints(shape) {
  if (shape instanceof Flatten2.Point) return [shape];
  if (shape instanceof Flatten2.Segment) return [shape.start, shape.end];
  return shape.vertices;
}
function touches(a, b) {
  const aShape = a.shape, bShape = b.shape;
  if (aShape instanceof Flatten2.Polygon && representativePoints(bShape).some((p) => aShape.contains(p)))
    return true;
  if (bShape instanceof Flatten2.Polygon && representativePoints(aShape).some((p) => bShape.contains(p)))
    return true;
  return a.shape.distanceTo(b.shape)[0] <= a.radius + b.radius + EPSILON2;
}
function bounds({ shape, radius }) {
  const box = shape.box;
  return {
    minX: box.xmin - radius,
    minY: box.ymin - radius,
    maxX: box.xmax + radius,
    maxY: box.ymax + radius
  };
}
function overlap(a, b) {
  return a.minX <= b.maxX + EPSILON2 && a.maxX + EPSILON2 >= b.minX && a.minY <= b.maxY + EPSILON2 && a.maxY + EPSILON2 >= b.minY;
}
var CopperPourConnectivity = class {
  constructor(circuitJson, connectivity) {
    this.connectivity = connectivity;
    const pours = circuitJson.filter((e) => e.type === "pcb_copper_pour");
    const netIds = new Set(
      pours.map(
        (p) => p.source_net_id ? connectivity.getNetConnectedToId(p.source_net_id) : void 0
      )
    );
    netIds.delete(void 0);
    const add = (id, layers, geometry, net, isPour = false, portId) => {
      if (!net || !netIds.has(net)) return;
      const node = {
        ...geometry,
        index: this.nodes.length,
        id,
        layers,
        net,
        isPour,
        portId,
        bounds: bounds(geometry)
      };
      if (!Object.values(node.bounds).every(Number.isFinite)) return;
      this.nodes.push(node);
      this.parents.push(node.index);
      this.byId.set(id, [...this.byId.get(id) ?? [], node]);
      if (isPour)
        this.poursByNet.set(net, [...this.poursByNet.get(net) ?? [], node]);
    };
    const addShapes = (id, layers, geometry, net, portId) => {
      if (!geometry) return;
      if (geometry.kind === "pill")
        add(
          id,
          layers,
          { shape: geometry.centerLine, radius: geometry.radius },
          net,
          false,
          portId
        );
      else
        for (const shape of geometry.shapes)
          add(
            id,
            layers,
            shape instanceof Flatten2.Circle ? { shape: shape.center, radius: shape.r } : { shape, radius: 0 },
            net,
            false,
            portId
          );
    };
    for (const element of circuitJson) {
      if (element.type === "pcb_port") {
        const net = connectivity.getNetConnectedToId(element.pcb_port_id);
        if (net && netIds.has(net)) {
          this.sourceNetByPort.set(element.pcb_port_id, net);
          this.portsByNet.set(net, [
            ...this.portsByNet.get(net) ?? [],
            element
          ]);
        }
      }
      if (element.type === "pcb_copper_pour") {
        const polygon = pourPolygon(element);
        if (polygon)
          add(
            element.pcb_copper_pour_id,
            [element.layer],
            { shape: polygon, radius: 0 },
            element.source_net_id ? connectivity.getNetConnectedToId(element.source_net_id) : void 0,
            true
          );
      }
      if (element.type === "pcb_smtpad" || element.type === "pcb_plated_hole") {
        const id = getPrimaryId(element);
        const net = connectivity.getNetConnectedToId(id) ?? (element.pcb_port_id ? connectivity.getNetConnectedToId(element.pcb_port_id) : void 0);
        if (!net || !netIds.has(net)) continue;
        const geometry = element.type === "pcb_smtpad" ? getSmtPadGeometry(element) : getPlatedHoleGeometry(element, 0);
        if (element.type === "pcb_plated_hole" && geometry) {
          const copper = platedCopperPolygon(geometry, platedHoleDrill(element));
          if (!copper.isEmpty())
            add(
              id,
              getLayersOfPcbElement(element),
              { shape: copper, radius: 0 },
              net,
              false,
              element.pcb_port_id
            );
        } else
          addShapes(
            id,
            getLayersOfPcbElement(element),
            geometry,
            net,
            element.pcb_port_id
          );
      }
      if (element.type === "pcb_via") {
        const net = connectivity.getNetConnectedToId(element.pcb_via_id) ?? (element.pcb_trace_id ? connectivity.getNetConnectedToId(element.pcb_trace_id) : void 0);
        if (!net || !netIds.has(net) || element.outer_diameter <= 0) continue;
        const center = new Flatten2.Point(element.x, element.y);
        const outer = new Flatten2.Polygon(
          new Flatten2.Circle(center, element.outer_diameter / 2)
        );
        const copper = element.hole_diameter > 0 ? Flatten2.BooleanOperations.subtract(
          outer,
          new Flatten2.Polygon(
            new Flatten2.Circle(center, element.hole_diameter / 2)
          )
        ) : outer;
        if (!copper.isEmpty())
          add(
            element.pcb_via_id,
            getLayersOfPcbElement(element),
            { shape: copper, radius: 0 },
            net
          );
      }
      if (element.type === "pcb_trace") {
        if (element.route_thickness_mode === "interpolated") continue;
        const net = connectivity.getNetConnectedToId(element.pcb_trace_id);
        if (!net || !netIds.has(net)) continue;
        for (let i = 1; i < element.route.length; i++) {
          const a = element.route[i - 1], b = element.route[i];
          if (a.route_type !== "wire" || b.route_type !== "wire" || a.layer !== b.layer || a.width <= 0)
            continue;
          add(
            element.pcb_trace_id,
            [a.layer],
            {
              shape: new Flatten2.Segment(
                new Flatten2.Point(a.x, a.y),
                new Flatten2.Point(b.x, b.y)
              ),
              radius: a.width / 2
            },
            net
          );
        }
      }
    }
    const spatial = new SpatialObjectIndex({
      objects: this.nodes,
      getBounds: (n) => n.bounds,
      getId: (n) => String(n.index),
      CELL_SIZE: 5
    });
    for (const a of this.nodes) {
      for (const b of spatial.getObjectsInBounds(a.bounds, EPSILON2)) {
        if (b.index >= a.index || a.net !== b.net || !a.layers.some((l) => b.layers.includes(l)) || !overlap(a.bounds, b.bounds))
          continue;
        if (this.root(a.index) === this.root(b.index)) continue;
        if (touches(a, b)) this.parents[this.root(a.index)] = this.root(b.index);
      }
    }
    for (const node of this.nodes) {
      if (node.isPour) this.groupsWithPour.add(this.root(node.index));
      if (node.portId) this.groupsWithPort.add(this.root(node.index));
    }
  }
  connectivity;
  nodes = [];
  parents = [];
  byId = /* @__PURE__ */ new Map();
  poursByNet = /* @__PURE__ */ new Map();
  portsByNet = /* @__PURE__ */ new Map();
  groupsWithPour = /* @__PURE__ */ new Set();
  groupsWithPort = /* @__PURE__ */ new Set();
  sourceNetByPort = /* @__PURE__ */ new Map();
  root(index) {
    if (this.parents[index] !== index)
      this.parents[index] = this.root(this.parents[index]);
    return this.parents[index];
  }
  portGroups(portId) {
    return new Set(
      this.nodes.filter((n) => n.portId === portId).map((n) => this.root(n.index))
    );
  }
  /** A pour connection must physically reach the other required ports. */
  portsConnectedThroughPour(portIds) {
    const first = portIds[0];
    if (!first) return false;
    const groups = [...this.portGroups(first)].filter(
      (group) => this.groupsWithPour.has(group)
    );
    return groups.some(
      (group) => portIds.every((id) => this.portGroups(id).has(group))
    );
  }
  portConnectedToPourNet(portId) {
    const net = this.sourceNetByPort.get(portId);
    if (!net) return false;
    const ports = this.portsByNet.get(net) ?? [];
    return ports.length > 0 && this.portsConnectedThroughPour(ports.map((p) => p.pcb_port_id));
  }
  traceConnectedToPortThroughPour(traceId, portId) {
    const groups = this.portGroups(portId);
    return (this.byId.get(traceId) ?? []).some(
      (node) => this.groupsWithPour.has(this.root(node.index)) && groups.has(this.root(node.index))
    );
  }
  endpointTouchesConnectedPour(point, traceId, width) {
    if (point.route_type !== "wire" || !Number.isFinite(width) || width < 0)
      return false;
    const net = this.connectivity.getNetConnectedToId(traceId);
    if (!net) return false;
    const endpoint = {
      shape: new Flatten2.Point(point.x, point.y),
      radius: width / 2
    };
    const endpointBounds = bounds(endpoint);
    return (this.poursByNet.get(net) ?? []).some(
      (pour) => pour.layers.includes(point.layer) && this.groupsWithPort.has(this.root(pour.index)) && overlap(endpointBounds, pour.bounds) && touches(endpoint, pour)
    );
  }
};

// lib/util/get-readable-names.ts
import {
  getReadableNameForElement,
  getReadableNameForPcbPort,
  getBoundsOfPcbElements
} from "@tscircuit/circuit-json-util";
var CIRCUIT_JSON_ID_PATTERN = /\b(?:pcb|source|schematic|subcircuit)_[a-z0-9_]+\b/i;
var sanitizeReadableName = (candidate, id, fallbackLabel) => {
  if (!candidate || candidate === id || CIRCUIT_JSON_ID_PATTERN.test(candidate)) {
    return fallbackLabel;
  }
  return candidate;
};
var firstReadableName = (candidates, id) => {
  for (const candidate of candidates) {
    const readableName = sanitizeReadableName(candidate, id, "");
    if (readableName) return readableName;
  }
  return "";
};
var getReadableNameForComponent = (circuitJson, pcbComponentId) => sanitizeReadableName(
  getReadableNameForElement(circuitJson, pcbComponentId),
  pcbComponentId,
  "component"
);
var getReadableNameForPort = (circuitJson, pcbPortId) => {
  const pcbPort = circuitJson.find(
    (element) => element.type === "pcb_port" && element.pcb_port_id === pcbPortId
  );
  if (pcbPort?.type === "pcb_port") {
    const sourcePort = circuitJson.find(
      (element) => element.type === "source_port" && element.source_port_id === pcbPort.source_port_id
    );
    const sourceComponent = sourcePort?.type === "source_port" ? circuitJson.find(
      (element) => element.type === "source_component" && element.source_component_id === sourcePort.source_component_id
    ) : null;
    const readableSourceComponentName = firstReadableName(
      [
        sourceComponent?.type === "source_component" ? sourceComponent.name : null
      ],
      sourceComponent?.type === "source_component" ? sourceComponent.source_component_id : ""
    );
    const readableSourcePortName = firstReadableName(
      [
        sourcePort?.type === "source_port" ? sourcePort.name : null,
        sourcePort?.type === "source_port" ? sourcePort.pin_number?.toString() : null,
        sourcePort?.type === "source_port" ? sourcePort.port_hints?.[0] : null
      ],
      sourcePort?.type === "source_port" ? sourcePort.source_port_id : ""
    );
    if (readableSourceComponentName && readableSourcePortName) {
      return `${readableSourceComponentName}.${readableSourcePortName}`;
    }
    if (readableSourcePortName) {
      return readableSourcePortName;
    }
  }
  return sanitizeReadableName(
    getReadableNameForPcbPort(circuitJson, pcbPortId) ?? getReadableNameForElement(circuitJson, pcbPortId),
    pcbPortId,
    "port"
  );
};
var getReadableNameForSourceTrace = (circuitJson, sourceTrace) => {
  const displayName = sanitizeReadableName(
    sourceTrace.display_name,
    sourceTrace.source_trace_id,
    ""
  );
  if (displayName) return displayName;
  const connectedPortNames = (sourceTrace.connected_source_port_ids ?? []).map((sourcePortId) => {
    const pcbPort = circuitJson.find(
      (element) => element.type === "pcb_port" && element.source_port_id === sourcePortId
    );
    if (pcbPort?.type === "pcb_port") {
      return getReadableNameForPort(circuitJson, pcbPort.pcb_port_id);
    }
    const sourcePort = circuitJson.find(
      (element) => element.type === "source_port" && element.source_port_id === sourcePortId
    );
    if (sourcePort?.type !== "source_port") return null;
    const sourceComponent = circuitJson.find(
      (element) => element.type === "source_component" && element.source_component_id === sourcePort.source_component_id
    );
    const sourceComponentName = sourceComponent?.type === "source_component" ? sanitizeReadableName(
      sourceComponent.name,
      sourceComponent.source_component_id,
      ""
    ) : "";
    const sourcePortName = firstReadableName(
      [
        sourcePort.name,
        sourcePort.pin_number?.toString(),
        sourcePort.port_hints?.[0]
      ],
      sourcePort.source_port_id
    );
    if (sourceComponentName && sourcePortName) {
      return `${sourceComponentName}.${sourcePortName}`;
    }
    return sourcePortName || null;
  }).filter((name) => Boolean(name));
  if (connectedPortNames.length >= 2) {
    return `${connectedPortNames[0]} to ${connectedPortNames[1]}`;
  }
  if (connectedPortNames.length === 1) {
    return `trace connected to ${connectedPortNames[0]}`;
  }
  return `trace ${sourceTrace.source_trace_id}`;
};
var getReadableNameForElementId = (circuitJson, elementId) => sanitizeReadableName(
  getReadableNameForElement(circuitJson, elementId),
  elementId,
  "element"
);
var containsCircuitJsonId = (message) => CIRCUIT_JSON_ID_PATTERN.test(message);
function getReadableNameForFootprintPad(circuitJson, pad, ordinal) {
  const padKind = pad.type === "pcb_smtpad" ? "SMD pad" : "through-hole pad";
  const portRef = pad.pcb_port_id ? getReadableNameForPort(circuitJson, pad.pcb_port_id) : null;
  const bounds2 = getBoundsOfPcbElements([pad]);
  const centerX = (bounds2.minX + bounds2.maxX) / 2;
  const centerY = (bounds2.minY + bounds2.maxY) / 2;
  const location = `(${centerX.toFixed(2)}mm, ${centerY.toFixed(2)}mm)`;
  if (portRef) return `${padKind} ${portRef} at ${location}`;
  return `${padKind} #${ordinal + 1} at ${location}`;
}

// lib/check-each-pcb-port-connected-to-pcb-trace.ts
function checkEachPcbPortConnectedToPcbTraces(circuitJson) {
  addStartAndEndPortIdsIfMissing(circuitJson);
  const sourceTraces = circuitJson.filter(
    (item) => item.type === "source_trace"
  );
  const pcbPorts = circuitJson.filter(
    (item) => item.type === "pcb_port"
  );
  const sourceNets = circuitJson.filter(
    (item) => item.type === "source_net"
  );
  const errors = [];
  const connectivityMap = getFullConnectivityMapFromCircuitJson(circuitJson);
  const pcbConnectivityMap = new PcbConnectivityMap(circuitJson);
  let pourConnectivity;
  const getPourConnectivity = () => pourConnectivity ??= new CopperPourConnectivity(
    circuitJson,
    connectivityMap
  );
  const sourcePortToPcbPort = /* @__PURE__ */ new Map();
  for (const pcbPort of pcbPorts) {
    sourcePortToPcbPort.set(pcbPort.source_port_id, pcbPort);
  }
  const sourceNetNameById = new Map(
    sourceNets.map((sourceNet) => [sourceNet.source_net_id, sourceNet.name])
  );
  for (const sourceTrace of sourceTraces) {
    const connectedSourcePortIds = sourceTrace.connected_source_port_ids;
    if (connectedSourcePortIds.length === 1 && sourceTrace.connected_source_net_ids.length > 0) {
      const pcbPort = sourcePortToPcbPort.get(connectedSourcePortIds[0]);
      if (!pcbPort) continue;
      const connectedPcbTraces = pcbConnectivityMap.getAllTracesConnectedToPort(
        pcbPort.pcb_port_id
      );
      if (connectedPcbTraces.length === 0 && !getPourConnectivity().portConnectedToPourNet(pcbPort.pcb_port_id)) {
        const connectedNetNames = sourceTrace.connected_source_net_ids.map((sourceNetId) => sourceNetNameById.get(sourceNetId)).filter((name) => Boolean(name));
        const netDescription = connectedNetNames.length > 0 ? `net [${connectedNetNames.join(", ")}]` : "its connected net";
        errors.push({
          type: "pcb_port_not_connected_error",
          message: `Port [${getReadableNameForPort(circuitJson, pcbPort.pcb_port_id)}] is not connected to ${netDescription} by a PCB trace.`,
          error_type: "pcb_port_not_connected_error",
          pcb_port_ids: [pcbPort.pcb_port_id],
          pcb_component_ids: pcbPort.pcb_component_id ? [pcbPort.pcb_component_id] : [],
          pcb_port_not_connected_error_id: `pcb_port_not_connected_error_trace_${sourceTrace.source_trace_id}`
        });
      }
      continue;
    }
    if (connectedSourcePortIds.length < 2) {
      continue;
    }
    const pcbPortsInTrace = [];
    const missingPcbPorts = [];
    for (const sourcePortId of connectedSourcePortIds) {
      const pcbPort = sourcePortToPcbPort.get(sourcePortId);
      if (pcbPort) {
        pcbPortsInTrace.push(pcbPort);
      } else {
        missingPcbPorts.push(sourcePortId);
      }
    }
    if (pcbPortsInTrace.length < 2) {
      continue;
    }
    const firstPcbPort = pcbPortsInTrace[0];
    const referenceNetId = connectivityMap.getNetConnectedToId(
      firstPcbPort.pcb_port_id
    );
    const netElementIds = connectivityMap.getIdsConnectedToNet(referenceNetId);
    const pcbTraceIds = netElementIds.filter(
      (id) => circuitJson.some(
        (element) => element.type === "pcb_trace" && ("pcb_trace_id" in element && element.pcb_trace_id === id || "route_id" in element && element.route_id === id)
      )
    );
    if (pcbTraceIds.length === 0 && !getPourConnectivity().portsConnectedThroughPour(
      pcbPortsInTrace.map((p) => p.pcb_port_id)
    )) {
      const uniqueComponentIds = new Set(
        pcbPortsInTrace.map((p) => p.pcb_component_id)
      );
      if (uniqueComponentIds.size > 1) {
        errors.push({
          type: "pcb_port_not_connected_error",
          message: `Ports [${pcbPortsInTrace.map((p) => getReadableNameForPort(circuitJson, p.pcb_port_id)).join(", ")}] are not connected together through the same net.`,
          error_type: "pcb_port_not_connected_error",
          pcb_port_ids: pcbPortsInTrace.map((p) => p.pcb_port_id),
          pcb_component_ids: pcbPortsInTrace.map((p) => p.pcb_component_id).filter((id) => id !== void 0),
          pcb_port_not_connected_error_id: `pcb_port_not_connected_error_trace_${sourceTrace.source_trace_id}`
        });
      }
    }
  }
  return errors;
}

// lib/check-each-pcb-trace-non-overlapping/check-each-pcb-trace-non-overlapping.ts
import { cju as cju2, getReadableNameForElement as getReadableNameForElement2 } from "@tscircuit/circuit-json-util";
import { getPrimaryId as getPrimaryId2 } from "@tscircuit/circuit-json-util";
import {
  segmentToBoundsMinDistance,
  segmentToCircleMinDistance as segmentToCircleMinDistance2
} from "@tscircuit/math-utils";
import { segmentToSegmentMinDistance as segmentToSegmentMinDistance3 } from "@tscircuit/math-utils";
import {
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson2
} from "circuit-json-to-connectivity-map";

// lib/check-pad-clearance/common.ts
import {
  cju,
  distanceBetweenCircleAndCircle,
  distanceBetweenCircleAndPolygon,
  distanceBetweenPolygonAndPolygon,
  getBoundsOfPcbElements as getBoundsOfPcbElements2
} from "@tscircuit/circuit-json-util";
import {
  midpoint,
  pointToSegmentClosestPoint as pointToSegmentClosestPoint2,
  segmentToCircleMinDistance,
  segmentToSegmentMinDistance as segmentToSegmentMinDistance2
} from "@tscircuit/math-utils";
var getPadBounds = (pad) => {
  if (pad.type === "pcb_keepout") {
    if (pad.shape === "outline") {
      return {
        minX: Math.min(...pad.outline.map((point) => point.x)),
        minY: Math.min(...pad.outline.map((point) => point.y)),
        maxX: Math.max(...pad.outline.map((point) => point.x)),
        maxY: Math.max(...pad.outline.map((point) => point.y))
      };
    }
    if (pad.shape === "circle") {
      return {
        minX: pad.center.x - pad.radius,
        minY: pad.center.y - pad.radius,
        maxX: pad.center.x + pad.radius,
        maxY: pad.center.y + pad.radius
      };
    }
    return {
      minX: pad.center.x - pad.width / 2,
      minY: pad.center.y - pad.height / 2,
      maxX: pad.center.x + pad.width / 2,
      maxY: pad.center.y + pad.height / 2
    };
  }
  return getBoundsOfPcbElements2([pad]);
};
var getPadCenter = (pad) => {
  if (pad.type === "pcb_keepout" && pad.shape !== "outline") return pad.center;
  const bounds2 = getPadBounds(pad);
  return midpoint(
    { x: bounds2.minX, y: bounds2.minY },
    { x: bounds2.maxX, y: bounds2.maxY }
  );
};
var getPadRadius = (pad) => {
  if (pad.type === "pcb_keepout" && pad.shape === "circle") return pad.radius;
  const bounds2 = getPadBounds(pad);
  return Math.min(bounds2.maxX - bounds2.minX, bounds2.maxY - bounds2.minY) / 2;
};
var isCircularPad = (pad) => pad.type === "pcb_via" || pad.shape === "circle";
var isPillPad = (pad) => pad.type === "pcb_smtpad" && (pad.shape === "pill" || pad.shape === "rotated_pill") || pad.type === "pcb_plated_hole" && (pad.shape === "oval" || pad.shape === "pill");
var getCircleShape = (pad) => {
  const center = getPadCenter(pad);
  return {
    kind: "circle",
    x: center.x,
    y: center.y,
    radius: getPadRadius(pad)
  };
};
var getPolygonShape = (pad) => {
  if (pad.type === "pcb_keepout") {
    if (pad.shape === "outline") {
      return {
        kind: "polygon",
        points: pad.outline
      };
    }
    if (pad.shape !== "rect") {
      throw new Error(`Expected rectangular keepout, got ${pad.shape}`);
    }
    return {
      kind: "polygon",
      points: [
        {
          x: pad.center.x - pad.width / 2,
          y: pad.center.y - pad.height / 2
        },
        {
          x: pad.center.x + pad.width / 2,
          y: pad.center.y - pad.height / 2
        },
        {
          x: pad.center.x + pad.width / 2,
          y: pad.center.y + pad.height / 2
        },
        {
          x: pad.center.x - pad.width / 2,
          y: pad.center.y + pad.height / 2
        }
      ]
    };
  }
  if (pad.type === "pcb_smtpad" && (pad.shape === "polygon" || pad.shape === "rotated_rect")) {
    return {
      kind: "polygon",
      points: getPolygonPointsForPad(pad)
    };
  }
  if (pad.type === "pcb_plated_hole" && "rect_pad_width" in pad && "rect_pad_height" in pad) {
    return {
      kind: "polygon",
      points: getPolygonPointsForPad(pad)
    };
  }
  const bounds2 = getPadBounds(pad);
  return {
    kind: "polygon",
    points: [
      { x: bounds2.minX, y: bounds2.minY },
      { x: bounds2.maxX, y: bounds2.minY },
      { x: bounds2.maxX, y: bounds2.maxY },
      { x: bounds2.minX, y: bounds2.maxY }
    ]
  };
};
var getPadToPadGap = (padA, padB) => {
  if (isPillPad(padA) && isPillPad(padB)) {
    const pillA = getPillCenterLineForPad(padA);
    const pillB = getPillCenterLineForPad(padB);
    return segmentToSegmentMinDistance2(
      pillA.start,
      pillA.end,
      pillB.start,
      pillB.end
    ) - pillA.radius - pillB.radius;
  }
  if (isPillPad(padA) && isCircularPad(padB)) {
    const pill2 = getPillCenterLineForPad(padA);
    return segmentToCircleMinDistance(pill2.start, pill2.end, getCircleShape(padB)) - pill2.radius;
  }
  if (isCircularPad(padA) && isPillPad(padB)) {
    const pill2 = getPillCenterLineForPad(padB);
    return segmentToCircleMinDistance(pill2.start, pill2.end, getCircleShape(padA)) - pill2.radius;
  }
  if (isPillPad(padA)) {
    const pill2 = getPillCenterLineForPad(padA);
    return getSegmentToPolygonClearanceFromPoints(
      pill2.start,
      pill2.end,
      getPolygonShape(padB).points
    ).distance - pill2.radius;
  }
  if (isPillPad(padB)) {
    const pill2 = getPillCenterLineForPad(padB);
    return getSegmentToPolygonClearanceFromPoints(
      pill2.start,
      pill2.end,
      getPolygonShape(padA).points
    ).distance - pill2.radius;
  }
  if (isCircularPad(padA) && isCircularPad(padB)) {
    return distanceBetweenCircleAndCircle(
      getCircleShape(padA),
      getCircleShape(padB)
    );
  }
  if (isCircularPad(padA)) {
    return distanceBetweenCircleAndPolygon(
      getCircleShape(padA),
      getPolygonShape(padB)
    );
  }
  if (isCircularPad(padB)) {
    return distanceBetweenCircleAndPolygon(
      getCircleShape(padB),
      getPolygonShape(padA)
    );
  }
  return distanceBetweenPolygonAndPolygon(
    getPolygonShape(padA),
    getPolygonShape(padB)
  );
};
var getPads = (circuitJson) => [
  ...cju(circuitJson).pcb_smtpad.list(),
  ...cju(circuitJson).pcb_plated_hole.list()
];
var getTraceSegments = (circuitJson) => {
  const pcbTraces = cju(circuitJson).pcb_trace.list();
  return pcbTraces.flatMap((pcbTrace) => {
    const segments = [];
    for (let i = 0; i < pcbTrace.route.length - 1; i++) {
      const p1 = pcbTrace.route[i];
      const p2 = pcbTrace.route[i + 1];
      if (p1.route_type !== "wire") continue;
      if (p2.route_type !== "wire") continue;
      if (p1.layer !== p2.layer) continue;
      segments.push({
        type: "pcb_trace_segment",
        pcb_trace_id: pcbTrace.pcb_trace_id,
        _pcbTrace: pcbTrace,
        thickness: "width" in p1 ? p1.width : "width" in p2 ? p2.width : DEFAULT_TRACE_THICKNESS,
        layer: p1.layer,
        x1: p1.x,
        y1: p1.y,
        x2: p2.x,
        y2: p2.y
      });
    }
    return segments;
  });
};
var getTraceCenter = (segment) => {
  const routePoints = segment._pcbTrace.route.flatMap((routePoint) => {
    if (routePoint.route_type === "through_pad") {
      return [routePoint.start, routePoint.end];
    }
    return [{ x: routePoint.x, y: routePoint.y }];
  });
  const firstPoint = routePoints[0];
  const lastPoint = routePoints[routePoints.length - 1];
  if (!firstPoint || !lastPoint) {
    return midpoint(
      { x: segment.x1, y: segment.y1 },
      { x: segment.x2, y: segment.y2 }
    );
  }
  return midpoint(firstPoint, lastPoint);
};
var getCenterBetweenCopperEdges = ({
  tracePoint,
  obstaclePoint,
  traceRadius,
  obstacleRadius
}) => {
  const dx = obstaclePoint.x - tracePoint.x;
  const dy = obstaclePoint.y - tracePoint.y;
  const distance3 = Math.hypot(dx, dy);
  if (distance3 === 0) return midpoint(tracePoint, obstaclePoint);
  const unitX = dx / distance3;
  const unitY = dy / distance3;
  const traceEdge = {
    x: tracePoint.x + unitX * traceRadius,
    y: tracePoint.y + unitY * traceRadius
  };
  const obstacleEdge = {
    x: obstaclePoint.x - unitX * obstacleRadius,
    y: obstaclePoint.y - unitY * obstacleRadius
  };
  return midpoint(traceEdge, obstacleEdge);
};
var getTraceObstacleClearance = (segment, obstacle) => {
  const start = { x: segment.x1, y: segment.y1 };
  const end = { x: segment.x2, y: segment.y2 };
  const traceRadius = segment.thickness / 2;
  if (obstacle.type === "pcb_via" || isCircularPad(obstacle)) {
    const circle = obstacle.type === "pcb_via" ? {
      x: obstacle.x,
      y: obstacle.y,
      radius: obstacle.outer_diameter / 2
    } : getCircleShape(obstacle);
    const closestPoint = pointToSegmentClosestPoint2(circle, start, end);
    return {
      gap: segmentToCircleMinDistance(start, end, circle) - traceRadius,
      center: getCenterBetweenCopperEdges({
        tracePoint: closestPoint,
        obstaclePoint: circle,
        traceRadius,
        obstacleRadius: circle.radius
      })
    };
  }
  if (isPillPad(obstacle)) {
    const clearance2 = getSegmentToPillClearance(segment, obstacle);
    return {
      gap: clearance2.distance - traceRadius - clearance2.radius,
      center: getCenterBetweenCopperEdges({
        tracePoint: clearance2.tracePoint,
        obstaclePoint: clearance2.obstaclePoint,
        traceRadius,
        obstacleRadius: clearance2.radius
      })
    };
  }
  const clearance = getSegmentToPolygonClearanceFromPoints(
    start,
    end,
    getPolygonShape(obstacle).points
  );
  return {
    gap: clearance.distance - traceRadius,
    center: getCenterBetweenCopperEdges({
      tracePoint: clearance.tracePoint,
      obstaclePoint: clearance.obstaclePoint,
      traceRadius,
      obstacleRadius: 0
    })
  };
};
var isTraceObstacleOverlap = (gap) => gap <= 0;

// lib/check-each-pcb-trace-non-overlapping/getClosestPointBetweenSegmentAndBounds.ts
var getClosestPointBetweenSegmentAndBounds = (segment, bounds2) => {
  const p1 = { x: segment.x1, y: segment.y1 };
  const p2 = { x: segment.x2, y: segment.y2 };
  const minX = bounds2.minX;
  const minY = bounds2.minY;
  const maxX = bounds2.maxX;
  const maxY = bounds2.maxY;
  if (p1.x === p2.x && p1.y === p2.y) {
    const closestX = Math.max(minX, Math.min(maxX, p1.x));
    const closestY = Math.max(minY, Math.min(maxY, p1.y));
    if (closestX === p1.x && closestY === p1.y) {
      return { x: p1.x, y: p1.y };
    }
    return { x: closestX, y: closestY };
  }
  const dx = p2.x - p1.x;
  const dy = p2.y - p1.y;
  const tMinX = dx !== 0 ? (minX - p1.x) / dx : Number.NEGATIVE_INFINITY;
  const tMaxX = dx !== 0 ? (maxX - p1.x) / dx : Number.POSITIVE_INFINITY;
  const tMinY = dy !== 0 ? (minY - p1.y) / dy : Number.NEGATIVE_INFINITY;
  const tMaxY = dy !== 0 ? (maxY - p1.y) / dy : Number.POSITIVE_INFINITY;
  const tEnter = Math.max(Math.min(tMinX, tMaxX), Math.min(tMinY, tMaxY));
  const tExit = Math.min(Math.max(tMinX, tMaxX), Math.max(tMinY, tMaxY));
  if (tEnter <= tExit && tExit >= 0 && tEnter <= 1) {
    const t = Math.max(0, Math.min(1, tEnter));
    return {
      x: p1.x + t * dx,
      y: p1.y + t * dy
    };
  }
  const closestToP1 = {
    x: Math.max(minX, Math.min(maxX, p1.x)),
    y: Math.max(minY, Math.min(maxY, p1.y))
  };
  const closestToP2 = {
    x: Math.max(minX, Math.min(maxX, p2.x)),
    y: Math.max(minY, Math.min(maxY, p2.y))
  };
  const distToP1Squared = (closestToP1.x - p1.x) ** 2 + (closestToP1.y - p1.y) ** 2;
  const distToP2Squared = (closestToP2.x - p2.x) ** 2 + (closestToP2.y - p2.y) ** 2;
  const edges = [
    { start: { x: minX, y: minY }, end: { x: maxX, y: minY } },
    // Bottom edge
    { start: { x: maxX, y: minY }, end: { x: maxX, y: maxY } },
    // Right edge
    { start: { x: maxX, y: maxY }, end: { x: minX, y: maxY } },
    // Top edge
    { start: { x: minX, y: maxY }, end: { x: minX, y: minY } }
    // Left edge
  ];
  let minDistance = Math.min(distToP1Squared, distToP2Squared);
  let closestPoint = distToP1Squared <= distToP2Squared ? closestToP1 : closestToP2;
  const clamp2 = (value, min, max) => {
    return Math.max(min, Math.min(max, value));
  };
  for (const edge of edges) {
    const va = { x: p2.x - p1.x, y: p2.y - p1.y };
    const vb = { x: edge.end.x - edge.start.x, y: edge.end.y - edge.start.y };
    const w = { x: p1.x - edge.start.x, y: p1.y - edge.start.y };
    const dotAA = va.x * va.x + va.y * va.y;
    const dotAB = va.x * vb.x + va.y * vb.y;
    const dotAW = va.x * w.x + va.y * w.y;
    const dotBB = vb.x * vb.x + vb.y * vb.y;
    const dotBW = vb.x * w.x + vb.y * w.y;
    const denominator = dotAA * dotBB - dotAB * dotAB;
    if (Math.abs(denominator) < 1e-10) continue;
    let tA = (dotAB * dotBW - dotBB * dotAW) / denominator;
    let tB = (dotAA * dotBW - dotAB * dotAW) / denominator;
    tA = clamp2(tA, 0, 1);
    tB = clamp2(tB, 0, 1);
    const closestOnSegment = {
      x: p1.x + tA * va.x,
      y: p1.y + tA * va.y
    };
    const closestOnEdge = {
      x: edge.start.x + tB * vb.x,
      y: edge.start.y + tB * vb.y
    };
    const dx2 = closestOnSegment.x - closestOnEdge.x;
    const dy2 = closestOnSegment.y - closestOnEdge.y;
    const distSquared = dx2 * dx2 + dy2 * dy2;
    if (distSquared < minDistance) {
      minDistance = distSquared;
      closestPoint = {
        x: (closestOnSegment.x + closestOnEdge.x) / 2,
        y: (closestOnSegment.y + closestOnEdge.y) / 2
      };
    }
  }
  return closestPoint;
};

// lib/check-each-pcb-trace-non-overlapping/getClosestPointBetweenSegments.ts
var getClosestPointBetweenSegments = (segmentA, segmentB) => {
  const a1 = { x: segmentA.x1, y: segmentA.y1 };
  const a2 = { x: segmentA.x2, y: segmentA.y2 };
  const b1 = { x: segmentB.x1, y: segmentB.y1 };
  const b2 = { x: segmentB.x2, y: segmentB.y2 };
  const va = { x: a2.x - a1.x, y: a2.y - a1.y };
  const vb = { x: b2.x - b1.x, y: b2.y - b1.y };
  const lenSqrA = va.x * va.x + va.y * va.y;
  const lenSqrB = vb.x * vb.x + vb.y * vb.y;
  if (lenSqrA === 0 || lenSqrB === 0) {
    if (lenSqrA === 0 && lenSqrB === 0) {
      return {
        x: (a1.x + b1.x) / 2,
        y: (a1.y + b1.y) / 2
      };
    }
    if (lenSqrA === 0) {
      const t2 = clamp(
        ((a1.x - b1.x) * vb.x + (a1.y - b1.y) * vb.y) / lenSqrB,
        0,
        1
      );
      const closestOnB2 = {
        x: b1.x + t2 * vb.x,
        y: b1.y + t2 * vb.y
      };
      return {
        x: (a1.x + closestOnB2.x) / 2,
        y: (a1.y + closestOnB2.y) / 2
      };
    }
    const t = clamp(
      ((b1.x - a1.x) * va.x + (b1.y - a1.y) * va.y) / lenSqrA,
      0,
      1
    );
    const closestOnA2 = {
      x: a1.x + t * va.x,
      y: a1.y + t * va.y
    };
    return {
      x: (closestOnA2.x + b1.x) / 2,
      y: (closestOnA2.y + b1.y) / 2
    };
  }
  const w = { x: a1.x - b1.x, y: a1.y - b1.y };
  const dotAA = va.x * va.x + va.y * va.y;
  const dotAB = va.x * vb.x + va.y * vb.y;
  const dotAW = va.x * w.x + va.y * w.y;
  const dotBB = vb.x * vb.x + vb.y * vb.y;
  const dotBW = vb.x * w.x + vb.y * w.y;
  const denominator = dotAA * dotBB - dotAB * dotAB;
  if (denominator < 1e-10) {
    return closestPointsParallelSegments(
      a1,
      a2,
      b1,
      b2,
      va,
      vb,
      lenSqrA,
      lenSqrB
    );
  }
  let tA = (dotAB * dotBW - dotBB * dotAW) / denominator;
  let tB = (dotAA * dotBW - dotAB * dotAW) / denominator;
  tA = clamp(tA, 0, 1);
  tB = clamp(tB, 0, 1);
  tB = (tA * dotAB + dotBW) / dotBB;
  tB = clamp(tB, 0, 1);
  tA = (tB * dotAB - dotAW) / dotAA;
  tA = clamp(tA, 0, 1);
  const closestOnA = {
    x: a1.x + tA * va.x,
    y: a1.y + tA * va.y
  };
  const closestOnB = {
    x: b1.x + tB * vb.x,
    y: b1.y + tB * vb.y
  };
  const dx = closestOnA.x - closestOnB.x;
  const dy = closestOnA.y - closestOnB.y;
  const distance3 = Math.sqrt(dx * dx + dy * dy);
  const averagePoint = {
    x: (closestOnA.x + closestOnB.x) / 2,
    y: (closestOnA.y + closestOnB.y) / 2
  };
  return averagePoint;
};
var closestPointsParallelSegments = (a1, a2, b1, b2, va, vb, lenSqrA, lenSqrB) => {
  let tA = ((b1.x - a1.x) * va.x + (b1.y - a1.y) * va.y) / lenSqrA;
  tA = clamp(tA, 0, 1);
  const pointOnA1 = { x: a1.x + tA * va.x, y: a1.y + tA * va.y };
  let tA2 = ((b2.x - a1.x) * va.x + (b2.y - a1.y) * va.y) / lenSqrA;
  tA2 = clamp(tA2, 0, 1);
  const pointOnA2 = { x: a1.x + tA2 * va.x, y: a1.y + tA2 * va.y };
  let tB = ((a1.x - b1.x) * vb.x + (a1.y - b1.y) * vb.y) / lenSqrB;
  tB = clamp(tB, 0, 1);
  const pointOnB1 = { x: b1.x + tB * vb.x, y: b1.y + tB * vb.y };
  let tB2 = ((a2.x - b1.x) * vb.x + (a2.y - b1.y) * vb.y) / lenSqrB;
  tB2 = clamp(tB2, 0, 1);
  const pointOnB2 = { x: b1.x + tB2 * vb.x, y: b1.y + tB2 * vb.y };
  const distances = [
    {
      pointA: pointOnA1,
      pointB: b1,
      distance: Math.sqrt(
        (pointOnA1.x - b1.x) ** 2 + (pointOnA1.y - b1.y) ** 2
      )
    },
    {
      pointA: pointOnA2,
      pointB: b2,
      distance: Math.sqrt(
        (pointOnA2.x - b2.x) ** 2 + (pointOnA2.y - b2.y) ** 2
      )
    },
    {
      pointA: a1,
      pointB: pointOnB1,
      distance: Math.sqrt(
        (a1.x - pointOnB1.x) ** 2 + (a1.y - pointOnB1.y) ** 2
      )
    },
    {
      pointA: a2,
      pointB: pointOnB2,
      distance: Math.sqrt(
        (a2.x - pointOnB2.x) ** 2 + (a2.y - pointOnB2.y) ** 2
      )
    }
  ];
  const closestPair = distances.reduce(
    (closest, current) => current.distance < closest.distance ? current : closest
  );
  return {
    x: (closestPair.pointA.x + closestPair.pointB.x) / 2,
    y: (closestPair.pointA.y + closestPair.pointB.y) / 2
  };
};
var clamp = (value, min, max) => {
  return Math.max(min, Math.min(max, value));
};

// lib/check-each-pcb-trace-non-overlapping/getCollidableBounds.ts
import { getBoundsOfPcbElements as getBoundsOfPcbElements3 } from "@tscircuit/circuit-json-util";
var getCollidableBounds = (collidable) => {
  if (collidable.type === "pcb_trace_segment") {
    return {
      minX: Math.min(collidable.x1, collidable.x2),
      minY: Math.min(collidable.y1, collidable.y2),
      maxX: Math.max(collidable.x1, collidable.x2),
      maxY: Math.max(collidable.y1, collidable.y2)
    };
  }
  if (collidable.type === "pcb_smtpad" || collidable.type === "pcb_plated_hole") {
    const isPolygon = collidable.type === "pcb_smtpad" && (collidable.shape === "rotated_rect" || collidable.shape === "polygon") || collidable.type === "pcb_plated_hole" && "rect_pad_width" in collidable && "rect_pad_height" in collidable;
    if (isPolygon) {
      const polygonPoints = getPolygonPointsForPad(collidable);
      return {
        minX: Math.min(...polygonPoints.map((point) => point.x)),
        minY: Math.min(...polygonPoints.map((point) => point.y)),
        maxX: Math.max(...polygonPoints.map((point) => point.x)),
        maxY: Math.max(...polygonPoints.map((point) => point.y))
      };
    }
    if (collidable.type === "pcb_smtpad" && collidable.shape === "rotated_pill") {
      const pill2 = getPillCenterLineForPad(collidable);
      return {
        minX: Math.min(pill2.start.x, pill2.end.x) - pill2.radius,
        minY: Math.min(pill2.start.y, pill2.end.y) - pill2.radius,
        maxX: Math.max(pill2.start.x, pill2.end.x) + pill2.radius,
        maxY: Math.max(pill2.start.y, pill2.end.y) + pill2.radius
      };
    }
  }
  return getBoundsOfPcbElements3([collidable]);
};

// lib/check-each-pcb-trace-non-overlapping/getPcbPortIdsConnectedToTraces.ts
function getPcbPortIdsConnectedToRoutePoint(routePoint) {
  if (routePoint.route_type !== "wire") return [];
  return [routePoint.start_pcb_port_id, routePoint.end_pcb_port_id].filter(
    (portId) => Boolean(portId)
  );
}
function getPcbPortIdsConnectedToTrace(trace) {
  const connectedPcbPorts = /* @__PURE__ */ new Set();
  for (const segment of trace.route) {
    for (const portId of getPcbPortIdsConnectedToRoutePoint(segment)) {
      connectedPcbPorts.add(portId);
    }
  }
  return Array.from(connectedPcbPorts);
}
function getPcbPortIdsConnectedToTraces(traces) {
  const connectedPorts = /* @__PURE__ */ new Set();
  for (const trace of traces) {
    for (const portId of getPcbPortIdsConnectedToTrace(trace)) {
      connectedPorts.add(portId);
    }
  }
  return Array.from(connectedPorts);
}

// lib/check-each-pcb-trace-non-overlapping/getRadiusOfCircuitJsonElement.ts
var getRadiusOfCircuitJsonElement = (obj) => {
  if (obj.type === "pcb_via") {
    return obj.outer_diameter / 2;
  }
  if (obj.type === "pcb_plated_hole" && obj.shape === "circle") {
    return obj.outer_diameter / 2;
  }
  if (obj.type === "pcb_hole" && obj.hole_shape === "circle") {
    return obj.hole_diameter / 2;
  }
  if (obj.type === "pcb_smtpad" && obj.shape === "circle") {
    return obj.radius;
  }
  throw new Error(
    `Could not determine radius of element: ${JSON.stringify(obj)}`
  );
};

// lib/check-each-pcb-trace-non-overlapping/check-each-pcb-trace-non-overlapping.ts
var getPcbComponentConnectionElementId = (element) => {
  if (element.type === "pcb_port") return element.pcb_port_id;
  if (element.type === "pcb_smtpad") return element.pcb_smtpad_id;
  return element.pcb_plated_hole_id;
};
function checkEachPcbTraceNonOverlapping(circuitJson, {
  connMap,
  minClearance
} = {}) {
  const errors = [];
  addStartAndEndPortIdsIfMissing(circuitJson);
  connMap ??= getFullConnectivityMapFromCircuitJson2(circuitJson);
  const board = getPcbBoard(circuitJson);
  minClearance ??= getBoardDrcValue(board, "min_trace_to_pad_edge_clearance") ?? DEFAULT_TRACE_MARGIN;
  const pcbTraces = cju2(circuitJson).pcb_trace.list();
  const pcbTraceSegments = pcbTraces.flatMap((pcbTrace) => {
    const segments = [];
    for (let i = 0; i < pcbTrace.route.length - 1; i++) {
      const p1 = pcbTrace.route[i];
      const p2 = pcbTrace.route[i + 1];
      if (p1.route_type !== "wire") continue;
      if (p2.route_type !== "wire") continue;
      if (p1.layer !== p2.layer) continue;
      segments.push({
        type: "pcb_trace_segment",
        pcb_trace_id: pcbTrace.pcb_trace_id,
        _pcbTrace: pcbTrace,
        thickness: "width" in p1 ? p1.width : "width" in p2 ? p2.width : DEFAULT_TRACE_THICKNESS,
        layer: p1.layer,
        x1: p1.x,
        y1: p1.y,
        x2: p2.x,
        y2: p2.y
      });
    }
    return segments;
  });
  const pcbSmtPads = cju2(circuitJson).pcb_smtpad.list();
  const pcbPlatedHoles = cju2(circuitJson).pcb_plated_hole.list();
  const pcbPorts = cju2(circuitJson).pcb_port.list();
  const pcbHoles = cju2(circuitJson).pcb_hole.list();
  const pcbVias = cju2(circuitJson).pcb_via.list();
  const pcbKeepouts = cju2(circuitJson).pcb_keepout.list();
  const pcbComponentConnectionElements = [
    ...pcbPorts,
    ...pcbSmtPads,
    ...pcbPlatedHoles
  ];
  const excludedConnectionIdsByKeepoutId = /* @__PURE__ */ new Map();
  for (const keepout of pcbKeepouts) {
    const excludedPcbComponentIds = new Set(
      keepout.excluded_pcb_component_ids ?? []
    );
    if (excludedPcbComponentIds.size === 0) continue;
    excludedConnectionIdsByKeepoutId.set(
      keepout.pcb_keepout_id,
      pcbComponentConnectionElements.filter(
        (element) => element.pcb_component_id && excludedPcbComponentIds.has(element.pcb_component_id)
      ).map(getPcbComponentConnectionElementId)
    );
  }
  const allObjects = [
    ...pcbTraceSegments,
    ...pcbSmtPads,
    ...pcbPlatedHoles,
    ...pcbHoles,
    ...pcbVias,
    ...pcbKeepouts
  ];
  const spatialIndex = new SpatialObjectIndex({
    objects: allObjects,
    getBounds: getCollidableBounds
  });
  const getReadableName = (id) => getReadableNameForElement2(circuitJson, id);
  const constructErrorMessage = (traceName, otherName, gap) => {
    if (isTraceObstacleOverlap(gap)) {
      return `PCB trace ${traceName} overlaps with ${otherName} (accidental contact)`;
    }
    return `PCB trace ${traceName} is too close to ${otherName} (gap: ${gap.toFixed(3)}mm)`;
  };
  const errorIds = /* @__PURE__ */ new Set();
  for (const segmentA of pcbTraceSegments) {
    const requiredMargin = minClearance;
    const bounds2 = getCollidableBounds(segmentA);
    const nearbyObjects = spatialIndex.getObjectsInBounds(
      bounds2,
      requiredMargin + segmentA.thickness / 2
    );
    if (segmentA.x1 === segmentA.x2 && segmentA.y1 === segmentA.y2) continue;
    for (const obj of nearbyObjects) {
      if (!getLayersOfPcbElement(obj).includes(segmentA.layer)) {
        continue;
      }
      if (obj.type === "pcb_keepout" && (excludedConnectionIdsByKeepoutId.get(obj.pcb_keepout_id) ?? []).some(
        (connectionId) => connMap.areIdsConnected(segmentA.pcb_trace_id, connectionId)
      )) {
        continue;
      }
      if (obj.type === "pcb_trace_segment") {
        const segmentB = obj;
        if (segmentA.layer !== segmentB.layer) continue;
        if (connMap.areIdsConnected(segmentA.pcb_trace_id, segmentB.pcb_trace_id))
          continue;
        const gap2 = segmentToSegmentMinDistance3(
          { x: segmentA.x1, y: segmentA.y1 },
          { x: segmentA.x2, y: segmentA.y2 },
          { x: segmentB.x1, y: segmentB.y1 },
          { x: segmentB.x2, y: segmentB.y2 }
        ) - segmentA.thickness / 2 - segmentB.thickness / 2;
        if (gap2 > minClearance - EPSILON) continue;
        const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${segmentB.pcb_trace_id}`;
        const pcb_trace_error_id_reverse = `overlap_${segmentB.pcb_trace_id}_${segmentA.pcb_trace_id}`;
        if (errorIds.has(pcb_trace_error_id)) continue;
        if (errorIds.has(pcb_trace_error_id_reverse)) continue;
        errorIds.add(pcb_trace_error_id);
        errors.push({
          type: "pcb_trace_error",
          error_type: "pcb_trace_error",
          message: constructErrorMessage(
            getReadableName(segmentA.pcb_trace_id),
            getReadableName(segmentB.pcb_trace_id),
            gap2
          ),
          pcb_trace_id: segmentA.pcb_trace_id,
          source_trace_id: "",
          pcb_trace_error_id,
          pcb_component_ids: [],
          center: getClosestPointBetweenSegments(segmentA, segmentB),
          pcb_port_ids: getPcbPortIdsConnectedToTraces([
            segmentA._pcbTrace,
            segmentB._pcbTrace
          ])
        });
        continue;
      }
      const primaryObjId = getPrimaryId2(obj);
      if (connMap.areIdsConnected(
        segmentA.pcb_trace_id,
        "pcb_trace_id" in obj ? obj.pcb_trace_id : primaryObjId
      ))
        continue;
      if (obj.type === "pcb_smtpad" || obj.type === "pcb_plated_hole" || obj.type === "pcb_via") {
        const { gap: gap2, center } = getTraceObstacleClearance(segmentA, obj);
        if (!isTraceObstacleOverlap(gap2)) continue;
        const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${primaryObjId}`;
        if (errorIds.has(pcb_trace_error_id)) continue;
        errorIds.add(pcb_trace_error_id);
        errors.push({
          type: "pcb_trace_error",
          error_type: "pcb_trace_error",
          message: constructErrorMessage(
            getReadableName(segmentA.pcb_trace_id),
            `${obj.type} "${getReadableName(primaryObjId)}"`,
            gap2
          ),
          pcb_trace_id: segmentA.pcb_trace_id,
          center,
          source_trace_id: "",
          pcb_trace_error_id,
          pcb_component_ids: [
            "pcb_component_id" in obj ? obj.pcb_component_id : void 0
          ].filter(Boolean),
          pcb_port_ids: [
            ...getPcbPortIdsConnectedToTraces([segmentA._pcbTrace]),
            "pcb_port_id" in obj ? obj.pcb_port_id : void 0
          ].filter(Boolean)
        });
        continue;
      }
      const isCircular = obj.type === "pcb_hole";
      if (isCircular) {
        const radius = getRadiusOfCircuitJsonElement(obj);
        const distance3 = segmentToCircleMinDistance2(
          { x: segmentA.x1, y: segmentA.y1 },
          { x: segmentA.x2, y: segmentA.y2 },
          { x: obj.x, y: obj.y, radius }
        );
        const gap2 = distance3 - segmentA.thickness / 2;
        if (gap2 > minClearance - EPSILON) continue;
        const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${primaryObjId}`;
        if (errorIds.has(pcb_trace_error_id)) continue;
        errorIds.add(pcb_trace_error_id);
        errors.push({
          type: "pcb_trace_error",
          error_type: "pcb_trace_error",
          message: constructErrorMessage(
            getReadableName(segmentA.pcb_trace_id),
            `${obj.type} "${getReadableName(getPrimaryId2(obj))}"`,
            gap2
          ),
          pcb_trace_id: segmentA.pcb_trace_id,
          center: getClosestPointBetweenSegmentAndBounds(
            segmentA,
            getCollidableBounds(obj)
          ),
          source_trace_id: "",
          pcb_trace_error_id,
          pcb_component_ids: [
            "pcb_component_id" in obj ? obj.pcb_component_id : void 0
          ].filter(Boolean),
          pcb_port_ids: [
            ...getPcbPortIdsConnectedToTraces([segmentA._pcbTrace]),
            "pcb_port_id" in obj ? obj.pcb_port_id : void 0
          ].filter(Boolean)
        });
      }
      const gap = segmentToBoundsMinDistance(
        { x: segmentA.x1, y: segmentA.y1 },
        { x: segmentA.x2, y: segmentA.y2 },
        getCollidableBounds(obj)
      ) - segmentA.thickness / 2;
      if (gap + EPSILON < requiredMargin) {
        const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${primaryObjId}`;
        if (errorIds.has(pcb_trace_error_id)) continue;
        errorIds.add(pcb_trace_error_id);
        errors.push({
          type: "pcb_trace_error",
          error_type: "pcb_trace_error",
          message: constructErrorMessage(
            getReadableName(segmentA.pcb_trace_id),
            `${obj.type} "${getReadableName(getPrimaryId2(obj))}"`,
            gap
          ),
          pcb_trace_id: segmentA.pcb_trace_id,
          source_trace_id: "",
          pcb_trace_error_id,
          pcb_component_ids: [
            "pcb_component_id" in obj ? obj.pcb_component_id : void 0
          ].filter(Boolean),
          center: getClosestPointBetweenSegmentAndBounds(
            segmentA,
            getCollidableBounds(obj)
          ),
          pcb_port_ids: [
            ...getPcbPortIdsConnectedToTraces([segmentA._pcbTrace]),
            "pcb_port_id" in obj ? obj.pcb_port_id : void 0
          ].filter(Boolean)
        });
      }
    }
  }
  return errors;
}

// lib/net-manager.ts
var NetManager = class {
  networks = /* @__PURE__ */ new Set();
  setConnected(nodes) {
    if (nodes.length < 2) return;
    let targetNetwork = null;
    for (const network of this.networks) {
      for (const node of nodes) {
        if (network.has(node)) {
          if (targetNetwork === null) {
            targetNetwork = network;
          } else if (targetNetwork !== network) {
            for (const mergeNode of network) {
              targetNetwork.add(mergeNode);
            }
            this.networks.delete(network);
          }
          break;
        }
      }
      if (targetNetwork !== null && targetNetwork !== network) break;
    }
    if (targetNetwork === null) {
      targetNetwork = new Set(nodes);
      this.networks.add(targetNetwork);
    } else {
      for (const node of nodes) {
        targetNetwork.add(node);
      }
    }
  }
  isConnected(nodes) {
    if (nodes.length < 2) return true;
    for (const network of this.networks) {
      if (nodes.every((node) => network.has(node))) {
        return true;
      }
    }
    return false;
  }
};

// lib/check-pcb-components-out-of-board/checkViasOffBoard.ts
import { getReadableNameForElement as getReadableNameForElement3 } from "@tscircuit/circuit-json-util";
function checkViasOffBoard(circuitJson) {
  const vias = circuitJson.filter((element) => element.type === "pcb_via");
  const violationsById = new Map(
    checkCopperToBoardEdgeClearance(
      circuitJson.filter(
        (element) => element.type === "pcb_board" || element.type === "pcb_via"
      )
    ).map((error) => [
      error.pcb_placement_error_id.replace(
        "copper_too_close_to_board_edge_",
        ""
      ),
      error
    ])
  );
  return vias.flatMap((via) => {
    const violation = violationsById.get(via.pcb_via_id);
    if (!violation) return [];
    const viaName = getReadableNameForElement3(circuitJson, via.pcb_via_id);
    return [
      {
        ...violation,
        pcb_placement_error_id: `out_of_board_${via.pcb_via_id}`,
        message: `Via ${viaName} is outside or crossing the board boundary`
      }
    ];
  });
}

// lib/check-pcb-components-out-of-board/checkPcbComponentsOutOfBoard.ts
import * as Flatten3 from "@flatten-js/core";
import { rotateDEG as rotateDEG2, applyToPoint as applyToPoint2 } from "transformation-matrix";
function isPolygonCCW(poly) {
  return poly.area() >= 0;
}
function rectanglePolygon({
  center,
  size,
  rotationDeg = 0
}) {
  const cx = center.x;
  const cy = center.y;
  const hw = size.width / 2;
  const hh = size.height / 2;
  const corners = [
    new Flatten3.Point(cx - hw, cy - hh),
    new Flatten3.Point(cx + hw, cy - hh),
    new Flatten3.Point(cx + hw, cy + hh),
    new Flatten3.Point(cx - hw, cy + hh)
  ];
  let poly = new Flatten3.Polygon(corners);
  if (rotationDeg) {
    const matrix = rotateDEG2(rotationDeg, cx, cy);
    const rotatedCorners = corners.map((pt) => {
      const p = applyToPoint2(matrix, { x: pt.x, y: pt.y });
      return new Flatten3.Point(p.x, p.y);
    });
    poly = new Flatten3.Polygon(rotatedCorners);
  }
  if (!isPolygonCCW(poly)) poly.reverse();
  return poly;
}
function boardToPolygon2({
  board
}) {
  if (board.outline && board.outline.length > 0) {
    const points = board.outline.map((p) => new Flatten3.Point(p.x, p.y));
    const poly = new Flatten3.Polygon(points);
    if (!isPolygonCCW(poly)) {
      poly.reverse();
    }
    return poly;
  }
  if (board.center && typeof board.width === "number" && typeof board.height === "number") {
    return rectanglePolygon({
      center: board.center,
      size: { width: board.width, height: board.height },
      rotationDeg: 0
    });
  }
  return null;
}
function getComponentName({
  circuitJson,
  component
}) {
  if (component.source_component_id) {
    const sourceComponent = circuitJson.find(
      (el) => el.type === "source_component" && el.source_component_id === component.source_component_id
    );
    if (sourceComponent && "name" in sourceComponent && sourceComponent.name) {
      return sourceComponent.name;
    }
  }
  return getReadableNameForComponent(circuitJson, component.pcb_component_id);
}
function computeOverlapDistance(compPoly, boardPoly, componentCenter, componentWidth, componentHeight, rotationDeg) {
  const centerPoint = new Flatten3.Point(componentCenter.x, componentCenter.y);
  if (!boardPoly.contains(centerPoint)) {
    const dist = boardPoly.distanceTo(centerPoint);
    return Array.isArray(dist) ? dist[0] : Number(dist) || 0;
  }
  const hw = componentWidth / 2;
  const hh = componentHeight / 2;
  const corners = [
    { x: componentCenter.x - hw, y: componentCenter.y - hh },
    { x: componentCenter.x + hw, y: componentCenter.y - hh },
    { x: componentCenter.x + hw, y: componentCenter.y + hh },
    { x: componentCenter.x - hw, y: componentCenter.y + hh }
  ];
  const midpoints = [];
  for (let i = 0; i < 4; i++) {
    const next = (i + 1) % 4;
    midpoints.push({
      x: (corners[i].x + corners[next].x) / 2,
      y: (corners[i].y + corners[next].y) / 2
    });
  }
  const matrix = rotateDEG2(rotationDeg, componentCenter.x, componentCenter.y);
  const rotatePoint2 = (pt) => {
    const p = applyToPoint2(matrix, pt);
    return new Flatten3.Point(p.x, p.y);
  };
  const rotatedPoints = corners.concat(midpoints).map(rotatePoint2);
  let maxDistance = 0;
  for (const pt of rotatedPoints) {
    if (!boardPoly.contains(pt)) {
      const dist = boardPoly.distanceTo(pt);
      const d = Array.isArray(dist) ? dist[0] : Number(dist) || 0;
      if (d > maxDistance) maxDistance = d;
    }
  }
  if (maxDistance > 0) {
    return maxDistance;
  }
  try {
    const intersection = Flatten3.BooleanOperations.intersect(
      compPoly,
      boardPoly
    );
    let intersectionArea = 0;
    if (!intersection) {
      intersectionArea = 0;
    } else if (Array.isArray(intersection)) {
      intersectionArea = intersection.reduce(
        (sum, p) => sum + (typeof p.area === "function" ? p.area() : 0),
        0
      );
    } else if (typeof intersection.area === "function") {
      intersectionArea = intersection.area();
    } else {
      intersectionArea = 0;
    }
    const compArea = compPoly.area();
    if (intersectionArea > 0 && intersectionArea < compArea) {
      const overlapRatio = 1 - intersectionArea / compArea;
      const compWidth = Math.abs(componentWidth);
      const compHeight = Math.abs(componentHeight);
      return Math.min(compWidth, compHeight) * overlapRatio;
    } else if (intersectionArea === 0) {
      return 0.1;
    } else {
      return 0.1;
    }
  } catch {
    return 0.1;
  }
}
function getRepositionSuggestion({
  componentPoly,
  boardPoly
}) {
  const boardBox = boardPoly.box;
  const componentBox = componentPoly.box;
  let deltaX = 0;
  let deltaY = 0;
  if (componentBox.xmin < boardBox.xmin) {
    deltaX = boardBox.xmin - componentBox.xmin;
  } else if (componentBox.xmax > boardBox.xmax) {
    deltaX = boardBox.xmax - componentBox.xmax;
  }
  if (componentBox.ymin < boardBox.ymin) {
    deltaY = boardBox.ymin - componentBox.ymin;
  } else if (componentBox.ymax > boardBox.ymax) {
    deltaY = boardBox.ymax - componentBox.ymax;
  }
  if (deltaX === 0 && deltaY === 0) {
    return null;
  }
  const xDir = deltaX >= 0 ? "right" : "left";
  const yDir = deltaY >= 0 ? "up" : "down";
  const absDx = Math.abs(Math.round(deltaX * 100) / 100);
  const absDy = Math.abs(Math.round(deltaY * 100) / 100);
  if (absDx > 0 && absDy > 0) {
    return `Try moving it ${absDx}mm ${xDir} and ${absDy}mm ${yDir} to fit within the board edge.`;
  }
  if (absDx > 0) {
    return `Try moving it ${absDx}mm ${xDir} to fit within the board edge.`;
  }
  return `Try moving it ${absDy}mm ${yDir} to fit within the board edge.`;
}
function checkPcbComponentsOutOfBoard(circuitJson) {
  const board = circuitJson.find(
    (el) => el.type === "pcb_board"
  );
  if (!board) return [];
  const boardPoly = boardToPolygon2({ board });
  if (!boardPoly) return [];
  const components = circuitJson.filter(
    (el) => el.type === "pcb_component"
  );
  if (components.length === 0) return [];
  const errors = [];
  for (const c of components) {
    if (c.is_allowed_to_be_off_board) continue;
    if (!c.center || typeof c.width !== "number" || typeof c.height !== "number")
      continue;
    if (c.width <= 0 || c.height <= 0) continue;
    const compPoly = rectanglePolygon({
      center: c.center,
      size: { width: c.width, height: c.height },
      rotationDeg: 0
    });
    if (compPoly.area() === 0) continue;
    const isInside = boardPoly.contains(compPoly);
    if (isInside) continue;
    const overlapDistance = computeOverlapDistance(
      compPoly,
      boardPoly,
      c.center,
      c.width,
      c.height,
      0
    );
    const compName = getComponentName({ circuitJson, component: c });
    const overlapDistanceMm = Math.round(overlapDistance * 100) / 100;
    const repositionSuggestion = getRepositionSuggestion({
      componentPoly: compPoly,
      boardPoly
    });
    errors.push({
      type: "pcb_component_outside_board_error",
      error_type: "pcb_component_outside_board_error",
      pcb_component_outside_board_error_id: `pcb_component_outside_board_${c.pcb_component_id}`,
      message: `Component ${compName} extends outside board boundaries by ${overlapDistanceMm}mm.${repositionSuggestion ? ` ${repositionSuggestion}` : ""}`,
      pcb_component_id: c.pcb_component_id,
      pcb_board_id: board.pcb_board_id,
      component_center: c.center,
      component_bounds: {
        min_x: compPoly.box.xmin,
        max_x: compPoly.box.xmax,
        min_y: compPoly.box.ymin,
        max_y: compPoly.box.ymax
      },
      subcircuit_id: c.subcircuit_id,
      source_component_id: c.source_component_id
    });
  }
  return errors;
}

// lib/check-pcb-component-over-cutout.ts
import { doBoundsOverlap } from "@tscircuit/math-utils";
import * as Flatten4 from "@flatten-js/core";
import { applyToPoint as applyToPoint3, rotateDEG as rotateDEG3 } from "transformation-matrix";
var CUTOUT_CIRCLE_SEGMENTS = 32;
function rectanglePolygon2({
  center,
  width,
  height,
  rotation = 0
}) {
  const halfWidth = width / 2;
  const halfHeight = height / 2;
  const corners = [
    { x: center.x - halfWidth, y: center.y - halfHeight },
    { x: center.x + halfWidth, y: center.y - halfHeight },
    { x: center.x + halfWidth, y: center.y + halfHeight },
    { x: center.x - halfWidth, y: center.y + halfHeight }
  ];
  const matrix = rotateDEG3(rotation, center.x, center.y);
  return new Flatten4.Polygon(
    corners.map((corner) => {
      const rotated = rotation ? applyToPoint3(matrix, corner) : corner;
      return new Flatten4.Point(rotated.x, rotated.y);
    })
  );
}
function circlePolygon({
  center,
  radius
}) {
  return new Flatten4.Polygon(
    Array.from({ length: CUTOUT_CIRCLE_SEGMENTS }, (_, index) => {
      const angle = 2 * Math.PI * index / CUTOUT_CIRCLE_SEGMENTS;
      return new Flatten4.Point(
        center.x + Math.cos(angle) * radius,
        center.y + Math.sin(angle) * radius
      );
    })
  );
}
function cutoutToPolygon(cutout) {
  if (cutout.shape === "rect") {
    return rectanglePolygon2({
      center: cutout.center,
      width: cutout.width,
      height: cutout.height,
      rotation: cutout.rotation ?? 0
    });
  }
  if (cutout.shape === "circle") {
    return circlePolygon({ center: cutout.center, radius: cutout.radius });
  }
  if (cutout.shape === "polygon") {
    return new Flatten4.Polygon(
      cutout.points.map((point) => new Flatten4.Point(point.x, point.y))
    );
  }
  return null;
}
function polygonBoxToBounds(polygon) {
  return {
    minX: polygon.box.xmin,
    minY: polygon.box.ymin,
    maxX: polygon.box.xmax,
    maxY: polygon.box.ymax
  };
}
function doPolygonsOverlap(polygonA, polygonB) {
  if (!doBoundsOverlap(polygonBoxToBounds(polygonA), polygonBoxToBounds(polygonB))) {
    return false;
  }
  if (polygonA.contains(polygonB) || polygonB.contains(polygonA)) return true;
  try {
    const intersections = Flatten4.BooleanOperations.intersect(
      polygonA,
      polygonB
    );
    if (Array.isArray(intersections)) {
      return intersections.some((polygon) => polygon.area() > 0);
    }
    return intersections.area() > 0;
  } catch {
    return false;
  }
}
function checkPcbComponentOverCutout(circuitJson) {
  const cutouts = circuitJson.filter(
    (element) => element.type === "pcb_cutout"
  );
  const components = circuitJson.filter(
    (element) => element.type === "pcb_component"
  );
  if (cutouts.length === 0 || components.length === 0) return [];
  const cutoutPolygons = cutouts.map((cutout) => ({ cutout, polygon: cutoutToPolygon(cutout) })).filter(
    (entry) => entry.polygon !== null && entry.polygon.area() > 0
  );
  const errors = [];
  for (const component of components) {
    if (!component.center || component.width <= 0 || component.height <= 0) {
      continue;
    }
    const componentPolygon = rectanglePolygon2({
      center: component.center,
      width: component.width,
      height: component.height
    });
    for (const { cutout, polygon: cutoutPolygon } of cutoutPolygons) {
      if (cutout.pcb_component_id === component.pcb_component_id) continue;
      if (!doPolygonsOverlap(componentPolygon, cutoutPolygon)) continue;
      const componentName = getReadableNameForComponent(
        circuitJson,
        component.pcb_component_id
      );
      const cutoutId = cutout.pcb_cutout_id;
      errors.push({
        type: "pcb_placement_error",
        pcb_placement_error_id: `component_over_cutout_${component.pcb_component_id}_${cutoutId}`,
        error_type: "pcb_placement_error",
        message: `Component ${componentName} overlaps with pcb_cutout [${cutoutId}]`,
        subcircuit_id: component.subcircuit_id
      });
    }
  }
  return errors;
}

// lib/check-pcb-copper-over-keepout.ts
import { cju as cju3, getPrimaryId as getPrimaryId3 } from "@tscircuit/circuit-json-util";
var getErrorOwnerId = (copper) => "pcb_component_id" in copper && copper.pcb_component_id ? copper.pcb_component_id : getPrimaryId3(copper);
var getReadableCopperName = (circuitJson, copper) => {
  if ("pcb_component_id" in copper && copper.pcb_component_id) {
    const pcbComponent = circuitJson.find(
      (element) => element.type === "pcb_component" && element.pcb_component_id === copper.pcb_component_id
    );
    const sourceComponent = pcbComponent?.type === "pcb_component" ? circuitJson.find(
      (element) => element.type === "source_component" && element.source_component_id === pcbComponent.source_component_id
    ) : void 0;
    const componentName = sourceComponent?.type === "source_component" && sourceComponent.name ? sourceComponent.name : getReadableNameForComponent(circuitJson, copper.pcb_component_id);
    return `component ${componentName}`;
  }
  return copper.type === "pcb_via" ? `via ${copper.pcb_via_id}` : `${copper.type} ${getPrimaryId3(copper)}`;
};
function checkPcbCopperOverKeepout(circuitJson) {
  const keepouts = cju3(circuitJson).pcb_keepout.list();
  if (keepouts.length === 0) return [];
  const copper = [
    ...getPads(circuitJson),
    ...cju3(circuitJson).pcb_via.list()
  ];
  const errors = /* @__PURE__ */ new Map();
  for (const keepout of keepouts) {
    const excludedComponentIds = new Set(
      keepout.excluded_pcb_component_ids ?? []
    );
    for (const copperElement of copper) {
      const copperComponentId = "pcb_component_id" in copperElement ? copperElement.pcb_component_id : void 0;
      if (copperComponentId && excludedComponentIds.has(copperComponentId)) {
        continue;
      }
      const copperLayers = getLayersOfPcbElement(copperElement);
      if (!copperLayers.some((layer) => keepout.layers.includes(layer))) {
        continue;
      }
      if (getPadToPadGap(copperElement, keepout) > EPSILON) continue;
      const ownerId = getErrorOwnerId(copperElement);
      const errorId = `copper_over_keepout_${ownerId}_${keepout.pcb_keepout_id}`;
      if (errors.has(errorId)) continue;
      errors.set(errorId, {
        type: "pcb_placement_error",
        pcb_placement_error_id: errorId,
        error_type: "pcb_placement_error",
        message: `Copper for ${getReadableCopperName(
          circuitJson,
          copperElement
        )} overlaps ${keepout.description ? `PCB keepout "${keepout.description}"` : "a PCB keepout"}`,
        subcircuit_id: copperElement.subcircuit_id ?? keepout.subcircuit_id
      });
    }
  }
  return [...errors.values()];
}

// lib/check-same-net-via-spacing.ts
import { getReadableNameForElement as getReadableNameForElement4 } from "@tscircuit/circuit-json-util";
import {
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson3
} from "circuit-json-to-connectivity-map";

// lib/util/distance.ts
function distance2(a, b) {
  return Math.hypot(a.x - b.x, a.y - b.y);
}

// lib/util/viasAreAtSameLocation.ts
function viasAreAtSameLocation(a, b) {
  return distance2(a, b) <= EPSILON;
}

// lib/check-same-net-via-spacing.ts
function checkSameNetViaSpacing(circuitJson, {
  connMap,
  minClearance
} = {}) {
  const vias = circuitJson.filter((el) => el.type === "pcb_via");
  if (vias.length < 2) return [];
  const board = getPcbBoard(circuitJson);
  minClearance ??= getBoardDrcValue(board, "min_via_hole_edge_to_via_hole_edge_clearance") ?? jlcMinTolerances.min_via_hole_edge_to_via_hole_edge_clearance;
  connMap ??= getFullConnectivityMapFromCircuitJson3(circuitJson);
  const errors = [];
  const reported = /* @__PURE__ */ new Set();
  for (let i = 0; i < vias.length; i++) {
    for (let j = i + 1; j < vias.length; j++) {
      const viaA = vias[i];
      const viaB = vias[j];
      if (viasAreAtSameLocation(viaA, viaB)) continue;
      if (!connMap.areIdsConnected(viaA.pcb_via_id, viaB.pcb_via_id)) continue;
      const gap = distance2(viaA, viaB) - viaA.hole_diameter / 2 - viaB.hole_diameter / 2;
      if (gap + EPSILON >= minClearance) continue;
      const pairId = [viaA.pcb_via_id, viaB.pcb_via_id].sort().join("_");
      if (reported.has(pairId)) continue;
      reported.add(pairId);
      errors.push({
        type: "pcb_via_clearance_error",
        pcb_error_id: `same_net_vias_close_${pairId}`,
        message: `Vias ${getReadableNameForElement4(
          circuitJson,
          viaA.pcb_via_id
        )} and ${getReadableNameForElement4(
          circuitJson,
          viaB.pcb_via_id
        )} are too close together (gap: ${gap.toFixed(3)}mm)`,
        error_type: "pcb_via_clearance_error",
        pcb_via_ids: [viaA.pcb_via_id, viaB.pcb_via_id],
        minimum_clearance: minClearance,
        actual_clearance: gap,
        pcb_center: {
          x: (viaA.x + viaB.x) / 2,
          y: (viaA.y + viaB.y) / 2
        }
      });
    }
  }
  return errors;
}

// lib/check-different-net-via-spacing.ts
import { getReadableNameForElement as getReadableNameForElement5 } from "@tscircuit/circuit-json-util";
import {
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson4
} from "circuit-json-to-connectivity-map";
function checkDifferentNetViaSpacing(circuitJson, {
  connMap,
  minClearance
} = {}) {
  const vias = circuitJson.filter((el) => el.type === "pcb_via");
  if (vias.length < 2) return [];
  const board = getPcbBoard(circuitJson);
  minClearance ??= getBoardDrcValue(board, "min_via_hole_edge_to_via_hole_edge_clearance") ?? jlcMinTolerances.min_via_hole_edge_to_via_hole_edge_clearance;
  connMap ??= getFullConnectivityMapFromCircuitJson4(circuitJson);
  const errors = [];
  const reported = /* @__PURE__ */ new Set();
  for (let i = 0; i < vias.length; i++) {
    for (let j = i + 1; j < vias.length; j++) {
      const viaA = vias[i];
      const viaB = vias[j];
      if (viasAreAtSameLocation(viaA, viaB)) continue;
      if (connMap.areIdsConnected(viaA.pcb_via_id, viaB.pcb_via_id)) continue;
      const gap = distance2(viaA, viaB) - viaA.hole_diameter / 2 - viaB.hole_diameter / 2;
      if (gap + EPSILON >= minClearance) continue;
      const pairId = [viaA.pcb_via_id, viaB.pcb_via_id].sort().join("_");
      if (reported.has(pairId)) continue;
      reported.add(pairId);
      errors.push({
        type: "pcb_via_clearance_error",
        pcb_error_id: `different_net_vias_close_${pairId}`,
        message: `Vias ${getReadableNameForElement5(
          circuitJson,
          viaA.pcb_via_id
        )} and ${getReadableNameForElement5(
          circuitJson,
          viaB.pcb_via_id
        )} from different nets are too close together (gap: ${gap.toFixed(
          3
        )}mm)`,
        error_type: "pcb_via_clearance_error",
        pcb_via_ids: [viaA.pcb_via_id, viaB.pcb_via_id],
        minimum_clearance: minClearance,
        actual_clearance: gap,
        pcb_center: {
          x: (viaA.x + viaB.x) / 2,
          y: (viaA.y + viaB.y) / 2
        }
      });
    }
  }
  return errors;
}

// lib/check-source-traces-match-pcb-trace-thickness.ts
import { cju as cju4 } from "@tscircuit/circuit-json-util";
import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson5 } from "circuit-json-to-connectivity-map";
function checkSourceTracesMatchPcbTraceThickness(circuitJson) {
  const warnings = [];
  const db = cju4(circuitJson);
  const sourceTraces = db.source_trace.list();
  const pcbTraces = db.pcb_trace.list();
  const pcbPorts = db.pcb_port.list();
  const connectivityMap = getFullConnectivityMapFromCircuitJson5(circuitJson);
  for (const sourceTrace of sourceTraces) {
    const requestedThickness = sourceTrace.min_trace_thickness;
    if (requestedThickness === void 0) continue;
    const connectedPcbPorts = pcbPorts.filter(
      (pcbPort) => sourceTrace.connected_source_port_ids?.includes(pcbPort.source_port_id)
    );
    if (connectedPcbPorts.length < 2) continue;
    const referenceNetId = connectivityMap.getNetConnectedToId(
      connectedPcbPorts[0].pcb_port_id
    );
    if (!referenceNetId) continue;
    const netElementIds = connectivityMap.getIdsConnectedToNet(referenceNetId);
    const relatedPcbTraces = pcbTraces.filter(
      (pcbTrace) => netElementIds.includes(pcbTrace.pcb_trace_id)
    );
    if (relatedPcbTraces.length === 0) continue;
    const actualWireWidths = relatedPcbTraces.flatMap(
      (pcbTrace) => pcbTrace.route.filter((point) => point.route_type === "wire").map((point) => point.width)
    );
    if (actualWireWidths.length === 0) continue;
    const actualThickness = Math.min(...actualWireWidths);
    if (actualThickness >= requestedThickness) continue;
    let undersizedSegment;
    for (const relatedPcbTrace of relatedPcbTraces) {
      for (let i = 0; i < relatedPcbTrace.route.length - 1; i++) {
        const point = relatedPcbTrace.route[i];
        const nextPoint = relatedPcbTrace.route[i + 1];
        if (!point || !nextPoint) continue;
        if (point.route_type !== "wire" || nextPoint.route_type !== "wire") {
          continue;
        }
        if (point.width !== actualThickness) continue;
        undersizedSegment = {
          pcb_trace_id: relatedPcbTrace.pcb_trace_id,
          center: {
            x: (point.x + nextPoint.x) / 2,
            y: (point.y + nextPoint.y) / 2
          }
        };
        break;
      }
      if (undersizedSegment) break;
    }
    if (!undersizedSegment) continue;
    warnings.push({
      type: "pcb_trace_warning",
      pcb_trace_warning_id: `pcb_trace_warning_${sourceTrace.source_trace_id}`,
      warning_type: "pcb_trace_warning",
      message: `Trace [${getReadableNameForSourceTrace(circuitJson, sourceTrace)}] is routed thinner than requested (requested: ${requestedThickness}mm, actual: ${actualThickness}mm).`,
      center: undersizedSegment.center,
      source_trace_id: sourceTrace.source_trace_id,
      pcb_trace_id: undersizedSegment.pcb_trace_id,
      pcb_component_ids: Array.from(
        new Set(
          connectedPcbPorts.map((pcbPort) => pcbPort.pcb_component_id).filter((id) => id !== void 0)
        )
      ),
      pcb_port_ids: connectedPcbPorts.map((pcbPort) => pcbPort.pcb_port_id)
    });
  }
  return warnings;
}

// lib/check-source-traces-have-pcb-traces.ts
import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson6 } from "circuit-json-to-connectivity-map";
function checkSourceTracesHavePcbTraces(circuitJson) {
  const errors = [];
  const sourceTraces = circuitJson.filter(
    (el) => el.type === "source_trace"
  );
  const pcbTraces = circuitJson.filter(
    (el) => el.type === "pcb_trace"
  );
  const pcbPorts = circuitJson.filter(
    (el) => el.type === "pcb_port"
  );
  const sourcePortToPcbPort = new Map(
    pcbPorts.map((pcbPort) => [pcbPort.source_port_id, pcbPort])
  );
  const connectivityMap = getFullConnectivityMapFromCircuitJson6(circuitJson);
  for (const sourceTrace of sourceTraces) {
    if (!sourceTrace.connected_source_port_ids?.length) continue;
    if ((sourceTrace.connected_source_net_ids?.length ?? 0) > 0) continue;
    if (sourceTrace.connected_source_port_ids.length < 2) continue;
    const hasPcbTrace = pcbTraces.some(
      (pcbTrace) => connectivityMap.areIdsConnected(
        sourceTrace.source_trace_id,
        pcbTrace.pcb_trace_id
      )
    );
    if (!hasPcbTrace) {
      const connectedPcbPorts = sourceTrace.connected_source_port_ids.map((sourcePortId) => sourcePortToPcbPort.get(sourcePortId)).filter((pcbPort) => pcbPort !== void 0);
      const connectedPcbComponentIds = Array.from(
        new Set(
          connectedPcbPorts.map((port) => port.pcb_component_id).filter((id) => id !== void 0)
        )
      );
      errors.push({
        type: "pcb_trace_missing_error",
        pcb_trace_missing_error_id: `pcb_trace_missing_${sourceTrace.source_trace_id}`,
        error_type: "pcb_trace_missing_error",
        message: `Trace [${sourceTrace.display_name && !containsCircuitJsonId(sourceTrace.display_name) ? sourceTrace.display_name : "trace"}] is not connected (it has no PCB trace)`,
        source_trace_id: sourceTrace.source_trace_id,
        pcb_component_ids: connectedPcbComponentIds,
        pcb_port_ids: connectedPcbPorts.map((port) => port.pcb_port_id)
      });
    }
  }
  return errors;
}

// lib/check-traces-are-contiguous/check-traces-are-contiguous.ts
import { pointToSegmentDistance as pointToSegmentDistance3 } from "@tscircuit/math-utils";
import {
  getReadableNameForPcbPort as getReadableNameForPcbPort2,
  getReadableNameForPcbTrace
} from "@tscircuit/circuit-json-util";
import {
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson7,
  PcbConnectivityMap as PcbConnectivityMap2
} from "circuit-json-to-connectivity-map";

// lib/check-traces-are-contiguous/via-contact-index.ts
import {
  all_layers as all_layers2
} from "circuit-json";
import { getPrimaryId as getPrimaryId4 } from "@tscircuit/circuit-json-util";
import { pointToSegmentDistance as pointToSegmentDistance2 } from "@tscircuit/math-utils";
var CONTACT_EPSILON = 1e-9;
function getViaContactIndex(circuitJson, connectivity) {
  const index = /* @__PURE__ */ new Map();
  const pads = getPads(circuitJson);
  const traces = circuitJson.filter((element) => element.type === "pcb_trace");
  const vias = circuitJson.filter((element) => element.type === "pcb_via");
  const board = circuitJson.find((element) => element.type === "pcb_board");
  const layerCount = board?.num_layers;
  const innerLayers = all_layers2.filter((layer) => layer.startsWith("inner"));
  const stack = [
    "top",
    ...innerLayers.slice(
      0,
      layerCount === void 0 ? void 0 : Math.max(0, layerCount - 2)
    ),
    ...layerCount === 1 ? [] : ["bottom"]
  ];
  const add = (id, layers, contact) => {
    if (![contact.x, contact.y].every(Number.isFinite)) return;
    const net = connectivity.getNetConnectedToId(id);
    if (!net) return;
    const touchesPad = pads.some((pad) => {
      if (connectivity.getNetConnectedToId(getPrimaryId4(pad)) !== net || !getLayersOfPcbElement(pad).some((layer) => layers.includes(layer)))
        return false;
      if (contact.radius === void 0) return isPointInPad(contact, pad);
      const viaGeometry = {
        type: "pcb_via",
        pcb_via_id: id,
        x: contact.x,
        y: contact.y,
        outer_diameter: contact.radius * 2,
        hole_diameter: 0,
        layers
      };
      return getPadToPadGap(viaGeometry, pad) <= CONTACT_EPSILON;
    });
    const touchingTraceIds = /* @__PURE__ */ new Set();
    for (const trace of traces) {
      if (trace.route_thickness_mode === "interpolated" || connectivity.getNetConnectedToId(trace.pcb_trace_id) !== net)
        continue;
      for (let i = 1; i < trace.route.length; i++) {
        const a = trace.route[i - 1], b = trace.route[i];
        if (a.route_type !== "wire" || b.route_type !== "wire" || a.layer !== b.layer || !layers.includes(a.layer) || !Number.isFinite(a.width) || a.width <= 0 || Math.hypot(a.x - b.x, a.y - b.y) <= CONTACT_EPSILON)
          continue;
        const reach = contact.radius === void 0 ? 0 : contact.radius + a.width / 2;
        if (pointToSegmentDistance2(contact, a, b) <= reach + CONTACT_EPSILON) {
          touchingTraceIds.add(trace.pcb_trace_id);
          break;
        }
      }
    }
    const copper = { ...contact, touchesPad, touchingTraceIds };
    const byLayer = index.get(net) ?? /* @__PURE__ */ new Map();
    for (const layer of layers) {
      const contacts = byLayer.get(layer) ?? [];
      contacts.push(copper);
      byLayer.set(layer, contacts);
    }
    index.set(net, byLayer);
  };
  for (const via of vias) {
    if (!Number.isFinite(via.outer_diameter) || via.outer_diameter <= 0)
      continue;
    add(via.pcb_via_id, getLayersOfPcbElement(via), {
      x: via.x,
      y: via.y,
      ownerTraceId: via.pcb_trace_id,
      radius: via.outer_diameter / 2
    });
  }
  for (const trace of circuitJson) {
    if (trace.type !== "pcb_trace") continue;
    for (const point of trace.route) {
      if (point.route_type !== "via") continue;
      if (vias.some(
        (via) => Math.hypot(via.x - point.x, via.y - point.y) <= CONTACT_EPSILON && (via.pcb_trace_id === trace.pcb_trace_id || !via.pcb_trace_id && connectivity.areIdsConnected(
          via.pcb_via_id,
          trace.pcb_trace_id
        ))
      ))
        continue;
      const from = stack.indexOf(point.from_layer);
      const to = stack.indexOf(point.to_layer);
      if (from < 0 || to < 0) continue;
      const diameter = point.outer_diameter;
      if (diameter !== void 0 && (!Number.isFinite(diameter) || diameter <= 0))
        continue;
      add(
        trace.pcb_trace_id,
        stack.slice(Math.min(from, to), Math.max(from, to) + 1),
        {
          x: point.x,
          y: point.y,
          ownerTraceId: trace.pcb_trace_id,
          radius: diameter === void 0 ? void 0 : diameter / 2
        }
      );
    }
  }
  return index;
}
function endpointTouchesVia({
  point,
  width,
  ownerTrace,
  index,
  connectivity
}) {
  if (point.route_type !== "wire" || !Number.isFinite(width) || width <= 0)
    return false;
  const net = connectivity.getNetConnectedToId(ownerTrace.pcb_trace_id);
  if (!net) return false;
  return (index.get(net)?.get(point.layer) ?? []).some((via) => {
    if (via.ownerTraceId === ownerTrace.pcb_trace_id) return false;
    if (!via.touchesPad && ![...via.touchingTraceIds].some((id) => id !== ownerTrace.pcb_trace_id))
      return false;
    const contactDistance = via.radius === void 0 ? 0 : via.radius + width / 2;
    return Math.hypot(point.x - via.x, point.y - via.y) <= contactDistance + CONTACT_EPSILON;
  });
}

// lib/check-traces-are-contiguous/check-traces-are-contiguous.ts
var ENDPOINT_CONTACT_EPSILON = 1e-9;
var TRACE_SEGMENT_GEOMETRY_EPSILON = 1e-9;
function routePointTouchesPad(point, pad) {
  return point.route_type === "wire" && getLayersOfPcbElement(pad).includes(point.layer) && isPointInPad(point, pad);
}
function getTraceWireSegmentsByNetAndLayer(pcbTraces, fullConnectivityMap) {
  const segmentsByNetAndLayer = /* @__PURE__ */ new Map();
  for (const trace of pcbTraces) {
    if (trace.route_thickness_mode === "interpolated") continue;
    const netId = fullConnectivityMap.getNetConnectedToId(trace.pcb_trace_id);
    if (!netId) continue;
    for (let i = 0; i < trace.route.length - 1; i++) {
      const start = trace.route[i];
      const end = trace.route[i + 1];
      if (start.route_type !== "wire" || end.route_type !== "wire") continue;
      if (start.layer !== end.layer) continue;
      if (Math.hypot(start.x - end.x, start.y - end.y) <= TRACE_SEGMENT_GEOMETRY_EPSILON) {
        continue;
      }
      const segmentsByLayer = segmentsByNetAndLayer.get(netId) ?? /* @__PURE__ */ new Map();
      const segments = segmentsByLayer.get(start.layer) ?? [];
      segments.push({ trace, start, end });
      segmentsByLayer.set(start.layer, segments);
      segmentsByNetAndLayer.set(netId, segmentsByLayer);
    }
  }
  return segmentsByNetAndLayer;
}
function getEndpointTraceCopperWidth(trace, endpoint) {
  if (trace.route_thickness_mode === "interpolated") return void 0;
  let segmentStartIndex = endpoint === "start" ? 0 : trace.route.length - 2;
  const indexStep = endpoint === "start" ? 1 : -1;
  while (segmentStartIndex >= 0 && segmentStartIndex < trace.route.length - 1) {
    const segmentStart = trace.route[segmentStartIndex];
    const segmentEnd = trace.route[segmentStartIndex + 1];
    if (segmentStart?.route_type !== "wire" || segmentEnd?.route_type !== "wire" || segmentStart.layer !== segmentEnd.layer) {
      return void 0;
    }
    if (Math.hypot(segmentStart.x - segmentEnd.x, segmentStart.y - segmentEnd.y) > TRACE_SEGMENT_GEOMETRY_EPSILON) {
      return segmentStart.width;
    }
    segmentStartIndex += indexStep;
  }
  return void 0;
}
function routePointTouchesLogicallyConnectedTraceCopper({
  point,
  endpointTraceCopperWidth,
  ownerTrace,
  traceWireSegmentsByNetAndLayer,
  fullConnectivityMap
}) {
  if (point.route_type !== "wire") return false;
  const ownerNetId = fullConnectivityMap.getNetConnectedToId(
    ownerTrace.pcb_trace_id
  );
  if (!ownerNetId) return false;
  const candidateSegments = traceWireSegmentsByNetAndLayer.get(ownerNetId)?.get(point.layer) ?? [];
  for (const segment of candidateSegments) {
    if (segment.trace.pcb_trace_id === ownerTrace.pcb_trace_id) continue;
    const maximumContactDistance = endpointTraceCopperWidth / 2 + segment.start.width / 2 + ENDPOINT_CONTACT_EPSILON;
    if (pointToSegmentDistance3(point, segment.start, segment.end) <= maximumContactDistance) {
      return true;
    }
  }
  return false;
}
function getRoutePointCenter(point) {
  if (point.route_type === "through_pad") {
    return {
      x: (point.start.x + point.end.x) / 2,
      y: (point.start.y + point.end.y) / 2
    };
  }
  return { x: point.x, y: point.y };
}
function routePointConnectsToAnotherExpectedPort(point, expectedPorts, missingPcbPortId, padMap) {
  return expectedPorts.some((expectedPort) => {
    if (!expectedPort.pcb_port_id || expectedPort.pcb_port_id === missingPcbPortId) {
      return false;
    }
    const expectedPads = padMap.get(expectedPort.pcb_port_id);
    return expectedPads?.some((pad) => routePointTouchesPad(point, pad)) ?? false;
  });
}
function getMissingConnectionErrorCenter({
  firstPoint,
  lastPoint,
  port,
  expectedPorts,
  padMap
}) {
  let errorLocation;
  const firstWirePoint = firstPoint.route_type === "wire" ? firstPoint : void 0;
  const lastWirePoint = lastPoint.route_type === "wire" ? lastPoint : void 0;
  const firstWirePointReferencesPort = getPcbPortIdsConnectedToRoutePoint(
    firstPoint
  ).includes(port.pcb_port_id);
  const lastWirePointReferencesPort = getPcbPortIdsConnectedToRoutePoint(
    lastPoint
  ).includes(port.pcb_port_id);
  if (firstWirePointReferencesPort && firstWirePoint) {
    errorLocation = firstWirePoint;
  } else if (lastWirePointReferencesPort && lastWirePoint) {
    errorLocation = lastWirePoint;
  } else if (routePointConnectsToAnotherExpectedPort(
    firstPoint,
    expectedPorts,
    port.pcb_port_id,
    padMap
  ) && lastWirePoint) {
    errorLocation = lastWirePoint;
  } else if (routePointConnectsToAnotherExpectedPort(
    lastPoint,
    expectedPorts,
    port.pcb_port_id,
    padMap
  ) && firstWirePoint) {
    errorLocation = firstWirePoint;
  } else if (firstWirePoint && lastWirePoint) {
    errorLocation = distance2(firstWirePoint, port) <= distance2(lastWirePoint, port) ? firstWirePoint : lastWirePoint;
  } else if (firstWirePoint) {
    errorLocation = firstWirePoint;
  } else if (lastWirePoint) {
    errorLocation = lastWirePoint;
  }
  const firstPointCenter = getRoutePointCenter(firstPoint);
  const lastPointCenter = getRoutePointCenter(lastPoint);
  return errorLocation ? { x: errorLocation.x, y: errorLocation.y } : {
    x: (firstPointCenter.x + lastPointCenter.x) / 2,
    y: (firstPointCenter.y + lastPointCenter.y) / 2
  };
}
function checkTracesAreContiguous(circuitJson) {
  const errors = [];
  const pcbPorts = circuitJson.filter(
    (el) => el.type === "pcb_port"
  );
  const pcbTraces = circuitJson.filter(
    (el) => el.type === "pcb_trace"
  );
  const sourceTraces = circuitJson.filter(
    (el) => el.type === "source_trace"
  );
  const pcbSmtPads = circuitJson.filter(
    (el) => el.type === "pcb_smtpad"
  );
  const pcbPlatedHoles = circuitJson.filter(
    (el) => el.type === "pcb_plated_hole"
  );
  const padMap = /* @__PURE__ */ new Map();
  const pcbConnectivityMap = new PcbConnectivityMap2(circuitJson);
  let fullConnectivityMap;
  let traceWireSegmentsByNetAndLayer;
  const getFullConnectivityMap = () => {
    fullConnectivityMap ??= getFullConnectivityMapFromCircuitJson7(circuitJson);
    return fullConnectivityMap;
  };
  const getTraceWireSegmentIndex = () => {
    traceWireSegmentsByNetAndLayer ??= getTraceWireSegmentsByNetAndLayer(
      pcbTraces,
      getFullConnectivityMap()
    );
    return traceWireSegmentsByNetAndLayer;
  };
  let viaContactIndex;
  const getViaIndex = () => {
    viaContactIndex ??= getViaContactIndex(
      circuitJson,
      getFullConnectivityMap()
    );
    return viaContactIndex;
  };
  let pourConnectivity;
  const getPourConnectivity = () => pourConnectivity ??= new CopperPourConnectivity(
    circuitJson,
    getFullConnectivityMap()
  );
  const checkedSourceTraceIds = /* @__PURE__ */ new Set();
  for (const pad of pcbSmtPads) {
    if (pad.pcb_port_id) {
      padMap.set(pad.pcb_port_id, [...padMap.get(pad.pcb_port_id) ?? [], pad]);
    }
  }
  for (const hole of pcbPlatedHoles) {
    if (hole.pcb_port_id) {
      padMap.set(hole.pcb_port_id, [
        ...padMap.get(hole.pcb_port_id) ?? [],
        hole
      ]);
    }
  }
  const touchedPortIdsByTraceId = /* @__PURE__ */ new Map();
  const traceIdsByTouchedPortId = /* @__PURE__ */ new Map();
  for (const trace of pcbTraces) {
    const touchedPortIds = /* @__PURE__ */ new Set();
    const firstPoint = trace.route[0];
    const lastPoint = trace.route.at(-1);
    for (const point of [firstPoint, lastPoint]) {
      if (!point) continue;
      for (const [pcbPortId, pads] of padMap) {
        if (pads.some((pad) => routePointTouchesPad(point, pad))) {
          touchedPortIds.add(pcbPortId);
        }
      }
    }
    touchedPortIdsByTraceId.set(trace.pcb_trace_id, touchedPortIds);
    for (const pcbPortId of touchedPortIds) {
      const traceIds = traceIdsByTouchedPortId.get(pcbPortId) ?? /* @__PURE__ */ new Set();
      traceIds.add(trace.pcb_trace_id);
      traceIdsByTouchedPortId.set(pcbPortId, traceIds);
    }
  }
  const physicallyConnectedTracesByTraceId = /* @__PURE__ */ new Map();
  const getPhysicallyConnectedTraces = (startTrace) => {
    const cached = physicallyConnectedTracesByTraceId.get(
      startTrace.pcb_trace_id
    );
    if (cached) return cached;
    const connectedTraceIds = /* @__PURE__ */ new Set();
    const pendingTraceIds = [startTrace.pcb_trace_id];
    while (pendingTraceIds.length > 0) {
      const traceId = pendingTraceIds.pop();
      if (connectedTraceIds.has(traceId)) continue;
      connectedTraceIds.add(traceId);
      for (const connectedTrace of pcbConnectivityMap.getAllTracesConnectedToTrace(
        traceId
      )) {
        if (!connectedTraceIds.has(connectedTrace.pcb_trace_id)) {
          pendingTraceIds.push(connectedTrace.pcb_trace_id);
        }
      }
      for (const pcbPortId of touchedPortIdsByTraceId.get(traceId) ?? []) {
        for (const touchingTraceId of traceIdsByTouchedPortId.get(pcbPortId) ?? []) {
          if (!connectedTraceIds.has(touchingTraceId)) {
            pendingTraceIds.push(touchingTraceId);
          }
        }
      }
    }
    const connectedTraces = pcbTraces.filter(
      (trace) => connectedTraceIds.has(trace.pcb_trace_id)
    );
    for (const trace of connectedTraces) {
      physicallyConnectedTracesByTraceId.set(
        trace.pcb_trace_id,
        connectedTraces
      );
    }
    return connectedTraces;
  };
  for (const trace of pcbTraces) {
    if (trace.route.length === 0) continue;
    const firstPoint = trace.route[0];
    const lastPoint = trace.route[trace.route.length - 1];
    const sourceTrace = sourceTraces.find(
      (st) => st.source_trace_id === trace.source_trace_id
    );
    const expectedPorts = sourceTrace ? pcbPorts.filter(
      (port) => sourceTrace.connected_source_port_ids?.includes(port.source_port_id)
    ) : [];
    for (let i = 1; i < trace.route.length - 1; i++) {
      const prevPoint = trace.route[i - 1];
      const currentPoint = trace.route[i];
      const nextPoint = trace.route[i + 1];
      if (currentPoint.route_type === "via") {
        const prevIsWire = prevPoint.route_type === "wire";
        const nextIsWire = nextPoint.route_type === "wire";
        if (prevIsWire && nextIsWire) {
          const prevAligned = Math.abs(prevPoint.x - currentPoint.x) < 0.01 && Math.abs(prevPoint.y - currentPoint.y) < 0.01;
          const nextAligned = Math.abs(nextPoint.x - currentPoint.x) < 0.01 && Math.abs(nextPoint.y - currentPoint.y) < 0.01;
          if (!prevAligned || !nextAligned) {
            const traceName2 = getReadableNameForPcbTrace(
              circuitJson,
              trace.pcb_trace_id
            );
            errors.push({
              type: "pcb_trace_error",
              message: `Via in trace [${traceName2}] is misaligned at position {x: ${currentPoint.x}, y: ${currentPoint.y}}.`,
              source_trace_id: sourceTrace?.source_trace_id || trace.source_trace_id || `!${trace.pcb_trace_id}`,
              error_type: "pcb_trace_error",
              pcb_trace_id: trace.pcb_trace_id,
              pcb_trace_error_id: `misaligned_via_${trace.pcb_trace_id}_${i}`,
              pcb_component_ids: [],
              pcb_port_ids: []
            });
          }
        }
      }
    }
    const traceName = getReadableNameForPcbTrace(
      circuitJson,
      trace.pcb_trace_id
    );
    if (sourceTrace && expectedPorts.length > 0) {
      if (checkedSourceTraceIds.has(sourceTrace.source_trace_id)) continue;
      checkedSourceTraceIds.add(sourceTrace.source_trace_id);
    }
    for (const port of expectedPorts) {
      if (!port.pcb_port_id) continue;
      const pads = padMap.get(port.pcb_port_id);
      if (!pads?.length) continue;
      const isConnectedByRoutedSourceTrace = getPhysicallyConnectedTraces(
        trace
      ).some(
        (candidateTrace) => touchedPortIdsByTraceId.get(candidateTrace.pcb_trace_id)?.has(port.pcb_port_id)
      );
      if (isConnectedByRoutedSourceTrace || getPourConnectivity().traceConnectedToPortThroughPour(
        trace.pcb_trace_id,
        port.pcb_port_id
      ))
        continue;
      const isFirstPointConnected = pads.some(
        (pad) => routePointTouchesPad(firstPoint, pad)
      );
      const isLastPointConnected = pads.some(
        (pad) => routePointTouchesPad(lastPoint, pad)
      );
      if (!isFirstPointConnected && !isLastPointConnected) {
        const portName = getReadableNameForPcbPort2(
          circuitJson,
          port.pcb_port_id
        ).replace("pcb_port", "");
        const padType = pads[0].type.replace(/pcb_/, "");
        const errorCenter = getMissingConnectionErrorCenter({
          firstPoint,
          lastPoint,
          port,
          expectedPorts,
          padMap
        });
        errors.push({
          type: "pcb_trace_error",
          message: `Trace [${traceName}] is missing a connection to ${padType}${portName}`,
          source_trace_id: sourceTrace?.source_trace_id || trace.source_trace_id || `!${trace.pcb_trace_id}`,
          error_type: "pcb_trace_error",
          pcb_trace_id: trace.pcb_trace_id,
          pcb_trace_error_id: `missing_connection_${trace.pcb_trace_id}_${port.pcb_port_id}`,
          center: errorCenter,
          pcb_component_ids: [],
          pcb_port_ids: [port.pcb_port_id]
        });
      }
    }
    if (expectedPorts.length === 0) {
      let firstConnectsToAnyPad = false;
      let lastConnectsToAnyPad = false;
      for (const pads of padMap.values()) {
        if (pads.some((pad) => routePointTouchesPad(firstPoint, pad))) {
          firstConnectsToAnyPad = true;
        }
        if (pads.some((pad) => routePointTouchesPad(lastPoint, pad))) {
          lastConnectsToAnyPad = true;
        }
      }
      const firstEndpointTraceCopperWidth = !firstConnectsToAnyPad ? getEndpointTraceCopperWidth(trace, "start") : void 0;
      const lastEndpointTraceCopperWidth = !lastConnectsToAnyPad ? getEndpointTraceCopperWidth(trace, "end") : void 0;
      const firstConnectsToLogicallyConnectedTraceCopper = firstEndpointTraceCopperWidth !== void 0 && routePointTouchesLogicallyConnectedTraceCopper({
        point: firstPoint,
        endpointTraceCopperWidth: firstEndpointTraceCopperWidth,
        ownerTrace: trace,
        traceWireSegmentsByNetAndLayer: getTraceWireSegmentIndex(),
        fullConnectivityMap: getFullConnectivityMap()
      });
      const lastConnectsToLogicallyConnectedTraceCopper = lastEndpointTraceCopperWidth !== void 0 && routePointTouchesLogicallyConnectedTraceCopper({
        point: lastPoint,
        endpointTraceCopperWidth: lastEndpointTraceCopperWidth,
        ownerTrace: trace,
        traceWireSegmentsByNetAndLayer: getTraceWireSegmentIndex(),
        fullConnectivityMap: getFullConnectivityMap()
      });
      const firstIsConnected = firstConnectsToAnyPad || firstConnectsToLogicallyConnectedTraceCopper || getPourConnectivity().endpointTouchesConnectedPour(
        firstPoint,
        trace.pcb_trace_id,
        firstEndpointTraceCopperWidth ?? 0
      ) || firstEndpointTraceCopperWidth !== void 0 && endpointTouchesVia({
        point: firstPoint,
        width: firstEndpointTraceCopperWidth,
        ownerTrace: trace,
        index: getViaIndex(),
        connectivity: getFullConnectivityMap()
      });
      const lastIsConnected = lastConnectsToAnyPad || lastConnectsToLogicallyConnectedTraceCopper || getPourConnectivity().endpointTouchesConnectedPour(
        lastPoint,
        trace.pcb_trace_id,
        lastEndpointTraceCopperWidth ?? 0
      ) || lastEndpointTraceCopperWidth !== void 0 && endpointTouchesVia({
        point: lastPoint,
        width: lastEndpointTraceCopperWidth,
        ownerTrace: trace,
        index: getViaIndex(),
        connectivity: getFullConnectivityMap()
      });
      const endpointsAreSame = firstPoint.route_type === "wire" && lastPoint.route_type === "wire" && firstPoint.layer === lastPoint.layer && Math.hypot(firstPoint.x - lastPoint.x, firstPoint.y - lastPoint.y) <= ENDPOINT_CONTACT_EPSILON;
      if (!firstIsConnected && firstPoint.route_type === "wire") {
        errors.push({
          type: "pcb_trace_error",
          message: `Trace [${traceName}] has disconnected endpoint at (${firstPoint.x.toFixed(2)}, ${firstPoint.y.toFixed(2)})`,
          source_trace_id: sourceTrace?.source_trace_id || trace.source_trace_id || `!${trace.pcb_trace_id}`,
          error_type: "pcb_trace_error",
          pcb_trace_id: trace.pcb_trace_id,
          pcb_trace_error_id: `disconnected_endpoint_${trace.pcb_trace_id}_start`,
          center: { x: firstPoint.x, y: firstPoint.y },
          pcb_component_ids: [],
          pcb_port_ids: []
        });
      }
      if (!lastIsConnected && lastPoint.route_type === "wire" && !(endpointsAreSame && !firstIsConnected)) {
        errors.push({
          type: "pcb_trace_error",
          message: `Trace [${traceName}] has disconnected endpoint at (${lastPoint.x.toFixed(2)}, ${lastPoint.y.toFixed(2)})`,
          source_trace_id: sourceTrace?.source_trace_id || trace.source_trace_id || `!${trace.pcb_trace_id}`,
          error_type: "pcb_trace_error",
          pcb_trace_id: trace.pcb_trace_id,
          pcb_trace_error_id: `disconnected_endpoint_${trace.pcb_trace_id}_end`,
          center: { x: lastPoint.x, y: lastPoint.y },
          pcb_component_ids: [],
          pcb_port_ids: []
        });
      }
    }
  }
  return errors;
}

// lib/check-trace-out-of-board/checkTraceOutOfBoard.ts
import { cju as cju5 } from "@tscircuit/circuit-json-util";
import { segmentToSegmentMinDistance as segmentToSegmentMinDistance4 } from "@tscircuit/math-utils";
function getBoardPolygonPoints(board) {
  if (board.outline && board.outline.length > 0) {
    return board.outline.map((p) => ({ x: p.x, y: p.y }));
  }
  if (board.center && typeof board.width === "number" && typeof board.height === "number") {
    const cx = board.center.x;
    const cy = board.center.y;
    const hw = board.width / 2;
    const hh = board.height / 2;
    return [
      { x: cx - hw, y: cy - hh },
      // bottom-left
      { x: cx + hw, y: cy - hh },
      // bottom-right
      { x: cx + hw, y: cy + hh },
      // top-right
      { x: cx - hw, y: cy + hh }
      // top-left
    ];
  }
  return null;
}
function checkPcbTracesOutOfBoard(circuitJson, config = {}) {
  const errors = [];
  const board = getPcbBoard(circuitJson);
  if (!board) return errors;
  const margin = config.margin ?? getBoardDrcValue(board, "min_board_edge_clearance") ?? jlcMinTolerances.min_board_edge_clearance;
  const boardPoints = getBoardPolygonPoints(board);
  if (!boardPoints) return errors;
  const pcbTraces = cju5(circuitJson).pcb_trace.list();
  for (const trace of pcbTraces) {
    if (trace.route.length < 2) continue;
    for (let i = 0; i < trace.route.length - 1; i++) {
      const p1 = trace.route[i];
      const p2 = trace.route[i + 1];
      if (p1.route_type !== "wire" || p2.route_type !== "wire") continue;
      const traceWidth = "width" in p1 ? p1.width : "width" in p2 ? p2.width : 0.1;
      const segmentStart = { x: p1.x, y: p1.y };
      const segmentEnd = { x: p2.x, y: p2.y };
      let minDistance = Number.POSITIVE_INFINITY;
      for (let j = 0; j < boardPoints.length; j++) {
        const edgeStart = boardPoints[j];
        const edgeEnd = boardPoints[(j + 1) % boardPoints.length];
        const distance3 = segmentToSegmentMinDistance4(
          segmentStart,
          segmentEnd,
          edgeStart,
          edgeEnd
        );
        if (distance3 < minDistance) {
          minDistance = distance3;
        }
      }
      const minimumDistance = traceWidth / 2 + margin;
      if (minDistance < minimumDistance) {
        const error = {
          type: "pcb_trace_error",
          error_type: "pcb_trace_error",
          pcb_trace_error_id: `trace_too_close_to_board_${trace.pcb_trace_id}_segment_${i}`,
          message: `Trace too close to board edge (${minDistance.toFixed(3)}mm < ${minimumDistance.toFixed(3)}mm required, margin: ${margin}mm)`,
          pcb_trace_id: trace.pcb_trace_id,
          source_trace_id: trace.source_trace_id || "",
          center: {
            x: (segmentStart.x + segmentEnd.x) / 2,
            y: (segmentStart.y + segmentEnd.y) / 2
          },
          pcb_component_ids: [],
          pcb_port_ids: []
        };
        errors.push(error);
      }
    }
  }
  return errors;
}

// lib/check-pcb-components-overlap/checkPcbComponentOverlap.ts
import {
  cju as cju6,
  getBoundsOfPcbElements as getBoundsOfPcbElements5,
  getPrimaryId as getPrimaryId5
} from "@tscircuit/circuit-json-util";
import { doBoundsOverlap as doBoundsOverlap3 } from "@tscircuit/math-utils";
import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson8 } from "circuit-json-to-connectivity-map";

// lib/check-pcb-components-overlap/doPcbElementsOverlap.ts
import { getBoundsOfPcbElements as getBoundsOfPcbElements4 } from "@tscircuit/circuit-json-util";
import { doBoundsOverlap as doBoundsOverlap2 } from "@tscircuit/math-utils";
function getElementLayers(elem) {
  if (elem.type === "pcb_courtyard_circle" || elem.type === "pcb_courtyard_outline" || elem.type === "pcb_courtyard_polygon" || elem.type === "pcb_courtyard_rect") {
    return [elem.layer];
  }
  return getLayersOfPcbElement(elem);
}
function doLayersOverlap(layers1, layers2) {
  if (layers1.length === 0 || layers2.length === 0) return true;
  return layers1.some((l) => layers2.includes(l));
}
function doPcbElementsOverlap(elem1, elem2) {
  const layers1 = getElementLayers(elem1);
  const layers2 = getElementLayers(elem2);
  if (!doLayersOverlap(layers1, layers2)) return false;
  if (elem1.type === "pcb_smtpad" && elem2.type === "pcb_smtpad") {
    return getPadToPadGap(elem1, elem2) <= 0;
  }
  const bounds1 = getBoundsOfPcbElements4([elem1]);
  const bounds2 = getBoundsOfPcbElements4([elem2]);
  return doBoundsOverlap2(bounds1, bounds2);
}

// lib/check-pcb-components-overlap/checkPcbComponentOverlap.ts
var isCourtyardElement = (element) => element.type === "pcb_courtyard_circle" || element.type === "pcb_courtyard_outline" || element.type === "pcb_courtyard_polygon" || element.type === "pcb_courtyard_rect";
var formatOverlapElementDescription = (circuitJson, element) => {
  if ("pcb_port_id" in element && element.pcb_port_id) {
    return getReadableNameForPort(circuitJson, element.pcb_port_id);
  }
  const id = getPrimaryId5(element);
  const readableName = getReadableNameForElementId(circuitJson, id);
  return readableName === "element" ? `[${id}]` : readableName;
};
function checkPcbComponentOverlap(circuitJson) {
  const errors = [];
  const connMap = getFullConnectivityMapFromCircuitJson8(circuitJson);
  const smtPads = cju6(circuitJson).pcb_smtpad.list();
  const platedHoles = cju6(circuitJson).pcb_plated_hole.list();
  const holes = cju6(circuitJson).pcb_hole.list();
  const courtyards = circuitJson.filter(isCourtyardElement);
  const componentMap = /* @__PURE__ */ new Map();
  for (const pad of smtPads) {
    const componentId = pad.pcb_component_id || `standalone_pad_${getPrimaryId5(pad)}`;
    if (!componentMap.has(componentId)) {
      componentMap.set(componentId, {
        component_id: componentId,
        elements: []
      });
    }
    componentMap.get(componentId).elements.push(pad);
  }
  for (const hole of platedHoles) {
    const componentId = hole.pcb_component_id || `standalone_plated_hole_${getPrimaryId5(hole)}`;
    if (!componentMap.has(componentId)) {
      componentMap.set(componentId, {
        component_id: componentId,
        elements: []
      });
    }
    componentMap.get(componentId).elements.push(hole);
  }
  for (const hole of holes) {
    const componentId = hole.pcb_component_id || `standalone_hole_${getPrimaryId5(hole)}`;
    if (!componentMap.has(componentId)) {
      componentMap.set(componentId, {
        component_id: componentId,
        elements: [hole]
      });
    }
  }
  for (const courtyard of courtyards) {
    const componentId = courtyard.pcb_component_id;
    if (!componentMap.has(componentId)) {
      componentMap.set(componentId, {
        component_id: componentId,
        elements: []
      });
    }
    componentMap.get(componentId).elements.push(courtyard);
  }
  for (const [componentId, componentData] of componentMap) {
    if (componentData.elements.length > 0) {
      componentData.bounds = getBoundsOfPcbElements5(componentData.elements);
    }
  }
  const componentsWithElements = Array.from(componentMap.values());
  for (let i = 0; i < componentsWithElements.length; i++) {
    for (let j = i + 1; j < componentsWithElements.length; j++) {
      const comp1 = componentsWithElements[i];
      const comp2 = componentsWithElements[j];
      if (!comp1.bounds || !comp2.bounds) {
        continue;
      }
      if (!doBoundsOverlap3(comp1.bounds, comp2.bounds)) {
        continue;
      }
      for (const elem1 of comp1.elements) {
        for (const elem2 of comp2.elements) {
          const id1 = getPrimaryId5(elem1);
          const id2 = getPrimaryId5(elem2);
          if ((isCourtyardElement(elem1) || isCourtyardElement(elem2)) && elem1.type !== "pcb_hole" && elem2.type !== "pcb_hole") {
            continue;
          }
          if (elem1.type === "pcb_smtpad" && elem2.type === "pcb_smtpad" && connMap.areIdsConnected(id1, id2)) {
            continue;
          }
          if (doPcbElementsOverlap(elem1, elem2)) {
            const elem1Description = formatOverlapElementDescription(
              circuitJson,
              elem1
            );
            const elem2Description = formatOverlapElementDescription(
              circuitJson,
              elem2
            );
            const error = {
              type: "pcb_footprint_overlap_error",
              pcb_error_id: `pcb_footprint_overlap_${id1}_${id2}`,
              error_type: "pcb_footprint_overlap_error",
              message: `${elem1.type} ${elem1Description} overlaps with ${elem2.type} ${elem2Description}`
            };
            if (elem1.type === "pcb_smtpad" || elem2.type === "pcb_smtpad") {
              error.pcb_smtpad_ids = [];
              if (elem1.type === "pcb_smtpad") error.pcb_smtpad_ids.push(id1);
              if (elem2.type === "pcb_smtpad") error.pcb_smtpad_ids.push(id2);
            }
            if (elem1.type === "pcb_plated_hole" || elem2.type === "pcb_plated_hole") {
              error.pcb_plated_hole_ids = [];
              if (elem1.type === "pcb_plated_hole")
                error.pcb_plated_hole_ids.push(id1);
              if (elem2.type === "pcb_plated_hole")
                error.pcb_plated_hole_ids.push(id2);
            }
            if (elem1.type === "pcb_hole" || elem2.type === "pcb_hole") {
              error.pcb_hole_ids = [];
              if (elem1.type === "pcb_hole") error.pcb_hole_ids.push(id1);
              if (elem2.type === "pcb_hole") error.pcb_hole_ids.push(id2);
            }
            errors.push(error);
          }
        }
      }
    }
  }
  return errors;
}

// lib/check-pcb-components-missing-courtyard.ts
var courtyardTypes = /* @__PURE__ */ new Set([
  "pcb_courtyard_circle",
  "pcb_courtyard_outline",
  "pcb_courtyard_polygon",
  "pcb_courtyard_pill",
  "pcb_courtyard_rect"
]);
function checkPcbComponentsMissingCourtyard(circuitJson) {
  const componentIdsWithCourtyards = new Set(
    circuitJson.filter((element) => courtyardTypes.has(element.type)).flatMap(
      (element) => "pcb_component_id" in element && element.pcb_component_id ? [element.pcb_component_id] : []
    )
  );
  return circuitJson.filter(
    (element) => element.type === "pcb_component"
  ).filter(
    (component) => !componentIdsWithCourtyards.has(component.pcb_component_id)
  ).map((component) => {
    const sourceComponent = component.source_component_id ? circuitJson.find(
      (element) => element.type === "source_component" && element.source_component_id === component.source_component_id
    ) : void 0;
    const componentName = sourceComponent?.type === "source_component" ? sourceComponent.name : getReadableNameForComponent(circuitJson, component.pcb_component_id);
    return {
      type: "pcb_component_missing_courtyard_warning",
      pcb_component_missing_courtyard_warning_id: `pcb_component_missing_courtyard_warning_${component.pcb_component_id}`,
      warning_type: "pcb_component_missing_courtyard_warning",
      message: `${componentName} has no courtyard`,
      pcb_component_id: component.pcb_component_id,
      source_component_id: component.source_component_id,
      subcircuit_id: component.subcircuit_id
    };
  });
}

// lib/check-pcb-trace-lengths.ts
var DEFAULT_VIA_LENGTH_MM = 1.6;
var getRoutePointPosition = (routePoint) => routePoint.route_type === "through_pad" ? routePoint.start : { x: routePoint.x, y: routePoint.y };
var getPcbTraceLength = (pcbTrace) => {
  if (pcbTrace.trace_length !== void 0) return pcbTrace.trace_length;
  let traceLength = 0;
  for (let routePointIndex = 0; routePointIndex < pcbTrace.route.length; routePointIndex++) {
    const routePoint = pcbTrace.route[routePointIndex];
    if (!routePoint) continue;
    if (routePoint.route_type === "via") {
      traceLength += DEFAULT_VIA_LENGTH_MM;
      continue;
    }
    const nextRoutePoint = pcbTrace.route[routePointIndex + 1];
    if (!nextRoutePoint) continue;
    const routePointPosition = getRoutePointPosition(routePoint);
    const nextRoutePointPosition = getRoutePointPosition(nextRoutePoint);
    traceLength += Math.hypot(
      nextRoutePointPosition.x - routePointPosition.x,
      nextRoutePointPosition.y - routePointPosition.y
    );
  }
  return traceLength;
};
var getReferencedPcbPortIds = (pcbTrace) => {
  const pcbPortIds = /* @__PURE__ */ new Set();
  for (const routePoint of pcbTrace.route) {
    if (routePoint.route_type !== "wire") continue;
    if (routePoint.start_pcb_port_id) {
      pcbPortIds.add(routePoint.start_pcb_port_id);
    }
    if (routePoint.end_pcb_port_id) {
      pcbPortIds.add(routePoint.end_pcb_port_id);
    }
  }
  return pcbPortIds;
};
var checkPcbTraceLengths = (circuitJson) => {
  const sourceTraces = circuitJson.filter(
    (element) => element.type === "source_trace"
  );
  const pcbTraces = circuitJson.filter(
    (element) => element.type === "pcb_trace"
  );
  const pcbPorts = circuitJson.filter(
    (element) => element.type === "pcb_port"
  );
  const sourceTracesById = new Map(
    sourceTraces.map((sourceTrace) => [
      sourceTrace.source_trace_id,
      sourceTrace
    ])
  );
  const pcbPortIdsBySourcePortId = /* @__PURE__ */ new Map();
  for (const pcbPort of pcbPorts) {
    if (!pcbPort.source_port_id) continue;
    const pcbPortIds = pcbPortIdsBySourcePortId.get(pcbPort.source_port_id) ?? /* @__PURE__ */ new Set();
    pcbPortIds.add(pcbPort.pcb_port_id);
    pcbPortIdsBySourcePortId.set(pcbPort.source_port_id, pcbPortIds);
  }
  const pcbTracesBySourceTraceId = /* @__PURE__ */ new Map();
  for (const pcbTrace of pcbTraces) {
    if (!pcbTrace.source_trace_id) continue;
    const matchingPcbTraces = pcbTracesBySourceTraceId.get(pcbTrace.source_trace_id) ?? [];
    matchingPcbTraces.push(pcbTrace);
    pcbTracesBySourceTraceId.set(pcbTrace.source_trace_id, matchingPcbTraces);
  }
  const exactEndpointPcbTraceIdsBySourceTraceId = /* @__PURE__ */ new Map();
  for (const sourceTrace of sourceTraces) {
    if (sourceTrace.connected_source_port_ids.length !== 2) continue;
    const endpointPcbPortIds = sourceTrace.connected_source_port_ids.map(
      (sourcePortId) => pcbPortIdsBySourcePortId.get(sourcePortId)
    );
    if (endpointPcbPortIds.some((pcbPortIds) => !pcbPortIds?.size)) continue;
    const exactEndpointPcbTraceIds = new Set(
      (pcbTracesBySourceTraceId.get(sourceTrace.source_trace_id) ?? []).filter((pcbTrace) => {
        const referencedPcbPortIds = getReferencedPcbPortIds(pcbTrace);
        return endpointPcbPortIds.every(
          (pcbPortIds) => [...pcbPortIds].some(
            (pcbPortId) => referencedPcbPortIds.has(pcbPortId)
          )
        );
      }).map((pcbTrace) => pcbTrace.pcb_trace_id)
    );
    if (exactEndpointPcbTraceIds.size > 0) {
      exactEndpointPcbTraceIdsBySourceTraceId.set(
        sourceTrace.source_trace_id,
        exactEndpointPcbTraceIds
      );
    }
  }
  const warnings = [];
  for (const pcbTrace of pcbTraces) {
    if (!pcbTrace.source_trace_id) continue;
    const sourceTrace = sourceTracesById.get(pcbTrace.source_trace_id);
    if (!sourceTrace) continue;
    const maximumTraceLength = sourceTrace.max_length;
    if (typeof maximumTraceLength !== "number") continue;
    const exactEndpointPcbTraceIds = exactEndpointPcbTraceIdsBySourceTraceId.get(sourceTrace.source_trace_id);
    if (exactEndpointPcbTraceIds && !exactEndpointPcbTraceIds.has(pcbTrace.pcb_trace_id)) {
      continue;
    }
    const actualTraceLength = getPcbTraceLength(pcbTrace);
    if (actualTraceLength <= maximumTraceLength) continue;
    warnings.push({
      type: "pcb_trace_too_long_warning",
      pcb_trace_too_long_warning_id: `pcb_trace_too_long_warning_${pcbTrace.pcb_trace_id}`,
      warning_type: "pcb_trace_too_long_warning",
      message: `PCB trace is ${actualTraceLength.toFixed(2)}mm long, exceeding the ${maximumTraceLength}mm maximum`,
      pcb_trace_id: pcbTrace.pcb_trace_id,
      source_trace_id: sourceTrace.source_trace_id,
      source_net_id: sourceTrace.connected_source_net_ids[0],
      actual_trace_length: actualTraceLength,
      maximum_trace_length: maximumTraceLength,
      subcircuit_id: pcbTrace.subcircuit_id ?? sourceTrace.subcircuit_id
    });
  }
  return warnings;
};

// lib/check-pcb-trace-via-counts.ts
var checkPcbTraceViaCounts = (circuitJson) => {
  const sourceTraces = circuitJson.filter(
    (element) => element.type === "source_trace"
  );
  const pcbTraces = circuitJson.filter(
    (element) => element.type === "pcb_trace"
  );
  const pcbPorts = circuitJson.filter(
    (element) => element.type === "pcb_port"
  );
  const errors = [];
  for (const sourceTrace of sourceTraces) {
    const maximumViaCount = sourceTrace.max_via_count;
    if (typeof maximumViaCount !== "number") continue;
    const routedPcbTraces = pcbTraces.filter(
      (pcbTrace) => pcbTrace.source_trace_id === sourceTrace.source_trace_id
    );
    if (routedPcbTraces.length === 0) continue;
    const actualViaCount = routedPcbTraces.reduce(
      (viaCount, pcbTrace) => viaCount + pcbTrace.route.filter((routePoint) => routePoint.route_type === "via").length,
      0
    );
    if (actualViaCount <= maximumViaCount) continue;
    const connectedPcbPorts = pcbPorts.filter(
      (pcbPort) => pcbPort.source_port_id !== void 0 && sourceTrace.connected_source_port_ids.includes(pcbPort.source_port_id)
    );
    errors.push({
      type: "pcb_trace_error",
      pcb_trace_error_id: `max_via_count_exceeded_${sourceTrace.source_trace_id}`,
      error_type: "pcb_trace_error",
      message: `PCB trace uses ${actualViaCount} vias, exceeding the ${maximumViaCount} maximum`,
      pcb_trace_id: routedPcbTraces[0].pcb_trace_id,
      source_trace_id: sourceTrace.source_trace_id,
      pcb_component_ids: [
        ...new Set(
          connectedPcbPorts.map((pcbPort) => pcbPort.pcb_component_id).filter(
            (pcbComponentId) => pcbComponentId !== void 0
          )
        )
      ],
      pcb_port_ids: connectedPcbPorts.map((pcbPort) => pcbPort.pcb_port_id),
      subcircuit_id: routedPcbTraces[0].subcircuit_id ?? sourceTrace.subcircuit_id
    });
  }
  return errors;
};

// lib/check-pad-pad-clearance.ts
import {
  getPrimaryId as getPrimaryId6,
  getReadableNameForElement as getReadableNameForElement6
} from "@tscircuit/circuit-json-util";
import { formatMm } from "format-si-unit";
import {
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson9
} from "circuit-json-to-connectivity-map";
function checkPadPadClearance(circuitJson, {
  connMap,
  minClearance
} = {}) {
  const pads = getPads(circuitJson);
  if (pads.length < 2) return [];
  const board = getPcbBoard(circuitJson);
  minClearance ??= getBoardDrcValue(board, "min_pad_edge_to_pad_edge_clearance") ?? jlcMinTolerances.min_pad_edge_to_pad_edge_clearance;
  connMap ??= getFullConnectivityMapFromCircuitJson9(circuitJson);
  const spatialIndex = new SpatialObjectIndex({
    objects: pads,
    getBounds: getPadBounds,
    getId: (pad) => getPrimaryId6(pad)
  });
  const errors = /* @__PURE__ */ new Map();
  for (const padA of pads) {
    const padAId = getPrimaryId6(padA);
    const nearbyPads = spatialIndex.getObjectsInBounds(
      getPadBounds(padA),
      minClearance
    );
    for (const padB of nearbyPads) {
      const padBId = getPrimaryId6(padB);
      if (padAId === padBId) continue;
      if (!getLayersOfPcbElement(padA).some(
        (layer) => getLayersOfPcbElement(padB).includes(layer)
      )) {
        continue;
      }
      if (connMap.areIdsConnected(padAId, padBId)) continue;
      const pairId = [padAId, padBId].sort().join("_");
      const gap = getPadToPadGap(padA, padB);
      if (gap + EPSILON >= minClearance) continue;
      const centerA = getPadCenter(padA);
      const centerB = getPadCenter(padB);
      const nextError = {
        type: "pcb_pad_pad_clearance_error",
        pcb_pad_pad_clearance_error_id: `pad_pad_clearance_${pairId}`,
        error_type: "pcb_pad_pad_clearance_error",
        message: `Pads ${getReadableNameForElement6(circuitJson, padAId)} and ${getReadableNameForElement6(circuitJson, padBId)} are too close (clearance: ${formatMm(gap)}, minimum: ${formatMm(minClearance)})`,
        pcb_pad_ids: [padAId, padBId],
        minimum_clearance: minClearance,
        actual_clearance: gap,
        center: {
          x: (centerA.x + centerB.x) / 2,
          y: (centerA.y + centerB.y) / 2
        }
      };
      if (!errors.has(pairId)) {
        errors.set(pairId, nextError);
      }
    }
  }
  return Array.from(errors.values());
}

// lib/check-pad-trace-clearance.ts
import {
  getPrimaryId as getPrimaryId7,
  getReadableNameForElement as getReadableNameForElement7
} from "@tscircuit/circuit-json-util";
import { formatMm as formatMm2 } from "format-si-unit";
import {
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson10
} from "circuit-json-to-connectivity-map";
function checkPadTraceClearance(circuitJson, {
  connMap,
  minClearance
} = {}) {
  const pads = getPads(circuitJson);
  const segments = getTraceSegments(circuitJson);
  if (pads.length === 0 || segments.length === 0) return [];
  const board = getPcbBoard(circuitJson);
  minClearance ??= getBoardDrcValue(board, "min_trace_to_pad_edge_clearance") ?? jlcMinTolerances.min_trace_to_pad_edge_clearance;
  connMap ??= getFullConnectivityMapFromCircuitJson10(circuitJson);
  const spatialIndex = new SpatialObjectIndex({
    objects: pads,
    getBounds: getPadBounds,
    getId: (pad) => getPrimaryId7(pad)
  });
  const errors = /* @__PURE__ */ new Map();
  const overlappingPairIds = /* @__PURE__ */ new Set();
  for (const segment of segments) {
    const nearbyPads = spatialIndex.getObjectsInBounds(
      getCollidableBounds(segment),
      minClearance + segment.thickness / 2
    );
    for (const pad of nearbyPads) {
      const padId = getPrimaryId7(pad);
      if (!getLayersOfPcbElement(pad).includes(segment.layer)) continue;
      if (connMap.areIdsConnected(segment.pcb_trace_id, padId)) continue;
      const pairId = `${padId}_${segment.pcb_trace_id}`;
      const { gap } = getTraceObstacleClearance(segment, pad);
      if (isTraceObstacleOverlap(gap)) {
        errors.delete(pairId);
        overlappingPairIds.add(pairId);
        continue;
      }
      if (overlappingPairIds.has(pairId)) continue;
      if (gap + EPSILON >= minClearance) continue;
      const nextError = {
        type: "pcb_pad_trace_clearance_error",
        pcb_pad_trace_clearance_error_id: `pad_trace_clearance_${pairId}`,
        error_type: "pcb_pad_trace_clearance_error",
        message: `Pad ${getReadableNameForElement7(circuitJson, padId)} and trace ${getReadableNameForElement7(circuitJson, segment.pcb_trace_id)} are too close (clearance: ${formatMm2(gap)}, minimum: ${formatMm2(minClearance)})`,
        pcb_pad_id: padId,
        pcb_trace_id: segment.pcb_trace_id,
        minimum_clearance: minClearance,
        actual_clearance: gap,
        center: getTraceCenter(segment)
      };
      const current = errors.get(pairId);
      if (!current || gap < current.gap) {
        errors.set(pairId, { error: nextError, gap });
      }
    }
  }
  return Array.from(errors.values()).map(({ error }) => error);
}

// lib/check-via-trace-clearance.ts
import { getReadableNameForElement as getReadableNameForElement8 } from "@tscircuit/circuit-json-util";
import { formatMm as formatMm3 } from "format-si-unit";
import {
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson11
} from "circuit-json-to-connectivity-map";
function checkViaTraceClearance(circuitJson, {
  connMap,
  minClearance
} = {}) {
  const vias = circuitJson.filter((el) => el.type === "pcb_via");
  const segments = getTraceSegments(circuitJson);
  if (vias.length === 0 || segments.length === 0) return [];
  const board = getPcbBoard(circuitJson);
  minClearance ??= getBoardDrcValue(board, "min_trace_to_pad_edge_clearance") ?? jlcMinTolerances.min_trace_to_pad_edge_clearance;
  connMap ??= getFullConnectivityMapFromCircuitJson11(circuitJson);
  const errors = /* @__PURE__ */ new Map();
  const overlappingPairIds = /* @__PURE__ */ new Set();
  for (const via of vias) {
    for (const segment of segments) {
      if (!getLayersOfPcbElement(via).includes(segment.layer)) continue;
      if (connMap.areIdsConnected(segment.pcb_trace_id, via.pcb_via_id))
        continue;
      const pairId = `${via.pcb_via_id}_${segment.pcb_trace_id}`;
      const { gap } = getTraceObstacleClearance(segment, via);
      if (isTraceObstacleOverlap(gap)) {
        errors.delete(pairId);
        overlappingPairIds.add(pairId);
        continue;
      }
      if (overlappingPairIds.has(pairId)) continue;
      if (gap + EPSILON >= minClearance) continue;
      const nextError = {
        type: "pcb_via_trace_clearance_error",
        pcb_via_trace_clearance_error_id: `via_trace_clearance_${pairId}`,
        error_type: "pcb_via_trace_clearance_error",
        message: `Via ${getReadableNameForElement8(circuitJson, via.pcb_via_id)} and trace ${getReadableNameForElement8(circuitJson, segment.pcb_trace_id)} are too close (clearance: ${formatMm3(gap)}, minimum: ${formatMm3(minClearance)})`,
        pcb_via_id: via.pcb_via_id,
        pcb_trace_id: segment.pcb_trace_id,
        minimum_clearance: minClearance,
        actual_clearance: gap,
        center: getTraceCenter(segment)
      };
      const current = errors.get(pairId);
      if (!current || gap < current.gap) {
        errors.set(pairId, { error: nextError, gap });
      }
    }
  }
  return Array.from(errors.values()).map(({ error }) => error);
}

// lib/check-via-pad-clearance.ts
import {
  getPrimaryId as getPrimaryId8,
  getReadableNameForElement as getReadableNameForElement9
} from "@tscircuit/circuit-json-util";
import { formatMm as formatMm4 } from "format-si-unit";
import {
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson12
} from "circuit-json-to-connectivity-map";
function checkViaPadClearance(circuitJson, {
  connMap,
  minClearance
} = {}) {
  const vias = circuitJson.filter(
    (element) => element.type === "pcb_via"
  );
  const pads = getPads(circuitJson);
  if (vias.length === 0 || pads.length === 0) return [];
  const board = getPcbBoard(circuitJson);
  const requiredClearance = minClearance ?? getBoardDrcValue(board, "min_pad_edge_to_pad_edge_clearance") ?? jlcMinTolerances.min_pad_edge_to_pad_edge_clearance;
  connMap ??= getFullConnectivityMapFromCircuitJson12(circuitJson);
  const padIndex = new SpatialObjectIndex({
    objects: pads,
    getBounds: getPadBounds,
    getId: getPrimaryId8
  });
  const errors = [];
  for (const via of vias) {
    const nearbyPads = padIndex.getObjectsInBounds(
      getPadBounds(via),
      requiredClearance
    );
    for (const pad of nearbyPads) {
      const padId = getPrimaryId8(pad);
      if (!getLayersOfPcbElement(via).some(
        (layer) => getLayersOfPcbElement(pad).includes(layer)
      )) {
        continue;
      }
      if (connMap.areIdsConnected(via.pcb_via_id, padId)) continue;
      const gap = getPadToPadGap(via, pad);
      if (gap + EPSILON >= requiredClearance) continue;
      const viaCenter = getPadCenter(via);
      const padCenter = getPadCenter(pad);
      errors.push({
        type: "pcb_pad_pad_clearance_error",
        pcb_pad_pad_clearance_error_id: `via_pad_clearance_${via.pcb_via_id}_${padId}`,
        error_type: "pcb_pad_pad_clearance_error",
        message: `Via ${getReadableNameForElement9(circuitJson, via.pcb_via_id)} and pad ${getReadableNameForElement9(circuitJson, padId)} are too close (clearance: ${formatMm4(gap)}, minimum: ${formatMm4(requiredClearance)})`,
        pcb_pad_ids: [via.pcb_via_id, padId],
        minimum_clearance: requiredClearance,
        actual_clearance: gap,
        center: {
          x: (viaCenter.x + padCenter.x) / 2,
          y: (viaCenter.y + padCenter.y) / 2
        }
      });
    }
  }
  return errors;
}

// lib/check-vias-in-pads.ts
import { getPrimaryId as getPrimaryId9 } from "@tscircuit/circuit-json-util";
import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson13 } from "circuit-json-to-connectivity-map";
function checkViasInPads(circuitJson) {
  const board = getPcbBoard(circuitJson);
  if (board && "is_via_in_pad_allowed" in board && board.is_via_in_pad_allowed === true) {
    return [];
  }
  const vias = circuitJson.filter(
    (element) => element.type === "pcb_via"
  );
  const pads = getPads(circuitJson);
  if (vias.length === 0 || pads.length === 0) return [];
  const connMap = getFullConnectivityMapFromCircuitJson13(circuitJson);
  const padOrdinals = new Map(
    pads.map((pad, index) => [getPrimaryId9(pad), index])
  );
  const padIndex = new SpatialObjectIndex({
    objects: pads,
    getBounds: getPadBounds,
    getId: getPrimaryId9
  });
  const errors = [];
  for (const via of vias) {
    const nearbyPads = padIndex.getObjectsInBounds(getPadBounds(via));
    for (const pad of nearbyPads) {
      const padId = getPrimaryId9(pad);
      const viaLayers = getLayersOfPcbElement(via);
      const padLayers = getLayersOfPcbElement(pad);
      if (!viaLayers.some((layer) => padLayers.includes(layer))) continue;
      if (connMap.areIdsConnected(via.pcb_via_id, padId)) continue;
      if (getPadToPadGap(via, pad) > 0) continue;
      const padOrdinal = padOrdinals.get(padId) ?? 0;
      const padName = getReadableNameForFootprintPad(
        circuitJson,
        pad,
        padOrdinal
      );
      errors.push({
        type: "pcb_placement_error",
        pcb_placement_error_id: `via_in_pad_${via.pcb_via_id}_${padId}`,
        error_type: "pcb_placement_error",
        message: `Via copper at (${via.x.toFixed(2)}mm, ${via.y.toFixed(2)}mm) overlaps ${padName}`,
        subcircuit_id: via.subcircuit_id ?? pad.subcircuit_id
      });
    }
  }
  return errors;
}

// lib/dedupe-pcb-drc-errors.ts
var dedupePcbDrcErrors = (errors) => {
  const specificallyReportedPairIds = /* @__PURE__ */ new Set();
  for (const error of errors) {
    if (error.type === "pcb_pad_trace_clearance_error" && typeof error.pcb_trace_id === "string" && typeof error.pcb_pad_id === "string") {
      specificallyReportedPairIds.add(
        `overlap_${error.pcb_trace_id}_${error.pcb_pad_id}`
      );
    }
    if (error.type === "pcb_via_trace_clearance_error" && typeof error.pcb_trace_id === "string" && typeof error.pcb_via_id === "string") {
      specificallyReportedPairIds.add(
        `overlap_${error.pcb_trace_id}_${error.pcb_via_id}`
      );
    }
  }
  return errors.filter((element) => {
    const error = element;
    return !(error.type === "pcb_trace_error" && typeof error.pcb_trace_error_id === "string" && specificallyReportedPairIds.has(error.pcb_trace_error_id));
  });
};

// lib/check-pin-must-be-connected.ts
function checkPinMustBeConnected(circuitJson) {
  const errors = [];
  const sourceComponents = circuitJson.filter(
    (el) => "source_component_id" in el && (el.type === "source_component" || el.type.startsWith("source_simple_"))
  );
  const sourcePorts = circuitJson.filter(
    (el) => el.type === "source_port"
  );
  const sourceTraces = circuitJson.filter(
    (el) => el.type === "source_trace"
  );
  const connectedPortIds = /* @__PURE__ */ new Set();
  for (const trace of sourceTraces) {
    for (const portId of trace.connected_source_port_ids ?? []) {
      connectedPortIds.add(portId);
    }
  }
  const componentInternalConnections = /* @__PURE__ */ new Map();
  for (const component of sourceComponents) {
    if ("internally_connected_source_port_ids" in component && component.internally_connected_source_port_ids) {
      componentInternalConnections.set(
        component.source_component_id,
        component.internally_connected_source_port_ids
      );
    }
  }
  for (const internalGroups of componentInternalConnections.values()) {
    for (const group of internalGroups) {
      if (group.some((portId) => connectedPortIds.has(portId))) {
        for (const portId of group) {
          connectedPortIds.add(portId);
        }
      }
    }
  }
  for (const port of sourcePorts) {
    if (port.must_be_connected === true) {
      if (!connectedPortIds.has(port.source_port_id)) {
        const component = sourceComponents.find(
          (c) => c.source_component_id === port.source_component_id
        );
        const componentName = component?.name ?? "Unknown";
        errors.push({
          type: "source_pin_must_be_connected_error",
          source_pin_must_be_connected_error_id: `source_pin_must_be_connected_error_${port.source_port_id}`,
          error_type: "source_pin_must_be_connected_error",
          message: `Port ${port.name} on ${componentName} must be connected but is floating`,
          source_component_id: port.source_component_id ?? "",
          source_port_id: port.source_port_id,
          subcircuit_id: port.subcircuit_id
        });
      }
    }
  }
  return errors;
}

// lib/check-two-terminal-switch-contacts-on-different-nets.ts
import {
  source_component_misconfigured_error
} from "circuit-json";
function checkTwoTerminalSwitchContactsOnDifferentNets(circuitJson) {
  const switchingComponents = circuitJson.filter(
    (element) => element.type === "source_component" && (element.ftype === "simple_push_button" || element.ftype === "simple_switch")
  );
  const sourcePorts = circuitJson.filter(
    (element) => element.type === "source_port"
  );
  const schematicPorts = circuitJson.filter(
    (element) => element.type === "schematic_port"
  );
  const errors = [];
  for (const switchingComponent of switchingComponents) {
    const schematicContactSourcePorts = schematicPorts.flatMap(
      (schematicPort) => {
        const sourcePort = sourcePorts.find(
          (sourcePort2) => sourcePort2.source_port_id === schematicPort.source_port_id && sourcePort2.source_component_id === switchingComponent.source_component_id
        );
        if (!sourcePort) return [];
        return [sourcePort];
      }
    );
    if (schematicContactSourcePorts.length !== 2) continue;
    const firstContactConnectivityKey = schematicContactSourcePorts[0].subcircuit_connectivity_map_key;
    const secondContactConnectivityKey = schematicContactSourcePorts[1].subcircuit_connectivity_map_key;
    if (!firstContactConnectivityKey) continue;
    if (firstContactConnectivityKey !== secondContactConnectivityKey) continue;
    errors.push(
      source_component_misconfigured_error.parse({
        type: "source_component_misconfigured_error",
        message: `Switch ${switchingComponent.name} has both schematic contacts connected to the same net. Check internallyConnectedPins and the footprint pin mapping.`,
        source_component_ids: [switchingComponent.source_component_id],
        source_port_ids: schematicContactSourcePorts.map(
          (sourcePort) => sourcePort.source_port_id
        ),
        is_fatal: true
      })
    );
  }
  return errors;
}

// lib/check-all-pins-in-component-are-underspecified.ts
import { cju as cju7 } from "@tscircuit/circuit-json-util";
var PIN_ATTRIBUTE_KEYS = [
  "must_be_connected",
  "provides_power",
  "requires_power",
  "provides_ground",
  "requires_ground",
  "provides_voltage",
  "requires_voltage",
  "do_not_connect",
  "include_in_board_pinout",
  "can_use_internal_pullup",
  "is_using_internal_pullup",
  "needs_external_pullup",
  "can_use_internal_pulldown",
  "is_using_internal_pulldown",
  "needs_external_pulldown",
  "can_use_open_drain",
  "is_using_open_drain",
  "can_use_push_pull",
  "is_using_push_pull",
  "should_have_decoupling_capacitor",
  "recommended_decoupling_capacitor_capacitance",
  "is_configured_for_i2c_sda",
  "is_configured_for_i2c_scl",
  "is_configured_for_spi_mosi",
  "is_configured_for_spi_miso",
  "is_configured_for_spi_sck",
  "is_configured_for_spi_cs",
  "is_configured_for_uart_tx",
  "is_configured_for_uart_rx",
  "supports_i2c_sda",
  "supports_i2c_scl",
  "supports_spi_mosi",
  "supports_spi_miso",
  "supports_spi_sck",
  "supports_spi_cs",
  "supports_uart_tx",
  "supports_uart_rx"
];
function hasAnyPinAttribute(port) {
  return PIN_ATTRIBUTE_KEYS.some((key) => port[key] !== void 0);
}
function checkAllPinsInComponentAreUnderspecified(circuitJson) {
  const warnings = [];
  const db = cju7(circuitJson);
  const sourceComponents = db.source_component.list();
  const sourcePorts = db.source_port.list();
  const portsByComponent = /* @__PURE__ */ new Map();
  for (const port of sourcePorts) {
    if (!port.source_component_id) continue;
    const existing = portsByComponent.get(port.source_component_id) ?? [];
    existing.push(port);
    portsByComponent.set(port.source_component_id, existing);
  }
  for (const component of sourceComponents) {
    if (component.ftype !== "simple_chip") continue;
    const componentPorts = portsByComponent.get(component.source_component_id) ?? [];
    if (componentPorts.length === 0) continue;
    const hasAnySpecifiedAttributes = componentPorts.some(
      (port) => hasAnyPinAttribute(port)
    );
    if (hasAnySpecifiedAttributes) continue;
    warnings.push({
      type: "source_component_pins_underspecified_warning",
      source_component_pins_underspecified_warning_id: `source_component_pins_underspecified_warning_${component.source_component_id}`,
      warning_type: "source_component_pins_underspecified_warning",
      message: `All pins on ${component.name} are underspecified (no pinAttributes set)`,
      source_component_id: component.source_component_id,
      source_port_ids: componentPorts.map((port) => port.source_port_id),
      subcircuit_id: componentPorts[0]?.subcircuit_id
    });
  }
  return warnings;
}

// lib/check-no-power-pin-defined.ts
import { cju as cju8 } from "@tscircuit/circuit-json-util";

// lib/util/should-check-chip-power-ground-pins.ts
var shouldCheckChipPowerGroundPins = (component, ports) => component.ftype === "simple_chip" && ports.filter((port) => port.do_not_connect !== true).length >= 2;

// lib/check-no-power-pin-defined.ts
function checkNoPowerPinDefined(circuitJson) {
  const warnings = [];
  const db = cju8(circuitJson);
  const sourceComponents = db.source_component.list();
  const sourcePorts = db.source_port.list();
  const portsByComponent = /* @__PURE__ */ new Map();
  for (const port of sourcePorts) {
    if (!port.source_component_id) continue;
    const existing = portsByComponent.get(port.source_component_id) ?? [];
    existing.push(port);
    portsByComponent.set(port.source_component_id, existing);
  }
  for (const component of sourceComponents) {
    const componentPorts = portsByComponent.get(component.source_component_id) ?? [];
    if (!shouldCheckChipPowerGroundPins(component, componentPorts)) continue;
    const hasRequiredPowerPin = componentPorts.some(
      (port) => port.requires_power === true
    );
    if (hasRequiredPowerPin) continue;
    warnings.push({
      type: "source_no_power_pin_defined_warning",
      source_no_power_pin_defined_warning_id: `source_no_power_pin_defined_warning_${component.source_component_id}`,
      warning_type: "source_no_power_pin_defined_warning",
      message: `${component.name} has no pin with requires_power=true`,
      source_component_id: component.source_component_id,
      source_port_ids: componentPorts.map((port) => port.source_port_id),
      subcircuit_id: componentPorts[0]?.subcircuit_id
    });
  }
  return warnings;
}

// lib/check-no-ground-pin-defined.ts
import { cju as cju9 } from "@tscircuit/circuit-json-util";
function checkNoGroundPinDefined(circuitJson) {
  const warnings = [];
  const db = cju9(circuitJson);
  const sourceComponents = db.source_component.list();
  const sourcePorts = db.source_port.list();
  const portsByComponent = /* @__PURE__ */ new Map();
  for (const port of sourcePorts) {
    if (!port.source_component_id) continue;
    const existing = portsByComponent.get(port.source_component_id) ?? [];
    existing.push(port);
    portsByComponent.set(port.source_component_id, existing);
  }
  for (const component of sourceComponents) {
    const componentPorts = portsByComponent.get(component.source_component_id) ?? [];
    if (!shouldCheckChipPowerGroundPins(component, componentPorts)) continue;
    const hasRequiredGroundPin = componentPorts.some(
      (port) => port.requires_ground === true
    );
    if (hasRequiredGroundPin) continue;
    warnings.push({
      type: "source_no_ground_pin_defined_warning",
      source_no_ground_pin_defined_warning_id: `source_no_ground_pin_defined_warning_${component.source_component_id}`,
      warning_type: "source_no_ground_pin_defined_warning",
      message: `${component.name} has no pin with requires_ground=true`,
      source_component_id: component.source_component_id,
      source_port_ids: componentPorts.map((port) => port.source_port_id),
      subcircuit_id: componentPorts[0]?.subcircuit_id
    });
  }
  return warnings;
}

// lib/check-schematic-component-excessive-vertical-padding.ts
var DEFAULT_PIN_SPACING = 0.2;
var MAX_VERTICAL_PADDING_IN_PIN_SPACINGS = 3;
var FLOATING_POINT_TOLERANCE = 1e-9;
function checkSchematicComponentExcessiveVerticalPadding(circuitJson) {
  const schematicComponents = circuitJson.filter(
    (element) => element.type === "schematic_component"
  );
  const schematicPorts = circuitJson.filter(
    (element) => element.type === "schematic_port"
  );
  const sourceComponents = circuitJson.filter(
    (element) => element.type === "source_component"
  );
  const sourceComponentById = new Map(
    sourceComponents.map((component) => [
      component.source_component_id,
      component
    ])
  );
  const portsByComponentId = /* @__PURE__ */ new Map();
  for (const port of schematicPorts) {
    if (!port.schematic_component_id) continue;
    const componentPorts = portsByComponentId.get(port.schematic_component_id) ?? [];
    componentPorts.push(port);
    portsByComponentId.set(port.schematic_component_id, componentPorts);
  }
  const warnings = [];
  for (const component of schematicComponents) {
    if (!component.is_box_with_pins || component.size.height <= 0) continue;
    const sidePorts = (portsByComponentId.get(component.schematic_component_id) ?? []).filter(
      (port) => port.side_of_component === "left" || port.side_of_component === "right"
    );
    if (sidePorts.length < 2) continue;
    const pinYs = sidePorts.map((port) => port.center.y);
    const highestPinY = Math.max(...pinYs);
    const lowestPinY = Math.min(...pinYs);
    const componentTopY = component.center.y + component.size.height / 2;
    const componentBottomY = component.center.y - component.size.height / 2;
    const pinSpacing = component.pin_spacing ?? DEFAULT_PIN_SPACING;
    const maximumPadding = pinSpacing * MAX_VERTICAL_PADDING_IN_PIN_SPACINGS;
    const paddingBySide = {
      top: componentTopY - highestPinY,
      bottom: lowestPinY - componentBottomY
    };
    const sourceComponent = component.source_component_id ? sourceComponentById.get(component.source_component_id) : void 0;
    const componentName = sourceComponent?.name ?? component.schematic_component_id;
    for (const side of ["top", "bottom"]) {
      const padding = paddingBySide[side];
      if (padding <= maximumPadding + FLOATING_POINT_TOLERANCE || padding <= 0) {
        continue;
      }
      const relativePosition = side === "top" ? "above" : "below";
      const stylingIssueType = `excessive_${side}_padding`;
      warnings.push({
        type: "schematic_component_styling_warning",
        schematic_component_styling_warning_id: `schematic_component_styling_warning_${component.schematic_component_id}_${stylingIssueType}`,
        warning_type: "schematic_component_styling_warning",
        message: `${componentName} has excessive empty space ${relativePosition} its pins (${padding.toFixed(2)}mm, more than ${MAX_VERTICAL_PADDING_IN_PIN_SPACINGS} pin spacings)`,
        schematic_component_id: component.schematic_component_id,
        styling_issue_type: stylingIssueType,
        schematic_port_ids: sidePorts.map((port) => port.schematic_port_id),
        source_component_id: component.source_component_id,
        schematic_sheet_id: component.schematic_sheet_id,
        subcircuit_id: component.subcircuit_id
      });
    }
  }
  return warnings;
}

// lib/check-schematic-component-missing-reference-designator-text.ts
var isFallbackReferenceDesignator = (name) => /^unnamed_[a-z0-9_-]+\d+$/i.test(name);
var isTextWithinComponentBounds = (schematicText, schematicComponent) => {
  const { center, size } = schematicComponent;
  const tolerance = 1e-9;
  return schematicText.position.x >= center.x - size.width / 2 - tolerance && schematicText.position.x <= center.x + size.width / 2 + tolerance && schematicText.position.y >= center.y - size.height / 2 - tolerance && schematicText.position.y <= center.y + size.height / 2 + tolerance;
};
function checkSchematicComponentMissingReferenceDesignatorText(circuitJson) {
  const schematicComponents = circuitJson.filter(
    (element) => element.type === "schematic_component"
  );
  const sourceComponents = circuitJson.filter(
    (element) => element.type === "source_component"
  );
  const schematicTexts = circuitJson.filter(
    (element) => element.type === "schematic_text"
  );
  const sourceComponentById = new Map(
    sourceComponents.map((component) => [
      component.source_component_id,
      component
    ])
  );
  const textBySchematicComponentId = /* @__PURE__ */ new Map();
  const customSymbolTexts = [];
  for (const schematicText of schematicTexts) {
    if (!schematicText.schematic_component_id) {
      if (schematicText.schematic_symbol_id) {
        customSymbolTexts.push(schematicText);
      }
      continue;
    }
    const componentTexts = textBySchematicComponentId.get(schematicText.schematic_component_id) ?? /* @__PURE__ */ new Set();
    componentTexts.add(schematicText.text.trim());
    textBySchematicComponentId.set(
      schematicText.schematic_component_id,
      componentTexts
    );
  }
  const warnings = [];
  for (const schematicComponent of schematicComponents) {
    if (!schematicComponent.source_component_id) continue;
    const sourceComponent = sourceComponentById.get(
      schematicComponent.source_component_id
    );
    if (!sourceComponent) continue;
    const referenceDesignators = new Set(
      [sourceComponent.name, sourceComponent.display_name].map((name) => name?.trim()).filter((name) => Boolean(name))
    );
    const nonFallbackReferenceDesignator = [...referenceDesignators].find(
      (referenceDesignator) => !isFallbackReferenceDesignator(referenceDesignator)
    );
    const componentTexts = textBySchematicComponentId.get(
      schematicComponent.schematic_component_id
    );
    const hasReferenceDesignatorText = [...referenceDesignators].some(
      (referenceDesignator) => componentTexts?.has(referenceDesignator)
    ) || customSymbolTexts.some(
      (schematicText) => referenceDesignators.has(schematicText.text.trim()) && isTextWithinComponentBounds(schematicText, schematicComponent)
    );
    if (nonFallbackReferenceDesignator && hasReferenceDesignatorText) {
      continue;
    }
    const readableComponentName = nonFallbackReferenceDesignator ?? "Schematic component";
    warnings.push({
      type: "schematic_component_styling_warning",
      schematic_component_styling_warning_id: `schematic_component_styling_warning_${schematicComponent.schematic_component_id}_missing_reference_designator_text`,
      warning_type: "schematic_component_styling_warning",
      message: `${readableComponentName} is missing schematic reference designator text. For a custom symbol, add name="{REFDES}" inside the symbol.`,
      schematic_component_id: schematicComponent.schematic_component_id,
      styling_issue_type: "missing_reference_designator_text",
      source_component_id: schematicComponent.source_component_id,
      schematic_sheet_id: schematicComponent.schematic_sheet_id,
      subcircuit_id: schematicComponent.subcircuit_id
    });
  }
  return warnings;
}

// lib/check-schematic-component-ports-outside-body.ts
var FLOATING_POINT_TOLERANCE2 = 1e-9;
var getPortLabel = (port) => port.display_pin_label ?? `pin ${port.pin_number}`;
function checkSchematicComponentPortsOutsideBody(circuitJson) {
  const schematicComponents = circuitJson.filter(
    (element) => element.type === "schematic_component"
  );
  const schematicPorts = circuitJson.filter(
    (element) => element.type === "schematic_port"
  );
  const sourceComponents = circuitJson.filter(
    (element) => element.type === "source_component"
  );
  const sourceComponentById = new Map(
    sourceComponents.map((component) => [
      component.source_component_id,
      component
    ])
  );
  const portsByComponentId = /* @__PURE__ */ new Map();
  for (const port of schematicPorts) {
    if (!port.schematic_component_id) continue;
    const componentPorts = portsByComponentId.get(port.schematic_component_id) ?? [];
    componentPorts.push(port);
    portsByComponentId.set(port.schematic_component_id, componentPorts);
  }
  const warnings = [];
  for (const component of schematicComponents) {
    if (component.is_box_with_pins === false || component.size.width <= 0 || component.size.height <= 0) {
      continue;
    }
    const componentLeftX = component.center.x - component.size.width / 2;
    const componentRightX = component.center.x + component.size.width / 2;
    const componentBottomY = component.center.y - component.size.height / 2;
    const componentTopY = component.center.y + component.size.height / 2;
    const componentPorts = portsByComponentId.get(component.schematic_component_id) ?? [];
    const portsOutsideBody = componentPorts.filter((port) => {
      if (port.side_of_component === "left" || port.side_of_component === "right") {
        return port.center.y < componentBottomY - FLOATING_POINT_TOLERANCE2 || port.center.y > componentTopY + FLOATING_POINT_TOLERANCE2;
      }
      if (port.side_of_component === "top" || port.side_of_component === "bottom") {
        return port.center.x < componentLeftX - FLOATING_POINT_TOLERANCE2 || port.center.x > componentRightX + FLOATING_POINT_TOLERANCE2;
      }
      return false;
    }).sort((portA, portB) => {
      const portAIsVertical = portA.side_of_component === "left" || portA.side_of_component === "right";
      const portBIsVertical = portB.side_of_component === "left" || portB.side_of_component === "right";
      if (portAIsVertical && portBIsVertical) {
        return portB.center.y - portA.center.y;
      }
      if (!portAIsVertical && !portBIsVertical) {
        return portA.center.x - portB.center.x;
      }
      return portAIsVertical ? -1 : 1;
    });
    if (portsOutsideBody.length === 0) continue;
    const requiredHeight = Math.max(
      component.size.height,
      ...portsOutsideBody.filter(
        (port) => port.side_of_component === "left" || port.side_of_component === "right"
      ).map((port) => 2 * Math.abs(port.center.y - component.center.y))
    );
    const requiredWidth = Math.max(
      component.size.width,
      ...portsOutsideBody.filter(
        (port) => port.side_of_component === "top" || port.side_of_component === "bottom"
      ).map((port) => 2 * Math.abs(port.center.x - component.center.x))
    );
    const suggestedDimensionChanges = [];
    if (requiredHeight > component.size.height + FLOATING_POINT_TOLERANCE2) {
      suggestedDimensionChanges.push(
        `increase schHeight to at least ${requiredHeight.toFixed(2)}mm`
      );
    }
    if (requiredWidth > component.size.width + FLOATING_POINT_TOLERANCE2) {
      suggestedDimensionChanges.push(
        `increase schWidth to at least ${requiredWidth.toFixed(2)}mm`
      );
    }
    const sourceComponent = component.source_component_id ? sourceComponentById.get(component.source_component_id) : void 0;
    const componentName = sourceComponent?.name ?? component.schematic_component_id;
    const portLabels = portsOutsideBody.map(getPortLabel).join(", ");
    warnings.push({
      type: "schematic_component_styling_warning",
      schematic_component_styling_warning_id: `schematic_component_styling_warning_${component.schematic_component_id}_ports_outside_body`,
      warning_type: "schematic_component_styling_warning",
      message: `${componentName} has schematic pins outside its body (${portLabels}); ${suggestedDimensionChanges.join(" and ")}`,
      schematic_component_id: component.schematic_component_id,
      styling_issue_type: "ports_outside_body",
      schematic_port_ids: portsOutsideBody.map(
        (port) => port.schematic_port_id
      ),
      source_component_id: component.source_component_id,
      schematic_sheet_id: component.schematic_sheet_id,
      subcircuit_id: component.subcircuit_id
    });
  }
  return warnings;
}

// lib/check-same-name-nets-are-connected.ts
function checkSameNameNetsAreConnected(circuitJson) {
  const parents = /* @__PURE__ */ new Map();
  const find = (id) => {
    let root = id;
    while (parents.has(root) && parents.get(root) !== root) {
      root = parents.get(root);
    }
    while (parents.has(id) && parents.get(id) !== root) {
      const next = parents.get(id);
      parents.set(id, root);
      id = next;
    }
    return root;
  };
  const connect = (ids) => {
    if (ids.length < 2) return;
    const root = find(ids[0]);
    for (const id of ids.slice(1)) parents.set(find(id), root);
  };
  const netsByName = /* @__PURE__ */ new Map();
  for (const element of circuitJson) {
    if (element.type === "source_net" && element.name.trim()) {
      const nets = netsByName.get(element.name) ?? [];
      nets.push(element);
      netsByName.set(element.name, nets);
    }
    if (element.type === "source_trace") {
      connect([
        ...element.connected_source_net_ids.map((id) => `net:${id}`),
        ...element.connected_source_port_ids.map((id) => `port:${id}`)
      ]);
    }
    if (element.type === "source_component_internal_connection") {
      connect(element.source_port_ids.map((id) => `port:${id}`));
    }
    if (element.type === "source_component") {
      for (const ids of element.internally_connected_source_port_ids ?? []) {
        connect(ids.map((id) => `port:${id}`));
      }
    }
  }
  const warnings = [];
  for (const [name, nets] of netsByName) {
    const islands = new Set(nets.map((net) => find(`net:${net.source_net_id}`)));
    if (islands.size < 2) continue;
    const ids = nets.map((net) => net.source_net_id).sort();
    warnings.push({
      type: "source_confusing_net_name_warning",
      source_confusing_net_name_warning_id: `source_confusing_net_name_warning_${ids[0]}`,
      warning_type: "source_confusing_net_name_warning",
      message: `Nets named "${name}" are not all connected (${islands.size} separate electrical networks). Connect them or use distinct names to avoid confusion.`,
      source_net_ids: ids,
      net_name: name,
      ...nets.every((net) => net.subcircuit_id === nets[0].subcircuit_id) ? { subcircuit_id: nets[0].subcircuit_id } : {}
    });
  }
  return warnings;
}

// lib/check-connector-accessible-orientation.ts
import { getBoardBounds } from "@tscircuit/circuit-json-util";
function getFacingDirectionFromInsertionDirection(component) {
  switch (component.insertion_direction) {
    case "from_left":
      return "x-";
    case "from_right":
      return "x+";
    case "from_top":
      return "y+";
    case "from_bottom":
      return "y-";
    case "from_above":
    case "from_below":
      return null;
    default:
      return null;
  }
}
function getFacingDirection(component) {
  if (component.insertion_direction) {
    return getFacingDirectionFromInsertionDirection(component);
  }
  if (!component.center || !component.cable_insertion_center) return null;
  const dx = component.cable_insertion_center.x - component.center.x;
  const dy = component.cable_insertion_center.y - component.center.y;
  if (Math.abs(dx) < 1e-6 && Math.abs(dy) < 1e-6) return null;
  if (Math.abs(dx) >= Math.abs(dy)) {
    return dx >= 0 ? "x+" : "x-";
  }
  return dy >= 0 ? "y+" : "y-";
}
function getRecommendedFacingDirection(component, bounds2) {
  if (!component.center) return null;
  const distances = [
    { direction: "x-", distance: component.center.x - bounds2.minX },
    { direction: "x+", distance: bounds2.maxX - component.center.x },
    { direction: "y-", distance: component.center.y - bounds2.minY },
    { direction: "y+", distance: bounds2.maxY - component.center.y }
  ];
  distances.sort((a, b) => a.distance - b.distance);
  return distances[0]?.direction ?? null;
}
function checkConnectorAccessibleOrientation(circuitJson) {
  const board = circuitJson.find(
    (el) => el.type === "pcb_board"
  );
  if (!board) return [];
  const bounds2 = (() => {
    try {
      return getBoardBounds(board);
    } catch {
      return null;
    }
  })();
  if (!bounds2) return [];
  const warnings = [];
  const components = circuitJson.filter(
    (el) => el.type === "pcb_component"
  );
  for (const component of components) {
    const facingDirection = getFacingDirection(component);
    const recommendedFacingDirection = getRecommendedFacingDirection(
      component,
      bounds2
    );
    if (!facingDirection || !recommendedFacingDirection) continue;
    if (facingDirection === recommendedFacingDirection) continue;
    const componentName = getReadableNameForComponent(
      circuitJson,
      component.pcb_component_id
    );
    warnings.push({
      type: "pcb_connector_not_in_accessible_orientation_warning",
      warning_type: "pcb_connector_not_in_accessible_orientation_warning",
      pcb_connector_not_in_accessible_orientation_warning_id: `pcb_connector_not_in_accessible_orientation_warning_${component.pcb_component_id}`,
      message: `${componentName} is facing ${facingDirection} but should face ${recommendedFacingDirection} so the connector is accessible from the board edge`,
      pcb_component_id: component.pcb_component_id,
      source_component_id: component.source_component_id,
      pcb_board_id: board.pcb_board_id,
      facing_direction: facingDirection,
      recommended_facing_direction: recommendedFacingDirection,
      subcircuit_id: component.subcircuit_id
    });
  }
  return warnings;
}

// lib/check-courtyard-overlap/checkCourtyardOverlap.ts
import {
  doSegmentsIntersect,
  isPointInsidePolygon as isPointInsidePolygon2
} from "@tscircuit/math-utils";
function getCourtyardPolygon(el) {
  if (el.type === "pcb_courtyard_rect") {
    const hw = el.width / 2;
    const hh = el.height / 2;
    const corners = [
      { x: -hw, y: -hh },
      { x: +hw, y: -hh },
      { x: +hw, y: +hh },
      { x: -hw, y: +hh }
    ];
    const angle = (el.ccw_rotation ?? 0) * Math.PI / 180;
    const cos = Math.cos(angle);
    const sin = Math.sin(angle);
    return corners.map(({ x, y }) => ({
      x: el.center.x + x * cos - y * sin,
      y: el.center.y + x * sin + y * cos
    }));
  }
  if (el.type === "pcb_courtyard_circle") {
    const N = 32;
    return Array.from({ length: N }, (_, i) => {
      const a = 2 * Math.PI * i / N;
      return {
        x: el.center.x + el.radius * Math.cos(a),
        y: el.center.y + el.radius * Math.sin(a)
      };
    });
  }
  return el.outline;
}
function getComponentName2(circuitJson, pcbComponentId) {
  const pcbComponent = circuitJson.find(
    (el) => el.type === "pcb_component" && el.pcb_component_id === pcbComponentId
  );
  if (pcbComponent?.type !== "pcb_component") return pcbComponentId;
  const sourceComponent = circuitJson.find(
    (el) => el.type === "source_component" && el.source_component_id === pcbComponent.source_component_id
  );
  if (sourceComponent?.type === "source_component" && sourceComponent.name) {
    return sourceComponent.name;
  }
  return pcbComponentId;
}
function polygonsOverlap(polyA, polyB) {
  if (polyA.some((p) => isPointInsidePolygon2(p, polyB))) return true;
  if (polyB.some((p) => isPointInsidePolygon2(p, polyA))) return true;
  for (let i = 0; i < polyA.length; i++) {
    const a1 = polyA[i];
    const a2 = polyA[(i + 1) % polyA.length];
    for (let j = 0; j < polyB.length; j++) {
      const b1 = polyB[j];
      const b2 = polyB[(j + 1) % polyB.length];
      if (doSegmentsIntersect(a1, a2, b1, b2)) return true;
    }
  }
  return false;
}
function checkCourtyardOverlap(circuitJson) {
  const courtyards = circuitJson.filter(
    (el) => el.type === "pcb_courtyard_rect" || el.type === "pcb_courtyard_circle" || el.type === "pcb_courtyard_outline"
  );
  const byComponent = /* @__PURE__ */ new Map();
  for (const el of courtyards) {
    const id = el.pcb_component_id;
    if (!byComponent.has(id)) byComponent.set(id, []);
    byComponent.get(id).push(el);
  }
  const componentIds = Array.from(byComponent.keys());
  const errors = [];
  for (let i = 0; i < componentIds.length; i++) {
    for (let j = i + 1; j < componentIds.length; j++) {
      const idA = componentIds[i];
      const idB = componentIds[j];
      let overlapping = false;
      outer: for (const a of byComponent.get(idA)) {
        for (const b of byComponent.get(idB)) {
          if ("layer" in a && "layer" in b && a.layer !== b.layer) {
            continue;
          }
          const polyA = getCourtyardPolygon(a);
          const polyB = getCourtyardPolygon(b);
          if (polygonsOverlap(polyA, polyB)) {
            overlapping = true;
            break outer;
          }
        }
      }
      if (overlapping) {
        errors.push({
          type: "pcb_courtyard_overlap_error",
          pcb_error_id: `pcb_courtyard_overlap_${idA}_${idB}`,
          error_type: "pcb_courtyard_overlap_error",
          message: `Courtyard of ${getComponentName2(circuitJson, idA)} overlaps with courtyard of ${getComponentName2(circuitJson, idB)}`,
          pcb_component_ids: [idA, idB]
        });
      }
    }
  }
  return errors;
}

// lib/check-testpoint-accessibility.ts
import { isPointInsidePolygon as isPointInsidePolygon3 } from "@tscircuit/math-utils";
var isCourtyardElement2 = (element) => element.type === "pcb_courtyard_circle" || element.type === "pcb_courtyard_outline" || element.type === "pcb_courtyard_polygon" || element.type === "pcb_courtyard_rect";
var isPointInsideCourtyard = (point, courtyard) => {
  if (courtyard.type === "pcb_courtyard_circle") {
    const dx = point.x - courtyard.center.x;
    const dy = point.y - courtyard.center.y;
    return dx * dx + dy * dy <= courtyard.radius * courtyard.radius;
  }
  if (courtyard.type === "pcb_courtyard_rect") {
    const angle = -1 * (courtyard.ccw_rotation ?? 0) * Math.PI / 180;
    const dx = point.x - courtyard.center.x;
    const dy = point.y - courtyard.center.y;
    const localX = dx * Math.cos(angle) - dy * Math.sin(angle);
    const localY = dx * Math.sin(angle) + dy * Math.cos(angle);
    return Math.abs(localX) <= courtyard.width / 2 && Math.abs(localY) <= courtyard.height / 2;
  }
  const polygon = courtyard.type === "pcb_courtyard_polygon" ? courtyard.points : courtyard.outline;
  return isPointInsidePolygon3(point, polygon);
};
var getPcbComponentName = (circuitJson, pcbComponentId) => {
  const pcbComponent = circuitJson.find(
    (element) => element.type === "pcb_component" && element.pcb_component_id === pcbComponentId
  );
  const sourceComponent = circuitJson.find(
    (element) => element.type === "source_component" && element.source_component_id === pcbComponent?.source_component_id
  );
  return (sourceComponent && "name" in sourceComponent ? sourceComponent.name : void 0) ?? getReadableNameForComponent(circuitJson, pcbComponentId);
};
function checkTestPointAccessibility(circuitJson) {
  const sourceTestPoints = circuitJson.filter(
    (element) => element.type === "source_component" && element.ftype === "simple_test_point"
  );
  const sourceTestPointIds = new Set(
    sourceTestPoints.map((testPoint) => testPoint.source_component_id)
  );
  const testPointNames = new Map(
    sourceTestPoints.map((testPoint) => [
      testPoint.source_component_id,
      testPoint.name
    ])
  );
  const testPointComponents = circuitJson.filter(
    (element) => element.type === "pcb_component" && sourceTestPointIds.has(element.source_component_id)
  );
  const courtyards = circuitJson.filter(isCourtyardElement2);
  const errors = [];
  const reportedComponentPairs = /* @__PURE__ */ new Set();
  for (const testPoint of testPointComponents) {
    for (const courtyard of courtyards) {
      if (courtyard.pcb_component_id === testPoint.pcb_component_id) continue;
      if (courtyard.layer !== testPoint.layer) continue;
      if (!isPointInsideCourtyard(testPoint.center, courtyard)) continue;
      const componentPair = `${testPoint.pcb_component_id}:${courtyard.pcb_component_id}`;
      if (reportedComponentPairs.has(componentPair)) continue;
      reportedComponentPairs.add(componentPair);
      const testPointName = testPointNames.get(testPoint.source_component_id) ?? "Test point";
      const obstructingComponentName = getPcbComponentName(
        circuitJson,
        courtyard.pcb_component_id
      );
      errors.push({
        type: "pcb_placement_error",
        pcb_placement_error_id: `testpoint_in_courtyard_${testPoint.pcb_component_id}_${courtyard.pcb_component_id}`,
        error_type: "pcb_placement_error",
        message: `Test point ${testPointName} is not accessible because it is inside the courtyard of ${obstructingComponentName}`,
        subcircuit_id: testPoint.subcircuit_id
      });
    }
  }
  return errors;
}

// lib/run-all-checks.ts
async function runAllPlacementChecks(circuitJson) {
  return [
    ...checkCopperToBoardEdgeClearance(circuitJson),
    ...checkViasInPads(circuitJson),
    ...checkPcbComponentsOutOfBoard(circuitJson),
    ...checkPcbComponentOverCutout(circuitJson),
    ...checkPcbCopperOverKeepout(circuitJson),
    ...checkPcbComponentOverlap(circuitJson),
    ...checkPcbComponentsMissingCourtyard(circuitJson),
    ...checkPadPadClearance(circuitJson),
    ...checkCourtyardOverlap(circuitJson),
    ...checkConnectorAccessibleOrientation(circuitJson),
    ...checkTestPointAccessibility(circuitJson)
  ];
}
async function runAllNetlistChecks(circuitJson) {
  return [
    ...checkPinMustBeConnected(circuitJson),
    ...checkSameNameNetsAreConnected(circuitJson),
    ...checkTwoTerminalSwitchContactsOnDifferentNets(circuitJson)
  ];
}
async function runAllSchematicChecks(circuitJson) {
  return [
    ...checkSchematicComponentExcessiveVerticalPadding(circuitJson),
    ...checkSchematicComponentMissingReferenceDesignatorText(circuitJson),
    ...checkSchematicComponentPortsOutsideBody(circuitJson)
  ];
}
async function runAllPinSpecificationChecks(circuitJson) {
  return [
    ...checkAllPinsInComponentAreUnderspecified(circuitJson),
    ...checkNoPowerPinDefined(circuitJson),
    ...checkNoGroundPinDefined(circuitJson)
  ];
}
async function runAllRoutingChecks(circuitJson) {
  return [
    ...checkEachPcbPortConnectedToPcbTraces(circuitJson),
    ...checkSourceTracesHavePcbTraces(circuitJson),
    ...checkPcbTraceLengths(circuitJson),
    ...checkPcbTraceViaCounts(circuitJson),
    ...checkEachPcbTraceNonOverlapping(circuitJson),
    ...checkPadTraceClearance(circuitJson),
    ...checkViaTraceClearance(circuitJson),
    ...checkViaPadClearance(circuitJson),
    ...checkSameNetViaSpacing(circuitJson),
    ...checkDifferentNetViaSpacing(circuitJson),
    ...checkTracesAreContiguous(circuitJson),
    ...checkPcbTracesOutOfBoard(circuitJson)
  ];
}
async function runAllChecks(circuitJson) {
  return [
    ...await runAllPlacementChecks(circuitJson),
    ...await runAllSchematicChecks(circuitJson),
    ...await runAllNetlistChecks(circuitJson),
    ...await runAllPinSpecificationChecks(circuitJson),
    ...await runAllRoutingChecks(circuitJson)
  ];
}
export {
  NetManager,
  checkAllPinsInComponentAreUnderspecified,
  checkConnectorAccessibleOrientation,
  checkCopperToBoardEdgeClearance,
  checkDifferentNetViaSpacing,
  checkEachPcbPortConnectedToPcbTraces,
  checkEachPcbTraceNonOverlapping,
  checkNoGroundPinDefined,
  checkNoPowerPinDefined,
  checkPadPadClearance,
  checkPadTraceClearance,
  checkPcbComponentOverCutout,
  checkPcbComponentOverlap,
  checkPcbComponentsMissingCourtyard,
  checkPcbComponentsOutOfBoard,
  checkPcbCopperOverKeepout,
  checkPcbTraceLengths,
  checkPcbTraceViaCounts,
  checkPcbTracesOutOfBoard,
  checkPinMustBeConnected,
  checkSameNameNetsAreConnected,
  checkSameNetViaSpacing,
  checkSchematicComponentExcessiveVerticalPadding,
  checkSchematicComponentMissingReferenceDesignatorText,
  checkSchematicComponentPortsOutsideBody,
  checkSourceTracesHavePcbTraces,
  checkSourceTracesMatchPcbTraceThickness,
  checkTestPointAccessibility,
  checkTracesAreContiguous,
  checkTwoTerminalSwitchContactsOnDifferentNets,
  checkViaPadClearance,
  checkViaTraceClearance,
  checkViasInPads,
  checkViasOffBoard,
  dedupePcbDrcErrors,
  runAllChecks,
  runAllNetlistChecks,
  runAllPinSpecificationChecks,
  runAllPlacementChecks,
  runAllRoutingChecks,
  runAllSchematicChecks
};
//# sourceMappingURL=index.js.map