Загрузка данных


import bpy
import math
import random
from mathutils import Vector

bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

for m in list(bpy.data.materials):
    bpy.data.materials.remove(m)

def c_mat(c, r=0.5, m=0.0, em=0.0):
    mat = bpy.data.materials.new("Material")
    mat.use_nodes = True
    b = mat.node_tree.nodes.get("Principled BSDF")
    if b:
        b.inputs['Base Color'].default_value = c
        b.inputs['Roughness'].default_value = r
        b.inputs['Metallic'].default_value = m
        if em > 0:
            if 'Emission Color' in b.inputs:
                b.inputs['Emission Color'].default_value = c
                b.inputs['Emission Strength'].default_value = em
            elif 'Emission' in b.inputs:
                b.inputs['Emission'].default_value = c
                if 'Emission Strength' in b.inputs:
                    b.inputs['Emission Strength'].default_value = em
    return mat

def c_poop_mat():
    mat = bpy.data.materials.new("Material")
    mat.use_nodes = True
    nodes = mat.node_tree.nodes
    links = mat.node_tree.links
    b = nodes.get("Principled BSDF")
    if b:
        b.inputs['Base Color'].default_value = (0.14, 0.055, 0.015, 1.0)
        b.inputs['Roughness'].default_value = 0.08
        noise = nodes.new(type='ShaderNodeTexNoise')
        noise.inputs['Scale'].default_value = 18.0
        noise.inputs['Detail'].default_value = 10.0
        noise.inputs['Roughness'].default_value = 0.65
        bump = nodes.new(type='ShaderNodeBump')
        bump.inputs['Strength'].default_value = 0.45
        bump.inputs['Distance'].default_value = 0.03
        links.new(noise.outputs['Fac'], bump.inputs['Height'])
        links.new(bump.outputs['Normal'], b.inputs['Normal'])
    return mat

m_sea = c_mat((0.01, 0.035, 0.065, 1.0), r=0.1, m=0.1)
m_wood_dark = c_mat((0.12, 0.06, 0.025, 1.0), r=0.75)
m_wood_mid = c_mat((0.22, 0.11, 0.045, 1.0), r=0.7)
m_wood_deck = c_mat((0.32, 0.19, 0.08, 1.0), r=0.65)
m_sail = c_mat((0.78, 0.74, 0.66, 1.0), r=0.8)
m_iron = c_mat((0.1, 0.11, 0.13, 1.0), r=0.35, m=0.9)
m_gold = c_mat((0.95, 0.72, 0.15, 1.0), r=0.22, m=0.95)
m_steel = c_mat((0.85, 0.88, 0.92, 1.0), r=0.12, m=0.98)
m_lantern = c_mat((1.0, 0.65, 0.15, 1.0), r=0.2, em=14.0)
m_skin = c_mat((0.82, 0.62, 0.48, 1.0), r=0.55)
m_coat = c_mat((0.55, 0.06, 0.08, 1.0), r=0.65)
m_shirt = c_mat((0.85, 0.82, 0.76, 1.0), r=0.7)
m_pants = c_mat((0.08, 0.1, 0.14, 1.0), r=0.75)
m_boots = c_mat((0.05, 0.04, 0.03, 1.0), r=0.4)
m_beard = c_mat((0.06, 0.04, 0.03, 1.0), r=0.85)
m_poop = c_poop_mat()
m_rain = c_mat((0.55, 0.75, 1.0, 1.0), r=0.1, em=1.5)
m_snow = c_mat((0.95, 0.98, 1.0, 1.0), r=0.3, em=0.8)
m_bolt = c_mat((0.75, 0.9, 1.0, 1.0), r=0.1, em=50.0)

world = bpy.context.scene.world
if not world:
    world = bpy.data.worlds.new("World")
    bpy.context.scene.world = world
world.use_nodes = True
bg_node = world.node_tree.nodes.get("Background")
if bg_node:
    bg_node.inputs[0].default_value = (0.015, 0.025, 0.045, 1.0)
    bg_node.inputs[1].default_value = 0.35

def make_limb_cyl(r1, r2, length, mat, segs=12, parent=None, loc=(0, 0, 0)):
    bpy.ops.mesh.primitive_cone_add(vertices=segs, radius1=r1, radius2=r2, depth=length, location=(0, 0, 0))
    o = bpy.context.active_object
    for v in o.data.vertices:
        v.co.z += length * 0.5
    o.data.update()
    o.data.materials.append(mat)
    if parent:
        o.parent = parent
    o.location = loc
    return o

