component
RC0402FR-0710ML - YAGEO
Public Unreviewedby John Lauer
Thick film chip resistor 10 MOhm 1%, 1/16 W, 0402 (1005 metric)
main
John Lauer
Remove the AI-added MPN-etched model (the part has no marking per its datasheet; John's rule: real marking only). Add d…
59964aa
18h ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
"""Prove a two-terminal chip model (MLCC / chip resistor) against its datasheet table.
The maker prints a dimension table with an unscaled sketch, so the overlay is made
against a true-scale drawing generated from the printed table (300 px/mm):
nominal outline solid black, tolerance limits dashed blue, the allowed range of
the terminal inner edge shaded amber. The model is rendered at the same scale
(50 % fill, red outline) on top. The numeric check measures the STEP itself.
usage: overlay_chip.py spec.json model.step out.png
spec: {"label","mpn","source","L":[nom,tol],"W":[nom,tol],"T":[nom,tol],
"band_top":[min,max],"band_bot":[min,max],"crops":[[png,[x0,y0,x1,y1]],...],
"marking": "..."}
"""
import sys, json
import numpy as np
from PIL import Image, ImageDraw, ImageFont, ImageFilter
sys.path.insert(0, __file__.rsplit("/", 1)[0])
from ortho import load_tris, render
from OCP.STEPControl import STEPControl_Reader
from OCP.TopExp import TopExp_Explorer
from OCP.TopAbs import TopAbs_FACE
from OCP.TopoDS import TopoDS
from OCP.Bnd import Bnd_Box
from OCP.BRepBndLib import BRepBndLib
from OCP.BRepAdaptor import BRepAdaptor_Surface
from OCP.GeomAbs import GeomAbs_Plane
PP = 300.0
spec = json.load(open(sys.argv[1])); step = sys.argv[2]; out = sys.argv[3]
F = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
font = ImageFont.truetype(F, 22); fsm = ImageFont.truetype(F, 18); fbig = ImageFont.truetype(F, 26)
# ---------- numeric measurement of the STEP ----------
tris = load_tris(step)
allp = np.concatenate([t[0] for t in tris])
x0, y0, z0 = allp.min(0); x1, y1, z1 = allp.max(0)
Lm, Wm, Tm = x1 - x0, y1 - y0, z1 - z0
# terminal colour = colour of triangles lying on the +X end face
endcols = {}
for p, c in tris:
if np.allclose(p[:, 0], x1, atol=1e-4):
endcols[c] = endcols.get(c, 0) + 1
tcol = max(endcols, key=endcols.get)
def band(zlev):
xs = [p[:, 0] for p, c in tris if c == tcol and np.allclose(p[:, 2], zlev, atol=1e-4)]
if not xs:
return None
xs = np.concatenate(xs)
pos = xs[xs > 0]; neg = xs[xs < 0]
return round(min(x1 - pos.min(), -x0 + neg.max() if len(neg) else 9), 3)
bt, bb = band(z1), band(z0)
def rng(v):
return (v[0] - v[1], v[0] + v[1]) if len(v) == 2 and v[1] < v[0] else tuple(v)
checks = []
def chk(name, val, lo, hi):
ok = val is not None and lo - 1e-6 <= val <= hi + 1e-6
checks.append((name, val, lo, hi, ok))
chk("L", round(Lm, 3), *rng(spec["L"]))
chk("W", round(Wm, 3), *rng(spec["W"]))
chk("T/H", round(Tm, 3), *rng(spec["T"]))
chk(spec.get("band_top_name", "terminal band, top"), bt, *spec["band_top"])
chk(spec.get("band_bot_name", "terminal band, bottom"), bb, *spec["band_bot"])
centred = bool(abs(x0 + x1) < 1e-3 and abs(y0 + y1) < 1e-3 and abs(z0) < 1e-3)
longx = bool(Lm > Wm)
# ---------- true-scale drawing + model per view ----------
def view_panel(view, a_len, a_tol, b_len, b_tol, bands, title):
im = render(tris, view, PP, size=(560, 420))
W_, H_ = im.size; cx = W_ / 2
panel = Image.new("RGBA", (W_, H_), (255, 255, 255, 255))
d = ImageDraw.Draw(panel)
seat = view in ("front", "end")
def rect(a, b, **kw):
# a along horizontal, b along vertical; for side views the box sits on the seat line
if seat:
return [cx - a * PP / 2, H_ / 2 - b * PP, cx + a * PP / 2, H_ / 2]
return [cx - a * PP / 2, H_ / 2 - b * PP / 2, cx + a * PP / 2, H_ / 2 + b * PP / 2]
# allowed inner-edge range for the terminals (relative to nominal length)
for (lo, hi, where) in bands:
for s in (-1, 1):
xa = cx + s * (a_len / 2 - hi) * PP; xb = cx + s * (a_len / 2 - lo) * PP
if where == "full":
ya, yb = rect(a_len, b_len)[1], rect(a_len, b_len)[3]
elif where == "top":
r_ = rect(a_len, b_len); ya, yb = r_[1] - 12, r_[1] + 12
else:
r_ = rect(a_len, b_len); ya, yb = r_[3] - 12, r_[3] + 12
d.rectangle([min(xa, xb), ya, max(xa, xb), yb], fill=(255, 196, 80, 255))
def dashed(box, col):
xa, ya, xb, yb = box
for (p, q) in (((xa, ya), (xb, ya)), ((xb, ya), (xb, yb)), ((xb, yb), (xa, yb)), ((xa, yb), (xa, ya))):
n = int(max(abs(q[0] - p[0]), abs(q[1] - p[1])) / 8) or 1
for i in range(0, n, 2):
t0, t1 = i / n, min(1, (i + 1) / n)
d.line([(p[0] + (q[0] - p[0]) * t0, p[1] + (q[1] - p[1]) * t0),
(p[0] + (q[0] - p[0]) * t1, p[1] + (q[1] - p[1]) * t1)], fill=col, width=2)
dashed(rect(a_len - a_tol, b_len - b_tol), (30, 90, 220))
dashed(rect(a_len + a_tol, b_len + b_tol), (30, 90, 220))
d.rectangle(rect(a_len, b_len), outline=(0, 0, 0), width=2)
arr = np.asarray(im).copy(); alpha = arr[:, :, 3].copy(); arr[:, :, 3] = (alpha * 0.5).astype(np.uint8)
panel.alpha_composite(Image.fromarray(arr))
m = Image.fromarray(((alpha > 128) * 255).astype(np.uint8))
edge = (np.asarray(m, int) - np.asarray(m.filter(ImageFilter.MinFilter(3)), int)) > 0
red = np.zeros((H_, W_, 4), np.uint8); red[edge] = (220, 30, 30, 255)
panel.alpha_composite(Image.fromarray(red))
d = ImageDraw.Draw(panel); d.text((10, 8), title, fill=(0, 0, 0), font=fsm)
return panel
L, W, T = spec["L"], spec["W"], spec["T"]
bt_r, bb_r = spec["band_top"], spec["band_bot"]
panels = [
view_panel("top", L[0], L[1], W[0], W[1], [(bt_r[0], bt_r[1], "full")], "TOP (looking down, X right)"),
view_panel("bottom", L[0], L[1], W[0], W[1], [(bb_r[0], bb_r[1], "full")], "BOTTOM (flipped about X)"),
view_panel("front", L[0], L[1], T[0], T[1], [(bt_r[0], bt_r[1], "top"), (bb_r[0], bb_r[1], "bot")], "SIDE (looking +Y)"),
view_panel("end", W[0], W[1], T[0], T[1], [], "END (looking -X)"),
]
pw, ph = panels[0].size
sheet_w = pw * 4 + 50
crops = [Image.open(p).convert("RGB").crop(tuple(b)) for p, b in spec["crops"]]
crop_h = 0; scaled = []
for c in crops:
s = min(1.0, (sheet_w - 40) / c.width, 420 / c.height)
c2 = c.resize((int(c.width * s), int(c.height * s)), Image.LANCZOS); scaled.append(c2); crop_h += c2.height + 20
text_h = 120 + 30 * len(checks) + 90
sheet = Image.new("RGB", (sheet_w, 80 + ph + text_h + crop_h), (255, 255, 255))
d = ImageDraw.Draw(sheet)
d.text((20, 15), f"{spec['label']}: model vs datasheet table, true scale {int(PP)} px/mm", fill=(0, 0, 0), font=fbig)
d.text((20, 50), f"Model: {step.rsplit('/', 1)[-1]} Datasheet: {spec['source']}", fill=(70, 70, 70), font=fsm)
for i, p in enumerate(panels):
sheet.paste(p.convert("RGB"), (10 + i * (pw + 10), 80))
y = 90 + ph
d.text((20, y), "Legend: black = printed nominal, blue dashed = printed tolerance limits, amber = allowed position of the terminal inner edge, red outline + 50% fill = the 3D model.", fill=(0, 0, 0), font=fsm); y += 34
d.text((20, y), "Measured on the STEP value datasheet range result", fill=(0, 0, 0), font=font); y += 32
for name, val, lo, hi, ok in checks:
d.text((20, y), f"{name:<34}{'-' if val is None else f'{val:.3f}':>8} {lo:.2f} to {hi:.2f} {'PASS' if ok else 'FAIL'}",
fill=(0, 120, 40) if ok else (200, 0, 0), font=font); y += 30
d.text((20, y + 4), f"Frame: Z-up, seated z=0, centred: {'yes' if centred else 'NO'}; long axis on X: {'yes' if longx else 'NO'}. Marking: {spec['marking']}", fill=(0, 0, 0), font=fsm)
y += 50
for c in scaled:
sheet.paste(c, (20, y)); d.rectangle([20, y, 20 + c.width, y + c.height], outline=(150, 150, 150)); y += c.height + 20
sheet.save(out)
res = {"step": step, "bbox_mm": [round(float(v), 4) for v in (x0, x1, y0, y1, z0, z1)],
"checks": [{"dim": n, "model_mm": v, "datasheet_min": lo, "datasheet_max": hi, "pass": bool(ok)} for n, v, lo, hi, ok in checks],
"z_up_seated_centred": bool(centred), "long_axis_x": bool(longx), "all_pass": bool(all(c[4] for c in checks) and centred and longx)}
print(json.dumps(res))