seveibar/am3352-dev-board
A 324-ball TI AM3352 ARM processor with DDR3, LCD, USB, Ethernet, storage, serial, analog, clock, reset, power, and JTAG interfaces, paired with configurable JST-GH board connectors and constrained PCB routing.
- Version
- 1.0.2
- License
- unset
- Stars
- 0
scripts/force-plane-fanout.py
"""Reserve remaining plane escapes; report pad-overlapping vias requiring fill/cap."""
import json,runpy,math
from pathlib import Path
from shapely.geometry import Point,LineString
from shapely.ops import unary_union
from shapely.prepared import prep
from shapely.strtree import STRtree
x=runpy.run_path('scripts/audit-copper.py');c=x['c'];d=json.loads(Path('design/routed-copper.json').read_text());meta=x['meta'];shapes=x['shapes'];tree=STRtree(shapes);removed=set();extras=[];report=[]
planes=['V1V8','V3V3','DDR_1V5','GND','VDD_CORE','VDD_MPU','A3V3'];byname={n['name']:n['source_net_id'] for n in c if n['type']=='source_net'}
values=sorted({a+b for a in [-1.6,-1.2,-.8,-.4,0,.4,.8,1.2,1.6] for b in [-.1,-.05,0,.05,.1]},key=abs)
for name in planes:
sid=byname[name];net=x['find'](sid);groups={}
for p in set(x['netports'][net]):groups.setdefault(x['component'](x['padnodes'][p][0]),[]).append(p)
if len(groups)<2:continue
main=max(groups,key=lambda k:len(groups[k]));hard=prep(unary_union([g for g,m in zip(shapes,meta) if m[1]!=net and (m[3] or m[2].endswith('_via'))]+[g for g,n,l,v in extras if n!=net or v]).buffer(.250001))
top={l:prep(unary_union([g for g,m in zip(shapes,meta) if m[0]==l and m[1]!=net and (m[3] or m[2].endswith('_via'))]+[g for g,n,el,v in extras if n!=net and el==l]).buffer(.150001)) for l in ['top','bottom']}
ownpads=unary_union([g for g,m in zip(shapes,meta) if m[1]==net and m[3]])
ownvias=[]
for i,m in enumerate(meta):
if m[1]==net and m[2].endswith('_via') and x['component'](i)==main:
v=tuple(shapes[i].centroid.coords[0])
if v not in ownvias:ownvias.append(v)
for group,ports in groups.items():
if group==main:continue
best=None
for p in ports:
port=x['ports'][p];px,py=port['x'],port['y'];layer=port['layers'][0]
candidates=[(vx,vy,False) for vx,vy in ownvias if math.dist((px,py),(vx,vy))<3]+[(round(px+dx,6),round(py+dy,6),True) for dx in values for dy in values]
for vx,vy,new in candidates:
v=Point(vx,vy)
if abs(vx)>34.5 or abs(vy)>29.5:continue
if hard.intersects(v) or (new and any(math.dist((vx,vy),o)<.4 for o in ownvias)):continue
if new and any(g.distance(v)<.250001 for g,n,l,isvia in extras if n!=net or isvia):continue
dx,dy=vx-px,vy-py;t=min(abs(dx),abs(dy));sx=1 if dx>=0 else -1;sy=1 if dy>=0 else -1
for pts in [[(px,py),(px+sx*t,py+sy*t),(vx,vy)],[(px,py),(vx-sx*t,vy-sy*t),(vx,vy)],[(px,py),(vx,py),(vx,vy)],[(px,py),(px,vy),(vx,vy)]]:
line=LineString(pts)
if top[layer].intersects(line):continue
blocked=set();topblocks=set();indices=set(map(int,tree.query(line.buffer(.150001))))
if new:indices.update(map(int,tree.query(v.buffer(.250001))))
for i in indices:
g,m=shapes[i],meta[i]
if m[1]==net or m[3] or m[2].endswith('_via') or m[2].startswith('pcb_copper_pour') or m[2] in removed:continue
if (m[0]==layer and g.distance(line)<.150001) or (new and g.distance(v)<.250001):
blocked.add(m[2])
if m[0] in ['top','bottom']:topblocks.add(m[2])
filled=new and ownpads.intersects(v.buffer(.15));score=(len(topblocks),int(filled),len(blocked),line.length)
if best is None or score<best[0]:best=(score,p,vx,vy,new,pts,blocked,filled)
if best is None:report.append({'net':name,'ports':ports,'status':'no pad-safe site'});print(name,'NO SITE',ports,flush=True);continue
score,p,vx,vy,new,pts,blocked,filled=best;removed.update(blocked);clean=[pts[0]]
for q in pts[1:]:
if math.dist(q,clean[-1])>1e-8:clean.append(q)
if len(clean)>1:d['nets'][sid].append([{'route_type':'wire','x':a,'y':b,'width':.1,'layer':layer} for a,b in clean]);extras.append((LineString(clean).buffer(.05),net,layer,False))
if new:
d['nets'][sid].append([{'route_type':'wire','x':vx,'y':vy,'width':.1,'layer':'top'},{'route_type':'via','x':vx,'y':vy,'from_layer':'top','to_layer':'bottom','via_diameter':.3,'via_hole_diameter':.15},{'route_type':'wire','x':vx,'y':vy,'width':.1,'layer':'bottom'}]);ownvias.append((vx,vy));extras.extend((Point(vx,vy).buffer(.15),net,l,True) for l in x['layers'])
report.append({'net':name,'port':p,'x':vx,'y':vy,'requiresFillAndCap':filled,'rerouteRecords':len(blocked),'status':'reserved'});print(name,p,'via',vx,vy,'filled',filled,'ripped',len(blocked),'score',score,flush=True)
for sid,rs in d['nets'].items():d['nets'][sid]=[r for i,r in enumerate(rs) if f'fr_{sid}_{i}' not in removed]
Path('design/routed-copper.json').write_text(json.dumps(d,separators=(',',':')));Path('design/plane-escape-report.json').write_text(json.dumps(report,indent=2));print('Reserved',sum(r['status']=='reserved' for r in report),'plane escapes; removed',len(removed),'traces',flush=True)