component
C1812C124J1GACTU - KEMET
Public Made by Adomby adom
MLCC 0.12 uF +/-5 %, 100 V, C0G/NP0, 1812 (4532 metric)
main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
"""Build the C1812C124J1GACTU page tree (KEMET STEP re-posed, KiCad footprint renamed, symbol, checks, GLB, LODs, figures).
usage: build.py (cadquery venv python, run with -I under tools/capped)
Writes /home/adom/project/uta-dynos/work/pages/c1812c124j1gactu/. Downloads are read from
/home/adom/project/uta-dynos/work/pages/_build/dl (never executed); scripts live in this folder.
"""
import sys, os, json, shutil, subprocess, hashlib, re
from PIL import Image
TOOLS = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, TOOLS)
from parts import P, ROOT, FIGS, DOCS, TODAY, DS_URL
DL = os.path.join(ROOT, "dl"); WORK = os.path.join(ROOT, "work", "kemet"); os.makedirs(WORK, exist_ok=True)
PY = "/home/adom/.venvs/cq/bin/python"
slug, MPN = P["slug"], P["mpn"]
OUT = f"/home/adom/project/uta-dynos/work/pages/{slug}"
sha = lambda f: hashlib.sha256(open(f, "rb").read()).hexdigest()
def run(cmd, **kw):
r = subprocess.run(cmd, capture_output=True, text=True, **kw)
if r.returncode != 0:
print("FAILED", cmd, r.stdout[-2000:], r.stderr[-2000:]); sys.exit(1)
return r
for d in ("docs/verify", "docs/models", "docs/figures", "lod", "provenance/tools", "land-pattern", "altium", "vendor"):
os.makedirs(os.path.join(OUT, d), exist_ok=True)
# ---------- STEP: KEMET's own model, original kept; board model re-posed to Z-up, logo glyphs dropped ----------
src_step = os.path.join(DL, "kemet", "step", f"{MPN}-kemet-original.step")
orig_name = f"vendor/{MPN}-kemet-original.step"
shutil.copyfile(src_step, os.path.join(OUT, orig_name))
step_name = f"{MPN}.step"; step_path = os.path.join(OUT, step_name)
r = run([PY, "-I", os.path.join(TOOLS, "repose_step.py"), src_step, step_path], cwd=TOOLS)
repose = json.loads(r.stdout.strip().splitlines()[-1]); repose["output"] = step_name
assert repose["source_sha256"] == sha(os.path.join(OUT, orig_name))
json.dump(repose, open(os.path.join(OUT, f"provenance/{step_name}.json"), "w"), indent=1)
# ---------- footprint: KiCad standard C_1812_4532Metric, renamed, model path set ----------
fp_name = f"{P['fp']}_{MPN}"
src_fp = open(os.path.join(DL, "kicad", P["fp"] + ".kicad_mod")).read()
fp = src_fp.replace(f'(footprint "{P["fp"]}"', f'(footprint "{fp_name}"', 1)
fp = re.sub(r'\(model "[^"]+"', f'(model "${{KIPRJMOD}}/{step_name}"', fp)
assert fp.count(fp_name) == 1 and "${KIPRJMOD}/" + step_name in fp
fp_file = f"{fp_name}.kicad_mod"
open(os.path.join(OUT, fp_file), "w").write(fp)
# ---------- symbol (standard capacitor graphic, pin ends on the 2.54 mm grid) ----------
d = P["dist"]
def prop(name, val, y, hide=True):
h = " hide" if hide else ""
return f' (property "{name}" "{val}" (at 0 {y} 0) (effects (font (size 1.27 1.27)){h}))\n'
props = (prop("Reference", "C", 0, False).replace("(at 0 0 0)", "(at 2.54 1.27 0)").replace("(font (size 1.27 1.27))", "(font (size 1.27 1.27)) (justify left)")
+ prop("Value", P["value_sym"], 0, False).replace("(at 0 0 0)", "(at 2.54 -1.27 0)").replace("(font (size 1.27 1.27))", "(font (size 1.27 1.27)) (justify left)")
+ prop("Footprint", fp_name, -7.62) + prop("Datasheet", DS_URL, -10.16) + prop("Description", P["brief"], -12.7)
+ prop("MPN", MPN, -15.24) + prop("Manufacturer", P["mfr"], -17.78)
+ prop("Mouser", d["mouser"][0], -20.32) + prop("DigiKey", d["digikey"][0], -22.86) + prop("LCSC", "", -25.4)
+ prop("Adom Wiki", f"https://wiki.adom.inc/adom/{slug}", -27.94))
sym = f'''(kicad_symbol_lib (version 20241209) (generator "adom_passive_symbol")
(symbol "{MPN}" (pin_numbers (hide yes)) (pin_names (offset 0.254) (hide yes)) (exclude_from_sim no) (in_bom yes) (on_board yes)
{props} (property "ki_keywords" "capacitor mlcc 1812 4532 100V C0G NP0 120nF {MPN}" (at 0 0 0) (effects (font (size 1.27 1.27)) hide))
(symbol "{MPN}_0_1"
(polyline (pts (xy -2.032 -0.762) (xy 2.032 -0.762)) (stroke (width 0.508) (type default)) (fill (type none)))
(polyline (pts (xy -2.032 0.762) (xy 2.032 0.762)) (stroke (width 0.508) (type default)) (fill (type none)))
)
(symbol "{MPN}_1_1"
(pin passive line (at 0 2.54 270) (length 1.778) (name "~" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27)))))
(pin passive line (at 0 -2.54 90) (length 1.778) (name "~" (effects (font (size 1.27 1.27)))) (number "2" (effects (font (size 1.27 1.27)))))
)
)
)
'''
sym_file = f"{MPN}.kicad_sym"
open(os.path.join(OUT, sym_file), "w").write(sym)
# ---------- documents + figure crops (150 dpi) ----------
pdfs = {}
for key, local, pub, title, url in DOCS:
shutil.copyfile(os.path.join(DL, local), os.path.join(OUT, pub)); pdfs[key] = os.path.join(DL, local)
figs = []
for name, doc, page, box, caption in FIGS:
png = os.path.join(WORK, f"fig150-{doc}-{page}.png")
if not os.path.exists(png):
run(["pdftoppm", "-r", "150", "-png", "-singlefile", "-f", str(page), "-l", str(page), pdfs[doc], png[:-4]])
b = [int(round(v * 1.5)) for v in box]
out = os.path.join(OUT, "docs/figures", name)
Image.open(png).convert("RGB").crop(b).save(out, optimize=True)
figs.append({"file": f"docs/figures/{name}", "document": next(t for k, _, _, t, _ in DOCS if k == doc), "page": page,
"crop_px_at_150dpi": b, "caption": caption, "sha256": sha(out)})
json.dump(figs, open(os.path.join(OUT, "docs/figures/figures.json"), "w"), indent=1)
# ---------- overlay proof of the maker's STEP against the printed limits ----------
crops100 = []
for pg, box in ((3, [40, 330, 810, 770]), (10, [40, 100, 610, 960])):
png = os.path.join(WORK, f"ds100-p{pg}.png")
if not os.path.exists(png):
run(["pdftoppm", "-r", "100", "-png", "-singlefile", "-f", str(pg), "-l", str(pg), pdfs["ds"], png[:-4]])
crops100.append([png, box])
spec = {"label": MPN, "mpn": MPN, **P["overlay"], "crops": crops100, "marking": "none (C0G is supplied unmarked, p.15)"}
sp = os.path.join(WORK, "overlay-spec.json"); json.dump(spec, open(sp, "w"))
r = run([PY, "-I", os.path.join(TOOLS, "overlay_chip.py"), sp, step_path, os.path.join(OUT, f"docs/verify/{slug}-overlay.png")], cwd=TOOLS)
check = json.loads(r.stdout.strip().splitlines()[-1]); check["step"] = step_name
json.dump(check, open(os.path.join(OUT, f"docs/verify/{slug}-check.json"), "w"), indent=1)
pub = dict(spec); pub["crops"] = [["datasheet p.3 render at 100 dpi", crops100[0][1]], ["datasheet p.10 render at 100 dpi", crops100[1][1]]]
json.dump(pub, open(os.path.join(OUT, f"provenance/tools/{slug}-overlay-spec.json"), "w"), indent=1)
print("overlay all_pass:", check["all_pass"], [(c["dim"], c["model_mm"], c["pass"]) for c in check["checks"]])
if not check["all_pass"]:
sys.exit(2)
# ---------- land: KiCad pads (used) vs KEMET Table 3 density level B ----------
r = run([PY, "-I", os.path.join(TOOLS, "landcheck.py"), slug, os.path.join(OUT, "land-pattern"), os.path.join(OUT, fp_file)], cwd=TOOLS)
land = json.loads(r.stdout.strip().splitlines()[-1])
json.dump(land, open(os.path.join(OUT, "land-pattern/land-check.json"), "w"), indent=1)
# ---------- GLB + thumbnails (step2glb service) ----------
glb_path = os.path.join(OUT, "package-reference.glb")
g = subprocess.run(["step2glb", "convert", step_path, "-o", glb_path], capture_output=True, text=True)
print("step2glb exit", g.returncode)
assert g.returncode == 0 and os.path.getsize(glb_path) > 1000
def thumb(src, out, wh=None):
extra = ["--width", str(wh[0]), "--height", str(wh[1])] if wh else []
return subprocess.run(["step2glb", "thumbnail", src, "-o", out] + extra, capture_output=True, text=True).returncode
th = [thumb(step_path, os.path.join(OUT, "docs/hero.png"), (1600, 1000)), thumb(step_path, os.path.join(OUT, "docs/models/step-plain.png"))]
print("thumbs", th)
# ---------- LODs ----------
run([PY, "-I", os.path.join(TOOLS, "lod.py"), step_path, MPN, OUT, step_name], cwd=TOOLS)
man = json.load(open(os.path.join(OUT, "lod/manifest.json")))
man["purpose"] = "clean board-use component models: no footprint pads, outlines, labels or silkscreen; no marking (KEMET supplies C0G parts unmarked)"
man["generator"] = "lod.py (OCCT via cadquery-ocp)"
json.dump(man, open(os.path.join(OUT, "lod/manifest.json"), "w"), indent=1)
for t in ("build.py", "parts.py", "repose_step.py", "ortho.py", "overlay_chip.py", "landcheck.py", "lod.py", "records.py", "finish.py", "push.sh", "verify_live.py"):
if os.path.exists(os.path.join(TOOLS, t)):
shutil.copyfile(os.path.join(TOOLS, t), os.path.join(OUT, "provenance/tools", t))
json.dump({"step": step_name, "orig": orig_name, "fp": fp_file, "sym": sym_file, "check": check, "land": land, "thumbs": th, "date": TODAY},
open(os.path.join(ROOT, "work", f"{slug}-build.json"), "w"), indent=1)
print("built", OUT)