"""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-220F, TO-220F-2, TO-262, TO-247AC, TO-126, TO-92, TO-18, TO-18-2, TO-39 or TO-46 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) # Central Semiconductor, "Package Details - TO-39" (REV R1, R0 21-June 2004) and "Package Details - TO-46" (REV R2, R0 17-July 2008), tables # (min, max) in mm, the letters as TO18's (C: the max, the only value; E: 12.70 min). TO-46's sheet names the tab width J and its length K; # here they are I and J, as on the TO-18 and TO-39 sheets. TO39 = dict(A=(8.51, 9.40), B=(8.00, 8.51), C=(1.02, 1.02), D=(6.10, 6.60), E=(12.70, 12.70), F=(0.41, 0.53), G=(5.08, 5.08), H=(2.54, 2.54), I=(0.71, 0.86), J=(0.74, 1.14)) TO46 = dict(A=(5.31, 5.84), B=(4.52, 4.95), C=(1.02, 1.02), D=(1.65, 2.16), E=(12.70, 12.70), F=(0.41, 0.53), G=(2.54, 2.54), H=(1.27, 1.27), I=(0.91, 1.17), J=(0.71, 1.22)) CANS = {"TO-18": TO18, "TO-18-2": TO18, "TO-39": TO39, "TO-46": TO46} # Central Semiconductor, "Package Details - TO-126" (REV R3, R0 21-June 2004), table (min, max) in mm, the middle (single values as given): # A body thickness, B the leads' centre plane from the back, C lead thickness, D width, E hole centre below the top, F hole diameter, # G body height, H lead length below the body (shoulder included), J lead width, K pitch, L lead 1 to lead 3, M shoulder width, N shoulder # length. The back carries the mounting plate ("Mounting Plate Common to Pin 2"). TO126 = dict(A=(2.40, 2.80), B=(1.27, 1.27), C=(0.38, 0.75), D=(7.40, 8.50), E=(3.75, 3.75), F=(3.00, 3.40), G=(10.50, 12.00), H=(15.70, 15.70), J=(0.62, 0.90), K=(2.25, 2.25), L=(4.50, 4.50), M=(1.14, 1.40), N=(2.10, 2.10)) # Central Semiconductor, "Package Details - TO-220FP Case" (REV R5, R2 06-June 2023) and "TO-220FP-2 Case" (REV R2, R0 28-January 2015), # tables (min, max) in mm; where a sheet gives only a maximum the model uses it. A body thickness, B the thinner top part's thickness, # C the leads' centre plane from the back, D lead thickness, E width, F the thin top part's height, G body height, H shoulder length, # J lead length below the body, K lead width, L pitch (TO-220FP-2: each lead's offset from the centre line), M hole centre below the top, # N hole diameter, P shoulder width. The fully moulded body has no exposed metal. TO220F = dict(A=(4.20, 5.12), B=(2.30, 3.30), C=(2.50, 3.10), D=(0.80, 0.80), E=(9.70, 10.63), F=(6.06, 7.00), G=(14.80, 16.25), H=(4.50, 4.50), J=(12.78, 13.80), K=(0.50, 0.90), L=(2.54, 2.54), M=(2.55, 3.55), N=(2.95, 3.40), P=(1.00, 1.47)) TO220F2 = dict(A=(4.80, 4.80), B=(3.12, 3.12), C=(2.50, 2.90), D=(0.81, 0.81), E=(10.31, 10.31), F=(6.30, 6.90), G=(14.80, 15.40), H=(4.10, 4.10), J=(13.00, 13.80), K=(0.90, 0.90), L=(2.55, 2.55), M=(2.55, 2.85), N=(3.00, 3.40), P=(1.40, 1.40)) # Proportions, for looks: the TO-126's mounting plate on the back is 0.3 thick, set in 0.08 x the width from the sides and the top and # ending 0.1 x the height above the bottom (drawn dashed in the front view, not dimensioned); the TO-220FP-2's cut-off middle lead stub # (O, P, Q on its sheet) isn't modelled. PLATE_T = 0.3 # Vishay Siliconix, "I2PAK (TO-262) (High Voltage)", Package Information, document 91367, revision 27-Oct-08 (DWG 5977), table (min, max) # in mm, the middle (a single value where the table gives only a min or a max: D1, E1 min; L1 max). A body thickness, A1 the leads' back face # to the back, b lead width, b2 shoulder width, c lead thickness, c2 tab thickness, D body height, D1 x E1 the thermal pad on the back, # E width, e pitch, L lead length below the body, L1 the tab above the body, L2 shoulder length. TO262 = dict(A=(4.06, 4.83), A1=(2.03, 3.02), b=(0.51, 0.99), b2=(1.14, 1.78), c=(0.38, 0.74), c2=(1.14, 1.65), D=(8.38, 9.65), D1=(6.86, 6.86), E=(9.65, 10.67), E1=(6.22, 6.22), e=(2.54, 2.54), L=(13.46, 14.10), L1=(1.65, 1.65), L2=(3.56, 3.71)) # Proportions, for looks: the TO-262 tab above the body is the body's width with its top corners chamfered 0.04 x the width (drawn, not # dimensioned), and the thermal pad on the back is flush, from the body's top down. PACKAGES = ("TO-220AB", "TO-220AC", "TO-220F", "TO-220F-2", "TO-262", "TO-247AC", "TO-126", "TO-92", "TO-18", "TO-18-2", "TO-39", "TO-46") if package not in PACKAGES: raise ValueError("package: " + ", ".join(PACKAGES)) 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-262": # Moulded body with the metal tab: c2 thick, flush with the back, showing as the pad D1 x E1 from the body's top down and rising L1 above it. t = mid(TO262) xs = [0.0, t["e"], 2 * t["e"]] cx = t["e"] back = t["c"] / 2 + t["A1"] front = back - t["A"] z0 = t["L2"] # the shoulders rest on the board top = z0 + t["D"] E, k = t["E"], 0.04 * t["E"] box("body", cx - E / 2, cx + E / 2, front, back, z0, top, "body_body", "new", "Body") box("pad_recess", cx - t["E1"] / 2, cx + t["E1"] / 2, back - t["c2"], back, top - t["D1"], top, "body_body", "cut", "Thermal pad recess") box("pad", cx - t["E1"] / 2, cx + t["E1"] / 2, back - t["c2"], back, top - t["D1"], top, "tab_body", "new", "Thermal pad") through_y("tab_top", [(cx - E / 2, top), (cx + E / 2, top), (cx + E / 2, top + t["L1"] - k), (cx + E / 2 - k, top + t["L1"]), (cx - E / 2 + k, top + t["L1"]), (cx - E / 2, top + t["L1"] - k)], back, back - t["c2"], "tab_body", "join", "Tab") bodies = {"body_body": ("pa6", "#1c1c1e"), "tab_body": ("copper", "#c8ccd0")} if detail == "full": leads(xs, t["b"], t["c"], t["L"], t["L2"], [t["b2"]] * 3, z0) elif package in ("TO-220F", "TO-220F-2"): # Fully moulded (no exposed metal): the full thickness A up to F below the top, then the thinner top part B thick, its back face flush. t = mid(TO220F if package == "TO-220F" else TO220F2) xs = [0.0, t["L"], 2 * t["L"]] if package == "TO-220F" else [0.0, 2 * t["L"]] cx = xs[-1] / 2 back = t["C"] front = back - t["A"] z0 = t["H"] # the shoulders rest on the board top = z0 + t["G"] E = t["E"] box("body", cx - E / 2, cx + E / 2, front, back, z0, top - t["F"], "body_body", "new", "Body") box("body_top", cx - E / 2, cx + E / 2, back - t["B"], back, top - t["F"], top, "body_body", "join", "Body") through_y("hole", (cx, top - t["M"], t["N"] / 2), back + 0.1, back - t["B"] - 0.1, "body_body", "cut", "Mounting hole") bodies = {"body_body": ("pa6", "#1c1c1e")} if detail == "full": leads(xs, t["K"], t["D"], t["J"], t["H"], [t["P"]] * len(xs), z0) elif package == "TO-126": # Moulded body with the mounting plate on its back and the hole through both; leads at pitch K, their centre plane B in front of the back. t = mid(TO126) xs = [0.0, t["K"], 2 * t["K"]] cx = t["K"] back = t["B"] front = back - t["A"] z0 = t["N"] top = z0 + t["G"] W = t["D"] box("body", cx - W / 2, cx + W / 2, front, back, z0, top, "body_body", "new", "Body") m = 0.08 * W box("plate_recess", cx - W / 2 + m, cx + W / 2 - m, back - PLATE_T, back, z0 + 0.1 * t["G"], top - m, "body_body", "cut", "Plate recess") box("plate", cx - W / 2 + m, cx + W / 2 - m, back - PLATE_T, back, z0 + 0.1 * t["G"], top - m, "plate_body", "new", "Mounting plate") through_y("hole", (cx, top - t["E"], t["F"] / 2), back + 0.1, front - 0.1, "body_body", "cut", "Mounting hole") through_y("plate_hole", (cx, top - t["E"], t["F"] / 2), back + 0.1, front - 0.1, "plate_body", "cut", "Mounting hole") bodies = {"body_body": ("pa6", "#1c1c1e"), "plate_body": ("copper", "#c8ccd0")} if detail == "full": leads(xs, t["J"], t["C"], t["H"], t["N"], [t["M"]] * 3, 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 CANS: # 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(CANS[package]) 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 in CANS 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()