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.")