"""Axial leaded resistor, Vishay Beyschlag professional metal film MBA/SMA 0204, MBB/SMA 0207 and MBE/SMA 0414, mounted horizontal or vertical. Lead 1 is at the origin and lead 2 at +X by the pitch; the leads go down through the board. Horizontal: the body lies on the board along X, centred between the leads. Vertical: the body stands on the board over lead 1 and lead 2 comes back down beside it. The resistance and tolerance make the colour rings (IEC 60062). Dimensions from Vishay's data sheet, listed in the README.""" import math from steve import Model series = "MBA" # MBA (DIN size 0204), MBB (0207) or MBE (0414) resistance = 10000.0 # Ω Resistance (in the range the sheet gives for the series and tolerance) tolerance = "F" # D 0.5 %, F 1 %, G 2 %, J 5 % (one the sheet offers for this resistance) mounting = "horizontal" # horizontal or vertical pitch = 7.62 # mm Lead pitch on the board tail = 3.0 # mm Leads below the board detail = "full" # full: body, colour rings and leads; simple: the body only # Vishay Beyschlag "MBA/SMA 0204, MBB/SMA 0207, MBE/SMA 0414 - Professional Metal Film Leaded Resistors", document number 28766, revision # 11-Jul-2018, page 13, table "DIMENSIONS - Leaded resistor types, mass and relevant physical dimensions": D max, L max, d nom and M min # (the smallest bent lead spacing), in mm. # Each entry: (body diameter max, body length max, lead diameter, smallest bent lead spacing). SIZES = { "MBA": (1.6, 3.6, 0.5, 5.0), "MBB": (2.5, 6.5, 0.6, 10.0), "MBE": (4.2, 11.9, 0.8, 15.0), } # The same sheet, page 3, "TEMPERATURE COEFFICIENT AND RESISTANCE RANGE - Standard Products", ± 50 ppm/K rows: resistance ranges in Ω by # tolerance (the ± 25 ppm/K rows lie inside these). Values come from E24 (± 5 %, ± 2 %), E24/E96 (± 1 %) or E24/E192 (± 0.5 %). RANGES = { "MBA": {"J": ((0.22, 0.91),), "F": ((1, 10e6),), "D": ((10, 475e3),)}, "MBB": {"J": ((0.22, 0.91), (11e6, 22e6)), "G": ((0.22, 0.91),), "F": ((1, 10e6),), "D": ((10, 1e6),)}, "MBE": {"J": ((0.22, 0.91),), "F": ((1, 22e6),), "D": ((10, 2.43e6),)}, } # Colour code, IEC 60062 (the sheet, page 7: "Four or five color code rings designate the resistance value and tolerance in accordance with # IEC 60062"): ± 5 % and ± 2 % carry two digit rings, ± 1 % and ± 0.5 % three; then the multiplier ring and the tolerance ring. DIGIT_COLOURS = ("#1a1a1a", "#7a4a26", "#c8302c", "#e8792b", "#f2d22e", "#3f8f3f", "#2f5fb3", "#7d4fa8", "#8a8a8a", "#f4f4f4") MULTIPLIER_COLOURS = {-2: "#c0c0c4", -1: "#cfa646", 0: "#1a1a1a", 1: "#7a4a26", 2: "#c8302c", 3: "#e8792b", 4: "#f2d22e", 5: "#3f8f3f", 6: "#2f5fb3"} TOLERANCE_COLOURS = {"D": "#3f8f3f", "F": "#7a4a26", "G": "#c8302c", "J": "#cfa646"} if series not in SIZES: raise ValueError(f"no series {series!r}: {', '.join(SIZES)}") if tolerance not in RANGES[series]: raise ValueError(f"Vishay {series} comes in tolerance {', '.join(RANGES[series])}") if not any(low * (1 - 1e-9) <= resistance <= high * (1 + 1e-9) for low, high in RANGES[series][tolerance]): spans = " or ".join(f"{low:g} to {high:g} Ω" for low, high in RANGES[series][tolerance]) raise ValueError(f"Vishay {series} at tolerance {tolerance}: {spans}") DIGITS = 2 if tolerance in "JG" else 3 exponent = math.floor(math.log10(resistance) + 1e-9) - (DIGITS - 1) digits = round(resistance / 10 ** exponent) if digits >= 10 ** DIGITS: digits, exponent = digits // 10, exponent + 1 if not math.isclose(digits * 10.0 ** exponent, resistance, rel_tol=1e-6) or exponent not in MULTIPLIER_COLOURS: raise ValueError(f"{resistance} Ω can't be written in {DIGITS} digit rings and a multiplier ring") diameter, length, lead, spacing = SIZES[series] if mounting not in ("horizontal", "vertical"): raise ValueError("mounting is horizontal or vertical") R, r = diameter / 2, lead / 2 BEND = lead # bend radius of the formed leads, one lead diameter: a proportion, for looks (the sheet gives none) LOOP = 2.0 # vertical: lead 2 clears the body's top end by this before it turns down, a proportion if mounting == "horizontal" and pitch < max(spacing, length + 2 * BEND): raise ValueError(f"horizontal pitch: at least {max(spacing, length + 2 * BEND):.2f} mm for {series} (the sheet's M min is {spacing})") if mounting == "vertical" and pitch < max(R + r, 2 * BEND): raise ValueError(f"vertical pitch must let lead 2 pass the body: at least {max(R + r, 2 * BEND):.2f} mm for a {diameter} mm body") def body_profile(): """The body outline as (u along the axis from the body's lead-1 end, radius), and the ring segments' indices in ring order. Proportions, for looks (the sheet draws the body but dimensions only D and L): the end caps are 0.25 x the length at the full diameter, their outer edges rounded by 0.25 x the radius; the coated middle is 0.9 x the diameter, reached over 0.05 x the length. Rings 0.08 x the length wide: the first on the lead-1 cap, the tolerance ring on the other cap, the rest spread over the middle.""" e, cap, taper, mid, w = 0.25 * R, 0.25 * length, 0.05 * length, 0.9 * R, 0.08 * length rings = DIGITS + 2 middle = (cap + taper, length - cap - taper) inner = rings - 2 gap = (middle[1] - middle[0] - inner * w) / (inner + 1) centres = [(e + cap) / 2] + [middle[0] + gap * (i + 1) + w * (i + 0.5) for i in range(inner)] + [length - (e + cap) / 2] pts = [(0, 0), (0, R - e)] pts += [(e * (1 - math.cos(math.pi / 2 * i / 4)), R - e + e * math.sin(math.pi / 2 * i / 4)) for i in range(1, 5)] flats = [(e, cap, R), (middle[0], middle[1], mid), (length - cap, length - e, R)] segments = [] for k, (a, b, rad) in enumerate(flats): if k: pts.append((a, rad)) for c in centres: if a < c < b: pts += [(c - w / 2, rad), (c + w / 2, rad)] segments.append(len(pts) - 2) pts.append((b, rad)) pts += [(length - u, v) for u, v in reversed(pts[1:5])] pts += [(length, 0)] return pts, segments def ring_colours(): text = str(digits).rjust(DIGITS, "0") return [DIGIT_COLOURS[int(t)] for t in text] + [MULTIPLIER_COLOURS[exponent], TOLERANCE_COLOURS[tolerance]] model = Model() outline, rings = body_profile() horizontal = mounting == "horizontal" x0 = pitch / 2 - length / 2 axis_z = R # horizontal: the body rests on the board if horizontal: points = [(x0 + u, axis_z + v) for u, v in outline] axis = ((0, 0, axis_z), (1, 0, 0)) else: points = [(v, u) for u, v in outline] axis = "Z" with model.sketch("body_sketch", on="XZ", name="Body") as sk: sk.polyline("body_outline", points, closed=True) model.revolve("body", "body_sketch", axis=axis, op="new", body="body_body", name="Body") bodies = ["body_body"] if detail == "full": # Leads, swept along their bends. Horizontal: up from below the board at x = 0 and x = pitch to the body's axis, bent toward the body. # Vertical: lead 1 straight down from the body's bottom end; lead 2 up from the top end, over and down at x = pitch. if horizontal: legs = [(0.0, 1, x0), (pitch, -1, x0 + length)] for i, (x, side, x_end) in enumerate(legs, 1): with model.sketch(f"lead_{i}_path", on="XZ", name=f"Lead {i} path") as sk: sk.line(f"lead_{i}_up", (x, -tail), (x, axis_z - BEND), construction=True) if side > 0: sk.arc(f"lead_{i}_bend", (x + BEND, axis_z - BEND), BEND, 90, 180, construction=True) else: sk.arc(f"lead_{i}_bend", (x - BEND, axis_z - BEND), BEND, 0, 90, construction=True) sk.line(f"lead_{i}_along", (x + side * BEND, axis_z), (x_end, axis_z), construction=True) model.plane(f"lead_{i}_start", on="XY", offset=-tail, name=f"Lead {i} end") with model.sketch(f"lead_{i}_section", on=f"lead_{i}_start", name=f"Lead {i}") as sk: sk.circle(f"lead_{i}_round", (x, 0), r) model.sweep(f"lead_{i}", f"lead_{i}_section", f"lead_{i}_path", op="new", body=f"lead_{i}_body", name=f"Lead {i}") bodies.append(f"lead_{i}_body") else: model.plane("lead_1_start", on="XY", offset=-tail, name="Lead 1 end") with model.sketch("lead_1_section", on="lead_1_start", name="Lead 1") as sk: sk.circle("lead_1_round", (0, 0), r) model.extrude("lead_1", "lead_1_section", distance=tail, op="new", body="lead_1_body", name="Lead 1") top = length + LOOP with model.sketch("lead_2_path", on="XZ", name="Lead 2 path") as sk: sk.line("lead_2_up", (0, length), (0, top - BEND), construction=True) sk.arc("lead_2_bend_1", (BEND, top - BEND), BEND, 90, 180, construction=True) sk.line("lead_2_over", (BEND, top), (pitch - BEND, top), construction=True) sk.arc("lead_2_bend_2", (pitch - BEND, top - BEND), BEND, 0, 90, construction=True) sk.line("lead_2_down", (pitch, top - BEND), (pitch, -tail), construction=True) model.plane("lead_2_start", on="XY", offset=length, name="Lead 2 start") with model.sketch("lead_2_section", on="lead_2_start", name="Lead 2") as sk: sk.circle("lead_2_round", (0, 0), r) model.sweep("lead_2", "lead_2_section", "lead_2_path", op="new", body="lead_2_body", name="Lead 2") bodies += ["lead_1_body", "lead_2_body"] # Materials, for looks and a rough mass: the body as its ceramic core (the sheet: "a high grade ceramic body") in the sheet's "light blue # protective coating"; the leads copper, tin plated ("electrolytic copper plated with 100 % pure tin"). model.material("alumina", bodies=["body_body"], color="#9cc3e0") if detail == "full": model.material("copper", bodies=[b for b in bodies if b.startswith("lead")], color="#c9ccd1") for i, colour in zip(rings, ring_colours()): model.appearance(color=colour, faces=[f"body.side(body_outline.side{i})"]) result = model.result()