main
noah Publish tmc2262 component page (1/5) a7e2d34 7d ago
"""Datasheet-built 3D models (CadQuery) for the LAN8770M (32-VQFN 5x5, Microchip C04-1268 Rev A) and the
TMC2262 (30L FC2QFN 6x6, ADI 21-100708 Rev A).

    ../.venv-cq/bin/python tools/build_models.py <out_dir>

Frame: Adom Z-up, millimetres, the part seated on z = 0 (the terminals' solder faces), centred on the footprint
origin. KiCad footprints are Y-down, so footprint (x, y) is model (x, -y): pin 1 sits at the top-left seen from
above. Nominal dimensions from each drawing's table; every value is cited in the provenance JSON this writes.
Mould compound is near-black, terminals matte tin. No part number is modelled (the virtual mark is added later by
the mark planner); the pin-1 dimple is the only top feature.
"""
import json, math, os, sys
import cadquery as cq

BODY = cq.Color(0.10, 0.10, 0.11)
TIN = cq.Color(0.80, 0.80, 0.82)


def rect(x0, y0, x1, y1, z0, z1):
    return cq.Workplane('XY').box(abs(x1 - x0), abs(y1 - y0), z1 - z0, centered=False).translate((min(x0, x1), min(y0, y1), z0))


def lead_rounded(cx, edge, inward, width, length, z0, z1, axis):
    """A QFN terminal of `width` running `length` inward from the package edge, inner end a full radius.
    axis 'y': edge is a y value, inward is -1/+1 along y; axis 'x' likewise."""
    r = width / 2
    if axis == 'y':
        y_in = edge + inward * (length - r)
        box = rect(cx - r, edge, cx + r, y_in, z0, z1)
        cap = cq.Workplane('XY').circle(r).extrude(z1 - z0).translate((cx, y_in, z0))
    else:
        x_in = edge + inward * (length - r)
        box = rect(edge, cx - r, x_in, cx + r, z0, z1)
        cap = cq.Workplane('XY').circle(r).extrude(z1 - z0).translate((x_in, cx, z0))
    return box.union(cap)


def save(parts, body, out, name):
    asm = cq.Assembly(name=name)
    asm.add(body, name='body', color=BODY)
    for i, (n, p) in enumerate(parts):
        asm.add(p, name='pin_%s' % n, color=TIN)
    path = os.path.join(out, name + '.step')
    asm.save(path)
    return path


# ------------------------------------------------------------------------------------------------ LAN8770M
def lan8770m(out):
    d = dict(D=5.00, E=5.00, A=0.85, A1=0.02, A3=0.203, b=0.25, L=0.40, e=0.50, D2=3.10, E2=3.10,
             A4=0.125, D3=0.04, pin1_dimple=0.50, ep_chamfer=0.30)
    src = 'Microchip DS00002550C Figure 3-1/3-2, drawing C04-1268 Rev A (nominal)'
    h = d['D'] / 2
    z0, z1 = 0.0, d['A3']
    pins = []
    for i in range(8):
        c = 1.75 - i * d['e']                         # model y of pins 1..8 (left side, top to bottom)
        pins.append((str(1 + i), lead_rounded(c, -h, +1, d['b'], d['L'], z0, z1, 'x')))
        pins.append((str(9 + i), lead_rounded(-1.75 + i * d['e'], -h, +1, d['b'], d['L'], z0, z1, 'y')))   # bottom, left to right
        pins.append((str(17 + i), lead_rounded(-1.75 + i * d['e'], h, -1, d['b'], d['L'], z0, z1, 'x')))   # right, bottom to top
        pins.append((str(25 + i), lead_rounded(1.75 - i * d['e'], h, -1, d['b'], d['L'], z0, z1, 'y')))    # top, right to left
    # wettable flank: a step cut D3 deep, A4 high at the outer end of every terminal (section A-A)
    cut = []
    for n, p in pins:
        bb = p.val().BoundingBox()
        if abs(bb.xmin + h) < 1e-6:
            cut.append(rect(-h, bb.ymin, -h + d['D3'], bb.ymax, 0, d['A4']))
        elif abs(bb.xmax - h) < 1e-6:
            cut.append(rect(h - d['D3'], bb.ymin, h, bb.ymax, 0, d['A4']))
        elif abs(bb.ymin + h) < 1e-6:
            cut.append(rect(bb.xmin, -h, bb.xmax, -h + d['D3'], 0, d['A4']))
        else:
            cut.append(rect(bb.xmin, h - d['D3'], bb.xmax, h, 0, d['A4']))
    pins = [(n, p.cut(c)) for (n, p), c in zip(pins, cut)]
    # exposed pad with its pin-1 chamfer (the drawing's isometric view), pad 33 = VSS
    s, c = d['D2'] / 2, d['ep_chamfer']
    ep = (cq.Workplane('XY').polyline([(-s + c, s), (s, s), (s, -s), (-s, -s), (-s, s - c)]).close().extrude(z1))
    pins.append(('33', ep))
    body = rect(-h, -h, h, h, 0, d['A'])
    for n, p in pins:
        body = body.cut(p)
    body = body.cut(cq.Workplane('XY').circle(d['pin1_dimple'] / 2).extrude(0.03).translate((-h + 0.65, h - 0.65, d['A'] - 0.03)))
    path = save(pins, body, out, 'LAN8770M')
    return path, d, src


