"""Primary injection test pin ("tulip") for testing molded case circuit breakers. One end is a milled hex that slides through the breaker's terminal cover into the set-screw box lug. A short round neck flows into a tangent "tulip" turned straight onto the square bar, so there is no sharp shoulder where the pin levers against the lug mouth. Everything behind the tulip is one solid rounded-square section (no round body, no holes) that the test cable's lug is clamped to with vise-grips. Each pin is made from one square bar of C18150 or C18200 copper alloy, TH04 temper, left bare. `size` picks one of seven sizes: the Ampteks traditional hex set (3/16, 7/32, 1/4, 5/16, 3/8, 1/2 in across flats) plus 5/8 for large frames. Hex lengths clear deep terminal covers. `hex_length` and `overall_length` can be overridden. Pin axis along +x, hex tip at the origin, a hex corner pointing +z. Dimensions are set in inches; the model is built in mm. """ import math import cadquery as cq from steve import Model size = "5" # Size hex_length = 0 # in Hex length override (0 = size default) overall_length = 0 # in Overall length override (0 = size default) IN = 25.4 LN = 0.25 # neck length SIZES = { # hex across flats, hex length, square width, corner radius, tulip length, overall length "1": dict(AF=3 / 16, Lh=2.00, sq=0.50, rc=0.06, Lf=0.50, L=8.0), "2": dict(AF=7 / 32, Lh=2.25, sq=0.50, rc=0.06, Lf=0.50, L=8.0), "3": dict(AF=1 / 4, Lh=2.25, sq=0.50, rc=0.06, Lf=0.50, L=8.0), "4": dict(AF=5 / 16, Lh=2.50, sq=0.625, rc=0.075, Lf=0.50, L=8.0), "5": dict(AF=3 / 8, Lh=3.25, sq=0.75, rc=0.09, Lf=0.50, L=8.5), "6": dict(AF=1 / 2, Lh=3.75, sq=1.00, rc=0.125, Lf=0.625, L=9.0), "7": dict(AF=5 / 8, Lh=4.00, sq=1.00, rc=0.125, Lf=0.625, L=9.5), } s = dict(SIZES[str(size)]) if hex_length: if not 1.0 <= hex_length <= 8.0: raise ValueError("hex_length must be 0 (size default) or between 1 and 8 in.") s["Lh"] = float(hex_length) if overall_length: s["L"] = float(overall_length) if s["L"] - s["Lh"] - LN - s["Lf"] < 2.0: raise ValueError("overall_length must leave at least 2 in of full square behind the tulip.") if s["L"] > 24.0: raise ValueError("overall_length must be at most 24 in.") AF, Lh, L = s["AF"], s["Lh"], s["L"] AC = AF / math.cos(math.radians(30)) # across corners Dn = math.ceil((AC + 0.01) * 200) / 200 # neck just over across-corners h = s["sq"] / 2 Rc = (h - s["rc"]) * math.sqrt(2) + s["rc"] # radial reach of the rounded corners x_neck, x_flare = Lh, Lh + LN x_full = x_flare + s["Lf"] def u(v): return v * IN # hex prism along +x with a 30 degree chamfered tip hexp = cq.Workplane("YZ").polygon(6, u(AC)).extrude(u(Lh + 0.02)) r0 = 0.45 * AF r1 = AC / 2 + 0.02 dx = (r1 - r0) * math.tan(math.radians(30)) lim = (cq.Workplane("XY").polyline([(0, 0), (0, u(r0)), (u(dx), u(r1)), (u(Lh + 0.02), u(r1)), (u(Lh + 0.02), 0)]) .close().revolve(360, (0, 0, 0), (1, 0, 0))) hexp = hexp.intersect(lim) # short round neck rn = Dn / 2 neck = (cq.Workplane("XY").moveTo(u(x_neck), 0).lineTo(u(x_neck), u(rn)).lineTo(u(x_flare + 0.01), u(rn)) .lineTo(u(x_flare + 0.01), 0).close().revolve(360, (0, 0, 0), (1, 0, 0))) # tulip turned onto the square bar: a revolved envelope intersected with the rounded-square prism Rt = Rc + 0.002 env = (cq.Workplane("XY").moveTo(u(x_flare), 0).lineTo(u(x_flare), u(rn)) .spline([(u(x_full), u(Rt))], tangents=[(1, 0), (1, 0)], includeCurrent=True) .lineTo(u(L), u(Rt)).lineTo(u(L), 0).close().revolve(360, (0, 0, 0), (1, 0, 0))) sqb = (cq.Workplane("YZ").workplane(offset=u(x_flare)).rect(u(s["sq"]), u(s["sq"])).extrude(u(L - x_flare)) .edges("|X").fillet(u(s["rc"]))) sqb = sqb.faces(">X").edges().chamfer(u(0.03)) body = sqb.intersect(env) pin = hexp.union(neck).union(body) try: pin = pin.faces("