Blender: Generating Seamless Flag Wave Animation from Base Pose

 

Generating Seamless Flag Wave Animation from Base Pose
(7-Bone Setup) 
Script Blender

Creating natural-looking flag wave animations in Blender often requires manual keyframing across multiple bones, which can be time-consuming to loop perfectly.

This Python script solves that problem by generating a continuous, looping flag wave animation automatically. It works by taking the initial pose of a 7-bone armature at Frame 1 as a baseline profile, sampling that spatial wave profile across the timeline, and applying it smoothly to subsequent keyframes (Frames 25, 50, 75, and 100).

Key Features & How It Works:

  1. Base Pose Sampling: Reads the current Y Location of all 7 bones at Frame 1 to create the initial wave shape.

  2. Traveling Profile Algorithm: Shifts the baseline wave profile along the bone chain across the timeline to simulate wave movement.

  3. Perfect Loop Guarantee: Forces Frame 100 to match Frame 1 exactly, ensuring a 100% seamless looping animation (Frames 1–100).

  4. Automatic Bezier Interpolation: Sets keyframe handles to BEZIER with AUTO handles to provide smooth interpolation without manual graph editing.

Kode Script (Python - Blender API)




import bpy
import math

# ============================================================
# 7-BONE FLAG WAVE FROM FRAME-1 POSE
#
# Armature : Armature.7
# Bones    : Bone.1 - Bone.7
#
# Frame 1   = Reads actual base pose
# Frame 25  = Wave shift
# Frame 50  = Wave shift
# Frame 75  = Wave shift
# Frame 100 = EXACT Frame 1 (Perfect Loop)
#
# Affects Y Location only
# ============================================================

ARMATURE_NAME = "Armature.7"

BONE_NAMES = [
    "Bone.1",
    "Bone.2",
    "Bone.3",
    "Bone.4",
    "Bone.5",
    "Bone.6",
    "Bone.7",
]

KEY_FRAMES = [1, 25, 50, 75, 100]

START_FRAME = 1
END_FRAME = 100

# ============================================================
# GET ARMATURE
# ============================================================

arm = bpy.data.objects.get(ARMATURE_NAME)

if arm is None:
    raise Exception(f"Armature '{ARMATURE_NAME}' not found.")

# ============================================================
# READ FRAME 1 BASE POSE
# ============================================================

bpy.context.scene.frame_set(1)

BASE_Y = []

print("")
print("========================================")
print("FRAME 1 SOURCE POSE")
print("========================================")

for bone_name in BONE_NAMES:
    bone = arm.pose.bones.get(bone_name)

    if bone is None:
        raise Exception(f"Bone '{bone_name}' not found.")

    y = bone.location.y
    BASE_Y.append(y)

    print(f"{bone_name:7s} = {y:+.5f}")

# ============================================================
# PROFILE SAMPLER
#
# Reads 7-bone profile cyclically.
# Can sample positions between two bones.
# ============================================================

def sample_profile(values, position):
    count = len(values)
    position = position % count

    i0 = int(math.floor(position))
    i1 = (i0 + 1) % count
    frac = position - i0

    return values[i0] * (1.0 - frac) + values[i1] * frac

# ============================================================
# DELETE OLD Y ANIMATION
#
# Only targets Bone.1 - Bone.7 Y Location.
# ============================================================

if arm.animation_data and arm.animation_data.action:
    action = arm.animation_data.action

    for fc in list(action.fcurves):
        if fc.array_index != 1:
            continue

        for bone_name in BONE_NAMES:
            target = f'pose.bones["{bone_name}"].location'

            if fc.data_path == target:
                action.fcurves.remove(fc)
                break

# ============================================================
# CREATE WAVE
# ============================================================

BONE_COUNT = len(BONE_NAMES)

for frame in KEY_FRAMES:
    bpy.context.scene.frame_set(frame)

    # Normalized timeline: frame 1 = 0, frame 100 = 1
    t = (frame - START_FRAME) / (END_FRAME - START_FRAME)

    # Profile travels one complete cycle
    shift = t * BONE_COUNT

    for bone_index, bone_name in enumerate(BONE_NAMES):
        bone = arm.pose.bones.get(bone_name)

        if bone is None:
            continue

        # Traveling Profile: "-" moves wave from Bone.1 to Bone.7
        # Change "-" to "+" for reverse direction
        source_position = bone_index - shift
        y = sample_profile(BASE_Y, source_position)

        # APPLY KEYFRAME
        bone.location.y = y
        bone.keyframe_insert(data_path="location", index=1, frame=frame)

# ============================================================
# FORCE FRAME 100 = EXACT FRAME 1
# ============================================================

bpy.context.scene.frame_set(100)

for bone_name, original_y in zip(BONE_NAMES, BASE_Y):
    bone = arm.pose.bones.get(bone_name)

    if bone is None:
        continue

    bone.location.y = original_y
    bone.keyframe_insert(data_path="location", index=1, frame=100)

# ============================================================
# SMOOTH TEMPORAL INTERPOLATION
# ============================================================

if arm.animation_data and arm.animation_data.action:
    action = arm.animation_data.action

    for fc in action.fcurves:
        if fc.array_index != 1:
            continue

        valid = False
        for bone_name in BONE_NAMES:
            if fc.data_path == f'pose.bones["{bone_name}"].location':
                valid = True
                break

        if not valid:
            continue

        for kp in fc.keyframe_points:
            kp.interpolation = 'BEZIER'
            kp.handle_left_type = 'AUTO'
            kp.handle_right_type = 'AUTO'

# ============================================================
# TIMELINE SETUP
# ============================================================

bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 100
bpy.context.scene.frame_set(1)

print("")
print("========================================")
print("7-BONE FLAG WAVE CREATED")
print("========================================")
print("Frame 1   = source pose")
print("Frame 25  = traveling wave")
print("Frame 50  = traveling wave")
print("Frame 75  = traveling wave")
print("Frame 100 = exact Frame 1")
print("========================================")

TRANSLATE this Page

Posting Komentar

0 Komentar