# 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"]) # Critique round 2 (m4): a STRAIGHT drum (the sheet draws x 928-950 with small rounded corners, the concept a smooth dark # cylinder): edge radius 0.06 m at both ends, SIDE's four lines across the body as thin RAISED rings (6 mm proud, 8 mm wide; # round 1's shallow grooves read as a tyre's tread); the face a thick rim round the recessed lens (the concept's ring) E_DR = 0.060 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 ring lines across the body: thin raised rings yr = Yi(xr) prof += [(R_DR, yr + 0.009), (R_DR + 0.006, yr + 0.004), (R_DR + 0.006, 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)] _mats = [FRAME] * 4 + [GUN] * (len(prof) - 7) + [FRAME, FRAME] for k_ in range(len(prof) - 1): # the rings dark (FrameDark), the body gunmetal (r0_, y0_), (r1_, y1_) = prof[k_], prof[k_ + 1] if 4 <= k_ < len(prof) - 3 and max(r0_, r1_) > R_DR + 0.001: _mats[k_] = FRAME revolve_y(att, prof, _mats, 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 (critique round 2, M2): the drum hangs from a SOLID BLOCK under the nose and a bent ROUND tube in each side plane. # * the block (0.34 m wide on the centre line, dark): its outline in SIDE px is the sheet's bracket plate (front edge on the # flange's back face at x 916.3 from y 401 down to its bottom at y 450, the bottom forward of x 906, a short lower-aft # edge to (889, 440.5)) joined to the chin: its top runs inside the chin's underside (y 396-397) from x 876 to the nose's # front (x 913.5), then down onto the flange's top (916.3, 401); its aft face slopes from (889, 440.5) up to the chin at # x 876 (the concept's solid block with its sloped back, as deep as the drum's upper two thirds). Convex, so its side # faces are clean Coons patches. Bolt heads where the flange is bolted to its front face: on each side by the flange # (SIDE's bolts at x 910-918) and round the flange's back face beside it. # * two round STUBS out of the block's sides to each side plane (lateral 0.577, FRONT's frame): the upper one at (905, 407), # the lower one at the X-marked clevis (912, 445) (FRONT's clamps at y 404-410 and 440-450); each ends in a hub with a pin; # * the BENT TUBE (round, 0.10 m) in each side plane from the upper hub down the aft arm, round the bend at y 462-469, and # up the forward arm into the clevis hub at (912, 445); a cross tube joins the two bends under the drum (FRONT's bottom # bar); # * the SLED: a bracket plate on the cheek (SIDE x 848-866 up into the hull, its foot at y 470) and the two rails (y 449 and # 461) from the plate's front edge to the bend, ending inside the tube. # Round 1's hangers, the square-section U tube, the cross bars through the block and the clamps to the flange are gone. 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 BLOCK_HW = 0.17 BLOCK_SIDES = [[(876.0, 397.0), (913.5, 396.0), (916.3, 401.0)], # top: inside the chin, then down onto the flange's top [(916.3, 401.0), (916.3, 450.0)], # front: on the flange's back face [(916.3, 450.0), (906.0, 450.0), (889.0, 440.5)], # bottom and its short lower-aft edge [(889.0, 440.5), (876.0, 397.0)]] # aft: sloped up into the chin def _block_ring(inset, n=4): """the block's outline (Y, Z), each vertex moved inward along its bisector by `inset` m, as 4 sides x n segments (a Coons-cap ring, first point the top-aft corner)""" corners = [] for sd in BLOCK_SIDES: for p in sd[:-1]: corners.append(p) m = len(corners) cv = [Vector((Yi(x), Zi(y))) for x, y in corners] cen = sum(cv, Vector((0.0, 0.0))) / m moved = {} for k in range(m): a, b, c = cv[k - 1], cv[k], cv[(k + 1) % m] t0, t1 = (b - a).normalized(), (c - b).normalized() n0, n1 = Vector((-t0.y, t0.x)), Vector((-t1.y, t1.x)) if n0.dot(cen - b) < 0: n0, n1 = -n0, -n1 bis = (n0 + n1).normalized() moved[corners[k]] = b + bis * inset / max(0.35, bis.dot(n0)) ring = [] for sd in BLOCK_SIDES: pts = [moved[p] for p in sd] seg = [(pts[q + 1] - pts[q]).length for q in range(len(pts) - 1)] tot = sum(seg) for j in range(n): s_ = tot * j / n q = 0 while q < len(seg) - 1 and s_ > seg[q]: s_ -= seg[q] q += 1 ring.append(pts[q].lerp(pts[q + 1], s_ / seg[q])) 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 by the flange (SIDE x 910-918) for xb_, yb_ in ((910.5, 405.0), (914.5, 405.0), (910.5, 413.0), (914.5, 413.0), (910.5, 441.0), (914.5, 441.0)): 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) for deg in (0.0, 30.0, -30.0, 180.0, 150.0, 210.0): # the flange's back face bolted round the block a = math.radians(deg) qcyl(att, 0.016, 0.022, Vector((DR_AX.x + 0.37 * math.cos(a), Yf0 + 0.008, DR_AX.z + 0.37 * math.sin(a))), STEEL, rot=(math.pi / 2, 0, 0), segs=8) STUBS = [(905.0, 407.0), (912.0, 445.0)] # the upper stub and the clevis (SIDE px) U_PATH = [(905.0, 407.0), (901.0, 416.0), (895.0, 430.0), (888.0, 445.0), (882.5, 455.5), (881.5, 462.0), (885.0, 466.0), (890.0, 466.5), (896.0, 461.0), (901.0, 455.0), (906.0, 449.0), (912.0, 445.0)] for sg in SG: for xs_, ys_ in STUBS: tube(att, [Vector((sg * (BLOCK_HW - 0.006), Yi(xs_), Zi(ys_))), cr(xs_, ys_, sg) - Vector((sg * 0.04, 0, 0))], Lp(4.0) * 0.5, FRAME, sides=12) qcyl(att, 0.058, 0.10, cr(xs_, ys_, sg), FRAME, rot=(0, math.pi / 2, 0), segs=16) # the hub qcyl(att, 0.022, 0.13, cr(xs_, ys_, sg), STEEL, rot=(0, math.pi / 2, 0), segs=12) # its pin # the bent tube: round, ends inside the two hubs tube(att, catmull([tuple(cr(x, y, sg)) for x, y in U_PATH], 3), R_TUBE, FRAME, sides=12) # the sled: the bracket plate on the cheek (up into the hull) and the two rails from its front edge into the bend plate_px = [(848.0, 470.0), (866.0, 470.0), (866.0, 412.5), (848.0, 444.5)] rings = [] for dx_, ins_ in ((-0.035, 0.008), (-0.031, 0.0), (0.031, 0.0), (0.035, 0.008)): q4 = [Vector((Yi(x), Zi(y))) for x, y in plate_px] c4 = sum(q4, Vector((0.0, 0.0))) / 4 q4 = [q + (c4 - q).normalized() * ins_ for q in q4] ring = [] for k in range(4): a, b = q4[k], q4[(k + 1) % 4] ring += [a.lerp(b, j / 3) for j in range(3)] rings.append([Vector((sg * L_CR + dx_, p.x, p.y)) for p in ring]) loft_rings(att, rings, FRAME) for yr_ in (449.0, 461.0): rrect_bar(att, cr(863.0, yr_, sg), cr(884.0, yr_, sg), 0.028, Lp(4.0) * 0.5, 0.010, FRAME) 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=12) # the lamp beside the drum (critique round 2, m5: round 1's was a 3 cm stud): a short housing on each side of the drum's lower # front edge, 25 deg below the side, its axis at r 0.53 (it sits on the face's rim and overhangs the body by 5 cm), a 10 cm # lens 4 cm proud of the face. Through the fitted concept camera the port lamp's lens projects about 8 px from the concept's # lamp at (703, 358). Mirrored to starboard. DRUM_LAMPS = [] for sg in SG: a_ = math.radians(-25.0) cL = Vector((sg * 0.53 * math.cos(a_), 0.0, DR_AX.z + 0.53 * math.sin(a_))) yF = Yd1 - 0.040 revolve_y(att, [(0.044, Yd1 + 0.045), (0.062, Yd1 + 0.035), (0.064, Yd1 + 0.020), (0.064, yF + 0.012), (0.060, yF), (0.052, yF), (0.050, yF + 0.004)], [FRAME, FRAME, FRAME, FRAME, FRAME, FRAME], cL, segs=20, cap0=True, cap1=False) revolve_y(att, [(0.050, yF + 0.004), (0.032, yF - 0.004), (0.012, yF - 0.008)], LAMPW, cL, segs=20, cap0=False, cap1=True) DRUM_LAMPS.append((Vector((cL.x, yF - 0.008, cL.z)), sg)) # ================================================================== 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 # (critique round 2, m2: the lip rings are closed solids, their inner start capped inside the lobe: through the pruned # lobe's edge the ring's inside was seen from outside) revolve_y(att, prof, mats_[:len(prof) - 1], c, segs=N_POD, cap0=True, 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=True, 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 # critique round 2 (m6): as long as TOP draws them, each ending on the lip ring in its way (the tail cone's at x 769, # the nose's at x 830.4), and RECESSED: a low rounded lip (5 mm) whose inner wall drops back to a dark floor just over # the nacelle, rounded ends (round 1: a 6 mm plate on a rim, 771-829, read as loose tiles) x0v, x1v = max(x0v, 769.7), min(x1v, 830.2) 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.012, -0.003), (0.010, 0.003), (0.006, 0.005), (0.002, 0.005), (0.0, 0.002), (-0.001, 0.0008)], FRAME, None, max_step=0.05) chart_plate(att, poly, lambda u, v: pod_pt(u, v, 0.0), RECESS, thick=0.0008, 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"