Automate Blender Animation with Python and CSV Data

Automate Blender Animation with Python and CSV Data

手動のキーフレーム付けは、少数のショットなら問題ありませんが、同じアニメーションを何度も行う必要があると、すぐに作業が反復的になります。グラフエディターでハンドルをドラッグしている時間は、もっと面白いことに使えるはずです。

Blender での Python スクリプトは、特定の問題を解決します。反復的で、データに基づいて動き、かつ複数のオブジェクトやショットにわたって確実に再現する必要があるアニメーションは、手作業で作成すべきではありません。このチュートリアルでは、その正確なやり方をお見せします。

この記事の最後には、CSV ファイルからカメラ位置データを読み取り、Blender 上で手動のキーフレームを一切使わずに完全なアニメーションを生成する動作するスクリプトが手に入ります。このスクリプトを任意の制作パイプラインに投入し、CSV を差し替えるだけで、数秒で新しいアニメーションを得られます。

1. アニメーションデータの構造

コードを書く前に、Blender が内部で使っているデータ構造を理解する必要があります。

階層は次のように動作します。Object(2D または 3D)がアニメーションデータを保持し、そのアニメーションデータが Action を参照し、Action には F-Curve のコレクションが含まれます。そして各 F-Curve にはキー フレームポイントのシーケンスが入っています。

各 F-Curve はちょうど 1 つのチャンネルに対応します。チャンネルは、次の 2 つで識別されます。data_path("location" や "rotation_euler" のような文字列)と、array_index(どの軸かを示す整数。0 は X、1 は Y、2 は Z です)。その F-Curve 上の各キーフレームポイントは座標ペアで、フレーム番号と値の組になっています。

把握しておくべき関連する Python オブジェクトは、bpy.data.actionsaction.fcurvesfcurve.keyframe_points、そして各 F-Curve の data_patharray_index 属性です。

スタジオ向けの実用的な注意点として、Action には明示的に名前を付けるのが常に良い考えです。Blender はデフォルトで匿名の Action を作成しますが、それらはオブジェクトが複製されたときや、ファイルがクリーンアップされたときに消えてしまいます。名前を付けることで、スクリプトから参照するための安定した対象が得られ、また問題の追跡が必要になった際に Action Editor で識別できるようになります。

2. Python でキーフレームを挿入する

Python でキーフレームを挿入する方法は 2 つあり、どちらを選ぶかはパフォーマンスに関わります。

1 つ目の方法は object.keyframe_insert(data_path, index, frame) です。これはビューポートで I を押したときに起きることを反映します。ドライバーや制約を尊重し、シーンを正しく評価し、小数のキーフレームしか扱わない場合や、オブジェクトが他のシーン要素に依存している場合に最も安全な選択肢です。

import bpy

obj = bpy.data.objects["Cube"]

for frame in range(0, 60, 5):
    obj.location.x = frame * 0.1
    obj.keyframe_insert(data_path="location", index=0, frame=frame)

index=0 引数は、X 軸のみをキーフレーム化するよう Blender に指示します。Y は 1、Z は 2 を使います。index を省略すると 3 軸すべてがキーフレーム化されるため、望まないことが多いでしょう。

この方法でよくあるミスは、keyframe_insert を呼ぶ前に値を設定し忘れることです。この関数は、現在設定されているプロパティの値を記録します。値を更新せずに呼び出すと、すべてのキーフレームが同じ値になり、アニメーションは平面的になってしまいます。

2 つ目の方法は、F-Curve に直接ポイントを挿入することです。

fcurve.keyframe_points.insert(frame, value)

この方法は UI ロジックの層を完全にバイパスするため、何百、何千というキーフレームを挿入する場合に大幅に高速です。外部ソースからデータ駆動のアニメーションを組み立てていて、速度が重要なときに使ってください。トレードオフとして、制約やドライバーの評価はトリガーされません。そのため、オブジェクトに更新が必要な依存関係がある場合は keyframe_insert を使ってください。

3. F-Curve を読み取り、修正する

キーフレームが作成されたら、補間とタイミングを正確に制御するために、基になる F-Curve を直接アクセスして変更できます。

キーフレームが挿入された後に F-Curve にアクセスするには:

action = obj.animation_data.action
fcurve = action.fcurves.find("location", index=0)

# OR

slot = action.slots[0]
channelbag = action.layers[0].strips[0].channelbag(slot)

for fcurve in channelbag.fcurves:
    # ... iterate over every fcurve
  • Action は、すべてのアニメーションデータを保持するトップレベルのコンテナです。
  • Slot は、アクションを特定のオブジェクトに結び付け、1 つの Action で複数のオブジェクトをアニメートできるようにします。
  • Layer は、ビデオ編集ソフトのトラックのように、他のレイヤーとブレンドできるアニメーションレイヤーです。
  • Strip は、タイムライン上のクリップに似た、レイヤー内の時間範囲で切られたクリップです。
  • Channelbag は、Strip 内の特定の Slot に属するすべての F-Curve をグループ化します。
  • F-curve は、(例:X 位置のように)単一のプロパティチャンネルを、キー フレームを使って時間にわたってアニメートします。
  • Keyframe point は、F-curve 上の単一のキーフレームであり、次のキーまでの移動方法を制御する補間モードが設定されています。

