"""Vishay reflective optical sensors with transistor output: the CNY70 cube and the TCRT5000 / TCRT5000L with two domed lenses and mounting clips. The sensor stands on the board (the XY plane), its body centred on the origin, the emitter and detector side by side along X, looking up (+Z), and the leads down through the board. The housing is one solid, each lens its own solid, and each lead its own solid. Dimensions from Vishay's data sheets; see the README.""" from steve import Model part_number = "CNY70" # Vishay part number # CNY70: Vishay CNY70 data sheet, Rev. 1.8, 30-Jul-12 (document 83751), page 5, PACKAGE DIMENSIONS, drawing 6.544-5062.01-4 issue 6: # body 7 +/-0.1 square, 6 -0.1 high, 12 overall (so the leads reach 6 below the body); leads 0.4 x 0.5 at 2.8 (+0.3) along X and # 2.54 nom. along Y, centred on the body's centre lines; windows d1.8 -0.1, 2.8 apart, on the body's centre line, 1 deep (side # view, "1"). Weight ca. 0.70 g. # (body length X, width Y, height, lead length below the body, lead section X, lead section Y, lead pitch X, lead pitch Y, # window diameter, window pitch, window depth) CNY70 = (7.0, 7.0, 6.0, 6.0, 0.4, 0.5, 2.8, 2.54, 1.8, 2.8, 1.0) # TCRT5000 / TCRT5000L: Vishay TCRT5000 data sheet, Rev. 1.7, 17-Aug-09 (document 83760), PACKAGE DIMENSIONS, page 4 (TCRT5000, # drawing 6.550-5096.01-4 issue 4) and page 5 (TCRT5000L, drawing 6.550-5146.01-4): body 10.2 (+0.1/-0.2) x 5.8 (+0.1/-0.2), its # nose flat 2.5 +/-0.3 wide; 3 from the reference plane (the board) to the shoulder, 6.3 (+0.5/-0.1) to the lens tops, 7 +/-0.2 to # the divider's top; leads 0.4 x 0.65 max, 5.5 apart along X and 2.54 along Y, 3.5 +/-0.3 (TCRT5000) or 15 +/-0.5 (TCRT5000L) # below the reference plane; locating peg d0.9 +/-0.05, 1.5 +/-0.2 below, 4.65 +/-0.1 from the clips' centre (footprint: 2.45 off # the centre line); two clips 0.95 +/-0.05 wide, reaching 3 below, barb 1.2 wide, 0.5 beyond the body's side, its top 1.6 to 1.8 # and its widest 2.2 +/-0.1 below, tips 2.45 off the centre line. Weight ca. 0.23 g. # (body length X, width Y, nose flat, shoulder height, lens top, divider top, lead section X, lead section Y, lead pitch X, # lead pitch Y, peg diameter, peg length, peg X from centre, peg Y from centre, clip width X, clip depth, barb width, barb out, # barb top, barb top inner, barb widest, clip tip Y) TCRT5000 = (10.2, 5.8, 2.5, 3.0, 6.3, 7.0, 0.4, 0.65, 5.5, 2.54, 0.9, 1.5, 4.65, 2.45, 0.95, 3.0, 1.2, 0.5, 1.8, 1.6, 2.2, 2.45) LEADS = {"TCRT5000": 3.5, "TCRT5000L": 15.0} # Proportions, for looks (the TCRT5000 drawings show these but don't dimension them; README): the body's underside stands 0.5 above # the reference plane except the foot at the peg end, which reaches it and runs 1.42 in from that end; above the shoulder the body # tapers to a top face 4.45 above the plane, 1.45 in at the ends and 1.1 in at the sides; the nose's corner chamfers run 1.7 along X; # each lens is 3.0 x 3.0, upright sides then a half-round top (radius 1.5) across Y, its centre 1.75 off the middle; the divider # between them is 0.45 thick and 3.0 long; the clip's stem is the barb's inner 0.7, and its tip is pointed 0.25 in from its inner # face. TCRT_PROPORTIONS = {"underside": 0.5, "foot": 1.42, "top": 4.45, "inset_x": 1.45, "inset_y": 1.1, "nose_x": 1.7, "lens": 3.0, "lens_x": 1.75, "divider": 0.45} PROUD = 0.03 # the CNY70's lens discs stand this far proud of the window floors so the renderer draws their edges model = Model() leads = [] if part_number == "CNY70": L, W, H, lead_len, lx, ly, px, py, wd, wp, depth = CNY70 model.box("housing", L, W, H, at=(0, 0, H / 2), op="new", body="housing_body", name="Housing") model.plane("window_plane", on="XY", offset=H - depth, name="Window floor") lenses = [] for k, (x, what) in enumerate(((-wp / 2, "emitter"), (wp / 2, "detector"))): with model.sketch(f"window_{k}_sketch", on="window_plane", name=f"{what.capitalize()} window") as sk: sk.circle(f"window_{k}_circle", (x, 0), wd / 2) model.extrude(f"window_{k}", f"window_{k}_sketch", distance=depth, op="cut", body="housing_body", name=f"{what.capitalize()} window") with model.sketch(f"lens_{k}_sketch", on="window_plane", name=f"{what.capitalize()} lens") as sk: sk.circle(f"lens_{k}_circle", (x, 0), wd / 2) model.extrude(f"lens_{k}", f"lens_{k}_sketch", distance=PROUD, op="new", body=f"lens_{k}_body", name=f"{what.capitalize()} lens") lenses.append(f"lens_{k}_body") emitter, detector = lenses for i, x in enumerate((-px / 2, px / 2)): for j, y in enumerate((-py / 2, py / 2)): n = 2 * i + j model.box(f"lead_{n}", lx, ly, lead_len, at=(x, y, -lead_len / 2), op="new", body=f"lead_{n}_body", name=f"Lead {n + 1}") leads.append(f"lead_{n}_body") elif part_number in LEADS: (L, W, nose, shoulder, lens_top, divider_top, lx, ly, px, py, peg_d, peg_len, peg_x, peg_y, clip_w, clip_depth, barb_w, barb_out, barb_top, barb_top_in, barb_wide, tip_y) = TCRT5000 P = TCRT_PROPORTIONS lead_len = LEADS[part_number] under, top = P["underside"], P["top"] def outline(dx, dy): """The body's plan outline (the nose at +X, its corners chamfered, its flat nose wide), inset dx at the ends and dy at the sides.""" x0, x1, y1, f = -L / 2 + dx, L / 2 - dx, W / 2 - dy, nose / 2 xn = x1 - P["nose_x"] return [(x0, -y1), (xn, -y1), (x1, -f), (x1, f), (xn, y1), (x0, y1)] model.plane("under_plane", on="XY", offset=under, name="Underside") with model.sketch("body_sketch", on="under_plane", name="Body") as sk: sk.polyline("body_outline", outline(0, 0), closed=True) model.extrude("housing", "body_sketch", distance=shoulder - under, op="new", body="housing_body", name="Housing") model.plane("shoulder_plane", on="XY", offset=shoulder, name="Shoulder") model.plane("top_plane", on="XY", offset=top, name="Top") with model.sketch("taper_base", on="shoulder_plane", name="Taper") as sk: sk.polyline("taper_base_outline", outline(0, 0), closed=True) with model.sketch("taper_top", on="top_plane", name="Taper") as sk: sk.polyline("taper_top_outline", outline(P["inset_x"], P["inset_y"]), closed=True) model.loft("taper", ["taper_base", "taper_top"], ruled=True, op="join", body="housing_body", name="Taper") # The foot at the peg end, down to the reference plane, and the locating peg below it. foot = P["foot"] model.box("foot", foot, W, under, at=(-L / 2 + foot / 2, 0, under / 2), op="join", body="housing_body", name="Foot") model.cylinder("peg", peg_d / 2, peg_len, at=(-peg_x, -peg_y, -peg_len), op="join", body="housing_body", name="Locating peg") # The divider between the lenses, up to its dimensioned top. d = P["divider"] model.box("divider", d, P["lens"], divider_top - top, at=(0, 0, top + (divider_top - top) / 2), op="join", body="housing_body", name="Divider") # The two clips, a side-view (YZ) outline each, extruded clip_w along X and centred on X = 0. for s, side in ((1, "right"), (-1, "left")): body_side, out = W / 2, W / 2 + barb_out stem_in = out - barb_w pts = [(stem_in, under), (body_side, under), (body_side, -barb_top_in), (out, -barb_top), (out, -barb_wide), (tip_y, -clip_depth), (stem_in, -clip_depth + (tip_y - stem_in))] with model.sketch(f"clip_{side}_sketch", on="YZ", name=f"Clip {side}") as sk: sk.polyline(f"clip_{side}_outline", [(s * y, z) for y, z in pts], closed=True) model.extrude(f"clip_{side}", f"clip_{side}_sketch", distance=clip_w, both=True, op="join", body="housing_body", name=f"Clip {side}") # The lenses: upright sides, then a half-round top across Y, each P["lens"] long along X. r = P["lens"] / 2 lenses = [] for k, (x, what) in enumerate(((-P["lens_x"], "detector"), (P["lens_x"], "emitter"))): model.plane(f"lens_{k}_plane", on="YZ", offset=x, name=f"{what.capitalize()} lens") with model.sketch(f"lens_{k}_sketch", on=f"lens_{k}_plane", name=f"{what.capitalize()} lens") as sk: sk.polyline(f"lens_{k}_sides", [(-r, lens_top - r), (-r, top), (r, top), (r, lens_top - r)], closed=False) sk.arc(f"lens_{k}_top", (0, lens_top - r), r, 0, 180) model.extrude(f"lens_{k}", f"lens_{k}_sketch", distance=P["lens"], both=True, op="new", body=f"lens_{k}_body", name=f"{what.capitalize()} lens") lenses.append(f"lens_{k}_body") detector, emitter = lenses for i, x in enumerate((-px / 2, px / 2)): for j, y in enumerate((-py / 2, py / 2)): n = 2 * i + j run = lead_len + under model.box(f"lead_{n}", lx, ly, run, at=(x, y, under - run / 2), op="new", body=f"lead_{n}_body", name=f"Lead {n + 1}") leads.append(f"lead_{n}_body") else: raise ValueError(f"no part {part_number!r}: CNY70, TCRT5000, TCRT5000L") # Materials (the sheets name none): the housing is black moulded plastic that blocks visible light ("daylight blocking filter", # page 1), shown as pc; the lenses are the clear epoxy over the emitter and the dark filter epoxy over the detector, shown as pmma # (1.18; epoxy is about 1.2); the leads are metal, shown as copper. model.material("pc", bodies=["housing_body"], color="#1c1c1e") model.material("pmma", bodies=[emitter], color="#8fb4e8") model.material("pmma", bodies=[detector], color="#2a2a30") model.material("copper", bodies=leads, color="#c9ccd1") result = model.result()