株式会社デジタル・フロンティア-Digital Frontier

Header

Main

  • TOP
  • DF TALK
  • MotionBuilderでScript!!便利なコンストレインの活用術!!(初級編)

MotionBuilderでScript!!便利なコンストレインの活用術!!(初級編)

2013/8/5

Tag: ,

皆さん始めまして、4月にTDチームに仲間入りをした山城と申します!
よろしくお願い致します。最近はカメラが楽しくて堪らない!カメラ中毒になりそうです!
カメラ女子なんてテーマでお話したいなと考えつつ、今回の気になるテーマは「MotionBuilder」
「カメラじゃないんかい!」と思った方…次回まで楽しみに待っていて下さい!


MotionBuilderって資料が少ないよね・・・

という事で、今回は皆さんお待ちかね!MotionBuilder(以下MB)についてお話していこうと思います!
私も四月からMBでスクリプトを書いていますが(今まで触ったことも無かったので四苦八苦)
日本語の文献は殆ど無く…大半が英文なのですが、英語でさえ少ない…困ります、いやとっても困ってます。
今回ははそんなMBでちょっとモーションとは余り関係の無いDamping(追従)の動きを作ろう!というテーマでやって行きたいと思います!
目指す動きはこんな感じ。

なんかCGっぽいですよね!面白そうですよね!でもこの動きを作るだけだと
楽しくないのでDampingの動きを自動生成するスクリプトの作成を題材としてお話していきます!

皆さんもご存知だと思いますが、MB内には複数のコンストレインが存在します。
今回の動きを再現するのに欠かせないのが、Relationコンストレインです。
いわゆる関数みたいなもので制御が出来るコンストレインなのですが、
こやつはスクリプトで制御しようとすると少し…いや、かなり厄介です。
私も散々苦しめられました。書いていると段々と発狂したくなります。
きっと他にも苦しめられている方がいらっしゃると思います…今回はそんなRelationとの対決方法も交えつつ進めて行きましょう!


Dampingの動きを作ってみよう!

※MotionBuilder2013を使用しております

まず最初にRelationコンストレインを作成します。
オペレータのリストの中にあるOtherという項目を開いてください。
その中にDamping(3D)という名前のFunctionBoxがあるかと思います。
今回はこのFunctionBoxを使おうと思います。

ちなみオペレータのリストの中には、他にも複数Dampingと名前が付くFunctionBoxが存在するのですが、
一個一個が地味に出来ることが違いますので、ユーザーガイドで確認してお好みの動きを再現してみて下さい。
DampingBoxを作成したら、ノードに対してそれぞれ対応するものを接続していくのですが、
今回の追従はTranslationとRotationの軸に対して行う為、Damping(3D)のPに対して
センダーのTranslationとRotationノードを繋ぎます。
追従スピードは好みですが、今回は50で設定します。

IntegerのFunctionBoxはNumberの中に入っています。
この作業を子ノードに対して一つ一つ行なっていきます。
「ええー何個やればいいのー?メンドクサイ」となってしまいますよね…
と、言うことでここからは、先ほど行なった作業をScriptで自動化しようと思います!


Dampingスクリプトの作成

事前にDampingを適用させたいモデルは親子関係を結んでおいて下さい。
一番上の親ノードだけを選択し、子ノードはスクリプト内で取得しましょう。
親子関係が結ばれていれば全部を選択する必要は無いので、親ノードから
子ノードを取得していきます。その方が選択するのは一つで済むし、順番にリストも作れるので
作成側としても使用側としても楽ちんです!
下記がスクリプトの内容です。

def FindChild(pModel):
    # 子ノードが存在するかを調べる。
    for Node in pModel.Children:
        ChildList.append(Node)
        FinedChild(Node)
    return ChildList


lModelList = FBModelList()
FBGetSelectedModels( lModelList ) # 現在選択されているモデルの取得

lRelation = FBConstraintRelation() # Relationを作ります。
lRelation.LongName = "Damping"
lRelation.Active = True

