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
8h ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
#!/usr/bin/env python3
"""Build the MLX90614xCI (5 degree FOV, long collimator cap) TO-39 4-lead model from the Melexis datasheet.
Source: Melexis MLX90614 datasheet, Datasheet Rev 021 (02-Jun-2026), DOC#3901090614,
p.49 Figure 45 (Mechanical drawing of MLX90614xCI) and p.6 Figure 1 (pin description, bottom view).
Frame (Adom / KiCad 3D): Z up, millimetres, origin = KiCad TO-39-4 footprint pad 1,
x = KiCad x, y = -KiCad y. Can axis at (2.54, 0). Seating plane z = 0 is the bottom of the
Ø1.20 lead beads (they stand 0.38 below the header); the header bottom is at z = 0.38.
Printed dimensions (p.49): cap Ø9.00 ±0.1, 0.50x45 top chamfer, Ø1.50 groove whose centre lies 0.20 outside
the cap wall and 1.58 below the top, overall 20.20 ±0.15 (top to header bottom), 19.50 ±0.1 (top to
header top line), header Ø9.15 +0.20/-0.10, tab 0.80 ±0.1 wide at 45 deg between pins 1 and 4,
M5 thread in the top, leads Ø0.45 ±0.04 on a Ø5.08 circle, 13.00 ±0.5 long below the header,
Ø1.20 lead beads 0.38 below the header.
Measured off the drawing (400 dpi render, 44.5 px/mm calibrated on Ø9.00 and 20.20; both read true):
header flange thickness 0.40, upper header step 0.29 at Ø9.00, tab tip 5.46 from the can axis,
M5 minor-diameter bore Ø4.13 (thread convention: full circle = minor diameter).
Not given anywhere in the datasheet: bore depth (modelled 2.00, assumption) and where the laser marking sits.
usage: build_to39_bci.py out_dir
writes: mlx90614esf-bci-000-tu.step (plain), mlx90614esf-bci-000-tu-etched.step (with the BCI device code),
dims.json
"""
import json, math, sys, os
import cadquery as cq
OUT = sys.argv[1]
os.makedirs(OUT, exist_ok=True)
CX, CY = 2.54, 0.0
D = dict(
cap_d=9.00, chamfer=0.50, groove_d=1.50, groove_off=0.20, groove_from_top=1.58,
overall=20.20, top_to_header_top=19.50, header_d=9.15, header_t=0.40, header_step_t=0.29,
tab_w=0.80, tab_tip_r=5.46, tab_angle_deg=135.0, bore_d=4.13, bore_depth=2.00,
lead_d=0.45, lead_circle_d=5.08, lead_len_below_header=13.00, bead_d=1.20, bead_h=0.38,
)
Z_HDR_BOT = D['bead_h'] # 0.38
Z_TOP = Z_HDR_BOT + D['overall'] # 20.58
Z_HDR_TOP = Z_HDR_BOT + D['header_t'] # 0.78 (flange)
Z_CAP_BOT = Z_TOP - D['top_to_header_top'] # 1.08
assert abs(Z_CAP_BOT - (Z_HDR_TOP + D['header_step_t'])) < 0.02, (Z_CAP_BOT, Z_HDR_TOP)
PINS = { # pad number -> (x, y) in the model frame (KiCad y flipped)
'1': (0.0, 0.0), '2': (2.54, -2.54), '3': (5.08, 0.0), '4': (2.54, 2.54)}
def cyl(d, z0, z1, x=CX, y=CY):
return cq.Workplane('XY').workplane(offset=z0).center(x, y).circle(d / 2).extrude(z1 - z0)
# ---- can (header flange + tab + header step + cap with chamfer, groove and M5 bore)
flange = cyl(D['header_d'], Z_HDR_BOT, Z_HDR_TOP)
a = math.radians(D['tab_angle_deg'])
tab_len = D['tab_tip_r']
tab = (cq.Workplane('XY').workplane(offset=Z_HDR_BOT)
.center(CX + math.cos(a) * tab_len / 2, CY + math.sin(a) * tab_len / 2)
.rect(tab_len, D['tab_w']).extrude(D['header_t'])
.rotate((CX + math.cos(a) * tab_len / 2, CY + math.sin(a) * tab_len / 2, 0),
(CX + math.cos(a) * tab_len / 2, CY + math.sin(a) * tab_len / 2, 1), D['tab_angle_deg']))
step = cyl(D['cap_d'], Z_HDR_TOP, Z_CAP_BOT)
cap = cyl(D['cap_d'], Z_CAP_BOT, Z_TOP).faces('>Z').edges().chamfer(D['chamfer'])
groove_r = D['cap_d'] / 2 + D['groove_off']
groove_z = Z_TOP - D['groove_from_top']
torus = cq.Workplane('XY').add(cq.Solid.makeTorus(groove_r, D['groove_d'] / 2,
pnt=cq.Vector(CX, CY, groove_z), dir=cq.Vector(0, 0, 1)))
cap = cap.cut(torus)
bore = cyl(D['bore_d'], Z_TOP - D['bore_depth'], Z_TOP + 0.1)
cap = cap.cut(bore)
can = flange.union(tab).union(step).union(cap)
# dark optics floor at the bottom of the bore (the window/lens seen from the top; its detail is not drawn)
window = cyl(D['bore_d'] - 0.02, Z_TOP - D['bore_depth'], Z_TOP - D['bore_depth'] + 0.05)
# ---- leads and glass beads
lead_bot = Z_HDR_BOT - D['lead_len_below_header']
leads = None
beads = None
for n, (x, y) in PINS.items():
l = cyl(D['lead_d'], lead_bot, Z_HDR_TOP - 0.05, x, y)
b = cyl(D['bead_d'], 0.0, Z_HDR_BOT + 0.01, x, y).cut(cyl(D['lead_d'], -0.1, 0.5, x, y))
leads = l if leads is None else leads.union(l)
beads = b if beads is None else beads.union(b)
NICKEL = cq.Color(0.80, 0.80, 0.78)
TIN = cq.Color(0.86, 0.86, 0.85)
GLASS = cq.Color(0.42, 0.52, 0.48)
OPTIC = cq.Color(0.06, 0.06, 0.07)
MARK = cq.Color(0.30, 0.30, 0.32)
def assembly(with_mark):
asm = cq.Assembly(name='MLX90614ESF-BCI-000-TU')
asm.add(can, name='body', color=NICKEL)
asm.add(window, name='window', color=OPTIC)
asm.add(beads, name='beads', color=GLASS)
asm.add(leads, name='pins', color=TIN)
if with_mark:
asm.add(mark_solid(), name='Adom.LaserEtch', color=MARK)
return asm
def mark_solid(text='BCI', size=1.6, zc=None):
"""The Melexis device code (Table 20, p.50) on the +Y wall, conforming to the cylinder (30 um proud)."""
zc = zc if zc is not None else (Z_CAP_BOT + groove_z) / 2 + 2.0
r0 = D['cap_d'] / 2
# text lying in an XZ plane, extruded through the wall along +Y
wp = cq.Workplane(cq.Plane(origin=(CX, CY + r0 - 1.0, zc), xDir=(-1, 0, 0), normal=(0, 1, 0)))
txt = wp.text(text, size, 2.0, combine=True, halign='center', valign='center', kind='bold')
shell = cyl(2 * (r0 + 0.03), zc - 3, zc + 3).cut(cyl(2 * r0 - 0.002, zc - 4, zc + 4))
return txt.intersect(shell)
assembly(False).save(os.path.join(OUT, 'mlx90614esf-bci-000-tu.step'), exportType='STEP')
assembly(True).save(os.path.join(OUT, 'mlx90614esf-bci-000-tu-etched.step'), exportType='STEP')
bb = can.val().BoundingBox()
summary = dict(D, z_header_bottom=Z_HDR_BOT, z_header_top=Z_HDR_TOP, z_cap_bottom=Z_CAP_BOT, z_top=Z_TOP,
groove_centre_z=groove_z, lead_bottom_z=lead_bot,
can_bbox_mm=[bb.xmin, bb.ymin, bb.zmin, bb.xmax, bb.ymax, bb.zmax], pins=PINS,
mark=dict(text='BCI', size_mm=1.6, wall='+Y', proud_mm=0.03))
json.dump(summary, open(os.path.join(OUT, 'dims.json'), 'w'), indent=1)
print('OK', json.dumps(summary['can_bbox_mm']), 'top', Z_TOP, 'leads to', lead_bot)
if os.environ.get('BBOX'):
for nm, o in (('can', can), ('window', window), ('beads', beads), ('leads', leads), ('mark', mark_solid())):
b = o.val().BoundingBox(); print(nm, round(b.xmin, 3), round(b.ymin, 3), round(b.zmin, 3), round(b.xmax, 3), round(b.ymax, 3), round(b.zmax, 3))