Maya

PySideの小ネタ

はじめに

こんにちは。モノリスソフト テクニカルアーティストの菅原です。
今回はMayaでツールを作成するにあたり、自分が使うPySide周りの小ネタをご紹介できればと思います。

ソースファイルを記事内に含めるととても長くなってしまいますので、この記事ではどういった処理かとその使用例をまとめます。
記事内の使用例はサンプルからの切り抜きとなり、他の処理も必要になってくるためそのままでは動作しません。

詳細な処理内容をご覧になりたい方・実際に使用してみたい方は添付されているサンプルファイル(monolithtech_pyside.zip 10.7KB)をダウンロードしてください。

サンプルファイル

実行方法

  1. %USERPROFILE%/Documents/maya/2019/scriptsの中に解凍したtechblog_pysideをフォルダごとコピーしてください。
    フォルダ構造としては以下のようになります。
    DOCUMENTS\MAYA\2019\SCRIPTS
    │
    └─techblog_pysideフォルダ
        │  __init__.py
        │
        ├─mixinフォルダ
        ├─uiフォルダ
        └─utilsフォルダ
    
  2. Mayaを起動して以下のPythonをスクリプトエディタから実行してください。

    サンプル実行方法

    import techblog_pyside
      
    # PySideで作成したFrameLayout
    techblog_pyside.frame_main()
      
    # UIの動的追加処理
    techblog_pyside.variadic_main()
      
    # 仮ツール
    techblog_pyside.default_main()
      
    # 仮ツールにファイルメニュー・ヘルプメニューの機能追加
    techblog_pyside.menu_main()
      
    # 全部
    techblog_pyside.main()
    

PySideで作成したFrameLayout

tech_05_02.png
tech_05_03.png

maya.cmdsのUIとして良く使用されるFrameLayoutを真似てPySideで実装したものです。
▼印のバー部分をクリックすると内部Layoutに設定したWidgetの表示・非表示が切り替えられます。

使用例

class FrameLayoutWidget(mayaMixin.MayaQWidgetBaseMixin, qt.QtDefaultWidget):
    def __init__(self, parent=None):
        super(FrameLayoutWidget, self).__init__(parent)
        self.setLayout(QtWidgets.QVBoxLayout())
        self.setObjectName('FrameLayoutWidget')
  
        # PySideで作成したFrameLayout
        self.__dummy_frame = qt.FrameLayout(self)
        # FrameLayoutが閉じた状態になった時の処理を登録
        self.__dummy_frame.frame_btn.collapsed.add(lambda: print('collapsed'))
        # FrameLayoutが開いた状態になった時の処理を登録
        self.__dummy_frame.frame_btn.expanded.add(lambda: print('expanded'))
        # FrameLayoutをWidgetに追加
        self.layout().addWidget(self.__dummy_frame)
  
        for i in range(5):
            # FrameLayoutに追加するボタンを作成
            btn = QtWidgets.QPushButton(str(i))
            # ボタンが押されたときの処理を登録
            btn.clicked.connect(functools.partial(print, i))
  
            # FrameLayoutにボタンを追加
            self.__dummy_frame.frame_layout.addWidget(btn)

UIの動的追加処理

tech_05_04.png
tech_05_05.png

動的にUIを追加したい場合に使用する処理です。
追加したUIへのアクセスはQtVariadic.instancesから取得できる作りになっています。

使用例

class PathForm(ui_path.Ui_Form):
    """UIの動的追加処理に設定したいUIの中で完結できる部分の処理を実装
    """
    def __init__(self):
        super(PathForm, self).__init__()
        self.__subject = common.Subject()
  
    @property
    def updated(self):
        """関数登録部分の外部公開
        """
        return self.__subject.listen
  
    def setupUi(self, Form):
        """SIGNAL・SLOTの追加設定
        """
        super(PathForm, self).setupUi(Form)
        self.lineEdit.textChanged.connect(self.__on_path_changed)
        self.toolButton.clicked.connect(functools.partial(self.__on_btn_clicked, self.lineEdit))
  
    def __on_path_changed(self):
        """LineEdit変更時の登録された関数への通知
        """
        self.__subject.emit(os.path.basename(self.lineEdit.text()))
  
    def __on_btn_clicked(self, line_edit):
        """ボタン押下時のLineEditへのフォルダパス設定
        """
        p = QtWidgets.QFileDialog.getExistingDirectory()
        if p:
            line_edit.setText(p)
  
  
