main
John Lauer Add symbol, footprint, Fusion and Altium libraries, viewers, provenance, README ab389b2 3h ago
#!/usr/bin/env python3
"""Build TI's DVG0010A package (10-pin wide-body SOIC used by TMCS1123) from the package drawing
(SBOSA24D p.45, drawing 4226847/C 10/2022) with CadQuery, in the Adom / KiCad 3D frame: Z up, seated on z = 0,
centred on the footprint origin, pins along x, pin 1 at (-x, +y) (footprint pad 1 at (-x, -y) in KiCad's Y-down
footprint). Lead path construction (radiused gull-wing: shoulder, top bend, angled rise, bottom bend, flat foot)
follows the build_soic8.py builder on adom/mcp2562fd-e-sn.
Pins 1 (IN+) and 2 (IN-) are the two wide current leads on the -x side (4.12..4.32 wide, centred at y = +-2.54);
pins 3..10 are 0.31..0.51 wide on the +x side at 1.27 mm pitch, pin 3 at -y, pin 10 at +y.
usage: build_dvg10.py out.step [params.json]   (assembly: body (black epoxy), pins (tin))"""
import sys, json, math
import cadquery as cq

P = dict(
    E=10.30,            # overall lead span, drawing 9.97 .. 10.63 TYP -> mid
    E1=7.50,            # molded width, 7.4 .. 7.6
    D=10.30,            # molded length, 10.1 .. 10.5
    A=2.55,             # overall height, drawing 2.65 MAX (not printed nominal; chosen inside the limit)
    A1=0.20,            # standoff, 0.1 .. 0.3 -> mid
    b=0.41,             # signal lead width, 0.31 .. 0.51 -> mid
    bw=4.22,            # current lead width, 4.12 .. 4.32 -> mid
    yw=2.54,            # current lead centre offset from the package centre line, 2.44 .. 2.64 -> mid
    e=1.27,             # signal pitch, 7X 1.27
    c=0.275,            # lead thickness, 0.23 .. 0.32 -> mid
    parting=1.10,       # parting line height: not dimensioned; read off the side view (about 0.43 A)
    top_inset=0.15, bot_inset=0.10,   # mold draft insets per side: not dimensioned (JEDEC MS-013 style draft)
    r_in=0.10,          # inner bend radius: not dimensioned (as KiCad's gull-wing generator)
    lead_angle=8.0,     # rise off vertical, degrees: not dimensioned
    foot_flat=0.50,     # flat foot at z = 0; gauge-plane L = foot_flat + 0.354 = 0.854 (drawing 0.40 .. 1.27)
    pin1=dict(x=-2.60, y=4.00, d=0.90, depth=0.05),  # dimple inside the drawing's PIN 1 ID AREA (top-left)
    detail="full",      # full | low (bends as 3 chords) | superlow (1 chord, undrafted body)
)
if len(sys.argv) > 2:
    P.update(json.load(open(sys.argv[2])))

def path_pts():
    c, r = P["c"], P["r_in"] + P["c"] / 2
    tip = P["E"] / 2; a = math.radians(P["lead_angle"])
    x_fb = tip - P["foot_flat"]; cb = (x_fb, c / 2 + r)
    pb_end = (cb[0] - r * math.cos(a), cb[1] - r * math.sin(a))
    ct_z = P["parting"] - r
    pt_start_z = ct_z + r * math.sin(a)
    dz = pt_start_z - pb_end[1]
    pt_start = (pb_end[0] - dz * math.tan(a), pt_start_z)
    ct = (pt_start[0] - r * math.cos(a), ct_z)
    return dict(inner=P["E1"] / 2 - 0.40, x_sh=ct[0], ct=ct, pb_end=pb_end, cb=cb, x_fb=x_fb, tip=tip, r=r)

