Maya PySide2 / PySide チュートリアル 初級編 ② – .uiをMayaで表示する

python,qt,PySide,PySide2,Tutorial

このチュートリアルでは.uiをMaya上で表示する方法を学びます

準備

チュートリアルを始める前に__init__.pyvtxMain.pyの二種類のpythonファイルを用意し、前回作成したsampleフォルダの中に入れてください
uiフォルダの中には前パートで作成した.uiファイルのvtxMain.uiが保存されています

データ構成

C:\Users\<USERNAME>\Documents\maya\scripts\sample\ui\vtxMain.ui
C:\Users\<USERNAME>\Documents\maya\scripts\sample\__init__.py
C:\Users\<USERNAME>\Documents\maya\scripts\sample\vtxMain.py

20220402_01

.uiのイメージ

WidgetはPySideのUIを構築するパーツの1単位で基本的にPySideのGUIはWidgetというGUIのパーツとなります
このPySideが標準で用意しているWidgetも分解してみれば複数のWidgetで構成されたテンプレートのGUIのようなものだったりします
つまり、.uiで構築したGUIもザックリととらえるとWidgetという1パーツとして捉えてもらっても差し支えがあまりないのです
複数の.uiを用意し、複数の.uiを大きな別のWidgetに入れ、GUIを構築するなんてことはよく用いることです

今回も似たような形でvtxMain.uiというwidgetをQMainWindowというWidgetのCentralWigetという配置場所に表示してみます

20220402_02
Maya PySide2 / PySide チュートリアル 初学編 ⑤ – メインアプリケーションを作ろうでもQMainWindowについて触れましたがQMainWindowはアプリケーションのユーザーインターフェイスを構築するためのフレームワークを提供しており、 QToolBarやQDockWidgetなど追加できる独自のレイアウト機能が存在しています
今回のチュートリアルではそのあたりは触れませんがアプリケーションとして作成する際は便利なWidgetとなるため、カスタムしたWidgetをセットするという感覚に慣れてもらうためにあえてQMainWindowを使用してみます

# !/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys

try:
    from PySide2.QtWidgets import *
    from PySide2.QtCore import *
except ImportError:
    from PySide.QtGui import *
    from PySide.QtCore import *

class vtxMainWindow(QMainWindow):

    def __init__(self, parent=None):
        super(vtxMainWindow, self).__init__(parent)
        self.setGeometry(500, 300, 400, 270)
        self.setWindowTitle(".ui MainWindow")


def main():
    app = QApplication.instance()
    mainWin = vtxMainWindow()
    mainWin.show()
    sys.exit()
    app.exec_()

上のコードをvtxMain.pyに記述してください
今回の例では、単純にQMainWindowが表示されるひな型になります
このUIをMaya上で使用するには下のコードをMayaのScript Editorで実行する必要があります
Script EditorはMayaの右下、上の画像のような位置にあるボタンから起動できます
20220402_05
起動すると下の画像のようなWindowが表示されるかと思います
20220402_05
そのWindowの中央にmelとpythonといった文字が書かれているtabがあるのでそのタブのpythonを選択しそこに下のコードをコピペして実行してください

import sample.vtxMain as vtxMain
reload(vtxMain)
vtxMain.main()

実行すると下の画像のような何もないWindowが表示されます

20220402_03

何も表示されずエラーが出たりする場合はscripの保存場所は名前の大文字小文字が間違っていたり、全角になっていたりするかもしれません
その場合は改めてデータ構成を確認してみてください

うまく起動ができた人は下のコードを追加してください

try:
    from PySide2.QtWidgets import *
    from PySide2.QtCore import *
    from PySide2.QtUiTools import QUiLoader
except ImportError:
    from PySide.QtGui import *
    from PySide.QtCore import *
    from PySide.QtUiTools import QUiLoader

このfrom PySide2.QtUiTools import QUiLoaderは.uiからGUIを構築するために使用するもので、QUiLoaderで.uiを読み込むには.uiのpathを知らなければなりません

詳細説明

QUiLoaderクラスを使用すると、スタンドアロンアプリケーションは、UIファイルに格納されている情報、またはプラグインパスで指定されている情報を使用して、実行時にユーザーインターフェイスを動的に作成できます


class vtxMainWindow(QMainWindow):
    __currentPath = os.path.dirname(__file__)
    __uiFilePath = os.path.join(__currentPath, "ui", "vtxMain.ui")

    def __init__(self, parent=None):
        super(vtxMainWindow, self).__init__(parent)