List = len(lModelList)
# 選択されたモデルが子ノードを持っているかを調べます。
ChildrenList = FinedChild(lModelList[0])
List = len(ChildrenList)
if List != 0:
    for i in range(List):   
        CreateDamping(lRelation, ChildrenList[i])

↑↑↑が呼んでいる関数↓↓↓

def Find_AnimationNode( pParent, pName ):
    # Boxが指定された名前のノードを持っているかを調べる。
    lResult = None
    for lNode in pParent.Nodes:
        if lNode.Name == pName:
            lResult = lNode
            break
    return lResult

def Create_Damping(pRel, pModel):
    # 今回はRot側だけのスクリプトを書きます。Translation側はRoltationと同じように記述してみて下さい。
    # Rotation Damp-------------------------------------------------------------------------
    # CreateFunctionBox()にはオペレータリストの項目名、項目名の中にあるFunctionBox名の順番で記述します。
    rDampingBox = pRel.CreateFunctionBox("Other", "Damping (3D)")
    rDampingBox.Name = "Damping(3D)-Rotation"    
    rDam_Fact  = Find_AnimationNode( rDampingBox.AnimationNodeInGet(), 'Damping Factor' )
    rDam_p  = Find_AnimationNode( rDampingBox.AnimationNodeInGet(), 'P' )
    rDam_Resu  = Find_AnimationNode( rDampingBox.AnimationNodeOutGet(), 'Result' )

    # Rotation Integer----------------------------------------------------------------------
    rInteger = pRel.CreateFunctionBox("Number", "Integer")
    rInte_a  = Find_AnimationNode( rInteger.AnimationNodeInGet(), 'a' )
    rInte_a.WriteData([50.0]) # 書き込みたい数値を指定。今回は50を指定します。
    rInte_Resu  = Find_AnimationNode( rInteger.AnimationNodeOutGet(), 'Result' )

    # Sender Box----------------------------------------------------------------------------
    # センダーを作成 SetAsSource()にセンダーにしたいモデルデータを渡します。
    lSender = pRel.SetAsSource( pModel )
    lSend_Rot  = Find_AnimationNode( lSender.AnimationNodeOutGet(), 'Rotation' )
    lSend_Tra  = Find_AnimationNode( lSender.AnimationNodeOutGet(), 'Translation' )

    # Recever Box---------------------------------------------------------------------------
    # レシーバーを作成 ConstrainObject()にレシーバーにしたいモデルデータを渡します。
    lRecever = pRel.ConstrainObject( pModel )
    lRece_Rot  = Find_AnimationNode( lRecever.AnimationNodeInGet(), 'Rotation' )
    lRece_Tra  = Find_AnimationNode( lRecever.AnimationNodeInGet(), 'Translation' )

    # ConnectNodes--------------------------------------------------------------------------
    Connect_Node(lSend_Rot, rDam_p)
    Connect_Node(rInte_Resu, rDam_Fact)
    Connect_Node(rDam_Resu, lRece_Rot)

def Connect_Node(lOutBox, lInBox):
    # ノード同士の接続。
    if lOutBox and lInBox:
        FBConnect( lOutBox, lInBox )

実際にスクリプトを使用する際にはSetBoxPosition()を使ってBoxの位置を指定することをお勧めします

整理しておかないと、ごちゃごちゃで何がなんだか分からない!ってなっちゃいます。
ちゃんと整頓して実行すると、見やすくて簡単にBoxが出来ちゃいます!!

こんな感じでDampingの作成を自動化することが出来るのですが、数値を変えたい時は一度Integerを作り直さなきゃいけないんです…不便ですよね。
WriteData()呼べば良いんじゃない?って思いますが、その方法だと書き換える事がどうも出来ないんです。
どうやら既に作られている物に対してWriteData()では書き込みが出来ないみたいです。
今回の場合は名前が分かっているので、取得も簡単に出来て、特に問題は無いのですが
「どれがセンダーでレシーバ?FunctionBoxはどれ?」っていうのを区別したい時や、名前が不特定のときなど、
Boxの分け方について下記のスクリプトで解説していきたいと思います。

