MayaでのGUI
以前Qt云々でやろうとしたけど、その後全然Mayaを触らず、しかして最近一つツールを作らねばならぬ事態となったため、maya.cmdsでのGUI作成をお勉強です。
ウインドウを作る
import maya.cmds as cmds win = cmds.window('test_Window', title='Test Window', widthHeight=(546,350) ) cmds.showWindow(win)
この場合、ウインドウのタイトルがtest_Windowとなり、これを名前の衝突が生まれないように決めてやる必要がある(ということで、会社名や個人名を名前の頭につけることが多いそうで)。ということで、これで作られたウインドウが開いたままで、同じtest_Windowという名前のウインドウを作成しようとすると"Object's name 'test_Window' is not unique."とかいうエラーが出て失敗することになります。もしそうしたいなら、すでに開いているウインドウを破棄して(ウインドウを閉じるか、deleteUIコマンドを走らせる)、その上で実行する必要があるようですね。
cmds.deleteUI(win, window=True)
というわけで、Pythonを使っているのだからウインドウクラスを用意すると良いと参考にしている書籍には書いてありました。んが、とりあえずクラス云々の中身、GUIの部品をどう作るかが興味の対象です。
メニューを作る
import maya.cmds as cmds if cmds.window(win, exists=True): cmds.deleteUI(win, window=True) win = cmds.window('test_Window', title='NEW TITLE2', widthHeight=(546,350), menuBar=True) #ここからMenu 1メニュー cmds.menu( label='Menu 1' ) cmds.menuItem( label='Command 1') cmds.menuItem( label='Command 2') cmds.menuItem( divider=True ) cmds.radioMenuItemCollection() cmds.menuItem( label='Radio Button1', radioButton=True, enable=True ) cmds.menuItem( label='Radio Button2', radioButton=False, enable=True ) #ここからMenu 2メニュー cmds.menu( label='Menu 2' ) cmds.menuItem( label='Command 3') cmds.menuItem( subMenu=True, label='Command 4' ) cmds.menuItem( label='Sub Menu 1' ) cmds.menuItem( label='Sub Menu 2' ) cmds.menuItem( label='Sub Menu 3' ) cmds.setParent( '..', menu=True ) cmds.menuItem( label='Command 5') cmds.showWindow(win)
Menu 2に置いて、サブメニューを作っているけど、その下にsetParentを実行しているのがミソですか。
で、メニューを実行した時にある関数内の処理を行わせようとすると、menuItemコマンドのcommandフラグで関数を指定するのだそうです。

import maya.cmds as cmds def Cmd1( *args): cmds.about() if cmds.window(win, exists=True): cmds.deleteUI(win, window=True) win = cmds.window('test_Window', title='NEW TITLE2', widthHeight=(546,350), menuBar=True) cmds.menu( label='Menu 1' ) #メニューを選ぶと関数が実行される cmds.menuItem( label='Command 1' , command=Cmd1 ) cmds.showWindow(win)
なんだけど、Cmd1を呼び出すにあたって、引数を渡すことができないという問題があるようです。
ちなみに*argsは余った引数を収めるたプルとして機能するらしいですな(Pythonの機能だそうです。頭にアスタリスクをつけた変数の取り扱いのお話し。付け加えると**とアスタリスクを二つ重ねると辞書として機能するらしいです)。
というわけで、functoolsモジュールのparial()関数を使いなさい、だそうです。Maya ヘルプ: Python で初めてスクリプトを記述する場合のヒントとコツ[help.autodesk.com]
import maya.cmds as cmds from functools import partial def Cmd1( *args): print(args) if cmds.window(win, exists=True): cmds.deleteUI(win, window=True) win = cmds.window('test_Window', title='NEW TITLE2', widthHeight=(546,350), menuBar=True) cmds.menu( label='Menu 1' ) #引数に3つのテキストを渡そうとしている。 cmds.menuItem( label='Command 1' , command=partial(Cmd1, 'A', 'B', 'C') ) cmds.showWindow(win)
上記スクリプトの結果として、実行すると('A', 'B', 'C', False)ってのがログに記されます。
ボタンを作る
import maya.cmds as cmds from functools import partial def Cmd1( *args): print(args) if cmds.window(win, exists=True): cmds.deleteUI(win, window=True) win = cmds.window('test_Window', title='TEST WINDOW',widthHeight=(600,350)) cmds.rowLayout( numberOfColumns=3, \ columnWidth3=( 200, 200, 180 ), \ columnAttach=( (1,'both',20), (2,'both',10), (3,'both',5) ) ) cmds.button( label='Command 1', height=26, command=partial(Cmd1, 'A', 'B', 'C') ) cmds.button( label='Command 2', height=26, command=partial(Cmd1, 'D', 'E', 'F') ) cmds.button( label='Command 3', height=26, command=partial(Cmd1, 'G', 'H', 'I') ) cmds.showWindow(win)
上のコードでは子を水平方向に並べるrowLayoutでボタン配置を定義しています。ボタンなどのコントロールには親となるレイアウトが必要ということです。
numberOfColumnsで子(ボタン)の数を3と指定し
columnWidth3でその子(ボタン)が3つの場合のそれぞれの幅を指定しています。
columnAttachではそれぞれのボタンの設定ですね。3つの値を持つタプルでボタンごとの設定をしています。タプルの中身は、一つ目はボタンのインデックス(1スタート)。二つ目はアタッチメント(取り付け)の位置の指定でしょうか。取る値はleft, both, rightとなります。bothにしないと、ボタンのサイズがラベルの幅になってしまうようです。三つ目の値がオフセット値でbothにしている時、ボタンの左右の余白の値になるみたいです。
上を実行すると、下図のウインドウが出来上がるわけですが、幅600のウインドウに対し、
一つ目のボタン:幅が200で余白が20
二つ目のボタン:幅が200で余白が10
三つ目のボタン:幅が180で余白が5
となりまする。なんとなくわかったような気がしたような…