class vtxMainWindow(QMainWindow): def __init__(self, parent=None):の間にクラス変数__currentPath__uiFilePathを追加しました

__currentPathはその名の通り、現在のpathという意味で__file__のdirnameを指定しています
__file__はPythonで実行中のスクリプトファイル.pyの場所(パス)を取得することができるものです
os.path.dirname()はパス(文字列)を代入している変数から、フォルダ名(ディレクトリ名)を取得することが可能なメソッドになります
このos.path.dirname()__file__の二つを組み合わせることで現在のpathを取得しています
そして、次に__uiFilePathはその名の通り.uiファイルのpathになるのですがos.path.joinを使うことで引数に与えられた文字列を結合させ、一つのパスにすることを行っています
vtxMain.uiはuiフォルダに格納しているので現在のパス、フォルダ名、uiファイル名を引数に指定することで.uiのpathを取得できるようにしています


class vtxMainWindow(QMainWindow):
    __currentPath = os.path.dirname(__file__)
    __uiFilePath = os.path.join(__currentPath, "ui", "vtxMain.ui")

    def __init__(self, parent=None):
        super(vtxMainWindow, self).__init__(parent)
        loader = QUiLoader()
        uiWidget = loader.load(self.__uiFilePath)
        self.setCentralWidget(uiWidget)
        self.setGeometry(500, 300, 400, 270)
        self.setWindowTitle(".ui MainWindow")

次に.uiをQUiLoaderを使って読み込んでみます
.uiを読み込むにはQUiLoaderのload()使用します
loader = QUiLoader()で変数名を宣言し、loader.load(self.__uiFilePath)で呼び出した.uiをuiWidgetに入れます
そして、呼び出した.uiをQMainWindowのCentralWidgetにself.setCentralWidget(uiWidget)でセットしています
この状態で再度Script Editorで呼び出すと下の画像のようにQt Designerで作成したGUIが表示されます
20220402_06

さてここで一つ大きな問題があります
それは表示したWindowがMayaのほかのUIを触れたり、ほかのソフトウェアなど表示したWindowからフォーカスが外れると突然Windowが消えてしまう問題です
20220402_07
これは消えているのではなくMayaのWindowの裏側に回っているだけで実際は消えていないのですがこれはこれでとても使いづらく、めんどくさく感じることでしょう
基本的にPySideで作成するWindowはMayaと親子関係を付けてあげる必要があります
そうしなければ後ろに回ってしまい使いにくいツールになってしまうからです

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):
## 省略しています

def main():
    app = QApplication.instance()
    mainWin = vtxMainWindow(parent=mayaMainWindow)
    mainWin.show()
    sys.exit()
    app.exec_()

import文にimport maya.OpenMayaUI as OpenMayaUIimport shiboken2 as shibokenを追加しました
OpenMayaUI.MQtUtilはMayaのUIにアクセスするためのクラスでOpenMayaUI.MQtUtil.mainWindow()を使用することでMayaのメインウィンドウにアクセスしています
このままだと使用できないため、shiboken.wrapInstanceというものを使用してPySideで使えるようにします
詳しくはわからなくていいですがMayaのUI情報をいい感じに使えるようにする便利屋さんと思っていればいいです

使えるように加工したmayaMainWindow = shiboken.wrapInstance(long(ptr), QMainWindow)
mainWin = vtxMainWindow(parent=mayaMainWindow)でvtxMainWindowとMayaのMainWindowの親子関係をつけました
この状態で実行すると表示されたWindowが裏に回るということはなくなります。

詳しく
MayaにはmayaMixin.MayaQWidgetBaseMixinというMayaとPySideの親子付けを簡単にできるクラスがあるのでこちらを使用してもいいのですがこのクラスが追加された次のバージョンかその次のバージョンのマイナーバージョンで突然使えないという不具合に悩まされたことがあるので基本的に上記のやり方を私は行っていますがこの辺はお好みでどうぞ


Maya PySide2 / PySide チュートリアルのこのパートでは、.uiをMaya上で表示させる方法を学びました

次はMaya PySide2 / PySide チュートリアル 初級編 ③ – GUIからMayaのコマンドを実行、BuddyやTab orderの設定方法
前はMaya PySide2 / PySide チュートリアル 初級編 ① – Qt Designer を使ってみる