lConstraints = FBSystem().Scene.Constraints
# 選択されているコンストレインを取得
lConstraint = [ lObj for lObj in FBSystem().Scene.Constraints if lObj and ( lObj.Selected == True ) ]


for lConst in lConstraint:
    # lConst.BoxesでRelation内に存在するBoxの取得
    for lBox in lConst.Boxes:
        RECEIVER = 0
        SENDER = 0
        # AnimationNodeInGet().Nodesで受け取る側のノードを取得
        lAnimationNodesIN = lBox.AnimationNodeInGet().Nodes
        for Node in lAnimationNodesIN:
            for i in range(Node.GetSrcCount()):
                # Boxのモデルを取得するには・・・
                # レシーバー側からセンダーのモデルを取得
                SenderModel = Node.GetSrc(i).GetOwner().Model
                # レシーバーのモデルを取得
                ReceiverModel = Node.GetOwner().Model
                RECEIVER = 1 
        # AnimationNodeOutGet().Nodesで受け渡す側のノードを取得    
        lAnimationNodesOut = lBox.AnimationNodeOutGet().Nodes
        for Node in lAnimationNodesOut
            for i in range(Node.GetDstCount()):
                # レシーバー側からセンダーのモデルを取得
                ReceiverModel = Node.GetDst(i).GetOwner().Model
                SENDER = 1
               

        if RECEIVER == 0 and SENDER == 1:
            print "SenderBox"
        if RECEIVER == 1 and SENDER == 0:
            print "ReceverBox"
        if RECEIVER == 1 and SENDER == 1:
            print "FunctionBox"

でもこれだけだと実は上手く分けられない…なぜかと言うと

レシーバはノードの接続状態解除する際にDeleteでBoxを消すのではなく
disconnectで解除した後にBoxを削除しないと、今までの情報を保持してしまうのです…

しかもBoxはGlobalなのにプログラム上ではLocal表示になったりと、
Relationはまだまだ謎めいた子なんです。もう少し良い子になって欲しいですね!

ちなみにBoxがGlobalかLocalかはBox.UseGlobalTransforms()から取得できます。
上記の分け方にプラスでGlobal、Localでの分岐などをいれ、センダーの厄介な動きを弾いていかないと
しっかりと分けるのが難しいんですよね。
Relationのスクリプトを書く際に注意するべき所がBox名での制御です。
センダー側では情報を保持している可能性がある為、実際のModelの名前とBoxの名前が異なる時があります。
その為上記で示したようにNode.GetSrc(i).GetOwner().ModelNode.GetOwner().Model
でモデル自体を取得して、制御をすると名前が違うなどの心配も無くなります!

最後に

もしセンダーとレシーバーとで分けたスクリプトを書きたい場合は、レシーバー側(AnimationNodeInGet()で取得できる方)
から操作するとセンダーとは違いレシーバーは今までの情報を保持していたりはしませんので
操作する際にはレシーバー側から行なう事をお勧めします!
ちょっと中途半端な感じになってしまいましたが、コンストレインのスクリプトを書く際に
少しでも何かの参考にして頂ければ幸いです。
最後までお付き合い頂きありがとう御座いました。
ではまた今度お会いいたしましょう♪


本当に本当の最後♪

日本語の文献が少ないMBですが・・・
弊社では数少ないMBの記事をいくつか紹介しております!
良かったらこちらもご覧下さい!

*モーションビルダースクリプトTips
https://dftalk.jp/cp-bin/wordpress/?p=4700

*MotionBuilder2012のGUIはQtで作られてるのにスクリプトでPyQtが使えないっておかしくね?
https://dftalk.jp/cp-bin/wordpress/?p=4018

*MotionBuilderでツールをより使ってもらうために
https://dftalk.jp/cp-bin/wordpress/?p=6539

*MotionBuilderをカスタマイズしよう
https://dftalk.jp/cp-bin/wordpress/?p=7339


※免責事項※
本記事内で公開している全ての手法の有用性、安全性について、当方は一切の保証を与えるものではありません。
これらの手法を実施したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください

Pocket

コメント

コメントはありません

コメントフォーム

コメントは承認制ですので、即時に反映されません。ご了承ください。

*