def make_limb_box(sx, sy, sz, mat, parent=None, loc=(0, 0, 0), pivot_z=0.0):
    bpy.ops.mesh.primitive_cube_add(size=1.0, location=(0, 0, 0))
    o = bpy.context.active_object
    for v in o.data.vertices:
        v.co.x *= sx
        v.co.y *= sy
        v.co.z = (v.co.z + pivot_z) * sz
    o.data.update()
    o.data.materials.append(mat)
    if parent:
        o.parent = parent
    o.location = loc
    return o

bpy.ops.mesh.primitive_grid_add(x_subdivisions=80, y_subdivisions=80, size=150.0, location=(0, 0, 0))
ocean = bpy.context.active_object
ocean.data.materials.append(m_sea)
bpy.ops.object.shade_smooth()

w1 = ocean.modifiers.new("Wave", 'WAVE')
w1.height = 0.75
w1.width = 4.8
w1.narrowness = 1.5
w1.speed = 0.38

w2 = ocean.modifiers.new("Wave.001", 'WAVE')
w2.use_x = False
w2.use_y = True
w2.height = 0.45
w2.width = 3.4
w2.narrowness = 1.8
w2.speed = 0.48

bpy.ops.mesh.primitive_cube_add(size=1.0, location=(0, -14, 0.2))
ship = bpy.context.active_object

hull_levels = [
    (-1.0, [(0.0, -5.6), (0.8, -4.8), (1.1, -1.5), (1.1, 2.2), (0.7, 4.4), (0.0, 5.4)]),
    (-0.2, [(0.0, -6.0), (1.6, -5.0), (2.2, -1.5), (2.2, 2.4), (1.4, 4.8), (0.0, 6.0)]),
    ( 0.8, [(0.0, -6.4), (2.2, -5.2), (2.8, -1.5), (2.8, 2.6), (1.9, 5.2), (0.0, 6.8)]),
    ( 1.9, [(0.0, -6.7), (2.4, -5.4), (3.0, -1.5), (3.0, 2.8), (2.1, 5.5), (0.0, 7.4)]),
    ( 2.4, [(0.0, -6.9), (2.3, -5.5), (2.9, -1.5), (2.9, 2.8), (2.1, 5.6), (0.0, 7.7)])
]

h_verts = []
h_faces = []
ring_len = 10

for z, prof in hull_levels:
    ring = [(0.0, prof[0][1], z)]
    for x, y in prof[1:-1]:
        ring.append((x, y, z))
    ring.append((0.0, prof[-1][1], z))
    for x, y in reversed(prof[1:-1]):
        ring.append((-x, y, z))
    h_verts.extend(ring)

for l in range(len(hull_levels) - 1):
    b = l * ring_len
    t = (l + 1) * ring_len
    for i in range(ring_len):
        ni = (i + 1) % ring_len
        h_faces.append([b + i, b + ni, t + ni, t + i])

b_top = (len(hull_levels) - 1) * ring_len
h_faces.append(list(range(ring_len - 1, -1, -1)))
h_faces.append([b_top + i for i in range(ring_len)])

ship.data.clear_geometry()
ship.data.from_pydata(h_verts, [], h_faces)
ship.data.update()
ship.data.materials.append(m_wood_dark)

make_limb_box(0.35, 12.2, 0.5, m_wood_dark, ship, (0, -0.2, -1.1), 0.0)
make_limb_box(5.2, 10.8, 0.18, m_wood_deck, ship, (0, 0.3, 1.9), 0.0)

make_limb_box(4.4, 3.4, 1.4, m_wood_mid, ship, (0, -4.7, 1.9), 0.5)
make_limb_box(4.7, 3.7, 0.18, m_wood_deck, ship, (0, -4.7, 3.3), 0.0)

make_limb_box(3.8, 2.2, 0.9, m_wood_mid, ship, (0, 5.1, 1.9), 0.5)
make_limb_box(4.0, 2.4, 0.16, m_wood_deck, ship, (0, 5.1, 2.8), 0.0)

