my funeral week

少しでも日々の生活に変化を。

MayaScripts_StaticAnimationRemover:フラットなキーフレームの削除

アニメーションを制作する上で、皆さんが気にしていることはなんでしょうか?
ポージング?タイミング?テンポの良さ?カメラワーク?キャラクター性?

私は、ゴミのようなキーフレームがないかどうか を気にしています。


こういうやつですよ。

キャプチャーデータの編集とか、外部リグに出したり戻したりを繰り返しながら編集するアニメーションに多く存在します。
キーフレーム自体がフルベイクされているのは全く問題ないのですけど、値の変動が一切ないアトリビュートにキーフレームが入っているのが許せません。
単純に不要なアニメーションカーブノードがシーンに含まれていることになりますし、思わぬ不具合に繋がる可能性もあるので、いつも見つけ次第削除しています。

とは言っても、いちいち全てに対応するのも 面倒 なので、これを作成しました。

StaticAnimationRemover

選択ノードのアニメーションのキーフレームに値の変動が見つからなければ、そのアニメーションをゴミとして削除します。


コード

# encoding:utf-8

import pymel.core as pm

VERSION = "1.1"

print ("-*- import {0}.{1} -*-".format(__name__,VERSION))

SAFEMODE = True
ROUND_NUM = 5

def __getAnimatedAttrs (target_node):
    """任意のノードにて、AnimCurveに接続されたAttrを返す"""
    # type:(pm.PyNode) -> list[pm.general.Attribute]
    
    animatable_attrs = target_node.listAnimatable()
    return [a for a in animatable_attrs if a.connections(t = "animCurve", d = False)]
    

def __isStaticKeyframe (keyframe_values):
    """与えられたキーフレーム値が最初の値から変化しないか"""
    # type:(list[float]) -> bool
    
    firstframe_value = round(keyframe_values[0], ROUND_NUM)
    return len([v for v in keyframe_values if firstframe_value != round(v, ROUND_NUM)]) == 0
    
    
def __isStaticAnim (target_attr):
    """与えられたAttrのKeyframeが最初の値から変化しないか"""
    # type:(pm.general.Attribute) -> bool
    
    animCurve = target_attr.connections(t = "animCurve")
    keyframe_values = pm.keyframe(animCurve, q = True, vc = True)
    return __isStaticKeyframe(keyframe_values)
    
    
def __getStaticAnimAttrs (target_node):
    """任意のノードからAnimateされていないAttrを取得"""
    # type:(pm.PyNode) -> list[pm.general.Attribute]
    
    return [a for a in __getAnimatedAttrs(target_node) if __isStaticAnim(a)]
    
    
def __showConfirmDialog (target_attrs, deleteAll):
    """削除対象Attrを表示するDialogを出す"""
    # type:(list[pm.general.Attribute], bool) -> bool
    
    if not SAFEMODE : return True
    
    msg = "\n".join([str(a.name()) for a in target_attrs])
    y_string = "Delete" if deleteAll else "Clear"
    dialog = pm.confirmDialog(t = "{0}StaticAnim".format(y_string), m = msg, b = [y_string,"Cancel"])
    
    return dialog == y_string


def __getTimerange ():
    """現在のTimerangeを返す"""
    # type:() -> list[float]
    
    return [
        pm.playbackOptions(q = True, min = True) + 1,
        pm.playbackOptions(q = True, max = True) - 1,
        ]


def __deleteAnim (target_attrs, deleteAll):
    """対象AttrのAnimを削除する"""
    # type:(list[pm.general.Attribute], bool) -> bool
    
    if not target_attrs : return False
    if not __showConfirmDialog(target_attrs, deleteAll) : return False
    
    if deleteAll:
        pm.cutKey(target_attrs)
        return True
    pm.cutKey(target_attrs, t = __getTimerange())
    return True
    
    
def __getAllStaticAnimAttr (target_nodes):
    """任意のノードからAnimateされていないAttrを全て取得"""
    # type:(list[pm.PyNode]) -> list[pm.general.Attribute]
    
    static_attrs = list()
    for n in target_nodes:
        static_attrs.extend(__getStaticAnimAttrs(n))
    return static_attrs
    
    
def main ():
    
    selection = pm.selected()
    isDeleteAll = pm.getModifiers() == 8# Alt押しながらで全削除
    result = __deleteAnim(__getAllStaticAnimAttr(selection), isDeleteAll)
    
    pm.headsUpMessage("-*- Remove Static Animations {0}. -*-".format("Done" if result else "Canceled"))

また面倒ごとが一個減った。


インストール

上記のコードを適当な名前でPythonファイルとして保存して、

C:\Users\ユーザー名\Documents\maya\scripts

に配置してください。

そして、以下のコードで実行します。

import StaticAnimationRemover
StaticAnimationRemover.main()


使い方

  1. ゴミを清掃したいノードを全て選択した状態で実行してください。
  2. 削除対象のアトリビュート名がダイアログで表示されます。
  3. Enterを押すと削除完了です。

ちなみにソースコード内の定数:「SAFEMODE」を「False」にするとダイアログは表示されなくなります。
また、「Altキー」を押しながら実行すると、AnimationCurveごと削除します。


余談

同じような条件のアトリビュートでも、場合によっては必要なものとして残しているものもありますが・・・経験上、ただのゴミであることが多いです。
リグを制作する際に、Animatableでないアトリビュートロック しておくと、このようなゴミアトリビュートが発生しにくいので、リグ制作の際は忘れずに。

以上です。