component
LTC2945CMS#PBF - Wide Range I2C Power Monitor
Public Made by Adomby adom
Analog Devices (Linear Technology) LTC2945CMS#PBF: wide range I2C power monitor, 0 V to 80 V current and voltage sensing, 12-bit ADC, 24-bit power, min/max and alerts, split SDA for opto-isolation, 0 C to 70 C, MSOP-12. KiCad, Fusion and Altium libraries, 3D model and datasheet.
main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
#!/usr/bin/env python3
"""Overlay a 3D model of the MS12 (12-lead MSOP) package on Analog Devices' package drawing (LTC DWG 05-08-1668 Rev A,
LTC2945 datasheet Rev. D p.32, rendered at 400 dpi).
The drawing says "DRAWING NOT TO SCALE", so each view is calibrated on its own printed reference lines and the scale
per axis is reported:
top view : x from the 4.039 mm body-length extension lines, y from the 3.00 mm body-width extension lines;
side view: x shares the top view's columns; z from the seating plane (z = 0) and the 0.86 REF body-top line
(z = 0.1016 + 0.86 = 0.9616 mm nominal).
The pixel positions of those lines were found by dark-pixel projections of the crops (rows/columns with long runs).
The model is rotated +90 deg about Z so that its pin 1 lands bottom-left like the drawing's top view.
Model faces are drawn at 45 % opacity (red body, blue leads, green pin-1 dimple); the residuals at printed features
NOT used for calibration (lead tips, standoff) are written on the image.
usage: overlay_ms12.py page32_400dpi.png model.step out.png out.json (run with the CadQuery python)"""
import sys, os, json
import numpy as np
from PIL import Image, ImageDraw, ImageFont
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from ortho import load_tris
page, step, out, outj = sys.argv[1:5]
im = Image.open(page).convert("RGB")
TOP = (2050, 1400, 2600, 2200)
SIDE = (2050, 2200, 2600, 2550)
top_x0, top_x1 = 55.0, 368.0 # 4.039 mm body length extension lines (crop columns)
top_y0, top_y1 = 333.5, 584.5 # 3.00 mm body width extension lines (crop rows)
tip_y0, tip_y1 = 251.0, 669.5 # 4.90 mm tip-to-tip extension lines (not used for calibration)
side_seat, side_top = 208.0, 92.5 # seating plane, 0.86 REF top line (side crop rows)
side_body_bot = 189.5 # 0.86 REF lower line (body underside, not used for calibration)
sx = (top_x1 - top_x0) / 4.039; cx = (top_x0 + top_x1) / 2
sy = (top_y1 - top_y0) / 3.00; cy = (top_y0 + top_y1) / 2
sz = (side_seat - side_top) / (0.1016 + 0.86)
tris = load_tris(step)
R = np.array([[0, -1, 0], [1, 0, 0], [0, 0, 1]], float)
def draw_view(crop, project, view_dir):
base = crop.convert("RGBA")
lay = Image.new("RGBA", base.size, (0, 0, 0, 0)); d = ImageDraw.Draw(lay)
items = []
for p, col in tris:
q = p @ R.T
n = np.cross(q[1] - q[0], q[2] - q[0]); nn = np.linalg.norm(n)
if nn == 0 or n @ view_dir / nn <= 1e-6: continue
items.append(((q @ view_dir).mean(), [project(v) for v in q], col))
items.sort(key=lambda t: t[0])
for _, xy, col in items:
lum = sum(col) / 3 if col is not None else 0.0
c = (230, 60, 40, 115) if lum < 0.15 else ((40, 110, 230, 115) if lum > 0.5 else (20, 170, 60, 200))
d.polygon(xy, fill=c)
return Image.alpha_composite(base, lay)
top_img = draw_view(im.crop(TOP), lambda v: (cx + v[0] * sx, cy - v[1] * sy), np.array([0, 0, 1.0]))
side_img = draw_view(im.crop(SIDE), lambda v: (cx + v[0] * sx, side_seat - v[2] * sz), np.array([0, -1.0, 0]))
allq = np.concatenate([t[0] for t in tris]) @ R.T
standoff_x = ((side_seat - side_body_bot) / sz) / 0.1016
res = {
"scale_px_per_mm": {"top_x": round(sx, 2), "top_y": round(sy, 2), "side_z": round(sz, 2)},
"drawing_note": "DRAWING NOT TO SCALE (note 2 on the drawing): x and y scales differ by %.1f %%, and the side view's standoff is drawn about %.1fx its printed value." % (abs(sy / sx - 1) * 100, standoff_x),
"lead_tips_drawn_mm": round((tip_y1 - tip_y0) / sy, 3),
"lead_tips_model_mm": round(float(allq[:, 1].max() - allq[:, 1].min()), 3),
"lead_tips_printed_mm": "4.90 +-0.152",
"calibrated_on": ["4.039 body length (x)", "3.00 body width (y)", "seating plane and 0.86 REF top line (z)"],
}
F = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
f = ImageFont.truetype(F, 18); fb = ImageFont.truetype(F, 22)
W = 1150; H = 70 + top_img.height + 20 + side_img.height + 210
sheet = Image.new("RGBA", (W, H), (255, 255, 255, 255)); dr = ImageDraw.Draw(sheet)
dr.text((20, 15), "LTC2945CMS#PBF 3D model over ADI drawing 05-08-1668 Rev A (datasheet Rev. D p.32)", fill=(0, 0, 0), font=fb)
dr.text((20, 42), "red: model body, blue: model leads, green: model pin-1 dimple; 45 % opacity; model rotated +90 deg about Z", fill=(60, 60, 60), font=f)
sheet.alpha_composite(top_img, (20, 70))
sheet.alpha_composite(side_img, (20, 90 + top_img.height))
y = 100 + top_img.height + side_img.height
for l in [
"Top view calibrated on 4.039 (x, %.1f px/mm) and 3.00 (y, %.1f px/mm) extension lines." % (sx, sy),
"Side view: x as top view, z on seating plane and 0.86 REF top (%.1f px/mm)." % sz,
"Lead tips: model %.3f mm; printed 4.90 +-0.152; drawn %.3f mm at the y scale." % (res["lead_tips_model_mm"], res["lead_tips_drawn_mm"]),
"Drawing note 2: NOT TO SCALE; standoff drawn about %.1fx its printed 0.1016 mm." % standoff_x,
"Pin 1: drawing dot and pin 1 bottom-left; model pin-1 dimple bottom-left.",
]:
dr.text((20, y), l, fill=(0, 0, 0), font=f); y += 30
sheet.convert("RGB").save(out)
json.dump(res, open(outj, "w"), indent=1)
print(json.dumps(res))