for step in range(6):
    for sx in [-1.3, 1.3]:
        make_limb_box(0.7, 0.25, 0.12, m_wood_deck, ship, (sx, -2.8 - step * 0.2, 2.0 + step * 0.22), 0.0)

for wx in [-1.3, -0.45, 0.45, 1.3]:
    make_limb_box(0.55, 0.12, 0.6, m_lantern, ship, (wx, -3.0, 2.65), 0.0)
    make_limb_box(0.55, 0.12, 0.6, m_lantern, ship, (wx, -6.42, 2.65), 0.0)

for side in [-1, 1]:
    make_limb_box(0.22, 8.2, 0.2, m_wood_mid, ship, (side * 2.75, 0.5, 2.75), 0.0)
    for py in [-2.4, -1.0, 0.4, 1.8, 3.2]:
        make_limb_box(0.45, 0.35, 0.25, m_wood_dark, ship, (side * 2.2, py, 2.0), 0.5)
        barrel = make_limb_cyl(0.16, 0.12, 0.95, m_iron, 12, ship, (side * 2.0, py, 2.32))
        barrel.rotation_euler.y = math.radians(side * 90)

bowsprit = make_limb_cyl(0.22, 0.09, 5.0, m_wood_mid, 12, ship, (0, 6.2, 2.2))
bowsprit.rotation_euler.x = math.radians(-68)
fig_head = make_limb_cyl(0.25, 0.12, 1.1, m_gold, 10, ship, (0, 7.1, 1.5))
fig_head.rotation_euler.x = math.radians(-50)

masts_cfg = [(0.0, 0.6, 10.2, 1.05), (0.0, 4.2, 8.2, 0.85), (0.0, -4.2, 7.4, 0.78)]
sails = []

for mx, my, mh, sc in masts_cfg:
    make_limb_cyl(0.26 * sc, 0.14 * sc, mh, m_wood_mid, 14, ship, (mx, my, 1.9))
    make_limb_cyl(0.55 * sc, 0.7 * sc, 0.45, m_wood_dark, 12, ship, (mx, my, 1.9 + mh * 0.68))

    for side in [-1, 1]:
        rope = make_limb_cyl(0.025, 0.025, mh * 0.72, m_wood_dark, 6, ship, (side * 2.6, my, 2.3))
        rope.rotation_euler.y = math.radians(-side * 18)

    for sz, sh, sw in [(mh * 0.32, mh * 0.28, 4.8 * sc), (mh * 0.64, mh * 0.24, 3.8 * sc), (mh * 0.88, mh * 0.16, 2.6 * sc)]:
        bpy.ops.mesh.primitive_cylinder_add(vertices=10, radius=0.09 * sc, depth=sw + 0.7, location=(0, 0, 0), rotation=(0, math.radians(90), 0))
        yard = bpy.context.active_object
        yard.data.materials.append(m_wood_dark)
        yard.parent = ship
        yard.location = (mx, my, 1.9 + sz + sh * 0.5)

        bpy.ops.mesh.primitive_grid_add(x_subdivisions=10, y_subdivisions=10, size=1.0, location=(0, 0, 0))
        sl = bpy.context.active_object
        for v in sl.data.vertices:
            nx = v.co.x * 2.0
            ny = v.co.y * 2.0
            bulge = (1.0 - nx * nx) * (1.0 - ny * ny) * 0.75
            v.co.x = v.co.x * sw
            v.co.z = v.co.y * sh
            v.co.y = bulge + 0.14
        sl.data.update()
        sl.data.materials.append(m_sail)
        sl.parent = ship
        sl.location = (mx, my, 1.9 + sz)
        sails.append(sl)

bpy.ops.mesh.primitive_torus_add(major_radius=0.5, minor_radius=0.055, location=(0, 0, 0), rotation=(math.radians(90), 0, 0))
wheel = bpy.context.active_object
wheel.data.materials.append(m_wood_dark)
wheel.parent = ship
wheel.location = (0, -3.5, 4.0)
make_limb_box(0.24, 0.24, 0.7, m_wood_dark, ship, (0, -3.6, 3.3), 0.5)

