Skill/scripts/build_part_request_manifest.py

95 lines
5.6 KiB
Python

#!/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 <schlib params.json> --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()