imrishabh18/pedometer

This code defines and assembles a simple radio receiver hardware circuit using specific imported capacitors, inductors, RF connectors, and oscillator components with precise footprints and schematic attributes.

Version
1.1.3
License
unset
Stars
0

scripts/check-no-via-in-pad.ts

import fs from "node:fs";
import crypto from "node:crypto";

/** Independent physical drill audit, including same-net pads. Generic DRC
 * permits same-net copper intersections and cannot establish this policy. */
export function auditBgaVias(circuit: any[]) {
  const sources = new Map(circuit.filter(x => x.type === "source_component").map(x => [x.source_component_id, x]));
  const components = circuit.filter(x => x.type === "pcb_component");
  const pads = circuit.filter(x => x.type === "pcb_smtpad");
  // The current BOM has two BGA families. Identify by MPN so renaming or moving
  // U2/U3 cannot silently remove them from the audit.
  const bgaComponents = components.filter(x => /^(BQ25150YFP|BQ27427YZF)/.test(sources.get(x.source_component_id)?.manufacturer_part_number ?? ""));
  const refs = new Map(bgaComponents.map(x => [x.pcb_component_id, sources.get(x.source_component_id).name]));
  const vias = [...new Map(circuit.filter(x => x.type === "pcb_via").map(x => [
    `${x.x.toFixed(6)},${x.y.toFixed(6)},${x.hole_diameter},${[...(x.layers ?? [x.from_layer, x.to_layer])].sort().join(",")}`, x,
  ])).values()];
  const violations: any[] = [];
  for (const pad of pads.filter(x => refs.has(x.pcb_component_id))) {
    if (pad.shape !== "circle") throw new Error(`Unexpected BGA land shape: ${pad.pcb_smtpad_id}`);
    for (const via of vias) {
      // Account for physical through-via layers, not just the trace transition.
      if (!(via.layers ?? [via.from_layer, via.to_layer]).includes(pad.layer)) continue;
      const copperGap = Math.hypot(via.x-pad.x, via.y-pad.y)-via.hole_diameter/2-pad.radius;
      const maskGap = copperGap-(pad.soldermask_margin ?? 0);
      if (Math.min(copperGap, maskGap) < -1e-6) violations.push({
        reference: refs.get(pad.pcb_component_id), pad: pad.port_hints,
        via: via.pcb_via_id, x: via.x, y: via.y,
        drillIntersectsCopperLand: copperGap < -1e-6,
        drillIntersectsMaskOpening: maskGap < -1e-6,
        drillToCopperGapMm: copperGap, drillToMaskGapMm: maskGap,
      });
    }
  }
  return {
    bgaReferences: [...refs.values()].sort(),
    bgaPadCount: pads.filter(x => refs.has(x.pcb_component_id)).length,
    viaInPadCount: new Set(violations.filter(x => x.drillIntersectsCopperLand).map(x => x.via)).size,
    viaInMaskOpeningCount: new Set(violations.filter(x => x.drillIntersectsMaskOpening).map(x => x.via)).size,
    violations,
  };
}

if (import.meta.main) {
  const input = process.argv[2] ?? "dist/index/circuit.json";
  const bytes = fs.readFileSync(input);
  const circuit = JSON.parse(bytes.toString());
  if (!circuit.some((x: any) => x.type === "pcb_trace")) throw new Error("No routed traces to audit");
  const result = auditBgaVias(circuit);
  const allowViaInPad=process.argv.includes("--allow-via-in-pad");
  const report = { input, policy:allowViaInPad?"via-in-pad permitted; filled and capped process required":"no-via-in-pad", circuitSha256: crypto.createHash("sha256").update(bytes).digest("hex"), ...result };
  if (process.argv[3]) fs.writeFileSync(process.argv[3], JSON.stringify(report,null,2)+"\n");
  console.log(`${result.bgaPadCount} BGA lands audited: ${result.viaInPadCount} drill/land overlaps, ${result.viaInMaskOpeningCount} drill/mask overlaps.`);
  for (const v of result.violations) console.error(`${v.reference} ${v.pad.join("/")}: ${v.via}`);
  if (result.violations.length && !allowViaInPad) {
    console.error("FABRICATION HOLD: the routed board does not meet the no-via-in-pad requirement.");
    process.exit(1);
  }
  console.log(allowViaInPad?"BGA via inventory recorded; filled and capped vias are permitted by the current design requirement.":"BGA no-via-in-pad check passed.");
}