lantern_pivots = []
for lx, ly, lz in [(-1.8, -3.0, 3.2), (1.8, -3.0, 3.2), (0.0, 0.6, 5.2), (0.0, 3.8, 3.8)]:
    lp = make_limb_cyl(0.03, 0.03, 0.38, m_iron, 6, ship, (lx, ly, lz))
    lp.rotation_euler.x = math.radians(180)
    bulb = make_limb_cyl(0.16, 0.12, 0.32, m_lantern, 8, lp, (0, 0, 0.38))
    bpy.ops.object.light_add(type='POINT', location=(0, 0, 0))
    pt = bpy.context.active_object
    pt.data.energy = 240.0
    pt.data.color = (1.0, 0.62, 0.22)
    pt.parent = bulb
    pt.location = (0, 0, 0.16)
    lantern_pivots.append(lp)

p_root = make_limb_box(0.42, 0.26, 0.22, m_pants, ship, (0.0, 2.1, 2.95), 0.0)
p_belt = make_limb_box(0.45, 0.29, 0.1, m_boots, p_root, (0, 0, 0.08), 0.0)
make_limb_box(0.14, 0.06, 0.12, m_gold, p_belt, (0, 0.15, 0), 0.0)

p_torso = make_limb_box(0.48, 0.28, 0.62, m_coat, p_root, (0, 0, 0.1), 0.5)
make_limb_box(0.22, 0.3, 0.58, m_shirt, p_torso, (0, 0.02, 0.02), 0.5)

p_head = make_limb_box(0.3, 0.3, 0.32, m_skin, p_torso, (0, 0, 0.64), 0.5)
make_limb_box(0.32, 0.22, 0.2, m_beard, p_head, (0, 0.08, -0.02), 0.5)
make_limb_box(0.1, 0.05, 0.08, m_boots, p_head, (0.08, 0.15, 0.18), 0.5)

p_leg_l = make_limb_cyl(0.12, 0.09, 0.88, m_pants, 10, p_root, (0.14, 0, 0.0))
p_leg_l.rotation_euler.x = math.radians(180)
make_limb_box(0.16, 0.3, 0.22, m_boots, p_leg_l, (0, -0.05, 0.82), 0.5)

p_leg_r = make_limb_cyl(0.12, 0.08, 0.45, m_pants, 10, p_root, (-0.14, 0, 0.0))
p_leg_r.rotation_euler.x = math.radians(180)
make_limb_cyl(0.06, 0.03, 0.48, m_wood_mid, 8, p_leg_r, (0, 0, 0.45))

p_arm_u_l = make_limb_cyl(0.09, 0.075, 0.4, m_coat, 10, p_torso, (0.3, 0, 0.55))
p_arm_l_l = make_limb_cyl(0.075, 0.06, 0.38, m_coat, 10, p_arm_u_l, (0, 0, 0.4))
make_limb_box(0.1, 0.1, 0.12, m_skin, p_arm_l_l, (0, 0, 0.38), 0.5)

p_arm_u_r = make_limb_cyl(0.09, 0.075, 0.4, m_coat, 10, p_torso, (-0.3, 0, 0.55))
p_arm_l_r = make_limb_cyl(0.075, 0.06, 0.38, m_coat, 10, p_arm_u_r, (0, 0, 0.4))
p_hand_r = make_limb_box(0.1, 0.1, 0.12, m_skin, p_arm_l_r, (0, 0, 0.38), 0.5)

sword_grip = make_limb_cyl(0.028, 0.025, 0.24, m_wood_dark, 8, p_hand_r, (0, 0, 0.06))
sword_grip.rotation_euler.x = math.radians(-90)
make_limb_cyl(0.14, 0.04, 0.1, m_gold, 12, sword_grip, (0, 0, 0.2))
make_limb_box(0.28, 0.04, 0.03, m_gold, sword_grip, (0, 0, 0.22), 0.0)
make_limb_box(0.02, 0.07, 1.25, m_steel, sword_grip, (0, 0, 0.24), 0.5)

bpy.ops.mesh.primitive_cube_add(size=1.0, location=(0, 0, 0))
poop_hat = bpy.context.active_object

n_steps = 85
n_ring = 16
p_verts = []
p_faces = []

