// lib/index.ts
import { LightBurnProject, CutSetting } from "lbrnts";
import { cju as cju2 } from "@tscircuit/circuit-json-util";

// lib/element-handlers/addPlatedHole/addCirclePlatedHole.ts
import { ShapePath } from "lbrnts";

// lib/helpers/circleShape.ts
var createCirclePath = ({
  centerX,
  centerY,
  radius,
  segments = 64
}) => {
  const verts = [];
  const prims = [];
  for (let i = 0; i < segments; i++) {
    const angle = i / segments * Math.PI * 2;
    const x = centerX + radius * Math.cos(angle);
    const y = centerY + radius * Math.sin(angle);
    verts.push({ x, y });
    prims.push({ type: 0 });
  }
  if (verts.length > 0) {
    const firstVert = verts[0];
    if (firstVert) {
      verts.push({ ...firstVert });
      prims.push({ type: 0 });
    }
  }
  return { verts, prims };
};

// lib/element-handlers/addPlatedHole/addCirclePlatedHole.ts
import { Circle, point } from "@flatten-js/core";

// lib/element-handlers/addPcbTrace/circle-to-polygon.ts
import Flatten from "@flatten-js/core";
var circleToPolygon = (circle, segments = 32) => {
  const points = [];
  for (let i = 0; i < segments; i++) {
    const angle = i * 2 * Math.PI / segments;
    const x = circle.center.x + circle.r * Math.cos(angle);
    const y = circle.center.y + circle.r * Math.sin(angle);
    points.push(Flatten.point(x, y));
  }
  return new Flatten.Polygon(points);
};

