# Spearfish: everything on the hull (exec'd by build_spearfish.py after the hull, glass, frames and cabin). # Numbers are blueprint pixels from sf_measure.py unless they are in metres. from mathutils.bvhtree import BVHTree att = MB("_attached", [PAINT, GRAPH, SENSOR, CHROME, STEEL, RUBBER, YELLOW, LENS, LAMP, SEAM]) # ------------------------------------------------------------------ stern end face: closes the hull's last ring bms = bmesh.new() end = rings[-1] yE = YS[-1] zc = ZMf(yE) cen = Vector((0, yE, zc)) def _ring(scale, dy): return [bms.verts.new(cen + (p - cen) * scale + Vector((0, dy, 0))) for p in end] R_end = [bms.verts.new(p) for p in end] R_a = _ring(0.94, 0.0) R_b = _ring(0.90, -0.045) R_c = _ring(0.55, -0.045) R_d = _ring(0.20, -0.045) cv_s = bms.verts.new(cen + Vector((0, -0.045, 0))) MI_P, MI_G = hull.mi(PAINT), hull.mi(GRAPH) for A, B, mt in ((R_end, R_a, MI_P), (R_a, R_b, MI_P), (R_b, R_c, MI_G), (R_c, R_d, MI_G)): for k in range(M): f = bms.faces.new((A[k], A[(k + 1) % M], B[(k + 1) % M], B[k])) f.material_index = mt for k in range(M): f = bms.faces.new((R_d[k], R_d[(k + 1) % M], cv_s)); f.material_index = MI_G bms.normal_update() for f in bms.faces: if f.normal.y < 0 and abs(f.normal.y) > 0.5: f.normal_flip() bms.normal_update() hull.merge(bms, None) hull.bm.normal_update() HBVH = BVHTree.FromBMesh(hull.bm) def proj(view, u, v): """(u, v) of a view's 2D outline (metres) -> (point on the hull, outward normal)""" if view == "top": o, d = Vector((u, v, 5.0)), Vector((0, 0, -1)) elif view == "bot": o, d = Vector((u, v, -5.0)), Vector((0, 0, 1)) elif view == "sideP": o, d = Vector((5.0, u, v)), Vector((-1, 0, 0)) elif view == "sideS": o, d = Vector((-5.0, u, v)), Vector((1, 0, 0)) elif view == "rear": o, d = Vector((u, 12.0, v)), Vector((0, -1, 0)) hit, nrm, fi, dist = HBVH.ray_cast(o, d) if hit is None: return None, None if hit.y > Y_TAIL - 0.06 or abs(nrm.dot(normal_at(hit))) < 0.5: n = nrm.normalized() else: n = normal_at(hit) if n.dot(-d) < 0: n = -n return hit, n def patch(mb, outline, view, mat, lift=0.002, bulge=0.0, rim=None, rim_r=0.008, step=0.02, scales=(1.0, 0.75, 0.5, 0.25)): """a thin plate on the hull over a closed 2D outline (view coordinates, metres): lifted, optionally domed, optionally with a raised rim (a tube along its edge): reads as a lens, a vent or a recess""" pts = resample(outline, step) cu, cvv = centroid(outline) rows = [] for s in scales: row = [] for (u, v) in pts: uu, vv = cu + (u - cu) * s, cvv + (v - cvv) * s p, n = proj(view, uu, vv) if p is None: return False row.append(p + n * (lift + bulge * (1 - s * s))) rows.append(row) pc, nc = proj(view, cu, cvv) if pc is None: return False grid_rings(mb, rows, mat, cap1=pc + nc * (lift + bulge), expect=(0, 0, tuple(nc))) if rim is not None: loop = [] for (u, v) in pts + [pts[0]]: p, n = proj(view, u, v) loop.append(p + n * rim_r * 0.35) mb.sweep(loop, rim_r, rim, sides=6, caps=False) return True def capsule(u0, u1, v, r, n=6): """a stadium outline from (u0, v) to (u1, v), radius r""" out = [] for j in range(n + 1): a = -math.pi / 2 + math.pi * j / n out.append((u1 + r * math.cos(a), v + r * math.sin(a))) for j in range(n + 1): a = math.pi / 2 + math.pi * j / n out.append((u0 + r * math.cos(a), v + r * math.sin(a))) return out def mirror_lat(outline): return [(-u, v) for u, v in outline] # ------------------------------------------------------------------ fins (TOP planforms), low on the flank def scan(poly, L): xs = [] n = len(poly) for i in range(n): (x1, l1), (x2, l2) = poly[i], poly[(i + 1) % n] if l1 == l2: continue if (l1 - L) * (l2 - L) <= 0: xs.append(x1 + (x2 - x1) * (L - l1) / (l2 - l1)) return min(xs), max(xs) def fin(poly, Ls, z_img, anh, th0, th1, side, droop_from=None, droop_deg=0.0, mat=PAINT): lmin = min(l for _, l in poly) + 1e-6 secs = [] lat0 = Ls[0] * S for j, L in enumerate(Ls): xa, xf = scan(poly, max(L, lmin)) t = j / (len(Ls) - 1) lat = L * S z = Zi(z_img) - math.tan(math.radians(anh)) * (lat - lat0) bank = math.radians(anh) if droop_from is not None: dd = smoothstep(droop_from, 1.0, t) z -= (lat - Ls[0] * S) * 0.0 + 0.10 * dd ** 1.5 bank += math.radians(droop_deg) * dd nd = Vector((side * math.sin(bank), 0, math.cos(bank))) secs.append((Vector((side * lat, Yi((xa + xf) / 2), z)), Vector((0, 1, 0)), nd, (xf - xa) * S, th0 + (th1 - th0) * t)) foil(att, secs, mat) return secs BIG_LS = [56, 62, 68, 73, 78, 83, 88, 93, 98, 103, 107, 110.5, 113.5, 116.5, 118.8, 120.6, 121.8, 122.6] CAN_LS = [52, 58, 63, 67, 71, 74.5, 77.5, 80, 81.8, 83.0] TIP_POD = {} for side in (1, -1): fs = fin(BIGFIN, BIG_LS, BIGFIN_Y, BIGFIN_ANHEDRAL, 0.13, 0.035, side) tip = fs[-4][0] TIP_POD[side] = tip fin(CANARD, CAN_LS, CANARD_Y, 0.0, 0.075, 0.028, side, droop_from=0.55, droop_deg=38.0) # lamp pods on the big fins' outer fore corners (FRONT: a round disc at the end of each fin) POD_R, POD_X0, POD_X1, POD_L = 0.070, 771.0, 727.0, 117.0 # inside the TOP planform (L 116.5-120.5 there) for side in (1, -1): zt = Zi(BIGFIN_Y) - math.tan(math.radians(BIGFIN_ANHEDRAL)) * (POD_L - BIG_LS[0]) * S front = Vector((side * POD_L * S, Yi(POD_X0), zt)) ln = (POD_X0 - POD_X1) * S prof = [(0.0, 0.0), (0.045, 0.0), (0.054, 0.004), (0.066, 0.012), (POD_R, 0.04), (POD_R, ln - 0.20), (0.050, ln - 0.03), (0.0, ln)] lathe(att, prof, [LENS, CHROME, CHROME, PAINT, PAINT, PAINT, PAINT], segs=20, xf=Matrix.Translation(front) @ YPOS) att.cyl(0.040, 0.040, 0.004, front + Vector((0, 0.012, 0)), LAMP, rot=(math.pi / 2, 0, 0), segs=20) # fin root fairing along the flank (SIDE: the swept blade from aft-low to fore-high) def surf_loft(mb, P, ws, hs, mat): bm = bmesh.new() R = [] n = len(P) for j in range(n): t = (P[min(j + 1, n - 1)] - P[max(j - 1, 0)]).normalized() N = normal_at(P[j]); N = (N - N.dot(t) * t).normalized() b = N.cross(t) w, h = ws[j], hs[j] sec_pts = [(-w, -0.025), (-0.75 * w, 0.55 * h), (-0.3 * w, h), (0.3 * w, h), (0.75 * w, 0.55 * h), (w, -0.025), (0.4 * w, -0.035), (-0.4 * w, -0.035)] R.append([bm.verts.new(P[j] + b * u + N * v) for u, v in sec_pts]) for j in range(n - 1): for k in range(8): bm.faces.new((R[j][k], R[j + 1][k], R[j + 1][(k + 1) % 8], R[j][(k + 1) % 8])) for r in (R[0], R[-1]): c = bm.verts.new(sum((v.co for v in r), Vector()) / 8) for k in range(8): bm.faces.new((r[k], r[(k + 1) % 8], c)) bm.normal_update() bmesh.ops.recalc_face_normals(bm, faces=bm.faces[:]) mb.merge(bm, mat) for sg in (1, -1): pts2 = resample([(Yi(x), Zi(v)) for x, v in STRAKE], 0.05, closed=False) P = [on_side(sg, y, z) for y, z in pts2] n = len(P) # SIDE: a thin blade aft, about 0.2 m across where it meets the ribbed actuator and the canopy's aft corner ws = [0.030 + 0.075 * smoothstep(0.0, 0.62, j / (n - 1)) - 0.06 * smoothstep(0.88, 1.0, j / (n - 1)) for j in range(n)] hs = [0.020 + 0.050 * smoothstep(0.0, 0.5, j / (n - 1)) - 0.04 * smoothstep(0.85, 1.0, j / (n - 1)) for j in range(n)] surf_loft(att, P, ws, hs, PAINT) # ribbed actuator under the fairing's fore part (SIDE: the ribbed "gills"; FRONT: the ribbed root of each fin) for sg in (1, -1): A = on_side(sg, Yi(RIBBED[0][0]), Zi(RIBBED[0][1])) B = on_side(sg, Yi(RIBBED[1][0]), Zi(RIBBED[1][1])) Nm = normal_at((A + B) / 2) A = A + normal_at(A) * 0.045; B = B + normal_at(B) * 0.045 d = B - A Ln = d.length zax = d.normalized() xax = (Nm - Nm.dot(zax) * zax).normalized() yax = zax.cross(xax) MX = Matrix((xax, yax, zax)).transposed().to_4x4() MX.translation = A prof = [(0.0, 0.0), (0.035, 0.0)] ribs = 6 for j in range(ribs): a0 = 0.06 + j * 0.13 r = 0.070 + 0.070 * math.sin(math.pi * (j + 0.7) / (ribs + 0.6)) prof += [(r * 0.80, a0 * Ln), (r, (a0 + 0.08) * Ln), (r * 0.86, (a0 + 0.11) * Ln)] prof += [(0.05, 0.90 * Ln), (0.025, 0.98 * Ln), (0.0, Ln)] lathe(att, prof, [STEEL] * (len(prof) - 1), segs=18, xf=MX) # dark recess round it (reads as the gill slot) out2 = [] for j in range(13): tt = j / 12 p = A.lerp(B, tt) r = 0.05 + 0.13 * math.sin(math.pi * tt) ** 0.8 out2.append((p.y, p.z + r)) for j in range(13): tt = 1 - j / 12 p = A.lerp(B, tt) r = 0.05 + 0.13 * math.sin(math.pi * tt) ** 0.8 out2.append((p.y, p.z - r)) patch(att, out2, "sideP" if sg > 0 else "sideS", GRAPH, lift=0.002) # small dark root blocks (TOP: at the big fin's fore root, its aft root and the canard's fore root) for side in (1, -1): for (xa, xb, la, lb, zi) in ((808, 822, 72, 80, BIGFIN_Y), (697, 706, 66, 70, BIGFIN_Y), (882, 890, 60, 66, CANARD_Y)): c = Vector((side * (la + lb) / 2 * S, Yi((xa + xb) / 2), Zi(zi) + 0.01)) att.box(((lb - la) * S + 0.02, (xb - xa) * S, 0.07), c, GRAPH, bevel=0.012) # ------------------------------------------------------------------ dorsal rails (TOP: two long capsules; SIDE: raised 0.12 m) rx0, rx1, rlat, rhw, rtop = RAILS for side in (1, -1): lat = side * rlat * S secs = [] ztop = Zi(rtop) n = 40 for j in range(n + 1): x = rx0 + (rx1 - rx0) * j / n y = Yi(x) zh = hull_z_at(lat, y) ramp = smoothstep(rx0, rx0 + 16, x) * (1 - smoothstep(rx1 - 14, rx1, x)) z1 = zh + 0.012 + (ztop - zh - 0.012) * ramp ** 0.7 w = rhw * S * (0.55 + 0.45 * ramp ** 0.5) secs.append((y, w, zh - 0.06, z1, min(w * 0.55, 0.05))) secs.sort(key=lambda s: s[0]) loft(att, secs, PAINT, xf=Matrix.Translation((lat, 0, 0)) @ Matrix.Rotation(math.pi / 2, 4, 'Z')) # the groove along the top gs = [] for j in range(n + 1): x = rx0 + 8 + (rx1 - rx0 - 16) * j / n y = Yi(x) zh = hull_z_at(lat, y) ramp = smoothstep(rx0, rx0 + 16, x) * (1 - smoothstep(rx1 - 14, rx1, x)) z1 = zh + 0.012 + (ztop - zh - 0.012) * ramp ** 0.7 gs.append((y, 0.025 * (0.3 + 0.7 * ramp), z1 - 0.012, z1 + 0.002, 0.008)) gs.sort(key=lambda s: s[0]) loft(att, gs, GRAPH, xf=Matrix.Translation((lat, 0, 0)) @ Matrix.Rotation(math.pi / 2, 4, 'Z')) # ------------------------------------------------------------------ dorsal scoop (TOP: dark horseshoe; SIDE: a hump; FRONT: a dark arch) hx0, hx1, hhw, htop = SCOOP_HUMP H_SC = 0.10 rows = [] nu = 14 xs = [hx0 + (hx1 - hx0) * j / 12 for j in range(13)] for x in xs: y = Yi(x) row = [] for q in range(nu + 1): u = -1 + 2 * q / nu lat = u * hhw * S * (0.55 + 0.45 * smoothstep(hx0, hx1, x) ** 0.5) h = H_SC * smoothstep(hx0, hx1 - 4, x) ** 0.8 * max(0.0, 1 - abs(u) ** 3) ** 0.5 row.append(Vector((lat, y, hull_z_at(lat, y) + h - 0.01 * (abs(u) > 0.999)))) rows.append(row) # the mouth: the hump's fore face, down to the hull last = rows[-1] mouth = [Vector((p.x, p.y, hull_z_at(p.x, p.y) - 0.01)) for p in last] grid_rings(att, [list(r) for r in zip(*rows)], PAINT, closed_u=False, expect=(nu // 2, 6, (0, 0, 1))) grid_rings(att, [last, mouth], GRAPH, closed_u=False, expect=(0, nu // 2, (0, -1, 0))) recess = [(938, 27), (950, 14), (952, 0), (950, -14), (938, -27), (960, -18), (993, -11), (993, 11), (960, 18)] patch(att, [(l * S, Yi(x)) for x, l in recess], "top", SENSOR, lift=0.002) rx_a, rx_b, rhw2 = SCOOP_RAMP patch(att, [(-rhw2 * S, Yi(rx_a)), (rhw2 * S, Yi(rx_a)), (rhw2 * S, Yi(rx_b)), (-rhw2 * S, Yi(rx_b))], "top", PAINT, lift=0.004, rim=GRAPH, rim_r=0.006) # ------------------------------------------------------------------ nose sensors (TOP and UNDERSIDE: dark capsules near the tip) def sensor_outline(spec, x_end): up = [(spec[0][0] - 4, 0.0)] + [(x, l) for x, l in spec] + [(x_end, 0.0)] dn = [(x, -l) for x, l in reversed(spec)] return [(l * S, Yi(x)) for x, l in up + dn] patch(att, sensor_outline(NOSE_SENSOR_TOP, 1101), "top", SENSOR, lift=0.002, bulge=0.012, rim=GRAPH, rim_r=0.006, step=0.012) patch(att, sensor_outline(NOSE_SENSOR_BOT, 1098), "bot", SENSOR, lift=0.002, bulge=0.010, rim=GRAPH, rim_r=0.006, step=0.012) # ------------------------------------------------------------------ stern # lip over the end face (SIDE: the beak at the top of the stern) and the two lugs under it (TOP) lip = [] for j in range(9): x = 520 + (536 - 520) * j / 8 y = Yi(x) zt = min(TOPf(min(y, Y_TAIL)), Zi(STERN_LIP[1]) + (j / 8) * 0.06) if y <= Y_TAIL else Zi(STERN_LIP[1]) + (j / 8) * 0.06 lip.append((y, 18 * S * (0.85 + 0.15 * j / 8), zt - 0.05, zt, 0.02)) lip.sort(key=lambda s: s[0]) loft(att, lip, PAINT, xf=Matrix.Rotation(math.pi / 2, 4, 'Z')) lx0, lx1, llat, lz = STERN_LUGS for side in (1, -1): att.box((0.10, (lx1 - lx0) * S, 0.13), (side * llat * S, Yi((lx0 + lx1) / 2), Zi(lz)), PAINT, bevel=0.03, seg=2) # the stern hatch and its vent slots (REAR view), seen from behind on the end face and the steep aft underside hatch_out = [(-0.30, 0.47), (0.30, 0.47), (0.38, 0.38), (0.42, 0.0), (0.38, -0.20), (0.26, -0.27), (-0.26, -0.27), (-0.38, -0.20), (-0.42, 0.0), (-0.38, 0.38)] patch(att, hatch_out, "rear", PAINT, lift=0.006, rim=GRAPH, rim_r=0.010, step=0.025) for sx in (-0.33, -0.25, 0.25, 0.33): patch(att, [(sx + v, u) for u, v in capsule(-0.12, 0.10, 0.0, 0.022)], "rear", SENSOR, lift=0.009, step=0.012) # top aft vent: three slots across (TOP: the grille on the aft slope) tx0, tx1, tlat = TOP_AFT_VENT for x in (549.5, 556.0, 562.5): patch(att, capsule(-tlat * S, tlat * S, Yi(x), 2.0 * S), "top", SENSOR, lift=0.002, step=0.015) # side vents on the upper flanks at the stern (SIDE: dark slot; TOP: slots at lateral 43 px) for vw, sg in (("sideP", 1), ("sideS", -1)): (xa, va), (xb, vb) = STERN_VENT_SIDE patch(att, capsule(Yi(xb), Yi(xa), Zi(va), 5 * S), vw, SENSOR, lift=0.002, rim=PAINT, rim_r=0.008, step=0.015) # pods with whip antennas (TOP: two small capsules with antennas raked aft and out; SIDE: the mast) MASTS = {} px0, px1, plat = STERN_PODS ax_, alat, av = ANTENNA_TIP for side in (1, -1): yc = Yi((px0 + px1) / 2) zc = hull_z_at(plat * S, yc) c = Vector((side * plat * S, yc, zc)) att.sphere(1.0, c + Vector((0, 0, 0.005)), GRAPH, segs=16, rings=8, scale=(0.075, 0.12, 0.055)) att.sphere(1.0, c + Vector((0, -0.02, 0.025)), LENS, segs=16, rings=8, scale=(0.045, 0.07, 0.04)) base = c + Vector((0, 0.07, 0.03)) tipp = Vector((side * alat * S, Yi(ax_), Zi(av))) # the whip is its own object: raised as drawn, folded aft along the stern slope for the carousel (the raised whips # reach 0.52 m into the bay's curved wall in the carousel sweep; folded they clear it, build/sweep_check.py) mb_m = MB("SM_Spearfish_Mast_%s" % ("L" if side > 0 else "R"), [STEEL, GRAPH]) pts = [base.lerp(tipp, t) for t in (0, 0.08, 0.3, 0.6, 1.0)] mb_m.sweep(pts, [0.016, 0.010, 0.008, 0.006, 0.004], STEEL, sides=6) mb_m.sphere(0.022, base, GRAPH, segs=12, rings=6) MASTS[side] = (mb_m, base.copy(), (tipp - base).normalized(), (tipp - base).length) att.cyl(0.028, 0.024, 0.07, base + Vector((0, 0, -0.01)), GRAPH, segs=12) # the small sensor box on a post beside the mast (SIDE: the rounded tag at the mast's foot) bx = Vector((side * plat * S, Yi(584.0), Zi(338.0))) att.cyl(0.012, 0.012, 0.12, bx + Vector((0, 0, -0.08)), STEEL, segs=8) att.box((0.05, 0.11, 0.10), bx, GRAPH, bevel=0.015) # folded pose: pointing straight aft and down the stern slope, clear of the skin (checked here along its length) MAST_FOLD = {} for side, (mb_m, base, d_r, ln) in MASTS.items(): best = None for k in range(40): slope = -0.20 - 0.01 * k d_f = Vector((0, 1.0, slope)).normalized() clear = min(base.z + d_f.z * ln * t - hull_z_at(base.x, base.y + d_f.y * ln * t) for t in [j / 20 for j in range(2, 21)] if base.y + d_f.y * ln * t <= Y_TAIL) if clear >= 0.025: best = (d_f, clear) if best is None: best = (Vector((0, 1.0, -0.20)).normalized(), None) d_f = best[0] axis = d_r.cross(d_f).normalized() ang = math.acos(max(-1.0, min(1.0, d_r.dot(d_f)))) MAST_FOLD[side] = (axis, ang, best[1]) REPORT["mast_fold"] = {("L" if s > 0 else "R"): dict(axis=[round(c, 4) for c in v[0]], deg=round(math.degrees(v[1]), 2), least_clear_m=None if v[2] is None else round(v[2], 3)) for s, v in MAST_FOLD.items()} # ------------------------------------------------------------------ underside (UNDERSIDE, inferred) bx0, bx1, bhw, (sl0, sl1) = VENTRAL_BLISTER bout = [(bhw * S * math.cos(a), Yi(bx0 + bhw) + bhw * S * math.sin(a)) for a in [math.pi * j / 8 for j in range(9)]] bout = bout + [(-bhw * S, Yi(bx1)), (bhw * S, Yi(bx1))] patch(att, bout, "bot", PAINT, lift=0.004, bulge=0.020, rim=GRAPH, rim_r=0.007) # SIDE shows nothing below the keel for x in (893, 898, 903, 908): p, n = proj("bot", 0.0, Yi(x)) att.box((0.17, 0.030, 0.012), p + n * 0.020, SENSOR, bevel=0.004) ux0, ux1, ulat = UNDER_SLOTS for side in (1, -1): patch(att, [(side * ulat * S + v, u) for u, v in capsule(Yi(ux1), Yi(ux0), 0.0, 2.5 * S)], "bot", SENSOR, lift=0.002, rim=PAINT, rim_r=0.007, step=0.015) sx0, sx1, shw = UNDER_STERN_VENT for l in (-8, 0, 8): patch(att, [(l * S + v, u) for u, v in capsule(Yi(sx1), Yi(sx0), 0.0, 2.2 * S)], "bot", SENSOR, lift=0.002, step=0.015) # ------------------------------------------------------------------ panel seams (thin dark strips on the paint) def strip(mb, P, half=0.006, lift=0.0015): rows = [] for off in (-1, 1): row = [] n = len(P) for j in range(n): t = (P[min(j + 1, n - 1)] - P[max(j - 1, 0)]).normalized() N = normal_at(P[j]) b = N.cross(t).normalized() row.append(P[j] + N * lift + b * half * off) rows.append(row) grid_rings(mb, [list(r) for r in zip(*rows)], SEAM, closed_u=False, expect=(len(P) // 2, 0, tuple(normal_at(P[len(P) // 2])))) def in_opening(p): return any(pip((p.y, p.z), poly) for poly in SIDE_OPENINGS) for x in SEAMS_X: y = Yi(x) pts = [sec(y, -math.pi / 2 + 2 * math.pi * k / 240) for k in range(241)] run = [] for p in pts: bad = in_opening(p) if bad: if len(run) > 2: strip(att, run) run = [] else: run.append(p) if len(run) > 2: strip(att, run) dx0, dx1, dlat = DECK_SEAM for side in (1, -1): P = [on_top(side * dlat * S, Yi(dx0 + (dx1 - dx0) * j / 60)) for j in range(61)] strip(att, P) # ------------------------------------------------------------------ docking interface: holder clips for the docking arm # 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, claw pads r 0.555 at z 1.24-1.34. As on the Manta: an arc of the Kazah collar's outer profile # at each claw (wall r 0.535 z 1.18-1.335, hook lip r 0.552, top z 1.375). They stand on a slim column between the # dorsal rails (the rails' inner edges 0.403 m off the centre line, their tops at z 1.094, 3 cm under the disc). clip = MB("_clamp", [PAINT, GRAPH, YELLOW, STEEL]) CL_C = Vector((0.0, Yi(CL_IMG), 0.0)) MC = 64 SK_R0, SK_R1, SK_Z1 = 0.37, 0.32, 1.12 skirt = [] for t in [j / 5 for j in range(6)]: ring = [] for a in range(MC): ph = 2 * math.pi * a / MC r = SK_R1 + (SK_R0 - SK_R1) * (1 - math.sin(t * math.pi / 2)) zb = hull_z_at(CL_C.x + SK_R0 * math.cos(ph), CL_C.y + SK_R0 * math.sin(ph)) - 0.02 z = zb + (SK_Z1 - zb) * (1 - math.cos(t * math.pi / 2)) ring.append(Vector((CL_C.x + r * math.cos(ph), CL_C.y + r * math.sin(ph), z))) skirt.append(ring) disc = [[Vector((CL_C.x + r * math.cos(2 * math.pi * a / MC), CL_C.y + r * math.sin(2 * math.pi * a / MC), z)) for a in range(MC)] for (r, z) in ((0.50, 1.122), (0.53, 1.130), (0.53, 1.150), (0.46, 1.150), (0.40, 1.150), (0.36, 1.150))] grid_rings(clip, skirt + disc, [PAINT] * 5 + [PAINT, PAINT, PAINT, PAINT, YELLOW, GRAPH], cap1=Vector((CL_C.x, CL_C.y, 1.15)), expect=(8, 0, (0, 0, 1))) CLIP_LOOP = [(0.400, 1.150), (0.520, 1.150), (0.535, 1.180), (0.535, 1.335), (0.548, 1.350), (0.552, 1.365), (0.545, 1.375), (0.400, 1.375)] CLIP_HALF = math.radians(20.0) def revolve_arc(mb, loop, a0, a1, segs, mat): bm2 = bmesh.new() cols = [[bm2.verts.new((CL_C.x + r * math.cos(a0 + (a1 - a0) * j / segs), CL_C.y + r * math.sin(a0 + (a1 - a0) * j / segs), z)) for (r, z) in loop] for j in range(segs + 1)] n = len(loop) for j in range(segs): for q in range(n): bm2.faces.new((cols[j][q], cols[j + 1][q], cols[j + 1][(q + 1) % n], cols[j][(q + 1) % n])) bm2.faces.new(list(reversed(cols[0]))) bm2.faces.new(cols[-1]) bm2.normal_update() bmesh.ops.recalc_face_normals(bm2, faces=bm2.faces[:]) mb.merge(bm2, mat) CLIP_AZ = (45.0, 135.0, 225.0, 315.0) for az in CLIP_AZ: a = math.radians(az) revolve_arc(clip, CLIP_LOOP, a - CLIP_HALF, a + CLIP_HALF, 10, STEEL) clip.box((0.05, 0.05, 0.006), (CL_C.x + 0.47 * math.cos(a), CL_C.y + 0.47 * math.sin(a), 1.378), YELLOW, rot=(0, 0, a)) # ------------------------------------------------------------------ top hatch (TOP: the trapezoid panel ahead of the rails); moves (hxa, hwa), (hxf, hwf) = HATCH hatch = MB("SM_Spearfish_Hatch", [PAINT, GRAPH, YELLOW]) ho = [(-hwa * S, Yi(hxa)), (hwa * S, Yi(hxa)), (hwf * S, Yi(hxf)), (-hwf * S, Yi(hxf))] LID_Z0, LID_Z1 = 0.012, 0.030 # the lid's underside and top over the skin (the frame's flange reaches 0.007) def slab(mb, outline, z0, z1, mat, step=0.02, scales=(1.0, 0.96, 0.7, 0.4)): """a plate of thickness on the top of the hull over a TOP-view outline (x, y): top, underside and edge band""" pts = resample(outline, step) cu, cvv = centroid(outline) def rows_at(lift): out = [] for s in scales: row = [] for (u, v) in pts: q = on_top(cu + (u - cu) * s, cvv + (v - cvv) * s) row.append(q + normal_at(q) * lift) out.append(row) return out pc = on_top(cu, cvv); nc = normal_at(pc) top, bot = rows_at(z1), rows_at(z0) grid_rings(mb, top, mat, cap1=pc + nc * z1, expect=(0, 0, tuple(nc))) grid_rings(mb, bot, mat, cap1=pc + nc * z0, expect=(0, 0, tuple(-nc))) o = Vector((pts[0][0] - cu, pts[0][1] - cvv, 0)).normalized() grid_rings(mb, [top[0], bot[0]], mat, expect=(0, 0, tuple(o))) slab(hatch, ho, LID_Z0, LID_Z1, PAINT) # four bolts (TOP: dots at the corners) and a yellow grab bar at the fore edge cu, cvv = centroid(ho) for (u, v) in ho: q = on_top(cu + (u - cu) * 0.84, cvv + (v - cvv) * 0.84) hatch.cyl(0.014, 0.014, 0.010, q + normal_at(q) * (LID_Z1 + 0.003), GRAPH, segs=10) gy = Yi(hxf) + 0.07 hatch.sweep([on_top(x, gy) + Vector((0, 0, LID_Z1 + 0.03 * (abs(x) < 0.10))) for x in (-0.14, -0.09, 0.09, 0.14)], 0.012, YELLOW, sides=8) HATCH_HINGE = Vector((0.0, Yi(hxa), hull_z_at(0.0, Yi(hxa)) + 0.5 * (LID_Z0 + LID_Z1))) # ------------------------------------------------------------------ prune what sank into the hull REPORT["pruned_faces"] = prune_inside(att, inside_depth, keep_depth=0.02) REPORT["stage"] = "fittings"