# Grouper: the docking collar (the sheet's top ring IS the Kazah collar, gr_measure.py section 3) and the hatch inside it # (exec'd by build_grouper.py after gr_parts.py). # # The docking arm (models/docking-arm) closes four claws at 45/135/225/315 deg round the clamp axis (0, -0.09), wrist 1.94 m # over the origin; probed (tender-mako/build/probe_head.py, checks/head_parts.json): shut, the claws reach r 0.558-0.693 at # z >= 1.215 within +-11 deg of each diagonal; open, r 0.613-0.793 at z >= 1.213; the landing feet stay at z 1.405 over # r 0.41-0.53 (az 30-55, 120-145, 210-235, 300-325). Azimuth here: degrees from +X (port) toward +Y (aft), as the probe's. # # The collar is one revolved part round the clamp axis, its section changing with azimuth: # * between the claws the drawn wall (COLLAR_PROFILE_M): the top face r 0.5067-0.547 at z 1.375 (rounded outer corner), the # rim r 0.547 -> 0.573 (the light ring's band, M_Grouper_Strip), the wall falling to the footprint r 0.665 and a skirt # onto the hull (the lens SIDE draws: the wall is stretched to reach the hull at every azimuth); # * in four claw pockets (+-16 deg round each diagonal, full depth within +-14.5, a 2 deg blend each side) the Kazah # profile: the hook lip r 0.552 (z 1.335-1.375) and the wall r 0.535 down to z 1.19, then a fillet to the hull at r 0.585. # The claws (r >= 0.558, z >= 1.213) find the lip over them and nothing under them; # * the bore r 0.5067 down through the hull and the lining, with a flange under the cabin's ceiling. # The hull's own hole (gr_hull.py) is r 0.512 round the bore. The hatch lid (r 0.500, 6.7 mm gap) is domed as SIDE draws it # (1.418 at its centre, 1.375 at r 0.42 and beyond) and hinged at its forward edge on an axis along X at z 1.392. CL_C = Vector((0.0, Y_CL, 0.0)) CLAW_AZ = (45.0, 135.0, 225.0, 315.0) POCKET_FULL, POCKET_EDGE = 13.5, 21.5 # the claws' pads reach +-11 deg; an 8 deg chamfer each side (round 1) R_BORE = 0.5067 def az_pt(r, az, z): a = math.radians(az) return Vector((CL_C.x + r * math.cos(a), CL_C.y + r * math.sin(a), z)) def hull_top_at(r, az): p = az_pt(r, az, 0.0) return z_top(p.x, p.y) def pocket_w(az): """1 in a claw pocket, 0 on the drawn wall; LINEAR across the 4 deg between them (critique round 1 m7: a straight chamfer from the drawn wall down into the pocket, so the pockets read as designed clamp sockets, not notches)""" d = min(abs((az - c + 180.0) % 360.0 - 180.0) for c in CLAW_AZ) return max(0.0, min(1.0, (POCKET_EDGE - d) / (POCKET_EDGE - POCKET_FULL))) # Critique round 2 (M1): the collar is a CLOSED cap over the hull's clean round hole (r 0.512, the warped grid square; no # faces are pruned round it any more: the old per-face prune left a stair-stepped opening up to r 0.63 that the claw pockets, # stepping in to r 0.535, did not cover, so the two aft pockets were see-through). Every section now ends in the same SKIRT: a # swept ring at r 0.695 whose lower edge lies on the actual (faceted) skin with a small fillet (+5.5 mm, +1.2 mm, then 3 mm # under it, like the window frames' edges), so the collar meets the hull along one clean curve at every azimuth. In the claw # pockets a FLOOR PLATE runs from the Kazah wall's foot (r 0.535, a 7 mm fillet) straight out to that skirt (the claws, at # r >= 0.558 and z >= 1.213, stay 28 mm or more above it). The hull's skin faces under the collar stay (hidden inside it). R_SKIRT = 0.695 # 6 deg steps round the collar, every pocket's edges exact; the chamfers in 2 deg steps with the blend's middle stage # (w = 0.5, 17.5 deg) on a sample (round 2, m3: the chamfer quads twisted 71-78 deg over 2.5-3 deg steps); the pockets in # 2.25 deg steps, so the floor plate and its skirt follow the faceted skin closely AZ = sorted(set([round(6.0 * k, 4) for k in range(60)] + [round((c + s * d) % 360.0, 4) for c in CLAW_AZ for s in (-1, 1) for d in (0.0, 2.25, 4.5, 6.75, 9.0, 11.25, POCKET_FULL, 14.5, 15.5, 16.5, 17.5, 18.5, 19.5, 20.5, POCKET_EDGE)])) AZ = [a for i, a in enumerate(AZ) if i == 0 or a - AZ[i - 1] > 0.9] def skin_top_at(r, az): """the ACTUAL skin's height (the faceted grid, not the analytic surface) under (r, az): the collar's foot is fitted to it""" p = az_pt(r, az, 0.0) z = hull_z_at(p.x, p.y) return z if z is not None else hull_top_at(r, az) def collar_section(az): """the section at azimuth az: (r, z) from the flange under the ceiling, up the bore, over the top, down to the hull""" w = pocket_w(az) zh_bore = hull_top_at(R_BORE, az) zc = min(zh_bore, hull_top_at(R_BORE + 0.12, az), hull_top_at(R_BORE + 0.20, az)) - 0.17 # under the lining (113 mm) by 57 mm # the flange under the ceiling reaches r 0.70 (round 1: the coarser lining's hole round the bore is wider), short of the # cabin's rear bulkhead aft sa = math.sin(math.radians(az)) r_fl = R_BORE + 0.190 if sa < 0.05 else min(R_BORE + 0.190, (Y_BULK - 0.015 - Y_CL) / sa) inner = [(r_fl, zc), (r_fl - 0.006, zc + 0.012), (R_BORE, zc + 0.012), (R_BORE, zh_bore - 0.04), (R_BORE, 1.35), (R_BORE, 1.375)] zf = skin_top_at(0.665, az) + 0.012 z_rim = 1.313 def stretch(z): # the drawn wall from the rim (1.313) to the foot (1.180) return zf + (z - 1.180) * (z_rim - zf) / (z_rim - 1.180) # the skirt (all azimuths): the fillet onto the faceted skin SK = [(R_SKIRT - 0.014, skin_top_at(R_SKIRT - 0.014, az) + 0.0055), (R_SKIRT - 0.005, skin_top_at(R_SKIRT - 0.005, az) + 0.0012), (R_SKIRT, skin_top_at(R_SKIRT, az) - 0.003)] # the drawn rim at r 0.553 (was 0.5472; TOP's top-face circle is 0.5505): the pockets' hook lip (r 0.552) never stands out # of the drawn wall, so the pocket's profile lies inside the wall's at every point and the blend between them cannot fold D = [(0.528, 1.375), (0.548, 1.3735), (0.553, 1.365), (0.553, z_rim), (0.5729, stretch(1.293)), (0.6156, stretch(1.252)), (0.6327, stretch(1.200)), (0.6650, zf)] + SK zw = max(1.19, skin_top_at(0.535, az) + 0.030) # the Kazah wall's foot def on_floor(r0, t): # a point a fraction t along the floor plate from r0 return (r0 + (SK[0][0] - r0) * t, zw + (SK[0][1] - zw) * t) # the wall stands on the floor with a crisp corner (round 2: a 7 mm fillet smoothed the floor's shading into the wall) K = [(0.528, 1.375), (0.546, 1.375), (0.552, 1.369), (0.552, 1.341), (0.546, 1.335), (0.535, 1.335), (0.535, zw), on_floor(0.535, 0.06)] + SK # two stages (round 1: a straight blend turned the hook's undercut over the drawn wall's slope and folded quads): from the # pocket the undercut fills first (Kf: the lip's face carried straight down to the wall's foot), then Kf becomes the # drawn wall # (round 2, m3) Kf's fifth point sits just under the fourth, so the undercut opens by moving it INWARD (to the wall, # r 0.535) rather than 7 cm up the face: the chamfer's quads twisted 71-78 deg Kf = [K[0], K[1], K[2], K[3], (0.552, 1.329), (0.552, 1.321), (0.552, zw), on_floor(0.552, 0.06)] + SK if w >= 0.5: u = (w - 0.5) / 0.5 outer = [(f[0] + (k[0] - f[0]) * u, f[1] + (k[1] - f[1]) * u) for f, k in zip(Kf, K)] else: u = w / 0.5 outer = [(d[0] + (f[0] - d[0]) * u, d[1] + (f[1] - d[1]) * u) for d, f in zip(D, Kf)] return inner + outer, w coll_mb = MB("_collar", [GUN, STRIP, FRAME]) rows, ws = [], [] for az in AZ: sec, w = collar_section(az) rows.append([az_pt(r, az, z) for r, z in sec]) ws.append(w) bmc = bmesh.new() _stv = bmc.verts.layers.float.new("strip_v") # the light ring's line (critique round 1 M5), as the grooves' strips _stw = bmc.verts.layers.float.new("strip_w") VC = [[bmc.verts.new(p) for p in row] for row in rows] for a_ in range(len(rows)): for q_ in range(len(rows[0])): VC[a_][q_][_stv], VC[a_][q_][_stw] = -1.0, 0.0 NP = len(rows[0]) N_AZ = len(AZ) I_RIM = 6 + 3 # the segment from the rim's top to its foot (the light ring) for a_ in range(N_AZ): w_ = (VC[a_][I_RIM + 1].co - VC[a_][I_RIM].co).length VC[a_][I_RIM][_stv], VC[a_][I_RIM][_stw] = 0.0, w_ VC[a_][I_RIM + 1][_stv], VC[a_][I_RIM + 1][_stw] = 1.0, w_ for a in range(N_AZ): b = (a + 1) % N_AZ for q in range(NP - 1): f = bmc.faces.new((VC[a][q], VC[a][q + 1], VC[b][q + 1], VC[b][q])) if q < 5: f.material_index = coll_mb.mi(FRAME) # the flange and the bore elif q >= NP - 3: f.material_index = coll_mb.mi(GUN) # the skirt's fillet onto the hull: one ring all round elif max(ws[a], ws[b]) > 1e-3 and q >= 7: f.material_index = coll_mb.mi(FRAME) # the claw pockets and their chamfers: dark (critique m7) elif q == I_RIM: f.material_index = coll_mb.mi(STRIP) # the collar's light ring, ending cleanly at each pocket else: f.material_index = coll_mb.mi(GUN) bmc.normal_update() bmesh.ops.recalc_face_normals(bmc, faces=bmc.faces[:]) bmc.normal_update() ftop = max(bmc.faces, key=lambda f: f.calc_center_median().z) if ftop.normal.z < 0: bmesh.ops.reverse_faces(bmc, faces=bmc.faces[:]) coll_mb.merge(bmc, None) REPORT["collar"] = {"azimuths": N_AZ, "section_points": NP, "pockets_deg": [POCKET_FULL, POCKET_EDGE], "lip_r": 0.552, "wall_r": 0.535, "lip_top_z": 1.375} # ------------------------------------------------------------------ the hatch lid (moving: SM_Grouper_Hatch) HATCH_R = HATCH["r"] HATCH_DOME_R, HATCH_DOME_H = HATCH["dome_r"], HATCH["dome_h"] def dome_z(r): return 1.375 + (HATCH_DOME_H * (1 - (r / HATCH_DOME_R) ** 2) if r < HATCH_DOME_R else 0.0) hatch = MB("SM_Grouper_Hatch", [GUN, FRAME, STEEL]) prof = [(0.06, 1.315), (0.30, 1.315), (0.47, 1.315), (0.49, 1.318), (HATCH_R, 1.327), (HATCH_R, 1.366), (0.495, 1.375), (0.47, 1.375), (0.44, 1.375), (HATCH_DOME_R, dome_z(HATCH_DOME_R))] for r_ in (0.37, 0.31, 0.25, 0.19, 0.13, 0.07): prof.append((r_, dome_z(r_))) bmh = bmesh.new() segs = 40 rowsh = [[bmh.verts.new(az_pt(r, 360.0 * k / segs, z)) for k in range(segs)] for r, z in prof] for i in range(len(rowsh) - 1): mat_i = 1 if i < 6 else 0 for k in range(segs): k2 = (k + 1) % segs f = bmh.faces.new((rowsh[i][k], rowsh[i + 1][k], rowsh[i + 1][k2], rowsh[i][k2])) f.material_index = mat_i for f in coons_cap(bmh, rowsh[0][::-1], 1): pass for f in coons_cap(bmh, rowsh[-1], 0): pass bmh.normal_update() bmesh.ops.recalc_face_normals(bmh, faces=bmh.faces[:]) bmh.normal_update() ftop = max(bmh.faces, key=lambda f: f.calc_center_median().z) if ftop.normal.z < 0: bmesh.ops.reverse_faces(bmh, faces=bmh.faces[:]) hatch.merge(bmh, None) # a grip: a low bar across the lid, aft of the dome's top (a handle the crew pull from outside) rrect_bar(hatch, az_pt(0.33, 90.0, dome_z(0.33) + 0.012) + Vector((-0.10, 0, 0)), az_pt(0.33, 90.0, dome_z(0.33) + 0.012) + Vector((0.10, 0, 0)), 0.012, 0.010, 0.006, FRAME) for sx in (-0.10, 0.10): rrect_bar(hatch, az_pt(0.33, 90.0, dome_z(0.33) - 0.004) + Vector((sx, 0, 0)), az_pt(0.33, 90.0, dome_z(0.33) + 0.022) + Vector((sx, 0, 0)), 0.012, 0.010, 0.005, FRAME, up=Vector((0, 1, 0))) # the hinge: on an axis along X over the lid's forward edge; the lid's own knuckle in the middle, the collar's two outside HINGE_Z = 1.392 HINGE_Y = Y_CL - HATCH_R HATCH_HINGE = Vector((0.0, HINGE_Y, HINGE_Z)) KR = 0.012 qcyl(hatch, KR, 0.096, HATCH_HINGE, STEEL, rot=(0, math.pi / 2, 0), segs=12) rrect_bar(hatch, HATCH_HINGE + Vector((0, 0.004, -0.004)), Vector((0.0, HINGE_Y + 0.045, 1.379)), 0.040, 0.006, 0.004, FRAME, up=Vector((0, 0, 1))) hinge_fx = MB("_hingefix", [FRAME, STEEL]) for sx in (-1, 1): c = HATCH_HINGE + Vector((sx * 0.086, 0, 0)) qcyl(hinge_fx, KR, 0.064, c, STEEL, rot=(0, math.pi / 2, 0), segs=12) # a bracket from the knuckle forward down onto the collar's top face (r 0.51 .. 0.545 at az 270) rrect_bar(hinge_fx, c + Vector((0, -0.006, -0.002)), Vector((c.x, Y_CL - 0.538, 1.371)), 0.028, 0.009, 0.004, FRAME, up=Vector((0, 0, 1))) # the hatch's seat (finishing step, 2026-10-22): a ring round the bore 8 mm under the lid's underside (z 1.315), from 3 mm in # the bore's wall to r 0.47 (30 mm under the lid's rim), so no line of sight runs down the 6.7 mm gap round the lid into the # cabin. The lid only rises as it opens (hinged over its forward edge): the seat never meets it (gr_motion_check.py). N_SEAT = 64 seat_loops = [] for rr, zz in ((R_BORE + 0.003, 1.307), (0.470, 1.307), (0.470, 1.295), (R_BORE + 0.003, 1.295)): seat_loops.append([Vector((rr * math.cos(2 * math.pi * k / N_SEAT), Y_CL + rr * math.sin(2 * math.pi * k / N_SEAT), zz)) for k in range(N_SEAT)]) grid_rings(coll_mb, seat_loops, FRAME, expect=(0, 0, (0.0, 0.0, 1.0))) HATCH_MAX = HATCH["open_deg"][1] REPORT["hatch"] = {"r": HATCH_R, "gap_m": round(R_BORE - HATCH_R, 4), "dome_top_z": round(dome_z(0.0), 4), "hinge": [0.0, round(HINGE_Y, 4), HINGE_Z], "open_deg": [0.0, HATCH_MAX]}