// lib/element-handlers/addPlatedHole/addCirclePlatedHole.ts
var addCirclePlatedHole = (platedHole, ctx) => {
  const {
    project,
    topCopperCutSetting,
    bottomCopperCutSetting,
    soldermaskCutSetting,
    throughBoardCutSetting,
    topCutNetGeoms,
    bottomCutNetGeoms,
    origin,
    includeCopper,
    includeSoldermask,
    connMap,
    globalCopperSoldermaskMarginAdjustment,
    solderMaskMarginPercent,
    includeLayers
  } = ctx;
  const centerX = platedHole.x + origin.x;
  const centerY = platedHole.y + origin.y;
  if (platedHole.outer_diameter > 0 && includeCopper) {
    const netId = connMap.getNetConnectedToId(platedHole.pcb_plated_hole_id);
    const outerRadius = platedHole.outer_diameter / 2;
    const circle = new Circle(point(centerX, centerY), outerRadius);
    const polygon = circleToPolygon(circle);
    if (netId) {
      if (includeLayers.includes("top")) {
        topCutNetGeoms.get(netId)?.push(polygon.clone());
      }
      if (includeLayers.includes("bottom")) {
        bottomCutNetGeoms.get(netId)?.push(polygon.clone());
      }
    } else {
      const outer = createCirclePath({
        centerX,
        centerY,
        radius: outerRadius
      });
      if (includeLayers.includes("top")) {
        project.children.push(
          new ShapePath({
            cutIndex: topCopperCutSetting.index,
            verts: outer.verts,
            prims: outer.prims,
            isClosed: true
          })
        );
      }
      if (includeLayers.includes("bottom")) {
        project.children.push(
          new ShapePath({
            cutIndex: bottomCopperCutSetting.index,
            verts: outer.verts,
            prims: outer.prims,
            isClosed: true
          })
        );
      }
    }
  }
  if (platedHole.outer_diameter > 0 && includeSoldermask) {
    const percentMargin = solderMaskMarginPercent / 100 * platedHole.outer_diameter;
    const totalMargin = globalCopperSoldermaskMarginAdjustment + (platedHole.soldermask_margin ?? 0) + percentMargin;
    const smRadius = Math.max(platedHole.outer_diameter / 2 + totalMargin, 0);
    const outer = createCirclePath({
      centerX,
      centerY,
      radius: smRadius
    });
    project.children.push(
      new ShapePath({
        cutIndex: soldermaskCutSetting.index,
        verts: outer.verts,
        prims: outer.prims,
        isClosed: true
      })
    );
  }
  if (platedHole.hole_diameter > 0 && includeCopper) {
    const innerRadius = platedHole.hole_diameter / 2;
    const inner = createCirclePath({
      centerX,
      centerY,
      radius: innerRadius
    });
    project.children.push(
      new ShapePath({
        cutIndex: throughBoardCutSetting.index,
        verts: inner.verts,
        prims: inner.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPlatedHole/addOvalPlatedHole.ts
import { ShapePath as ShapePath2 } from "lbrnts";

// lib/helpers/ovalShape.ts
var createOvalPath = ({
  centerX,
  centerY,
  width,
  height,
  rotation = 0,
  segments = 64
}) => {
  const verts = [];
  const prims = [];
  const radiusX = width / 2;
  const radiusY = height / 2;
  const cosTheta = Math.cos(rotation);
  const sinTheta = Math.sin(rotation);
  for (let i = 0; i < segments; i++) {
    const angle = i / segments * Math.PI * 2;
    const localX = radiusX * Math.cos(angle);
    const localY = radiusY * Math.sin(angle);
    const rotatedX = centerX + localX * cosTheta - localY * sinTheta;
    const rotatedY = centerY + localX * sinTheta + localY * cosTheta;
    verts.push({ x: rotatedX, y: rotatedY });
    prims.push({ type: 0 });
  }
  if (verts.length > 0) {
    const firstVert = verts[0];
    if (firstVert) {
      verts.push({ ...firstVert });
      prims.push({ type: 0 });
    }
  }
  return { verts, prims };
};

// lib/element-handlers/addPlatedHole/addOvalPlatedHole.ts
var addOvalPlatedHole = (platedHole, ctx) => {
  const {
    project,
    topCopperCutSetting,
    bottomCopperCutSetting,
    soldermaskCutSetting,
    throughBoardCutSetting,
    origin,
    includeCopper,
    includeSoldermask,
    globalCopperSoldermaskMarginAdjustment,
    solderMaskMarginPercent,
    includeLayers
  } = ctx;
  if (platedHole.outer_width <= 0 || platedHole.outer_height <= 0) {
    return;
  }
  const centerX = platedHole.x + origin.x;
  const centerY = platedHole.y + origin.y;
  const rotation = (platedHole.ccw_rotation ?? 0) * (Math.PI / 180);
  if (platedHole.outer_width > 0 && platedHole.outer_height > 0 && includeCopper) {
    const outer = createOvalPath({
      centerX,
      centerY,
      width: platedHole.outer_width,
      height: platedHole.outer_height,
      rotation
    });
    if (includeLayers.includes("top")) {
      project.children.push(
        new ShapePath2({
          cutIndex: topCopperCutSetting.index,
          verts: outer.verts,
          prims: outer.prims,
          isClosed: true
        })
      );
    }
    if (includeLayers.includes("bottom")) {
      project.children.push(
        new ShapePath2({
          cutIndex: bottomCopperCutSetting.index,
          verts: outer.verts,
          prims: outer.prims,
          isClosed: true
        })
      );
    }
  }
  if (platedHole.outer_width > 0 && platedHole.outer_height > 0 && includeSoldermask) {
    const percentMarginX = solderMaskMarginPercent / 100 * platedHole.outer_width;
    const percentMarginY = solderMaskMarginPercent / 100 * platedHole.outer_height;
    const totalMarginX = Math.max(
      globalCopperSoldermaskMarginAdjustment + (platedHole.soldermask_margin ?? 0) + percentMarginX,
      -platedHole.outer_width / 2
    );
    const totalMarginY = Math.max(
      globalCopperSoldermaskMarginAdjustment + (platedHole.soldermask_margin ?? 0) + percentMarginY,
      -platedHole.outer_height / 2
    );
    const smWidth = platedHole.outer_width + 2 * totalMarginX;
    const smHeight = platedHole.outer_height + 2 * totalMarginY;
    const outer = createOvalPath({
      centerX,
      centerY,
      width: smWidth,
      height: smHeight,
      rotation
    });
    project.children.push(
      new ShapePath2({
        cutIndex: soldermaskCutSetting.index,
        verts: outer.verts,
        prims: outer.prims,
        isClosed: true
      })
    );
  }
  if (platedHole.hole_width > 0 && platedHole.hole_height > 0 && includeCopper) {
    const inner = createOvalPath({
      centerX,
      centerY,
      width: platedHole.hole_width,
      height: platedHole.hole_height,
      rotation
    });
    project.children.push(
      new ShapePath2({
        cutIndex: throughBoardCutSetting.index,
        verts: inner.verts,
        prims: inner.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPlatedHole/addCircularHoleWithRectPad.ts
import { ShapePath as ShapePath4 } from "lbrnts";

// lib/helpers/roundedRectShape.ts
var createRoundedRectPath = ({
  centerX,
  centerY,
  width,
  height,
  borderRadius = 0,
  segments = 4,
  rotation = 0
}) => {
  const verts = [];
  const prims = [];
  const halfWidth = width / 2;
  const halfHeight = height / 2;
  const rotatePoint = (x, y) => {
    const cos = Math.cos(rotation);
    const sin = Math.sin(rotation);
    return {
      x: x * cos - y * sin,
      y: x * sin + y * cos
    };
  };
  const addPoint = (x, y) => {
    const rotated = rotation ? rotatePoint(x, y) : { x, y };
    verts.push({ x: centerX + rotated.x, y: centerY + rotated.y });
    prims.push({ type: 0 });
  };
  addPoint(-halfWidth + borderRadius, -halfHeight);
  addPoint(halfWidth - borderRadius, -halfHeight);
  if (borderRadius > 0) {
    for (let i = 0; i <= segments; i++) {
      const angle = Math.PI * 1.5 + i * Math.PI * 0.5 / segments;
      addPoint(
        halfWidth - borderRadius + Math.cos(angle) * borderRadius,
        -halfHeight + borderRadius + Math.sin(angle) * borderRadius
      );
    }
  } else {
    addPoint(halfWidth, -halfHeight);
  }
  addPoint(halfWidth, -halfHeight + borderRadius);
  addPoint(halfWidth, halfHeight - borderRadius);
  if (borderRadius > 0) {
    for (let i = 0; i <= segments; i++) {
      const angle = i * Math.PI * 0.5 / segments;
      addPoint(
        halfWidth - borderRadius + Math.cos(angle) * borderRadius,
        halfHeight - borderRadius + Math.sin(angle) * borderRadius
      );
    }
  } else {
    addPoint(halfWidth, halfHeight);
  }
  addPoint(halfWidth - borderRadius, halfHeight);
  addPoint(-halfWidth + borderRadius, halfHeight);
  if (borderRadius > 0) {
    for (let i = 0; i <= segments; i++) {
      const angle = Math.PI * 0.5 + i * Math.PI * 0.5 / segments;
      addPoint(
        -halfWidth + borderRadius + Math.cos(angle) * borderRadius,
        halfHeight - borderRadius + Math.sin(angle) * borderRadius
      );
    }
  } else {
    addPoint(-halfWidth, halfHeight);
  }
  addPoint(-halfWidth, halfHeight - borderRadius);
  addPoint(-halfWidth, -halfHeight + borderRadius);
  if (borderRadius > 0) {
    for (let i = 0; i <= segments; i++) {
      const angle = Math.PI + i * Math.PI * 0.5 / segments;
      addPoint(
        -halfWidth + borderRadius + Math.cos(angle) * borderRadius,
        -halfHeight + borderRadius + Math.sin(angle) * borderRadius
      );
    }
  } else {
    addPoint(-halfWidth, -halfHeight);
  }
  if (verts.length > 0) {
    const firstVert = verts[0];
    if (firstVert) {
      verts.push({ ...firstVert });
      prims.push({ type: 0 });
    }
  }
  return { verts, prims };
};

// lib/helpers/addCopperGeometryToNetOrProject.ts
import { ShapePath as ShapePath3 } from "lbrnts";

// lib/helpers/pathToPolygon.ts
import { Polygon, point as point2 } from "@flatten-js/core";
var pathToPolygon = (verts) => {
  const points = verts.map((v) => point2(v.x, v.y));
  return new Polygon(points);
};

// lib/helpers/addCopperGeometryToNetOrProject.ts
var addCopperGeometryToNetOrProject = ({
  geometryId,
  path,
  layer,
  ctx
}) => {
  const {
    project,
    connMap,
    topCutNetGeoms,
    bottomCutNetGeoms,
    topCopperCutSetting,
    bottomCopperCutSetting,
    includeLayers
  } = ctx;
  if (!includeLayers.includes(layer)) return;
  const netId = connMap.getNetConnectedToId(geometryId);
  const cutSetting = layer === "top" ? topCopperCutSetting : bottomCopperCutSetting;
  const netGeoms = layer === "top" ? topCutNetGeoms : bottomCutNetGeoms;
  if (netId) {
    const polygon = pathToPolygon(path.verts);
    netGeoms.get(netId)?.push(polygon);
  } else {
    project.children.push(
      new ShapePath3({
        cutIndex: cutSetting.index,
        verts: path.verts,
        prims: path.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPlatedHole/addCircularHoleWithRectPad.ts
var addCircularHoleWithRectPad = (platedHole, ctx) => {
  const {
    project,
    soldermaskCutSetting,
    throughBoardCutSetting,
    origin,
    includeCopper,
    includeSoldermask,
    globalCopperSoldermaskMarginAdjustment,
    solderMaskMarginPercent,
    includeLayers
  } = ctx;
  const centerX = platedHole.x + origin.x;
  const centerY = platedHole.y + origin.y;
  const holeRadius = platedHole.hole_diameter / 2;
  const padWidth = platedHole.rect_pad_width;
  const padHeight = platedHole.rect_pad_height;
  const borderRadius = platedHole.rect_border_radius ?? 0;
  const padPath = createRoundedRectPath({
    centerX,
    centerY,
    width: padWidth,
    height: padHeight,
    borderRadius
  });
  if (includeCopper) {
    addCopperGeometryToNetOrProject({
      geometryId: platedHole.pcb_plated_hole_id,
      path: padPath,
      layer: "top",
      ctx
    });
    addCopperGeometryToNetOrProject({
      geometryId: platedHole.pcb_plated_hole_id,
      path: padPath,
      layer: "bottom",
      ctx
    });
  }
  if (includeSoldermask) {
    const percentMarginX = solderMaskMarginPercent / 100 * padWidth;
    const percentMarginY = solderMaskMarginPercent / 100 * padHeight;
    const totalMarginX = Math.max(
      globalCopperSoldermaskMarginAdjustment + (platedHole.soldermask_margin ?? 0) + percentMarginX,
      -padWidth / 2
    );
    const totalMarginY = Math.max(
      globalCopperSoldermaskMarginAdjustment + (platedHole.soldermask_margin ?? 0) + percentMarginY,
      -padHeight / 2
    );
    const smPadWidth = padWidth + 2 * totalMarginX;
    const smPadHeight = padHeight + 2 * totalMarginY;
    const smPadPath = createRoundedRectPath({
      centerX,
      centerY,
      width: smPadWidth,
      height: smPadHeight,
      borderRadius
    });
    project.children.push(
      new ShapePath4({
        cutIndex: soldermaskCutSetting.index,
        verts: smPadPath.verts,
        prims: smPadPath.prims,
        isClosed: true
      })
    );
  }
  if (holeRadius > 0 && includeCopper) {
    const holeCenterX = centerX + (platedHole.hole_offset_x ?? 0);
    const holeCenterY = centerY + (platedHole.hole_offset_y ?? 0);
    const holePath = createCirclePath({
      centerX: holeCenterX,
      centerY: holeCenterY,
      radius: holeRadius,
      segments: 32
    });
    project.children.push(
      new ShapePath4({
        cutIndex: throughBoardCutSetting.index,
        verts: holePath.verts,
        prims: holePath.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPlatedHole/addPillHoleWithRectPad.ts
import { ShapePath as ShapePath5 } from "lbrnts";

// lib/helpers/pathPointUtils.ts
var createPointAdder = ({
  verts,
  prims,
  options = {}
}) => {
  const { rotation = 0, rotationCenter, translation } = options;
  const cos = Math.cos(rotation);
  const sin = Math.sin(rotation);
  const effectiveCenter = rotationCenter ?? translation ?? { x: 0, y: 0 };
  return (x, y) => {
    let px = x;
    let py = y;
    if (rotation) {
      const cx = effectiveCenter.x;
      const cy = effectiveCenter.y;
      const dx = px - cx;
      const dy = py - cy;
      px = cx + dx * cos - dy * sin;
      py = cy + dx * sin + dy * cos;
    }
    const tx = translation?.x ?? 0;
    const ty = translation?.y ?? 0;
    verts.push({ x: px + tx, y: py + ty });
    prims.push({ type: 0 });
  };
};

// lib/helpers/pillShape.ts
var createPillPath = ({
  centerX,
  centerY,
  width,
  height,
  rotation = 0,
  segments = 32
}) => {
  const verts = [];
  const prims = [];
  const halfWidth = width / 2;
  const halfHeight = height / 2;
  const radius = Math.min(halfWidth, halfHeight);
  const isVertical = height > width;
  const addPoint = createPointAdder({
    verts,
    prims,
    options: {
      rotation,
      rotationCenter: { x: centerX, y: centerY }
    }
  });
  if (isVertical) {
    const capOffset = halfHeight - radius;
    for (let i = 0; i <= segments; i++) {
      const angle = Math.PI + i / segments * Math.PI;
      const x = centerX + radius * Math.cos(angle);
      const y = centerY - capOffset + radius * Math.sin(angle);
      addPoint(x, y);
    }
    addPoint(centerX + radius, centerY + capOffset);
    for (let i = 0; i <= segments; i++) {
      const angle = i / segments * Math.PI;
      const x = centerX + radius * Math.cos(angle);
      const y = centerY + capOffset + radius * Math.sin(angle);
      addPoint(x, y);
    }
    addPoint(centerX - radius, centerY - capOffset);
  } else {
    const capOffset = halfWidth - radius;
    for (let i = 0; i <= segments; i++) {
      const angle = -Math.PI / 2 + i / segments * Math.PI;
      const x = centerX + capOffset + radius * Math.cos(angle);
      const y = centerY + radius * Math.sin(angle);
      addPoint(x, y);
    }
    addPoint(centerX - capOffset, centerY + radius);
    for (let i = 0; i <= segments; i++) {
      const angle = Math.PI / 2 + i / segments * Math.PI;
      const x = centerX - capOffset + radius * Math.cos(angle);
      const y = centerY + radius * Math.sin(angle);
      addPoint(x, y);
    }
    addPoint(centerX + capOffset, centerY - radius);
  }
  if (verts.length > 0) {
    const firstVert = verts[0];
    if (firstVert) {
      verts.push({ ...firstVert });
      prims.push({ type: 0 });
    }
  }
  return { verts, prims };
};

// lib/element-handlers/addPlatedHole/addPillHoleWithRectPad.ts
var addPillHoleWithRectPad = (platedHole, ctx) => {
  const {
    project,
    soldermaskCutSetting,
    throughBoardCutSetting,
    origin,
    includeCopper,
    includeSoldermask,
    globalCopperSoldermaskMarginAdjustment,
    solderMaskMarginPercent,
    includeLayers
  } = ctx;
  const centerX = platedHole.x + origin.x;
  const centerY = platedHole.y + origin.y;
  const padWidth = platedHole.rect_pad_width;
  const padHeight = platedHole.rect_pad_height;
  const borderRadius = platedHole.rect_border_radius ?? 0;
  if (padWidth > 0 && padHeight > 0) {
    const padPath = createRoundedRectPath({
      centerX,
      centerY,
      width: padWidth,
      height: padHeight,
      borderRadius
    });
    if (includeCopper) {
      addCopperGeometryToNetOrProject({
        geometryId: platedHole.pcb_plated_hole_id,
        path: padPath,
        layer: "top",
        ctx
      });
      addCopperGeometryToNetOrProject({
        geometryId: platedHole.pcb_plated_hole_id,
        path: padPath,
        layer: "bottom",
        ctx
      });
    }
    if (includeSoldermask) {
      const percentMarginX = solderMaskMarginPercent / 100 * padWidth;
      const percentMarginY = solderMaskMarginPercent / 100 * padHeight;
      const totalMarginX = Math.max(
        globalCopperSoldermaskMarginAdjustment + (platedHole.soldermask_margin ?? 0) + percentMarginX,
        -padWidth / 2
      );
      const totalMarginY = Math.max(
        globalCopperSoldermaskMarginAdjustment + (platedHole.soldermask_margin ?? 0) + percentMarginY,
        -padHeight / 2
      );
      const smPadWidth = padWidth + 2 * totalMarginX;
      const smPadHeight = padHeight + 2 * totalMarginY;
      const smPadPath = createRoundedRectPath({
        centerX,
        centerY,
        width: smPadWidth,
        height: smPadHeight,
        borderRadius
      });
      project.children.push(
        new ShapePath5({
          cutIndex: soldermaskCutSetting.index,
          verts: smPadPath.verts,
          prims: smPadPath.prims,
          isClosed: true
        })
      );
    }
  }
  const holeWidth = platedHole.hole_width;
  const holeHeight = platedHole.hole_height;
  if (holeWidth > 0 && holeHeight > 0 && includeCopper) {
    const holeCenterX = centerX + platedHole.hole_offset_x;
    const holeCenterY = centerY + platedHole.hole_offset_y;
    const holePath = createPillPath({
      centerX: holeCenterX,
      centerY: holeCenterY,
      width: holeWidth,
      height: holeHeight
    });
    project.children.push(
      new ShapePath5({
        cutIndex: throughBoardCutSetting.index,
        verts: holePath.verts,
        prims: holePath.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPlatedHole/addRotatedPillHoleWithRectPad.ts
import { ShapePath as ShapePath6 } from "lbrnts";
var addRotatedPillHoleWithRectPad = (platedHole, ctx) => {
  const {
    project,
    soldermaskCutSetting,
    throughBoardCutSetting,
    origin,
    includeCopper,
    includeSoldermask,
    globalCopperSoldermaskMarginAdjustment,
    solderMaskMarginPercent,
    includeLayers
  } = ctx;
  const centerX = platedHole.x + origin.x;
  const centerY = platedHole.y + origin.y;
  const padWidth = platedHole.rect_pad_width;
  const padHeight = platedHole.rect_pad_height;
  const borderRadius = platedHole.rect_border_radius ?? 0;
  const padRotation = (platedHole.rect_ccw_rotation ?? 0) * (Math.PI / 180);
  if (padWidth > 0 && padHeight > 0) {
    const padPath = createRoundedRectPath({
      centerX,
      centerY,
      width: padWidth,
      height: padHeight,
      borderRadius,
      segments: 4,
      rotation: padRotation
    });
    if (includeCopper) {
      addCopperGeometryToNetOrProject({
        geometryId: platedHole.pcb_plated_hole_id,
        path: padPath,
        layer: "top",
        ctx
      });
      addCopperGeometryToNetOrProject({
        geometryId: platedHole.pcb_plated_hole_id,
        path: padPath,
        layer: "bottom",
        ctx
      });
    }
    if (includeSoldermask) {
      const percentMarginX = solderMaskMarginPercent / 100 * padWidth;
      const percentMarginY = solderMaskMarginPercent / 100 * padHeight;
      const totalMarginX = Math.max(
        globalCopperSoldermaskMarginAdjustment + (platedHole.soldermask_margin ?? 0) + percentMarginX,
        -padWidth / 2
      );
      const totalMarginY = Math.max(
        globalCopperSoldermaskMarginAdjustment + (platedHole.soldermask_margin ?? 0) + percentMarginY,
        -padHeight / 2
      );
      const smPadWidth = padWidth + 2 * totalMarginX;
      const smPadHeight = padHeight + 2 * totalMarginY;
      const smPadPath = createRoundedRectPath({
        centerX,
        centerY,
        width: smPadWidth,
        height: smPadHeight,
        borderRadius,
        segments: 4,
        rotation: padRotation
      });
      project.children.push(
        new ShapePath6({
          cutIndex: soldermaskCutSetting.index,
          verts: smPadPath.verts,
          prims: smPadPath.prims,
          isClosed: true
        })
      );
    }
  }
  const holeWidth = platedHole.hole_width;
  const holeHeight = platedHole.hole_height;
  const holeRotation = (platedHole.hole_ccw_rotation ?? 0) * (Math.PI / 180);
  if (holeWidth > 0 && holeHeight > 0 && includeCopper) {
    const holeCenterX = centerX + platedHole.hole_offset_x;
    const holeCenterY = centerY + platedHole.hole_offset_y;
    const holePath = createPillPath({
      centerX: holeCenterX,
      centerY: holeCenterY,
      width: holeWidth,
      height: holeHeight,
      rotation: holeRotation
    });
    project.children.push(
      new ShapePath6({
        cutIndex: throughBoardCutSetting.index,
        verts: holePath.verts,
        prims: holePath.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPlatedHole/addHoleWithPolygonPad.ts
import { ShapePath as ShapePath7 } from "lbrnts";

// lib/helpers/polygonShape.ts
var createPolygonPathFromOutline = ({
  outline,
  offsetX,
  offsetY
}) => {
  const verts = [];
  for (const point5 of outline) {
    const x = (point5.x ?? 0) + offsetX;
    const y = (point5.y ?? 0) + offsetY;
    verts.push({ x, y });
  }
  if (verts.length === 0) {
    return { verts, prims: [] };
  }
  const first = verts[0];
  verts.push({ x: first.x, y: first.y });
  const prims = new Array(verts.length).fill({ type: 0 });
  return { verts, prims };
};

// lib/element-handlers/addPlatedHole/addHoleWithPolygonPad.ts
var addHoleWithPolygonPad = (platedHole, ctx) => {
  const {
    project,
    soldermaskCutSetting,
    throughBoardCutSetting,
    origin,
    includeCopper,
    includeSoldermask,
    globalCopperSoldermaskMarginAdjustment,
    includeLayers
  } = ctx;
  if (platedHole.pad_outline.length >= 3 && includeCopper) {
    const pad = createPolygonPathFromOutline({
      outline: platedHole.pad_outline,
      offsetX: platedHole.x + origin.x,
      offsetY: platedHole.y + origin.y
    });
    if (includeCopper) {
      addCopperGeometryToNetOrProject({
        geometryId: platedHole.pcb_plated_hole_id,
        path: pad,
        layer: "top",
        ctx
      });
      addCopperGeometryToNetOrProject({
        geometryId: platedHole.pcb_plated_hole_id,
        path: pad,
        layer: "bottom",
        ctx
      });
    }
    if (includeSoldermask) {
      project.children.push(
        new ShapePath7({
          cutIndex: soldermaskCutSetting.index,
          verts: pad.verts,
          prims: pad.prims,
          isClosed: true
        })
      );
    }
  }
  if (platedHole.hole_shape === "circle" && platedHole.hole_diameter && includeCopper) {
    const centerX = platedHole.x + platedHole.hole_offset_x + origin.x;
    const centerY = platedHole.y + platedHole.hole_offset_y + origin.y;
    const radius = platedHole.hole_diameter / 2;
    const hole = createCirclePath({
      centerX,
      centerY,
      radius,
      segments: 64
    });
    project.children.push(
      new ShapePath7({
        cutIndex: throughBoardCutSetting.index,
        verts: hole.verts,
        prims: hole.prims,
        isClosed: true
      })
    );
  }
  if (platedHole.hole_shape === "pill" && platedHole.hole_diameter && includeCopper) {
    const centerX = platedHole.x + platedHole.hole_offset_x + origin.x;
    const centerY = platedHole.y + platedHole.hole_offset_y + origin.y;
    const radius = platedHole.hole_diameter / 2;
    const hole = createCirclePath({
      centerX,
      centerY,
      radius,
      segments: 64
    });
    project.children.push(
      new ShapePath7({
        cutIndex: throughBoardCutSetting.index,
        verts: hole.verts,
        prims: hole.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPlatedHole/addPillPlatedHole.ts
import { ShapePath as ShapePath8 } from "lbrnts";
var addPcbPlatedHolePill = (platedHole, ctx) => {
  const {
    project,
    topCopperCutSetting,
    bottomCopperCutSetting,
    soldermaskCutSetting,
    throughBoardCutSetting,
    origin,
    includeCopper,
    includeSoldermask,
    globalCopperSoldermaskMarginAdjustment,
    solderMaskMarginPercent,
    includeLayers
  } = ctx;
  const centerX = platedHole.x + origin.x;
  const centerY = platedHole.y + origin.y;
  const rotation = (platedHole.ccw_rotation || 0) * (Math.PI / 180);
  if (platedHole.outer_width > 0 && platedHole.outer_height > 0 && includeCopper) {
    const outer = createPillPath({
      centerX,
      centerY,
      width: platedHole.outer_width,
      height: platedHole.outer_height,
      rotation
    });
    if (includeLayers.includes("top")) {
      project.children.push(
        new ShapePath8({
          cutIndex: topCopperCutSetting.index,
          verts: outer.verts,
          prims: outer.prims,
          isClosed: true
        })
      );
    }
    if (includeLayers.includes("bottom")) {
      project.children.push(
        new ShapePath8({
          cutIndex: bottomCopperCutSetting.index,
          verts: outer.verts,
          prims: outer.prims,
          isClosed: true
        })
      );
    }
  }
  if (platedHole.outer_width > 0 && platedHole.outer_height > 0 && includeSoldermask) {
    const percentMarginX = solderMaskMarginPercent / 100 * platedHole.outer_width;
    const percentMarginY = solderMaskMarginPercent / 100 * platedHole.outer_height;
    const totalMarginX = Math.max(
      globalCopperSoldermaskMarginAdjustment + (platedHole.soldermask_margin ?? 0) + percentMarginX,
      -platedHole.outer_width / 2
    );
    const totalMarginY = Math.max(
      globalCopperSoldermaskMarginAdjustment + (platedHole.soldermask_margin ?? 0) + percentMarginY,
      -platedHole.outer_height / 2
    );
    const smWidth = platedHole.outer_width + 2 * totalMarginX;
    const smHeight = platedHole.outer_height + 2 * totalMarginY;
    const outer = createPillPath({
      centerX,
      centerY,
      width: smWidth,
      height: smHeight,
      rotation
    });
    project.children.push(
      new ShapePath8({
        cutIndex: soldermaskCutSetting.index,
        verts: outer.verts,
        prims: outer.prims,
        isClosed: true
      })
    );
  }
  if (platedHole.hole_width > 0 && platedHole.hole_height > 0 && includeCopper) {
    const inner = createPillPath({
      centerX,
      centerY,
      width: platedHole.hole_width,
      height: platedHole.hole_height,
      rotation
    });
    project.children.push(
      new ShapePath8({
        cutIndex: throughBoardCutSetting.index,
        verts: inner.verts,
        prims: inner.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPlatedHole/index.ts
var addPlatedHole = (platedHole, ctx) => {
  switch (platedHole.shape) {
    case "circle":
      return addCirclePlatedHole(platedHole, ctx);
    case "oval":
      return addOvalPlatedHole(platedHole, ctx);
    case "pill":
      return addPcbPlatedHolePill(platedHole, ctx);
    case "circular_hole_with_rect_pad":
      return addCircularHoleWithRectPad(platedHole, ctx);
    case "pill_hole_with_rect_pad":
      return addPillHoleWithRectPad(platedHole, ctx);
    case "rotated_pill_hole_with_rect_pad":
      return addRotatedPillHoleWithRectPad(platedHole, ctx);
    case "hole_with_polygon_pad":
      return addHoleWithPolygonPad(platedHole, ctx);
    default:
      const _exhaustive = platedHole;
      console.warn(`Unknown plated hole shape: ${platedHole.shape}`);
  }
};

// lib/element-handlers/addSmtPad/addRectSmtPad.ts
import "@flatten-js/core";
import { ShapePath as ShapePath9 } from "lbrnts";
var addRectSmtPad = (smtPad, ctx) => {
  const {
    project,
    soldermaskCutSetting,
    origin,
    includeCopper,
    includeSoldermask,
    globalCopperSoldermaskMarginAdjustment,
    solderMaskMarginPercent,
    includeLayers
  } = ctx;
  const padLayer = smtPad.layer || "top";
  if (padLayer !== "top" && padLayer !== "bottom") {
    return;
  }
  if (!includeLayers.includes(padLayer)) {
    return;
  }
  const centerX = smtPad.x + origin.x;
  const centerY = smtPad.y + origin.y;
  const halfWidth = smtPad.width / 2;
  const halfHeight = smtPad.height / 2;
  if (includeCopper) {
    const path = {
      verts: [
        { x: centerX - halfWidth, y: centerY - halfHeight },
        { x: centerX + halfWidth, y: centerY - halfHeight },
        { x: centerX + halfWidth, y: centerY + halfHeight },
        { x: centerX - halfWidth, y: centerY + halfHeight },
        { x: centerX - halfWidth, y: centerY - halfHeight }
        // Close the path
      ],
      prims: [{ type: 0 }, { type: 0 }, { type: 0 }, { type: 0 }, { type: 0 }]
    };
    addCopperGeometryToNetOrProject({
      geometryId: smtPad.pcb_smtpad_id,
      path,
      layer: padLayer,
      ctx
    });
  }
  if (includeSoldermask) {
    const percentMarginX = solderMaskMarginPercent / 100 * smtPad.width;
    const percentMarginY = solderMaskMarginPercent / 100 * smtPad.height;
    const totalMarginX = Math.max(
      globalCopperSoldermaskMarginAdjustment + (smtPad.soldermask_margin ?? 0) + percentMarginX,
      -smtPad.width / 2
    );
    const totalMarginY = Math.max(
      globalCopperSoldermaskMarginAdjustment + (smtPad.soldermask_margin ?? 0) + percentMarginY,
      -smtPad.height / 2
    );
    const smHalfWidth = halfWidth + totalMarginX;
    const smHalfHeight = halfHeight + totalMarginY;
    const verts = [
      { x: centerX - smHalfWidth, y: centerY - smHalfHeight },
      { x: centerX + smHalfWidth, y: centerY - smHalfHeight },
      { x: centerX + smHalfWidth, y: centerY + smHalfHeight },
      { x: centerX - smHalfWidth, y: centerY + smHalfHeight },
      { x: centerX - smHalfWidth, y: centerY - smHalfHeight }
      // Close the path
    ];
    const prims = [
      { type: 0 },
      { type: 0 },
      { type: 0 },
      { type: 0 },
      { type: 0 }
    ];
    project.children.push(
      new ShapePath9({
        cutIndex: soldermaskCutSetting.index,
        verts,
        prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addSmtPad/addCircleSmtPad.ts
import { ShapePath as ShapePath10 } from "lbrnts";
var addCircleSmtPad = (smtPad, ctx) => {
  const {
    project,
    soldermaskCutSetting,
    origin,
    includeCopper,
    includeSoldermask,
    globalCopperSoldermaskMarginAdjustment,
    solderMaskMarginPercent,
    includeLayers
  } = ctx;
  const padLayer = smtPad.layer || "top";
  if (padLayer !== "top" && padLayer !== "bottom") {
    return;
  }
  if (!includeLayers.includes(padLayer)) {
    return;
  }
  const centerX = smtPad.x + origin.x;
  const centerY = smtPad.y + origin.y;
  if (smtPad.radius > 0) {
    const outerRadius = smtPad.radius;
    if (includeCopper) {
      const path = createCirclePath({
        centerX,
        centerY,
        radius: outerRadius
      });
      addCopperGeometryToNetOrProject({
        geometryId: smtPad.pcb_smtpad_id,
        path,
        layer: padLayer,
        ctx
      });
    }
    if (includeSoldermask) {
      const elementWidth = 2 * outerRadius;
      const elementHeight = 2 * outerRadius;
      const percentMarginX = solderMaskMarginPercent / 100 * elementWidth;
      const percentMarginY = solderMaskMarginPercent / 100 * elementHeight;
      const totalMarginX = Math.max(
        globalCopperSoldermaskMarginAdjustment + (smtPad.soldermask_margin ?? 0) + percentMarginX,
        -elementWidth / 2
      );
      const totalMarginY = Math.max(
        globalCopperSoldermaskMarginAdjustment + (smtPad.soldermask_margin ?? 0) + percentMarginY,
        -elementHeight / 2
      );
      const smRadius = outerRadius + totalMarginX;
      const outer = createCirclePath({
        centerX,
        centerY,
        radius: smRadius
      });
      project.children.push(
        new ShapePath10({
          cutIndex: soldermaskCutSetting.index,
          verts: outer.verts,
          prims: outer.prims,
          isClosed: true
        })
      );
    }
  }
};

// lib/element-handlers/addSmtPad/addPillSmtPad.ts
import { ShapePath as ShapePath11 } from "lbrnts";
var addPillSmtPad = (smtPad, ctx) => {
  const {
    project,
    soldermaskCutSetting,
    origin,
    includeCopper,
    includeSoldermask,
    globalCopperSoldermaskMarginAdjustment,
    solderMaskMarginPercent,
    includeLayers
  } = ctx;
  const padLayer = smtPad.layer || "top";
  if (padLayer !== "top" && padLayer !== "bottom") {
    return;
  }
  if (!includeLayers.includes(padLayer)) {
    return;
  }
  const centerX = smtPad.x + origin.x;
  const centerY = smtPad.y + origin.y;
  if (smtPad.width > 0 && smtPad.height > 0) {
    const outer = createPillPath({
      centerX,
      centerY,
      width: smtPad.width,
      height: smtPad.height
    });
    if (includeCopper) {
      addCopperGeometryToNetOrProject({
        geometryId: smtPad.pcb_smtpad_id,
        path: outer,
        layer: padLayer,
        ctx
      });
    }
    if (includeSoldermask) {
      const percentMarginX = solderMaskMarginPercent / 100 * smtPad.width;
      const percentMarginY = solderMaskMarginPercent / 100 * smtPad.height;
      const totalMarginX = Math.max(
        globalCopperSoldermaskMarginAdjustment + (smtPad.soldermask_margin ?? 0) + percentMarginX,
        -smtPad.width / 2
      );
      const totalMarginY = Math.max(
        globalCopperSoldermaskMarginAdjustment + (smtPad.soldermask_margin ?? 0) + percentMarginY,
        -smtPad.height / 2
      );
      const smWidth = smtPad.width + 2 * totalMarginX;
      const smHeight = smtPad.height + 2 * totalMarginY;
      const smOuter = createPillPath({
        centerX,
        centerY,
        width: smWidth,
        height: smHeight
      });
      project.children.push(
        new ShapePath11({
          cutIndex: soldermaskCutSetting.index,
          verts: smOuter.verts,
          prims: smOuter.prims,
          isClosed: true
        })
      );
    }
  }
};

// lib/element-handlers/addSmtPad/addRotatedPillSmtPad.ts
import { ShapePath as ShapePath12 } from "lbrnts";
var addRotatedPillSmtPad = (smtPad, ctx) => {
  const {
    project,
    soldermaskCutSetting,
    origin,
    includeCopper,
    includeSoldermask,
    globalCopperSoldermaskMarginAdjustment,
    solderMaskMarginPercent,
    includeLayers
  } = ctx;
  const padLayer = smtPad.layer || "top";
  if (padLayer !== "top" && padLayer !== "bottom") {
    return;
  }
  if (!includeLayers.includes(padLayer)) {
    return;
  }
  const centerX = smtPad.x + origin.x;
  const centerY = smtPad.y + origin.y;
  if (smtPad.width > 0 && smtPad.height > 0) {
    const outer = createPillPath({
      centerX,
      centerY,
      width: smtPad.width,
      height: smtPad.height,
      rotation: (smtPad.ccw_rotation ?? 0) * (Math.PI / 180)
    });
    if (includeCopper) {
      addCopperGeometryToNetOrProject({
        geometryId: smtPad.pcb_smtpad_id,
        path: outer,
        layer: padLayer,
        ctx
      });
    }
    if (includeSoldermask) {
      const percentMarginX = solderMaskMarginPercent / 100 * smtPad.width;
      const percentMarginY = solderMaskMarginPercent / 100 * smtPad.height;
      const totalMarginX = Math.max(
        globalCopperSoldermaskMarginAdjustment + (smtPad.soldermask_margin ?? 0) + percentMarginX,
        -smtPad.width / 2
      );
      const totalMarginY = Math.max(
        globalCopperSoldermaskMarginAdjustment + (smtPad.soldermask_margin ?? 0) + percentMarginY,
        -smtPad.height / 2
      );
      const smWidth = smtPad.width + 2 * totalMarginX;
      const smHeight = smtPad.height + 2 * totalMarginY;
      const smOuter = createPillPath({
        centerX,
        centerY,
        width: smWidth,
        height: smHeight,
        rotation: (smtPad.ccw_rotation ?? 0) * (Math.PI / 180)
      });
      project.children.push(
        new ShapePath12({
          cutIndex: soldermaskCutSetting.index,
          verts: smOuter.verts,
          prims: smOuter.prims,
          isClosed: true
        })
      );
    }
  }
};

// lib/element-handlers/addSmtPad/addPolygonSmtPad.ts
import { ShapePath as ShapePath13 } from "lbrnts";

// lib/polygon-to-shape-path.ts
function polygonToShapePathData(polygon) {
  const verts = [];
  const prims = [];
  const svgString = polygon.svg();
  const dAttributeMatch = svgString.match(/\bd="([^"]+)"/);
  if (!dAttributeMatch || !dAttributeMatch[1]) {
    return { verts, prims };
  }
  const pathData = dAttributeMatch[1].trim();
  const commands = pathData.match(/[MLz][^MLz]*/g) || [];
  for (const command of commands) {
    const type = command[0];
    const coords = command.slice(1).trim();
    if (type === "M" || type === "L") {
      const parts = coords.split(",");
      const x = Number(parts[0]);
      const y = Number(parts[1]);
      if (!Number.isNaN(x) && !Number.isNaN(y)) {
        verts.push({ x, y });
        prims.push({ type: 0 });
      }
    }
  }
  return { verts, prims };
}

// lib/element-handlers/addSmtPad/addPolygonSmtPad.ts
var addPolygonSmtPad = (smtPad, ctx) => {
  const {
    project,
    soldermaskCutSetting,
    origin,
    includeCopper,
    includeSoldermask,
    globalCopperSoldermaskMarginAdjustment,
    solderMaskMarginPercent,
    includeLayers
  } = ctx;
  const padLayer = smtPad.layer || "top";
  if (padLayer !== "top" && padLayer !== "bottom") {
    return;
  }
  if (!includeLayers.includes(padLayer)) {
    return;
  }
  if (smtPad.points.length >= 3) {
    const pad = createPolygonPathFromOutline({
      outline: smtPad.points,
      offsetX: origin.x,
      offsetY: origin.y
    });
    if (includeCopper) {
      addCopperGeometryToNetOrProject({
        geometryId: smtPad.pcb_smtpad_id,
        path: pad,
        layer: padLayer,
        ctx
      });
    }
    if (includeSoldermask) {
      project.children.push(
        new ShapePath13({
          cutIndex: soldermaskCutSetting.index,
          verts: pad.verts,
          prims: pad.prims,
          isClosed: true
        })
      );
    }
  }
};

// lib/element-handlers/addSmtPad/addRotatedRectSmtPad.ts
import { ShapePath as ShapePath14 } from "lbrnts";
var addRotatedRectSmtPad = (smtPad, ctx) => {
  const {
    project,
    soldermaskCutSetting,
    origin,
    includeCopper,
    includeSoldermask,
    globalCopperSoldermaskMarginAdjustment,
    solderMaskMarginPercent,
    includeLayers
  } = ctx;
  const padLayer = smtPad.layer || "top";
  if (padLayer !== "top" && padLayer !== "bottom") {
    return;
  }
  if (!includeLayers.includes(padLayer)) {
    return;
  }
  const centerX = smtPad.x + origin.x;
  const centerY = smtPad.y + origin.y;
  const rotation = (smtPad.ccw_rotation ?? 0) * (Math.PI / 180);
  const borderRadius = smtPad.rect_border_radius ?? 0;
  if (smtPad.width > 0 && smtPad.height > 0) {
    const outer = createRoundedRectPath({
      centerX,
      centerY,
      width: smtPad.width,
      height: smtPad.height,
      borderRadius,
      segments: 4,
      rotation
    });
    if (includeCopper) {
      addCopperGeometryToNetOrProject({
        geometryId: smtPad.pcb_smtpad_id,
        path: outer,
        layer: padLayer,
        ctx
      });
    }
    if (includeSoldermask) {
      const percentMarginX = solderMaskMarginPercent / 100 * smtPad.width;
      const percentMarginY = solderMaskMarginPercent / 100 * smtPad.height;
      const totalMarginX = Math.max(
        globalCopperSoldermaskMarginAdjustment + (smtPad.soldermask_margin ?? 0) + percentMarginX,
        -smtPad.width / 2
      );
      const totalMarginY = Math.max(
        globalCopperSoldermaskMarginAdjustment + (smtPad.soldermask_margin ?? 0) + percentMarginY,
        -smtPad.height / 2
      );
      const smWidth = smtPad.width + 2 * totalMarginX;
      const smHeight = smtPad.height + 2 * totalMarginY;
      const smOuter = createRoundedRectPath({
        centerX,
        centerY,
        width: smWidth,
        height: smHeight,
        borderRadius,
        segments: 4,
        rotation
      });
      project.children.push(
        new ShapePath14({
          cutIndex: soldermaskCutSetting.index,
          verts: smOuter.verts,
          prims: smOuter.prims,
          isClosed: true
        })
      );
    }
  }
};

// lib/element-handlers/addSmtPad/index.ts
var addSmtPad = (smtPad, ctx) => {
  switch (smtPad.shape) {
    case "rect":
      return addRectSmtPad(smtPad, ctx);
    case "circle":
      return addCircleSmtPad(smtPad, ctx);
    case "pill":
      return addPillSmtPad(smtPad, ctx);
    case "rotated_pill":
      return addRotatedPillSmtPad(smtPad, ctx);
    case "polygon":
      return addPolygonSmtPad(smtPad, ctx);
    case "rotated_rect":
      return addRotatedRectSmtPad(smtPad, ctx);
    default:
      throw new Error(`Unknown smt pad shape: ${smtPad.shape}`);
  }
};

// lib/element-handlers/addPcbTrace/splitRouteSegmentsByLayer.ts
var splitRouteSegmentsByLayer = (trace) => {
  const layerSegments = /* @__PURE__ */ new Map();
  if (!trace.route || trace.route.length < 2) {
    return layerSegments;
  }
  let currentSegment = [];
  let currentLayer = null;
  for (const point5 of trace.route) {
    if ("route_type" in point5 && point5.route_type === "via") {
      if (currentLayer && currentSegment.length > 0) {
        if (!layerSegments.has(currentLayer)) {
          layerSegments.set(currentLayer, []);
        }
        layerSegments.get(currentLayer).push(currentSegment);
      }
      currentSegment = [];
      currentLayer = null;
      continue;
    }
    const isWirePoint = !("route_type" in point5) || point5.route_type === "wire";
    if (isWirePoint && "layer" in point5 && point5.layer) {
      const pointLayer = point5.layer;
      if (pointLayer !== "top" && pointLayer !== "bottom") {
        continue;
      }
      if (currentLayer !== null && currentLayer !== pointLayer) {
        if (currentSegment.length > 0) {
          if (!layerSegments.has(currentLayer)) {
            layerSegments.set(currentLayer, []);
          }
          layerSegments.get(currentLayer).push(currentSegment);
        }
        currentSegment = [];
      }
      currentLayer = pointLayer;
      currentSegment.push({ x: point5.x, y: point5.y });
    }
  }
  if (currentLayer && currentSegment.length > 0) {
    if (!layerSegments.has(currentLayer)) {
      layerSegments.set(currentLayer, []);
    }
    layerSegments.get(currentLayer).push(currentSegment);
  }
  return layerSegments;
};

// lib/element-handlers/addPcbTrace/generateTracePolygons.ts
import "@flatten-js/core";

// lib/element-handlers/addPcbTrace/generateTraceOutline.ts
import Flatten2 from "@flatten-js/core";
var generateTraceOutline = ({
  points,
  width
}) => {
  if (points.length < 2) return null;
  const cleanedInputPoints = removeConsecutiveDuplicates(points);
  if (cleanedInputPoints.length < 2) return null;
  const radius = width / 2;
  const outlinePoints = [];
  const getPerpendicularOffset = (p1, p2) => {
    const dx = p2.x - p1.x;
    const dy = p2.y - p1.y;
    const len = Math.hypot(dx, dy);
    if (len === 0) return { nx: 0, ny: 1 };
    return { nx: -dy / len, ny: dx / len };
  };
  const lineIntersection = (p1, d1, p2, d2) => {
    const cross = d1.x * d2.y - d1.y * d2.x;
    if (Math.abs(cross) < 1e-10) return null;
    const dx = p2.x - p1.x;
    const dy = p2.y - p1.y;
    const t = (dx * d2.y - dy * d2.x) / cross;
    return {
      x: p1.x + t * d1.x,
      y: p1.y + t * d1.y
    };
  };
  const segmentData = [];
  for (let i = 0; i < cleanedInputPoints.length - 1; i++) {
    const p1 = cleanedInputPoints[i];
    const p2 = cleanedInputPoints[i + 1];
    const dx = p2.x - p1.x;
    const dy = p2.y - p1.y;
    const len = Math.hypot(dx, dy);
    const dir = len === 0 ? { x: 1, y: 0 } : { x: dx / len, y: dy / len };
    const { nx, ny } = getPerpendicularOffset(p1, p2);
    segmentData.push({
      p1,
      p2,
      dir,
      leftP1: { x: p1.x + nx * radius, y: p1.y + ny * radius },
      leftP2: { x: p2.x + nx * radius, y: p2.y + ny * radius },
      rightP1: { x: p1.x - nx * radius, y: p1.y - ny * radius },
      rightP2: { x: p2.x - nx * radius, y: p2.y - ny * radius }
    });
  }
  const firstSegment = segmentData[0];
  outlinePoints.push(firstSegment.rightP1);
  outlinePoints.push(firstSegment.leftP1);
  for (let i = 0; i < segmentData.length; i++) {
    const seg = segmentData[i];
    if (i > 0) {
      const prevSeg = segmentData[i - 1];
      const intersection = lineIntersection(
        prevSeg.leftP1,
        prevSeg.dir,
        seg.leftP1,
        seg.dir
      );
      if (intersection) {
        outlinePoints.push(intersection);
      } else {
        outlinePoints.push(seg.leftP1);
      }
    }
    if (i === segmentData.length - 1) {
      outlinePoints.push(seg.leftP2);
    }
  }
  const lastSegment = segmentData[segmentData.length - 1];
  outlinePoints.push(lastSegment.rightP2);
  for (let i = segmentData.length - 1; i >= 0; i--) {
    const seg = segmentData[i];
    if (i < segmentData.length - 1) {
      const nextSeg = segmentData[i + 1];
      const intersection = lineIntersection(
        seg.rightP1,
        seg.dir,
        nextSeg.rightP1,
        nextSeg.dir
      );
      if (intersection) {
        outlinePoints.push(intersection);
      } else {
        outlinePoints.push(seg.rightP2);
      }
    }
    if (i === 0) {
      outlinePoints.push(seg.rightP1);
    }
  }
  const cleanedPoints = removeDuplicatePoints(outlinePoints);
  if (cleanedPoints.length < 3) return null;
  const polygon = new Flatten2.Polygon(
    cleanedPoints.map((p) => Flatten2.point(p.x, p.y))
  );
  return polygon.reverse();
};
var removeConsecutiveDuplicates = (points, tolerance = 1e-9) => {
  if (points.length === 0) return [];
  const result = [points[0]];
  for (let i = 1; i < points.length; i++) {
    const prev = result[result.length - 1];
    const curr = points[i];
    const dist = Math.hypot(curr.x - prev.x, curr.y - prev.y);
    if (dist > tolerance) {
      result.push(curr);
    }
  }
  return result;
};
var removeDuplicatePoints = (points, tolerance = 1e-9) => {
  if (points.length === 0) return [];
  const result = [points[0]];
  for (let i = 1; i < points.length; i++) {
    const prev = result[result.length - 1];
    const curr = points[i];
    const dist = Math.hypot(curr.x - prev.x, curr.y - prev.y);
    if (dist > tolerance) {
      result.push(curr);
    }
  }
  if (result.length > 1) {
    const first = result[0];
    const last = result[result.length - 1];
    const dist = Math.hypot(last.x - first.x, last.y - first.y);
    if (dist <= tolerance) {
      result.pop();
    }
  }
  return result;
};

// lib/element-handlers/addPcbTrace/generateTracePolygons.ts
var generateTracePolygons = ({
  layerSegments,
  width,
  origin,
  includeLayers
}) => {
  const layerPolygons = /* @__PURE__ */ new Map();
  for (const [layer, segments] of layerSegments.entries()) {
    if (!includeLayers.includes(layer)) {
      continue;
    }
    const polygons = [];
    for (const points of segments) {
      if (points.length < 2) continue;
      const translatedPoints = points.map((p) => ({
        x: p.x + origin.x,
        y: p.y + origin.y
      }));
      const outline = generateTraceOutline({
        points: translatedPoints,
        width
      });
      if (outline) {
        polygons.push(outline);
      }
    }
    if (polygons.length > 0) {
      layerPolygons.set(layer, polygons);
    }
  }
  return layerPolygons;
};

// lib/element-handlers/addPcbTrace/unifyTracePolygons.ts
var unifyTracePolygons = ({
  polygons
}) => {
  if (polygons.length === 0) {
    return { success: true, result: [] };
  }
  if (polygons.length === 1) {
    return { success: true, result: polygons[0] };
  }
  return { success: true, result: polygons };
};

// lib/element-handlers/addPcbTrace/index.ts
import Flatten4 from "@flatten-js/core";
var createCirclePolygon = (center, radius, numPoints = 16) => {
  const points = [];
  for (let i = 0; i < numPoints; i++) {
    const angle = 2 * Math.PI * i / numPoints;
    points.push(
      Flatten4.point(
        center.x + radius * Math.cos(angle),
        center.y + radius * Math.sin(angle)
      )
    );
  }
  return new Flatten4.Polygon(points);
};
var positionKey = (x, y) => {
  return `${x.toFixed(6)},${y.toFixed(6)}`;
};
var addPcbTrace = (trace, ctx) => {
  const {
    topCutNetGeoms,
    bottomCutNetGeoms,
    topScanNetGeoms,
    bottomScanNetGeoms,
    connMap,
    origin,
    includeCopper,
    includeLayers,
    traceMargin,
    topTraceEndpoints,
    bottomTraceEndpoints
  } = ctx;
  if (!includeCopper) {
    return;
  }
  const netId = connMap.getNetConnectedToId(
    trace.source_trace_id ?? trace.pcb_trace_id
  );
  if (!netId) {
    return;
  }
  if (!trace.route || trace.route.length < 2) {
    return;
  }
  const wirePoint = trace.route.find((point5) => {
    if (!("route_type" in point5)) return true;
    return point5.route_type === "wire";
  });
  const traceWidth = wirePoint?.width ?? 0.15;
  const layerSegments = splitRouteSegmentsByLayer(trace);
  const normalPolygons = generateTracePolygons({
    layerSegments,
    width: traceWidth,
    origin,
    includeLayers
  });
  for (const [layer, polygons] of normalPolygons.entries()) {
    const { result } = unifyTracePolygons({
      polygons,
      traceId: trace.pcb_trace_id,
      layer
    });
    const cutNetGeoms = layer === "top" ? topCutNetGeoms : bottomCutNetGeoms;
    if (Array.isArray(result)) {
      for (const poly of result) {
        cutNetGeoms.get(netId)?.push(poly);
      }
    } else {
      cutNetGeoms.get(netId)?.push(result);
    }
    const segments = layerSegments.get(layer);
    const endpointSet = layer === "top" ? topTraceEndpoints : bottomTraceEndpoints;
    if (segments) {
      for (const segment of segments) {
        if (segment.length >= 2) {
          const firstPoint = segment[0];
          const lastPoint = segment[segment.length - 1];
          const radius = traceWidth / 2 * 1.1;
          const startX = firstPoint.x + origin.x;
          const startY = firstPoint.y + origin.y;
          const startKey = positionKey(startX, startY);
          if (!endpointSet.has(startKey)) {
            endpointSet.add(startKey);
            const startCircle = createCirclePolygon(
              { x: startX, y: startY },
              radius
            );
            cutNetGeoms.get(netId)?.push(startCircle);
          }
          const endX = lastPoint.x + origin.x;
          const endY = lastPoint.y + origin.y;
          const endKey = positionKey(endX, endY);
          if (!endpointSet.has(endKey)) {
            endpointSet.add(endKey);
            const endCircle = createCirclePolygon({ x: endX, y: endY }, radius);
            cutNetGeoms.get(netId)?.push(endCircle);
          }
        }
      }
    }
  }
  if (traceMargin > 0) {
    const scanPolygons = generateTracePolygons({
      layerSegments,
      width: traceWidth + 2 * traceMargin,
      origin,
      includeLayers
    });
    for (const [layer, polygons] of scanPolygons.entries()) {
      const { result } = unifyTracePolygons({
        polygons,
        traceId: trace.pcb_trace_id,
        layer
      });
      const scanNetGeoms = layer === "top" ? topScanNetGeoms : bottomScanNetGeoms;
      if (Array.isArray(result)) {
        for (const poly of result) {
          scanNetGeoms.get(netId)?.push(poly);
        }
      } else {
        scanNetGeoms.get(netId)?.push(result);
      }
    }
  }
};

// lib/element-handlers/addPcbBoard/index.ts
import { Polygon as Polygon2, point as point3 } from "@flatten-js/core";
import { ShapePath as ShapePath15 } from "lbrnts";
var addPcbBoard = (board, ctx) => {
  const {
    origin,
    project,
    throughBoardCutSetting,
    soldermaskCutSetting,
    includeCopper,
    includeSoldermask
  } = ctx;
  let polygon = null;
  if (board.outline?.length) {
    polygon = new Polygon2(
      board.outline.map(
        (outlinePoint) => point3(outlinePoint.x + origin.x, outlinePoint.y + origin.y)
      )
    );
  } else if (typeof board.width === "number" && typeof board.height === "number" && board.center) {
    const halfWidth = board.width / 2;
    const halfHeight = board.height / 2;
    const minX = board.center.x - halfWidth + origin.x;
    const minY = board.center.y - halfHeight + origin.y;
    const maxX = board.center.x + halfWidth + origin.x;
    const maxY = board.center.y + halfHeight + origin.y;
    polygon = new Polygon2([
      point3(minX, minY),
      point3(maxX, minY),
      point3(maxX, maxY),
      point3(minX, maxY)
    ]);
  }
  if (!polygon) return;
  const { verts, prims } = polygonToShapePathData(polygon);
  if (includeCopper) {
    project.children.push(
      new ShapePath15({
        cutIndex: throughBoardCutSetting.index,
        verts,
        prims,
        isClosed: true
      })
    );
  }
};

// lib/index.ts
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map";

// lib/calculateBounds.ts
import { cju } from "@tscircuit/circuit-json-util";
var calculateCircuitBounds = (circuitJson) => {
  const db = cju(circuitJson);
  let minX = Infinity;
  let minY = Infinity;
  let maxX = -Infinity;
  let maxY = -Infinity;
  for (const smtpad of db.pcb_smtpad.list()) {
    if (smtpad.shape === "rect") {
      const halfWidth = smtpad.width / 2;
      const halfHeight = smtpad.height / 2;
      minX = Math.min(minX, smtpad.x - halfWidth);
      minY = Math.min(minY, smtpad.y - halfHeight);
      maxX = Math.max(maxX, smtpad.x + halfWidth);
      maxY = Math.max(maxY, smtpad.y + halfHeight);
    } else if (smtpad.shape === "circle") {
      const radius = smtpad.radius;
      minX = Math.min(minX, smtpad.x - radius);
      minY = Math.min(minY, smtpad.y - radius);
      maxX = Math.max(maxX, smtpad.x + radius);
      maxY = Math.max(maxY, smtpad.y + radius);
    }
  }
  for (const trace of db.pcb_trace.list()) {
    const isWidthPoint = (point5) => "width" in point5 && typeof point5.width === "number";
    const halfWidth = trace.route_thickness_mode === "interpolated" ? 0 : (trace.route.find(isWidthPoint)?.width ?? 0) / 2;
    for (const point5 of trace.route) {
      const pointWidth = trace.route_thickness_mode === "interpolated" ? isWidthPoint(point5) ? point5.width / 2 : 0 : halfWidth;
      minX = Math.min(minX, point5.x - pointWidth);
      minY = Math.min(minY, point5.y - pointWidth);
      maxX = Math.max(maxX, point5.x + pointWidth);
      maxY = Math.max(maxY, point5.y + pointWidth);
    }
  }
  for (const hole of db.pcb_plated_hole.list()) {
    if (hole.shape === "circle") {
      const radius = hole.outer_diameter / 2;
      minX = Math.min(minX, hole.x - radius);
      minY = Math.min(minY, hole.y - radius);
      maxX = Math.max(maxX, hole.x + radius);
      maxY = Math.max(maxY, hole.y + radius);
    }
  }
  if (!isFinite(minX) || !isFinite(minY) || !isFinite(maxX) || !isFinite(maxY)) {
    return { minX: 0, minY: 0, maxX: 0, maxY: 0 };
  }
  return { minX, minY, maxX, maxY };
};
var calculateOriginFromBounds = (bounds, margin) => {
  const m = margin ?? 0.1;
  const originX = bounds.minX < m ? -bounds.minX + m : 0;
  const originY = bounds.minY < m ? -bounds.minY + m : 0;
  return { x: originX, y: originY };
};

// lib/element-handlers/addPcbVia/index.ts
import { ShapePath as ShapePath16 } from "lbrnts";
import { Circle as Circle2, point as point4 } from "@flatten-js/core";
var addPcbVia = (via, ctx) => {
  const {
    db,
    project,
    topCopperCutSetting,
    bottomCopperCutSetting,
    soldermaskCutSetting,
    throughBoardCutSetting,
    topCutNetGeoms,
    bottomCutNetGeoms,
    origin,
    includeCopper,
    includeSoldermask,
    connMap,
    globalCopperSoldermaskMarginAdjustment,
    solderMaskMarginPercent,
    includeLayers
  } = ctx;
  const centerX = via.x + origin.x;
  const centerY = via.y + origin.y;
  if (via.outer_diameter > 0 && includeCopper) {
    let netId;
    if (via.pcb_trace_id) {
      netId = connMap.getNetConnectedToId(via.pcb_trace_id);
    }
    if (!netId) {
      const viaPort = db.pcb_port.list().find((port) => port.x === via.x && port.y === via.y);
      if (viaPort) {
        netId = connMap.getNetConnectedToId(viaPort.pcb_port_id);
      }
    }
    const outerRadius = via.outer_diameter / 2;
    const circle = new Circle2(point4(centerX, centerY), outerRadius);
    const polygon = circleToPolygon(circle);
    if (netId) {
      if (includeLayers.includes("top")) {
        topCutNetGeoms.get(netId)?.push(polygon.clone());
      }
      if (includeLayers.includes("bottom")) {
        bottomCutNetGeoms.get(netId)?.push(polygon.clone());
      }
    } else {
      const outer = createCirclePath({
        centerX,
        centerY,
        radius: outerRadius
      });
      if (includeLayers.includes("top")) {
        project.children.push(
          new ShapePath16({
            cutIndex: topCopperCutSetting.index,
            verts: outer.verts,
            prims: outer.prims,
            isClosed: true
          })
        );
      }
      if (includeLayers.includes("bottom")) {
        project.children.push(
          new ShapePath16({
            cutIndex: bottomCopperCutSetting.index,
            verts: outer.verts,
            prims: outer.prims,
            isClosed: true
          })
        );
      }
    }
  }
  if (via.outer_diameter > 0 && includeSoldermask) {
    const percentMargin = solderMaskMarginPercent / 100 * via.outer_diameter;
    const totalMargin = globalCopperSoldermaskMarginAdjustment + percentMargin;
    const smRadius = Math.max(via.outer_diameter / 2 + totalMargin, 0);
    const outer = createCirclePath({
      centerX,
      centerY,
      radius: smRadius
    });
    project.children.push(
      new ShapePath16({
        cutIndex: soldermaskCutSetting.index,
        verts: outer.verts,
        prims: outer.prims,
        isClosed: true
      })
    );
  }
  if (via.hole_diameter > 0 && includeCopper) {
    const innerRadius = via.hole_diameter / 2;
    const inner = createCirclePath({
      centerX,
      centerY,
      radius: innerRadius
    });
    project.children.push(
      new ShapePath16({
        cutIndex: throughBoardCutSetting.index,
        verts: inner.verts,
        prims: inner.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPcbHole/addCirclePcbHole.ts
import { ShapePath as ShapePath17 } from "lbrnts";
var addCirclePcbHole = (hole, ctx) => {
  const { project, throughBoardCutSetting, origin, includeCopper } = ctx;
  const centerX = hole.x + origin.x;
  const centerY = hole.y + origin.y;
  if (hole.hole_diameter > 0 && includeCopper) {
    const radius = hole.hole_diameter / 2;
    const circlePath = createCirclePath({
      centerX,
      centerY,
      radius
    });
    project.children.push(
      new ShapePath17({
        cutIndex: throughBoardCutSetting.index,
        verts: circlePath.verts,
        prims: circlePath.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPcbHole/addRectPcbHole.ts
import { ShapePath as ShapePath18 } from "lbrnts";
var addRectPcbHole = (hole, ctx) => {
  const { project, throughBoardCutSetting, origin, includeCopper } = ctx;
  const centerX = hole.x + origin.x;
  const centerY = hole.y + origin.y;
  if (hole.hole_width > 0 && hole.hole_height > 0 && includeCopper) {
    const rectPath = createRoundedRectPath({
      centerX,
      centerY,
      width: hole.hole_width,
      height: hole.hole_height,
      borderRadius: 0
      // no border radius for rect holes
    });
    project.children.push(
      new ShapePath18({
        cutIndex: throughBoardCutSetting.index,
        verts: rectPath.verts,
        prims: rectPath.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPcbHole/addOvalPcbHole.ts
import { ShapePath as ShapePath19 } from "lbrnts";
var addOvalPcbHole = (hole, ctx) => {
  const { project, throughBoardCutSetting, origin, includeCopper } = ctx;
  const centerX = hole.x + origin.x;
  const centerY = hole.y + origin.y;
  if (hole.hole_width > 0 && hole.hole_height > 0 && includeCopper) {
    const ovalPath = createOvalPath({
      centerX,
      centerY,
      width: hole.hole_width,
      height: hole.hole_height
    });
    project.children.push(
      new ShapePath19({
        cutIndex: throughBoardCutSetting.index,
        verts: ovalPath.verts,
        prims: ovalPath.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPcbHole/addPillPcbHole.ts
import { ShapePath as ShapePath20 } from "lbrnts";
var addPillPcbHole = (hole, ctx) => {
  const { project, throughBoardCutSetting, origin, includeCopper } = ctx;
  const centerX = hole.x + origin.x;
  const centerY = hole.y + origin.y;
  if (hole.hole_width > 0 && hole.hole_height > 0 && includeCopper) {
    const pillPath = createPillPath({
      centerX,
      centerY,
      width: hole.hole_width,
      height: hole.hole_height
    });
    project.children.push(
      new ShapePath20({
        cutIndex: throughBoardCutSetting.index,
        verts: pillPath.verts,
        prims: pillPath.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPcbHole/addRotatedPillPcbHole.ts
import { ShapePath as ShapePath21 } from "lbrnts";
var addRotatedPillPcbHole = (hole, ctx) => {
  const { project, throughBoardCutSetting, origin, includeCopper } = ctx;
  const centerX = hole.x + origin.x;
  const centerY = hole.y + origin.y;
  const rotation = (hole.ccw_rotation || 0) * (Math.PI / 180);
  if (hole.hole_width > 0 && hole.hole_height > 0 && includeCopper) {
    const pillPath = createPillPath({
      centerX,
      centerY,
      width: hole.hole_width,
      height: hole.hole_height,
      rotation
    });
    project.children.push(
      new ShapePath21({
        cutIndex: throughBoardCutSetting.index,
        verts: pillPath.verts,
        prims: pillPath.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPcbHole/index.ts
var addPcbHole = (hole, ctx) => {
  switch (hole.hole_shape) {
    case "circle":
    case "square":
      return addCirclePcbHole(hole, ctx);
    case "rect":
      return addRectPcbHole(hole, ctx);
    case "oval":
      return addOvalPcbHole(hole, ctx);
    case "pill":
      return addPillPcbHole(hole, ctx);
    case "rotated_pill":
      return addRotatedPillPcbHole(hole, ctx);
    default:
      const _exhaustive = hole;
      console.warn(`Unknown hole shape: ${hole.hole_shape}`);
  }
};

// lib/element-handlers/addPcbCutout/addCirclePcbCutout.ts
import { ShapePath as ShapePath22 } from "lbrnts";
var addCirclePcbCutout = (cutout, ctx) => {
  const { project, throughBoardCutSetting, origin, includeCopper } = ctx;
  const centerX = cutout.center.x + origin.x;
  const centerY = cutout.center.y + origin.y;
  if (cutout.radius > 0 && includeCopper) {
    const circlePath = createCirclePath({
      centerX,
      centerY,
      radius: cutout.radius
    });
    project.children.push(
      new ShapePath22({
        cutIndex: throughBoardCutSetting.index,
        verts: circlePath.verts,
        prims: circlePath.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPcbCutout/addRectPcbCutout.ts
import { ShapePath as ShapePath23 } from "lbrnts";
var addRectPcbCutout = (cutout, ctx) => {
  const { project, throughBoardCutSetting, origin, includeCopper } = ctx;
  const centerX = cutout.center.x + origin.x;
  const centerY = cutout.center.y + origin.y;
  if (cutout.width > 0 && cutout.height > 0 && includeCopper) {
    const rotation = (cutout.rotation ?? 0) * (Math.PI / 180);
    const rectPath = createRoundedRectPath({
      centerX,
      centerY,
      width: cutout.width,
      height: cutout.height,
      borderRadius: cutout.corner_radius,
      segments: 4,
      // segments
      rotation
    });
    project.children.push(
      new ShapePath23({
        cutIndex: throughBoardCutSetting.index,
        verts: rectPath.verts,
        prims: rectPath.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPcbCutout/addPolygonPcbCutout.ts
import { ShapePath as ShapePath24 } from "lbrnts";
var addPolygonPcbCutout = (cutout, ctx) => {
  const { project, throughBoardCutSetting, origin, includeCopper } = ctx;
  if (cutout.points.length >= 3 && includeCopper) {
    const polygonPath = createPolygonPathFromOutline({
      outline: cutout.points,
      offsetX: origin.x,
      offsetY: origin.y
    });
    project.children.push(
      new ShapePath24({
        cutIndex: throughBoardCutSetting.index,
        verts: polygonPath.verts,
        prims: polygonPath.prims,
        isClosed: true
      })
    );
  }
};

// lib/element-handlers/addPcbCutout/addPathPcbCutout.ts
import { ShapePath as ShapePath25 } from "lbrnts";
var addPathPcbCutout = (cutout, ctx) => {
  const { project, throughBoardCutSetting, origin, includeCopper } = ctx;
  if (cutout.route.length >= 2 && includeCopper) {
    const verts = [];
    const prims = [];
    for (const point5 of cutout.route) {
      verts.push({
        x: point5.x + origin.x,
        y: point5.y + origin.y
      });
      prims.push({ type: 0 });
    }
    project.children.push(
      new ShapePath25({
        cutIndex: throughBoardCutSetting.index,
        verts,
        prims,
        isClosed: false
        // Paths are typically not closed
      })
    );
  }
};

// lib/element-handlers/addPcbCutout/index.ts
var addPcbCutout = (cutout, ctx) => {
  switch (cutout.shape) {
    case "circle":
      return addCirclePcbCutout(cutout, ctx);
    case "rect":
      return addRectPcbCutout(cutout, ctx);
    case "polygon":
      return addPolygonPcbCutout(cutout, ctx);
    case "path":
      return addPathPcbCutout(cutout, ctx);
    default:
      const _exhaustive = cutout;
      console.warn(`Unknown cutout shape: ${cutout.shape}`);
  }
};

// lib/createCopperShapesForLayer.ts
import { Polygon as Polygon3, Box as Box2, BooleanOperations } from "@flatten-js/core";
import { ShapePath as ShapePath26 } from "lbrnts";
var outputPolygon = (poly, cutIndex, project) => {
  if (poly.faces.size > 1) {
    for (const face of poly.faces) {
      const facePoints = [];
      for (const edge of face) {
        facePoints.push(edge.start);
      }
      if (facePoints.length >= 3) {
        const facePoly = new Polygon3(facePoints);
        const { verts: verts2, prims: prims2 } = polygonToShapePathData(facePoly);
        if (verts2.length > 0) {
          project.children.push(
            new ShapePath26({
              cutIndex,
              verts: verts2,
              prims: prims2,
              isClosed: false
            })
          );
        }
      }
    }
    return;
  }
  const { verts, prims } = polygonToShapePathData(poly);
  project.children.push(
    new ShapePath26({
      cutIndex,
      verts,
      prims,
      isClosed: false
    })
  );
};
var outputIndividualGeometries = (netGeoms, cutIndex, project) => {
  for (const geom of netGeoms) {
    const poly = geom instanceof Box2 ? new Polygon3(geom) : geom;
    outputPolygon(poly, cutIndex, project);
  }
};
var createCopperShapesForLayer = ({
  layer,
  ctx
}) => {
  const {
    project,
    connMap,
    topCutNetGeoms,
    bottomCutNetGeoms,
    topCopperCutSetting,
    bottomCopperCutSetting
  } = ctx;
  const netGeomMap = layer === "top" ? topCutNetGeoms : bottomCutNetGeoms;
  const cutSetting = layer === "top" ? topCopperCutSetting : bottomCopperCutSetting;
  if (!cutSetting) {
    throw new Error(`Cut setting not found for layer ${layer}`);
  }
  const cutIndex = cutSetting.index;
  for (const net of Object.keys(connMap.netMap)) {
    const netGeoms = netGeomMap.get(net);
    if (netGeoms.length === 0) {
      continue;
    }
    if (netGeoms.length === 1) {
      const geom = netGeoms[0];
      const poly = geom instanceof Box2 ? new Polygon3(geom) : geom;
      outputPolygon(poly, cutIndex, project);
      continue;
    }
    try {
      let union = netGeoms[0];
      if (union instanceof Box2) {
        union = new Polygon3(union);
      }
      let unionFailed = false;
      for (const geom of netGeoms.slice(1)) {
        const poly = geom instanceof Polygon3 ? geom : new Polygon3(geom);
        union = BooleanOperations.unify(union, poly);
        if (union.faces.size === 0) {
          unionFailed = true;
          break;
        }
      }
      if (unionFailed) {
        outputIndividualGeometries(netGeoms, cutIndex, project);
        continue;
      }
      const islands = union.splitToIslands();
      if (islands.length === 0) {
        outputIndividualGeometries(netGeoms, cutIndex, project);
        continue;
      }
      for (const island of islands) {
        outputPolygon(island, cutIndex, project);
      }
    } catch (error) {
      console.warn(
        `Failed to union geometries for net ${net} on ${layer} layer:`,
        error
      );
      outputIndividualGeometries(netGeoms, cutIndex, project);
    }
  }
};

// lib/createTraceClearanceAreasForLayer.ts
import { Polygon as Polygon4, Box as Box3, BooleanOperations as BooleanOperations2 } from "@flatten-js/core";
import { ShapePath as ShapePath27 } from "lbrnts";
var createTraceClearanceAreasForLayer = ({
  layer,
  ctx
}) => {
  const {
    project,
    connMap,
    topCutNetGeoms,
    bottomCutNetGeoms,
    topScanNetGeoms,
    bottomScanNetGeoms,
    topTraceClearanceAreaCutSetting,
    bottomTraceClearanceAreaCutSetting
  } = ctx;
  const cutSetting = layer === "top" ? topTraceClearanceAreaCutSetting : bottomTraceClearanceAreaCutSetting;
  if (!cutSetting) {
    return;
  }
  const innerGeomMap = layer === "top" ? topCutNetGeoms : bottomCutNetGeoms;
  const outerGeomMap = layer === "top" ? topScanNetGeoms : bottomScanNetGeoms;
  for (const net of Object.keys(connMap.netMap)) {
    const innerGeoms = innerGeomMap.get(net);
    const outerGeoms = outerGeomMap.get(net);
    if (innerGeoms.length === 0 || outerGeoms.length === 0) {
      continue;
    }
    try {
      let innerUnion = innerGeoms[0];
      if (innerUnion instanceof Box3) {
        innerUnion = new Polygon4(innerUnion);
      }
      for (const geom of innerGeoms.slice(1)) {
        if (geom instanceof Polygon4) {
          innerUnion = BooleanOperations2.unify(innerUnion, geom);
        } else if (geom instanceof Box3) {
          innerUnion = BooleanOperations2.unify(innerUnion, new Polygon4(geom));
        }
      }
      let outerUnion = outerGeoms[0];
      if (outerUnion instanceof Box3) {
        outerUnion = new Polygon4(outerUnion);
      }
      for (const geom of outerGeoms.slice(1)) {
        if (geom instanceof Polygon4) {
          outerUnion = BooleanOperations2.unify(outerUnion, geom);
        } else if (geom instanceof Box3) {
          outerUnion = BooleanOperations2.unify(outerUnion, new Polygon4(geom));
        }
      }
      const clearanceArea = BooleanOperations2.subtract(outerUnion, innerUnion);
      for (const island of clearanceArea.splitToIslands()) {
        const { verts, prims } = polygonToShapePathData(island);
        project.children.push(
          new ShapePath27({
            cutIndex: cutSetting.index,
            verts,
            prims,
            isClosed: true
            // Filled shapes should be closed
          })
        );
      }
    } catch (error) {
      console.warn(
        `Failed to create trace clearance area for net ${net} on ${layer} layer:`,
        error
      );
    }
  }
};

// lib/createCopperCutFillForLayer.ts
import { Polygon as Polygon5, Box as Box4, Point as Point2 } from "@flatten-js/core";
import { ShapePath as ShapePath28, ShapeGroup } from "lbrnts";

// lib/getManifold.ts
var manifoldInstance = null;
var getManifold = async () => {
  if (!manifoldInstance) {
    let ManifoldModule;
    try {
      const moduleName = "manifold-3d";
      ManifoldModule = (await import(
        /* @vite-ignore */
        moduleName
      )).default;
    } catch {
      try {
        const cdnUrl = "https://cdn.jsdelivr.net/npm/manifold-3d@3.0.0/manifold.js";
        ManifoldModule = (await import(
          /* @vite-ignore */
          cdnUrl
        )).default;
      } catch (cdnError) {
        throw new Error(
          `Failed to load manifold-3d: not available in Node.js or via CDN. ${cdnError}`
        );
      }
    }
    manifoldInstance = await ManifoldModule();
    manifoldInstance.setup();
  }
  return manifoldInstance;
};

// lib/createCopperCutFillForLayer.ts
var polygonToContours = (polygon) => {
  const contours = [];
  if (polygon instanceof Box4) {
    const { xmin, ymin, xmax, ymax } = polygon;
    contours.push([
      [xmin, ymin],
      [xmax, ymin],
      [xmax, ymax],
      [xmin, ymax]
    ]);
  } else {
    for (const face of polygon.faces) {
      const contour = [];
      for (const edge of face) {
        contour.push([edge.start.x, edge.start.y]);
      }
      if (contour.length >= 3) {
        contours.push(contour);
      }
    }
  }
  return contours;
};
var contourToPolygon = (contour) => {
  if (contour.length < 3) return null;
  const points = contour.map(([x, y]) => new Point2(x, y));
  try {
    const polygon = new Polygon5(points);
    if (polygon.faces.size > 0) {
      return polygon;
    }
  } catch {
  }
  return null;
};
var createCopperCutFillForLayer = async ({
  layer,
  ctx
}) => {
  const {
    project,
    connMap,
    topCutNetGeoms,
    bottomCutNetGeoms,
    topCopperCutFillCutSetting,
    bottomCopperCutFillCutSetting,
    copperCutFillMargin,
    boardOutlineContour
  } = ctx;
  const cutSetting = layer === "top" ? topCopperCutFillCutSetting : bottomCopperCutFillCutSetting;
  if (!cutSetting) {
    return;
  }
  const netGeomMap = layer === "top" ? topCutNetGeoms : bottomCutNetGeoms;
  const allGeoms = [];
  for (const net of Object.keys(connMap.netMap)) {
    const netGeoms = netGeomMap.get(net);
    if (netGeoms && netGeoms.length > 0) {
      allGeoms.push(...netGeoms);
    }
  }
  if (allGeoms.length === 0) {
    return;
  }
  try {
    const manifold = await getManifold();
    const { CrossSection } = manifold;
    const allContours = [];
    for (const geom of allGeoms) {
      const contours = polygonToContours(geom);
      allContours.push(...contours);
    }
    if (allContours.length === 0) {
      return;
    }
    const copperInside = new CrossSection(allContours, "Positive");
    const outerBoundary = copperInside.offset(
      copperCutFillMargin,
      "Round",
      // joinType for smooth corners
      2,
      // miterLimit
      32
      // circularSegments for round corners
    );
    let cutFillArea = outerBoundary.subtract(copperInside);
    if (boardOutlineContour && boardOutlineContour.length >= 3) {
      const boardOutline = new CrossSection([boardOutlineContour], "Positive");
      const clippedArea = cutFillArea.intersect(boardOutline);
      cutFillArea.delete();
      cutFillArea = clippedArea;
      boardOutline.delete();
    }
    const simplifiedArea = cutFillArea.simplify(1e-3);
    const resultContours = simplifiedArea.toPolygons();
    if (resultContours.length === 0) {
      copperInside.delete();
      outerBoundary.delete();
      cutFillArea.delete();
      simplifiedArea.delete();
      return;
    }
    const shapeGroup = new ShapeGroup();
    for (const contour of resultContours) {
      const polygon = contourToPolygon(contour);
      if (!polygon) continue;
      for (const island of polygon.splitToIslands()) {
        const { verts, prims } = polygonToShapePathData(island);
        if (verts.length > 0) {
          shapeGroup.children.push(
            new ShapePath28({
              cutIndex: cutSetting.index,
              verts,
              prims,
              isClosed: true
              // Filled shapes should be closed
            })
          );
        }
      }
    }
    if (shapeGroup.children.length > 0) {
      project.children.push(shapeGroup);
    }
    copperInside.delete();
    outerBoundary.delete();
    cutFillArea.delete();
    simplifiedArea.delete();
  } catch (error) {
    console.warn(`Failed to create copper cut fill for ${layer} layer:`, error);
  }
};

// lib/index.ts
var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
  const db = cju2(circuitJson);
  const project = new LightBurnProject({
    appVersion: "1.7.03",
    formatVersion: "1"
  });
  const includeLayers = options.includeLayers ?? ["top", "bottom"];
  const traceMargin = options.traceMargin ?? 0;
  const laserSpotSize = options.laserSpotSize ?? 5e-3;
  const includeCopper = options.includeCopper ?? true;
  const includeSoldermask = options.includeSoldermask ?? false;
  const globalCopperSoldermaskMarginAdjustment = options.globalCopperSoldermaskMarginAdjustment ?? 0;
  const solderMaskMarginPercent = options.solderMaskMarginPercent ?? 0;
  const laserProfile = options.laserProfile;
  const includeCopperCutFill = options.includeCopperCutFill ?? false;
  const copperCutFillMargin = options.copperCutFillMargin ?? 0.5;
  const defaultCopperSettings = {
    speed: 300,
    numPasses: 100,
    frequency: 2e4,
    pulseWidth: 1e-9
  };
  const defaultBoardSettings = {
    speed: 20,
    numPasses: 100,
    frequency: 2e4,
    pulseWidth: 1e-9
  };
  const copperSettings = { ...defaultCopperSettings, ...laserProfile?.copper };
  const boardSettings = { ...defaultBoardSettings, ...laserProfile?.board };
  const shouldGenerateTraceClearanceZones = traceMargin > 0 && includeCopper;
  if (traceMargin > 0 && !includeCopper) {
    throw new Error("traceMargin requires includeCopper to be true");
  }
  const topCopperCutSetting = new CutSetting({
    index: 0,
    name: "Cut Top Copper",
    numPasses: copperSettings.numPasses,
    speed: copperSettings.speed,
    frequency: copperSettings.frequency,
    pulseWidth: copperSettings.pulseWidth
  });
  project.children.push(topCopperCutSetting);
  const bottomCopperCutSetting = new CutSetting({
    index: 1,
    name: "Cut Bottom Copper",
    numPasses: copperSettings.numPasses,
    speed: copperSettings.speed,
    frequency: copperSettings.frequency,
    pulseWidth: copperSettings.pulseWidth
  });
  project.children.push(bottomCopperCutSetting);
  const throughBoardCutSetting = new CutSetting({
    index: 2,
    name: "Cut Through Board",
    numPasses: boardSettings.numPasses,
    speed: boardSettings.speed,
    frequency: boardSettings.frequency,
    pulseWidth: boardSettings.pulseWidth
  });
  project.children.push(throughBoardCutSetting);
  const soldermaskCutSetting = new CutSetting({
    type: "Scan",
    index: 3,
    name: "Cut Soldermask",
    numPasses: 1,
    speed: 150,
    scanOpt: "individual",
    interval: 0.18,
    angle: 45,
    crossHatch: true
  });
  project.children.push(soldermaskCutSetting);
  let nextCutIndex = 4;
  let topTraceClearanceAreaCutSetting;
  let bottomTraceClearanceAreaCutSetting;
  if (shouldGenerateTraceClearanceZones) {
    if (includeLayers.includes("top")) {
      topTraceClearanceAreaCutSetting = new CutSetting({
        type: "Scan",
        index: nextCutIndex++,
        name: "Clear Top Trace Clearance Areas",
        numPasses: 12,
        speed: 100,
        scanOpt: "individual",
        interval: laserSpotSize,
        angle: 45,
        crossHatch: true
      });
      project.children.push(topTraceClearanceAreaCutSetting);
    }
    if (includeLayers.includes("bottom")) {
      bottomTraceClearanceAreaCutSetting = new CutSetting({
        type: "Scan",
        index: nextCutIndex++,
        name: "Clear Bottom Trace Clearance Areas",
        numPasses: 12,
        speed: 100,
        scanOpt: "individual",
        interval: laserSpotSize,
        angle: 45,
        crossHatch: true
      });
      project.children.push(bottomTraceClearanceAreaCutSetting);
    }
  }
  let topCopperCutFillCutSetting;
  let bottomCopperCutFillCutSetting;
  if (includeCopperCutFill && includeCopper) {
    if (includeLayers.includes("top")) {
      topCopperCutFillCutSetting = new CutSetting({
        type: "Scan",
        index: nextCutIndex++,
        name: "Top Copper Cut Fill",
        numPasses: copperSettings.numPasses,
        speed: copperSettings.speed,
        scanOpt: "individual",
        interval: laserSpotSize,
        angle: 45,
        crossHatch: true
      });
      project.children.push(topCopperCutFillCutSetting);
    }
    if (includeLayers.includes("bottom")) {
      bottomCopperCutFillCutSetting = new CutSetting({
        type: "Scan",
        index: nextCutIndex++,
        name: "Bottom Copper Cut Fill",
        numPasses: copperSettings.numPasses,
        speed: copperSettings.speed,
        scanOpt: "individual",
        interval: laserSpotSize,
        angle: 45,
        crossHatch: true
      });
      project.children.push(bottomCopperCutFillCutSetting);
    }
  }
  const connMap = getFullConnectivityMapFromCircuitJson(circuitJson);
  let origin = options.origin;
  if (!origin) {
    const bounds = calculateCircuitBounds(circuitJson);
    origin = calculateOriginFromBounds(bounds, options.margin);
  }
  const ctx = {
    db,
    project,
    topCopperCutSetting,
    bottomCopperCutSetting,
    throughBoardCutSetting,
    soldermaskCutSetting,
    connMap,
    topCutNetGeoms: /* @__PURE__ */ new Map(),
    bottomCutNetGeoms: /* @__PURE__ */ new Map(),
    topScanNetGeoms: /* @__PURE__ */ new Map(),
    bottomScanNetGeoms: /* @__PURE__ */ new Map(),
    origin,
    includeCopper,
    includeSoldermask,
    globalCopperSoldermaskMarginAdjustment,
    includeLayers,
    traceMargin,
    laserSpotSize,
    topTraceClearanceAreaCutSetting,
    bottomTraceClearanceAreaCutSetting,
    solderMaskMarginPercent,
    topCopperCutFillCutSetting,
    bottomCopperCutFillCutSetting,
    copperCutFillMargin,
    topTraceEndpoints: /* @__PURE__ */ new Set(),
    bottomTraceEndpoints: /* @__PURE__ */ new Set()
  };
  for (const net of Object.keys(connMap.netMap)) {
    ctx.topCutNetGeoms.set(net, []);
    ctx.bottomCutNetGeoms.set(net, []);
    ctx.topScanNetGeoms.set(net, []);
    ctx.bottomScanNetGeoms.set(net, []);
  }
  for (const board of db.pcb_board.list()) {
    if (board.outline?.length) {
      ctx.boardOutlineContour = board.outline.map((outlinePoint) => [
        outlinePoint.x + origin.x,
        outlinePoint.y + origin.y
      ]);
    } else if (typeof board.width === "number" && typeof board.height === "number" && board.center) {
      const halfWidth = board.width / 2;
      const halfHeight = board.height / 2;
      const minX = board.center.x - halfWidth + origin.x;
      const minY = board.center.y - halfHeight + origin.y;
      const maxX = board.center.x + halfWidth + origin.x;
      const maxY = board.center.y + halfHeight + origin.y;
      ctx.boardOutlineContour = [
        [minX, minY],
        [maxX, minY],
        [maxX, maxY],
        [minX, maxY]
      ];
    }
    break;
  }
  for (const smtpad of db.pcb_smtpad.list()) {
    addSmtPad(smtpad, ctx);
  }
  for (const platedHole of db.pcb_plated_hole.list()) {
    addPlatedHole(platedHole, ctx);
  }
  for (const trace of db.pcb_trace.list()) {
    addPcbTrace(trace, ctx);
  }
  for (const board of db.pcb_board.list()) {
    addPcbBoard(board, ctx);
  }
  for (const via of db.pcb_via.list()) {
    addPcbVia(via, ctx);
  }
  for (const hole of db.pcb_hole.list()) {
    addPcbHole(hole, ctx);
  }
  for (const cutout of db.pcb_cutout.list()) {
    addPcbCutout(cutout, ctx);
  }
  if (includeCopper) {
    if (includeLayers.includes("top")) {
      createCopperShapesForLayer({ layer: "top", ctx });
    }
    if (includeLayers.includes("bottom")) {
      createCopperShapesForLayer({ layer: "bottom", ctx });
    }
  }
  if (shouldGenerateTraceClearanceZones) {
    if (includeLayers.includes("top")) {
      createTraceClearanceAreasForLayer({ layer: "top", ctx });
    }
    if (includeLayers.includes("bottom")) {
      createTraceClearanceAreasForLayer({ layer: "bottom", ctx });
    }
  }
  if (includeCopperCutFill && includeCopper) {
    if (includeLayers.includes("top")) {
      await createCopperCutFillForLayer({ layer: "top", ctx });
    }
    if (includeLayers.includes("bottom")) {
      await createCopperCutFillForLayer({ layer: "bottom", ctx });
    }
  }
  return project;
};
export {
  convertCircuitJsonToLbrn
};
