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;
}