for i in range(n_steps):
    t = i / (n_steps - 1)
    turns = 3.6
    theta = t * turns * 2.0 * math.pi
    helix_r = 0.24 * ((1.0 - t) ** 1.15)
    cz = t * 0.42
    cx = helix_r * math.cos(theta)
    cy = helix_r * math.sin(theta)
    if t > 0.88:
        tip_t = (t - 0.88) / 0.12
        cz += tip_t * 0.08

    base_tube_r = 0.135 * (math.sin(math.pi * min(0.98, t + 0.08)) ** 0.35) * (1.0 - 0.68 * t)
    pinch = 1.0 + 0.09 * math.sin(t * 24.0) + 0.05 * math.cos(t * 41.0)
    tube_r = max(0.006, base_tube_r * pinch)

    tangent = Vector((-math.sin(theta), math.cos(theta), 0.35)).normalized()
    up_v = Vector((0, 0, 1))
    norm_v = tangent.cross(up_v).normalized()
    binorm_v = tangent.cross(norm_v).normalized()
    center = Vector((cx, cy, cz))

    for j in range(n_ring):
        phi = (j / n_ring) * 2.0 * math.pi
        lump = 1.0 + 0.08 * math.sin(phi * 3.0 + t * 15.0) + 0.05 * math.cos(phi * 5.0 - t * 22.0)
        rv = tube_r * lump
        pt = center + (norm_v * math.cos(phi) + binorm_v * math.sin(phi)) * rv
        p_verts.append((pt.x, pt.y, pt.z))

for i in range(n_steps - 1):
    b = i * n_ring
    t_idx = (i + 1) * n_ring
    for j in range(n_ring):
        nj = (j + 1) % n_ring
        p_faces.append([b + j, b + nj, t_idx + nj, t_idx + j])

bot_center_idx = len(p_verts)
p_verts.append((0.24, 0.0, 0.0))
for j in range(n_ring):
    nj = (j + 1) % n_ring
    p_faces.append([bot_center_idx, nj, j])

top_center_idx = len(p_verts)
p_verts.append((0.0, 0.0, 0.51))
last_ring = (n_steps - 1) * n_ring
for j in range(n_ring):
    nj = (j + 1) % n_ring
    p_faces.append([last_ring + j, last_ring + nj, top_center_idx])

poop_hat.data.clear_geometry()
poop_hat.data.from_pydata(p_verts, [], p_faces)
poop_hat.data.update()
bpy.ops.object.shade_smooth()
sub_mod = poop_hat.modifiers.new("Subdivision", 'SUBSURF')
sub_mod.levels = 2
sub_mod.render_levels = 2
poop_hat.data.materials.append(m_poop)
poop_hat.parent = p_head
poop_hat.location = (0, 0, 0.30)

flies = []
for fi in range(3):
    bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=1, radius=0.025, location=(0, 0, 0))
    fly = bpy.context.active_object
    fly.data.materials.append(m_boots)
    fly.parent = poop_hat
    flies.append(fly)

bpy.ops.mesh.primitive_cylinder_add(vertices=6, radius=0.02, depth=0.7, location=(0, 0, -25))
rain_drop = bpy.context.active_object
rain_drop.data.materials.append(m_rain)

bpy.ops.mesh.primitive_plane_add(size=60.0, location=(0, 0, 24))
rain_emitter = bpy.context.active_object
ps_rain_mod = rain_emitter.modifiers.new("ParticleSystem", 'PARTICLE_SYSTEM')
ps_rain = rain_emitter.particle_systems[0].settings
ps_rain.count = 7000
ps_rain.frame_start = -40
ps_rain.frame_end = 250
ps_rain.lifetime = 55
ps_rain.render_type = 'OBJECT'
ps_rain.instance_object = rain_drop
ps_rain.particle_size = 1.0
ps_rain.object_align_factor = (-3.5, -2.0, -26.0)
ps_rain.effector_weights.gravity = 1.5
rain_emitter.show_instancer_for_viewport = False
rain_emitter.show_instancer_for_render = False

bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=1, radius=0.07, location=(0, 0, -28))
snow_flake = bpy.context.active_object
snow_flake.data.materials.append(m_snow)

