my funeral week

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

MayaScripts_FollowCamCreator : ノードを追従するカメラの生成

2022/11/26 Update

こんにちは。

さて、今回もアニメーション制作時の面倒ごとを一つ片づける為の小スクリプトを置いておきます。


FollowCamCreator

アニメーション制作中、キャラクターは 常にカメラに捉えておきたいものです
ただ、走りや大ジャンプ、大きな移動値を持つアニメーションの制作時に、
FIXのカメラではキャラクターがカメラの外に行ってしまいます。

他にも、手足の指のアニメーション制作時なんかは、常に手足をカメラ内に押さえて欲しいでしょう。

いちいちカメラを動かすのも 面倒 なので、今日からはコレを使いましょう。

選択した対象ノードに対して追従するカメラをボタン一つで作成できます。
ちょっとしたオプションもあるよ。


インストール

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

上記の場所に以下のコードをPythonFileとして、保存してください。


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

import pymel.core as pm

VERSION = "1.2"

print ("-*- import FollowCamCreator.{0} -*-".format(VERSION))

WINDOWNAME = "FollowCam"
WINDOWSIZE = [150,196]
TRANSLATE_ATTR = ["tx","ty","tz"]
SWITCH_CAMERA = True

def __bakeAnim (target_node, target_attribute):
    # type:(pm.PyNode, list[str]) -> None
    
    framerange = [
        pm.playbackOptions(q = True, min = True),
        pm.playbackOptions(q = True, max = True),
        ]
    
    attributes = [target_node.attr(a) for a in target_attribute]
    
    pm.refresh(su = True)
    pm.bakeResults(attributes, t = framerange, sm = True, pok = True)
    pm.refresh(su = False)

def __setPointContraints (src_node, dst_node, target_attribute):
    # type:(pm.PyNode, pm.PyNode, list[str]) -> list[pm.PyNode]
    
    skip_attribute = list()
    
    for a in TRANSLATE_ATTR:
        if a in target_attribute : continue
        skip_attribute.append(a.replace("t",""))
    
    return pm.pointConstraint(src_node, dst_node, skip = skip_attribute, mo = False)
    
def __getActiveModelEditor ():
    # type:() -> pm.uitypes.ModelEditor
    
    for e in pm.lsUI(ed = True):
        if type(e) == pm.uitypes.ModelEditor and e.getActiveView():
            return e
    
    return None 
            

def __createFollowCam (target_attribute, withRotation, withBake):
    # type:(list[str], bool, bool) -> list[pm.PyNode]
    
    selection = pm.selected()
    if not selection or (not target_attribute and not withRotation):
        print ("\nCanceled.")
        return None
    
    __deleteExitWindow()
    
    src_node = selection[0]
    name = "FollowCam_{0}".format(src_node.name(stripNamespace = True))
    
    print ("\nsrc_node:\t{0}\nattribute:\t{1}\nrotation:\t{2}\nanim_bake:\t{3}".format(src_node.name(), target_attribute, withRotation, withBake))
    
    cam_root = pm.spaceLocator(n = name)
    cam_nodes = pm.camera(name = name + "_CAM")
    cam_transform = cam_nodes[0]
    cam_transform.setParent(cam_root)
    
    if withRotation:
        pm.orientConstraint(src_node, cam_root)
    
    constraint_nodes = __setPointContraints(src_node, cam_root, target_attribute)
    
    if withBake:
        __bakeAnim(cam_root, target_attribute)
        pm.delete(constraint_nodes)
    
    if SWITCH_CAMERA:
        active_viewport = __getActiveModelEditor()
        active_viewport.setCamera(cam_nodes[1])
    
    result = [cam_root] + cam_nodes
    
    pm.select(result)
    pm.headsUpMessage("-*- FollowCamCreate done. : {0} -*-".format(cam_root))
    
    return result
    

def __getCheckedAttr (checkboxes):
    # type:(list[pm.uitypes.CheckBox]) -> list[str]
    
    attributes = list()
    
    for i in list(range(0, len(TRANSLATE_ATTR))):
        if checkboxes[i].getValue():
            attributes.append(TRANSLATE_ATTR[i])
    
    return attributes
    
    
