component
Wurth 61300411121 (WR-PHD 1x04 2.54 mm pin header)
Public Made by Adomby adom
Wurth Elektronik WR-PHD 61300411121: 4-pin, 2.54 mm, single-row THT vertical pin header, gold, 3 A. KiCad symbol, footprint at the datasheet's 1.10 mm hole, and KiCad's generic 1x04 header 3D model checked against the drawing.
main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
#!/usr/bin/env python3
"""Measure the 61300411121 3D model (step2glb GLB of the STEP) against the Wurth datasheet drawing.
measure_model.py <model.glb> <out.json>
The GLB from step2glb is in metres. The two materials are the insulator (dark) and the contacts (gold). Every
dimension the datasheet prints is compared with its tolerance; the verdict per row is pass or fail.
"""
import json, sys
import numpy as np
from pygltflib import GLTF2
glb, out = sys.argv[1], sys.argv[2]
g = GLTF2().load(glb)
blob = g.binary_blob()
def acc(i):
a = g.accessors[i]
bv = g.bufferViews[a.bufferView]
n = {"SCALAR": 1, "VEC2": 2, "VEC3": 3, "VEC4": 4}[a.type]
dt = {5126: np.float32, 5125: np.uint32, 5123: np.uint16, 5122: np.int16, 5121: np.uint8, 5120: np.int8}[a.componentType]
off = (bv.byteOffset or 0) + (a.byteOffset or 0)
size = n * np.dtype(dt).itemsize
stride = bv.byteStride or size
raw = np.frombuffer(blob, np.uint8, stride * (a.count - 1) + size, off)
out = np.lib.stride_tricks.as_strided(raw, (a.count, size), (stride, 1)).copy().view(dt).reshape(a.count, n).astype(float)
if a.normalized: # KHR_mesh_quantization: normalized integers
out /= {5122: 32767.0, 5120: 127.0, 5123: 65535.0, 5121: 255.0}[a.componentType]
return out
def world(node_idx, parent=np.eye(4)):
n = g.nodes[node_idx]
m = np.eye(4)
if n.matrix:
m = np.array(n.matrix).reshape(4, 4).T
else:
t = np.eye(4); r = np.eye(4); s = np.eye(4)
if n.translation: t[:3, 3] = n.translation
if n.rotation:
x, y, z, w = n.rotation
r[:3, :3] = [[1 - 2 * (y * y + z * z), 2 * (x * y - z * w), 2 * (x * z + y * w)],
[2 * (x * y + z * w), 1 - 2 * (x * x + z * z), 2 * (y * z - x * w)],
[2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x * x + y * y)]]
if n.scale: s[0, 0], s[1, 1], s[2, 2] = n.scale
m = t @ r @ s
m = parent @ m
res = []
if n.mesh is not None:
res.append((n.mesh, m))
for c in n.children or []:
res += world(c, m)
return res
pts = {}
for root in g.scenes[g.scene or 0].nodes:
for mesh_i, m in world(root):
for p in g.meshes[mesh_i].primitives:
v = acc(p.attributes.POSITION).astype(float)
v = (np.c_[v, np.ones(len(v))] @ m.T)[:, :3] * 1000.0 # mm
name = g.materials[p.material].name if p.material is not None else "none"
col = g.materials[p.material].pbrMetallicRoughness.baseColorFactor if p.material is not None else None
pts.setdefault((name, tuple(round(c, 3) for c in col[:3]) if col else None), []).append(v)
groups = {}
for (name, col), vs in pts.items():
v = np.vstack(vs)
groups[name] = dict(colour=col, n=len(v), min=v.min(0).round(3).tolist(), max=v.max(0).round(3).tolist(), pts=v)
# which axis is up? the longest extent of the gold pins is the pin axis
gold = [k for k, d in groups.items() if d["colour"] and d["colour"][0] > 0.4]
body = [k for k, d in groups.items() if k not in gold]
P = groups[gold[0]]["pts"]; B = groups[body[0]]["pts"]
ext = P.max(0) - P.min(0)
up = int(np.argmax(ext))
axes = "XYZ"
# pin cross-section: vertices in the middle of the mating length (well above the insulator), cluster by position
hi = P[:, up].max()
top_of_body = B[:, up].max()
band = P[(P[:, up] > top_of_body + 0.5) & (P[:, up] < hi - 0.05)] # full square section, below the tip chamfer
others = [i for i in range(3) if i != up]
# cluster pins along the longer of the two horizontal axes
long_ax = others[int(np.argmax([ext[i] for i in others]))]
short_ax = [i for i in others if i != long_ax][0]
order = np.sort(band[:, long_ax])
splits = np.where(np.diff(order) > 1.0)[0]
edges = [order[0]] + [order[s + 1] for s in splits]
pins = []
for k, e0 in enumerate(edges):
e1 = order[splits[k]] if k < len(splits) else order[-1]
sel = band[(band[:, long_ax] >= e0 - 1e-6) & (band[:, long_ax] <= e1 + 1e-6)]
pins.append(dict(centre=round(float((sel[:, long_ax].min() + sel[:, long_ax].max()) / 2), 3),
w_long=round(float(sel[:, long_ax].max() - sel[:, long_ax].min()), 3),
w_short=round(float(sel[:, short_ax].max() - sel[:, short_ax].min()), 3)))
centres = [p["centre"] for p in pins]
pitch = [round(abs(b - a), 3) for a, b in zip(centres, centres[1:])]
zb0, zb1 = B[:, up].min(), B[:, up].max()
zp0, zp1 = P[:, up].min(), P[:, up].max()
m = dict(
up_axis=axes[up],
body_len=round(float(B[:, long_ax].max() - B[:, long_ax].min()), 3),
body_wid=round(float(B[:, short_ax].max() - B[:, short_ax].min()), 3),
body_bottom=round(float(zb0), 3), body_top=round(float(zb1), 3),
insulator_h=round(float(zb1 - zb0), 3),
pin_bottom=round(float(zp0), 3), pin_top=round(float(zp1), 3),
pin_total=round(float(zp1 - zp0), 3),
mating=round(float(zp1 - zb1), 3), tail=round(float(zb0 - zp0), 3),
pins=pins, pitch=pitch,
)
DS = [ # (what, datasheet nominal, tol, measured key) from 61300411121 rev 003.000 p.1
("Pin square", 0.64, 0.15, [p["w_long"] for p in pins] + [p["w_short"] for p in pins]),
("Pitch", 2.54, 0.05, pitch),
("Total pin length", 11.54, 0.2, [m["pin_total"]]),
("Mating length above insulator", 6.0, 0.2, [m["mating"]]),
("Tail length below insulator", 3.0, 0.2, [m["tail"]]),
("Insulator height", 2.54, 0.15, [m["insulator_h"]]),
("Insulator width", 2.54, 0.15, [m["body_wid"]]),
("Insulator length L (4 pins)", 10.16, 0.35, [m["body_len"]]),
("Pin to pin pl (1 to 4)", 7.62, 0.15, [round(abs(centres[-1] - centres[0]), 3)]),
]
rows = []
for what, nom, tol, vals in DS:
ok = all(abs(v - nom) <= tol + 1e-6 for v in vals)
rows.append(dict(dimension=what, datasheet=f"{nom} +-{tol}", model=vals if len(vals) > 1 else vals[0],
result="pass" if ok else "fail"))
res = dict(model=glb, units="mm", source="Wurth Elektronik 61300411121 datasheet rev 003.000 (2023-01-08), page 1",
method="vertices of the step2glb GLB per material (insulator, contacts); pin section from the shaft vertices between the insulator top (+0.5 mm) and the tip chamfer",
measured=m, rows=rows, all_pass=all(r["result"] == "pass" for r in rows))
json.dump(res, open(out, "w"), indent=1)
print(json.dumps(res, indent=1))