bpy.ops.mesh.primitive_plane_add(size=60.0, location=(0, 0, 23))
snow_emitter = bpy.context.active_object
ps_snow_mod = snow_emitter.modifiers.new("ParticleSystem", 'PARTICLE_SYSTEM')
ps_snow = snow_emitter.particle_systems[0].settings
ps_snow.count = 4500
ps_snow.frame_start = -80
ps_snow.frame_end = 250
ps_snow.lifetime = 140
ps_snow.render_type = 'OBJECT'
ps_snow.instance_object = snow_flake
ps_snow.particle_size = 0.9
ps_snow.size_random = 0.5
ps_snow.brownian_factor = 1.8
ps_snow.object_align_factor = (-1.5, -0.8, -5.5)
ps_snow.effector_weights.gravity = 0.25
snow_emitter.show_instancer_for_viewport = False
snow_emitter.show_instancer_for_render = False

bolt_parts = []
bx, by, bz = -7.0, 20.0, 26.0
for bi in range(10):
    nbx = bx + random.uniform(-1.8, 1.8)
    nby = by + random.uniform(-0.8, 0.8)
    nbz = bz - 2.6
    vec = Vector((nbx - bx, nby - by, nbz - bz))
    mid = Vector(((bx + nbx) * 0.5, (by + nby) * 0.5, (bz + nbz) * 0.5))
    bpy.ops.mesh.primitive_cylinder_add(vertices=6, radius=0.24 * (1.0 - bi * 0.06), depth=vec.length, location=mid)
    b_seg = bpy.context.active_object
    b_seg.rotation_euler = vec.to_track_quat('Z', 'Y').to_euler()
    b_seg.data.materials.append(m_bolt)
    bolt_parts.append(b_seg)
    bx, by, bz = nbx, nby, nbz

bpy.ops.object.select_all(action='DESELECT')
for bp in bolt_parts:
    bp.select_set(True)
bpy.context.view_layer.objects.active = bolt_parts[0]
bpy.ops.object.join()
bolt_obj = bpy.context.active_object

bpy.ops.object.light_add(type='SUN', location=(8, -8, 15), rotation=(0.6, 0.2, 0.5))
sun = bpy.context.active_object
sun.data.energy = 0.35
sun.data.color = (0.3, 0.45, 0.75)

bpy.ops.object.light_add(type='POINT', location=(-4, 12, 24))
flash_light = bpy.context.active_object
flash_light.data.color = (0.75, 0.88, 1.0)
flash_light.data.energy = 0.0

bpy.ops.object.camera_add(location=(0, 0, 0))
cam = bpy.context.active_object
cam.data.lens = 28.0
cam.parent = ship
bpy.context.scene.camera = cam

bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 250

flash_frames = {28, 29, 31, 82, 83, 85, 138, 139, 142, 195, 196, 198, 235, 236}

