"""Door robot arm: lower arm (J2..J3), the J3 cylinder and its piston, the upper arm (J3..J4). Coordinates: each builder works in its own link frame (A axes, origin on the link's joint) and sets L.base to that origin in the arm frame A. Lower-arm frame: origin J2; x forward, z up; the lower arm's outline is drawn in x-z. Changes to the design, made here (reasons in STATUS.md): - drum diameter 0.49 (design 0.594): a 0.594 drum turning on J2 cuts 22 mm into the turret's front housing; the white ring round the drum in ref1 is a fixed collar on the turret (dra_parts_base); - the lower arm is built in layers: a narrow beam (|y| <= 0.175: two inner shells round a dark core with the cylinder's channel) from the drum to the crotch, and wide outer shells (|y| 0.17..0.451) only beyond 0.40 m from J2, so the arm can lean 58 degrees forward past the front housing and the motor pods (the door needs 53); - the trunnion pins are stubs on the cylinder's collar turning in bosses on the channel walls (no pin through the barrel); the knob pin is 0.046 (the design's 0.11 does not fit the rod eye's 0.05 bore); - the channel floor drops from 0.095 to 0.150 below the rod axis beyond the knee: the cylinder turns up to 5.2 degrees toward the core and the gland would touch a flat floor. """ import math import bmesh from mathutils import Vector, Matrix from dra_lib import (Link, lathe, slab, rbox, tube, knurl, star_slab, merge, xform, frame, basis, mirror_y, holed_surface, W, G, S, R, O, GL, LA, TAU, K, circle2, decal_strip, decal_poly, decal_dot, fillet3d, stack, sd_circle, sd_hull, sd_union, sd_inter, sd_minus, sd_half, contour, inset_poly, split_poly, ccw, face, bridge, fillet) from dra_shapes import (rrect, stadium, xz_slab, yz_slab, xy_slab, screw, dome, hexbolt, ring, cyl, grooved_cyl, holed_plate, notched_disc, half_discs, quarter_discs, u_handle, ribbed_boot, thread_rod, M_XZ) import dra_rigdata as rd V2 = lambda x, y: Vector((x, y)) # ---------------------------------------------------------------- lower-arm frame numbers J2A = rd.J2 T2 = V2(-0.5236, 0.330) # trunnion / knee J3L = V2(-0.869, 1.309) # J3 in the lower-arm frame U2 = V2(-0.7997, 0.6003) # cylinder axis (toward the knob) NB = V2(-0.6003, -0.7997) # from the cylinder axis toward the channel opening (the back) KD = (J3L - T2).normalized() # knee -> J3 KP = V2(KD.y, -KD.x) # across the band, toward the front C0 = J3L - KD * 0.571 # the yoke's crotch DRUM_R = 0.247 # barrel radius at its middle (it bulges; 0.236 at the ends) DRUM_R_END = 0.236 DRUM_HW = 0.200 # half length (ref7: about 0.42 m long with the hubs; CRITIQUE_1 item 3) STRAP_Y = (0.045, 0.130) # the root straps over the barrel (ref7), |y| STRAP_R = (0.251, 0.305) # their radii round J2 (ref1: the white ring round the drum, r 0.26..0.31) STRAP_ARC = (95.0, 228.0) # degrees round J2 (from +x toward +z), rest pose SKIN_Y = (0.345, 0.430) # outer shells: skin PANEL_TOP = 0.446 # raised panels 16 mm proud of the skin (CRITIQUE_1 item 5: real steps) RIM_TOP = 0.458 # the raised rim round the shell's edges, 12 mm over the panels # the dark core (fix round, CRITIQUE_2 should 2; ref1 x 1240-1420 y 810-1140, ref2, ref5): a long black stack along # the arm's front from the knee cap to below the J3 cap, set in between the white shells: the shells stop FRONT_D # behind the arm's front edge there and the stack fills that strip, inside the arm's outline (nothing stands proud). # Below the crotch three dark plates side by side (half width, how far their front is set back from the front edge, # edge round); above it one plate on each cheek (the upper arm turns between the cheeks). FRONT_D = 0.065 STACK_S = (0.02, 0.863) # along the arm from the knee centre (knee -> J3 is 1.038; J3 cap R 0.154) STACK = ((0.420, 0.016, 0.008), (0.300, 0.008, 0.006), (0.190, 0.0, 0.006)) STACK_CHEEK = ((0.277, 0.420), 0.010, 0.007) # the shell faces in steps (CRITIQUE_2 should 2, CRITIQUE_1 item 5; ref5): the knee block 14 mm over the panels, the # panel between the diagonal and the back strip 12 mm under them; chamfered edges KNEE_TOP = 0.460 MID_TOP = 0.434 ROOT_CLEAR = 0.315 # the white inner shells start this far from J2: the drum shows (ref7) def regions(): """signed distance functions (x, z) of the lower arm's layers""" Fc, Bc = C0 + KP * 0.19, C0 - KP * 0.19 beam = sd_union(sd_hull([(V2(0, 0), 0.20), (T2, 0.25)]), sd_hull([(T2, 0.25)], [Fc, Bc])) below = sd_half(C0, KD) # inside where (p - C0).KD < 0 lower_band = sd_hull([(T2, 0.275)], [C0 + KD * 0.05 + KP * 0.20, C0 + KD * 0.05 - KP * 0.20]) cheek_band = sd_hull([(J3L, 0.17)], [C0 - KD * 0.05 + KP * 0.20, C0 - KD * 0.05 - KP * 0.20]) beyond = lambda p: 0.40 - p.length # outside 0.40 m from J2 # CRITIQUE_1 item 5: an S-curved front edge (convex round the knee, a shallow hollow, convex at the crotch, ref1) fa, fb = T2 + KP * 0.275, C0 + KD * 0.05 + KP * 0.20 mid = (fa + fb) / 2 dn = fb - fa nrm = V2(dn.y, -dn.x).normalized() if nrm.dot(KP) < 0: nrm = -nrm carve = sd_circle(mid + nrm * (0.55 - 0.045), 0.55) band = sd_minus(sd_union(lower_band, cheek_band), carve) outer = sd_inter(band, beyond) outer_low = sd_inter(outer, below) outer_cheek = sd_inter(outer, lambda p: -((p - C0).dot(KD) + 0.03)) inner = sd_inter(beam, below) def floor_off(s): if s <= 0.25: return -0.095 if s >= 0.60: return -0.150 return -0.095 - 0.055 * (s - 0.25) / 0.35 core = sd_inter(lambda p: inner(p) + 0.040, lambda p: (p - T2).dot(NB) - floor_off((p - T2).dot(U2))) # the front zone: within FRONT_D of the arm's front edge (or in front of it), from the knee to below the J3 cap; # the white layers are cut back out of it and the dark stack fills it s_of = lambda p: (p - T2).dot(KD) front_zone = lambda p: max(-band(p + KP * FRONT_D), STACK_S[0] - s_of(p), s_of(p) - STACK_S[1]) def keep(f): return sd_minus(f, front_zone) def stack_plate(rec): """a dark plate of the stack below the crotch, its front 'rec' behind the arm's front edge""" return sd_inter(lambda p: band(p + KP * rec), front_zone, below, beyond) def stack_cheek(rec): """the dark plate on a cheek above the crotch""" return sd_inter(lambda p: band(p + KP * rec), front_zone, lambda p: -((p - C0).dot(KD) + 0.03), beyond) inner_shell = sd_inter(inner, lambda p: ROOT_CLEAR - p.length) return dict(inner=inner, inner_shell=keep(inner_shell), outer=keep(outer), outer_low=keep(outer_low), outer_cheek=keep(outer_cheek), core=core, floor_off=floor_off, stack_plate=stack_plate, stack_cheek=stack_cheek) BBOX = (-1.15, -0.30, 0.30, 1.52) def _outline(f, cell=0.004, tol=0.0014): return [(p.x, p.y, 0.0) for p in contour(f, BBOX, cell, tol)] def _panels(L, outline, y_face, depth, seams, pid, mat=W, side=(1, -1), inset=0.006, gap=0.004, bev=0.0022, seats=(), mats=None, tops=None, steps=2): """raised panels on one face of an x-z shell (y_face > 0 for the +y face; mirrored for side -1); tops(piece) -> (face y, edge round) gives each piece its own height (the base stays at y_face - depth)""" from dra_lib import fillet as _f P = ccw([Vector((p[0], p[1])) for p in outline]) P = inset_poly(P, inset) pieces = [P] for (a, b) in seams: nxt = [] for pc in pieces: l, r = split_poly(pc, a, b, gap) for q in (l, r): if len(q) >= 3 and abs(sum(q[i - 1].x * q[i].y - q[i].x * q[i - 1].y for i in range(len(q)))) > 2e-4: nxt.append(q) pieces = nxt for s in side: for k, pc in enumerate(pieces): o = [(p.x, p.y, 0.0) for p in pc] m = mat if mats is None else mats(pc) yf, bv = (y_face, bev) if tops is None else tops(pc) bm = xz_slab(o, y_face - depth - 0.002, yf, 0.0, bv, n=1, steps=steps) if s < 0: mirror_y(bm) L.put(bm, mat=m, pid=pid + ('.L' if s > 0 else '.R'), seats=seats) return pieces def drum_r(y): """barrel radius at y: a slight bulge (0.247 in the middle, 0.236 at the ends)""" t = min(1.0, abs(y) / DRUM_HW) return DRUM_R_END + (DRUM_R - DRUM_R_END) * (1.0 - t * t) def _drum(L): """L01: the perforated J2 drum (axis y), CRITIQUE_1 item 3: a bulging barrel 0.40 m long with a ring of 32 blind holes near each end (ref7: one row each side of the root straps), side faces with a ring of 32 holes (ref3) and a low stepped hub; one closed shell""" bm = bmesh.new() n = 32 us = [TAU * i / n for i in range(n + 1)] ys = [-0.196, -0.180, -0.146, -0.073, 0.0, 0.073, 0.146, 0.180, 0.196] # the middle is under the straps holes = {(i, 1) for i in range(n)} | {(i, 6) for i in range(n)} def mp(u, v): r = drum_r(v) return Vector((r * math.cos(u), v, r * math.sin(u))) holed_surface(bm, mp, us, ys, holes, 0.0120, 0.012, lambda u, v: Vector((math.cos(u), 0, math.sin(u))), periodic_u=True, mat=G) for s in (1, -1): rs = [0.226, 0.214, 0.186, 0.150] if s > 0 else [0.150, 0.186, 0.214, 0.226] holed_surface(bm, lambda u, v, s=s: Vector((v * math.cos(u), s * DRUM_HW, v * math.sin(u))), us, rs, {(i, 1) for i in range(n)}, 0.0095, 0.010, lambda u, v, s=s: Vector((0, s, 0)), periodic_u=True, mat=G) piece = bmesh.new() merge(piece, bm) bm.free() re = drum_r(0.196) for s in (1, -1): fil = lathe([(re, 0.196, 0, G), (0.2340, 0.1984, 0, G), (0.2305, 0.1996, 0, G), (0.226, DRUM_HW, 0, G)], 2 * n, n=1) hub = lathe([(0.150, DRUM_HW, 0, G), (0.138, DRUM_HW, 0, G), (0.136, DRUM_HW + 0.004, 0, G), (0.100, DRUM_HW + 0.004, 0, G), (0.096, DRUM_HW + 0.002, 0, G), (0.072, DRUM_HW + 0.002, 0, G), (0.070, DRUM_HW + 0.007, 0.001, S), (0.030, DRUM_HW + 0.007, 0, S), (0.030, DRUM_HW + 0.005, 0, G), (0.0, DRUM_HW + 0.005, 0, G)], 2 * n, n=1) for p in (fil, hub): M = frame((0, 0, 0), 'Y') if s > 0 else frame((0, 0, 0), '-Y') xform(p, M) merge(piece, p) p.free() bmesh.ops.remove_doubles(piece, verts=piece.verts[:], dist=2e-5) bmesh.ops.recalc_face_normals(piece, faces=piece.faces[:]) L.put(piece, None, None, 'L01') def arc_sweep(prof, a0, a1, n, mat=W): """a closed 2D profile (r, y) swept round the y axis from angle a0 to a1 (radians, from +x toward +z), with flat end caps; returns a bmesh""" from dra_lib import fill_cap P = ccw([Vector(p) for p in prof]) bm = bmesh.new() rings = [] for k in range(n + 1): a = a0 + (a1 - a0) * k / n ca, sa = math.cos(a), math.sin(a) rings.append([bm.verts.new((p.x * ca, p.y, p.x * sa)) for p in P]) m = len(P) for k in range(n): A, B = rings[k], rings[k + 1] am = a0 + (a1 - a0) * (k + 0.5) / n for j in range(m): j2 = (j + 1) % m e = P[j2] - P[j] o2 = Vector((e.y, -e.x)) # outward in (r, y) for a CCW profile o = Vector((o2.x * math.cos(am), o2.y, o2.x * math.sin(am))) face(bm, [A[j], A[j2], B[j2], B[j]], mat, out=o) t0 = Vector((-math.sin(a0), 0, math.cos(a0))) fill_cap(bm, rings[0], -t0, mat) t1 = Vector((-math.sin(a1), 0, math.cos(a1))) fill_cap(bm, rings[-1], t1, mat) bmesh.ops.recalc_face_normals(bm, faces=bm.faces[:]) return bm def _root_band(L): """CRITIQUE_1 item 3: the lower arm's root over the drum's back (ref7) = two white straps either side of a ribbed centre strip, wrapping the barrel round J2 from the top to the bottom-back (in a side view the white ring round the drum, ref1 and ref3). Centred on J2, so it sweeps no new space when the arm turns.""" r0, r1 = STRAP_R y0, y1 = STRAP_Y a0, a1 = math.radians(STRAP_ARC[0]), math.radians(STRAP_ARC[1]) prof = [(r0, y0), (r1 - 0.012, y0), (r1, y0 + 0.012), (r1, y1 - 0.012), (r1 - 0.012, y1), (r0, y1)] rounded = [p for p, _ in fillet([(x, y, 0.004 if i in (1, 2, 3, 4) else 0.0) for i, (x, y) in enumerate(prof)], closed=True, n=2)] for s in (1, -1): pr = [(p.x, s * p.y) for p in rounded] bm = arc_sweep(pr, a0, a1, 40, W) L.put(bm, None, W, 'L02' + ('.L' if s > 0 else '.R'), seats=('L01', 'L03.L', 'L03.R', 'L03c')) # a groove line along each strap (ref7): a thin dark inlay gp = [(r1 - 0.0005, s * (y0 + 0.040)), (r1 + 0.0012, s * (y0 + 0.040)), (r1 + 0.0012, s * (y0 + 0.044)), (r1 - 0.0005, s * (y0 + 0.044))] L.put(arc_sweep(gp, a0 + 0.03, a1 - 0.03, 40, G), None, G, 'L02g', seats=('L02.L', 'L02.R', 'L03.L', 'L03.R')) # the ribbed centre strip between the straps: cross rungs standing on the barrel k = 0 a = a0 + math.radians(2.0) while a < a1 - math.radians(1.5): ca, sa = math.cos(a), math.sin(a) M = Matrix(((-sa, 0, ca), (0, 1, 0), (ca, 0, sa))).to_4x4() # local x along the arc, z outward M.translation = Vector(((DRUM_R + 0.0085) * ca, 0, (DRUM_R + 0.0085) * sa)) L.put(rbox((0.012, 2 * y0 + 0.004, 0.019), 0.002, segs=1), M, G, 'L05d', seats=('L01', 'L02.L', 'L02.R')) a += math.radians(4.4) k += 1 def build_lower_arm(): L = Link('lower arm') L.base = J2A.copy() rg = regions() _drum(L) _root_band(L) # inner shells (two side walls of the beam, |y| 0.075..0.175) and the dark core between them o_inner = _outline(rg['inner_shell']) # stage 3: not round the drum (the root straps and the core carry it) for s in (1, -1): bm = xz_slab(o_inner, 0.075, 0.175, 0.003, 0.010) if s < 0: mirror_y(bm) L.put(bm, mat=W, pid='L03' + ('.L' if s > 0 else '.R'), seats=('L01', 'L03c', 'L02')) o_core = _outline(rg['core']) L.put(xz_slab(o_core, -0.0765, 0.0765, 0.004, 0.004), mat=G, pid='L03c', seats=('L01', 'L03', 'L02', 'L05d')) # outer shells beyond 0.40 m from J2: skin |y| 0.345..0.430, fills behind it; the cheeks' inner faces at 0.275 o_outer = _outline(rg['outer']) o_low = _outline(rg['outer_low']) o_cheek = _outline(rg['outer_cheek']) for s in (1, -1): sd = '.L' if s > 0 else '.R' for o, y0, y1, b0, b1, pid in ((o_outer, SKIN_Y[0], SKIN_Y[1], 0.004, 0.006, 'L04s'), (o_low, 0.170, 0.350, 0.006, 0.004, 'L04f'), (o_cheek, 0.275, 0.350, 0.008, 0.004, 'L14')): bm = xz_slab(o, y0, y1, b0, b1) if s < 0: mirror_y(bm) L.put(bm, mat=W, pid=pid + sd, seats=('L03' + sd, 'L04s' + sd, 'L04f' + sd, 'L14' + sd, 'L04r' + sd)) # CRITIQUE_1 item 5: a raised rim round the shell's edges (ref1, ref5) and recessed faces inside it Po = ccw([Vector((p[0], p[1])) for p in o_outer]) Pin = ccw([Vector((p[0], p[1])) for p in _outline(lambda p: rg['outer'](p) + 0.026)]) # the inset from the SDF: no miter spikes at the stack's step for s in (1, -1): sd = '.L' if s > 0 else '.R' bm = xz_slab([(p.x, p.y, 0.0) for p in Po], SKIN_Y[1] - 0.004, RIM_TOP, 0.0, 0.0045, n=1, holes=[[(p.x, p.y, 0.0) for p in Pin]]) if s < 0: mirror_y(bm) L.put(bm, mat=W, pid='L04r' + sd, seats=('L04s' + sd,)) # the dark stack along the front (see the notes at STACK): three plates side by side below the crotch, the middle # one furthest forward, and one plate on each cheek above it sts = ('L03', 'L03c', 'L04f', 'L04s', 'L04r', 'L14', 'L12', 'L03f0', 'L03f1', 'L03f2', 'L03f3') for k, (hw, rec, bev) in enumerate(STACK): oc = _outline(rg['stack_plate'](rec)) if len(oc) >= 3: L.put(xz_slab(oc, -hw, hw, bev, bev, steps=3), mat=G, pid='L03f%d' % k, seats=sts) (y0, y1), rec, bev = STACK_CHEEK oc = _outline(rg['stack_cheek'](rec)) for s in (1, -1): bm = xz_slab(oc, y0, y1, bev, bev, steps=3) if s < 0: mirror_y(bm) L.put(bm, mat=G, pid='L03f3' + ('.L' if s > 0 else '.R'), seats=sts) # recessed faces: panels inside the rim, cut by seams (across above the knee, across above the crotch, along the # back strip, a strip along the front, the diagonal groove up from the knee of ref5); 16 mm proud of the skin a1 = T2 + KD * 0.30 a2 = C0 + KD * 0.10 b0 = C0 - KP * 0.085 d45 = (KD + KP).normalized() # the diagonal groove up from the knee (ref5) q45 = T2 + KD * 0.40 seams = [(a1 - KP, a1 + KP), (a2 - KP, a2 + KP), (b0 - KD * 2, b0 + KD * 2), (q45 - d45 * 0.6, q45 + d45 * 0.6)] n45 = (KD - KP).normalized() def tops(pc): """the stepped faces: the knee block (below a1) high, the panel between the diagonal and the back strip low, the others at PANEL_TOP; chamfered edges""" c = sum(pc, Vector((0, 0))) / len(pc) sc, tc = (c - T2).dot(KD), (c - T2).dot(KP) if sc < (a1 - T2).dot(KD): return KNEE_TOP, 0.008 if tc > (b0 - T2).dot(KP) and sc < (a2 - T2).dot(KD) and (c - q45).dot(n45) > 0: return MID_TOP, 0.003 return PANEL_TOP, 0.005 _panels(L, [(p.x, p.y, 0.0) for p in Pin], PANEL_TOP, PANEL_TOP - SKIN_Y[1], seams, 'L12', inset=0.004, gap=0.005, bev=0.003, seats=('L04s', 'L04r'), tops=tops, steps=1) # stepped notch block at the top of the knee (ref5): a small raised block with 45-degree ends nb = T2 + KD * 0.27 + KP * 0.10 blk = [(-0.045, -0.025, 0.0), (0.045, -0.025, 0.0), (0.065, 0.0, 0.0), (0.045, 0.025, 0.0), (-0.045, 0.025, 0.0)] ang = math.atan2(KP.y, KP.x) ca, sa = math.cos(ang), math.sin(ang) blk = [(nb.x + x * ca - y * sa, nb.y + x * sa + y * ca, r) for x, y, r in blk] for s in (1, -1): bm = xz_slab(blk, KNEE_TOP - 0.002, KNEE_TOP + 0.014, 0.0, 0.002, n=1) if s < 0: mirror_y(bm) sd_ = '.L' if s > 0 else '.R' L.put(bm, mat=W, pid='L12b' + sd_, seats=('L12' + sd_, 'L06' + sd_, 'L04r' + sd_)) _knee(L) _elbow(L) _channel(L, rg) _front_ribs(L, rg) _handles(L) _left_side_details(L) _front_core_details(L) return L def _front_core_details(L): """L18: on the dark core in the front recess near the crotch: two round bosses with bolts and an inner round cap""" kp3 = Vector((KP.x, 0, KP.y)) for (t, y, kind) in ((-0.10, 0.034, 'boss'), (-0.10, -0.034, 'boss'), (-0.22, 0.0, 'cap')): p = C0 + KD * t + KP * 0.6 hit, n = L.snap((p.x, y, p.y), -kp3) if hit is None: continue if kind == 'boss': L.put(stack([(0.044, 0.010, 'gunmetal'), (0.024, 0.006, 'steel')], 20, base=0.002), frame(hit, n), None, 'L18', seats=('L03c', 'L03f0', 'L03f1', 'L03f2')) else: L.put(stack([(0.099, 0.008, 'gunmetal'), (0.080, 0.006, 'gunmetal'), (0.040, 0.008, 'steel')], 28, base=0.002), frame(hit, n), None, 'L18', seats=('L03c', 'L03f0', 'L03f1', 'L03f2')) def slotted_disc(r, n, w, depth, n_arc=4, n_end=4, phase=0.0): """a disc outline (angular order) with n short notches cut in from the rim, each with a round inner end""" pts = [] pa = TAU / n hw = w / 2 half = math.asin(hw / r) rr = math.sqrt(r * r - hw * hw) e = r - depth + hw # centre of the round end for k in range(n): c = phase + k * pa a0, a1 = c + half, c + pa - half for j in range(n_arc + 1): a = a0 + (a1 - a0) * j / n_arc pts.append((r * math.cos(a), r * math.sin(a))) cn = c + pa d = Vector((math.cos(cn), math.sin(cn))) t = Vector((-d.y, d.x)) pts.append(tuple(d * rr - t * hw)) for j in range(n_end + 1): ph = math.pi * j / n_end q = d * e + (-t * math.cos(ph) - d * math.sin(ph)) * hw pts.append((q.x, q.y)) pts.append(tuple(d * rr + t * hw)) out = [] for p in pts: if not out or (Vector(out[-1]) - Vector(p)).length > 1e-7: out.append(p) if (Vector(out[0]) - Vector(out[-1])).length < 1e-7: out.pop() return out def _knee(L): """L06 white boss ring and L07 knee cap on both sides (CRITIQUE_1 item 6, ref1 x 1295-1485, y 1075-1260 and ref5): ring 0.41 m, a dark disc 0.30 m set 8 mm down in it with ten short round-ended notches at its rim, a castellated inner ring 0.165 m round the hub, a thin ORANGE ring and the bolt. They cover the J3 cylinder's trunnion bearing.""" # fix round (CRITIQUE_2 should 5): a dark floor disc under the notched disc, so the ten short notches (0.045 long, # round inner ends) read as dark recesses at the rim (no white floor); the toothed ring 0.165 across; a dark hub # with the orange ring round a deep dark bore (no bright nut) base = KNEE_TOP - 0.003 for s in (1, -1): sd = '.L' if s > 0 else '.R' Mk = frame((T2.x, s * base, T2.y), (0, s, 0)) L.put(ring(0.150, 0.205, 0.026, 56, 0.004, W), Mk, W, 'L06' + sd, seats=('L12' + sd, 'L04s' + sd, 'L04r' + sd)) L.put(cyl(0.1505, 0.010, 56, 0.0008, G, z0=-0.006), Mk, G, 'L07' + sd, seats=('L12' + sd, 'L06' + sd)) disc = star_slab(slotted_disc(0.150, 10, 0.026, 0.045, n_arc=4, n_end=4, phase=math.radians(9)), 0.003, 0.020, hub_r=0.090, bev=0.0015, mat=G) L.put(disc, Mk, G, 'L07' + sd, seats=('L06' + sd, 'L07' + sd)) from dra_shapes import gear_outline cast = star_slab(gear_outline(12, 0.066, 0.0825, 0.30, 0.20), 0.018, 0.036, hub_r=0.045, bev=0.0012, mat=G) L.put(cast, Mk, G, 'L07c' + sd, seats=('L07' + sd,)) st = stack([(0.120, 0.006, 'gunmetal'), (0.098, 0.003, 'orange')], 40, base=0.002) L.put(st, Mk @ Matrix.Translation((0, 0, 0.036)), None, 'L07h' + sd, seats=('L07c' + sd, 'L07' + sd)) L.put(ring(0.026, 0.046, 0.014, 32, 0.002, G, z0=-0.001), Mk @ Matrix.Translation((0, 0, 0.045)), G, 'L07h' + sd, seats=('L07h' + sd,)) for k in range(8): a = TAU * (k + 0.5) / 8 p = Vector((T2.x + 0.178 * math.cos(a), s * (base + 0.026), T2.y + 0.178 * math.sin(a))) L.put(screw(0.0055), frame(p, (0, s, 0)), None, 'L07s' + sd, seats=('L06' + sd,)) def _elbow(L): """L15 elbow caps: white bezel, dark ring, a black cap with a straight slot ('minus' head) on a steel disc, two ticks""" for s in (1, -1): sd = '.L' if s > 0 else '.R' Me = frame((J3L.x, s * (PANEL_TOP - 0.003), J3L.y), (0, s, 0), spin=math.radians(-20) * s) L.put(ring(0.122, 0.154, 0.026, 36, 0.004, W), Me, W, 'L15' + sd, seats=('L12' + sd, 'L04s' + sd, 'L04r' + sd)) L.put(stack([(0.244, 0.012, 'gunmetal'), (0.206, 0.004, 'glass_dark')], 36, base=0.002), Me, None, 'L15' + sd, seats=('L15' + sd, 'L12' + sd, 'L04s' + sd, 'L04r' + sd)) L.put(half_discs(0.103, 0.026, 0.022, G, segs=12, bev=0.0015, z0=0.014), Me, G, 'L15' + sd, seats=('L15' + sd,)) for a in (math.pi / 2, -math.pi / 2): p = Vector((0.138 * math.cos(a), 0.138 * math.sin(a), 0.026)) L.put(rbox((0.010, 0.018, 0.004), 0.001), Me @ Matrix.Translation(p), G, 'L15t' + sd, seats=('L15' + sd,)) def _channel(L, rg): """L04/L05: the back channel between the inner shells: cross rungs on its floor under the barrel; bearing bosses for the trunnion stubs; L16: a guide rail with a threaded rod and a sliding block on the -y wall""" fo = rg['floor_off'] s = -0.32 nb3, u3 = Vector((NB.x, 0, NB.y)), Vector((U2.x, 0, U2.y)) while s <= 0.16: p = T2 + U2 * s + NB * (fo(s) + 0.011 - 0.002) L.put(rbox((0.012, 0.149, 0.022), 0.002), basis((p.x, 0, p.y), nb3, u3), G, 'L05', seats=('L03c', 'L03', 'L02', 'L03f0', 'L03f1', 'L03f2')) s += 0.044 for s_ in (1, -1): L.put(ring(0.031, 0.052, 0.0175, 24, 0.002, G), frame((T2.x, s_ * 0.0752, T2.y), (0, -s_, 0)), G, 'L04b' + ('.L' if s_ > 0 else '.R'), seats=('L03' + ('.L' if s_ > 0 else '.R'),)) # guide rail + threaded rod + sliding block on the -y wall, beside the barrel (|y| 0.056..0.075) pa = T2 + U2 * (-0.25) + NB * (-0.052) pb = T2 + U2 * (-0.075) + NB * (-0.052) rail_len = (pb - pa).length ang = math.atan2(U2.y, U2.x) Mr = Matrix.Translation(((pa.x + pb.x) / 2, 0, (pa.y + pb.y) / 2)) @ Matrix.Rotation(-ang, 4, 'Y') L.put(rbox((rail_len, 0.008, 0.026), 0.0015), Mr @ Matrix.Translation((0, -0.0712, 0)), G, 'L16', seats=('L03.R', 'L04b.R')) rod = thread_rod(0.0055, rail_len - 0.02, 0.011, S, 8) L.put(rod, Mr @ Matrix.Translation((-(rail_len - 0.02) / 2, -0.0610, 0.0)) @ frame((0, 0, 0), 'X'), S, 'L16', seats=('L16',)) L.put(rbox((0.05, 0.016, 0.030), 0.004), Mr @ Matrix.Translation((-0.02, -0.0615, 0)), W, 'L16b', seats=('L16',)) for e in (-1, 1): L.put(rbox((0.010, 0.014, 0.030), 0.0015), Mr @ Matrix.Translation((e * (rail_len / 2 - 0.006), -0.0635, 0)), G, 'L16', seats=('L16',)) def _front_ribs(L, rg): """the dashed strip of ref1/ref2: cross ribs in the dark front recess between the inner shells, near the root""" d = T2.normalized() nf = V2(-d.y, d.x) if nf.dot(V2(0.5, 0.85)) < 0: nf = -nf d3, n3 = Vector((d.x, 0, d.y)), Vector((nf.x, 0, nf.y)) for k in range(12): t = 0.29 + k * 0.026 edge = d * t + nf * (0.20 + 0.05 * t / T2.length) p = edge - nf * 0.0345 L.put(rbox((0.012, 0.148, 0.011), 0.0015), basis((p.x, 0, p.y), n3, d3), G, 'L05f', seats=('L03c', 'L03', 'L03f0', 'L03f1', 'L03f2')) def _handles(L): """L13: bent-tube handles standing off the back of the knee on both sides, a ribbed sleeve on the outer leg""" for s in (1, -1): sd = '.L' if s > 0 else '.R' feet = [] for z in (0.17, 0.48): hit, n = L.snap((-1.6, s * 0.407, z), (1, 0, 0)) feet.append(hit if hit is not None else Vector((-0.80, s * 0.407, z))) xo = min(f.x for f in feet) - 0.150 pts = [(feet[0].x + 0.004, s * 0.407, feet[0].z), (xo, s * 0.407, feet[0].z), (xo, s * 0.407, feet[1].z), (feet[1].x + 0.004, s * 0.407, feet[1].z)] L.put(tube(pts, 0.0132, 12, 0.035, caps=True), None, W, 'L13' + sd, seats=('L04s' + sd, 'L04f' + sd, 'L12' + sd, 'L04r' + sd)) for f in feet: L.put(rbox((0.030, 0.040, 0.040), 0.004), Matrix.Translation((f.x - 0.008, s * 0.407, f.z)), W, 'L13' + sd, seats=('L04s' + sd, 'L04f' + sd, 'L12' + sd, 'L13' + sd, 'L04r' + sd)) zc = (feet[0].z + feet[1].z) / 2 for k in range(5): z = zc - 0.10 + k * 0.05 L.put(lathe([(0.0125, -0.019, 0.0, W), (0.0175, -0.019, 0.0015, W), (0.0175, 0.019, 0.0015, W), (0.0125, 0.019, 0.0, W)], 12, closed=True, n=1), Matrix.Translation((xo, s * 0.407, z)), W, 'L13r' + sd, seats=('L13' + sd,)) def _left_side_details(L): """+y side only: L10 light slot, L11 round button, L08 hexagon, L09 triangle; L17 twin cable gland at the elbow""" # L10 light slot: a raised bezel with a recessed slot along the arm and a pale bar inside c = T2 + KD * 0.62 - KP * 0.13 ang = math.atan2(KD.y, KD.x) Mb = Matrix.Translation((c.x, PANEL_TOP + 0.0005, c.y)) @ Matrix.Rotation(-ang, 4, 'Y') @ basis((0, 0, 0), (0, 1, 0), (1, 0, 0)) bz = rbox((0.33, 0.052, 0.008), 0.002, ops=[('+z', -0.155, 0.155, -0.014, 0.014, -0.006)], rbev=0.0012) L.put(bz, Mb @ Matrix.Translation((0, 0, 0.002)), W, 'L10', seats=('L12.L', 'L04s.L')) L.put(xy_slab(stadium(0.29, 0.016), 0.0, 0.004, 0.0, 0.0012, n=4), Mb @ Matrix.Translation((0, 0, -0.0005)), LA, 'L10l', seats=('L10', 'L12.L', 'L04s.L')) # L11 button p = T2 + KD * 0.36 - KP * 0.17 M = L.on_surface((p.x, 1.0, p.y), (0, -1, 0)) if M is not None: L.put(stack([(0.066, 0.008, 'gunmetal'), (0.048, 0.006, 'paint_white')], 24, base=0.002), M, None, 'L11', seats=('L12.L', 'L04s.L', 'L04r.L')) L.put(xform(lathe([(0.0, 0.0, 0, W), (0.024, 0.0, 0, W), (0.022, 0.006, 0.003, W), (0.0, 0.009, 0, W)], 16), Matrix.Translation((0, 0, 0.014))), M, W, 'L11', seats=('L11',)) # L08 hexagon outline on the inner shell (left), L09 small triangle on the skin (left) M = L.on_surface((-0.176, 1.0, 0.297), (0, -1, 0), up=(0, 0, 1)) if M is not None: hexp = [(0.055 * math.cos(math.radians(30 + 60 * k)), 0.055 * math.sin(math.radians(30 + 60 * k))) for k in range(6)] L.decal(decal_strip(hexp, 0.0066), M, 'L08') p = T2 + KD * 0.40 + KP * 0.05 M = L.on_surface((p.x, 1.0, p.y), (0, -1, 0), up=(0, 0, 1)) if M is not None: tri = [(0.0, 0.022), (-0.019, -0.011), (0.019, -0.011)] L.decal(decal_strip(tri, 0.004), M, 'L09') # L17 twin cable gland on the +y cheek's back edge, pointing back hit, n = L.snap((-1.6, 0.39, 1.17), (1, 0, 0)) if hit is not None: g = lathe([(0.0, -0.01, 0, W), (0.044, -0.01, 0, W), (0.044, 0.018, 0.004, W), (0.0, 0.018, 0, W)], 24) L.put(g, frame(hit, (-1, 0, 0)), W, 'L17', seats=('L14.L', 'L04s.L', 'L12.L', 'L04r.L')) for dy in (-0.020, 0.020): L.put(stack([(0.030, 0.026, 'rubber')], 16, base=0.002), frame(hit + Vector((-0.018, dy, 0)), (-1, 0, 0)), None, 'L17', seats=('L17',)) L.gland_L17 = hit + Vector((-0.044, 0, 0)) # ---------------------------------------------------------------- the J3 cylinder and its piston def _u3(): return Vector((U2.x, 0, U2.y)) def build_cylinder(): """barrel with a blind bore the rod slides in (deep enough for version B's shortest length 0.587 m: the rod's end then reaches 0.213 m behind the trunnion, the bore 0.270), steel gland collar, trunnion collar with two stub pins, hose ports""" L = Link('J3 cylinder') L.base = J2A.copy() Tm = Vector((T2.x, 0, T2.y)) Mu = frame(Tm, _u3()) prof = [(0.0, -0.30, 0, G), (0.034, -0.30, 0.004, G), (0.042, -0.292, 0.004, G), (0.042, 0.455, 0.002, G), (0.047, 0.458, 0.002, S), (0.047, 0.500, 0.003, S), (0.0305, 0.500, 0.001, S), (0.0305, -0.270, 0, G), (0.0, -0.270, 0, G)] L.put(lathe(prof, 32), Mu, None, 'R01') for z in (-0.20, 0.30): L.put(ring(0.0418, 0.0450, 0.012, 32, 0.001, G, z0=z), Mu, G, 'R01', seats=('R01',)) L.put(lathe([(0.0420, -0.035, 0.0, W), (0.054, -0.035, 0.004, W), (0.054, 0.035, 0.004, W), (0.0420, 0.035, 0.0, W)], 32, closed=True), Mu, W, 'R02', seats=('R01',)) for s in (1, -1): L.put(cyl(0.030, 0.040, 24, 0.002, S, z0=0.0345), frame(Tm, (0, s, 0)), S, 'R02p', seats=('R02', 'R01')) for sp in (-0.24, 0.12): p = Tm + _u3() * sp L.put(stack([(0.020, 0.010, 'steel'), (0.014, 0.006, 'steel')], 12, base=0.004), frame(p + Vector((0, 0.0405, 0)), (0, 1, 0)), None, 'R04p', seats=('R01',)) return L def build_piston(): """the dark piston rod (dia 0.058, 0.80 m from its eye back to the piston) with a stepped collar at its end and a thin steel ring, and the rod eye on the knob pin (CRITIQUE_1 item 7)""" L = Link('J3 piston') L.base = J2A.copy() Km = Vector((-1.3354, 0, 0.9394)) # knob in the lower-arm frame at rest Mu = frame(Km, -_u3()) L.put(lathe([(0.0, 0.034, 0, G), (0.0215, 0.034, 0.001, G), (0.0215, 0.044, 0.0, G), (0.034, 0.046, 0.002, G), (0.034, 0.062, 0.002, G), (0.029, 0.064, 0.0, G), (0.029, 0.800, 0.002, G), (0.0, 0.800, 0, G)], 24), Mu, None, 'R05') L.put(ring(0.0289, 0.0312, 0.006, 24, 0.0005, S, z0=0.070), Mu, S, 'R05c', seats=('R05',)) eye = lathe([(0.0255, -0.012, 0.001, S), (0.0375, -0.012, 0.002, S), (0.0375, 0.012, 0.002, S), (0.0255, 0.012, 0.001, S)], 32, closed=True) L.put(eye, frame(Km, 'Y'), S, 'R06', seats=('R05',)) return L # ---------------------------------------------------------------- upper arm (frame: origin J3) KNOB_U = V2(-0.4664, -0.3696) def _truss_outline(): return ccw(list(sd_hull_pts())) def sd_hull_pts(): from dra_lib import hull2 pts = [V2(-0.022, 0.066), V2(0.110, -0.154), V2(-0.132, 0.066)] + circle2(KNOB_U, 0.072, 16) return hull2(pts) def build_upper_arm(): L = Link('upper arm') L.base = rd.J3.copy() # U01 elbow hub between the cheeks L.put(lathe([(0.0, -0.253, 0, G), (0.150, -0.253, 0.006, G), (0.165, -0.240, 0.006, G), (0.165, 0.240, 0.006, G), (0.150, 0.253, 0.006, G), (0.0, 0.253, 0, G)], 48), frame((0, 0, 0), 'Y'), G, 'U01') # U02 housing (the hump), CRITIQUE_1 item 5: the top is one convex curve from the front block over the hump down # into the elbow (ref1 x_img 1100-1560, y_img 468-640), 0.03-0.05 lower than before; the bottom edge as before ol = [(0.925, 0.552, 0.0), (0.800, 0.556, 0.0), (0.650, 0.553, 0.0), (0.520, 0.545, 0.0), (0.400, 0.528, 0.0), (0.300, 0.496, 0.0), (0.200, 0.443, 0.0), (0.110, 0.374, 0.0), (0.030, 0.293, 0.0), (-0.060, 0.192, 0.0), (-0.130, 0.082, 0.0), (-0.185, -0.030, 0.04), (-0.150, -0.090, 0.03), (0.176, -0.066, 0.06), (0.360, 0.040, 0.06), (0.550, 0.100, 0.05), (0.760, 0.128, 0.03), (0.925, 0.138, 0.0)] # stage 3 shape pass: rounded transitions from the sides into the top and the belly (ref1, ref3) L.put(xz_slab(ol, -0.236, 0.236, 0.032, 0.032, steps=3), mat=W, pid='U02', seats=('U01', 'U12', 'U12c')) Po = ccw([p for p, _ in fillet(ol, closed=True, n=3)]) Pi = inset_poly(Po, 0.034) # the dark recessed strip along the lower edge (ref1 x_img 1180-1470, y_img 640-690): what is below the line # z = 0.060 + 0.105 (x - 0.15) / 0.75 (from x 0.0 to the front block) def zl(x): return 0.062 + 0.098 * (x - 0.15) / 0.75 from dra_lib import clip_half as _ch strip = _ch(Po, Vector((0.0, -1.0)), -(-0.10)) # z >= -0.10 strip = _ch(strip, Vector((-1.0, 0.0)), -0.180) # x >= 0.18 (clear of the elbow hub) strip = _ch(strip, Vector((1.0, 0.0)), 0.90) # x <= 0.90 n_l = Vector((-(0.098 / 0.75), 1.0)).normalized() # keep below the line strip = _ch(strip, n_l, n_l.dot(Vector((0.15, 0.062)))) strip = inset_poly(ccw(strip), 0.030) if len(strip) >= 3 else strip # inside the rounded edges for s_ in (1, -1): if len(strip) >= 3: bm = xz_slab([(p.x, p.y, 0.0) for p in strip], 0.226, 0.239, 0.0, 0.002, n=1) if s_ < 0: mirror_y(bm) L.put(bm, mat=G, pid='U02d' + ('.L' if s_ > 0 else '.R'), seats=('U02',)) # the raised side panels (12 mm) above the strip: the big one carries the cap, the back one the vents up = _ch(Pi, -n_l, -n_l.dot(Vector((0.15, 0.062))) - 0.006) # 6 mm above the strip's line up = _ch(up, Vector((-1.0, 0.0)), -0.085) # clear of the elbow's rounded end up = _ch(up, Vector((1.0, 0.0)), 0.900) seams = [((0.300, -0.2), (0.350, 0.7)), ((0.840, -0.2), (0.840, 0.7))] _panels(L, [(p.x, p.y, 0.0) for p in up], 0.248, 0.012, seams, 'U02p', inset=0.0, gap=0.005, bev=0.003, seats=('U02', 'U01', 'U02d')) # a raised spine along the top (ref1: the stepped top line) top = [(0.80, 0.556), (0.650, 0.553), (0.520, 0.545), (0.400, 0.528), (0.300, 0.496), (0.200, 0.443), (0.110, 0.374)] # a raised lid along the top (ref1, ref3: the hump's cover), 10 mm proud, its ends stepped down into the body; # the narrow spine stands on it lid = [(x, z - 0.045) for x, z in top] + [(x, z + 0.010) for x, z in reversed(top)] L.put(xz_slab([(x, z, 0.0) for x, z in lid], -0.180, 0.180, 0.008, 0.008, n=1, steps=3), mat=W, pid='U02l', seats=('U02',)) spine = [(x, z - 0.02) for x, z in top[:-1]] + [(x, z + 0.020) for x, z in reversed(top[:-1])] L.put(xz_slab([(x, z, 0.0) for x, z in spine], -0.055, 0.055, 0.005, 0.005, n=1), mat=W, pid='U02s', seats=('U02', 'U02l')) _upper_side_details(L) _truss(L) _front_block(L) return L def _upper_side_details(L): for s in (1, -1): sd = '.L' if s > 0 else '.R' # U03 big caps: white bezel ring, thin orange ring in its bore, a dark glass disc set back, a slotted steel head Mc = frame((0.634, s * 0.247, 0.341), (0, s, 0)) bez = lathe([(0.116, 0.0, 0.0, W), (0.181, 0.0, 0.006, W), (0.181, 0.031, 0.010, W), (0.125, 0.031, 0.004, W), (0.116, 0.024, 0.0, W)], 40, closed=True) L.put(bez, Mc, None, 'U03' + sd, seats=('U02p' + sd, 'U02')) L.put(ring(0.110, 0.1162, 0.022, 40, 0.0, O), Mc, O, 'U03' + sd, seats=('U03' + sd,)) L.put(lathe([(0.0, 0.0006, 0, GL), (0.1104, 0.0006, 0, GL), (0.1104, 0.012, 0.002, GL), (0.0, 0.012, 0, GL)], 40), Mc, GL, 'U03g' + sd, seats=('U03' + sd, 'U02p' + sd)) L.put(cyl(0.079, 0.006, 32, 0.001, GL, z0=0.012), Mc, GL, 'U03h' + sd, seats=('U03g' + sd,)) # dark glass disc (ref1) L.put(xy_slab(stadium(0.150, 0.030), 0.018, 0.028, 0.0, 0.002, n=3), Mc, G, 'U03h' + sd, seats=('U03h' + sd,)) # U04 ribbed vents along the top edge (inclined like the edge): a raised plate with 8 slots ang = math.atan2(0.154, 0.286) Mv = Matrix.Translation((0.180, s * 0.248, 0.300)) @ Matrix.Rotation(-ang, 4, 'Y') L.put(xz_slab(rrect(0.22, 0.121, 0.012), -0.004, 0.004, 0.0, 0.002) if False else rbox((0.22, 0.008, 0.121), 0.003), Mv @ Matrix.Translation((0, s * 0.002, 0)), W, 'U04' + sd, seats=('U02p' + sd,)) for k in range(9): x = -0.096 + k * 0.024 L.put(rbox((0.012, 0.010, 0.099), 0.002, segs=1), Mv @ Matrix.Translation((x, s * 0.006, 0)), W, 'U04' + sd, seats=('U04' + sd,)) # U05 indicator light, U06 small dot (left), U07 chevrons (left) M = frame((0.429, s * 0.248, 0.200), (0, s, 0)) L.put(stack([(0.073, 0.006, 'gunmetal'), (0.055, 0.005, 'lamp')], 24, base=0.002), M, None, 'U05' + sd, seats=('U02p' + sd,)) L.put(stack([(0.018, 0.004, 'gunmetal')], 12, base=0.002), frame((0.297, 0.248, 0.250), 'Y'), None, 'U06', seats=('U02p.L',)) M = L.on_surface((0.080, 1.0, 0.380), (0, -1, 0), up=(0, 0, 1)) if M is not None: bm = decal_strip([(-0.014, -0.010), (0.0, 0.004), (0.014, -0.010)], 0.0045, closed=False) b2 = decal_strip([(-0.014, 0.004), (0.0, 0.018), (0.014, 0.004)], 0.0045, closed=False) merge(bm, b2) b2.free() L.decal(bm, M, 'U07') # U08 twin cable gland on the top behind the vents, two rubber glands facing back; on it the short thick looped # hose of ref1 (x_img 1377-1463, y_img 480-560) and ref3 (x 630-730, y 450-530) hit, n = L.snap((0.24, 0.110, 1.5), (0, 0, -1)) if hit is not None: L.put(rbox((0.11, 0.088, 0.100), 0.010), Matrix.Translation(hit + Vector((0, 0, 0.005))), W, 'U08', seats=('U02', 'U02s', 'U02p.L', 'U02l')) for dy in (-0.020, 0.020): L.put(stack([(0.0352, 0.022, 'rubber'), (0.026, 0.006, 'rubber')], 16, base=0.002), frame(hit + Vector((-0.053, dy, 0.030)), (-1, 0, 0)), None, 'U08g', seats=('U08',)) L.gland_U08 = hit + Vector((-0.083, 0, 0.030)) top = hit.z + 0.055 for xf in (0.030, -0.034): L.put(stack([(0.044, 0.010, 'paint_white'), (0.036, 0.012, 'rubber')], 16, base=0.002), frame((hit.x + xf, hit.y, top), 'Z'), None, 'U08h', seats=('U08',)) loop = [(hit.x + 0.030, hit.y, top + 0.018), (hit.x + 0.040, hit.y, top + 0.085), (hit.x - 0.060, hit.y, top + 0.105), (hit.x - 0.070, hit.y, top + 0.050), (hit.x - 0.034, hit.y, top + 0.018)] L.put(tube(loop, 0.0155, 12, 0.035, caps=True), None, R, 'U08l', seats=('U08h',)) # indicator rectangle (lamp) and dark bolt near the front block, left side L.put(rbox((0.036, 0.006, 0.020), 0.002), Matrix.Translation((0.870, 0.2485, 0.430)), LA, 'U12i', seats=('U02p.L',)) L.put(hexbolt(0.020, 0.008), frame((0.860, 0.2385, 0.128), 'Y'), None, 'U12d', seats=('U02d.L',)) def _truss(L): """U10 truss plates (|y| 0.052..0.090; stage 2: the inner faces moved out from 0.040 so the J3 barrel (r 0.047 at its gland) passes between the plates when the elbow folds for version B, J3 up to 45 deg), CRITIQUE_1 item 7: a white lever about 0.15 m wide (ref1 x_img 1600-1795, y_img 720-890; ref5) with a raised rim and five recessed pockets with white floors on each outer face: a slot and four triangles in a zigzag (the 'ANN' of the pictures). U11 knob: the pin through both plates and the rod eye, on each side a finely knurled ring round a domed boss.""" A0 = V2(-0.070, -0.035) # the lever's elbow end, behind the elbow cap K_ = KNOB_U from dra_lib import hull2 H = hull2([A0 + V2(math.cos(a), math.sin(a)) * 0.078 for a in [TAU * k / 32 for k in range(32)]] + [K_ + V2(math.cos(a), math.sin(a)) * 0.070 for a in [TAU * k / 32 for k in range(32)]]) H = ccw(H) d = (K_ - A0).normalized() nrm = V2(-d.y, d.x) pockets = [] sl = [A0 + d * (0.120 + x) + nrm * y for (x, y) in stadium(0.075, 0.032, 0.0, 0.0, 0.0, n=4)] pockets.append([(p.x, p.y, 0.0) for p in ccw(sl)]) w = 0.046 for k in range(4): t0 = 0.180 + k * 0.064 t1 = t0 + 0.090 if k % 2 == 0: tri = [A0 + d * t0 - nrm * w, A0 + d * t1 - nrm * w, A0 + d * ((t0 + t1) / 2) + nrm * w] else: tri = [A0 + d * t0 + nrm * w, A0 + d * ((t0 + t1) / 2) - nrm * w, A0 + d * t1 + nrm * w] cc = sum(tri, V2(0, 0)) / 3 tri = [cc + (p - cc) * 0.78 for p in tri] pockets.append([(p.x, p.y, 0.006) for p in ccw(tri)]) outline = [(p.x, p.y, 0.0) for p in H] land = [(p.x, p.y, 0.0) for p in inset_poly(H, 0.0135)] hole = [(p.x, p.y, 0.0) for p in inset_poly(H, 0.013)] for s in (1, -1): sd = '.L' if s > 0 else '.R' parts = ((xz_slab(outline, 0.052, 0.074, 0.003, 0.0, n=1), 'U10'), (xz_slab(outline, 0.072, 0.090, 0.0, 0.003, n=1, holes=[hole]), 'U10r'), (xz_slab(land, 0.072, 0.0855, 0.0, 0.002, n=1, holes=pockets), 'U10l')) for bm, pid in parts: if s < 0: mirror_y(bm) L.put(bm, mat=W, pid=pid + sd, seats=('U01', 'U02', 'U10' + sd, 'U10r' + sd, 'U10l' + sd)) Mk = frame((K_.x, s * 0.0855, K_.y), (0, s, 0)) L.put(knurl(0.0585, 0.0625, 0.034, 36, 0.003, G), Mk, None, 'U11' + sd, seats=('U10' + sd, 'U10r' + sd, 'U10l' + sd)) L.put(lathe([(0.0, 0.030, 0, G), (0.052, 0.030, 0.002, G), (0.050, 0.036, 0.0, G), (0.040, 0.036, 0.0, G), (0.038, 0.040, 0.0, G), (0.030, 0.046, 0.0, G), (0.018, 0.050, 0.0, G), (0.0, 0.0515, 0, G)], 32), Mk, None, 'U11d' + sd, seats=('U11' + sd,)) L.put(cyl(0.023, 0.180, 24, 0.001, S, z0=-0.090), frame((K_.x, 0, K_.y), 'Y'), S, 'U11p', seats=('U10.L', 'U10.R', 'U11.L', 'U11.R', 'U10r.L', 'U10r.R', 'U10l.L', 'U10l.R', 'U11d.L', 'U11d.R')) # spacers on the pin between the plates and the rod eye (the plates stand 0.052 off the middle so the rod's end # collar passes between them) for s in (1, -1): L.put(ring(0.0228, 0.031, 0.0375, 24, 0.001, S, z0=0.0145), frame((K_.x, 0, K_.y), (0, s, 0)), S, 'U11w', seats=('U11p', 'U10.L', 'U10.R')) def _front_block(L): """U12 front block (J4 housing): four slices with three ring grooves, on a core; square button on the left""" x0, x1 = 0.9035, 1.1125 zc, hz = 0.356, 0.451 L.put(rbox((x1 - x0 - 0.01, 0.450, hz - 0.012), 0.010), Matrix.Translation(((x0 + x1) / 2, 0, zc)), W, 'U12c', seats=('U02',)) cuts = [x0, 0.955, 0.959, 1.010, 1.014, 1.065, 1.069, x1] for k in range(0, len(cuts), 2): a, b = cuts[k], cuts[k + 1] L.put(rbox((b - a, 0.462, hz), 0.004, rv=0.0264, vax=0, segs=2, segs_v=3), Matrix.Translation(((a + b) / 2, 0, zc)), W, 'U12', seats=('U12c', 'U02')) L.put(rbox((0.026, 0.008, 0.026), 0.003), Matrix.Translation((0.982, 0.233, 0.44)), G, 'U12b', seats=('U12',)) # ---------------------------------------------------------------- curves: the cables over the elbow, the cylinder hoses def _curve(name, pts, radius, coll, parent, mat): cu = bpy.data.curves.get(name) or bpy.data.curves.new(name, 'CURVE') cu.splines.clear() cu.dimensions = '3D' cu.bevel_mode = 'ROUND' cu.bevel_depth = radius cu.bevel_resolution = 2 cu.resolution_u = 8 cu.use_fill_caps = True sp = cu.splines.new('BEZIER') sp.bezier_points.add(len(pts) - 1) for bp, p in zip(sp.bezier_points, pts): bp.co = p bp.handle_left_type = bp.handle_right_type = 'AUTO' cu.materials.clear() cu.materials.append(bpy.data.materials[mat]) ob = bpy.data.objects.new(name, cu) coll.objects.link(ob) ob.parent = parent return ob import bpy # noqa: E402 def add_curves(coll, prefix, objs): """elbow cable pair (U09) from the upper arm's twin gland over the elbow to the gland at the back of the elbow, and the two cylinder hoses; at rest, parented to the lower arm (the rig stage hooks their ends)""" la = objs.get("lower arm") if la is None or "upper arm" not in objs: return J3l = Vector((J3L.x, 0, J3L.y)) me_u = objs["upper arm"].data me_l = la.data if "gland_U08" not in me_u or "gland_L17" not in me_l: return gu = Vector(me_u["gland_U08"]) + (rd.J3 - rd.J2) # in the lower-arm frame gl = Vector(me_l["gland_L17"]) # CRITIQUE_1 item 9: two loops of different size that cross once in a side view (ref1, ref3): the first leaves the # gland low and makes the big loop, the second leaves steeply and makes the small loop inside it paths = ([gu + Vector((0, -0.020, 0)), gu + Vector((-0.13, -0.020, 0.07)), J3l + Vector((-0.18, 0.16, 0.62)), J3l + Vector((-0.52, 0.27, 0.36)), gl + Vector((-0.15, -0.020, 0.08)), gl + Vector((0, -0.020, 0))], [gu + Vector((0, 0.020, 0)), gu + Vector((-0.05, 0.020, 0.13)), J3l + Vector((-0.30, 0.21, 0.44)), J3l + Vector((-0.42, 0.31, 0.18)), gl + Vector((-0.09, 0.020, -0.03)), gl + Vector((0, 0.020, 0))]) for k, pts in enumerate(paths): _curve("%s | cable elbow %d" % (prefix, k + 1), pts, 0.013, coll, la, "M_DoorArm_Rubber") Tm = Vector((T2.x, 0, T2.y)) for nm, sp in (("rear", -0.24), ("front", 0.12)): p0 = Tm + _u3() * sp + Vector((0, 0.052, 0)) p1 = p0 + Vector((0, 0.008, 0)) + Vector((NB.x, 0, NB.y)) * -0.03 p2 = Tm + _u3() * (sp + 0.07) + Vector((NB.x, 0, NB.y)) * -0.060 + Vector((0, 0.0735, 0)) _curve("%s | J3 hose %s" % (prefix, nm), [p0, p1, p2], 0.010, coll, la, "M_DoorArm_Rubber")