"""Through-hole transistor packages standing on the board: TO-220AB (3 leads), TO-220AC (2 leads), TO-247AC (3 leads) and TO-92 (straight leads). Lead 1 is at the origin, the leads run along +X, the moulded front faces -Y (TO-92: the flat face) and the metal tab or pad is at the back (+Y). The board is the XY plane: the leads' shoulders (TO-92: the seating plane) rest on it and the leads go down through it. Dimensions from the makers' package drawings, listed in the README.""" from steve import Model import math package = "TO-220AB" # TO-220AB, TO-220AC, TO-247AC, TO-92, TO-18 or TO-18-2 detail = "full" # full: body, tab and leads; simple: the body and tab only # Vishay Siliconix, "TO-220AB (High Voltage)", Package Information, document 92506, revision 30-Sep-2024 (DWG 6031), page 2 table; and # Vishay Semiconductors, "TO-220AC", Outline Dimensions, document 95221, revision 16-Jan-2026, table: (min, max) in mm, the model uses the # middle of each. A body+tab thickness, A1 tab thickness, A2 lead's back face to the tab's back, b lead width, b2 shoulder width, # c lead thickness, D overall height, D1 moulded body height, D2 metal on the back (from the top), E width, E1 back metal width, E2 tab notch # depth, e1 lead 1 to the last lead, H1 tab above the body, L lead length, L1 shoulder length, P hole diameter, Q hole centre below the top. TO220AB = dict(A=(4.25, 4.65), A1=(1.20, 1.34), A2=(2.563, 2.92), b=(0.71, 0.91), b2=(1.14, 1.78), c=(0.46, 0.61), D=(14.32, 15.86), D1=(8.39, 8.79), D2=(12.20, 12.80), E=(9.96, 10.36), E1=(8.14, 8.74), E2=(0.59, 0.69), e1=(5.08, 5.08), H1=(6.30, 6.70), L=(13.40, 14.40), L1=(3.65, 4.05), P=(3.60, 3.80), Q=(2.54, 2.94)) TO220AC = dict(A=(4.25, 4.65), A1=(1.14, 1.40), A2=(2.56, 2.92), b=(0.69, 1.01), b2=(1.20, 1.73), c=(0.36, 0.61), D=(14.85, 15.25), D1=(8.38, 9.02), D2=(11.68, 12.88), E=(10.11, 10.51), E1=(6.86, 8.89), E2=(0.76, 0.76), e1=(4.88, 5.28), H1=(5.84, 6.86), L=(13.52, 14.02), L1=(3.32, 3.82), P=(3.54, 3.73), Q=(2.60, 3.00)) # Vishay Siliconix, "TO-247AC (High Voltage)", Package Information, document 91360, revision 07-Sep-2026, version 1 (facility code 9), # table (nominal) in mm: A body thickness, A1 back face to the leads' back face, A2 exposed copper (drawn at the back face), b lead width, # b2 outer shoulders, b4 middle shoulder, c lead thickness, D height, D1 x E1 the exposed copper, D2 its top below the top, E width, E2 side # slot width, e pitch, L lead length, L1 shoulder length, P hole diameter, P1 copper ring round the hole (ref), Q hole and slots below the top. TO247AC = dict(A=5.02, A1=2.41, A2=1.27, b=1.20, b2=2.00, b4=3.00, c=0.50, D=20.55, D1=16.76, D2=0.66, E=15.70, E1=14.02, E2=4.91, e=5.46, L=15.15, L1=4.06, P=3.61, P1=7.19, Q=5.50) # Texas Instruments, LP0003A "TO-92 - 5.34 mm max height", drawing 4215214/C 04/2025 (ti.com/lit/pdf/msot002), straight lead option: # (min, max) in mm, the model uses the middle: W body width, H body height, T body depth (round back to flat face), K round back to the leads' # centre line, F flat face width (min), b lead width, c lead thickness, e pitch (1.27 +/-0.13), L lead length (12.7 min), S the seating plane # below the body ((1.5) typical, reference). TO92 = dict(W=(4.44, 5.21), H=(4.32, 5.34), T=(3.17, 4.19), K=(2.03, 2.67), F=(3.43, 3.43), b=(0.38, 0.55), c=(0.35, 0.43), e=(1.27, 1.27), L=(12.7, 12.7), S=(1.5, 1.5)) # Central Semiconductor, "Package Details - TO-18" (REV: R1; R0 21-June 2004) and "TO-18 2-Lead Case" (REV: R0; 13-March 2013), table # (min, max) in mm, the model uses the middle (C: its max, the only value; E: 12.70 min): A flange Ø, B can Ø, C flange thickness, D height, # E lead length, F lead Ø, G lead circle Ø (3 leads) or lead spacing (2 leads), H lead 2 off the line of leads 1 and 3, I tab width, # J tab length (read as beyond the flange's edge); the tab is at 45 deg, beside lead 1. TO18 = dict(A=(5.31, 5.84), B=(4.52, 4.95), C=(0.76, 0.76), D=(4.32, 5.33), E=(12.70, 12.70), F=(0.41, 0.48), G=(2.54, 2.54), H=(1.27, 1.27), I=(0.91, 1.17), J=(0.71, 1.22)) # Proportions, for looks (README): the TO-220 tab's side notches are 1.4 high, from 1.4 to 2.8 below the top (as drawn); the TO-220AC's cut-off middle lead isn't # modelled; the TO-247's side slots are half-round (Ø E2) and the copper ring P1 isn't modelled; the TO-92's flat face is cut from a round body # W across; every lead's shoulder narrows to the lead in one step. NOTCH = (1.4, 2.8) if package not in ("TO-220AB", "TO-220AC", "TO-247AC", "TO-92", "TO-18", "TO-18-2"): raise ValueError("package: TO-220AB, TO-220AC, TO-247AC, TO-92, TO-18 or TO-18-2") def mid(table): return {k: (v[0] + v[1]) / 2 if isinstance(v, tuple) else v for k, v in table.items()} model = Model() def box(fid, x0, x1, y0, y1, z0, z1, body, op, name): model.plane(f"{fid}_plane", on="XY", offset=z0, name=name) with model.sketch(f"{fid}_sketch", on=f"{fid}_plane", name=name) as sk: sk.rect(f"{fid}_rect", (x0, y0), (x1, y1)) model.extrude(fid, f"{fid}_sketch", distance=z1 - z0, op=op, body=body, name=name) def through_y(fid, profile, y0, y1, body, op, name): """A prism of `profile` (x, z points, or a circle (x, z, r)) from y0 to y1 (y0 > y1: an XZ plane at y0 extrudes along -Y).""" model.plane(f"{fid}_plane", on="XZ", offset=-y0, name=name) with model.sketch(f"{fid}_sketch", on=f"{fid}_plane", name=name) as sk: if len(profile) == 3 and not isinstance(profile[0], tuple): sk.circle(f"{fid}_circle", (profile[0], profile[1]), profile[2]) else: sk.polyline(f"{fid}_outline", profile, closed=True) model.extrude(fid, f"{fid}_sketch", distance=y0 - y1, op=op, body=body, name=name) def leads(xs, b, c, L, shoulder, widths, top): """Each lead: its shoulder (width from `widths`) from the body's underside `top` down `shoulder`, then the lead (b x c) to L below it.""" model.plane("leads_plane", on="XY", offset=top - L, name="Leads") with model.sketch("leads_sketch", on="leads_plane", name="Leads") as sk: for i, x in enumerate(xs): sk.rect(f"lead_{i}", (x - b / 2, -c / 2), (x + b / 2, c / 2)) model.extrude("leads", "leads_sketch", distance=L - shoulder, op="new", body="leads_body", name="Leads") model.plane("shoulders_plane", on="XY", offset=top - shoulder, name="Lead shoulders") with model.sketch("shoulders_sketch", on="shoulders_plane", name="Lead shoulders") as sk: for i, (x, w) in enumerate(zip(xs, widths)): sk.rect(f"shoulder_{i}", (x - w / 2, -c / 2), (x + w / 2, c / 2)) model.extrude("shoulders", "shoulders_sketch", distance=shoulder, op="join", body="leads_body", name="Lead shoulders") bodies = {} if package in ("TO-220AB", "TO-220AC"): t = mid(TO220AB if package == "TO-220AB" else TO220AC) xs = [0.0, t["e1"] / 2, t["e1"]] if package == "TO-220AB" else [0.0, t["e1"]] cx = t["e1"] / 2 back = t["c"] / 2 + t["A2"] front = back - t["A"] z0 = t["L1"] # the shoulders rest on the board top = z0 + t["D"] E, E1 = t["E"], t["E1"] # Moulded body, with the back recessed where the metal shows (E1 wide, from the body's top down to D2 below the package's top). box("body", cx - E / 2, cx + E / 2, front, back, z0, z0 + t["D1"], "body_body", "new", "Body") box("back_recess", cx - E1 / 2, cx + E1 / 2, back - t["A1"], back, top - t["D2"], z0 + t["D1"], "body_body", "cut", "Back metal recess") # Metal tab: E wide above the body, E1 wide down the back; side notches E2 deep at the hole; the hole P at Q below the top. box("tab", cx - E / 2, cx + E / 2, back - t["A1"], back, z0 + t["D1"], top, "tab_body", "new", "Tab") box("tab_back", cx - E1 / 2, cx + E1 / 2, back - t["A1"], back, top - t["D2"], z0 + t["D1"], "tab_body", "join", "Tab") zq = top - t["Q"] for side, x0 in (("left", cx - E / 2), ("right", cx + E / 2 - t["E2"])): box(f"notch_{side}", x0, x0 + t["E2"], back - t["A1"], back, top - NOTCH[1], top - NOTCH[0], "tab_body", "cut", "Tab notch") through_y("tab_hole", (cx, zq, t["P"] / 2), back + 0.1, back - t["A1"] - 0.1, "tab_body", "cut", "Mounting hole") bodies = {"body_body": ("pa6", "#1c1c1e"), "tab_body": ("copper", "#c8ccd0")} if detail == "full": leads(xs, t["b"], t["c"], t["L"], t["L1"], [t["b2"]] * len(xs), z0) elif package == "TO-247AC": t = TO247AC xs = [0.0, t["e"], 2 * t["e"]] cx = t["e"] back = t["c"] / 2 + t["A1"] front = back - t["A"] z0 = t["L1"] top = z0 + t["D"] E = t["E"] box("body", cx - E / 2, cx + E / 2, front, back, z0, top, "body_body", "new", "Body") zq = top - t["Q"] for side, x in (("left", cx - E / 2), ("right", cx + E / 2)): through_y(f"slot_{side}", (x, zq, t["E2"] / 2), back + 0.1, front - 0.1, "body_body", "cut", "Side slot") # Exposed copper on the back, flush: D1 x E1 from D2 below the top, A2 thick, with the hole through it. box("pad_recess", cx - t["E1"] / 2, cx + t["E1"] / 2, back - t["A2"], back, top - t["D2"] - t["D1"], top - t["D2"], "body_body", "cut", "Copper recess") box("pad", cx - t["E1"] / 2, cx + t["E1"] / 2, back - t["A2"], back, top - t["D2"] - t["D1"], top - t["D2"], "pad_body", "new", "Exposed copper") through_y("hole", (cx, zq, t["P"] / 2), back + 0.1, front - 0.1, "body_body", "cut", "Mounting hole") through_y("pad_hole", (cx, zq, t["P"] / 2), back + 0.1, front - 0.1, "pad_body", "cut", "Mounting hole") for side, x in (("left", cx - E / 2), ("right", cx + E / 2)): through_y(f"pad_slot_{side}", (x, zq, t["E2"] / 2), back + 0.1, front - 0.1, "pad_body", "cut", "Side slot") bodies = {"body_body": ("pa6", "#1c1c1e"), "pad_body": ("copper", "#c8ccd0")} if detail == "full": leads(xs, t["b"], t["c"], t["L"], t["L1"], [t["b2"], t["b4"], t["b2"]], z0) elif package in ("TO-18", "TO-18-2"): # Metal can: the flange (header) on the board, the can on it; leads on a circle round the axis (lead 1 at the origin, lead 3 or lead 2 # at +X by G, the 3-lead part's lead 2 H off that line along -Y); the tab at 45 deg toward -X +Y, beside lead 1. t = mid(TO18) cx = t["G"] / 2 xs = [(0.0, 0.0), (t["G"], 0.0)] if package == "TO-18-2" else [(0.0, 0.0), (cx, -t["H"]), (t["G"], 0.0)] model.plane("flange_plane", on="XY", offset=0.0, name="Flange") with model.sketch("flange_sketch", on="flange_plane", name="Flange") as sk: sk.circle("flange_round", (cx, 0.0), t["A"] / 2) u = (-math.sqrt(0.5), math.sqrt(0.5)) v = (u[1], -u[0]) r0, r1, w = t["A"] / 2 - 0.3, t["A"] / 2 + t["J"], t["I"] / 2 sk.polyline("tab_outline", [(cx + u[0] * r + v[0] * s, u[1] * r + v[1] * s) for r, s in ((r0, -w), (r1, -w), (r1, w), (r0, w))], closed=True) model.extrude("flange", "flange_sketch", distance=t["C"], op="new", body="header_body", name="Flange and tab") model.plane("can_plane", on="XY", offset=t["C"], name="Can") with model.sketch("can_sketch", on="can_plane", name="Can") as sk: sk.circle("can_round", (cx, 0.0), t["B"] / 2) model.extrude("can", "can_sketch", distance=t["D"] - t["C"], op="new", body="can_body", name="Can") bodies = {"header_body": ("nickel", "#c9ccd1"), "can_body": ("steel-dc01", "#d5d8dc")} if detail == "full": model.plane("leads_plane", on="XY", offset=-t["E"], name="Leads") with model.sketch("leads_sketch", on="leads_plane", name="Leads") as sk: for i, (x, y) in enumerate(xs): sk.circle(f"lead_{i}", (x, y), t["F"] / 2) model.extrude("leads", "leads_sketch", distance=t["E"], op="new", body="leads_body", name="Leads") else: t = mid(TO92) xs = [0.0, t["e"], 2 * t["e"]] cx = t["e"] R = t["W"] / 2 yc = t["K"] - R # the round body's axis: K from the round back to the leads, so the back is at +K flat = t["K"] - t["T"] # the flat face (-Y) z0 = t["S"] n = 24 a0 = math.asin(max(-1.0, min(1.0, (flat - yc) / R))) pts = [(cx + R * math.cos(a), yc + R * math.sin(a)) for a in (a0 + (math.pi - 2 * a0) * k / n for k in range(n + 1))] model.plane("body_plane", on="XY", offset=z0, name="Body") with model.sketch("body_sketch", on="body_plane", name="Body") as sk: sk.polyline("body_outline", [(cx + R * math.cos(a0), flat)] + pts[1:-1] + [(cx - R * math.cos(a0), flat)], closed=True) model.extrude("body", "body_sketch", distance=t["H"], op="new", body="body_body", name="Body") bodies = {"body_body": ("pa6", "#1c1c1e")} if detail == "full": model.plane("leads_plane", on="XY", offset=z0 - t["L"], name="Leads") with model.sketch("leads_sketch", on="leads_plane", name="Leads") as sk: for i, x in enumerate(xs): sk.rect(f"lead_{i}", (x - t["b"] / 2, -t["c"] / 2), (x + t["b"] / 2, t["c"] / 2)) model.extrude("leads", "leads_sketch", distance=t["L"], op="new", body="leads_body", name="Leads") if detail == "full": bodies["leads_body"] = ("nickel", "#d8c58a") if package.startswith("TO-18") else ("copper", "#c9ccd1") # Materials, for looks and a rough mass: the moulding is epoxy (nylon, PA6, the nearest in the list), the tab and pad copper (nickel or tin # plated), the leads tinned copper. TO-18 (Central's material sheets): the header and leads Kovar (density 8.36; nickel, 8.9, is the nearest # in the list), the can a nickel-plated iron alloy (steel). for body, (material, colour) in bodies.items(): model.material(material, bodies=[body], color=colour) result = model.result()