class QtVariadicWidget(mayaMixin.MayaQWidgetBaseMixin, qt.QtDefaultWidget):
    def __init__(self, parent=None):
        super(QtVariadicWidget, self).__init__(parent)
        self.setLayout(QtWidgets.QVBoxLayout())
        self.layout().setAlignment(QtCore.Qt.AlignTop)
        self.setObjectName('QtVariadicWidget')
  
        # 動的なUIの追加処理に使用するボタン
        btn_layout = QtWidgets.QHBoxLayout()
        # 追加用ボタン
        add_btn = QtWidgets.QPushButton('+', self)
        add_btn.setObjectName('add_btn')
        # 削除用ボタン
        sub_btn = QtWidgets.QPushButton('-', self)
        sub_btn.setObjectName('sub_btn')
        # Layoutに配置
        btn_layout.addWidget(add_btn)
        btn_layout.addWidget(sub_btn)
        self.layout().addLayout(btn_layout)
  
        # Layoutに対して動的なUI追加を行う処理の作成と追加できる最小・最大の設定
        self.__variadic = qt.QtVariadic(PathForm, self.layout(), 2, 10)
        # 追加用ボタンの登録
        self.__variadic.register_add_btn(add_btn)
        # 削除用ボタンの登録
        self.__variadic.register_sub_btn(sub_btn)
        # 動的に追加されたUIに対して設定するコールバック処理
        self.__variadic.register_callback({'updated.add': print})
        # 最小の初期化されたUIの作成
        self.__variadic.initialize()

多重継承による機能追加

tech_05_06.png

以下の処理は画像のような元々のツール(MixinWidgetクラス)があったと仮定し、それに対して多重継承を用いて機能追加を行っています。

  • ファイルメニュー・ヘルプメニューの機能追加

この記事で使用しているMayaのツールUIへの多重継承による機能追加の手法について、機能追加用クラスの作成時・使用時にいくつか注意点がございますので、
同じような作りで処理を作成される場合は以下の点について気を付けてください。

  • 作成時
    • 機能追加クラスの親クラスはobjectとする
    • 機能追加クラスはinitの引数でparentを受け取れるようにする
    • 機能追加クラスのinitの先頭にはsuperで親クラスのinitを呼ぶような処理を書く
  • 使用時
    • あるクラスの機能を前提とした機能追加の場合は多重継承時に機能追加クラスを前提クラスの左側に書く必要がある
      • 例:QWidgetクラスを前提としたMayaQWidgetBaseMixinクラスの機能追加は以下のように書く
class Hoge(MayaQWidgetBaseMixin, QWidget):
    def __init__(self, parent=None):
        super(Hoge, self).__init__(parent)
.
.
.

今回は上記の点に注意して作成・使用したものを掲載しています。
もっと詳しい内容についてはpython mroやMixinで検索してみてください。

ファイルメニュー・ヘルプメニューの機能追加

tech_05_07.png
tech_05_08.png

ファイルメニューはUIの状態保存・復元機能です。
ヘルプメニューはURLを指定してドキュメントへのリンク作成機能となっています。

多重継承している各クラスの役割は以下になります。

  • menu.MinimumFileMenuMixin
    • ファイルメニュー作成
  • menu.MinimumHelpMenuMixin
    • ヘルプメニュー作成
  • menu.MenubarMixin
    • メニューバー作成

ファイルメニュー・ヘルプメニュー作成機能がメニューバー作成機能を前提としているため使用例のような継承順になっています。

使用例

class MenuMixinWidget(menu.MinimumFileMenuMixin, menu.MinimumHelpMenuMixin, menu.MenubarMixin, MixinWidget):
    def __init__(self, parent=None):
        # MixinWidget→MenubarMixin→MinimumHelpMenuMixin→MinimumFileMenuMixinの順でinitの処理が実行される
        super(MenuMixinWidget, self).__init__(parent)
        self.setObjectName('MenuMixinWidget')
  
        # メニュー作成
        self.create_file_menu()
        self.create_help_menu('https://www.monolithsoft.co.jp/')

まとめ

いかがでしたでしょうか。

今回の記事はPySideの小ネタをご紹介いたしました。今後PySideでツールを作る必要があった時の手助けになれましたら幸いです。

AUTHOR:菅原

インタラクティブコンテンツ制作会社を経てモノリスソフトへ入社。 以来、テクニカルアーティストとして主にMayaツール開発の業務を担当。 好きな食べものはごはん。