# Grouper: everything on the hull but the collar and the doors (exec'd by build_grouper.py after gr_clamp.py). Numbers are the # sheet's pixels from gr_measure.py unless in metres; SIDE rules every x and height, TOP gives widths, FRONT/BACK what SIDE # cannot show. Quads only (lofts and revolves with Coons caps, tubes, plates over outlines); parts that sink into the hull are # pruned by the finish pass (faces wholly deeper than 6 cm under the skin go). att = MB("_attached", [GUN, BAND, FRAME, STRIP, LAMPW, SCAN, RECESS, STEEL, DUCTG]) SG = (1, -1) # port, starboard # ================================================================== stern: stator, hub, rotor (the duct's walls are the grid's, gr_hull.py) DUCT_AX = Vector((0.0, 0.0, Zi(DUCT["y_c"]))) R_HUB = Lp(DUCT["r_hub"]) * DUCT["r_out"] / 41.7 R_HUBCAP = Lp(DUCT["r_hub_cap"]) * DUCT["r_out"] / 41.7 Y_STATOR = Yi(DUCT["stator_x"]) Y_ROTOR = Yi(DUCT["rotor_x"]) ROT_C = Vector((0.0, Y_ROTOR, DUCT_AX.z)) # the static hub: from the stern plate to the rotor, and from the rotor through the stator to the cap revolve_y(att, [(R_HUB, Y_PLATE - 0.01), (R_HUB, Y_ROTOR - 0.045)], FRAME, DUCT_AX, segs=32, cap0=False, cap1=True) revolve_y(att, [(R_HUB, Y_ROTOR + 0.045), (R_HUB, Y_STATOR + 0.03), (R_HUB * 0.97, Y_STATOR + 0.06), (R_HUBCAP, Y_LIP - 0.02), (R_HUBCAP * 0.6, Y_LIP + 0.005)], [FRAME, FRAME, FRAME, FRAME], DUCT_AX, segs=32) # six stator spokes (BACK: 30, 90, 150, 210, 270, 330 deg; vertical at the top and bottom), the hub to the inner wall for deg in DUCT["stator_deg"]: a = math.radians(deg) d = Vector((math.cos(a), 0, math.sin(a))) sec = rrect2d(0.035, Lp(DUCT["stator_width"]) * 0.5, 0.012, 2) tvec = Vector((0, 1, 0)).cross(d) rings = [] for rr in (R_HUB - 0.02, R_IN + 0.012): c = DUCT_AX + d * rr + Vector((0, Y_STATOR, 0)) rings.append([c + Vector((0, u, 0)) + tvec * v for u, v in sec]) loft_rings(att, rings, FRAME) # the rotor (SM_Grouper_Rotor, moving: spins about Y): five twisted blades on a ring, behind the stator (my addition: the # sheet draws the duct, the stator and the hub) rotor = MB("SM_Grouper_Rotor", [FRAME, STEEL]) revolve_y(rotor, [(R_HUB - 0.004, Y_ROTOR - 0.040), (R_HUB - 0.004, Y_ROTOR + 0.040)], STEEL, DUCT_AX, segs=40) R_BLADE = Lp(DUCT["rotor_r"]) * DUCT["r_out"] / 41.7 for kb in range(DUCT["rotor_blades"]): a0 = 2 * math.pi * kb / DUCT["rotor_blades"] secs = [] for q in range(6): t = q / 5 rr = (R_HUB - 0.01) + (R_BLADE - R_HUB + 0.01) * t d = Vector((math.cos(a0), 0, math.sin(a0))) tang = Vector((0, 1, 0)).cross(d) pitch = math.radians(48 - 22 * t) cd = (Vector((0, 1, 0)) * math.cos(pitch) + tang * math.sin(pitch)).normalized() nd = d.cross(cd).normalized() secs.append((ROT_C + d * rr, cd, nd, 0.12 - 0.03 * t, 0.018 - 0.008 * t)) foil(rotor, secs, FRAME) # ================================================================== the ventral skeg (SIDE "Stabilizer Fin", lower aft) def keel_z(x): return BOTf(Yi(x)) skeg_rings = [] SK = SKEG["profile"] for q in range(len(SK)): x, yb = SK[q] zr = keel_z(x) + 0.06 # the root inside the hull zb = Zi(yb) if zb > keel_z(x) - 0.02: zb = keel_z(x) - 0.02 t = Lp(SKEG["blade_thick_px"]) * 0.5 # the root's fillet slimmed (critique m8: BACK read the wide root as a trapezoid plate under the duct the sheet does not # draw; it draws the narrow blade only): 6 px half width at the root instead of 17 wr = Lp(6.0) * (1.0 - 0.4 * smoothstep(600.0, 614.0, x)) pts = [] for v in (0.0, 0.35, 0.62, 0.84, 0.97): # one side down (a concave root fillet), round bottom, the other side up w = t + (wr - t) * (1 - v) ** 3 pts.append((w, zr + (zb - zr) * v)) pts += [(t * 0.45, zb - t * 0.35), (-t * 0.45, zb - t * 0.35)] pts += [(-w_, z_) for w_, z_ in reversed(pts[:5])] pts = pts[:5] + pts[5:7] + pts[7:] skeg_rings.append([Vector((w_, Yi(x), z_)) for w_, z_ in pts]) # 12 points per ring, symmetric (one side down, round bottom, the other side up): ladder caps close both ends loft_rings(att, skeg_rings, GUN, ladder=True) # the skeg's root ridge forward of the blade (BOTTOM's teardrop to x 645): a low fairing on the keel rr = [] for x in (612.0, 620.0, 630.0, 640.0, 645.0): f = 1.0 - smoothstep(612.0, 645.0, x) w = Lp(5.0) * (0.5 + 0.5 * f) # slimmed with the skeg's root (critique m8) zk = keel_z(x) h = 0.015 + 0.025 * f sec = [(w, zk + 0.05), (w * 0.8, zk - h * 0.4), (w * 0.4, zk - h * 0.9), (0.0, zk - h), (-w * 0.4, zk - h * 0.9), (-w * 0.8, zk - h * 0.4), (-w, zk + 0.05), (0.0, zk + 0.08)] rr.append([Vector((a, Yi(x), b)) for a, b in sec]) loft_rings(att, rr, GUN) # ================================================================== stern fins and their mount plates (SIDE: the "T" in the waist band) FIN = STERN_FIN x_m0, x_m1 = STERN_FIN_MOUNT["x"] for sg in SG: # the mount plate: a strip across the band from the upper rim to the lower rim, 2 px proud of the band xm = 0.5 * (x_m0 + x_m1) zs = [Zi(STERN_FIN_MOUNT["y"][0] + (STERN_FIN_MOUNT["y"][1] - STERN_FIN_MOUNT["y"][0]) * q / 10) for q in range(11)] surface_bar(att, sg, [(Yi(xm), z) for z in zs], Lp(x_m1 - x_m0) * 0.5, Lp(STERN_FIN_MOUNT["proud_px"]) + 0.01, GUN, level=LV_BAND) # the fin: sections out from the band; root chord x 574.5 (the mount) .. 606, 16 px thick, rounded front (SIDE); tip at # 64 px lateral (TOP) with a shorter chord at x 594 .. 604 rings = [] for q in range(6): t = q / 5 lat_out = Lp(FIN["tip_lateral_px"]) x_a = 574.5 + (594.0 - 574.5) * t ** 0.8 x_f = 606.0 - 2.0 * t h = Lp(16.0) * 0.5 * (1.0 - 0.55 * t) zc = Zi(FIN["y_c"]) yc = Yi(0.5 * (x_a + x_f)) if q == 0: lat = hull_x_at(zc, yc) - 0.10 else: lat = hull_x_at(zc, yc) + LV_BAND + (lat_out - hull_x_at(zc, yc) - LV_BAND) * t st2 = stadium2d(min(Yi(x_a), Yi(x_f)), max(Yi(x_a), Yi(x_f)), h, 3) rings.append([Vector((sg * lat, u, zc + v)) for u, v in st2]) loft_rings(att, rings, GUN) # ================================================================== the dorsal ridge (TOP's teardrop x 551-630 on the upper aft body) DR = DORSAL_RIDGE hw_f = lambda x: Lp(lin_px([(x_, h_) for x_, h_ in DR["top_planform"]], x)) rings = [] for x in (548.6, 551.0, 556.0, 565.0, 575.0, 590.0, 600.0, 610.0, 618.0, 624.0, 628.0): y = Yi(x) zt = TOPDRAWNf(y) hw = max(0.03, hw_f(min(max(x, 551.0), 630.0))) zh = TOPf(y) hgt = max(0.006, zt - zh) sec = [(hw + 0.03, zh - 0.03), (hw + 0.012, zh + 0.004), (hw, zh + 0.35 * hgt), (hw * 0.93, zh + 0.75 * hgt), (hw * 0.75, zt - 0.08 * hgt), (hw * 0.3, zt)] sec = sec + [(-a, b) for a, b in reversed(sec)] rings.append([Vector((a, y, b)) for a, b in sec]) loft_rings(att, rings, GUN, ladder=True) # its aft face (BACK's "tombstone") carries a slot: a dark plate in a low rim yA = Yi(548.6) + 0.004 zA = TOPf(Yi(548.6)) + 0.06 rrect_bar(att, Vector((0, yA - 0.006, zA)), Vector((0, yA + 0.004, zA)), 0.11, 0.035, 0.02, RECESS, up=Vector((0, 0, 1))) # ================================================================== bilge strakes (BACK: two slanted plates under the aft lower body) BS = BILGE_STRAKE for sg in SG: secs = [] for q in range(6): x = BS["x"][0] + (BS["x"][1] - BS["x"][0]) * q / 5 y = Yi(x) e = math.sin(math.pi * q / 5) ** 0.5 lat0 = Lp(BS["lateral_px"]) - 0.17 rp = Vector((sg * lat0, y, hull_z_at(sg * lat0, y, top=False) + 0.04)) tipd = Vector((sg * math.cos(math.radians(BS["angle_deg"])), 0, -math.sin(math.radians(BS["angle_deg"])))).normalized() span = Lp(BS["span_px"]) * (0.35 + 0.65 * e) mid = rp + tipd * (span * 0.5) secs.append((mid, tipd, Vector((0, 1, 0)).cross(tipd).normalized(), span, Lp(BS["thick_px"]))) # a foil whose chord is the span (from the root inside the hull out to the tip), lofted along the hull's length foil(att, secs, GUN) # ================================================================== the deck: the two lamp heads on pylons, the sensor box, the antenna EM = EMITTER EM_LAT = Lp(EM["lateral_px"]) EM_Z = Zi(EM["lens_y"]) Y_EM0, Y_EM1 = Yi(EM["x"][0] + 2.0), Yi(EM["lens_x"]) LAMP_HEADS = [] for sg in SG: c = Vector((sg * EM_LAT, 0.0, EM_Z)) R_H = Lp(EM["half_width_px"]) * 0.95 R_LN = R_H - 0.024 prof = [(0.03, Y_EM0 + 0.004), (0.075, Y_EM0 - 0.006), (0.105, Y_EM0 - 0.03), (R_H, Y_EM0 - 0.075), (R_H, Y_EM1 + 0.02), (R_H - 0.004, Y_EM1 + 0.004), (R_H - 0.016, Y_EM1), (R_LN + 0.002, Y_EM1), (R_LN, Y_EM1 + 0.012)] revolve_y(att, prof, [GUN, GUN, GUN, GUN, FRAME, FRAME, FRAME, FRAME], c, segs=16, cap0=True, cap1=False) revolve_y(att, [(R_LN, Y_EM1 + 0.012), (R_LN * 0.6, Y_EM1 + 0.004)], LAMPW, c, segs=16, cap0=False, cap1=True) LAMP_HEADS.append((c + Vector((0, Y_EM1 - 0.02, 0)), sg)) # the pylon: a tapered neck from the shoulder up under the head, leaning out (FRONT) ym = 0.5 * (Y_EM0 + Y_EM1) + 0.03 zbase = hull_z_at(sg * (EM_LAT - 0.06), ym) rings = [] for t in (0.0, 0.45, 1.0): lat = EM_LAT - 0.07 * (1 - t) z = (zbase - 0.06) + (EM_Z - 0.03 - (zbase - 0.06)) * t w, h = 0.085 - 0.02 * t, 0.10 - 0.02 * t rings.append([Vector((sg * lat + u, ym + v, z)) for u, v in rrect2d(w * 0.5, h, 0.02, 2)]) loft_rings(att, rings, GUN) SB = SENSOR_BOX y_sb = Yi(0.5 * (SB["x"][0] + SB["x"][1])) hs = Lp(SB["half_px"]) z_top_sb = Zi(SB["top_y"]) z_bot_sb = z_top_sb - SB["box_h_m"] z_deck = hull_z_at(0.0, y_sb) qcyl(att, SB["stalk_dia_m"] * 0.5, z_bot_sb - z_deck + 0.06, Vector((0, y_sb, 0.5 * (z_bot_sb + z_deck - 0.06) + 0.01)), FRAME, segs=12) qbox(att, (2 * hs, 2 * hs, SB["box_h_m"]), Vector((0, y_sb, 0.5 * (z_top_sb + z_bot_sb))), GUN, bevel=0.012) qbox(att, (0.12, 0.012, 0.06), Vector((0, y_sb - hs - 0.004, 0.5 * (z_top_sb + z_bot_sb) + 0.01)), RECESS, bevel=0.003) qbox(att, (0.20, 0.13, 0.03), Vector((0, y_sb, z_deck + 0.005)), GUN, bevel=0.008) # the antenna (SM_Grouper_Antenna, moving: folds aft about X at its foot): its foot bracket stays on the hull AN = ANTENNA A_FOOT = Vector((0.0, Yi(AN["foot"][0]), Zi(AN["foot"][1]))) A_TIP = Vector((0.0, Yi(AN["tip"][0]), Zi(AN["tip"][1]))) antenna = MB("SM_Grouper_Antenna", [FRAME, STEEL, LAMPW]) d_ax = (A_TIP - A_FOOT).normalized() tube(antenna, [A_FOOT - d_ax * 0.012, A_FOOT + d_ax * 0.05, A_TIP - d_ax * 0.02, A_TIP], [0.019, Lp(AN["dia_px"]) * 0.5, 0.012, 0.010], FRAME, sides=8) qsphere(antenna, A_TIP, (0.016, 0.016, 0.016), LAMPW, cuts=1) qcyl(antenna, 0.026, 0.042, A_FOOT, STEEL, rot=(0, math.pi / 2, 0), segs=12) # the hinge's knuckle on the mast fb0, fb1 = Yi(AN["foot_bracket"][0]), Yi(AN["foot_bracket"][2]) zdk = hull_z_at(0.0, A_FOOT.y) for sx in (-1, 1): # a clevis: two cheeks either side rrect_bar(att, Vector((sx * 0.033, A_FOOT.y, zdk - 0.03)), Vector((sx * 0.033, A_FOOT.y, A_FOOT.z + 0.012)), 0.006, 0.032, 0.005, FRAME, up=Vector((0, 1, 0))) qbox(att, (0.10, abs(fb0 - fb1), 0.035), Vector((0, 0.5 * (fb0 + fb1), zdk + 0.006)), FRAME, bevel=0.008) qcyl(att, 0.008, 0.088, A_FOOT, STEEL, rot=(0, math.pi / 2, 0), segs=8) # the pin # ================================================================== the scanner drum on its cradle under the nose SD = SENSOR_DRUM DR_AX = Vector((0.0, 0.0, Zi(SD["y_c"]))) R_DR = Lp(SD["r"]) R_FL = Lp(SD["flange_r"]) E_DR = Lp(SD["edge_r"]) Yf0, Yf1 = Yi(SD["flange_x"][0]), Yi(SD["flange_x"][1]) Yd0, Yd1 = Yi(SD["x_body"][0]), Yi(SD["x_face"]) REC = Lp(SD["face_recess"]) prof = [(0.12, Yf0 + 0.01), (R_FL - 0.03, Yf0), (R_FL, Yf0 - 0.02), (R_FL, Yf1 + 0.005), (R_DR - E_DR, Yd0)] for q in range(1, 4): a = math.pi / 2 * q / 4 prof.append((R_DR - E_DR + E_DR * math.sin(a), Yd0 - E_DR + E_DR * math.cos(a))) prof += [(R_DR, Yd0 - E_DR)] for xr in (931.0, 934.5, 938.0, 942.0): # SIDE's ribs across the drum body: shallow grooves (critique m5) yr = Yi(xr) prof += [(R_DR, yr + 0.009), (R_DR - 0.007, yr + 0.004), (R_DR - 0.007, yr - 0.004), (R_DR, yr - 0.009)] prof += [(R_DR, Yd1 + E_DR)] for q in range(1, 4): a = math.pi / 2 * q / 4 prof.append((R_DR - E_DR + E_DR * math.cos(a), Yd1 + E_DR - E_DR * math.sin(a))) R_LENS = R_DR - E_DR - 0.012 prof += [(R_DR - E_DR, Yd1), (R_LENS, Yd1), (R_LENS, Yd1 + REC)] revolve_y(att, prof, [FRAME] * 4 + [GUN] * (len(prof) - 7) + [FRAME, FRAME], DR_AX, segs=32, cap0=True, cap1=True, ang0=math.pi / 32) for xr in SD["flange_ribs_x"]: # the flange's ribs yr = Yi(xr) revolve_y(att, [(R_FL - 0.01, yr + 0.008), (R_FL + 0.014, yr + 0.006), (R_FL + 0.014, yr - 0.006), (R_FL - 0.01, yr - 0.008)], FRAME, DR_AX, segs=32, cap0=False, cap1=False) # the lens (the concept: a glowing faceted crystal; the sheet's layout: six sectors round a hexagonal hub, dividers at # 30/90/../330 deg): a raised hexagon of six facets and twelve faceted cells, dark ribs between the sectors Y_LF = Yd1 + REC # the lens's base plane def lp(r, a, dz=0.0): return Vector((DR_AX.x + r * math.cos(a), Y_LF - dz, DR_AX.z + r * math.sin(a))) lens = bmesh.new() R_HEX = Lp(SD["face_r"]["hub_hex"]) * KF * R_DR / (Lp(SD["face_r"]["rim"][1]) * KF) R_MID = 0.5 * (R_HEX + R_LENS) C0 = lens.verts.new(lp(0.0, 0.0, 0.065)) HV = [lens.verts.new(lp(R_HEX, math.radians(30 + 60 * i), 0.020)) for i in range(6)] HM = [lens.verts.new(lp(R_HEX * 0.866 * 1.0, math.radians(60 + 60 * i), 0.026)) for i in range(6)] for i in range(6): lens.faces.new((C0, HM[i - 1], HV[i], HM[i])) for i in range(6): # a sector between hex vertices i and i+1: an inner and an outer cell a0, a1 = math.radians(30 + 60 * i), math.radians(90 + 60 * i) am = 0.5 * (a0 + a1) g = 0.010 inner = [HV[i], HM[i], HV[(i + 1) % 6]] mid0, mid1 = lens.verts.new(lp(R_MID, a0 + 0.04, 0.018)), lens.verts.new(lp(R_MID, a1 - 0.04, 0.018)) midc = lens.verts.new(lp(R_MID * 1.02, am, 0.030)) out0, out1 = lens.verts.new(lp(R_LENS - g, a0 + 0.03, 0.004)), lens.verts.new(lp(R_LENS - g, a1 - 0.03, 0.004)) outc = lens.verts.new(lp(R_LENS - g, am, 0.006)) lens.faces.new((HV[i], mid0, midc, HM[i])) lens.faces.new((HM[i], midc, mid1, HV[(i + 1) % 6])) lens.faces.new((mid0, out0, outc, midc)) lens.faces.new((midc, outc, out1, mid1)) lens.normal_update() for f in lens.faces: if f.normal.y > 0: f.normal_flip() lay_cr = lens.faces.layers.int.new("crisp") # faceted crystal: every edge sharp for f in lens.faces: f.material_index = att.mi(SCAN) f[lay_cr] = 1 att.merge(lens, None) for i in range(6): # a dark rib round the hexagon rrect_bar(att, lp(R_HEX * 1.02, math.radians(30 + 60 * i), 0.024), lp(R_HEX * 1.02, math.radians(90 + 60 * i), 0.024), 0.010, 0.012, 0.004, FRAME, up=Vector((0, -1, 0))) for i in range(6): # the dark ribs on the sector lines and round the hexagon a = math.radians(30 + 60 * i) rrect_bar(att, lp(R_HEX, a, 0.026), lp(R_LENS, a, 0.008), 0.012, 0.012, 0.005, FRAME, up=Vector((0, -1, 0))) SCANNER_C = Vector((DR_AX.x, Y_LF - 0.05, DR_AX.z)) # the cradle: in each side plane (lateral 0.576, just outside the drum's rim) a hanger under the nose carrying the upper arm and # the bent U tube, the lower rail from the cheek to the flange's clevis; a cross tube under the drum, a top bar behind it, # clamps from the rails to the flange CR = CRADLE L_CR = Lp(CR["lateral_px"]) def cr(x, y, sg): return Vector((sg * L_CR, Yi(x), Zi(y))) R_TUBE = Lp(6.0) * 0.5 # Critique round 1 (M1): the mount is a SOLID BRACKET BLOCK under the nose (the concept's dark block from the lower nose to the # drum's back; SIDE's closed shape on the flange's top at x 893-925, y 398-412, with bolts), 0.30 m wide on the centre line, # its aft face sloped; under it, in each side plane (FRONT's side plates at +-0.577), the BENT TUBE as drawn: two forward- # leaning arms joined by a bend at y 460-466 (8x zoom of SIDE: the arm from the top bar (904, 402) down to the bend at # (881-891, 455-466), the other up to the clevis at the flange's lower rear (917, 428)), and SLED RAILS from a plate on the # cheek (x 849-853) forward to the bend (SIDE y 445-470: rails at y 449 and 461). The block replaces the thin upper arms; # the top bar runs through it (FRONT's frame: top bar, side tubes, bottom cross tube). BLOCK_HW = 0.15 BLOCK_PX = [(876.0, 397.0), (923.0, 397.0), (923.0, 416.5), (898.0, 413.5)] # SIDE: top aft, top front, bottom front, bottom aft def _block_ring(inset): """the block's outline (Y, Z) offset inward by `inset` m (each side moved in parallel, corners where they meet), as 4 sides x 4 segments (a Coons-cap ring)""" pts = [Vector((Yi(x), Zi(y))) for x, y in BLOCK_PX] cen = sum(pts, Vector((0.0, 0.0))) / 4 lines = [] for k in range(4): a, b = pts[k], pts[(k + 1) % 4] t = (b - a).normalized() n = Vector((-t.y, t.x)) if n.dot(cen - a) < 0: n = -n lines.append((a + n * inset, t)) corners = [] for k in range(4): (p1, t1), (p2, t2) = lines[k - 1], lines[k] den = t1.x * t2.y - t1.y * t2.x s_ = ((p2.x - p1.x) * t2.y - (p2.y - p1.y) * t2.x) / den corners.append(p1 + t1 * s_) ring = [] for k in range(4): a, b = corners[k], corners[(k + 1) % 4] ring += [a.lerp(b, q / 4) for q in range(4)] return ring rings_b = [] for xl, ins in ((-BLOCK_HW, 0.014), (-BLOCK_HW + 0.004, 0.004), (-BLOCK_HW + 0.014, 0.0), (BLOCK_HW - 0.014, 0.0), (BLOCK_HW - 0.004, 0.004), (BLOCK_HW, 0.014)): rings_b.append([Vector((xl, p.x, p.y)) for p in _block_ring(ins)]) loft_rings(att, rings_b, FRAME) for sx in (-1, 1): # bolts on its sides where it meets the flange for xb_, yb_ in ((912.5, 401.5), (917.0, 401.5), (912.5, 409.5), (917.0, 409.5)): qcyl(att, 0.014, 0.020, Vector((sx * (BLOCK_HW + 0.006), Yi(xb_), Zi(yb_))), STEEL, rot=(0, math.pi / 2, 0), segs=8) U_PATH = [(900.5, 402.5), (893.0, 422.0), (885.0, 443.0), (880.0, 455.0), (881.0, 461.5), (884.5, 466.0), (890.0, 466.5), (896.0, 460.0), (906.0, 445.0), (916.5, 428.5)] # 8x zoom of SIDE: arm 21 deg, arm 35 deg, the bend TOP_BAR = (900.5, 402.5) for sg in SG: # a hanger from the nose's underside down to the top bar over each side tube (the nose is 0.62 m wide here) yh0, yh1 = Yi(895.0), Yi(901.5) zt_ = min(hull_z_at(sg * L_CR + dx_, y_, top=False) for dx_ in (-0.02, 0.02) for y_ in (yh0, yh1)) rrect_bar(att, Vector((sg * L_CR, 0.5 * (yh0 + yh1), zt_ + 0.03)), Vector((sg * L_CR, 0.5 * (yh0 + yh1), Zi(403.0))), 0.030, 0.085, 0.012, FRAME, up=Vector((0, 1, 0))) tube(att, catmull([tuple(cr(x, y, sg)) for x, y in U_PATH], 3), R_TUBE, FRAME, sides=8) # the sled: two rails from the cheek plate forward to the bend, the plate up into the cheek zc_ = hull_z_at(sg * L_CR, Yi(851.0), top=False) rrect_bar(att, Vector((sg * L_CR, Yi(851.0), zc_ + 0.04)), Vector((sg * L_CR, Yi(851.0), Zi(470.0))), 0.035, 0.035, 0.010, FRAME, up=Vector((0, 1, 0))) for yr_ in (449.0, 461.0): rrect_bar(att, cr(850.5, yr_, sg), cr(884.0, yr_, sg) + Vector((0, 0, 0)), 0.032, Lp(4.0) * 0.5, 0.010, FRAME) x0c, y0c, x1c, y1c = CR["clevis"] qbox(att, (0.07, Lp(x1c - x0c), Lp(y1c - y0c)), cr(0.5 * (x0c + x1c), 0.5 * (y0c + y1c), sg), FRAME, bevel=0.01) for yc_ in CR["flange_clamps_y"]: # clamps from the side frame to the flange zc2 = Zi(yc_) dz = zc2 - DR_AX.z rin = math.sqrt(max(0.0, R_FL ** 2 - dz * dz)) - 0.02 rrect_bar(att, Vector((sg * rin, Yi(922.0), zc2)), Vector((sg * (L_CR + 0.02), Yi(922.0), zc2)), 0.03, 0.025, 0.008, FRAME, up=Vector((0, 0, 1))) tube(att, [cr(CR["cross_tube_x"], CR["cross_tube_y"], 1), cr(CR["cross_tube_x"], CR["cross_tube_y"], -1)], R_TUBE, FRAME, sides=8) # a small lamp beside the drum's face on each side (critique round 1 m4: the concept's bright lamp at picture (703, 358), right # of and below the drum's face). Placed with the fitted concept camera: its ray through that pixel crosses the face plane inside # the drum's rim, so a lamp further aft (on the cradle) would be hidden by the drum; on the face's rounded rim at r 0.505, # 26 deg below the port side, it projects about 8 px from the concept's lamp. Mirrored to starboard. A short housing sunk into # the rim, its lens 2 cm proud of the face, facing forward. DRUM_LAMPS = [] for sg in SG: a_ = math.radians(-26.0) cL = Vector((sg * 0.505 * math.cos(a_), 0.0, DR_AX.z + 0.505 * math.sin(a_))) yF = Yd1 - 0.024 revolve_y(att, [(0.020, Yd1 + 0.05), (0.034, Yd1 + 0.04), (0.034, yF + 0.012), (0.030, yF), (0.022, yF)], [FRAME, FRAME, FRAME, FRAME], cL, segs=16, cap0=True, cap1=False) revolve_y(att, [(0.022, yF), (0.012, yF - 0.004)], LAMPW, cL, segs=16, cap0=False, cap1=True) DRUM_LAMPS.append((Vector((cL.x, yF - 0.006, cL.z)), sg)) tube(att, [cr(TOP_BAR[0], TOP_BAR[1], 1), cr(TOP_BAR[0], TOP_BAR[1], -1)], Lp(4.0) * 0.5, FRAME, sides=8) # ================================================================== cheek fins (FRONT's small slanted fins on the lower nose's cheeks) CF = CHEEK_FIN for sg in SG: secs = [] for q in range(5): x = CF["x"][0] + (CF["x"][1] - CF["x"][0]) * q / 4 y = Yi(x) lat = Lp(CF["lateral_px"]) zr = None for zz in [-0.5 - 0.02 * k for k in range(60)]: if hull_x_at(zz, y) < lat - 0.05: zr = zz break if zr is None: zr = -1.0 e = math.sin(math.pi * q / 4) ** 0.6 a = math.radians(CF["angle_deg"]) d = Vector((sg * math.sin(a), 0, -math.cos(a))) span = 0.25 * (0.4 + 0.6 * e) + 0.06 rp = Vector((sg * (lat - 0.05), y, zr + 0.04)) secs.append((rp + d * span * 0.5, d, Vector((0, 1, 0)).cross(d).normalized(), span, Lp(CF["thick_px"]))) foil(att, secs, GUN) # ================================================================== the pods' ends: the nose with its lamp, the ringed tail cone # (critique round 1, M2/M3). The pod is a straight nacelle on its axis (+-1.017, z -1.382); its middle is the hull grid's lobe # (gr_shape.py), its two ends are these revolves, 4 mm over the lobe's radius where they overlap it (the lobe sinks into the # hull inside them; the finish pass prunes its hidden faces), each beginning with a dark lip ring (a seam) over the lobe. # * NOSE (x 831 -> 845): SIDE's rounded bottom outline (an ellipse 10 px long from x 836), then a dark bezel and the lamp # (FRONT's rings, the concept's bright round lamp), r 0.209 / 0.145 (FRONT px x KF); # * TAIL CONE (x 768 -> 735): the drawn taper (SIDE's bottom line) stepping in at BACK's rings, r 15, 11 and 8 px (8 mm steps), # closed by an end cap r 5 px: from behind four concentric circles on the pod's axis, as BACK draws them. Aft of x 747 the # pod hangs clear of the hull (the drawn tail is 0.05-0.2 m from it). POD_LAMPS = [] R_BEZ = Lp(POD["face"]["r_duct"]) * KF R_PL = Lp(POD["face"]["r_ring"]) * KF N_POD = 20 def _x_at_r(r_px, lo, hi): """image x on the tail's taper where the drawn radius is r_px (bisection; the taper grows forward)""" for _ in range(50): mid = 0.5 * (lo + hi) if pod_r_true(mid) < Lp(r_px): lo = mid else: hi = mid return 0.5 * (lo + hi) X_RINGS = [_x_at_r(r_, 733.0, 768.0) for r_ in POD["tail_rings_r"][:3]] # r 15, 11, 8 px X_CAP = _x_at_r(POD["tail_rings_r"][3], 733.0, 768.0) # r 5 px POD_TAIL = {"rings_x": [round(x, 1) for x in X_RINGS], "cap_x": round(X_CAP, 1)} for sg in SG: c = Vector((sg * POD_L, 0.0, POD_Z)) # ---- the nose (forward = -Y): from inside the lobe, the lip, the elliptic nose, the bezel, the lamp rN = pod_r_true(831.0) prof = [(rN - 0.014, Yi(830.4)), (rN + 0.010, Yi(830.8)), (rN + 0.010, Yi(831.8)), (rN + 0.004, Yi(832.2))] for xq in (834.0, 836.0, 838.0, 839.6, 841.0, 842.2): prof.append((pod_r_true(xq) + 0.004, Yi(xq))) yB = Yi(843.6) prof += [(R_BEZ + 0.012, yB + 0.004), (R_BEZ, yB - 0.004), (R_BEZ, yB - 0.022), (R_BEZ - 0.010, yB - 0.032), (R_BEZ - 0.030, yB - 0.032), (R_PL + 0.012, yB - 0.016), (R_PL, yB - 0.012)] mats_ = [FRAME, FRAME, FRAME] + [GUN] * 7 + [FRAME] * 6 revolve_y(att, prof, mats_[:len(prof) - 1], c, segs=N_POD, cap0=False, cap1=False) revolve_y(att, [(R_PL, yB - 0.012), (R_PL * 0.75, yB - 0.020), (R_PL * 0.40, yB - 0.025)], LAMPW, c, segs=N_POD, cap0=False, cap1=True) POD_LAMPS.append((c + Vector((0, yB - 0.03, 0)), sg)) # ---- the tail cone (aft = +Y): from inside the lobe, the lip, the taper with three stepped rings, the end cap rT = pod_r_true(768.6) prof = [(rT - 0.014, Yi(769.4)), (rT + 0.010, Yi(769.0)), (rT + 0.010, Yi(768.0)), (pod_r_true(767.4) + 0.004, Yi(767.6))] mats_ = [FRAME, FRAME, FRAME] # telescoping: each segment runs from 4 mm under the drawn taper at its forward ring to 4 mm over it at the next ring aft, # where it steps 8 mm in (a dark ring face): from behind the steps' edges are circles at BACK's radii xs_ = [767.4] + X_RINGS + [X_CAP] for k in range(len(xs_) - 1): xa, xb = xs_[k], xs_[k + 1] for q in range(1, 3): t_ = q / 2 xq = xa + (xb - xa) * t_ off = 0.004 if k == 0 else (-0.004 + 0.008 * t_) prof.append((pod_r_true(xq) + off, Yi(xq))) mats_.append(GUN) if k < 3: # the ring: a step 8 mm in, its face dark prof.append((pod_r_true(xb) - 0.004, Yi(xb) + 0.005)) mats_.append(FRAME) r_cap = pod_r_true(X_CAP) + 0.004 prof += [(r_cap * 0.85, Yi(X_CAP) + 0.010), (r_cap * 0.55, Yi(X_CAP) + 0.012)] mats_ += [FRAME, FRAME] revolve_y(att, prof, mats_, c, segs=N_POD, cap0=False, cap1=True) # ---- TOP's vents and raised capsule on the pod's upper outer side (TOP px: x0, x1, lateral in, lateral out), on the # nacelle's own surface (its axis and radius), 3 mm proud def pod_pt(u, v, h=0.0, sg=sg): x_ = ximg(u) r_ = pod_r_true(min(max(x_, 733.5), 836.0)) + (0.004 if x_ < 768.0 else 0.0) cth = max(-1.0, min(1.0, (v - POD_L) / r_)) th = math.acos(cth) n_ = Vector((sg * cth, 0.0, math.sin(th))) return Vector((sg * POD_L, u, POD_Z)) + n_ * (r_ + h), n_ for (x0v, x1v, l0, l1) in POD["vents"]: # on the nacelle's body between its tail cone (lip ring at x 768-769.4) and its nose (lip at x 831-832): TOP draws the # long vent from x 737 and the short one to x 840, which would run over those lip rings and the cone's steps x0v, x1v = max(x0v, 771.0), min(x1v, 829.0) poly = [(Yi(x), Lp(l)) for x, l in rrect_px(x0v, l0, x1v, l1, 0.5 * (l1 - l0))] frame_sweep(att, poly, lambda u, v: pod_pt(u, v, 0.0), [(0.010, -0.003), (0.010, 0.004), (0.006, 0.006), (0.0, 0.006), (0.0, 0.002)], FRAME, None, max_step=0.07) chart_plate(att, poly, lambda u, v: pod_pt(u, v, 0.0), RECESS, thick=0.002, lift=0.0, cell=0.05) x0v, x1v, l0, l1 = POD["raised_capsule"] x0v = max(x0v, 771.0) # (TOP: x 757-782) forward of the tail cone's lip ring poly = [(Yi(x), Lp(l)) for x, l in rrect_px(x0v, l0, x1v, l1, 0.5 * (l1 - l0))] chart_plate(att, poly, lambda u, v: pod_pt(u, v, 0.0), GUN, thick=0.022, lift=0.0, cell=0.05) # ================================================================== flanks: the vents and panels aft of the door, the trace panel FD = FLANK_DETAILS for sg in SG: # the fill port and the long slot touch on the sheet (x 676): each drawn in 1.2 px so their rims stay apart x0, y0, x1, y1 = FD["fill_port"] slot_trim(att, rrect_px(x0, y0, x1 - 1.2, y1, 3.0), sg, LV_BAND, FRAME, RECESS, rim=0.009) for nm in ("slot_1", "slot_2", "slot_3"): x0, y0, x1, y1 = FD[nm] if nm == "slot_1": x0 += 1.2 slot_trim(att, rrect_px(x0, y0, x1, y1, 0.5 * (y1 - y0)), sg, LV_BAND, FRAME, RECESS, rim=0.009) x0, y0, x1, y1 = FD["panel_right"] slot_trim(att, rrect_px(x0, y0, x1, y1, 2.5), sg, LV_BAND, FRAME, RECESS) for xd, yd in FD["dots"]: p, n = on_side(sg, Yi(xd), Zi(yd), LV_BAND) qsphere(att, p, (0.022, 0.022, 0.022), STEEL, cuts=1) x0, y0, x1, y1 = FD["trace_panel"] chart_plate(att, [(Yi(x), Zi(y)) for x, y in rrect_px(x0, y0, x1, y1, 3.0)], lambda u, v, sg=sg: on_side(sg, u, v, LV_BAND), GUN, thick=0.004, cell=0.10) # the hull's own finish (critique round 1 M4) # the pod's flank slot (SIDE x 790-808, y 439-443) x0, y0, x1, y1 = POD["slot"] slot_trim(att, rrect_px(x0, y0, x1, y1, 2.0), sg, LV_BAND, FRAME, RECESS) # the slot on the duct's flank (SIDE x 530, y 368-388) xs, ys0, ys1 = DUCT["flank_slot"] # on the shroud's forward part (under its step), following its curvature (it stood off the round shroud at its ends) rings = [] sec = rrect2d(0.012, 0.007, 0.005, 2) for q in range(7): z_ = Zi(ys0) + (Zi(ys1) - Zi(ys0)) * q / 6 a_ = math.asin(max(-1.0, min(1.0, (z_ - DUCT_AX.z) / R_FWD))) nrm_ = Vector((sg * math.cos(a_), 0.0, math.sin(a_))) c_ = Vector((0.0, Yi(xs), DUCT_AX.z)) + nrm_ * (R_FWD + 0.002) rings.append([c_ + Vector((0, u, 0)) + nrm_ * v for u, v in sec]) loft_rings(att, rings, RECESS) # ================================================================== the belly: the ventral port's rings and the transducer VP = VENTRAL_PORT def belly_ring(r0, r1, mat, lift=0.004, segs=72): bm2 = bmesh.new() rows_ = [] for r_ in (r0, r1): row = [] for k in range(segs): a = 2 * math.pi * k / segs x, y = r_ * math.cos(a), Y_CL + r_ * math.sin(a) z = hull_z_at(x, y, top=False) row.append(bm2.verts.new(Vector((x, y, z - lift)))) rows_.append(row) for k in range(segs): k2 = (k + 1) % segs bm2.faces.new((rows_[0][k], rows_[1][k], rows_[1][k2], rows_[0][k2])) bm2.normal_update() if sum(f.normal.z for f in bm2.faces) > 0: bmesh.ops.reverse_faces(bm2, faces=bm2.faces[:]) for f in bm2.faces: f.material_index = att.mi(mat) att.merge(bm2, None) for r0, r1 in ((Lp(VP["r_groove"][0]) - 0.008, Lp(VP["r_groove"][0]) + 0.008), (Lp(VP["r_groove"][1]) - 0.008, Lp(VP["r_groove"][1]) + 0.008)): belly_ring(r0, r1, FRAME, lift=0.003) VT = VENTRAL_TRANSDUCER yt = Yi(0.5 * (VT["x"][0] + VT["x"][1])) qsphere(att, Vector((0.0, yt, hull_z_at(0.0, yt, top=False) + 0.01)), (Lp(VT["half_px"]) * 1.6, Lp(VT["x"][1] - VT["x"][0]) * 0.5, Lp(VT["proud_px"]) + 0.01), FRAME, cuts=2) REPORT["stage"] = "fittings"