def __deleteExitWindow (*arg):

    if pm.window(WINDOWNAME, q = True, ex = True):
        pm.deleteUI(WINDOWNAME)


def __checkAll (checkboxes) :
    # type:(list[pm.uitypes.CheckBox]) -> None
    
    if False not in [c.getValue() for c in checkboxes]:
        checkboxes[0].setValue(False)
        checkboxes[1].setValue(False)
        return
    
    for c in checkboxes:
        c.setValue(True)
    
    

def __showUI ():
    # type:() -> None
    
    __deleteExitWindow()

    with pm.window(WINDOWNAME, mnb = False, mxb = False, s = False) as w:

        w.setWidthHeight(WINDOWSIZE)

        with pm.columnLayout(adj = True, rs = 5):

            pm.text(l = "Select Follow Axis", bgc = [0.9,0.5,0.3])

        with pm.columnLayout(adj = True, rs = 5, co = ["both", 10]):
            
            pm.separator(h = 6) # ---------------------------------------

            with pm.rowLayout(nc = 4):
                
                attr_checkboxes = [
                    pm.checkBox(l = "X", w = 48),
                    pm.checkBox(l = "Y", w = 48),
                    pm.checkBox(l = "Z", v = True)
                ]
            
            check_all_cmd = lambda *arg : __checkAll(attr_checkboxes)
            pm.button(l = "Check All", h = 17, c = check_all_cmd)
            
            pm.separator(h = 6) # ---------------------------------------

            wr = pm.checkBox(l = "With Rotation", v = False)
            wb = pm.checkBox(l = "With AnimBake", v = False)

            pm.separator(h = 6) # ---------------------------------------

            with pm.rowLayout(nc = 2, h = 36) as r:

                r.rowAttach([(1, "both", 0), (2, "both", 0)])
                
                main_cmd = lambda *arg : __createFollowCam(__getCheckedAttr(attr_checkboxes), wr.getValue(), wb.getValue())
                pm.button(l = "OK", w = 61, c = main_cmd)
                pm.button(l = "Cancel", w = 61, c = __deleteExitWindow)



def main () :

    if pm.getModifiers() == 5:
        __showUI()
        return
    __createFollowCam(TRANSLATE_ATTR, False, False)



その後、MayaのシェルフやHotKeyに以下の2行のスクリプトを保存してください。

import FollowCamCreator
FollowCamCreator.main()


使い方

適当なノードを選択した状態で実行 すると、MayaのPointConstraintにて「選択された対象を追従するカメラノード」を生成します。
生成後はアクティブなビューポートのカメラを生成したカメラに切り替えます。

それと、「 Shift + Ctrl 」 を押した状態で実行すると、下図のようなオプション用のUIが立ち上がります。

  • オプション項目
    • X~Y のチェックボックス
      • 追従する移動軸を選択できます。
    • Check All
      • 上記3軸すべてにチェックを入れます。
      • もう一度押すと、Z軸にのみチェックした状態に戻ります。
    • With Rotation
      • 回転にも追従するようになります。
    • With AnimBake
      • チェックを入れた移動軸に追従するようにカメラの親ノードに対してアニメーションのベイク処理を行います。
      • チェックを外している場合は、コンストレイントのみ行います。
    • OK ボタン
      • カメラを作成します。
    • Cancel ボタン
      • ウィンドウを閉じます。


余談

こういうちょっとしたスクリプトのUIを作るときも、なるべくレイアウトをキレイに整えたいんだけど・・・、MayaのデフォルトのUI作成コマンドだけでやると、正直むちゃくちゃめんどくさい。思った通りのマージンやサイズにならねえ。

やっぱりQtDesignerとか使った方がいいんですかね~。
宗教上の理由であんまり使いたくないから、普段これよりも複雑なUI作るときは、MayaのFormLayoutをある程度簡単に使えるようにした自前のスクリプトで抵抗しています。
とは言え、Qtはレイアウトだけじゃなく、ボタンからのコマンドとかユーザーからのアクションに対しても柔軟に対応できたりと、勉強するだけのメリットがあるのはワカル。まぁいつかブログで備忘録紹介できる程度には学習したいと思います。

それでは。