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