<< TOP

Maya; Python 入門 ; Jointサイズ変更ツールを作る

Maya; Python Startup ; Make a Set Joint Size tool
(Used Maya 2012, 2014, 2015)
    プロローグ; Prologue
 Maya で、単純な反復作業がウザくてちょっとした便利ツールがあれば
作業効率がグンっと上がるのにな~、とか
プログラマーに頼む程では無いし、自分がここさえ我慢すればいいか~
などという時、自分でちょっとツールが作れたら良いですよね。

 という訳で、今回は、自分で作る Pythonツール として、
目標は 『選択した階層下の全てのJointサイズを変更する GUIを持ったツール』
にしてみます。

 超ご丁寧な説明付きチュートリアルですので、一気に Python 覚えてしまいましょう。

あ、完成したツールも下に置いておきますね。
 
 なお、このサイトで掲載している事例の決まり事ですが、
使用に関しては自己責任でよろしくお願い申し上げます。
(Maya2012、2014、2015で動作確認しています)

It's about How to make a Python tool in Maya.
The objective is "A GUI tool to set all joints size from selected ".
But , please use it as your own risk.

> 構想
> jointのサイズ設定 と 選別処理
> PythonでGUIを作る
> 完成へ

 > ri_set_joint_size.py
Windows7 Professional 64Bit、Intel Core i7-3930K (3.20GHz,6コア/12スレッド)
メモリ16GB、NVIDIA GeForce GTX560 Ti 2GB (メモリ2GB)








 構想
     目標は 『選択した階層下の全てのJointサイズを変更する GUIを持ったツール』 ということで
    実際 Maya上のオブジェクトに動作をする部分 と GUIの制御部分 に分けて考えます。

    前半部分では、こんな風に考えて組み立てましょう。
    • 何か選択されているかの判別と処置
    • 何か選択されていたら、それ以下の階層構造のリストを作成
    • GUIから設定したいJointの大きさの値を取得
    • リストの中のものが、joint だったら その値を設定

    と書き出すだけでなんとなく構造が見えて来ます。

    次に後半のGUIの方も組み立てるものを列挙すると
    • window に名前を付け、同じ名前の window は、1枚しか表示しないようにする
    • window には、説明テキスト、数値を入れる空欄、値を送るボタン、windowを閉じるボタン、を用意する
    • 上記表示物は横一列に配置して、サイズを決めた値で固定する
    • 入力した値を上部に送るコマンドをボタンに設定する

    とすると、かなり具体的に何のコマンドとどういうオプション設定にすれば良いか浮かんで来ます。

    ここで覚えておくと良いノウハウ、良くミスをする点、としては、
    • 予測されるエラーへの対処
    • 取得して来たコレクションの選別と処理
    • window を1枚しか表示しない仕組み
    • window のサイズを細かく設定する仕組み
    • 値を入力するテキスト・フィールドが 文字型 だった場合 浮動小数点数として置換る方法
    • window を閉じる方法を幾つか用意しておく
    • 処理後の状態を考えおく

    と、使う側の事まで考えて組み立てられていたらバッチリです。

    そんな構想て、実際に Maya で操作実験しながら前半部分から組み立てていきます。


 jointのサイズ設定 と 選別処理
     まずは Joint サイズ はどこでコマンドは何かをログから見ると、こんな感じです。



    setAttr Python コマンドで見てみますと、Melと同じですね。

    import maya.cmds as cmds
    
    cmds.setAttr( 'joint1.radius', 0.25)

    で、joint1 がちっさくなるはずです。
    そしたら、今度は選択したトップから下の階層選択するのは 編集>階層の選択 なので、またログから見て
    cmds.select(hierarchy=True) と解ります。
    階層選択したものをリストとして取得します。その時使用するのが
    cmds.ls(sl=True) です。これは色々なケースで良く使用するコマンドです。良く見ると、条件を絞ったリストを返すことも出来ますね。

    import maya.cmds as cmds
    
    cmds.select(hierarchy=True)
    selected_object = cmds.ls(sl=True)
    print selected_object
    
    #結果
    [u'joint1', u'joint2', u'joint3', u'joint4']



    このリストにある joint を順番に 設定したいサイズ値に設定すればOKですよね。
    リストの中を繰り返すには、for in 文が便利です。
    import maya.cmds as cmds
    
    cmds.select(hierarchy=True)
    selected_object = cmds.ls(sl=True)
    for node in selected_object:
        cmds.setAttr( node + '.radius', 0.25 )

    はい、だいたい出来ました。エラーや条件分岐で色々なケース回避をしましょう。
    まずは、リストの中に joint じゃないものが混じっていた場合に備えます。
    それは、for in 文内に if 文で選別しましょう。
    Python の if 文はとても楽で、カッコでくくることもなく、終わり行に何か書く必要もない、とってもスマートな書き方です。
    何々なら、どう って書くだけです。
    何々なら、ってところは、ここでは joint ならになるので、
    for in 文内で順番に拾って来たオブジェクトの種類を調べるのは objectType を使います。


    import maya.cmds as cmds
    
    cmds.objectType( 'joint1' )
    
    # 結果: joint # 

    ちなみに、他のオブジェクトに実行すると、どんな objectType が返って来るのかと実行してみると...


     意外な答えが返って来たのではないでしょうか?
    つまり、 joint だけが 'joint' を返し、あとは 'transform' ってなってます。
    もし、予想した答えが ナーブスの球の場合、nurbsSurface だったとしたら、それはシェープノードの事になります。
    下図の例をご覧ください。



     ここでは Maya で最初に認識しておくべきことなのですが、
    ナーブスの球を作成すると必ず オブジェクト名リストとしてトランスフォームノード と  シェイプノード が返されます。
    print cmds.sphere() ナーブスの球を作成する文をプリントアウトすると 2つ 返って来ることが見て取れます。
    その状態を、ハイパーグラフ接続 ウィンド や ノードエディタ ウィンド で見ると、どういう状態なのかモット良く解るかと思います。
    つまり、''nurbsSphere1'objectType は トランスフォームノード だった、ということになります。




     さて、回り道しましたが、リストから拾って来たものが joint なら、半径を 0.25 にします、というスクリプトです。

    import maya.cmds as cmds
    
    cmds.select(hierarchy=True)
    selected_object = cmds.ls(sl=True)
    for node in selected_object:
        if cmds.objectType( node ) == 'joint':
            cmds.setAttr( node + '.radius', 0.25 )


     もう少し気遣いをしましょう。
    もし、何も選択してなくて実行したら・・・のエラーを回避しましょう。
     これには、もう上で使った ls を使い、[リストが空じゃない=True じゃ無かったら]、つまり 空なら という書き方になります。

    import maya.cmds as cmds
    
    if not cmds.ls( sl=True ):
        print 'Nothing is selected'
    else:
        cmds.select(hierarchy=True)
        selected_object = cmds.ls(sl=True)
        for node in selected_object:
            if cmds.objectType( node ) == 'joint':
               cmds.setAttr( node + '.radius', 0.25 )


     後は 後半に作るGUIから来る値が 0.25 の所に入って来るようにする記述を最後に書き込みます。

    追加の後処理として、このツールを実行した時の最初の状態に戻す、
    つまり 階層のトップのオブジェクトを選択した状態に復帰する
    というような処理をしてあげるとフレンドリーかと思います。
    そこまで書いて一旦前半を終わります。

    #coding: UTF-8
    import maya.cmds as cmds
    
    def set_joint_size(*args):
        if not cmds.ls( sl=True ):
            print 'Nothing is selected'
        else:
            first_selected = cmds.ls(sl=True)[0]
    #       ここに GUIからの値 joint_size が入って来るスクリプト待ち
            cmds.select(hierarchy=True)
            selected_object = cmds.ls(sl=True)
            for node in selected_object:
                if cmds.objectType( node ) == 'joint':
                   cmds.setAttr( node + '.radius', joint_size)
    
            cmds.select( clear=True )
            cmds.select( first_selected, replace=True )




 PythonでGUIを作る
     ここからは表示されるウィンドメニューの作成になります。
    ある程度までは何となく作れるのですが、肝心なところで思惑道理にならないなど
    細かいところの参考になるかと思います。

     ウィンドーを作って表示する、ってのはドキュメントの例に良く載っています。
    window のタイトル、テキストと値が書き込める欄、ボタンがセットになった textFieldButtonGrp
    window を閉じるのに使う 1つのボタン button を用意してみます。


    import maya.cmds as cmds
    
    window = cmds.window(title='Set Joint Size')
    cmds.columnLayout()
    cmds.textFieldButtonGrp('Set_JointSize_Button',label='Set Joint Size', text='0.5', buttonLabel='Set Size')
    cmds.button(label='Close')
    cmds.showWindow(window)

    textFieldButtonGrp には、きちんとその固有の名前 'Set_JointSize_Button' を記述しておきます、重要です。

     さて、この window にはオブジェクトとしての名前で指定して管理していないので、
    このスクリプトを何回も押すと次々と window を作成していってしまいます。



    同じ名前のウィンドは1枚しか絶対に表示しない、って風にしていきたいのですが、
    今度は名前を付けると1枚目は良くても、2枚目の時エラーとなります。(まー、2枚目は表示しなくなりますが...)

    import maya.cmds as cmds
    
    window = cmds.window('SetJointSize', title='Set Joint Size')
    cmds.columnLayout()
    cmds.textFieldButtonGrp('Set_JointSize_Button',label='Set Joint Size', text='0.5', buttonLabel='Set Size')
    cmds.button(label='Close')
    cmds.showWindow(window)



    エラーの内容は最初に作成した window と同じ名前の window を作成しようとした為既にあるのでエラーになっているだけです。
    つまり設定している名前で認識はしてくれている、ということになります。
    このことから、既に同じ名前の window があったら閉じて、同じ名前の新しいwindow を表示する、という風に解決します。
    window のコマンドは作成するだけじゃなく、存在するかもオプションで判別させることが出来ます。
    また、既存 window を閉じるのは deleteUI を使用します。


    import maya.cmds as cmds
    
    if cmds.window('SetJointSize', exists=True):
        cmds.deleteUI('SetJointSize')
    
    window = cmds.window('SetJointSize', title='Set Joint Size')
    cmds.columnLayout()
    cmds.textFieldButtonGrp('Set_JointSize_Button',label='Set Joint Size', text='0.5', buttonLabel='Set Size')
    cmds.button(label='Close')
    cmds.showWindow(window)

    これでもうエラーも無く、1枚しか表示しません。
    そして、閉じるボタンに設定するコマンドも解ったことになります。

    import maya.cmds as cmds
    
    if cmds.window('SetJointSize', exists=True):
        cmds.deleteUI('SetJointSize')
    
    window = cmds.window('SetJointSize', title='Set Joint Size')
    cmds.columnLayout()
    cmds.textFieldButtonGrp('Set_JointSize_Button',label='Set Joint Size', text='0.5', buttonLabel='Set Size')
    cmds.button(label='Close', command='cmds.deleteUI("SetJointSize")')
    cmds.showWindow(window)




     少し追加の情報も書きます。
    この window を閉じる っていう設定というのは思いの外重要で、window 内に別の形で用意できることを知っておくといいかも知れません。
    その1つに メニューバー を紹介します。
    window コマンド内に 追加のオプションとして、menuBar=True を追加し、
    メニューに例えば File というラベルを設定し、その中に exit という項目を作り、そこに閉じるコマンドを設定します。

    import maya.cmds as cmds
    
    if cmds.window('SetJointSize', exists=True):
        cmds.deleteUI('SetJointSize')
    
    window = cmds.window('SetJointSize', title='Set Joint Size', menuBar=True )
    cmds.menu( label='File' )
    cmds.menuItem( label='exit', command='cmds.deleteUI("SetJointSize")' )
    cmds.columnLayout()
    cmds.textFieldButtonGrp('Set_JointSize_Button',label='Set Joint Size', text='0.5', buttonLabel='Set Size')
    cmds.button(label='Close', command='cmds.deleteUI("SetJointSize")')
    cmds.showWindow(window)



    大きなメニュー画面の時に使えそうな方法で、メニューやタブも作れることが分ります。



     さて続きです。
    textFieldButtonGrp button の2つの表示物が今は縦に並んでいます。
    縦並びに設定しているのは columnLayout で、もう1つ横並びにする  rowLayout を追記し、横に並べます。


    import maya.cmds as cmds
    
    if cmds.window('SetJointSize', exists=True):
        cmds.deleteUI('SetJointSize')
    
    window = cmds.window('SetJointSize', title='Set Joint Size')
    cmds.columnLayout()
    cmds.rowLayout(numberOfColumns=2)
    cmds.textFieldButtonGrp('Set_JointSize_Button',label='Set Joint Size', text='0.5', buttonLabel='Set Size')
    cmds.button(label='Close', command='cmds.deleteUI("SetJointSize")')
    cmds.showWindow(window)

    rowLayoutnumberOfColumns というオプションにきちんと個数を入れないとエラーになります。ここでは2個。



    整って来たところで、どうも textFieldButtonGrp の3つの要素のうち、
    最初のテキストの左側、値を記入する欄の大きさがイマイチなので値を設定して好みに合わせます。
    columnWidth3 というオプションを使用します。

    import maya.cmds as cmds
    
    if cmds.window('SetJointSize', exists=True):
        cmds.deleteUI('SetJointSize')
    
    window = cmds.window('SetJointSize', title='Set Joint Size')
    cmds.columnLayout()
    cmds.rowLayout(numberOfColumns=2)
    cmds.textFieldButtonGrp('Set_JointSize_Button',label='Set Joint Size', text='0.5',
        buttonLabel='Set Size', columnWidth3=[80,50,70])
    cmds.button(label='Close', command='cmds.deleteUI("SetJointSize")')
    cmds.showWindow(window)

    かなりいい感じになってきましたが、実はこの winodw 端っこがつまめて、
    びよ~ん と伸びてしまいます。しかも次に表示した時は伸ばした状態で表示されます。




    import maya.cmds as cmds
    
    if cmds.window('SetJointSize', exists=True):
        cmds.deleteUI('SetJointSize')
    
    window = cmds.window('SetJointSize', title='Set Joint Size',sizeable=False, topLeftCorner=[200, 200], widthHeight=(250,30))
    cmds.columnLayout()
    cmds.rowLayout(numberOfColumns=2)
    cmds.textFieldButtonGrp('Set_JointSize_Button',label='Set Joint Size', text='0.5',
        buttonLabel='Set Size', columnWidth3=[80,50,70])
    cmds.button(label='Close', command='cmds.deleteUI("SetJointSize")')
    cmds.showWindow(window)

    window コマンド内に 追加のオプションとして、sizeable=False でサイズを固定、topLeftCorner=[200, 200] で最初に表示した時の位置、
    widthHeight=(250, 30) でサイズを設定します。


     以上で表示系は完成しました。
    残りの作業は、textFieldButtonGrp のボタンを押したら上部のスクリプトが動くようにコマンドを埋め込みます。
    前半部分の 関数の飛び先 def set_joint_size(*args): ともう書いてあります。
    そこに行くように buttonCommand=set_joint_size というオプションを追加します。

    import maya.cmds as cmds
    
    if cmds.window('SetJointSize', exists=True):
        cmds.deleteUI('SetJointSize')
    
    window = cmds.window('SetJointSize', title='Set Joint Size',sizeable=False, topLeftCorner=[200, 200], widthHeight=(250,30))
    cmds.columnLayout()
    cmds.rowLayout(numberOfColumns=2)
    cmds.textFieldButtonGrp('Set_JointSize_Button',label='Set Joint Size', text='0.5',
        buttonLabel='Set Size', columnWidth3=[80,50,70], buttonCommand=set_joint_size)
    cmds.button(label='Close', command='cmds.deleteUI("SetJointSize")')
    cmds.showWindow(window)

    後半部分はこれで本当に終了です。

    ただし、問題が残ってます。
    この textFieldButtonGrp の値を記入する欄は テキスト=文字列です。
    joint のサイズを記入するところは 数値 であり、尚且つ 小数点を扱う float型 にしなくてはなりません。
    このことを踏まえつつ、最後の仕上げといきましょう。



 完成へ
     さて、上記で書いた、取得して来た値の型がなんの タイプか調べるのには type を使用します。
    ここは一旦出来上がった前半部分を少し崩して、textFieldButtonGrp の値を記入する欄の取得の仕方と合わせて
    タイプを確認したいと思います。
     実はもう一度 textFieldButtonGrp のコマンドを設定した 固有名と共に query=True, text=True というオプションを書けば取得出来ます。
    cmds.textFieldButtonGrp('Set_JointSize_Button',query=True, text=True) となります。
    この値のタイプをプリントしてみます。


    import maya.cmds as cmds
    
    def set_joint_size(*args):
        joint_size = cmds.textFieldButtonGrp('Set_JointSize_Button',query=True, text=True)
        print type(joint_size)
    
    if cmds.window('SetJointSize', exists=True):
        cmds.deleteUI('SetJointSize')
    
    window = cmds.window('SetJointSize', title='Set Joint Size',sizeable=False, topLeftCorner=[200, 200], widthHeight=(250, 30))
    cmds.columnLayout()
    cmds.rowLayout(numberOfColumns=2)
    cmds.textFieldButtonGrp('Set_JointSize_Button',label='Set Joint Size', text='0.5',
        buttonLabel='Set Size', columnWidth3=[80,50,70], buttonCommand=set_joint_size)
    cmds.button(label='Close', command='cmds.deleteUI("SetJointSize")')
    cmds.showWindow(window)

    結果はもちろん
    <type 'unicode'>

    ですよね。ここで面白い書き方としてこうしてしまいます。

        joint_size = float(cmds.textFieldButtonGrp('Set_JointSize_Button',query=True, text=True))

    ちょっと強引ですかね。結果は
    <type 'float'>

    となって、joint の大きさを扱えるデータになりました。
     が、またまた意地悪出来ますとね。そうテキスト欄だから数字じやないもの記入出来てしまいます。
    それを float型にしようとしたら エラーで出るに決まってます。



     エラーが出た時の対処..といつも思い浮かぶのが、try except 文です。
    値を取得するところを try とし、エラーが出たら except で 数値を入れてくれよ とプリントしてみます。

    import maya.cmds as cmds
    
    def set_joint_size(*args):
        try:
            joint_size = float(cmds.textFieldButtonGrp('Set_JointSize_Button',query=True, text=True))
            print type(joint_size)
        except:
            print 'Please set numbers'
    
    if cmds.window('SetJointSize', exists=True):
        cmds.deleteUI('SetJointSize')
    
    window = cmds.window('SetJointSize', title='Set Joint Size',sizeable=False, topLeftCorner=[200, 200], widthHeight=(250, 30))
    cmds.columnLayout()
    cmds.rowLayout(numberOfColumns=2)
    cmds.textFieldButtonGrp('Set_JointSize_Button',label='Set Joint Size', text='0.5',
        buttonLabel='Set Size', columnWidth3=[80,50,70], buttonCommand=set_joint_size)
    cmds.button(label='Close', command='cmds.deleteUI("SetJointSize")')
    cmds.showWindow(window)

    これでエラーを回避してみました。
    これを 途中まで作っていた前半部分と、GUIを組み合わせて .py ファイルを完成させます。



 ri_set_joint_size.py
    import maya.cmds as cmds
    
    def set_joint_size(*args):
        if not cmds.ls( sl=True ):
            print 'Nothing is selected'
        else:
            first_selected = cmds.ls(sl=True)[0]
            try:
                joint_size = float(cmds.textFieldButtonGrp('Set_JointSize_Button',query=True, text=True))
                cmds.select(hierarchy=True)
                selected_object = cmds.ls(sl=True)
                for node in selected_object:
                    if cmds.objectType( node ) == 'joint':
                       cmds.setAttr( node + '.radius', joint_size)
    
                cmds.select( clear=True )
                cmds.select( first_selected, replace=True )
    
            except:
                    print 'Please set numbers'
    
    def set_joint_size_menu():
        if cmds.window('SetJointSize', exists=True):
            cmds.deleteUI('SetJointSize')
    
        window = cmds.window('SetJointSize', title='Set Joint Size',sizeable=False, topLeftCorner=[200, 200], widthHeight=(250, 30))
        cmds.columnLayout()
        cmds.rowLayout(numberOfColumns=2)
        cmds.textFieldButtonGrp('Set_JointSize_Button',label='Set Joint Size', text='0.5',
            buttonLabel='Set Size', columnWidth3=[80,50,70], buttonCommand=set_joint_size)
        cmds.button(label='Close', command='cmds.deleteUI("SetJointSize")')
        cmds.showWindow(window)

     ちっさいGUIに、ちょっとしたツールでも、Python学ぶにはもってこいの内容でした。

    完成した .py は シェルフに登録 する場合は ユーザーディレクトリー にコピーして、
    例 C:\Users\ユーザー名\Documents\maya\2015-x64\ja_JP\prefs\scripts (MAYA_APP_DIR がある場合はそちら)
    以下のコマンドで立ち上げます。OKなら、これを シェルフに登録します。
    import ri_set_joint_size
    ri_set_joint_size.set_joint_size_menu()

    プルダウンのメニューに登録する方法もあります。
     参照 > Maya; Python で カスタム・グラフエディタ を作ろう!! http://www.comtec.daikin.co.jp/DC/UsersNotes/Ritaro/tutorial/maya_02/#Shelf


    ri_set_joint_size.py
    (Maya2012,2014,2015)
    あくまでも自己責任でお使いください。 use as your own risk.

    ri_set_joint_size.zip  0.7KB  2014/10/26 現在





     
    という訳で、次回 は ... What's Next ...

      乞う、ご期待!! Stay tuned ..