とはいえ、もっとレイアウトをちゃんとコントロールしたい時、formLayoutを使うのだそうです。
import maya.cmds as cmds from functools import partial def Cmd1( *args): print(args) if cmds.window(win, exists=True): cmds.deleteUI(win, window=True) win = cmds.window('test_Window', title='TEST WINDOW',widthHeight=(600,350)) mainForm = cmds.formLayout( numberOfDivisions=100 ) b1 = cmds.button( label='Command 1', height=26, command=partial(Cmd1, 'A', 'B', 'C') ) b2 = cmds.button( label='Command 2', height=26, command=partial(Cmd1, 'D', 'E', 'F') ) b3 = cmds.button( label='Command 3', height=26, command=partial(Cmd1, 'G', 'H', 'I') ) cmds.formLayout( mainForm, edit=True, \ #フォームの境界にボタンのどのエッジを固定するかの指定。オフセット値を5としている。 attachForm = ( [ b1, 'left', 5 ], \ [ b1, 'bottom', 5 ], \ [ b2, 'bottom', 5 ], \ [ b3, 'bottom', 5 ], \ [ b3, 'right', 5 ]), \ #ボタンをフォームのどの位置に固定するかの指定。b1の右辺を33%の位置に、b3の左辺を67%の位置に。 attachPosition = ( [ b1, 'right' , 0, 33], \ [ b3, 'left' , 0, 67] ), \ #真ん中のボタンb2が左右のボタンの隣接する辺に固定ための設定。 attachControl = ( [ b2, 'left', 4, b1 ], \ [ b2, 'right', 4, b3 ] ),\ #すべてのボタンの上辺は固定しない。 attachNone = ( [ b1, 'top' ], [ b2, 'top' ], [ b3, 'top' ] ) ) cmds.showWindow(win)
mainForm = cmds.formLayout( numberOfDivisions=100 ) をウインドウを作成するすぐ後ろに設定することで、mainFormがWindowsのすぐ子供になり、これの子供に各コントロール要素を配置していくイメージのようです。numberOfDivisions=100はウインドウの左上を基準として、右下が縦横それぞれ100となるという設定。0~100%の範囲で位置を指定するための設定みたいです。
rowLayoutを削除し、formLayoutを追加しています。また、作成したボタンはb1~b3の変数に入れています。
これにより、Mayaのウインドウっぽいボタンが三つウインドウの下に並ぶもんが出来上がりました。

なので、これをクラスとして保持しておけば、いろいろ流用できて便利だよってのが参考書籍のおっしゃる内容でございます。
でもって、空いている上側のスペースにラジオボタンを設定してみます。
import maya.cmds as cmds from functools import partial def applyAndCloseBtm(*args): applyBtm() closeBtm() def applyBtm(*args): urlList = { 1:'http://toyota.jp', \ 2:'http://www.nissan.co.jp', \ 3:'http://www.honda.co.jp', \ 4:'http://www.subaru.jp', \ } siteIndex = cmds.radioButtonGrp( siteUrl, q=True, \ select=True \ ) cmds.launch(webPage=urlList[siteIndex]) def closeBtm(*args): cmds.deleteUI(win, window=True) if cmds.window(win, exists=True): cmds.deleteUI(win, window=True) win = cmds.window('test_Windowc', title='TEST WINDOW',widthHeight=(600,350)) mainForm = cmds.formLayout( numberOfDivisions=100 ) b1 = cmds.button( label='Apply and Close', height=26, command=applyAndCloseBtm ) b2 = cmds.button( label='Apply', height=26, command=applyBtm ) b3 = cmds.button( label='Close', height=26, command=closeBtm ) siteUrl = cmds.radioButtonGrp( label='WWW: ', labelArray4=[ 'TOYOTA', \ 'NISSAN', \ 'HONDA', \ 'SUBARU' ], \ numberOfRadioButtons=4, \ select=1 \ ) cmds.formLayout( mainForm, edit=True, \ attachForm = ( [ b1, 'left', 5 ], \ [ b1, 'bottom', 5 ], \ [ b2, 'bottom', 5 ], \ [ b3, 'bottom', 5 ], \ [ b3, 'right', 5 ]), \ attachPosition = ( [ b1, 'right' , 0, 33], \ [ b3, 'left' , 0, 67], [ siteUrl, 'top', 10, 0] ), \ attachControl = ( [ b2, 'left', 4, b1 ], \ [ b2, 'right', 4, b3 ] ),\ attachNone = ( [ b1, 'top' ], [ b2, 'top' ], [ b3, 'top' ] ) ) cmds.showWindow(win)
チェックボックスから選んでApplyを押すと選ばれた会社のサイトがデフォルトブラウザで開く、というものとなります。
ちょっとだけわかってきたような、そんな感じで今日は終わります。
maya GUI って自分の思い通りのレイアウトにするのって
けっこう面倒ですよね。
いろいろ試行錯誤を,
いつも繰り返しているような印象が自分にあり、
これを解消するために、
pyside, pymel 等のQt(キュート)があるのでしょうが、
自分は、今はそこに興味がなく、
自分でmaya defaultでGUI作成が出来てからのお話と
割り切って、それは後回しにしています。
そういった中で、このページは、
その試行錯誤を、順に追っていっている、
検証のページとして、大変参考になります。
これからも参考にさせてもらいます。
ところで、
「これをクラスとして保持しておけば、いろいろ流用できて便利だよってのが参考書籍のおっしゃる内容。。」
とおしゃっていますが、
記述の仕方を
例えばでよいので、見せていただくことは可能でしょうか?
どうかよろしくお願いします。
44heee
あと、もう一つ質問です。
記述にある、
実行すると('A', 'B', 'C', False)ってのがログ
がかえってくるとなていますが、
False
とは、何のためにあるのか、いったい何なのか
も教えていただけると助かります。
44heee
そーいうものだ、という理解でおります。
https://knowledge.autodesk.com/ja/support/maya/learn-explore/caas/CloudHelp/cloudhelp/2016/JPN/Maya/files/GUID-8A96A8DB-FD6F-434F-A878-288DD84E99C7-htm.html
こちらにある「Python クラスを使用したカスタム UI」のコードを実行すると、"button 0 got False"といった返事が返ってきます。このFalseとご質問のFalseは同じもののようですが、とりあえず、渡される引数の最後にはFalseが追加される、と憶えております(^^;
ちなみに、partialはpartialオブエジェクトを作成するものだそうで、そもそもは、関数の引数の一部を固定するような用途に使うみたいです。
(以下、インデントに全角スペースを使っています(^^;;)
import functools
def mult(a,b):
print( a * b )
multNew=functools.partial( mult , 10 )
multNew( 2 )
とすると、mult(a,b)の a に常に10が入り、上の場合、 10 * 2 が実行されて、20と帰ってきます。
これを便利と感じるほど、Pythonが分かっているわけじゃないので、もっとちゃんと説明したページを見た方がいいでしょう。
クラスについてはまたそのうちに。
例題のコーディング
参考になりました。
さらにいろんなものをみてみたいと思います。
大変ありがとうございます。
44heee