"""Tiger: the jaw as one quad grid (exec'd into tg_geom's namespace by tg_geom.part_jaw; plain numpy). The jaw is the red wedge under the glass. Its port flank is a grid of stations (from the front corner back to y 0.10) by rows (from the sill, t = 0, down to the keel, t = 1); the starboard flank mirrors it, a narrow keel strip joins them below and the cockpit's floor closes them over the top inside the glass. The stations are not planes: four of them follow each of the picture's two gill grooves (cast onto the flank), so each groove is a real channel of the grid, 2 cm deep with sloped ends. The dark oval intake at the jaw's rear (the picture's "C") is a block of the grid whose edge is moved onto the oval, its faces replaced by a socket 8 cm deep. """ JAW_YEND = 0.10 COCKPIT_FLOOR_Z = -0.13 # the jaw's top behind the glass's rear rim: the sill drops under the side body's front _JT_Z = pchip([Y_G1, -0.40, -0.22, -0.05, JAW_YEND], [float(SILL(Y_G1)), 0.10, -0.06, -0.12, -0.12]) def _sill_x(y, zs): yy = min(y, Y_G1) a = tc.angle_of_z(CP, yy, zs, CREST, GBOT, GMID) x, _ = tc.ring_point(CP, yy, a, CREST, GBOT, GMID, HALFW) return max(x, 0.02) _X_G1 = _sill_x(Y_G1, float(SILL(Y_G1))) def jaw_top(y): if y <= Y_G1: zs = float(SILL(y)) if y > Y_JAW0 + 1e-3 else float(GBOT(y)) return _sill_x(y, zs), zs zs = float(_JT_Z(y)) return _X_G1 + (0.60 - _X_G1) * min(1.0, (y - Y_G1) / 0.3), zs def jaw_pt(y, t): """port flank point at station y, t from the top (0) to the keel (1)""" y = min(max(y, Y_JAW0), JAW_YEND) xs, zs = jaw_top(y) zk, wk = float(JZK(y)), float(JWK(y)) bul = 0.012 * math.sin(math.pi * t) * min(1.0, (y - Y_JAW0) / 0.3) return np.array([xs + (wk - xs) * t ** 1.12 + bul, y, zs + (zk - zs) * t]) def flank_x(y, z): """the jaw's port flank x at station y and height z""" return float(jaw_pt(y, min(1.0, max(0.0, t_of(y, z))))[0]) def t_of(y, z): xs, zs = jaw_top(y) return (zs - z) / (zs - float(JZK(y))) # the grooves' centre lines, cast from the picture onto the flank (y, z) GROOVES = [[(-0.967, -0.309), (-0.947, -0.427), (-0.852, -0.545), (-0.701, -0.672), (-0.593, -0.728)], [(-0.653, -0.240), (-0.663, -0.348), (-0.561, -0.494), (-0.388, -0.622), (-0.298, -0.670)]] # the oval intake: centre, half axes (y, z) and tilt, read from the picture's dark oval cast onto the flank OVAL = dict(cy=-0.205, cz=-0.395, ay=0.255, az=0.165, tilt=math.radians(-18)) N_T = 14 TS = cos_space(N_T, 0.0, 1.0) def _station_fn(pts, shift=0.0): """y(t) along a groove (t from the flank), held flat beyond its ends""" ts = [t_of(y, z) for y, z in pts] ys = [y + shift for y, _ in pts] f = pchip(ts, ys) t0, t1 = ts[0], ts[-1] s0 = (ys[1] - ys[0]) / (ts[1] - ts[0]) s1 = (ys[-1] - ys[-2]) / (ts[-1] - ts[-2]) def g(t): if t < t0: return ys[0] + s0 * 0.3 * (t - t0) if t > t1: return ys[-1] + s1 * 0.5 * (t - t1) return float(f(t)) return g, t0, t1 def _blend(fa, fb, k): return lambda t: fa(t) * (1 - k) + fb(t) * k def jaw_stations(): """station functions y(t) in order front to back, and the groove ranges (station index pairs to sink, t range)""" st = [] vert = lambda y0: (lambda t: y0) for y0 in (Y_JAW0, Y_JAW0 + 0.02, -1.26, -1.18, -1.11): st.append(vert(y0)) sinks = [] prev = st[-1] for gi, pts in enumerate(GROOVES): g0, t0, t1 = _station_fn(pts, -0.026) # blend from the previous station into the groove's shape for k in (0.35, 0.7): st.append(_blend(prev, g0, k) if gi == 0 else _blend(prev, g0, k)) i0 = len(st) for sh in (-0.026, -0.011, 0.011, 0.026): st.append(_station_fn(pts, sh)[0]) sinks.append(((i0 + 1, i0 + 2), t0, t1)) prev = st[-1] # behind the second groove, upright again toward the intake and the jaw's end for y0 in (-0.42, -0.30, -0.17, -0.04, 0.04, JAW_YEND): st.append(_blend(prev, vert(y0), 0.6) if y0 == -0.42 else vert(y0)) return st, sinks def part_jaw(): st, sinks = jaw_stations() nu = len(st) G = np.zeros((nu, N_T, 3)) for i, f in enumerate(st): for j, t in enumerate(TS): G[i, j] = jaw_pt(f(t), t) # grooves: the two middle stations of each sink 2 cm inward (along the flank's inward normal), ends sloped for (ia, ib), t0, t1 in sinks: for i in (ia, ib): for j, t in enumerate(TS): if t < t0 - 0.02 or t > t1 + 0.02: continue w = min(1.0, (t - (t0 - 0.02)) / 0.06, ((t1 + 0.02) - t) / 0.06) a, b = G[i, max(0, j - 1)], G[i, min(N_T - 1, j + 1)] c, d = G[i - 1, j], G[i + 1, j] n = np.cross(b - a, d - c) n /= np.linalg.norm(n) + 1e-12 if n[0] < 0: n = -n G[i, j] -= n * 0.03 * w # the oval: the grid block whose centres fall inside it is cut; its boundary vertices go onto the oval def oval_r(y, z): dy, dz = y - OVAL["cy"], z - OVAL["cz"] c, s = math.cos(OVAL["tilt"]), math.sin(OVAL["tilt"]) u, v = c * dy + s * dz, -s * dy + c * dz return math.hypot(u / OVAL["ay"], v / OVAL["az"]), math.atan2(v / OVAL["az"], u / OVAL["ay"]) cut = np.zeros((nu - 1, N_T - 1), bool) for i in range(nu - 1): for j in range(N_T - 1): q = (G[i, j] + G[i + 1, j] + G[i, j + 1] + G[i + 1, j + 1]) / 4 cut[i, j] = oval_r(q[1], q[2])[0] < 1.0 # boundary vertices of the cut block bnd = set() for i in range(nu - 1): for j in range(N_T - 1): if cut[i, j]: for (a, b) in ((i, j), (i + 1, j), (i, j + 1), (i + 1, j + 1)): bnd.add((a, b)) inner = set() for (a, b) in bnd: faces = [(a - di, b - dj) for di in (0, 1) for dj in (0, 1)] if all(0 <= fi < nu - 1 and 0 <= fj < N_T - 1 and cut[fi, fj] for fi, fj in faces): inner.add((a, b)) rim = sorted(bnd - inner) for (a, b) in rim: y, z = G[a, b, 1], G[a, b, 2] r, ang = oval_r(y, z) c, s = math.cos(OVAL["tilt"]), math.sin(OVAL["tilt"]) u, v = OVAL["ay"] * math.cos(ang), OVAL["az"] * math.sin(ang) ny, nz = OVAL["cy"] + c * u - s * v, OVAL["cz"] + s * u + c * v t = min(1.0, max(0.0, t_of(ny, nz))) G[a, b] = jaw_pt(ny, t) # mesh: port flank (minus the cut), starboard mirror, keel strip, floor strip, front and rear caps V = list(G.reshape(-1, 3)) n_port = len(V) V += list((G * np.array([-1, 1, 1])).reshape(-1, 3)) idx = lambda i, j, side=0: side * n_port + i * N_T + j F, C, M = [], [], [] groove = np.zeros((nu - 1, N_T - 1), bool) for (ia, ib), t0, t1 in sinks: for i in range(ia - 1, ib + 1): for j in range(N_T - 1): tm = (TS[j] + TS[j + 1]) / 2 groove[i, j] = t0 - 0.01 < tm < t1 + 0.01 for side in (0, 1): for i in range(nu - 1): for j in range(N_T - 1): if cut[i, j]: continue q = (idx(i, j, side), idx(i, j + 1, side), idx(i + 1, j + 1, side), idx(i + 1, j, side)) F.append(q if side == 0 else q[::-1]) C.append(BLACK if groove[i, j] else RED) M.append("groove_dark" if groove[i, j] else "paint_red") # the keel strip: the port row's last point joined to the starboard's for i in range(nu - 1): F.append((idx(i, N_T - 1), idx(i, N_T - 1, 1), idx(i + 1, N_T - 1, 1), idx(i + 1, N_T - 1))) C.append(RED) M.append("paint_red") # the cockpit's floor: from each sill an inner wall 3 cm in and down to a flat floor at z_floor (0.13 under the datum, or # 3 cm under the sill where the sill is lower), the floor across; the cabin's parts stand on it (tg_cabin_check.py) fl = len(V) for i in range(nu): p = G[i, 0] zf = min(COCKPIT_FLOOR_Z, p[2] - 0.03) xf = max(0.02, flank_x(p[1], zf) - 0.03) # 3 cm inside the flank at the floor's height (it narrows downward) V.append(np.array([xf, p[1], zf])) V.append(np.array([-xf, p[1], zf])) fp = lambda i, side: fl + 2 * i + side for i in range(nu - 1): F.append((idx(i, 0), idx(i + 1, 0), fp(i + 1, 0), fp(i, 0))) F.append((idx(i + 1, 0, 1), idx(i, 0, 1), fp(i, 1), fp(i + 1, 1))) F.append((fp(i, 0), fp(i + 1, 0), fp(i + 1, 1), fp(i, 1))) C += [DARK, DARK, DARK] M += ["cockpit_dark", "cockpit_dark", "cockpit_floor"] for i, cap in ((0, "front"), (nu - 1, "rear")): loop = [idx(i, j) for j in range(N_T)] + [idx(i, j, 1) for j in range(N_T - 1, -1, -1)] + [fp(i, 1), fp(i, 0)] F.append(tuple(loop if cap == "rear" else loop[::-1])) C.append(RED) M.append("paint_red") # the oval socket: the rim loop (ordered round the oval), stepped inward, closed by a dark back rim_sorted = sorted(rim, key=lambda ab: oval_r(G[ab[0], ab[1], 1], G[ab[0], ab[1], 2])[1]) ring0 = np.array([G[a, b] for a, b in rim_sorted]) cen = ring0.mean(0) nrm = np.cross(ring0[len(ring0) // 4] - ring0[0], ring0[len(ring0) // 2] - ring0[0]) nrm /= np.linalg.norm(nrm) if nrm[0] < 0: nrm = -nrm for side in (0, 1): mir = np.array([1, 1, 1]) if side == 0 else np.array([-1, 1, 1]) base = len(V) rim_ids = [idx(a, b, side) for a, b in rim_sorted] r1 = [(p - nrm * 0.045) * 1.0 for p in ring0] r1 = [cen - nrm * 0.045 + (p - cen) * 0.92 for p in ring0] r2 = [cen - nrm * 0.085 + (p - cen) * 0.80 for p in ring0] V += [p * mir for p in r1] + [p * mir for p in r2] + [(cen - nrm * 0.10) * mir] m = len(ring0) for k in range(m): k1 = (k + 1) % m q1 = (rim_ids[k], rim_ids[k1], base + k1, base + k) q2 = (base + k, base + k1, base + m + k1, base + m + k) tri = (base + m + k, base + m + k1, base + 2 * m) for q in (q1, q2, tri): F.append(q if side == 0 else q[::-1]) C.append(NAVY) M.append("intake_dark") return Part("jaw", np.array(V), F, C, M, True)