my funeral week

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

MayaScripts_SmoothAnimCurve:アニメーションカーブのスムージング

こんにちは。
今回は小スクリプトの紹介です。


SmoothAnimCurve

過去、XSIというソフトウェアにはアニメーションカーブ(XSI上での呼称はファンクションカーブでしたか)の形状に対して様々なフィルターが用意されていました。私の記憶が正しければ・・・リサンプルやスムージング、ノイズや簡略化等があったように思います。Mayaにも多少は同じようなフィルターが用意されていますが、XSI程ではないと思います。

今回はその中でも、アニメーションカーブをスムージング処理するスクリプトを作成しましたので、共有致します。


インストール

以下のコードを適当なPythonファイルに保存してください。

# -*- coding:utf-8 -*-

import pymel.core as pm

VERSION = "1.0"
WINDOWNAME = "SmoothAnimCurve_Option"
AVENUM = 3
DEFAULTSMOOTHLEVEL = 1

def getAnimCurves ():
    names = pm.keyframe(q = True, sl = True, n = True)
    return [pm.nodetypes.AnimCurve(i) for i in names]
    
def getCalcLoopNum (keyframes):
    return len(keyframes) - (AVENUM - 1)
    
def getSmoothKeyframeValues (orgKeyframeValues):
    result = list()
    result.append(orgKeyframeValues[0]) # 最初のキー
    for i in range(0, getCalcLoopNum(orgKeyframeValues)):
        keyValues = orgKeyframeValues[i : i + AVENUM]
        average = sum(keyValues) / AVENUM
        result.append(average)
    result.append(orgKeyframeValues[-1]) # 最後のキー
    return result
    
def setSmoothKeyframeValues (targetAnimCurve, animation):
    for keyframe in animation:
        time = keyframe[0]
        value = keyframe[1]
        pm.setKeyframe(targetAnimCurve, t = time, v = value)

def getAnimation (animCurve, timeRange):
    return pm.keyframe(animCurve, q = True, t = timeRange, tc = True, vc = True)
   
def updateAnimationKeyValues (animation, keyValues):
    newAnimation = list()
    for i in range(0, len(animation)):
        time = animation[i][0]
        value = keyValues[i]
        newAnimation.append([time, value])
    return newAnimation
   
def smoothCurve (smoothLevel, frameRange):
   for animCurve in getAnimCurves():
        animation = getAnimation(animCurve, frameRange)
        smoothKeyValues = [v[1] for v in animation]
        for i in range(0, smoothLevel):
            smoothKeyValues = getSmoothKeyframeValues(smoothKeyValues)
        smoothAnimation = updateAnimationKeyValues(animation, smoothKeyValues)
        setSmoothKeyframeValues(animCurve, smoothAnimation)
   
def startSmoothing (smoothLevel = 0, frameRange = []):
    if not frameRange:
        minF = pm.playbackOptions(q = True, min = True)
        maxF = pm.playbackOptions(q = True, max = True)
        frameRange = [minF, maxF]
    
    if smoothLevel == 0:
        smoothLevel = DEFAULTSMOOTHLEVEL
    
    smoothCurve(smoothLevel, frameRange)

def deleteExistWindow (*arg):
    if pm.window(WINDOWNAME, q = True, ex = True):
        pm.deleteUI(WINDOWNAME)
   
def buildWindow ():
    deleteExistWindow()
    with pm.window(WINDOWNAME, mnb = False, mxb = False) as w:
        w.setWidthHeight([158,120])
        
        with pm.columnLayout(adj = True, rs = 4):
            pm.text(l = "Smoothing Option", bgc = [0.9,0.5,0.3])
        with pm.columnLayout(adj = True, co = ["both", 4], rs = 4):
            pm.separator(st = "in", h = 2)
            with pm.rowLayout(nc = 2):
                pm.text(l = "Level", w = 40)
                levelField = pm.intField(v = DEFAULTSMOOTHLEVEL, w = 104)
            with pm.rowLayout(nc = 4):
                pm.text(l = "Range", w = 40)
                minField = pm.intField(v = pm.playbackOptions(q = True, min = True), w = 40)
                pm.text(l = "~", w = 20)
                maxField = pm.intField(v = pm.playbackOptions(q = True, max = True), w = 40)
            pm.separator(st = "in", h = 2)
            with pm.rowLayout(nc = 2):
                mainCommand = lambda *arg: startSmoothing(
                    levelField.getValue(),
                    [minField.getValue(), maxField.getValue()]
                    )
                pm.button(l = "Accept", c = mainCommand, w = 71)
                pm.button(l = "Close", c = deleteExistWindow, w = 71)
    
def main ():
    if pm.getModifiers() == 5:
        buildWindow()
        return
    startSmoothing()


で、以下の2行をシェルフとかに

import SmoothAnimCurve
SmoothAnimCurve.main()


使い方

これをご覧ください。
ノイズにまみれたアニメーションカーブです。

f:id:garysfirearms108:20200322151139j:plain

このカーブにスムージング処理を施したいと思います。


まずは、
以下のようにスムーズにしたいアニメーションカーブを選択してください。

f:id:garysfirearms108:20200322151209j:plain

で、スクリプトを実行します。
実行すればする程、滑らかになっていきます。


あれほどガタガタだったカーブがこうなります。

f:id:garysfirearms108:20200322151051j:plain

複数のアニメーションカーブに対しても問題なく実行できると思いますが、処理速度は大幅に低下するかもしれません。

基本的な使い方は以上となります。
ちなみに範囲の最初と最後のキーフレームの値は変更されません。


オプション

Shift + Ctrl」を押しながら実行すると、以下のようなウィンドウが立ち上がります。

f:id:garysfirearms108:20200322151221j:plain

各項目について

  • Level
    • スムージング強度の値
  • Range
    • 処理を施すフレーム範囲
  • Accept
    • 上記の入力内容を基にスムージング処理を開始
  • Close
    • ウィンドウを閉じます。


余談

月並みですが、このスムージング処理は移動平均という計算方法を使って実現しており、スムージング強度は、この計算を複数回繰り返すことで表現しています。

よって、複数本の長尺のアニメーションカーブに対して実行すると思った以上に計算に時間が掛かるかもしれません。そういった状況下において実行する場合は事前にセーブしておくことを強くオススメします。なにかあっても責任は取れませんので悪しからず。

それと、利用方法に関して一言。Mayaの標準機能にもアニメーションカーブの簡略化という機能がありますが、それと違ってこのスクリプトはキーフレームの数を増減させることはありません。なので、移動平均という計算方法の仕様的にモーションキャプチャーのノイズ除去等、ベイク処理されたアニメーションに対しての利用を想定しています。

そんな感じです。よろしくお願い致します。