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
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
#!/usr/bin/env python3
"""Depth-buffered orthographic render of a chip GLB (Z up; float or KHR_mesh_quantization accessors, node TRS applied), for previews and checks.
usage: render_glb.py model.glb out.png [--view iso|top|bottom|front|side|corner] [--size 480] [--bg 36,40,46]
iso: from front-left-up (pin-1 side, -x/+y quadrant shown at the left); top: looking down -Z, +Y up;
bottom: looking up +Z (mirrored as seen from below); front: along +Y; side: along -X."""
import sys, math, argparse, json
import numpy as np
from PIL import Image
sys.path.insert(0, __file__.rsplit("/", 1)[0])
from pygltflib import GLTF2
ap = argparse.ArgumentParser(); ap.add_argument("glb"); ap.add_argument("out")
ap.add_argument("--view", default="iso"); ap.add_argument("--size", type=int, default=480)
ap.add_argument("--bg", default="36,40,46"); a = ap.parse_args()
CT = {5126: np.float32, 5125: np.uint32, 5123: np.uint16, 5122: np.int16, 5121: np.uint8, 5120: np.int8}
NORM = {5122: 32767.0, 5123: 65535.0, 5120: 127.0, 5121: 255.0} # KHR_mesh_quantization normalized accessors
g = GLTF2().load(a.glb); blob = g.binary_blob()
def acc(i):
x = g.accessors[i]; bv = g.bufferViews[x.bufferView]; n = {"SCALAR": 1, "VEC3": 3}[x.type]
st = bv.byteStride; it = np.dtype(CT[x.componentType]).itemsize
off = (bv.byteOffset or 0) + (x.byteOffset or 0)
if st and st != it * n:
raw = np.stack([np.frombuffer(blob, np.uint8, it * n, off + k * st) for k in range(x.count)])
arr = raw.view(CT[x.componentType]).reshape(x.count, n)
else:
arr = np.frombuffer(blob, CT[x.componentType], x.count * n, off).reshape(-1, n)
if x.normalized: arr = np.maximum(arr / NORM[x.componentType], -1.0)
return arr if n > 1 else arr.ravel()
def mat(n):
if n.matrix: return np.array(n.matrix).reshape(4, 4).T
M = np.eye(4)
if n.translation: T = np.eye(4); T[:3, 3] = n.translation; M = M @ T
if n.rotation:
qx, qy, qz, qw = n.rotation; Rq = np.eye(4)
Rq[:3, :3] = [[1 - 2 * (qy * qy + qz * qz), 2 * (qx * qy - qz * qw), 2 * (qx * qz + qy * qw)],
[2 * (qx * qy + qz * qw), 1 - 2 * (qx * qx + qz * qz), 2 * (qy * qz - qx * qw)],
[2 * (qx * qz - qy * qw), 2 * (qy * qz + qx * qw), 1 - 2 * (qx * qx + qy * qy)]]
M = M @ Rq
if n.scale: M = M @ np.diag(list(n.scale) + [1])
return M
tris = []
def walk(ni, M):
n = g.nodes[ni]; M = M @ mat(n)
if n.mesh is not None:
for p in g.meshes[n.mesh].primitives:
P = acc(p.attributes.POSITION).astype(float); P = (np.c_[P, np.ones(len(P))] @ M.T)[:, :3] * 1000
I = acc(p.indices).reshape(-1, 3)
m = g.materials[p.material]; pbr = m.pbrMetallicRoughness
tris.append((P[I], np.array(pbr.baseColorFactor[:3]), pbr.metallicFactor or 0.0, n.name))
for c in n.children or []: walk(c, M)
for r in g.scenes[g.scene or 0].nodes: walk(r, np.eye(4))
def R(view):
def rz(d): t = math.radians(d); return np.array([[math.cos(t), -math.sin(t), 0], [math.sin(t), math.cos(t), 0], [0, 0, 1]])
def rx(d): t = math.radians(d); return np.array([[1, 0, 0], [0, math.cos(t), -math.sin(t)], [0, math.sin(t), math.cos(t)]])
return {"top": np.eye(3), "bottom": rx(180), "front": rx(-90), "side": rx(-90) @ rz(90),
"iso": rx(-55) @ rz(-35), "corner": rx(-70) @ rz(-30)}[view]
Rm = R(a.view)
allp = np.concatenate([t[0].reshape(-1, 3) for t in tris]) @ Rm.T
lo, hi = allp[:, :2].min(0), allp[:, :2].max(0); S = a.size * 3; k = 0.86 * S / max(hi - lo)
c0 = (lo + hi) / 2
img = np.zeros((S, S, 3)); img[:] = np.array([int(v) for v in a.bg.split(",")]) / 255; zb = np.full((S, S), -1e9)
L1 = np.array([-0.4, 0.5, 0.8]); L1 /= np.linalg.norm(L1); L2 = np.array([0.6, -0.3, 0.4]); L2 /= np.linalg.norm(L2)
for T, col, metal, name in tris:
V = T @ Rm.T
for tri in V:
n = np.cross(tri[1] - tri[0], tri[2] - tri[0]); ln = np.linalg.norm(n)
if ln == 0: continue
n /= ln
if n[2] < 0: n = -n
sh = 0.35 + 0.55 * max(0, n @ L1) + 0.25 * max(0, n @ L2)
c = np.clip(col ** (1 / 2.2) * sh + (0.15 * metal * max(0, n @ L1) ** 8), 0, 1)
xy = (tri[:, :2] - c0) * k + S / 2; xy[:, 1] = S - xy[:, 1]
x0, y0 = np.floor(xy.min(0)).astype(int); x1, y1 = np.ceil(xy.max(0)).astype(int)
x0, y0 = max(x0, 0), max(y0, 0); x1, y1 = min(x1, S - 1), min(y1, S - 1)
if x1 < x0 or y1 < y0: continue
X, Y = np.meshgrid(np.arange(x0, x1 + 1) + 0.5, np.arange(y0, y1 + 1) + 0.5)
(ax, ay), (bx, by), (cx, cy) = xy
d = (by - cy) * (ax - cx) + (cx - bx) * (ay - cy)
if abs(d) < 1e-12: continue
w1 = ((by - cy) * (X - cx) + (cx - bx) * (Y - cy)) / d; w2 = ((cy - ay) * (X - cx) + (ax - cx) * (Y - cy)) / d; w3 = 1 - w1 - w2
inside = (w1 >= -1e-6) & (w2 >= -1e-6) & (w3 >= -1e-6)
z = w1 * tri[0, 2] + w2 * tri[1, 2] + w3 * tri[2, 2] + (0.002 if name.startswith("Adom.LaserEtch") else 0)
sub = zb[y0:y1 + 1, x0:x1 + 1]; m = inside & (z > sub)
sub[m] = z[m]; img[y0:y1 + 1, x0:x1 + 1][m] = c
im = Image.fromarray((img * 255).astype(np.uint8)).resize((a.size, a.size), Image.LANCZOS)
im.save(a.out); print(a.out)