参考4-1: スクリプトによるモーションブレンド
スクリプトによるモーション合成
モーション合成用のプログラムが仕込まれたテンプレートファイルをダウンロードしてください.
※ NLA Editorの方がインタラクティブに動作し,直観的なので基本はこちらを使えば良いです.
※ スクリプトでもキーフレーム情報を読み取って同様のことができることを確認する検証実験です.
初めにモーションを読み込む
初めに大元となるモーションを読み込んでください.
duplicate_armature.pyの実行
前回のプログラムと同じです.
duplicate_armature.py
import bpy
from mathutils import Vector
import numpy as np
def duplicate_armature(src_armature_name="Armature", dst_armature_name="DstArmature",
with_mesh=False, translation=Vector((1.0, 0.0, 0.0))):
armature = bpy.data.objects[src_armature_name]
armature.select_set(True)
if with_mesh:
for child in armature.children:
child.select_set(True)
bpy.ops.object.duplicate()
selected_objects = bpy.context.selected_objects
for object in selected_objects:
if object.type == "ARMATURE":
object.name = dst_armature_name
object.location += translation
src_armature_name = "Armature"
dst_armature_name = "DstArmature"
with_mesh = False
translation = Vector((1.0, 0.0, 0.0))
duplicate_armature(src_armature_name=src_armature_name, dst_armature_name=dst_armature_name,
with_mesh=with_mesh, translation=translation)
DstArmatureが出来上がったら,Armatureを分かりやすい名前にリネームしましょう.
- 例) Armature -> Walk
各種合成用のモーションの読み込み
モーションデータを読み込むと,Armatureとして読み込まれますので適宜リネームします.
- 例) Armature -> Run
合成用のキーフレーム位置合わせ
- Object Modeで対象のモデルを選択
- Pose Modeに変更
- Aキーを押して関節を全選択
- Summaryモードにしてキーフレーム編集する
二つのモーション合成は,プログラム側で実行するので, ここでは横に移動してタイミングを調整するのが主な編集になります.
linear_blend.pyの実行
スクリプトを linear_blend.py に切り替えて編集します.
import bpy
from mathutils import Vector
def get_pose(bones, frame):
bpy.context.scene.frame_set(frame)
bpy.context.view_layer.update()
pose = []
for i, bone in enumerate(bones):
location = bone.location.copy()
scale = bone.scale.copy()
rotation_quaternion = bone.rotation_quaternion.copy()
pose.append([location, scale, rotation_quaternion])
return pose
def get_offset_pose(A_poses_switch, B_poses_switch, B_bose):
A_loc = A_poses_switch[0][0]
B_loc_offset = B_bose[0][0] - B_poses_switch[0][0]
B_loc = A_loc + B_loc_offset
B_bose[0][0] = B_loc
def set_pose(bones, pose, frame):
bpy.context.scene.frame_set(frame)
for i, bone in enumerate(bones):
location, scale, rotation_quaternion = pose[i]
if i == 0:
print(f"Set: {frame}: {location}")
bone.location = location
bone.scale = scale
bone.rotation_quaternion = rotation_quaternion
bone.keyframe_insert(data_path="location", frame=frame, group=bone.name)
bone.keyframe_insert(data_path="scale", frame=frame)
bone.keyframe_insert(data_path="rotation_quaternion", frame=frame)
bpy.context.view_layer.update()
def blend_pose(A_pose, B_pose, t):
pose = []
for i, (A_pose_i, B_pose_i) in enumerate(zip(A_pose, B_pose)):
location = A_pose_i[0].lerp(B_pose_i[0], t)
scale = A_pose_i[1].lerp(B_pose_i[1], t)
rotation_quaternion = A_pose_i[2].slerp(B_pose_i[2], t)
pose.append([location, scale, rotation_quaternion])
return pose
def linear_blend(A_armature_name, B_armature_name, dst_armature_name,
A_start, A_end, B_start, B_end,
offset_mode=False):
A_armature = bpy.data.objects[A_armature_name]
B_armature = bpy.data.objects[B_armature_name]
dst_armature = bpy.data.objects[dst_armature_name]
A_bones = A_armature.pose.bones
B_bones = B_armature.pose.bones
dst_bones = dst_armature.pose.bones
for frame in range(A_start, B_start):
A_pose = get_pose(A_bones, frame)
set_pose(dst_bones, A_pose, frame)
A_pose_switch = get_pose(A_bones, B_start)
B_pose_switch = get_pose(B_bones, B_start)
for frame in range(B_start, A_end):
t = (frame - B_start) / (A_end - B_start)
print(f"{frame}: {t}")
A_pose = get_pose(A_bones, frame)
B_pose = get_pose(B_bones, frame)
if offset_mode:
get_offset_pose(A_pose_switch, B_pose_switch, B_pose)
pose = blend_pose(A_pose, B_pose, t)
set_pose(dst_bones, pose, frame)
for frame in range(A_end, B_end+1):
B_pose = get_pose(B_bones, frame)
if offset_mode:
get_offset_pose(A_pose_switch, B_pose_switch, B_pose)
set_pose(dst_bones, B_pose, frame)
def step_blend(A_armature_name, B_armature_name, dst_armature_name,
A_start, B_start, B_end):
linear_blend(A_armature_name, B_armature_name, dst_armature_name,
A_start, B_start, B_start, B_end)
## Work Area
linear_blend("Idle", "Walk", "DstArmature", A_start=1, A_end=20, B_start=10, B_end=40)
step_blend("Idle", "Walk", "DstArmature", A_start=1, B_start=20, B_end=40)
ここで,いじるのは,linear_blendとstep_blendの中身です.
A_armature_nameとB_armature_nameのモーションを合成して dst_armature_nameにセットします.
例えば,
の場合,以下の図のように,A_start, A_end, B_start, B_endでモーションをオーバーラップさせて合成します.
合成を簡単にするため, AとBのポーズはタイムライン上で表示されているものをそのまま合成します.
合成を調整したい場合は,合成用のAとBの各モーションのキーフレームのタイミングを事前に合わせておく必要があります.
step_blend
step_blendでは,モーションを補間合成せずに,B_startの時点で急激に切り替えます.