Maya PySide2 / PySide チュートリアル 初級編 ④ – .uiとPythonのコーディングを組み合わせてみる
このチュートリアルでは PySide で.uiとPythonのコーディングと組み合わせる方法を学びます
Mayaのシーン上にあるメッシュをアウトライナーのような感じでリストで表示
表示したメッシュをWidget上で選択できる
選択したメッシュにVertexColorを設定する
という内容を作ってみます
完成イメージは下の画像のようなイメージです
実はSignal&SlotをQtDeisgner上で行うこともできるのですが今回はPython上で行います
QtDeisgnerでレイアウト
リスト表示させることができるWidgetが既にPySideでは用意されており、Item Widgets項目にあるList Widgetと名前の通りのWidgetです
一つ上の項目のItem Views項目にあるList Viewというものもありますが今回はこちらは使用しません
Viewではなく、Widgetを使用しましょう
配置すると上の画像のようにSpinBoxがつぶれてしまうかもしれませんがこれはsizePolicyの問題なのでProperty EditorでsizePolicy>Horizontal Polocy>Preferred, sizePolicy>Vertical Polocy>Preferredに設定してください
下の画像のようになっていたらうまく設定できています
この辺りは実際に起動した際とQtDeisgner上で若干見た目が変わることがあるので自分の意図した見た目になっているのであれば問題ないかと思います
Class | ObjectName |
---|---|
QCheckBox | checkBox_IsSelect |
QListWidget | listWidget |
それぞれの名前を上の表のように設定できたら.uiのレイアウトが完成です
コード
# !/usr/bin/env python # -*- coding: utf-8 -*- import os import sys import maya.cmds as cmds import maya.OpenMayaUI as OpenMayaUI try: from PySide2.QtWidgets import * from PySide2.QtCore import * from PySide2.QtUiTools import QUiLoader import shiboken2 as shiboken except ImportError: from PySide.QtGui import * from PySide.QtCore import * from PySide.QtUiTools import QUiLoader import shiboken ptr = OpenMayaUI.MQtUtil.mainWindow() mayaMainWindow = shiboken.wrapInstance(long(ptr), QMainWindow) class vtxMainWindow(QMainWindow): __currentPath = os.path.dirname(__file__) __uiFilePath = os.path.join(__currentPath, "ui", "vtxMain.ui") __geometries = [] def __init__(self, parent=None): super(vtxMainWindow, self).__init__(parent) loader = QUiLoader() uiWidget = loader.load(self.__uiFilePath) self.setCentralWidget(uiWidget) self.resize(400, 270) self.setWindowTitle(".ui MainWindow") self.installEventFilter(self) self.centralWidget().listWidget.setSelectionMode(QAbstractItemView.ExtendedSelection) self.__initSignalSlot() def __initSignalSlot(self): self.centralWidget().button_Apply.clicked.connect(self.setVertexColor) self.centralWidget().listWidget.itemClicked.connect(self.selectItem) self.centralWidget().checkBox_IsSelect.stateChanged.connect(self.selectItem) def setVertexColor(self): current = cmds.ls(sl=True) meshList = [] if self.centralWidget().checkBox_IsSelect.isChecked(): for item in self.centralWidget().listWidget.selectedItems(): meshList.append(item.text()) else: meshList = cmds.ls(sl=True) for mesh in meshList: cmds.select(mesh) cmds.polyColorPerVertex( r=self.centralWidget().doubleSpinBox_R.value(), g=self.centralWidget().doubleSpinBox_G.value(), b=self.centralWidget().doubleSpinBox_B.value(), a=self.centralWidget().doubleSpinBox_A.value() ) cmds.setAttr("%s.displayColors" % mesh, 1) cmds.select(current) def selectItem(self): if self.centralWidget().checkBox_IsSelect.isChecked(): cmds.select(cl=True) for item in self.centralWidget().listWidget.selectedItems(): cmds.select(item.text(), add=True) def eventFilter(self, object, event): if event.type() == QEvent.WindowActivate \ or event.type() == QEvent.FocusOut: self.widgetReload() return True return False def widgetReload(self): self.centralWidget().listWidget.clear() for count, geometry in enumerate(self.geometries): self.centralWidget().listWidget.insertItem(count, geometry) @property def geometries(self): self.__geometries = [] meshlist = cmds.ls(type="mesh") for mesh in meshlist: self.__geometries.append(cmds.listRelatives(mesh, fullPath=True, parent=True, type="transform")[0]) return self.__geometries def main(): app = QApplication.instance() mainWin = vtxMainWindow(parent=mayaMainWindow) mainWin.show() sys.exit() app.exec_()
ひとつ前のパートから5種類の関数が増えました
関数 | 説明 |
---|---|
__initSignalSlot | Signals&Slotsを行う関数 |
selectItem | listWidgetで選択したMeshをMaya上で選択状態にする関数 |
eventFilter | クラスvtxMainWindowで起きたイベントの受け取りを行う関数 |
widgetReload | listWidgetのQListWidgetItemをリロードする |
geometries | Mayaシーン内のmeshをlistするプロパティの定義 |
class vtxMainWindow(QMainWindow): __currentPath = os.path.dirname(__file__) __uiFilePath = os.path.join(__currentPath, "ui", "vtxMain.ui") __geometries = [] @property def geometries(self): self.__geometries = [] meshlist = cmds.ls(type="mesh") for mesh in meshlist: self.__geometries.append(cmds.listRelatives(mesh, fullPath=True, parent=True, type="transform")[0]) return self.__geometries
まずはdef geometries(self):
から解説していきますdef geometries(self):
はシーン内にあるmeshのTransfromを__geometries
にappendし、listを作成しています
試しに呼び出してみるとちゃんとMayaシーン内にあるMeshをリストしてくれていることが確認できます
def widgetReload(self): self.centralWidget().listWidget.clear() for count, geometry in enumerate(self.geometries): self.centralWidget().listWidget.insertItem(count, geometry)
def widgetReload(self):
は呼び出されたタイミングで現在設定されているWidgetItemをすべて削除し、
プロパティgeometries
を呼び出し、再度QListWidgetにinsertItemで項目を追加しています
QListWidgetのサンプル
import sys from PySide2.QtWidgets import QListWidget, QApplication class ListWidget(QListWidget): def __init__(self): QListWidget.__init__(self) self.insertItem(1, "Orange") self.insertItem(2, "Blue") self.insertItem(3, "White") self.insertItem(4, "Green") app = QApplication.instance() lw = ListWidget() lw.show() sys.exit() app.exec_()
QListWidgetのもっとも小さなサンプルです
insertItemとインサートしたいrow、stringを指定することで項目を追加することができます
Qtのイベント処理
Qtにはアプリケーション内や外部の活動の結果として発生した、例えばWindowがアクティブ、クローズ、フォーカスなどイベント自体を処理するのではなく、配信されたイベントの種類に基づいて、その特定の種類のイベントのイベントハンドラを呼び出し、イベントが受け入れられたか無視されたかに基づいて応答を送信させることができます
イベント通知の受け取りは通常、仮想関数を呼び出すことで例えば、QPaintEvent
はpaintEvent()
を呼び出すことによって受け取りすることができます
時には、あるオブジェクトが他のオブジェクトに配信されるイベントを見たり、場合によっては傍受したりする必要があるのですが今回はclass vtxMainWindow(QMainWindow):
で発声されるイベントを元にself.widgetReload()
を呼び出しています
イベントのタイミングはQEvent.WindowActivate
とQEvent.FocusOut
のタイミングにしています
eventFilterを有効にするにはinstallEventFilter
の呼び出しが必要なためself.installEventFilter(self)
で呼び出しています
class vtxMainWindow(QMainWindow): def __init__(self, parent=None): self.installEventFilter(self) def eventFilter(self, object, event): if event.type() == QEvent.WindowActivate \ or event.type() == QEvent.FocusOut: self.widgetReload() return True return False
今回のようなツールでは常に監視し、ListWidget上に表示されたmeshが最新である必要はないため、GUIを使用する際に最新の状態になっていればよいとしています
もちろんscriptJobやほかのイベントを使用すれば常に最新と同期させることもできるのでこの辺りはケースバイケースで設定してみてください
def __init__(self, parent=None): self.centralWidget().listWidget.setSelectionMode(QAbstractItemView.ExtendedSelection) def selectItem(self): if self.centralWidget().checkBox_IsSelect.isChecked(): cmds.select(cl=True) for item in self.centralWidget().listWidget.selectedItems(): cmds.select(item.text(), add=True)
このコードではcheckBox_IsSelectが有効になっている場合、Maya上の選択と同期し、無効になっている場合はMaya上の選択は変わらず何もしません
複数選択も行いたいので.setSelectionMode(QAbstractItemView.ExtendedSelection)
でselectionModeを変更しています
def setVertexColor(self): current = cmds.ls(sl=True) meshList = [] if self.centralWidget().checkBox_IsSelect.isChecked(): for item in self.centralWidget().listWidget.selectedItems(): meshList.append(item.text()) else: meshList = cmds.ls(sl=True) for mesh in meshList: cmds.select(mesh) cmds.polyColorPerVertex( r=self.centralWidget().doubleSpinBox_R.value(), g=self.centralWidget().doubleSpinBox_G.value(), b=self.centralWidget().doubleSpinBox_B.value(), a=self.centralWidget().doubleSpinBox_A.value() ) cmds.setAttr("%s.displayColors" % mesh, 1) cmds.select(current)
setVertexColor
もcheckBox_IsSelectが有効化無効化で処理が変わるように変更しています
有効の場合はlistWidgetで選択されたメッシュに対してvertexColorの設定
無効の場合はMaya上で選択中のメッシュに対してvertexColorの設定
class vtxMainWindow(QMainWindow): def __init__(self, parent=None): self.__initSignalSlot() def __initSignalSlot(self): self.centralWidget().button_Apply.clicked.connect(self.setVertexColor) self.centralWidget().listWidget.itemClicked.connect(self.selectItem) self.centralWidget().checkBox_IsSelect.stateChanged.connect(self.selectItem)
__initSignalSlotでは、signals&Slotsを行っています
button_Applyでは前回のパートでクリックした際にバーテックスカラーの設定が行われるsetVertexColor
を
listWidgetでitemClickedした際にselectItem
を
checkBox_IsSelectではチェックボックスがオンオフの際にselectItem
を呼び出しています
Maya PySide2 / PySide チュートリアルのこのパートでは、.uiとUIのコーディングと組み合わせる方法を学びました
次はMaya PySide2 / PySide チュートリアル 初級編 ⑤ – データフォーマットを扱ってみる
前はMaya PySide2 / PySide チュートリアル 初級編 ③ – GUIからMayaのコマンドを実行、BuddyやTab orderの設定方法
ディスカッション
コメント一覧
まだ、コメントがありません