imrishabh18/corne-keyboard

The code defines and renders two surface-mount chip components with SMT pads, silkscreen outlines, and 3D CAD models (OBJ and STEP) for PCB assembly.

Version
2.0.21
License
unset
Stars
1

scripts/export-assembly.py

"""Export Gerbers and honor pcb_component.do_not_place in assembly CSVs.

The pinned CLI omits DNP filtering. Keep every fabrication-layer byte intact,
remove DNP entries from BOM and PnP only, then verify the resulting populations.
"""
import argparse,csv,io,json,subprocess,tempfile,zipfile,hashlib
from pathlib import Path

def filter_assembly_zip(raw_zip, output_zip, circuit):
    sources={r['source_component_id']:r for r in circuit if r['type']=='source_component'}
    components=[r for r in circuit if r['type']=='pcb_component']
    excluded={sources[r['source_component_id']]['name'] for r in components if r.get('do_not_place')}
    populated={sources[r['source_component_id']]['name'] for r in components if not r.get('do_not_place')}
    summary={'excluded_designators':sorted(excluded),'populated_count':len(populated),'csv':{},'fabrication_files_unchanged':[]}
    with zipfile.ZipFile(raw_zip) as src, zipfile.ZipFile(output_zip,'w',zipfile.ZIP_DEFLATED) as dst:
        for entry in src.infolist():
            data=src.read(entry.filename)
            if entry.filename in ['bom.csv','pick_and_place.csv']:
                reader=csv.DictReader(io.StringIO(data.decode('utf-8-sig')))
                rows=list(reader); fields=reader.fieldnames
                if not fields or 'Designator' not in fields: raise ValueError('Unexpected CSV schema: '+entry.filename)
                kept=[r for r in rows if r['Designator'] not in excluded]
                names=[r['Designator'] for r in kept]
                if len(names)!=len(set(names)) or set(names)!=populated: raise ValueError('Assembly population mismatch: '+entry.filename)
                buffer=io.StringIO(newline='');writer=csv.DictWriter(buffer,fieldnames=fields);writer.writeheader();writer.writerows(kept)
                data=buffer.getvalue().encode()
                summary['csv'][entry.filename]={'rows_before':len(rows),'rows_after':len(kept),'removed':len(rows)-len(kept)}
            else:
                summary['fabrication_files_unchanged'].append({'file':entry.filename,'sha256':hashlib.sha256(data).hexdigest()})
            dst.writestr(entry.filename,data)
    if set(summary['csv'])!={'bom.csv','pick_and_place.csv'}: raise ValueError('Missing assembly CSV')
    return summary

if __name__=='__main__':
    parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('input');parser.add_argument('output');args=parser.parse_args()
    project=Path(__file__).resolve().parent.parent;input_path=Path(args.input).resolve();output_path=Path(args.output).resolve();output_path.parent.mkdir(parents=True,exist_ok=True)
    circuit=json.loads(input_path.read_text())
    if any(r['type']=='pcb_panel' for r in circuit):
        raise ValueError('The pinned Gerber exporter emits only the outer panel rectangle, omitting individual board outlines. Export the two halves separately until panel outlines and breakaway features are implemented.')
    with tempfile.TemporaryDirectory(prefix='corne-assembly-') as scratch:
        raw=Path(scratch)/'raw.zip'
        subprocess.run(['bun',str(project/'node_modules/tscircuit/cli.mjs'),'export',str(input_path),'-f','gerbers','-o',str(raw)],check=True,cwd=project)
        summary=filter_assembly_zip(raw,output_path,circuit)
    summary['input_sha256']=hashlib.sha256(input_path.read_bytes()).hexdigest()
    output_path.with_suffix('.assembly-verification.json').write_text(json.dumps(summary,indent=2)+'\n')
    print(f"Excluded {len(summary['excluded_designators'])} DNP components; kept {summary['populated_count']} populated components. Gerber/drill files unchanged.")