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

via-clearance.ts

import type { SimpleRouteJson, SimplifiedPcbTrace } from "@tscircuit/capacity-autorouter";
import { pointToBoxDistance, pointToSegmentDistance, segmentToBoxMinDistance, segmentToSegmentMinDistance } from "@tscircuit/math-utils";

type Point = {x:number;y:number};
type Segment = {a:Point;b:Point;width:number;layer:string;net:string;trace:SimplifiedPcbTrace};
type Via = Point & {diameter:number;net:string;key:string;fixed:boolean};
const same = (a:Point,b:Point) => Math.hypot(a.x-b.x,a.y-b.y)<1e-6;
const positionKey = (p:Point) => `${p.x.toFixed(6)},${p.y.toFixed(6)}`;

/** Legalize generated through-vias against exact copper geometry. Candidate
 * locations and connected wire endpoints are solved together; anchored vias
 * and the clock phase are fixed. No board coordinates are prescribed here. */
export function repairViaClearances(traces:SimplifiedPcbTrace[], input:SimpleRouteJson) {
  const fixed = input.traces ?? [];
  const fixedIds = new Set(fixed.map(t=>t.pcb_trace_id));
  const all = [...fixed, ...traces.filter(t=>!fixedIds.has(t.pcb_trace_id))];
  const netName = (t:SimplifiedPcbTrace) => input.connections
    .filter(c=>t.connection_name===c.name || t.connection_name.startsWith(`${c.name}_`))
    .sort((a,b)=>b.name.length-a.name.length)[0]?.name ?? t.connection_name;
  const clearance = Math.max(0.09,input.minTraceToPadEdgeClearance ?? 0.075,input.minViaEdgeToPadEdgeClearance ?? 0.075)+0.005;
  // JLCPCB requires 0.20 mm from drill edge to other-net copper. Use
  // 0.205 mm to retain margin after Gerber coordinate quantization.
  const holeRadius = (input.minViaHoleDiameter ?? 0.15) / 2;
  const drillClearance = 0.205;
  const viaCopperRadius = (v:Via) => Math.max(v.diameter / 2 + clearance, holeRadius + drillClearance);
  const anchors = input.connections.flatMap(c=>c.pointsToConnect);
  const outline = input.outline;
  const getGeometry = () => {
    const segments:Segment[]=[];
    const vias=new Map<string,Via>();
    for(const trace of all){
      const net=netName(trace);
      for(let i=0;i<trace.route.length;i++){
        const a=trace.route[i],b=trace.route[i+1];
        if(a.route_type==="via"){
          const key=`${net}:${positionKey(a)}`;
          vias.set(key,{x:a.x,y:a.y,key,net,diameter:a.via_diameter ?? input.minViaPadDiameter ?? 0.3,fixed:fixedIds.has(trace.pcb_trace_id)||anchors.some(p=>same(p,a))});
        }
        if(a.route_type==="wire"&&b?.route_type==="wire"&&a.layer===b.layer&&!same(a,b))
          segments.push({a,b,width:Math.max(a.width,b.width),layer:a.layer,net,trace});
      }
    }
    return {segments,vias:[...vias.values()]};
  };
  const sharesNet = (o:SimpleRouteJson["obstacles"][number],net:string) => o.connectedTo.includes(net);
  const inBoard = (p:Point,radius:number) => {
    if(!outline?.length)return p.x>input.bounds.minX+radius&&p.x<input.bounds.maxX-radius&&p.y>input.bounds.minY+radius&&p.y<input.bounds.maxY-radius;
    let inside=false;
    for(let i=0,j=outline.length-1;i<outline.length;j=i++){
      const a=outline[i],b=outline[j];
      if((a.y>p.y)!==(b.y>p.y)&&p.x<(b.x-a.x)*(p.y-a.y)/(b.y-a.y)+a.x)inside=!inside;
      if(pointToSegmentDistance(p,a,b)<radius+(input.minBoardEdgeClearance??0.3))return false;
    }
    return inside;
  };
  // Preserve the capacity router's 45/90-degree paths. Replacing these
  // bends with direct endpoint chords creates long arbitrary-angle routes.
  // Only move vias when required by the exact copper/drill-clearance checks.
  let moved=0;
  for(let pass=0;pass<3;pass++){
    let changed=false;
    for(const original of getGeometry().vias){
      if(original.fixed)continue;
      const {segments,vias}=getGeometry();
      const affected=segments.filter(s=>s.net===original.net&&(same(s.a,original)||same(s.b,original))&&!fixedIds.has(s.trace.pcb_trace_id));
      const otherSegments=segments.filter(s=>s.net!==original.net);
      const otherVias=vias.filter(v=>v.net!==original.net);
      const otherPads=input.obstacles.filter(o=>!sharesNet(o,original.net));
      const viaIsClear=(p:Point) => inBoard(p,original.diameter/2)
        && otherPads.every(o=>pointToBoxDistance(p,o)>=viaCopperRadius(original)-1e-7)
        && otherVias.every(v=>Math.hypot(p.x-v.x,p.y-v.y)>=(original.diameter+v.diameter)/2+clearance-1e-7)
        && otherSegments.every(s=>pointToSegmentDistance(p,s.a,s.b)>=viaCopperRadius(original)+s.width/2-1e-7);
      if(viaIsClear(original))continue;
      const candidateIsClear=(p:Point) => {
        if(!viaIsClear(p))return false;
        for(const old of affected){
          const a=same(old.a,original)?p:old.a,b=same(old.b,original)?p:old.b;
          if(otherPads.some(o=>o.layers.includes(old.layer)&&segmentToBoxMinDistance(a,b,o)<old.width/2+clearance-1e-7))return false;
          if(otherSegments.some(s=>s.layer===old.layer&&segmentToSegmentMinDistance(a,b,s.a,s.b)<(old.width+s.width)/2+clearance-1e-7))return false;
          if(otherVias.some(v=>pointToSegmentDistance(v,a,b)<old.width/2+viaCopperRadius(v)-1e-7))return false;
        }
        return true;
      };
      let selected:Point|undefined;
      // Search locally, nearest displacement first, at 25 um resolution.
      for(let radius=0.025;radius<=2&&!selected;radius+=0.025){
        const count=Math.max(16,Math.ceil(2*Math.PI*radius/0.025));
        for(let i=0;i<count;i++){
          const angle=i*2*Math.PI/count;
          const candidate={x:original.x+radius*Math.cos(angle),y:original.y+radius*Math.sin(angle)};
          if(candidateIsClear(candidate)){selected=candidate;break;}
        }
      }
      if(!selected)continue;
      for(const trace of traces){
        if(netName(trace)!==original.net||fixedIds.has(trace.pcb_trace_id))continue;
        for(const point of trace.route)if((point.route_type==="wire"||point.route_type==="via")&&same(point,original)){
          point.x=selected.x;point.y=selected.y;
        }
      }
      moved++;changed=true;
    }
    if(!changed)break;
  }
  console.log(`Autorouter clearance repair: ${moved} generated vias moved`);
  return traces;
}