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
bga-fanout.ts
import { FanoutSolver } from "./vendor/fanout-solver.js";
import type { FanoutBusSpec } from "@tscircuit/fanout-solver";
import type { SimpleRouteJson, TerminalViaHint } from "@tscircuit/capacity-autorouter";
/** Plan the enclosed lands of each BGA with FanoutSolver. The board router
* still routes every original electrical connection; no net is declared
* connected by an ideal plane. Outer balls use ordinary surface escape. */
export function planBgaTerminalEscapes(input: SimpleRouteJson) {
const plans = new Map<string, {x:number;y:number;layer:string;terminalVia:TerminalViaHint}>();
if (input.allowViaInPad !== true) return plans;
const groups = new Map<string, SimpleRouteJson["obstacles"]>();
for (const pad of input.obstacles) {
if (!pad.componentId || !pad.circuitJsonMetadata?.pcb_smtpad_id) continue;
const group = groups.get(pad.componentId) ?? [];
group.push(pad); groups.set(pad.componentId, group);
}
const connections:SimpleRouteJson["connections"] = [];
const buses:FanoutBusSpec[] = [];
const sourceByName = new Map<string, {padId:string;x:number;y:number}>();
const selectedPadIds = new Set<string>();
const counts:Record<string,number> = {};
for (const [componentId,pads] of groups) {
if (![9,20].includes(pads.length) || pads.some(p=>p.width>.35 || p.height>.35)) continue;
for (const pad of pads) {
const {x,y}=pad.center;
const row=pads.filter(p=>Math.abs(p.center.y-y)<.002);
const col=pads.filter(p=>Math.abs(p.center.x-x)<.002);
if (!row.some(p=>p.center.x<x-.01) || !row.some(p=>p.center.x>x+.01) ||
!col.some(p=>p.center.y<y-.01) || !col.some(p=>p.center.y>y+.01)) continue;
const original=input.connections.find(c=>c.pointsToConnect.some(p=>Math.hypot(p.x-x,p.y-y)<.001));
const point=original?.pointsToConnect.find(p=>Math.hypot(p.x-x,p.y-y)<.001);
if (!original || !point) continue;
const padId=pad.circuitJsonMetadata!.pcb_smtpad_id!;
const name=`${original.name}__fanout_${padId}`;
connections.push({...original,name,netConnectionName:original.name,pointsToConnect:[point]});
buses.push({busId:name,connectionNames:[name],sourceComponentId:componentId,
direction:"down",termination:{type:"plane",layer:"bottom"}});
sourceByName.set(name,{padId,x,y}); selectedPadIds.add(padId);
counts[componentId]=(counts[componentId]??0)+1;
}
}
if (!connections.length) return plans;
const diameter=input.minViaPadDiameter ?? .3;
// The plated via annulus becomes part of the physical copper land. Reserve
// that larger envelope during fanout, including against neighboring balls.
// Dynamic pours are regenerated around the resulting physical through-vias.
const obstacles=input.obstacles.filter(p=>!p.isCopperPour).map(p=>
selectedPadIds.has(p.circuitJsonMetadata?.pcb_smtpad_id ?? "")
? {...p,width:Math.max(p.width,diameter),height:Math.max(p.height,diameter)} : p);
const solver=new FanoutSolver({...input,obstacles,connections,traces:[]},{
buses,traceWidth:input.minTraceWidth,viaDiameter:diameter,
viaHoleDiameter:input.minViaHoleDiameter ?? .15,
clearance:Math.max(input.minTraceToPadEdgeClearance ?? .075,input.minViaEdgeToPadEdgeClearance ?? .075),
allowBlindAndBuriedVias:false,
});
solver.solve();
if (!solver.solved) throw new Error(`BGA fanout failed: ${solver.error}`);
const output=solver.getOutput();
for (const trace of output.fanoutTraces) {
const source=sourceByName.get(trace.connection_name);
const via=trace.route.find(p=>p.route_type==="via");
if (!source || via?.route_type!=="via" || Math.hypot(via.x-source.x,via.y-source.y)>1e-6)
throw new Error("BGA fanout returned an escape that cannot be represented by a terminal-via hint");
plans.set(source.padId,{x:via.x,y:via.y,layer:via.to_layer,
terminalVia:{toLayer:via.from_layer,viaDiameter:via.via_diameter ?? diameter}});
}
if (plans.size!==connections.length) throw new Error("Incomplete BGA fanout");
console.log(`FanoutSolver: ${plans.size} interior escapes across ${Object.keys(counts).length} BGAs validated`);
return plans;
}