for f in range(1, 251):
    t = f / 250.0
    ship.location.y = -14.0 + t * 28.0
    ship.location.x = math.sin(f * 0.05) * 0.75
    ship.location.z = 0.2 + math.sin(f * 0.12) * 0.45 + math.cos(f * 0.07) * 0.22

    ship.rotation_euler.x = math.sin(f * 0.10) * 0.11 + math.cos(f * 0.17) * 0.04
    ship.rotation_euler.y = math.sin(f * 0.08) * 0.16 + math.cos(f * 0.13) * 0.05
    ship.rotation_euler.z = math.sin(f * 0.04) * 0.07
    ship.keyframe_insert(data_path="location", frame=f)
    ship.keyframe_insert(data_path="rotation_euler", frame=f)

    wheel.rotation_euler.y = math.sin(f * 0.09) * 2.8
    wheel.keyframe_insert(data_path="rotation_euler", frame=f)

    for idx, lp in enumerate(lantern_pivots):
        lp.rotation_euler.x = math.radians(180) + math.sin(f * 0.15 + idx) * 0.38
        lp.rotation_euler.y = math.cos(f * 0.12 + idx) * 0.48
        lp.keyframe_insert(data_path="rotation_euler", frame=f)

    for idx, sl in enumerate(sails):
        sl.scale.y = 1.0 + math.sin(f * 0.25 + idx) * 0.14
        sl.keyframe_insert(data_path="scale", frame=f)

    p_root.location.z = 2.95 + abs(math.sin(f * 0.22)) * 0.08
    p_root.location.x = math.sin(f * 0.11) * 0.18
    p_root.rotation_euler.z = math.sin(f * 0.15) * 0.35
    p_root.keyframe_insert(data_path="location", frame=f)
    p_root.keyframe_insert(data_path="rotation_euler", frame=f)

    p_torso.rotation_euler.x = 0.12 + math.sin(f * 0.22) * 0.15
    p_torso.rotation_euler.z = math.sin(f * 0.25) * 0.45
    p_torso.keyframe_insert(data_path="rotation_euler", frame=f)

    p_head.rotation_euler.x = math.sin(f * 0.20) * 0.15
    p_head.rotation_euler.z = -math.sin(f * 0.25) * 0.25
    p_head.keyframe_insert(data_path="rotation_euler", frame=f)

    poop_hat.rotation_euler.x = math.sin(f * 0.35) * 0.14
    poop_hat.rotation_euler.y = math.cos(f * 0.30) * 0.14
    poop_hat.keyframe_insert(data_path="rotation_euler", frame=f)

    for fi, fly in enumerate(flies):
        ang = f * 0.35 + fi * 2.1
        fly.location = (math.cos(ang) * 0.32, math.sin(ang) * 0.32, 0.25 + math.sin(f * 0.6 + fi) * 0.12)
        fly.keyframe_insert(data_path="location", frame=f)

    p_arm_u_l.rotation_euler = (0.6 + math.sin(f * 0.2) * 0.25, 0.55, math.cos(f * 0.2) * 0.2)
    p_arm_l_l.rotation_euler = (0.7 + math.cos(f * 0.25) * 0.2, 0.0, 0.0)
    p_arm_u_l.keyframe_insert(data_path="rotation_euler", frame=f)
    p_arm_l_l.keyframe_insert(data_path="rotation_euler", frame=f)

    slash_a = f * 0.32
    p_arm_u_r.rotation_euler = (
        1.25 + math.sin(slash_a) * 0.75,
        -0.45 + math.cos(slash_a * 0.7) * 0.65,
        math.sin(slash_a * 1.3) * 0.55
    )
    p_arm_l_r.rotation_euler = (
        0.55 + abs(math.cos(slash_a)) * 0.65,
        math.sin(slash_a) * 0.35,
        0.0
    )
    p_hand_r.rotation_euler = (
        math.sin(slash_a * 2.0) * 0.45,
        math.cos(slash_a * 1.5) * 0.45,
        math.sin(slash_a) * 0.6
    )
    p_arm_u_r.keyframe_insert(data_path="rotation_euler", frame=f)
    p_arm_l_r.keyframe_insert(data_path="rotation_euler", frame=f)
    p_hand_r.keyframe_insert(data_path="rotation_euler", frame=f)

    p_leg_l.rotation_euler.x = math.radians(180) + math.sin(f * 0.22) * 0.2
    p_leg_r.rotation_euler.x = math.radians(180) - math.sin(f * 0.22) * 0.2
    p_leg_l.keyframe_insert(data_path="rotation_euler", frame=f)
    p_leg_r.keyframe_insert(data_path="rotation_euler", frame=f)

    is_flash = f in flash_frames
    flash_light.data.energy = 850000.0 if is_flash else 0.0
    flash_light.data.keyframe_insert(data_path="energy", frame=f)
    bolt_obj.scale = (1.0, 1.0, 1.0) if is_flash else (0.0, 0.0, 0.0)
    bolt_obj.keyframe_insert(data_path="scale", frame=f)
    if bg_node:
        bg_node.inputs[1].default_value = 3.8 if is_flash else 0.35
        bg_node.inputs[1].keyframe_insert(data_path="default_value", frame=f)

    cam_shake = 0.04 * math.sin(f * 2.8) if is_flash else 0.0
    cam_pos = Vector((
        -1.6 + math.sin(f * 0.035) * 0.9,
        -0.4 + math.cos(f * 0.03) * 0.8,
        3.7 + math.sin(f * 0.05) * 0.25 + cam_shake
    ))
    target_pos = Vector((
        0.0 + math.sin(f * 0.06) * 0.15,
        2.1,
        3.65 + math.sin(f * 0.08) * 0.1
    ))
    look_dir = target_pos - cam_pos
    cam_rot = look_dir.to_track_quat('-Z', 'Y').to_euler('XYZ')
    cam_rot.y += math.sin(f * 0.08) * 0.05 + cam_shake

    cam.location = cam_pos
    cam.rotation_euler = cam_rot
    cam.keyframe_insert(data_path="location", frame=f)
    cam.keyframe_insert(data_path="rotation_euler", frame=f)

bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.update()