component
MLX90614ESF-BCI-000-TU - 3 V Digital IR Thermometer, 5 deg FOV
Public Made by Adomby adom
Melexis MLX90614ESF-BCI-000-TU: factory-calibrated non-contact infrared thermometer, 3 V supply, 5 degree field of view (long collimator cap), gradient compensated, SMBus and PWM output, TO-39 4-lead can.
main
John Lauer
Add KiCad symbol and footprint, viewers, provenance, README, models.json and verification log
354b2dd
11h ago
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
#!/usr/bin/env python3
"""Pin-1 proof: footprint pads vs model leads, and the model's bottom view next to the datasheet's Figure 1.
1. Reads the KiCad footprint pads and the model's lead solids (STEP), maps KiCad (x, y-down) to the model frame
(x, -y) and checks every lead centre sits on its pad centre.
2. Draws the model seen from BELOW, turned so the tab points up as in Melexis datasheet Rev 021 p.6 Figure 1,
labels each lead with the footprint pad number and the symbol pin name, and places it beside the datasheet
figure (page crop) for comparison.
usage: pin1_proof.py model.step footprint.kicad_mod page6_png out.png out.json
"""
import json, math, re, sys
import cadquery as cq
from PIL import Image, ImageDraw, ImageFont
step, fp, p6, out_png, out_json = sys.argv[1:6]
NAMES = {'1': 'SCL', '2': 'SDA/PWM', '3': 'VDD', '4': 'VSS'}
CX, CY = 2.54, 0.0
pads = {m.group(1): (float(m.group(2)), float(m.group(3)))
for m in re.finditer(r'\(pad "(\d+)" thru_hole \w+\s*\(at ([-\d.]+) ([-\d.]+)\)', open(fp).read())}
shape = cq.importers.importStep(step)
solids = shape.solids().vals()
leads = []
for s in solids:
b = s.BoundingBox()
if b.zmin < -10 and (b.xmax - b.xmin) < 0.6: # a lead: thin and reaching below the board
leads.append(((b.xmin + b.xmax) / 2, (b.ymin + b.ymax) / 2))
if len(leads) != 4: # leads fused into one solid: split by connected faces is overkill; use the bbox of each lead face
leads = []
for f in shape.faces().vals():
b = f.BoundingBox()
if abs(b.zmin - b.zmax) < 1e-6 and b.zmin < -12 and (b.xmax - b.xmin) < 0.6:
leads.append(((b.xmin + b.xmax) / 2, (b.ymin + b.ymax) / 2))
res = {'pads_kicad': pads, 'leads_model': leads, 'pairs': {}, 'max_offset_mm': 0.0}
for n, (px, py) in pads.items():
mx, my = px, -py
d = min(math.hypot(mx - lx, my - ly) for lx, ly in leads)
res['pairs'][n] = {'pad_model_xy': [mx, my], 'nearest_lead_offset_mm': round(d, 4)}
res['max_offset_mm'] = max(res['max_offset_mm'], round(d, 4))
res['leads_on_pads'] = res['max_offset_mm'] < 0.01 and len(leads) == 4
# bottom view, tab up: mirror x (seen from below), then turn +45 deg (the tab at 135 deg in the top view goes to 90)
def bv(x, y):
dx, dy = -(x - CX), (y - CY)
a = math.radians(45)
return dx * math.cos(a) - dy * math.sin(a), dx * math.sin(a) + dy * math.cos(a)
S, K = 900, 70.0
im = Image.new('RGB', (S, S), (255, 255, 255))
d = ImageDraw.Draw(im)
f = ImageFont.truetype('DejaVuSans.ttf', 30)
fs = ImageFont.truetype('DejaVuSans.ttf', 24)
def P(u, v): return (S / 2 + u * K, S / 2 + 40 - v * K)
# header + tab (from the model's dims)
dims = dict(header_r=4.575, tab_w=0.8, tab_tip=5.46, tab_angle=135)
d.ellipse([P(-dims['header_r'], dims['header_r']), P(dims['header_r'], -dims['header_r'])], outline=(0, 0, 0), width=3)
ta = math.radians(dims['tab_angle'])
tx, ty = CX + math.cos(ta) * dims['tab_tip'], CY + math.sin(ta) * dims['tab_tip']
u, v = bv(tx, ty)
d.rectangle([P(u - 0.4, v), P(u + 0.4, v - 0.9)], outline=(0, 0, 0), width=3)
for n, (px, py) in pads.items():
u, v = bv(px, -py)
d.ellipse([P(u - 0.6, v + 0.6), P(u + 0.6, v - 0.6)], outline=(40, 40, 40), width=3, fill=(200, 200, 200))
d.text(P(u - 0.9, v + 1.5), f'{n} - {NAMES[n]}', fill=(200, 0, 0), font=f)
d.text((20, 15), 'Model bottom view (tab up), pad numbers from the footprint', fill=(0, 0, 0), font=fs)
d.text((20, S - 40), 'pin 1 = footprint pad 1 (oval pad); leads on pads: ' + ('PASS' if res['leads_on_pads'] else 'FAIL'),
fill=(0, 120, 0) if res['leads_on_pads'] else (200, 0, 0), font=fs)
ds = Image.open(p6).convert('RGB')
W, H = ds.size
ds = ds.crop((int(W * 0.30), int(H * 0.27), int(W * 0.70), int(H * 0.56)))
ds = ds.resize((S, int(ds.height * S / ds.width)))
canvas = Image.new('RGB', (2 * S + 40, max(S, ds.height) + 60), (255, 255, 255))
canvas.paste(ds, (0, 50)); canvas.paste(im, (S + 40, 0))
ImageDraw.Draw(canvas).text((20, 10), 'Melexis MLX90614 datasheet Rev 021 p.6 Figure 1 (bottom view)', fill=(0, 0, 0), font=fs)
canvas.save(out_png)
json.dump(res, open(out_json, 'w'), indent=1)
print('OK', res['leads_on_pads'], res['max_offset_mm'])