# Mako: the docking interface where the user's red line puts it (over the canopy's rear and the collar), and the hatch. # Exec'd by build_mako.py after mk_fittings.py. # # v03, the user: "holder clips smaller thinner". The docking arm (models/docking-arm) closes four claws round the clamp axis # (0, -0.09) at 45/135/225/315 deg, wrist 1.94 m over the origin. Probed (build/probe_head.py): the claws never come below # z 1.213 (shut: r 0.558-0.693, about 10 deg either side of each diagonal; open: r 0.61-0.79), the landing feet stay at # z 1.405 (r 0.41-0.53). So a clip only needs the Kazah collar's outer profile where a claw closes: a wall at r 0.535 from # z 1.19 to 1.335 and the hook lip (r 0.552, top z 1.375) the claw catches when the arm lifts. Each clip is now a curved # plate 3 cm thick and 26 deg long (v02: 15 cm thick, 40 deg, on a 1.06 m disc and saddle). The aft pair stands on the # collar; the fore pair on short posts on the canopy's two orange top rails (mk_fittings.py), which the sheet draws there. # The glass stays clear between them. The hatch is on the back behind the dorsal fin (the glass is under the clips now). clip = MB("_clamp", [STEEL, GRAPH, YELLOW, PAINT]) CL_C = Vector((0.0, Y_CL, 0.0)) CLIP_AZ = (45.0, 135.0, 225.0, 315.0) CLIP_RI, CLIP_RO, CLIP_LIP = 0.506, 0.535, 0.552 CLIP_Z0, CLIP_ZL, CLIP_ZT = 1.188, 1.335, 1.375 def revolve_arc(mb, loop, a0, a1, segs, mat): """an 8-point profile [(r, z)] swept round the clamp axis from a0 to a1, ends closed by three quads""" bm2 = bmesh.new() cols = [[bm2.verts.new((CL_C.x + r * math.cos(a0 + (a1 - a0) * j / segs), CL_C.y + r * math.sin(a0 + (a1 - a0) * j / segs), z)) for (r, z) in loop] for j in range(segs + 1)] n = len(loop) for j in range(segs): for q in range(n): bm2.faces.new((cols[j][q], cols[j + 1][q], cols[j + 1][(q + 1) % n], cols[j][(q + 1) % n])) for c in (cols[0], cols[-1]): bm2.faces.new((c[0], c[1], c[2], c[7])); bm2.faces.new((c[7], c[2], c[3], c[6])); bm2.faces.new((c[6], c[3], c[4], c[5])) bm2.normal_update() bmesh.ops.recalc_face_normals(bm2, faces=bm2.faces[:]) mb.merge(bm2, mat) def surface_top(x, y): """the hull's top with the collar's band (and the shell's plating behind it), where a clip stands""" z = hull_z_at(x, y) + (collar_raise(y) if Y_C0 <= y <= Y_C1 else 0.0) if y > Y_C1: z += H_SHELL return z def rail_top(sg, y): ys_, pts, hts = RAILS[sg] j = min(range(len(ys_)), key=lambda i: abs(ys_[i] - y)) return pts[j].z + hts[j] def sweep_arc(mb, prof_at, a0, a1, segs, mat): """an 8-point profile that may change along the arc: prof_at(u) -> [(r, z)], u from 0 to 1; ends closed by three quads""" bm2 = bmesh.new() cols = [] for j in range(segs + 1): u = j / segs ang = a0 + (a1 - a0) * u cols.append([bm2.verts.new((CL_C.x + r * math.cos(ang), CL_C.y + r * math.sin(ang), z)) for (r, z) in prof_at(u)]) n = len(cols[0]) for j in range(segs): for q in range(n): bm2.faces.new((cols[j][q], cols[j + 1][q], cols[j + 1][(q + 1) % n], cols[j][(q + 1) % n])) for c in (cols[0], cols[-1]): bm2.faces.new((c[0], c[1], c[2], c[7])); bm2.faces.new((c[7], c[2], c[3], c[6])); bm2.faces.new((c[6], c[3], c[4], c[5])) bm2.normal_update() bmesh.ops.recalc_face_normals(bm2, faces=bm2.faces[:]) mb.merge(bm2, mat) def post_loft(mb, secs, mat): """a post through chamfered-rectangle sections [(centre, chord dir, half chord, half thickness, chamfer)], stacked upward; three quads on each end""" bm2 = bmesh.new() rings = [] for (c, cd, hc, ht, ch) in secs: td = Vector((0, 0, 1)).cross(cd).normalized() pts = [(-hc + ch, -ht), (hc - ch, -ht), (hc, -ht + ch), (hc, ht - ch), (hc - ch, ht), (-hc + ch, ht), (-hc, ht - ch), (-hc, -ht + ch)] rings.append([bm2.verts.new(c + cd * u + td * v) for u, v in pts]) ring_rows(bm2, rings) for r in (rings[0], rings[-1]): bm2.faces.new((r[7], r[0], r[1], r[2])); bm2.faces.new((r[7], r[2], r[3], r[6])); bm2.faces.new((r[6], r[3], r[4], r[5])) bm2.normal_update() bmesh.ops.recalc_face_normals(bm2, faces=bm2.faces[:]) mb.merge(bm2, mat) CLIP_HALF = math.radians(11.0) # the claw's pad spans about 10 deg either side of its diagonal (probe_head.py) CHAMFER = 0.18 # the head's top corners fall away over the last 18 % of the arc at each end CLIP_INFO = {} for az in CLIP_AZ: a = math.radians(az) fore = math.sin(a) < 0 cx, cy = CL_C.x + 0.52 * math.cos(a), CL_C.y + 0.52 * math.sin(a) tang = Vector((-math.sin(a), math.cos(a), 0)) if fore: # a tapered pylon from the rail up under the clip: along the rail at its foot, turning into the clip's arc at its top sg = 1 if cx > 0 else -1 zr = rail_top(sg, cy) - 0.012 ztop = CLIP_Z0 + 0.012 secs = [] for k in range(6): t = k / 5 z = zr + (ztop - zr) * t cd = Vector((0, 1, 0)).lerp(tang if tang.y > 0 else -tang, smoothstep(0.2, 1.0, t)).normalized() c = Vector((sg * RAIL_LAT + (cx - sg * RAIL_LAT) * smoothstep(0.0, 1.0, t), cy + 0.03 * (1 - t), z)) secs.append((c, cd, 0.085 - 0.04 * t, 0.016 - 0.004 * t, 0.006)) post_loft(clip, secs, PAINT) z0, base = CLIP_Z0, zr + 0.012 else: # standing on the collar: its plate carried 3 cm down into the collar's band zc = min(surface_top(CL_C.x + rr * math.cos(a + da), CL_C.y + rr * math.sin(a + da)) for rr in (CLIP_RI, CLIP_RO) for da in (-CLIP_HALF, 0.0, CLIP_HALF)) z0, base = min(CLIP_Z0, zc - 0.03), zc def prof_at(u, z0=z0): e = max(0.0, abs(2 * u - 1) - (1 - 2 * CHAMFER)) / (2 * CHAMFER) # 0 in the middle, 1 at the arc's ends dz = 0.030 * e * e return [(CLIP_RI, z0), (CLIP_RO, z0), (CLIP_RO, CLIP_ZL - dz), (0.546, 1.348 - dz), (CLIP_LIP, 1.362 - dz), (0.546, CLIP_ZT - dz), (CLIP_RI, CLIP_ZT - dz), (CLIP_RI, 0.5 * (z0 + CLIP_ZT))] sweep_arc(clip, prof_at, a - CLIP_HALF, a + CLIP_HALF, 8, STEEL) qbox(clip, (0.022, 0.05, 0.004), (CL_C.x + 0.521 * math.cos(a), CL_C.y + 0.521 * math.sin(a), CLIP_ZT + 0.002), YELLOW, rot=(0, 0, a)) CLIP_INFO["%d" % az] = dict(fore=fore, centre=[round(cx, 3), round(cy, 3)], stands_on_z=round(base, 3), height_m=round(CLIP_ZT - base, 3), arc_deg=round(2 * math.degrees(CLIP_HALF), 1), plate_m=round(CLIP_RO - CLIP_RI, 3)) REPORT["clips"] = CLIP_INFO # ------------------------------------------------------------------ the hatch (v03: on the back, behind the dorsal fin) # A reinforced base plate on the hull (square outside, so it covers the hull's square hatch hole and its sleeve; round inside), # a coaming ring, a ledge, a short trunk down to the rear bay's lining and a trim plate under the lining; the lid (moves) is # hinged across its fore edge and opens toward the tail, away from the dorsal fin. HC0 = Vector((0.0, Y_HT, hull_z_at(0.0, Y_HT) + H_SHELL)) HN = normal_at(Vector((0.0, Y_HT, hull_z_at(0.0, Y_HT)))) HTAN = Vector((0, 1, 0)); HTAN = (HTAN - HTAN.dot(HN) * HN).normalized() HX = Vector((1, 0, 0)) def hl(lx, ly, lz): """a point in the hatch's frame: x across, y along the back (aft), z out of the hull""" return HC0 + HX * lx + HTAN * ly + HN * lz TRUNK_R, COAM_RI, COAM_RO, PLATE_H = 0.250, 0.272, 0.300, 0.008 LID_R = 0.268 MC = 32 hatch_fx = MB("_hatchfix", [GRAPH, PAINT, STEEL, YELLOW]) bmh = bmesh.new() def on_skin(lx, ly, lift): """a point of the hatch frame dropped onto the hull's top (with the shell's plating) and lifted""" p = hl(lx, ly, 0.0) # on the tangent plane, then straight down or up to the skin q = Vector((p.x, p.y, hull_z_at(p.x, p.y) + H_SHELL)) return q + normal_at(q) * lift def ring_v(r, lz=None, lift=None): out = [] for a in range(MC): ph = 2 * math.pi * a / MC lx, ly = r * math.cos(ph), r * math.sin(ph) out.append(bmh.verts.new(on_skin(lx, ly, lift) if lift is not None else hl(lx, ly, lz))) return out def square_v(half, lift): out = [] for a in range(MC): ph = 2 * math.pi * a / MC c, s_ = math.cos(ph), math.sin(ph) m = half / max(abs(c), abs(s_)) out.append(bmh.verts.new(on_skin(m * c, m * s_, lift))) return out plate_out_lo = square_v(HATCH_R + 0.05, -0.012) plate_out_hi = square_v(HATCH_R + 0.05, PLATE_H) plate_in = ring_v(COAM_RO + 0.01, lift=PLATE_H) coam_top_o = ring_v(COAM_RO, lz=0.066) # 1.5 cm higher than first drawn: the back is concave ahead of the hatch coam_top_i = ring_v(COAM_RI, lz=0.066) ledge_o = ring_v(COAM_RI, lz=0.036) ledge_i = ring_v(TRUNK_R, lz=0.036) seq = [plate_out_lo, plate_out_hi, plate_in, coam_top_o, coam_top_i, ledge_o, ledge_i] mats_h = [PAINT, PAINT, GRAPH, STEEL, GRAPH, GRAPH] # the doubler plate in the hull's paint, the coaming dark for (ra, rb), m_ in zip(zip(seq, seq[1:]), mats_h): ring_rows(bmh, [ra, rb], mat_index=hatch_fx.mi(m_)) # the trunk down to the lining, and the trim plate under the lining (round inside, square outside) def lining_pt(lx, ly): p = hl(lx, ly, 0.0) return Vector((p.x, p.y, hull_z_at(p.x, p.y, True, LINER) - 0.010)) trunk_bot = [bmh.verts.new(lining_pt(TRUNK_R * math.cos(2 * math.pi * a / MC), TRUNK_R * math.sin(2 * math.pi * a / MC))) for a in range(MC)] ring_rows(bmh, [ledge_i, trunk_bot], mat_index=hatch_fx.mi(GRAPH)) trim = [] for a in range(MC): ph = 2 * math.pi * a / MC c, s_ = math.cos(ph), math.sin(ph) m = (HATCH_R + 0.07) / max(abs(c), abs(s_)) trim.append(bmh.verts.new(lining_pt(m * c, m * s_))) ring_rows(bmh, [trunk_bot, trim], mat_index=hatch_fx.mi(GRAPH)) bmh.normal_update() bmesh.ops.recalc_face_normals(bmh, faces=bmh.faces[:]) bmh.normal_update() probe_f = max(bmh.faces, key=lambda f: (f.calc_center_median() - HC0).dot(HN)) if probe_f.normal.dot(HN) < 0: bmesh.ops.reverse_faces(bmh, faces=bmh.faces[:]) hatch_fx.merge(bmh, None) # the hinge's fixed knuckle on the plate, across the fore edge HATCH_HINGE = hl(0.0, -(COAM_RO + 0.022), 0.082) qcyl(hatch_fx, 0.016, 0.12, HATCH_HINGE, STEEL, rot=(0, math.pi / 2, 0), segs=8) qbox(hatch_fx, (0.10, 0.045, 0.06), HATCH_HINGE - HN * 0.03 - HTAN * 0.004, GRAPH, bevel=0.006) HATCH_MAX = 100.0 hatch = MB("SM_Mako_Hatch", [PAINT, GRAPH, STEEL, YELLOW]) bml = bmesh.new() LZ0, LZ1 = 0.038, 0.074 top_r = [bml.verts.new(hl(LID_R * math.cos(2 * math.pi * a / MC), LID_R * math.sin(2 * math.pi * a / MC), LZ1)) for a in range(MC)] bot_r = [bml.verts.new(hl(LID_R * math.cos(2 * math.pi * a / MC), LID_R * math.sin(2 * math.pi * a / MC), LZ0)) for a in range(MC)] ring_rows(bml, [top_r, bot_r], mat_index=0) coons_cap(bml, top_r, 0) coons_cap(bml, bot_r[::-1], 1) bml.normal_update() bmesh.ops.recalc_face_normals(bml, faces=bml.faces[:]) bml.normal_update() pf = max(bml.faces, key=lambda f: (f.calc_center_median() - HC0).dot(HN)) if pf.normal.dot(HN) < 0: bmesh.ops.reverse_faces(bml, faces=bml.faces[:]) hatch.merge(bml, None) wc = hl(0.0, 0.03, LZ1 + 0.026) hatch.sweep([wc + HX * (0.10 * math.cos(2 * math.pi * q / 24)) + HTAN * (0.10 * math.sin(2 * math.pi * q / 24)) for q in range(25)], 0.011, STEEL, sides=8, caps=False) HROT = tuple(Vector((0, 0, 1)).rotation_difference(HN).to_euler()) qcyl(hatch, 0.028, 0.04, wc - HN * 0.016, GRAPH, segs=12, rot=HROT) for q in range(3): a = 2 * math.pi * q / 3 + math.pi / 2 hatch.sweep([wc, wc + HX * (0.095 * math.cos(a)) + HTAN * (0.095 * math.sin(a))], 0.008, STEEL, sides=6, caps=True) for sx in (-0.15, 0.15): # the lid's knuckles on arms from its fore edge to the axis qcyl(hatch, 0.015, 0.06, HATCH_HINGE + HX * sx, GRAPH, rot=(0, math.pi / 2, 0), segs=8) arm_c = (HATCH_HINGE + HX * sx + hl(sx, -(LID_R - 0.05), LZ1 + 0.004)) * 0.5 qbox(hatch, (0.045, 0.09, 0.014), arm_c, GRAPH, rot=HROT, bevel=0.004) REPORT["stage"] = "clamp+hatch"