main
noah Add datasheet, figures, hero card, provenance, verification log and README a043b0e 8h ago
#!/usr/bin/env python3
"""Check the 61300411121 footprint against the Wurth datasheet's recommended hole pattern (rev 003.000, p.1) and
draw the evidence image: the datasheet's hole-pattern view beside the footprint drawn to scale in the same pose.

    check_footprint.py <61300411121.kicad_mod> <datasheet hole-pattern crop.png> <out.json> <out.png>
"""
import json, math, re, sys

kmod, crop, out_json, out_png = sys.argv[1:5]
text = open(kmod).read()
pads = []
for m in re.finditer(r'\(pad "([^"]+)" (\w+) (\w+)\s*\(at ([-\d.]+) ([-\d.]+)\)\s*\(size ([-\d.]+) ([-\d.]+)\)\s*\(drill ([-\d.]+)\)', text):
    num, kind, shape, x, y, w, h, d = m.groups()
    pads.append(dict(number=num, kind=kind, shape=shape, x=float(x), y=float(y), w=float(w), h=float(h), drill=float(d)))
pads.sort(key=lambda p: int(p["number"]))
pitches = [round(math.hypot(b["x"] - a["x"], b["y"] - a["y"]), 4) for a, b in zip(pads, pads[1:])]
pl = round(math.hypot(pads[-1]["x"] - pads[0]["x"], pads[-1]["y"] - pads[0]["y"]), 4)
ring = round(min((p["w"] - p["drill"]) / 2 for p in pads), 3)
pin_diag = round(0.64 * math.sqrt(2), 3)
rows = [
    dict(item="Positions", datasheet="4 (Article Properties: Pins 4; the drawing itself shows a generic 10-position row, 1 to End)",
         footprint=len(pads), result="pass" if len(pads) == 4 else "fail"),
    dict(item="Pitch", datasheet="2.54 +-0.05 mm", footprint=pitches, result="pass" if all(abs(v - 2.54) <= 0.05 for v in pitches) else "fail"),
    dict(item="pl, pin 1 to End", datasheet="7.62 +-0.1 mm (Pattern Properties)", footprint=pl, result="pass" if abs(pl - 7.62) <= 0.1 else "fail"),
    dict(item="Finished hole", datasheet="1.10 +-0.15 mm", footprint=sorted({p["drill"] for p in pads}),
         result="pass" if all(abs(p["drill"] - 1.10) <= 1e-6 for p in pads) else "fail"),
    dict(item="Single row", datasheet="one row", footprint="all pads at x = 0, along +Y",
         result="pass" if all(abs(p["x"]) < 1e-6 for p in pads) else "fail"),
    dict(item="Pin 1", datasheet="the end labelled 1", footprint=f"pad {pads[0]['number']} {pads[0]['shape']} at ({pads[0]['x']}, {pads[0]['y']})",
         result="pass" if pads[0]["shape"] == "rect" and pads[0]["x"] == 0 and pads[0]["y"] == 0 else "fail"),
    dict(item="Copper pad", datasheet="not specified (the drawing gives the hole only)",
         footprint=f"{pads[0]['w']} mm ({ring} mm annular ring, the same ring KiCad uses on its 1.0 mm drill / 1.7 mm pad header)",
         result="note"),
    dict(item="Pin in hole", datasheet="0.64 +-0.15 mm square pin",
         footprint=f"nominal pin diagonal {pin_diag} mm in a 1.10 mm hole: {round(1.10 - pin_diag, 3)} mm diametral clearance",
         result="pass" if pin_diag < 1.10 else "fail"),
]
res = dict(footprint=kmod, datasheet="Wurth Elektronik 61300411121 rev 003.000 (2023-01-08), p.1 Recommended Hole Pattern + Pattern Properties",
           pads=pads, rows=rows, all_pass=all(r["result"] in ("pass", "note") for r in rows),
           why_the_factory_disagreed=("The hole-pattern view is the series drawing: it shows ten holes from 1 to End with pl as a symbol; "
                                      "the 4-position value pl = 7.62 mm is only in the Pattern Properties table, and the copper pad size is not given at all. "
                                      "A reader that counts the drawn holes or invents a pad gets a different land pattern from one that reads the table, "
                                      "which fits the factory's refusal (two readings did not agree). Read from the table, the pattern is unambiguous."))
json.dump(res, open(out_json, "w"), indent=1)

# evidence image
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, Rectangle
from PIL import Image

fig = plt.figure(figsize=(16, 7.2), dpi=110, facecolor="white")
ax1 = fig.add_axes([0.01, 0.06, 0.46, 0.86]); ax1.imshow(Image.open(crop)); ax1.axis("off")
ax1.set_title("Datasheet p.1: Recommended Hole Pattern (series drawing) and Pattern Properties", fontsize=11)
ax2 = fig.add_axes([0.52, 0.12, 0.46, 0.78]); ax2.set_aspect("equal")
# draw horizontally like the datasheet: footprint +Y (pad 1 -> 4) becomes +X
for p in pads:
    cx = p["y"]; cy = 0.0
    if p["shape"] == "rect":
        ax2.add_patch(Rectangle((cx - p["w"] / 2, cy - p["h"] / 2), p["w"], p["h"], color="#c8962c", alpha=0.85))
    else:
        ax2.add_patch(Circle((cx, cy), p["w"] / 2, color="#c8962c", alpha=0.85))
    ax2.add_patch(Circle((cx, cy), p["drill"] / 2, color="white"))
    ax2.add_patch(Rectangle((cx - 0.32, cy - 0.32), 0.64, 0.64, fill=False, ec="#333333", lw=1.0, ls="--"))
    ax2.text(cx, -1.35, p["number"], ha="center", va="top", fontsize=12)
ax2.annotate("", xy=(0, 1.7), xytext=(7.62, 1.7), arrowprops=dict(arrowstyle="<->", lw=1))
ax2.text(3.81, 1.85, f"pl = {pl} mm (table: 7.62 +-0.1)", ha="center", va="bottom", fontsize=11)
ax2.annotate("", xy=(5.08, 1.15), xytext=(7.62, 1.15), arrowprops=dict(arrowstyle="<->", lw=1))
ax2.text(6.35, 1.25, f"{pitches[-1]} (2.54 +-0.05)", ha="center", va="bottom", fontsize=10)
ax2.text(0, -2.3, f"drill {pads[0]['drill']} mm (1.10 +-0.15)\npad {pads[0]['w']} mm, pad 1 square\ndashed: 0.64 mm pin", ha="left", va="top", fontsize=10)
ax2.set_xlim(-1.6, 9.3); ax2.set_ylim(-4.2, 2.8)
ax2.set_xlabel("mm (footprint +Y drawn to the right, as the datasheet draws pin 1 to End)")
ax2.set_title("Page footprint 61300411121.kicad_mod, to scale", fontsize=11)
ax2.grid(True, lw=0.3, alpha=0.5)
fig.savefig(out_png)
print(json.dumps(dict(rows=rows, all_pass=res["all_pass"]), indent=1))