main
John Lauer Three revisions with per-experiment write-ups, inline 3D viewers, sources, fab files, open-science scorecard 091a7c9 12d ago
#!/usr/bin/env python3
# Build a real BOM and pick-and-place from circuit.json (manufacturer part numbers included) and
# splice them into a gerber zip, replacing the empty bom.csv / pick_and_place.csv stubs that
# `tsci export --format gerbers` writes. Usage: python3 scripts/make-bom.py . path/to/gerbers.zip
import json,sys,csv,collections,zipfile,shutil,re
proj,zipf=sys.argv[1],sys.argv[2]
c=json.load(open(f'{proj}/dist/lib/index/circuit.json'))
LCSC={'TYPE-C-31-M-12':'C165948','IN-S63BT5UW':'','CR0402-FX-4700GLF':'','CR0402-FX-4701GLF':'','CL21A106KOQNNNG':'','0402B104J160CT':''}
pcb={p['source_component_id']:p for p in c if p['type']=='pcb_component'}
rows=collections.OrderedDict(); cpl=[]
def key(s):
    ft=s.get('ftype'); mpn=s.get('manufacturer_part_number') or ''
    val={'simple_resistor':s.get('display_resistance') or str(s.get('resistance','')),'simple_capacitor':s.get('display_capacitance') or str(s.get('capacitance','')),'simple_led':(s.get('color') or '')+' LED'}.get(ft, s.get('name'))
    fp={'simple_resistor':'0402','simple_capacitor':'0805' if 'CL21' in mpn else '0402','simple_led':'0603'}.get(ft,'')
    return (ft,val,fp,mpn)
for s in c:
    if s['type']!='source_component' or s['name'].startswith('MP'): continue
    k=key(s); rows.setdefault(k,[]).append(s['name'])
    p=pcb.get(s['source_component_id'])
    if p: cpl.append([s['name'],f"{p['center']['x']:.3f}",f"{p['center']['y']:.3f}",'top',f"{p.get('rotation',0):g}"])
nat=lambda n:[int(t) if t.isdigit() else t for t in re.split(r'(\d+)',n)]
with open(f'{proj}/bom.csv','w',newline='') as f:
    w=csv.writer(f); w.writerow(['Designator','Qty','Value','Footprint','Manufacturer Part Number','LCSC'])
    for (ft,val,fp,mpn),names in rows.items():
        names.sort(key=nat); w.writerow([' '.join(names),len(names),val,fp,mpn or '(generic, not pinned)',LCSC.get(mpn,'')])
with open(f'{proj}/pick_and_place.csv','w',newline='') as f:
    w=csv.writer(f); w.writerow(['Designator','Mid X (mm)','Mid Y (mm)','Layer','Rotation']); [w.writerow(r) for r in sorted(cpl,key=lambda r:nat(r[0]))]
tmp=zipf+'.tmp'
with zipfile.ZipFile(zipf) as zin, zipfile.ZipFile(tmp,'w',zipfile.ZIP_DEFLATED) as zout:
    for n in zin.namelist():
        if n not in('bom.csv','pick_and_place.csv'): zout.writestr(n,zin.read(n))
    zout.write(f'{proj}/bom.csv','bom.csv'); zout.write(f'{proj}/pick_and_place.csv','pick_and_place.csv')
shutil.move(tmp,zipf); print(open(f'{proj}/bom.csv').read())