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.");
}