#!/usr/bin/env python3 """Build (or append to) a `part_requests.json` manifest for the Altium 365 Part Request end task, from a component's schlib `params.json` plus its local file paths. The skill runs this at the end of a run, then commits the manifest + the component's `.SchLib`/`.PcbLib`/datasheet into the operator's Altium Runner inbox; the local runner picks it up and fills the Part Request. Usage: python build_part_request_manifest.py --params --typeid CER \ --file "C:/Altium Runner/inbox/JMK105BJ105KV-F.SchLib" \ --file "C:/Altium Runner/inbox/JMK105BJ105KV-F.PcbLib" \ --file "C:/Altium Runner/inbox/JMK105BJ105KV-F.pdf" \ --out "C:/Altium Runner/inbox/part_requests.json" `--component-type` overrides the typeid→type mapping; `--assignee` sets the librarian. """ import argparse, json, os # Vecmocon typeid -> Altium 365 Part Request "Component Type" (options captured live from the # form's dropdown). Edit as your Workspace's component-type list evolves; --component-type wins. TYPEID_TO_COMPONENT_TYPE = { "CER": "Capacitors", "ELE": "Capacitors", "TAN": "Capacitors", "PLY": "Capacitors", "FLM": "Capacitors", "SFY": "Capacitors", "SUP": "Capacitors", "FIX": "Resistors", "TFR": "Resistors", "MFR": "Resistors", "CFR": "Resistors", "MOR": "Resistors", "WWR": "Resistors", "SHT": "Resistors", "ARR": "Resistors", "POT": "Resistors", "FSR": "Resistors", "NTC": "Resistors", "PTC": "Resistors", "PWR": "Inductors", "FBD": "Inductors", "CMC": "Inductors", "RFI": "Inductors", "XFM": "Inductors", "CTX": "Inductors", "CPL": "Inductors", "REC": "Diodes", "FRD": "Diodes", "SCH": "Diodes", "SIC": "Diodes", "ZEN": "Diodes", "TVS": "Diodes", "ESD": "Diodes", "SWI": "Diodes", "BRG": "Diodes", "LED": "LED", "BJT": "Transistors", "MOS": "Transistors", "SCM": "Transistors", "GAN": "Transistors", "IGBT": "Transistors", "JFET": "Transistors", "DIG": "Transistors", "MCU": "Processors", "LDO": "Power Supply", "DCD": "Power Supply", "PMU": "Power Supply", "BMS": "Integrated Circuits", "DRV": "Drivers", "AMP": "Amplifiers", "CMP": "Amplifiers", "VRF": "Data Converters", "ADC": "Data Converters", "DAC": "Data Converters", "ISO": "Optoelectronics", "XCV": "Interface", "AFE": "Integrated Circuits", "MEM": "Memory", "LOG": "Logic", "SEN": "Sensors", "IFC": "Interface", "CLK": "Crystals & Oscillators", "SVR": "Power Supply", "MTR": "Integrated Circuits", "FUS": "Fuses", "RSF": "Fuses", "VAR": "Miscellaneous", "GDT": "Miscellaneous", "CBK": "Switches", "DCM": "Power Supply", "IDC": "Power Supply", "INV": "Power Supply", "OBC": "Power Supply", "CHG": "Power Supply", "PSU": "Power Supply", "RCM": "Power Supply", "RLS": "Relays", "RLP": "Relays", "SSR": "Relays", "RLR": "Relays", "CTC": "Relays", "SWT": "Switches", "PBT": "Switches", "DSW": "Switches", "RSW": "Switches", "RSY": "Switches", "CWB": "Connectors", "CBB": "Connectors", "HDR": "Connectors", "FFC": "Connectors", "USB": "Connectors", "PWC": "Connectors", "TBK": "Connectors", "ANC": "Radio&RF", "ANP": "Radio&RF", "ANE": "Radio&RF", "SAW": "Radio&RF", "RFM": "Radio&RF", "XTL": "Crystals & Oscillators", "OSC": "Crystals & Oscillators", "MMO": "Crystals & Oscillators", "RSN": "Crystals & Oscillators", "CLI": "Batteries", "CLF": "Batteries", "CCO": "Batteries", "CNI": "Batteries", "BPK": "Batteries", "CHL": "Batteries", "BUZ": "Audio", "PBZ": "Audio", "SPK": "Audio", "IND": "Optoelectronics", "DSG": "Optoelectronics", "OLE": "Optoelectronics", "LCD": "Optoelectronics", "TFT": "Optoelectronics", "STE": "Sensors", "SCU": "Sensors", "SVO": "Sensors", "SHA": "Sensors", "SIM": "Sensors", "SPR": "Sensors", "FAN": "Mechanicals", "HSK": "Mechanicals", "TPD": "Mechanicals", } def build_entry(params, typeid, component_type, files, assignee): p = params.get("parameters", {}) ctype = component_type or TYPEID_TO_COMPONENT_TYPE.get((typeid or "").upper(), "Miscellaneous") mpn = p.get("Manufacturer Part") or params.get("component") or "" return { "manufacturer": p.get("Manufacturer", ""), "mpn": mpn, "description": p.get("Description", ""), "component_type": ctype, "assignee": assignee or params.get("assignee", ""), "parameters": {k: v for k, v in p.items() if v not in ("", None)}, "files": files, } def main(): ap = argparse.ArgumentParser() ap.add_argument("--params", required=True, help="the schlib params.json for the component") ap.add_argument("--typeid", help="component typeid (auto-maps to an Altium Component Type)") ap.add_argument("--component-type", help="Altium Component Type (overrides the typeid map)") ap.add_argument("--assignee", default="", help="librarian to assign the request to (optional)") ap.add_argument("--file", action="append", default=[], help="local path to attach (repeatable): .SchLib, .PcbLib, datasheet") ap.add_argument("--out", required=True, help="part_requests.json (created or appended to)") a = ap.parse_args() params = json.load(open(a.params, encoding="utf-8")) entry = build_entry(params, a.typeid, a.component_type, a.file, a.assignee) data = json.load(open(a.out, encoding="utf-8")) if os.path.exists(a.out) else {"requests": []} data.setdefault("requests", []).append(entry) json.dump(data, open(a.out, "w", encoding="utf-8"), indent=2, ensure_ascii=False) print(f"wrote {a.out} (+1 request: {entry['mpn']} -> {entry['component_type']}, " f"{len(a.file)} file(s))") if __name__ == "__main__": main()