# ------------------------------------------------------------------------------------------------ TMC2262
def tmc2262(out):
    d = dict(D=6.0, E=6.0, A=0.75, A1=0.203, b=0.25, L=0.40, b1=0.40, L1=0.65, L2=0.675, b2=0.54, L3=1.95,
             e=0.5, D2=1.4, E2=4.0, ep_chamfer=0.20, pin1_dimple=0.50)
    src = 'ADI package outline 21-100708 Rev A, 30L FC2QFN 6x6x0.75 (nominal); positions from land pattern 90-100245 Rev B'
    h = d['D'] / 2
    z0, z1 = 0.0, d['A1']
    small_x = [-1.9, -1.4, -0.9, -0.4, 0.4, 0.9, 1.4, 1.9]
    bar_y = [1.73, 0.855, 0.0, -0.855, -1.73]          # model y (pins 1..5 left top to bottom; 16..20 right bottom to top reversed)
    pins = []
    for i, y in enumerate(bar_y):                      # pins 1..5, left side, top (model +y) to bottom
        pins.append((str(1 + i), rect(-h, y - d['b2'] / 2, -h + d['L3'], y + d['b2'] / 2, z0, z1)))
    for i, y in enumerate(reversed(bar_y)):            # pins 16..20, right side, bottom to top
        pins.append((str(16 + i), rect(h - d['L3'], y - d['b2'] / 2, h, y + d['b2'] / 2, z0, z1)))
    for i, x in enumerate(small_x):                    # pins 7..14 bottom row left to right; 22..29 top row right to left
        pins.append((str(7 + i), lead_rounded(x, -h, +1, d['b'], d['L'], z0, z1, 'y')))
        pins.append((str(29 - i), lead_rounded(x, h, -1, d['b'], d['L'], z0, z1, 'y')))

    def corner(sx, sy):
        """L terminal: a b1-wide leg from the top/bottom edge L1 long at x = sx*2.525, plus a leg to the side edge
        L2 long, b1 high, both legs meeting at the inner corner (21-100708 bottom view)."""
        xc = sx * 2.525
        x_in = xc - sx * d['b1'] / 2
        y_edge, y_in = sy * h, sy * (h - d['L1'])
        leg1 = rect(xc - d['b1'] / 2, y_in, xc + d['b1'] / 2, y_edge, z0, z1)
        leg2 = rect(x_in, y_in, sx * h, y_in + sy * d['b1'], z0, z1)
        return leg1.union(leg2)
    pins.append(('6', corner(-1, -1)))
    pins.append(('15', corner(+1, -1)))
    pins.append(('21', corner(+1, +1)))
    pins.append(('30', corner(-1, +1)))
    sx, sy, c = d['D2'] / 2, d['E2'] / 2, d['ep_chamfer']
    ep = cq.Workplane('XY').polyline([(-sx + c, sy), (sx, sy), (sx, -sy), (-sx, -sy), (-sx, sy - c)]).close().extrude(z1)
    pins.append(('EP', ep))
    body = rect(-h, -h, h, h, 0, d['A'])
    for n, p in pins:
        body = body.cut(p)
    body = body.cut(cq.Workplane('XY').circle(d['pin1_dimple'] / 2).extrude(0.03).translate((-h + 0.7, h - 0.7, d['A'] - 0.03)))
    path = save(pins, body, out, 'TMC2262AFV+')
    return path, d, src


if __name__ == '__main__':
    out = sys.argv[1] if len(sys.argv) > 1 else '.'
    os.makedirs(out, exist_ok=True)
    rec = {}
    for fn in (lan8770m, tmc2262):
        path, dims, src = fn(out)
        bb = cq.importers.importStep(path).val().BoundingBox()
        rec[os.path.basename(path)] = dict(source=src, dimensions_mm=dims, frame='Adom Z-up, mm, seated on z = 0, footprint origin, pin 1 top-left',
                                           bbox_mm=[round(v, 4) for v in (bb.xmin, bb.ymin, bb.zmin, bb.xmax, bb.ymax, bb.zmax)],
                                           tool='CadQuery %s (tools/build_models.py)' % cq.__version__)
        print('OK', path, rec[os.path.basename(path)]['bbox_mm'])
    json.dump(rec, open(os.path.join(out, 'models.provenance.json'), 'w'), indent=1)