component
RC0402FR-0733KL - YAGEO
Public Made by Adomby adom
Thick film chip resistor 33 kOhm +/-1 %, 1/16 W, 50 V, 0402 (1005 metric)
main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
"""Build one component page tree per part in parts.PARTS.
usage: build.py <slug> [--skip-remote]
Writes /tmp/claude-1001/passives5/pages/<slug>/ . Downloads are read from
/tmp/claude-1001/passives5/dl (never executed); scripts live in this folder.
"""
import sys, os, json, shutil, subprocess, hashlib, re
import numpy as np
from PIL import Image, ImageDraw, ImageFont
ROOT = "/tmp/claude-1001/esc3"
TOOLS = os.path.join(ROOT, "tools")
DL = os.path.join(ROOT, "dl")
PY = "/home/adom/.venvs/cq/bin/python"
sys.path.insert(0, TOOLS)
from parts import PARTS, TODAY
slug = sys.argv[1]
P = next(p for p in PARTS if p["slug"] == slug)
OUT = os.path.join(ROOT, "pages", slug)
os.makedirs(OUT, exist_ok=True)
MPN = P["mpn"]
sha = lambda f: hashlib.sha256(open(f, "rb").read()).hexdigest()
def gitblob(f):
b = open(f, "rb").read()
return hashlib.sha1(b"blob %d\0" % len(b) + b).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
F = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
FB = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
for d in ("docs/verify", "docs/models", "docs/figures", "lod", "provenance/tools", "land-pattern", "altium"):
os.makedirs(os.path.join(OUT, d), exist_ok=True)
# ---------- STEP (KiCad library bytes, unchanged) ----------
src_step = os.path.join(DL, "kicad", P["fp"] + ".step")
step_name = f"{MPN}.step"
step_path = os.path.join(OUT, step_name)
shutil.copyfile(src_step, step_path)
assert sha(step_path) == sha(src_step)
# ---------- footprint (KiCad library geometry, renamed + local model path) ----------
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)
pads = [(float(a), float(b), float(c), float(d)) for a, b, c, d in
re.findall(r'\(pad "\d" smd roundrect\s*\(at ([-\d.]+) ([-\d.]+)\)\s*\(size ([\d.]+) ([\d.]+)\)', fp)]
assert len(pads) == 2, pads
# ---------- symbol (original, conventional passive graphics) ----------
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'
ref = P["kind"]
desc = P["brief"]
props = (prop("Reference", ref, 0 if False else 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", P["ds"]["url"], -10.16) + prop("Description", desc, -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", d["lcsc"][0], -25.4) + prop("Adom Wiki", f"https://wiki.adom.inc/adom/{slug}", -27.94))
if ref == "C":
body = (' (polyline (pts (xy -2.032 -0.762) (xy 2.032 -0.762)) (stroke (width 0.508) (type default)) (fill (type none)))\n'
' (polyline (pts (xy -2.032 0.762) (xy 2.032 0.762)) (stroke (width 0.508) (type default)) (fill (type none)))\n')
plen = 2.794
else:
body = ' (rectangle (start -1.016 -2.54) (end 1.016 2.54) (stroke (width 0.254) (type default)) (fill (type none)))\n'
plen = 2.54
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" "{P['category'].lower()} {P['package'].split()[0]} {MPN}" (at 0 0 0) (effects (font (size 1.27 1.27)) hide))
(symbol "{MPN}_0_1"
{body} )
(symbol "{MPN}_1_1"
(pin passive line (at 0 5.08 270) (length {plen}) (name "~" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27)))))
(pin passive line (at 0 -5.08 90) (length {plen}) (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)
# ---------- datasheets ----------
shutil.copyfile(os.path.join(DL, P["ds"]["local"]), os.path.join(OUT, P["ds"]["file"]))
for x in P["extra_docs"]:
shutil.copyfile(os.path.join(DL, x["local"]), os.path.join(OUT, x["file"]))
# ---------- tools copied into provenance ----------
for t in ("lod.py", "ortho.py", "overlay_chip.py", "build.py", "parts.py", "landcheck.py", "records.py", "finish.py", "figures.py", "etch.py"):
if os.path.exists(os.path.join(TOOLS, t)):
shutil.copyfile(os.path.join(TOOLS, t), os.path.join(OUT, "provenance/tools", t))
# ---------- overlay proof against the datasheet table ----------
ov = dict(P["overlay"])
crop_png, box = ov.pop("crop")
spec = {"label": MPN, "mpn": MPN, **ov, "crops": [[os.path.join(ROOT, "work/crops", crop_png), box]],
"marking": (f"datasheet code {P['marking_code']} on the marked display copy; this check measures the unmarked STEP" if P.get("marking_code") else "none specified (" + P["marking_src"] + ")")}
spec_path = os.path.join(OUT, f"provenance/tools/{slug}-overlay-spec.json")
spec_pub = dict(spec); spec_pub["crops"] = [[f"datasheet page render ({crop_png}, 100 dpi)", box]]
json.dump(spec_pub, open(spec_path, "w"), indent=1)
tmp_spec = os.path.join(ROOT, "work", f"{slug}-spec.json"); json.dump(spec, open(tmp_spec, "w"))
r = run([PY, "-I", os.path.join(TOOLS, "overlay_chip.py"), tmp_spec, 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)
print("overlay all_pass:", check["all_pass"])
if not check["all_pass"]:
print(json.dumps(check, indent=1)); sys.exit(2)
# ---------- land pattern comparison (KiCad pads vs maker's recommendation) ----------
r = run([PY, "-I", os.path.join(TOOLS, "landcheck.py"), slug, os.path.join(OUT, "land-pattern")], 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)
# ---------- datasheet marking (RC0603): marked display STEP via the step2glb laser-etch service ----------
disp_step = step_path; marked_name = None; etch_info = None
if P.get("marking_code"):
marked_name = f"{MPN}-marked.step"
r = run(["python3", "-I", os.path.join(TOOLS, "etch.py"), step_path, P["marking_code"], os.path.join(OUT, marked_name), slug])
etch_info = json.loads(r.stdout.strip().splitlines()[-1])
disp_step = os.path.join(OUT, marked_name)
# ---------- GLB (step2glb) ----------
glb_path = os.path.join(OUT, "package-reference.glb")
g = subprocess.run(["step2glb", "convert", disp_step, "-o", glb_path], capture_output=True, text=True)
print("step2glb exit", g.returncode, g.stderr[-300:])
assert g.returncode == 0 and os.path.getsize(glb_path) > 1000
# ---------- thumbnails (service render of the STEP) ----------
def thumb(out, wh=None):
extra = ["--width", str(wh[0]), "--height", str(wh[1])] if wh else []
t = subprocess.run(["step2glb", "thumbnail", disp_step, "-o", out] + extra, capture_output=True, text=True)
return t.returncode, (t.stdout + t.stderr)[-300:]
hero_rc = thumb(os.path.join(OUT, "docs/hero.png"), (1600, 1000))
th_rc = thumb(os.path.join(OUT, "docs/models/package-reference-step.png"))
print("thumbs", hero_rc, th_rc)
# ---------- LODs ----------
run([PY, "-I", os.path.join(TOOLS, "lod.py"), disp_step, MPN, OUT, os.path.basename(disp_step)] + ([step_path] if marked_name else []), cwd=TOOLS)
man = json.load(open(os.path.join(OUT, "lod/manifest.json")))
man["source"]["path"] = os.path.basename(disp_step)
if marked_name:
man["purpose"] = ("clean board-use component models: no footprint pads, outlines, labels or silkscreen; the datasheet marking code "
+ P["marking_code"] + " on the top coat in high and medium, omitted in low and super-low")
man["source"]["unmarked"] = {"path": step_name, "sha256": sha(step_path)}
else:
man["purpose"] = "clean board-use component models: no footprint pads, outlines, labels or silkscreen; no marking (" + P["marking_src"] + ")"
json.dump(man, open(os.path.join(OUT, "lod/manifest.json"), "w"), indent=1)
run(["python3", "-I", os.path.join(TOOLS, "figures.py"), slug])
json.dump({"step": step_name, "marked": marked_name, "etch": etch_info, "fp": fp_file, "sym": sym_file, "check": check, "land": land,
"hero_rc": hero_rc, "thumb_rc": th_rc, "glb_stderr": g.stderr[-300:]},
open(os.path.join(ROOT, "work", f"{slug}-build.json"), "w"), indent=1)
print("built", OUT)