def arc(wp, c, r, t0, t1, s, top):
    pt = (lambda t: (c[0] + r * math.sin(t), c[1] + r * math.cos(t))) if top else (lambda t: (c[0] - r * math.cos(t), c[1] - r * math.sin(t)))
    if P["detail"] == "full":
        m, e = pt((t0 + t1) / 2), pt(t1)
        return wp.threePointArc((s * m[0], m[1]), (s * e[0], e[1]))
    n = 3 if P["detail"] == "low" else 1
    for i in range(1, n + 1):
        q = pt(t0 + (t1 - t0) * i / n); wp = wp.lineTo(s * q[0], q[1])
    return wp

def make_lead(y, s, width):
    q = path_pts(); r, a = q["r"], math.radians(P["lead_angle"]); zc = P["parting"]
    wp = cq.Workplane("XZ").moveTo(s * q["inner"], zc).lineTo(s * q["x_sh"], zc)
    wp = arc(wp, q["ct"], r, 0.0, math.pi / 2 - a, s, top=True)
    wp = wp.lineTo(s * q["pb_end"][0], q["pb_end"][1])
    wp = arc(wp, q["cb"], r, a, math.pi / 2, s, top=False)
    wp = wp.lineTo(s * q["tip"], P["c"] / 2)
    path = wp.wire().val()
    # XZ workplane maps local y to global z but local "out of plane" to -y: build at y = 0 and move
    prof = cq.Workplane(cq.Plane(origin=(s * q["inner"], 0, zc), xDir=(0, 1, 0), normal=(s, 0, 0))).rect(width, P["c"])
    solid = prof.sweep(cq.Workplane().add(path), transition="round" if P["detail"] == "full" else "right").val()
    return solid.translate(cq.Vector(0, y, 0))

def body():
    E1, D, A, A1, pz = P["E1"], P["D"], P["A"], P["A1"], P["parting"]
    p1 = P["pin1"]
    if P["detail"] == "superlow":
        b = cq.Workplane("XY").workplane(offset=A1).rect(E1, D).extrude(A - A1)
    else:
        lo = cq.Workplane("XY").workplane(offset=A1).rect(E1 - 2 * P["bot_inset"], D - 2 * P["bot_inset"]) \
            .workplane(offset=pz - A1).rect(E1, D).loft()
        hi = cq.Workplane("XY").workplane(offset=pz).rect(E1, D) \
            .workplane(offset=A - pz).rect(E1 - 2 * P["top_inset"], D - 2 * P["top_inset"]).loft()
        b = lo.union(hi)
    dimple = cq.Workplane("XY").workplane(offset=A - p1["depth"]).center(p1["x"], p1["y"]).circle(p1["d"] / 2).extrude(p1["depth"] + 0.01)
    return b.cut(dimple).val()

def main(out):
    q = path_pts()
    assert q["x_sh"] > P["E1"] / 2, f"shoulder {q['x_sh']:.3f} inside the body"
    leads = [make_lead(+P["yw"], -1, P["bw"]),          # pin 1 IN+  (-x, +y)
             make_lead(-P["yw"], -1, P["bw"])]          # pin 2 IN-  (-x, -y)
    for k in range(3, 11):                              # pin 3 at -y .. pin 10 at +y on +x
        leads.append(make_lead(-3.5 * P["e"] + (k - 3) * P["e"], +1, P["b"]))
    bd = body()
    pins = cq.Compound.makeCompound([l.cut(bd) for l in leads])
    asm = cq.Assembly(name="DVG0010A")
    asm.add(bd, name="body", color=cq.Color(0.1, 0.1, 0.1))
    asm.add(pins, name="pins", color=cq.Color(0.82, 0.82, 0.80))
    asm.save(out, exportType="STEP")
    L = P["foot_flat"] + (P["r_in"] + P["c"]) * math.cos(math.asin((P["r_in"] + P["c"] - 0.25) / (P["r_in"] + P["c"])))
    print(json.dumps({"out": out, "shoulder_x": round(q["x_sh"], 4), "gauge_L_mm": round(L, 4), "params": P}))

if __name__ == "__main__":
    main(sys.argv[1])