下記のような、一般的なfor文(すべてのキーフレームのデータを見て1つずつ処理するやり方では、数が多いとどう調整しても遅い。
複数のポーズオブジェクトの、何百ものボーンの、何千ものキーフレームをfor文で一括操作すると、1秒などかなり気になるレベルで処理が遅くなる。
obj = bpy.context.object
for fc in obj.animation_data.action.fcurves:
for ky in fc.keyframe_points:
ky
foreach_get/set
コレクションデータで利用することができる、for文より数百倍高速な手段。
ただし、すべてのアイテムに対して実行する一括設定であり、指定のアイテムだけ設定することができない。
obj = bpy.context.object
for fc in obj.animation_data.action.fcurves:
# データの入れ子となるダミーリスト。アイテムの個数分複製する。
base_crds = [False] * len(fc.keyframe_points)
fc.keyframe_points.foreach_get("select_control_point", base_crds) # 値を取得
fc.keyframe_points.foreach_set("select_control_point", base_crds) # 値を設定
# coのようなxとyで複数配列あるデータの場合は、その配列数分乗算する必要がある。
base_crds = [False] * len(fc.keyframe_points) * 2
fc.keyframe_points.foreach_get("co", base_crds) # 状態を取得
coのようなxとyで複数配列あるデータの場合は、ダミーリストは、その配列数分乗算する必要がある。
その場合、XYがすべてまぜこぜになるので、for文で偶数の値だけ設定(if i % 2 == 0)したりする必要があるが、for文を使うと結局遅くなってしまう。これの回避方法がわからない。
内蔵オペレーターを利用する
あまりきれいなやり方ではないが、正直これが一番早い。
キーの選択範囲は、各種標準のキー選択機能を利用してキーを選択し、キーの移動は標準の移動機能を実行する。
タイムラインの表示に依存するが、ボーン選択を一時的に変えたり、Fカーブのロックを活用することで、対象を限定することは可能。
サンプル
カレントフレーム・範囲・前方の3種類から選んで、対象のキーフレームを選択し、移動するサンプルコード。
エディター変更を利用しているため、コンソールエディターで下記のコードを実行した場合はクラッシュするので注意。
import bpy
l = ["CURRENT","RANGE","FORWARD"]
target_type = l[2]
range_frame_start = 25
range_frame_end = 50
delta = 100
sc = bpy.context.scene
# show keys
old_view_key = sc.show_keys_from_selected_only
sc.show_keys_from_selected_only = True
# 一時的にタイムラインエディターに変更する
old_area_type = bpy.context.area.type
bpy.context.area.type = 'DOPESHEET_EDITOR'
bpy.context.area.ui_type = 'TIMELINE'
# all delselect
bpy.ops.action.select_all(action="DESELECT")
# カレントフレーム
if target_type == "CURRENT":
bpy.ops.action.select_column(mode='CFRA')
# 指定の範囲
# 選択中のマーカーの間を選択を活用し、一時的にマーカーを作って選択し、一時マーカーを削除します
elif target_type == "RANGE":
old_mark_sel = []
for i in sc.timeline_markers:
if i.select:
old_mark_sel += [i]
i.select = False
item_start = sc.timeline_markers.new('LK_start', frame=range_frame_start)
item_start.select = True
item_end = sc.timeline_markers.new('LK_end', frame=range_frame_end)
item_end.select = True
bpy.ops.action.select_column(mode='MARKERS_BETWEEN') # マーカーの間を選択
sc.timeline_markers.remove(item_start)
sc.timeline_markers.remove(item_end)
for i in old_mark_sel:
i.select = True
# 現在のフレームの前方
elif target_type in {"FORWARD"}:
bpy.ops.action.select_leftright(mode='RIGHT', extend=False)
# 移動
bpy.ops.transform.transform(mode='TIME_TRANSLATE',value=(delta, 0, 0, 0))
# エディターを元に戻す
# Switch editor
bpy.context.area.type = old_area_type
# restore show keys
old_view_key = sc.show_keys_from_selected_only