#!/usr/bin/env python3 """Build ONE per-part Excel sheet from the master template. The new Vecmocon layout stores each component on its own: a folder ``MPN_make_typeid/`` holding that part's own single-sheet workbook (plus its datasheet, symbol and footprint). There is no accumulating master workbook any more - so this script takes one part and writes ``__.xlsx``, a copy of just that typeid's template sheet (headers, styling, widths, freeze preserved) with a single filled data row. What gets stamped automatically (never read from a datasheet): - column A ``MPN_make_type`` = the part tag ``__`` - column B ``Skill Version`` = this typeid's skill version (vN, from versions.json) - column C ``Template Version``= this typeid's template version (vN, from versions.json) The four design columns (Library Ref/Path, Footprint Ref/Path) are filled from a --design map when the symbol/footprint step provides them, and left blank otherwise. Everything else comes from the extracted ``values``. # one part, values only (design columns left blank for now) python fill_templates.py part.json --template assets/template/template.xlsx --dest out/ # later, same part with the Altium-derived design fields merged in python fill_templates.py part.json --template ... --dest out/ --design design.json ``part.json`` is either a single part object or {"parts": [ ... ]} (each part built to its own file). A part = {"mpn","manufacturer" (or "make"),"typeid","values":{header:value,...}}. """ import argparse, json, os import openpyxl from common import (norm, part_tag, version_labels, COL_TAG, COL_SKILL_VER, COL_TEMPLATE_VER, DESIGN_COLS) def template_headers(template_path, typeid): """Canonical headers for a typeid sheet, or None if the template has no such sheet.""" wb = openpyxl.load_workbook(template_path, read_only=True) if typeid not in wb.sheetnames: return None ws = wb[typeid] return [ws.cell(1, c).value for c in range(1, ws.max_column + 1)] def part_to_row(part, headers, design=None): """Map one part to {header: value}, following the fixed-column rules above.""" vals = {norm(k): v for k, v in part.get("values", {}).items()} dmap = {norm(k): v for k, v in (design or {}).items()} tag = part_tag(part) sver, tver = version_labels(part.get("typeid", "NA")) row = {} for h in headers: key = norm(h) if key == norm(COL_TAG): row[h] = tag elif key == norm(COL_SKILL_VER): row[h] = sver elif key == norm(COL_TEMPLATE_VER): row[h] = tver elif key == norm("Manufacturer"): row[h] = part.get("manufacturer", vals.get("manufacturer", "")) elif h in DESIGN_COLS: row[h] = dmap.get(key, vals.get(key, "")) else: row[h] = vals.get(key, "") return tag, row def build_part_sheet(part, template_path, dest_dir, design=None): """Write ``.xlsx`` = just this part's typeid sheet with one filled row. Returns (path, tag). Reused for backfill: re-extract a part and call this again to regenerate its sheet against the current template (new columns) and current stamped versions.""" typeid = part["typeid"] headers = template_headers(template_path, typeid) if headers is None: raise SystemExit(f"no template sheet for typeid '{typeid}'") wb = openpyxl.load_workbook(template_path) for name in list(wb.sheetnames): # keep only this typeid's sheet if name != typeid: del wb[name] ws = wb[typeid] tag, row = part_to_row(part, headers, design) for r in range(2, ws.max_row + 1): # clear any stray rows below the header for c in range(1, len(headers) + 1): ws.cell(r, c).value = None for c, h in enumerate(headers, start=1): ws.cell(2, c, row.get(h, "")) os.makedirs(dest_dir, exist_ok=True) out = os.path.join(dest_dir, f"{tag}.xlsx") wb.save(out) return out, tag def _iter_parts(data): return data["parts"] if isinstance(data, dict) and "parts" in data else [data] def main(): ap = argparse.ArgumentParser() ap.add_argument("part_json") ap.add_argument("--template", required=True) ap.add_argument("--dest", required=True) ap.add_argument("--design", default=None, help="JSON map of design columns to merge in") a = ap.parse_args() data = json.load(open(a.part_json, encoding="utf-8")) design = json.load(open(a.design, encoding="utf-8")) if a.design else None for p in _iter_parts(data): out, tag = build_part_sheet(p, a.template, a.dest, design) sver, tver = version_labels(p.get("typeid", "NA")) print(f"wrote {out} (tag {tag}, skill {sver}, template {tver})") if __name__ == "__main__": main()