キーフレーム値を読み取る、または変更するには:

for kp in fcurve.keyframe_points:
    print(kp.co)
    kp.co.y *= 2

fcurve.update()

Blender の F-curve 上のすべてのキーフレームポイントを反復し、各キーフレームの(フレーム、値)の座標ペアを出力し、値の成分を 2 倍にしてから、変更を反映するためにカーブを更新します。

fcurve.update() の呼び出しは必須です。Blender は内部的にカーブデータをキャッシュしており、この呼び出しを省略すると、変更が反映されなかったり、レンダリング中に結果が不整合になったりする可能性があります。

補間について、制作で最もよく使う 3 つのモードは CONSTANTLINEARBEZIER です。CONSTANT は次のキーフレームまで値を保持し、カットアウト アニメーションや離散的な状態を持つものに役立ちます。LINEAR は機械的で均一な動きを作り、カメラリグ、UI のアニメーション、プロシージャル効果などにうまく機能します。BEZIER は有機的でイーズされた動きを生成し、ほとんどのアニメーション作業のデフォルトです。

カーブ内のすべてのキーフレームポイントに補間を設定するには:

for kp in fcurve.keyframe_points:
    kp.interpolation = "LINEAR"

fcurve.update()

4. CSV からデータ駆動のアニメーションを構築する

基本が分かったところで、制作でも使える、より完全な例を扱いましょう。シナリオは、カメラのフライスルーです。このとき位置データは、測量アプリ、ゲームエンジン、プリビズツール、あるいはディレクター/クライアントが作ったスプレッドシートなど、別のツールからエクスポートされたものだとします。

このチュートリアルの CSV 形式はシンプルです。1 行目にヘッダーがあり、その後に 4 列のデータ行が続きます。framexyz です。

frame,x,y,z
0,0.0,0.0,5.0
10,1.5,0.2,4.8
20,3.1,0.5,4.5
30,4.8,0.9,4.1

次に、CSV ファイルで定義されたパスに沿ってカメラをアニメートする完全なスクリプトです。

import bpy
import csv

obj = bpy.data.objects["Camera"]
scene = bpy.context.scene

obj.animation_data_clear()

with open("camera_path.csv", newline="") as f:
    reader = csv.reader(f)
    next(reader)
    for row in reader:
        frame = int(row[0])
        x, y, z = float(row[1]), float(row[2]), float(row[3])
        scene.frame_set(frame)
        obj.location = (x, y, z)
        obj.keyframe_insert(data_path="location", frame=frame)

action = obj.animation_data.action
action.name = "CAM_flythrough_v01"

slot = action.slots[0]
channelbag = action.layers[0].strips[0].channelbag(slot)

for fcurve in channelbag.fcurves:
    for kp in fcurve.keyframe_points:
        kp.interpolation = "LINEAR"
    fcurve.update()

print("Done. Keyframes inserted and interpolation set.")
  • まず Camera オブジェクトと現在のシーンを取得し、そこから既存のアニメーションデータをクリアします。
  • 次に camera_path.csv ファイルを開き、各行(フレーム番号+ X/Y/Z 位置)を読み取ります。frame_set で対応するフレームへ移動し、カメラのロケーションを設定して、ロケーションのキーフレームを挿入します。
  • 直前に作成されたアニメーションアクションを取得して、名前を "CAM_flythrough_v01" に設定します。
  • 補間を線形にするために、アニメーションデータ構造(slots → layers → strips → channelbag)をたどって各 F-curve に到達し、すべてのキーフレームの補間を LINEAR に設定します。これにより、カメラはポイント間で一定速度で移動します。この単純な例では slot/layer/strip/channelbag が 1 つしかないので分かりやすいですが、それ以外の場合は再帰的に処理を巡回する必要があります。
  • 最後に、各カーブに対して fcurve.update() を呼び出し、最後の確認メッセージを出力します。

スクリプトを実際に試してみてください。これは ブログチュートリアル用のリポジトリ内で Github で公開されています

結論

ここまでで、表形式データとして表現できる任意のアニメーションについて、手動のキーフレーム付けを置き換える再現可能なパイプライン手順が手に入りました。

同じパターンは、オブジェクトの可視性、マテリアルのプロパティ、ライトの強度、カメラの焦点距離など、Blender でキーフレーム化できるあらゆる項目にも当てはまります。CSV、JSON ファイル、あるいは任意の構造化された出力を作り出せるデータソースなら、アニメーターがタイムラインに触れることなく、直接 Blender のアニメーションに投入できます。

そしてこのパターンがスタジオにとって本当に価値があるのは、外部データが流入し、Python がそれを一貫した補間のキーフレームへ変換し、出力が決定論的で、バージョン管理や制御ができるからです。カメラパスを変更する必要があるときは、シーンを再生成するだけで済みます。タイミングを 40 ショットにわたってずらす必要があるときは、スクリプトを更新します。もはや、その作業を手作業で行う必要はありません。