CG・コンテンツ制作
  1. CG・コンテンツ制作トップ
  2. DAIKIN CG Channel
  3. UsersNotes
  4. スクリプト
  5. Maya;Python 入門;Jointサイズ変更ツールを作る
SUITE USERS NOTES
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)
maya05_01
ritaro_ml



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

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

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

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

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

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

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

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



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

maya05_02

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

■ setAttr(Python)http://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/setAttr.html

import maya.cmds as cmds

cmds.setAttr( 'joint1.radius', 0.25)

で、 joint1 がちっさくなるはずです。
そしたら、今度は選択したトップから下の階層選択するのは 編集>階層の選択 なので、またログから見て
cmds.select(hierarchy=True) と解ります。

■ select(Python)http://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/select.html

階層選択したものをリストとして取得します。その時使用するのが
cmds.ls(sl=True) です。これは色々なケースで良く使用するコマンドです。良く見ると、条件を絞ったリストを返すことも出来ますね。

■ ls(Python)http://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/ls.html

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']

maya05_03

このリストにある joint を順番に設定したいサイズ値に設定すればOKですよね。
リストの中を繰り返すには、for in 文が便利です。

■ for(Python)http://docs.python.jp/2/tutorial/controlflow.html#for

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 文はとても楽で、カッコでくくることもなく、終わり行に何か書く必要もない、とってもスマートな書き方です。
何々なら、どうって書くだけです。

■  if(Python)http://docs.python.jp/2/tutorial/controlflow.html#if

何々なら、ってところは、ここでは joint ならになるので、
for in 文内で順番に拾って来たオブジェクトの種類を調べるのは objectType を使います。

■ objectType(Python)http://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/objectType.html

import maya.cmds as cmds

cmds.objectType( 'joint1' )

# 結果: joint # 

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

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

maya05_05

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

maya05_06


さて、回り道しましたが、リストから拾って来たものが 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 を用意してみます。
■  textFieldButtonGrp (Python)http://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/textFieldButtonGrp.html
■  button (Python)http://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/button.html

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 を作成していってしまいます。

maya05_07
同じ名前のウィンドは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 を使用します。
■  window (Python)http://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/window.html
■  deleteUI (Python)http://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/deleteUI.html

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)

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


さて続きです。
textFieldButtonGrp button の2つの表示物が今は縦に並んでいます。
縦並びに設定しているのは columnLayout で、もう1つ横並びにする rowLayout を追記し、横に並べます。
columnLayout (Python) http://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/columnLayout.html
rowLayout (Python) http://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/rowLayout.html

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)

rowLayout numberOfColumns というオプションにきちんと個数を入れないとエラーになります。ここでは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 端っこがつまめて、
びよ~んと伸びてしまいます。しかも次に表示した時は伸ばした状態で表示されます。

maya05_11
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) でサイズを設定します。

maya05_12
以上で表示系は完成しました。
残りの作業は、 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) となります。
この値のタイプをプリントしてみます。

■ type(Python)http://docs.python.jp/2/library/types.html

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 で数値を入れてくれよとプリントしてみます。

■ try except(Python)http://docs.python.jp/2/tutorial/errors.html?highlight=try%20except#tut-handling

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 で カスタム・グラフエディタ を作ろう!!https://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.zip0.7KB 2014/10/26 現在

という訳で、次回 は ... What's Next ...
乞う、ご期待!! Stay tuned ..
 

Twitter

ページの先頭へ