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/export-router.py
"""Export actual pad geometry to DSN without footprint deduplication or layer mirroring."""
from pathlib import Path
import json,math,collections
c=json.loads(Path('dist/index/circuit.json').read_text())
root={}
def find(x):
root.setdefault(x,x)
if root[x]!=x:root[x]=find(root[x])
return root[x]
def union(a,b):root[find(a)]=find(b)
for t in c:
if t['type']=='source_trace':
ids=t.get('connected_source_port_ids',[])+t.get('connected_source_net_ids',[])
for x in ids[1:]:union(ids[0],x)
names={find(n['source_net_id']):n['name'] for n in c if n['type']=='source_net'}
ports={p['pcb_port_id']:p for p in c if p['type']=='pcb_port'}
nets=collections.defaultdict(list);images=[];stacks=[];placements=[];mapping={};count=0
layers=['top',*[f'inner{i}' for i in range(1,next(x for x in c if x['type']=='pcb_board')['num_layers']-1)],'bottom']
q=lambda s:json.dumps(str(s))
u=lambda x:round(float(x)*1000,4)
for pad in c:
if pad['type'] not in ['pcb_smtpad','pcb_plated_hole','pcb_hole']:continue
count+=1;ident=f'P{count}';stack=f'S{count}'
layernames=[pad['layer']] if pad['type']=='pcb_smtpad' else layers
# Every physical pad has its own uniquely named image and DSN pin.
if pad['type']=='pcb_hole':shape=pad.get('hole_shape','circle');width=height=pad.get('hole_diameter',1)
else:
shape=pad['shape'];width=pad.get('width',pad.get('outer_width',pad.get('outer_diameter',pad.get('radius',0)*2)));height=pad.get('height',pad.get('outer_height',width))
if shape=='circular_hole_with_rect_pad':width=pad['rect_pad_width'];height=pad['rect_pad_height']
assert width>0 and height>0,(ident,pad)
angle=math.radians(pad.get('ccw_rotation',pad.get('rect_ccw_rotation',0)));pts=[]
if shape!='circle':
for x,y in [(-width/2,-height/2),(width/2,-height/2),(width/2,height/2),(-width/2,height/2)]:pts += [u(x*math.cos(angle)-y*math.sin(angle)),u(x*math.sin(angle)+y*math.cos(angle))]
shapes=[]
for layer in layernames:
geom=f'(circle {layer} {u(width)})' if shape=='circle' else f'(polygon {layer} 0 '+ ' '.join(map(str,pts))+')'
shapes.append(f'(shape {geom})')
stacks.append(f'(padstack {stack} '+ ' '.join(shapes)+' (attach off))')
images.append(f'(image {ident} (pin {stack} 1 0 0))')
placements.append(f'(component {ident} (place {ident} {u(pad["x"])} {u(pad["y"])} front 0))')
port=ports.get(pad.get('pcb_port_id'))
if port:
key=find(port['source_port_id']);net=names.get(key,'N_'+key)
nets[net].append(ident+'-1');mapping[ident]={'pcb_port_id':port['pcb_port_id'],'source_port_id':port['source_port_id'],'net':net}
else:nets['NC_'+ident].append(ident+'-1')
board=next(e for e in c if e['type']=='pcb_board');w=u(board['width']/2);h=u(board['height']/2)
via='VIA_300_150';stacks.append(f'(padstack {via} '+' '.join(f'(shape (circle {l} 300))' for l in layers)+' (attach off))')
stacks.append('(padstack VIA_THERMAL_400_200 '+' '.join(f'(shape (circle {l} 400))' for l in layers)+' (attach on))')
structure=' '.join(f'(layer {l} (type {"power" if l in ["inner1","inner6"] else "signal"}) (property (index {i})))' for i,l in enumerate(layers))
structure+=' '.join(f' (plane "GND" (rect {l} {-w+400} {-h+400} {w-400} {h-400}))' for l in ['inner1','inner6'] if l in layers)
structure+=f' (control (via_at_smd on)) (boundary (rect pcb {-w} {-h} {w} {h})) (via {via}) (rule (width 100) (clearance 100))'
network=' '.join(f'(net {q(net)} (pins '+ ' '.join(pins)+'))' for net,pins in nets.items())
network+=' (class default '+' '.join(q(n) for n in nets)+f' (circuit (use_via {via})) (rule (width 100) (clearance 100)))'
network+=' (class PLANE_FANOUT '+ ' '.join(q(n) for n in ['GND','DDR_1V5','VDD_CORE','VDD_MPU','V1V8','V3V3','A3V3'])+f' (circuit (use_via {via})) (rule (width 100) (clearance 100)))'
dsn='(pcb am3352 (parser (string_quote ") (space_in_quoted_tokens on)) (resolution um 1) (unit um)\n'
dsn+='(structure '+structure+')\n(placement '+'\n'.join(placements)+')\n(library '+'\n'.join(images+stacks)+')\n(network '+network+')\n(wiring))'
Path('../../work/compact-plane.dsn').write_text(dsn)
Path('../../work/router-pad-map.json').write_text(json.dumps(mapping,indent=2))
print('Exported',count,'physical pads/holes;',len([n for n in nets.values() if len(n)>1]),'connected nets')