# Mako v04: everything on the hull (exec'd by build_mako.py after mk_hull.py). Quads only: lofts and foils with three-quad end # caps, revolved parts with Coons caps, cube spheres, ladder patches (mk_topo.py); parts that sink into the hull are pruned. # Numbers are reference pixels from mk_measure.py unless they are in metres. v04 after the Critique (critique/round1.md): # side bodies with the recess above them instead of v03's black pods, straight faceted fins rooted low on them, the jaw's # rear "C" and broad prongs, a keel tooth, the stern machine, thin light frames on the glass (no belts, no rails). att = MB("_attached", [PAINT, DARK, GRAPH, FIN, SENSOR, STEEL, RUBBER, YELLOW, LENS, LAMP, JAW]) VIEW = {"top": (lambda u, v: Vector((u, v, 5.0)), Vector((0, 0, -1))), "front": (lambda u, v: Vector((u, -6.0, v)), Vector((0, 1, 0))), "bot": (lambda u, v: Vector((u, v, -5.0)), Vector((0, 0, 1))), "sideP": (lambda u, v: Vector((5.0, u, v)), Vector((-1, 0, 0))), "sideS": (lambda u, v: Vector((-5.0, u, v)), Vector((1, 0, 0)))} def proj(view, u, v): """(u, v) of a view's 2D coordinates (metres) -> (point on the skin, outward normal)""" of, d = VIEW[view] hit, nrm, fi, dist = SKIN.ray_cast(of(u, v), d) if hit is None: return None, None n = _snorm(hit, fi) if n.dot(-d) < 0: n = -n return hit, n def region(outline, axis): o = 1 - axis ts = [p[axis] for p in outline] t0, t1 = min(ts), max(ts) pad = (t1 - t0) * 0.02 def lo_hi(t): xs = [] n = len(outline) for i in range(n): a, b = outline[i], outline[(i + 1) % n] if (a[axis] - t) * (b[axis] - t) <= 0 and a[axis] != b[axis]: xs.append(a[o] + (b[o] - a[o]) * (t - a[axis]) / (b[axis] - a[axis])) return (min(xs), max(xs)) if xs else (0.0, 0.0) return lo_hi, t0 + pad, t1 - pad def patch(mb, outline, view, mat, axis=0, lift=0.004, bulge=0.0, rim=None, rim_r=0.008, nt=8, nacross=3, cell=0.035): """a thin plate on the hull over a 2D outline, as a ladder of quads (no fan); optional rim tube round it""" lo_hi, t0, t1 = region(outline, axis) wmax = max(lo_hi(t0 + (t1 - t0) * i / 8)[1] - lo_hi(t0 + (t1 - t0) * i / 8)[0] for i in range(9)) nt = max(nt, int(math.ceil((t1 - t0) / cell))) nacross = max(nacross, int(math.ceil(wmax / cell))) def to3d(t, s): uv = (t, s) if axis == 0 else (s, t) p, n = proj(view, *uv) return p def nrm(p): uv2 = (p.x, p.y) if view in ("top", "bot") else ((p.x, p.z) if view == "front" else (p.y, p.z)) _, n = proj(view, *uv2) return n if n is not None else normal_at(p) ladder_patch(mb, lo_hi, t0, t1, to3d, mat, nt=nt, nacross=nacross, lift=lift, bulge=bulge, normal=nrm) if rim is not None: chain = [(t0 + (t1 - t0) * i / nt, lo_hi(t0 + (t1 - t0) * i / nt)) for i in range(nt + 1)] loop2 = [(t, lh[0]) for t, lh in chain] + [(t, lh[1]) for t, lh in reversed(chain)] pts = [] for (t, s) in loop2 + [loop2[0]]: uv = (t, s) if axis == 0 else (s, t) p, n = proj(view, *uv) pts.append(p + n * rim_r * 0.35) mb.sweep(pts, rim_r, rim, sides=6, caps=False) def band_outline(pts2, half): """a polyline (2D) thickened to a closed band of half width `half` with round ends""" n = len(pts2) L, R = [], [] for j in range(n): a = Vector(pts2[max(j - 1, 0)]); b = Vector(pts2[min(j + 1, n - 1)]) t = (b - a).normalized() nn = Vector((-t.y, t.x)) p = Vector(pts2[j]) L.append(p + nn * half); R.append(p - nn * half) def cap(c, t, sgn): nn = Vector((-t.y, t.x)) return [c + (nn * math.cos(a) + t * math.sin(a) * sgn) * half for a in [math.pi * q / 4 for q in (1, 2, 3)]] t_end = (Vector(pts2[-1]) - Vector(pts2[-2])).normalized() t_beg = (Vector(pts2[1]) - Vector(pts2[0])).normalized() out = L + cap(Vector(pts2[-1]), t_end, 1) + R[::-1] + cap(Vector(pts2[0]), -t_beg, 1) return [(p.x, p.y) for p in out] def side_patch(img_pts, mat, lift=0.004, rim=None, half=None, nt=8, nacross=2): """a patch on both flanks over a SIDE outline (image px); with half: a band along the polyline""" pts2 = [(Yi(x), Zi(v)) for x, v in img_pts] if half is not None: pts2 = band_outline(pts2, half) for vw in ("sideP", "sideS"): patch(att, pts2, vw, mat, axis=0, lift=lift, rim=rim, rim_r=0.007, nt=nt, nacross=nacross) def crisp_merge(mb, bm2, mat): """merge a faceted part: every face flagged crisp (the builder makes all its edges sharp)""" lay = bm2.faces.layers.int.get("crisp") or bm2.faces.layers.int.new("crisp") for f in bm2.faces: f[lay] = 1 mb.merge(bm2, mat) def diamond(mb, sections, mat, crisp=True): """a faceted blade through sections [(centre, chord dir, normal dir, chord, thickness)]: a diamond section (ridge at mid chord, straight facets), quads along, three quads on each end""" bm2 = bmesh.new() prof = [(-0.50, 0.0), (-0.25, 0.5), (0.0, 1.0), (0.25, 0.5), (0.50, 0.0), (0.25, -0.5), (0.0, -1.0), (-0.25, -0.5)] rings = [[bm2.verts.new(c + cd * (u * ch) + nd * (v * th * 0.5)) for u, v in prof] for (c, cd, nd, ch, th) in sections] for i in range(len(rings) - 1): for k in range(8): k2 = (k + 1) % 8 bm2.faces.new((rings[i][k], rings[i + 1][k], rings[i + 1][k2], rings[i][k2])) for r in (rings[0], rings[-1]): bm2.faces.new((r[0], r[1], r[2], r[7])); 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[:]) if crisp: crisp_merge(mb, bm2, mat) else: mb.merge(bm2, mat) def ysec(le_tab, te_tab, z): """the chord's ends at height z (side view) from a leading edge and a trailing edge polyline [(img x, img y)]""" def at(tab): pts = sorted(tab, key=lambda p: -p[1]) for (xa, va), (xb, vb) in zip(pts, pts[1:]): za, zb = Zi(va), Zi(vb) if min(za, zb) - 1e-9 <= z <= max(za, zb) + 1e-9 and za != zb: return Yi(xa + (xb - xa) * (z - za) / (zb - za)) p = pts[0] if abs(z - Zi(pts[0][1])) < abs(z - Zi(pts[-1][1])) else pts[-1] return Yi(p[0]) return at(le_tab), at(te_tab) def vfin(le_tab, te_tab, z0, z1, th0, th1, mat, n=12, root_extra=0.12, down=False, tip_min=0.035): """a fin in the centre plane: chords at heights z0 (root, carried into the hull) .. z1 (tip)""" secs = [] for j in range(n + 1): t = j / n z = z0 + (z1 - z0) * t a, b = ysec(le_tab, te_tab, z) if j == 0: zr = z0 + (root_extra if down else -root_extra) secs.append((Vector((0, (a + b) / 2, zr)), Vector((0, 1, 0)), Vector((1, 0, 0)), abs(b - a), th0)) ch = max(abs(b - a), tip_min) if j == n else abs(b - a) secs.append((Vector((0, (a + b) / 2, z)), Vector((0, 1, 0)), Vector((1, 0, 0)), ch, th0 + (th1 - th0) * t)) foil(att, secs, mat) # ------------------------------------------------------------------ side bodies (v04): grey bodies under the recess, the fins' roots # Rounded-rectangle sections (superellipse) from deep inside the body out to TOP's outer edge, between SIDE's cheek panel top # (rising aft) and REAR's lobes' bottom; the top (the recess's floor) dark, the rest light grey; a rounded front (FRONT's grey # fronts under the recess openings) and a rounded rear end with two panel rings (REAR's lobes). POD_GEOM = {} POUT, PTOP, PBOT = table_f(POD_OUT), table_f(POD_TOP), table_f(POD_BOT) P_IN = 0.36 def side_body(side): NP = 24 xs = [306, 314, 325, 340, 355, 370, 385, 400, 415, 430, 445, 460, 475, 490, 504, 516, 524] def ring(x, k=1.0, n=3.0): y = Yi(x) o, zt, zb = POUT(y), PTOP(y), PBOT(y) cx, hw = 0.5 * (o + P_IN), 0.5 * (o - P_IN) cz, hh = 0.5 * (zt + zb), 0.5 * (zt - zb) out = [] for q in range(NP): a = 2 * math.pi * (q + 0.5) / NP c, s_ = math.cos(a), math.sin(a) out.append(Vector((side * (cx + hw * k * math.copysign(abs(c) ** (2 / n), c)), y, cz + hh * k * math.copysign(abs(s_) ** (2 / n), s_)))) return out, (side * cx, y, cz) rings = [] front = [ring(300.0, 0.78), ring(302.5, 0.93)] main = [ring(x) for x in xs] rear = [ring(527.0, 0.82, 2.6), ring(530.0, 0.55, 2.2)] allr = [r for r, c in front + main + rear] def mat_of(q): # dark on the top (the recess floor; FRONT sees it dark through the openings) a = 2 * math.pi * (q + 1.0) / NP return math.sin(a) > 0.62 # (SIDE first: the cheek panel light grey up to its top edge) bm2 = bmesh.new() V2 = [[bm2.verts.new(p) for p in r] for r in allr] mi_f, mi_d = att.mi(FIN), att.mi(DARK) # v04 round 2: the fins' light grey (SIDE's cheek panel) for i in range(len(V2) - 1): for q in range(NP): f = bm2.faces.new((V2[i][q], V2[i + 1][q], V2[i + 1][(q + 1) % NP], V2[i][(q + 1) % NP])) f.material_index = mi_d if (mat_of(q) or i >= 14) else mi_f # dark from x 475 on (SIDE's capsule front at 478) coons_cap(bm2, V2[0], mi_f) # the rounded front: grey (FRONT's grey fronts) coons_cap(bm2, V2[-1][::-1], mi_d) # the rear end: dark (REAR's dark lobes) bm2.normal_update() bmesh.ops.recalc_face_normals(bm2, faces=bm2.faces[:]) att.merge(bm2, None) for r_pts, c in rear: # REAR's lobes: two thin panel rings on the rounded rear end c = Vector(c) pts = [p + (p - c).normalized() * 0.004 for p in r_pts] att.sweep(pts + [pts[0]], 0.007, FIN, sides=6, caps=False) # REAR: lighter rings on the dark lobe POD_GEOM[side] = dict(front=Vector(front[0][1]), rear=Vector(rear[-1][1])) for side in (1, -1): side_body(side) # ------------------------------------------------------------------ big fins (v04): straight faceted spikes rooted low on the side bodies # Each is a flat blade in a plane along the hull's length (so FRONT and REAR see a straight line), through FRONT's root and the # tip; its leading edge straight from the root to the tip, its trailing edge dropping to SIDE's kink, then to the tip; a # diamond section 17 cm thick at the root, 3 cm at the tip. FIN_GEOM = {} def fin_v4(side): rl, rz = FIN4_ROOT tx, tl, tz = FIN4_TIP d2 = Vector((tl - rl, tz - rz)); vt = d2.length; d2.normalize() def P3(y, v): return Vector((side * (rl + d2.x * v), y, rz + d2.y * v)) nrm = Vector((side * -d2.y, 0.0, d2.x)).normalized() # up and out yLE0, yTE0, yT = Yi(FIN4_LE0), Yi(FIN4_TE0), Yi(tx) yK, vK = Yi(FIN4_KINK[0]), (rz - FIN4_KINK[1]) / -d2.y def le(v): return yLE0 + (yT - yLE0) * v / vt def te(v): if v <= vK: return yTE0 + (yK - yTE0) * v / vK return yK + (yT - yK) * (v - vK) / (vt - vK) secs = [] N = 20 for j in range(N + 1): v = -0.14 + (0.985 * vt + 0.14) * j / N vv = max(0.0, v) a, b = le(vv), te(vv) ch = max(0.05, b - a) th = 0.17 + (0.03 - 0.17) * (vv / vt) secs.append((P3(0.5 * (a + b), v), Vector((0, 1, 0)), nrm, ch, th)) diamond(att, secs, FIN) FIN_GEOM[side] = dict(root=P3(yLE0, 0.0), tip=P3(yT, vt), n=nrm) for side in (1, -1): fin_v4(side) # ------------------------------------------------------------------ dorsal fin, tail fin (orange) and the ventral stern fin (grey), thicker (v04) vfin(DORSAL_LE, DORSAL_TE, Zi(670.0), Zi(549.0), 0.18, 0.022, PAINT, n=12) vfin(TAIL4_LE, TAIL4_TE, Zi(762.0), Zi(657.0), 0.12, 0.05, PAINT, n=9, tip_min=0.12) vfin([VENT4[0], VENT4[1]], [VENT4[1], VENT4[2]], Zi(848.0), Zi(946.0), 0.08, 0.016, FIN, n=9, down=True) # ------------------------------------------------------------------ the jaw: two curved grooves, the rear "C" round its dark opening GROOVES4 = [[(209, 862), (204, 880), (212, 896), (230, 910), (248, 918)], [(263, 853), (256, 872), (259, 890), (270, 903), (280, 909)]] # (kept clear of the jaw's bottom corner) for g in GROOVES4: side_patch(g, SENSOR, lift=0.003, rim=GRAPH, half=GROOVE_HALF, nt=10) side_patch(C_OPEN, SENSOR, lift=0.005, rim=GRAPH, nt=10, nacross=4) # the C's dark opening, rimmed (its arms: mk_hull) def rect_loft(mb, secs, mat): """a bar through chamfered-rectangle sections [(centre, u dir, v dir, half u, half v, chamfer)]; three quads each end""" bm2 = bmesh.new() rings = [] for (c, ud, vd, hu, hv, ch) in secs: pts = [(-hu + ch, -hv), (hu - ch, -hv), (hu, -hv + ch), (hu, hv - ch), (hu - ch, hv), (-hu + ch, hv), (-hu, hv - ch), (-hu, -hv + ch)] rings.append([bm2.verts.new(c + ud * u + vd * 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) # the prongs: two broad flat prongs a side, continuing the jaw's sloped front edge down and aft (SIDE); FRONT: 12 cm wide def prong(side, root, tip, w_side=0.11, w_lat=0.12): r = Vector((side * PRONG_LAT, Yi(root[0]), Zi(root[1]))) t = Vector((side * (PRONG_LAT - 0.01), Yi(tip[0]), Zi(tip[1]))) ax = (t - r).normalized() r_in = r - ax * 0.10 # carried up into the jaw cd = Vector((0, ax.z, -ax.y)).normalized() # across the prong in the side plane secs = [] for j in range(8): u = j / 7 c = r_in.lerp(t, u) k = 1.0 - 0.25 * smoothstep(0.4, 1.0, u) # broad flat bars, not spikes if j == 7: # a chisel tip secs.append((c, cd, Vector((1, 0, 0)), 0.5 * w_side * 0.45, 0.5 * w_lat * 0.5, 0.006)) else: secs.append((c, cd, Vector((1, 0, 0)), 0.5 * w_side * k, 0.5 * w_lat * k, 0.012)) rect_loft(att, secs, JAW) for side in (1, -1): for p_root, p_tip, p_w in PRONGS4: # (not "root": that is the Tender's root empty) prong(side, p_root, p_tip, w_side=p_w) # the keel tooth: a dark blade under the jaw's centre between the prongs (FRONT 0.19 m wide, to z -1.24) kw, kz = KEEL4 qsecs = [] for j in range(7): t = j / 6 z = -0.74 + (kz + 0.74) * t yb, ye = Yi(270.0) + 0.05 * t, Yi(298.0) - 0.08 * t # behind the prongs in SIDE qsecs.append((Vector((0, 0.5 * (yb + ye), z)), Vector((0, 1, 0)), Vector((1, 0, 0)), max(0.05, ye - yb), kw * (1 - 0.75 * t))) diamond(att, qsecs, GRAPH) # FRONT: vertical slots on the jaw's sloped front face (two a side), projected from the front for lat, za, zb in JAW_SLOTS: for sg in (1, -1): outline = [(sg * lat - 0.018, za), (sg * lat + 0.018, za), (sg * lat + 0.018, zb), (sg * lat - 0.018, zb)] patch(att, outline, "front", SENSOR, axis=1, lift=0.003, rim=GRAPH, rim_r=0.006, nt=6, nacross=1) # ------------------------------------------------------------------ the recess: a small port on its wall, the black slot along the shell's curl py_, pz_ = Yi(PORT4[0]), Zi(PORT4[1]) for side in (1, -1): p = on_side(side, py_, pz_) n = normal_at(p) rot = Vector((0, 0, 1)).rotation_difference(n).to_euler() revolve(att, [(PORT4[2] + 0.010, -0.02), (PORT4[2] + 0.010, 0.008), (PORT4[2], 0.012), (PORT4[2] - 0.004, 0.004)], [GRAPH, GRAPH, GRAPH], segs=16, xf=Matrix.Translation(p) @ rot.to_matrix().to_4x4(), cap0=False, cap1=True, cap1_mat=SENSOR) side_patch([(420, 760), (424, 742), (432, 726), (444, 710), (450, 716), (447, 732), (440, 748), (430, 762)], SENSOR, lift=0.004, rim=GRAPH, nt=8, nacross=2) # v04 round 2: SIDE's black wedge along the shell's curl (x 420-450) # ------------------------------------------------------------------ under the cockpit: a grey plate on the lower body (SIDE 340-450, 900-950), two skids side_patch([(340, 898), (415, 898), (415, 930), (340, 938)], PODG, lift=0.010, rim=GRAPH, nt=8, nacross=3) # (v04 round 2: no skids) # ------------------------------------------------------------------ belly pack (SIDE: the dark box under the rear body), ribbed, on two skids bx0, bx1, bhw, bbot = BELLY byc, blen = Yi((bx0 + bx1) / 2), (bx1 - bx0) * S bz0 = Zi(bbot) bz1 = hull_z_at(0.0, byc, top=False) + 0.10 qbox(att, (2 * bhw * S, blen, bz1 - bz0), (0, byc, (bz0 + bz1) / 2), DARK, bevel=0.04) for t in (0.18, 0.5, 0.82): qbox(att, (2 * bhw * S + 0.03, 0.035, 0.08), (0, Yi(bx0) + blen * t, bz0 + 0.12), GRAPH, bevel=0.01) # (v04 round 2: the belly pack's skids are gone; the sheet draws none) # ------------------------------------------------------------------ stern machine (v04): REAR's square hub with a round face, cross arms, orange rings # The hull's last section steps into a recessed panel (mk_hull.py); on it the hub (0.42 m square, its aft face carrying the # thruster's duct, the rotor in mk_tools.py), cross arms with knobs, orange rings on its sides, a mast above (SIDE x 903), a # block below it, two pipes from the hull. HZ = 0.06 HY0, HY1 = Yi(HUB4["x0"]) - 0.04, Yi(912.0) HH = HUB4["half"] qbox(att, (2 * HH, HY1 - HY0, 2 * HH), (0, 0.5 * (HY0 + HY1), HZ), GRAPH, bevel=0.03) TH_C = Vector((0, HY1, HZ)) # thruster axis on the hub's aft face def duct_ring(r, dy, n=24): return [TH_C + Vector((r * math.cos(2 * math.pi * q / n), dy, r * math.sin(2 * math.pi * q / n))) for q in range(n)] grid_rings(att, [duct_ring(0.150, -0.01), duct_ring(0.150, 0.11), duct_ring(0.138, 0.125), duct_ring(0.122, 0.11), duct_ring(0.122, 0.005)], [STEEL, STEEL, STEEL, DARK], closed_u=True, expect=(0, 6, (0, 0, 1))) # on the hub's aft face bmd = bmesh.new() # the duct's dark bottom behind the rotor coons_cap(bmd, [bmd.verts.new(p) for p in duct_ring(0.122, 0.005)][::-1], 0) bmd.normal_update() if sum(f.normal.y for f in bmd.faces) < 0: bmesh.ops.reverse_faces(bmd, faces=bmd.faces[:]) att.merge(bmd, DARK) YA = HY1 - 0.05 # the cross arms, just ahead of the hub's aft face qbox(att, (0.07, 0.06, 0.62), (0, YA, HZ + 0.11), DARK, bevel=0.012) # the cross arm, above the hub's bottom only (round 3) for p in ((0, 0, HZ + 0.42),): qsphere(att, Vector((p[0], YA, p[2])), (0.04, 0.04, 0.04), STEEL, cuts=2) px_, py0_, py1_ = STERN_PLATE # SIDE: a short flat plate over the hub's aft end qbox(att, (0.12, 0.045, Zi(py0_) - Zi(py1_)), (0, Yi(px_), 0.5 * (Zi(py0_) + Zi(py1_))), GRAPH, bevel=0.008) lx_, lz_, lr_ = STERN_LENS # SIDE: an orange lens on each flank near the stern for side in (1, -1): # (round 3: a housing angled aft and outward, its lens orange) p = on_side(side, Yi(lx_), Zi(lz_)) n = (normal_at(p) + Vector((0, 0.9, 0))).normalized() rot = Vector((0, 0, 1)).rotation_difference(n).to_euler() revolve(att, [(lr_ + 0.016, -0.05), (lr_ + 0.016, 0.03), (lr_ + 0.004, 0.045), (lr_ - 0.008, 0.038)], [GRAPH, GRAPH, GRAPH], segs=16, xf=Matrix.Translation(p) @ rot.to_matrix().to_4x4(), cap0=False, cap1=True, cap1_mat=PAINT) qbox(att, (0.26, Yi(935.0) - Yi(884.0), 0.17), (0, 0.5 * (Yi(884.0) + Yi(935.0)), Zi(852.0) + 0.085), DARK, bevel=0.02) # the block, to y 852 for side in (1, -1): # pipes from the hull's stern face to the hub pts = [Vector((side * 0.17, Y_TAIL - 0.06, HZ + 0.25)), Vector((side * 0.20, Y_TAIL + 0.02, HZ + 0.25)), Vector((side * 0.23, Y_TAIL + 0.04, HZ + 0.21)), Vector((side * (HH + 0.015), Y_TAIL + 0.06, HZ + 0.18))] # (clear of the swinging tool arms) att.sweep(pts, 0.018, STEEL, sides=8, caps=True) qcyl(att, 0.035, 0.02, Vector((0, Yi(935.0) + 0.005, Zi(852.0) + 0.085)), LAMP, rot=(math.pi / 2, 0, 0), segs=12) # ------------------------------------------------------------------ canopy frames (v04): thin light ones only, as the sheet draws them # The hoop (TOP: an arc across the canopy, a circle in plan), the seam along the nose's top, FRONT's two meridians. Bars sit in # the glass (sunk 22 mm) and stand 6 mm proud; 1.4 cm wide. v03's belts and orange rails are gone (no view of the sheet has them). frames = MB("SM_Mako_Frames", [FRAMEL, GRAPH]) def bar(pts3, half_w, hb, ht, mat): nP = len(pts3) rows = [] for (w, which) in ((-half_w, 0), (-half_w * 0.8, 1), (half_w * 0.8, 1), (half_w, 0)): row = [] for j, p in enumerate(pts3): t = (pts3[min(j + 1, nP - 1)] - pts3[max(j - 1, 0)]).normalized() nrm = normal_at(p) bn = nrm.cross(t).normalized() row.append(p + bn * w + nrm * (ht if which else hb)) rows.append(row) grid_rings(frames, [list(r) for r in zip(*rows)], mat, closed_u=False, expect=(nP // 2, 1, tuple(normal_at(pts3[nP // 2])))) # (v04 round 2: no hoop, nose seam or meridians on the glass: they read as wire handles; the pale upper glass draws the # sheet's line across the canopy instead) # the rear glass bay's two thin raked posts (none since the user took the window out: BAY_ON) for sg in ((1, -1) if BAY_ON else ()): for (xa, va), (xb, vb) in BAY_POSTS: pts = [on_side(sg, Yi(xa + (xb - xa) * t), Zi(va + (vb - va) * t)) for t in [j / 10 for j in range(11)]] bar(pts, 0.010, -0.024, 0.005, FRAMEL) # ------------------------------------------------------------------ the brow (v04, SIDE first): the thin orange visor over the glass's crest # SIDE (the user's clean view) draws it from a point at x 178 over the glass's top to the collar, its top rising from image y 705 # to 677, its lower edge on the glass's crest (a dark line under it). A flat plate, cambered a little, its underside 8 mm over the # crest in the middle and drooping only 2.5 cm to its edges, so from the side the glass shows under it as drawn; it widens to # 0.48 m either side at the collar and carries the fore holder clips. Its top orange, its underside dark. BTOPf = table_f(BROW_TOP, Yi, Zi) BBOTf = table_f(BROW_BOT, Yi, Zi) BWf = pchip(sorted((Yi(x), w) for x, w in BROW_W)) def brow_top(lat, y): w = max(1e-3, BWf(y)) return BTOPf(y) - 0.045 * min(1.0, (lat / w) ** 2) def brow_ring(x): y = Yi(x) w = max(0.012, BWf(y)) a, zc, b = dome(y) zt, zb = BTOPf(y), min(zc + b + 0.008, BBOTf(y)) # (round 2: behind x 250 it curves down into the collar's front) pts, mats = [], [] for k in range(1, 12): # the top, port to starboard l = w * (1 - 2 * k / 12) pts.append(Vector((l, y, zt - 0.045 * (l / w) ** 2))) zs = 0.5 * (zt - 0.045 + zb - 0.03) pts.append(Vector((-w - 0.006, y, zs))) # the starboard edge, rounded for k in range(11, 0, -1): # the underside, starboard to port l = w * (1 - 2 * k / 12) zt_l = zt - 0.045 * (l / w) ** 2 pts.append(Vector((l, y, min(zb - 0.03 * (l / w) ** 2, zt_l - 0.012)))) pts.append(Vector((w + 0.006, y, zs))) # the port edge return pts xs_b = [179.0, 181.0, 184.0, 188.0, 194.0, 202.0, 212.0, 224.0, 236.0, 248.0, 260.0, 272.0, 283.0, 292.0, 302.0] # into the collar rings_b = [brow_ring(x) for x in xs_b] bmb_ = bmesh.new() VB = [[bmb_.verts.new(p) for p in r] for r in rings_b] mi_p, mi_g = att.mi(PAINT), att.mi(GRAPH) for i in range(len(VB) - 1): for q in range(24): f = bmb_.faces.new((VB[i][q], VB[i + 1][q], VB[i + 1][(q + 1) % 24], VB[i][(q + 1) % 24])) f.material_index = mi_p # (round 3: orange all round; its dark underside read as a band) coons_cap(bmb_, VB[0], mi_p) coons_cap(bmb_, VB[-1][::-1], mi_p) bmb_.normal_update() bmesh.ops.recalc_face_normals(bmb_, faces=bmb_.faces[:]) att.merge(bmb_, None) # ------------------------------------------------------------------ the dorsal spine (TOP: the strip from the bezel to the dorsal's back), raised rx0, rx1, rhw, rht = DORSAL_RIDGE rx1 = 552.0 # v04: it stops ahead of the hatch (the hatch is the next plate) SP_W = table_f(SPINE, Yi, lambda v: v * S) secs = [] for t in [j / 24 for j in range(25)]: x_ = rx0 + (rx1 - rx0) * t y = Yi(x_) w = max(0.012, SP_W(y) + 0.02) z0 = hull_z_at(0.0, y) - 0.03 h = rht * smoothstep(0.0, 0.06, t) * (1 - smoothstep(0.85, 1.0, t)) z1 = hull_z_at(0.0, y) + max(0.004, h) secs.append((y, w, z0, z1, min(0.015, w * 0.4))) loft(att, secs, PAINT, xf=Matrix.Rotation(math.pi / 2, 4, 'Z')) # ------------------------------------------------------------------ the rear body (v04 round 2): SIDE's light diagonal strip, the capsule's front side_patch([(482, 828), (640, 764)], FIN, lift=0.003, half=0.011, nt=14) # ------------------------------------------------------------------ the rear bay's machinery behind its glass (SIDE x 800-880) for side in (1, -1): for (xa, xb, za, zb_, dep) in ((806.0, 832.0, 790.0, 818.0, 0.05), (836.0, 862.0, 784.0, 808.0, 0.04), (812.0, 870.0, 818.0, 828.0, 0.03)): yc_, zc_ = Yi(0.5 * (xa + xb)), 0.5 * (Zi(za) + Zi(zb_)) p = on_side(side, yc_, zc_) qbox(att, (dep * 2, (xb - xa) * S, abs(Zi(za) - Zi(zb_))), (p.x, yc_, zc_), GRAPH, bevel=0.006) pts = [on_side(side, Yi(x_), Zi(z_), 0.02) for x_, z_ in ((804.0, 782.0), (830.0, 780.0), (850.0, 779.0), (874.0, 782.0))] att.sweep(pts, 0.012, STEEL, sides=6, caps=True) # ------------------------------------------------------------------ lamps (v04): small, dark-lensed, low on the jaw's sloped front (no "eyes") LAMPS = [] for side in (1, -1): p, n = proj("bot", side * 0.30, Yi(196.0)) rot = Vector((0, 0, 1)).rotation_difference(n).to_euler() revolve(att, [(0.045, -0.025), (0.045, 0.008), (0.040, 0.013), (0.033, 0.007)], [GRAPH, GRAPH, GRAPH], segs=16, xf=Matrix.Translation(p) @ rot.to_matrix().to_4x4(), cap0=False, cap1=True, cap1_mat=SENSOR) LAMPS.append((p + n * 0.03, (n + Vector((0, -1.6, 0.3))).normalized())) REPORT["stage"] = "fittings"