おはようございます。本日の当番、モーションデザイナーのR.Iです。
今回もスクリプトの話をしようと思います。
前回のブログではPythonファイル内にウィンドウ生成やボタン生成などを記載していましたが、今回はツールで作ったUIファイルをスクリプトから呼び出したりをやってみようと思います。
勉強も兼ねて作成したツールが以下になります。
オブジェクトを選択して、「point」「orient」「parent」というボタンを押したら、ロケータを生成。生成したロケータを選択したオブジェクトにコンストレインさせるツールです。
生成されたロケータを選択できるようにボタンの下のリストにロケータ名を追加します。リストに記載されたロケータを選択して、右クリックを押すと、ベイクしたり、コンストレインを反転したりと自分がよく使う機能をズラッと参照できるようにしてます。
キャラクターを動かすためのコントローラの仕組みを変化させる際に
1.ロケータ生成
2.コントローラにロケータをコンストレイン
3.ロケータをベイク
4.「2」の処理でのコンストレイン関係を入れ替える
この機能をサッとできるだけで作業効率が全然違います。
今回はUIファイルの呼び出しやPythonファイルを外部から呼び出したりするので、簡単にパッケージ化します。フォルダ構成は以下になります。
Geta_Toolフォルダ
┝ __init__.py
┝ geta.py(メインファイル)
└ Geta_Tool.ui(UIファイル)
以下がスクリプトの内容になります。
# -*- coding: utf-8 -*-
import PySide.QtGui as QtGui import PySide.QtCore as QtCore import shiboken from PySide.QtUiTools import QUiLoader from maya import OpenMayaUI as OpenMayaUI import maya.cmds as cmds import os
def getMayaWindow(): Ptr = OpenMayaUI.MQtUtil.mainWindow() return shiboken.wrapInstance(long(Ptr), QtGui.QWidget)
class Ui_MainWidget(QtGui.QMainWindow): def __init__(self, parent=None): QtGui.QMainWindow.__init__(self, getMayaWindow()) loader = QUiLoader() uifile = os.path.dirname(os.path.abspath(__file__)) + "/Geta_Tool.ui" self.ui = loader.load(uifile) self.setCentralWidget(self.ui) self.setGeometry(30, 30, 220, 400) self.setWindowTitle("GETA Tool")
self.ui.btn_point.clicked.connect(self.btn_point_clicked) self.ui.btn_orient.clicked.connect(self.btn_orient_clicked) self.ui.btn_parent.clicked.connect(self.btn_parent_clicked) self.ui.list_locators.itemClicked.connect(self.list_locators_item_clicked)
self.ui.list_locators.customContextMenuRequested.connect(self.list_locators_customContextMenuRequested)
def btn_point_clicked(self): for name in constraintsSpaceLocator("pos"): self.ui.list_locators.addItem(name)
def btn_orient_clicked(self): for name in constraintsSpaceLocator("rot"): self.ui.list_locators.addItem(name)
def btn_parent_clicked(self): for name in constraintsSpaceLocator("par"): self.ui.list_locators.addItem(name)
def list_locators_item_clicked(self): selList = [] for node in self.ui.list_locators.selectedItems(): selList.append(node.text()) cmds.select(selList)
#コンテキストメニュー def list_locators_customContextMenuRequested(self): point = QtCore.QPoint() index = self.ui.list_locators.indexAt(point) if not index.isValid(): return
menu=QtGui.QMenu(self) act_bake = menu.addAction('Selected Bake') act_bake.triggered.connect(self.act_bake) act_changeCons = menu.addAction('Change Constraints') act_changeCons.triggered.connect(self.act_changeCons) act_addNode = menu.addAction('Add SelectedObject') act_addNode.triggered.connect(self.act_addNode) act_deleteList = menu.addAction('Delete List') act_deleteList.triggered.connect(self.act_deleteList) act_deleteObject = menu.addAction('Delete Object') act_deleteObject.triggered.connect(self.act_deleteObject) menu.exec_(QtGui.QCursor.pos())
def act_bake(self): startFrame = cmds.playbackOptions(query=True, minTime=True) endFrame = cmds.playbackOptions(query=True, maxTime=True) cmds.bakeResults(t=(startFrame, endFrame))
def act_changeCons(self): constraintsChange()
def act_addNode(self): for node in cmds.ls(sl=True): self.ui.list_locators.addItem(node)
def act_deleteList(self): for node in self.ui.list_locators.selectedItems(): self.ui.list_locators.takeItem(self.ui.list_locators.row(node))
def act_deleteObject(self): cmds.delete() for node in self.ui.list_locators.selectedItems(): self.ui.list_locators.takeItem(self.ui.list_locators.row(node))
def constraintsSpaceLocator(ConstraintType): locatorNameList = [] for node in cmds.ls(sl=True): if ConstraintType == "pos": locator_list = cmds.spaceLocator(name = "%s_%s" %(node, ConstraintType)) cmds.pointConstraint(node, locator_list) elif ConstraintType == "rot": locator_list = cmds.spaceLocator(name = "%s_%s" %(node, ConstraintType)) cmds.orientConstraint(node, locator_list) elif ConstraintType == "par": locator_list = cmds.spaceLocator(name = "%s_%s" %(node, ConstraintType)) cmds.parentConstraint(node, locator_list)
locatorNameList.append(locator_list[0]) return locatorNameList
def constraintsChange(): for node in cmds.ls(sl=True): #選択中のオブジェクトにコンストレイントを取得(リスト) consList = cmds.listRelatives(node, type="constraint") #コンストレイントのリスト分だけループ for cons in consList: #コンストレイント先のオブジェクト名をアトリビュートから取得(リスト) consingObjList = cmds.listConnections("%s.target[0].targetParentMatrix" %(cons)) #コンストレイント先のオブジェクト名のリスト分だけループ for consingObj in consingObjList: consType = cmds.nodeType(cons) #取得したコンストレイントのタイプで処理を分岐 if consType == "pointConstraint": cmds.delete(cons) cmds.pointConstraint(node, consingObj)
elif consType == "orientConstraint": cmds.delete(cons) cmds.orientConstraint(node, consingObj) elif consType == "parentConstraint": cmds.delete(cons) cmds.parentConstraint(node, consingObj)
else: print u"想定していないコンストレイントです。"
def UiShow(): MainWindow = Ui_MainWidget() MainWindow.show()
|
そしてそれを呼び出して実行するための処理は以下になります。
import Geta_Tool.geta as Geta reload(Geta) Geta.UiShow()
|
メインファイルの中身について、主にUIとの関連部分を説明していきたいと思います。
import PySide.QtGui as QtGui import PySide.QtCore as QtCore import shiboken from PySide.QtUiTools import QUiLoader
from maya import OpenMayaUI as OpenMayaUI import maya.cmds as cmds import os
def getMayaWindow(): Ptr = OpenMayaUI.MQtUtil.mainWindow() return shiboken.wrapInstance(long(Ptr), QtGui.QWidget)
|
上記の部分で必要なライブラリの呼び出し、前回のブログで説明したウィンドウが手前にくるようにする処理です。
class Ui_MainWidget(QtGui.QMainWindow): def __init__(self, parent=None): QtGui.QMainWindow.__init__(self, getMayaWindow()) loader = QUiLoader() uifile = os.path.dirname(os.path.abspath(__file__)) + "/Geta_Tool.ui" self.ui = loader.load(uifile) self.setCentralWidget(self.ui) self.setGeometry(30, 30, 220, 400) self.setWindowTitle("GETA Tool")
|
上記の部分でUIファイルの呼び出し、取得、ウィンドウの表示位置、サイズなどを指定してます。
「uifile = os.path.dirname(os.path.abspath(__file__))」で実行されているPythonファイルのパスを取得することができ、同階層にあるUIファイルを取得できるようにしておきます。
あとは起動時の表示位置とウィンドウサイズを指定、ツールのタイトルを記載しています。自分の周りではロケータを使って構造を変更する際に「下駄をはかせる」などというため、下駄ツールとしています。
self.ui.btn_point.clicked.connect(self.btn_point_clicked) self.ui.btn_orient.clicked.connect(self.btn_orient_clicked) self.ui.btn_parent.clicked.connect(self.btn_parent_clicked) self.ui.list_locators.itemClicked.connect(self.list_locators_item_clicked)
self.ui.list_locators.customContextMenuRequested.connect(self.list_locators_customContextMenuRequested)
|
上記の部分でUIファイル内に設置されているボタンやリストに対してイベント設定をしています。以下の図のようにボタン、リストに名前をつけています。この名前を使ってUIファイル内のボタンなどにアクセスします。
先ほど「self.ui = loader.load(uifile)」でUIファイルの情報を代入していました。なので、「self.ui.btn_point」でpointボタンが取得できます。pointボタンは「QPushButton」なので、クリック時のイベントが取得できます。「self.ui.btn_orient.clicked.connect(self.btn_point_clicked)」で「.connect」のカッコ内にイベント発生時に実行される関数名を指定します。
def btn_point_clicked(self): for name in constraintsSpaceLocator("pos"): self.ui.list_locators.addItem(name)
|
上記の部分はボタンを押された時に呼び出される関数です。
「constraintsSpaceLocator()」という関数も処理中に呼んでいます。
「constraintsSpaceLocator()」は引数として文字列「pos」「rot」「par」を渡すと文字列によってコンストレイン処理が分岐します。コンストレインするロケータを生成し、戻り値として生成したロケータの配列を返します。その配列を一つずつ「name」に代入し「list_locators」の一覧に加えています。
#コンテキストメニュー def list_locators_customContextMenuRequested(self): point = QtCore.QPoint() index = self.ui.list_locators.indexAt(point) if not index.isValid(): return
menu=QtGui.QMenu(self) act_bake = menu.addAction('Selected Bake') act_bake.triggered.connect(self.act_bake)
menu.exec_(QtGui.QCursor.pos())
|
右クリックを押した際に出てくるメニューの作成は上記の部分で行っています。
始めの方は決まり文句のようなもので、自分もあまり理解できておりません。「QtGui.QMenu」を宣言し、「addAction」メソッド、引数に文字列で右クリック時に表示される項目を追加できます。「triggered.connect」メソッドに項目が選択された際に実行される関数名を指定します。
このあたりはボタンの時と似ていますね。
def UiShow(): MainWindow = Ui_MainWidget() MainWindow.show()
|
最後にウィンドウを表示する処理で終わりですね。
今回のツールは主にPySideの「QPushButton」と「QListWidget」を使って作成しております。PySideの資料は少ないですが、以下のサイトを参考にしながら勉強しています。
PySide v1.0.7 documentation
http://srinikom.github.io/pyside-docs/
Mayaのコマンド
http://me.autodesk.jp/wam/maya/docs/Maya2010/CommandsPython/
PySideのドキュメントは英語になりますが、同じような単語が並んでいます。そのため、慣れてくるとヒントも見つけやすくなると思います。今回のスクリプトは勉強用も兼ねているので、まだまだ調整したい所が多々あります。
次回のブログで書けることがあったら、またスクリプトについて書きたいと思います。
でも、このネタの需要ってどれくらいあるのかな...
コメント