Page Top
ubSearchMenu Making

挨拶

近況報告としては、前の会社を辞めた際に、アーティスト業は引退し、テック系CG屋に転職しました。プログラマーと言うよりは、エンジニアに近い感じです。

もともとは、ジェネラリストでしたが、当時からTAっぽい仕事が多かった気がします。テックに移って早4年経ち、今はもう、画作りにほぼ関わってないので、TAですらありません。笑

転職の際に、Python を覚える必要があり、慌てて勉強した気がします。世間と比べても、ちょっと遅めのデビューだったと思います。デビューが遅かったおかげか、色々なサイトを見て勉強する事が出来ました。

特に、PySide の記事を多く見かけ、色々カスタマイズされたUIを見て、モチベーション高く学習を続ける事ができました。この場を借りて、情報を公開して頂いてた、多くの方にお礼を言いたいと思います。ありがとうございました。

メニューに関するネタをあまり見かけなかったので、恩返しという訳ではありませんが、自分も情報共有に貢献出来たらと思い、記事にしました。

このページは、Maya Advent Calendar 2019 12/3用に書かれています。

Maya Advent Calendar 2019
https://qiita.com/advent-calendar/2019/maya

完成イメージ

作り方から書いてもあまりピンとこないと思うので、先に完成画像を貼っておきます。

Maya にあるメニューから検索し、アクションを実行するツールです。
せっかくなので、ある程度実用性のあるツールにしてみました。

ダウンロードと、使い方の説明は、ツール紹介を見てください。

Mayaの話

Maya のUIは、Qtで作られてるため、目に見えてる、ほぼすべてのウィジェットに対して、PySide からアクセス出来ます。例えば、Maya の、メインウインドウを取得したいだけなら、shiboken や、MayaQWidgetBaseMixin などを使う必要はありません。オブジェクト名が分かれば、PySide としてWidgetを検索し、取得できます。

shiboken

shiboken は、ざっくり言うと、DCCと、PySide を繋ぐ為のモジュールです。
DCC独自のウィジェットにアクセスしたり、取得したい時などに使う?らしいです。

PySide のドキュメントに、サイズが大きくて切り離した的なコメントも見かけますし、目的に対して、ちょっと大げさなのもあり、自分はこのモジュールを使った事がありません。

MayaQWidgetBaseMixin

MayaQWidgetBaseMixin は、Maya 側が用意してる PySide 用のクラスです。便利なクラスですが、ペアレント先を取得する為だけに、バージョンで仕様がコロコロ変わる Maya のモジュールを読みたくはありません。

都度、DCCを起動してのデバッグも大変ですし、自分は、ウィジェット作成中は、なるべく Maya のモジュールは読まず、スタンドアローンで動くようにしています。

ウィジェットの検索

通常、PySide で作成されたウィジェットには、オブジェクト名が付いています。
オブジェクト名がわかれば、Maya から、ウィジェットをオブジェクトで取得する事が出来ます。

初めて Maya のウィジェットにアクセスする人は、ちょっと規模の大きさに戸惑うと思ったので、ウィジェットの構造が一覧で見れるビューワーがあると便利だと思い、簡単なのですが作ってみました。

ダウンロード / ubWidgetHierarchy

Maya でツールを作成する場合、既に数多くあるツール郡の中の一つとして動作する事になるので、決め打ちで、ハードコーディングしてしまった方が、レスポンス的に都合が良い事が多いです。特にメニュー系などは、都度検索して遅くするより、速度面を優先して意識したいところです。

ちなみに、Maya のメインウインドウ名は、MayaWindow になります。
目的のウィジェットが事前に分かれば、後は名前で検索するだけです。

windowの取得

objectName() や、windowTitle() から取得する方法があります。
この方法は、他のDCCでも使えるので、覚えておくと便利だと思います。

Maya / PySide / MainWindowの取得
https://unitbus.github.io/pages/notes/maya/pyside#mainwindowの取得

今回は、parent先はmenuBarなので、menuBarを取得したいのですが、MayaWindow の、menuBarには objectName() が設定されてないので直接取得する事が出来ません。

また、MayaWindow は、QMainWindow を使わず、QWidget で作成されてるため、QMainWindow.menuBar() メソッドが存在せず、取得できません。
QWidget から辿って、QMenuBar を探す必要があります。

from PySide2.QtGui import *

def getTopLevelWidget(name):
    
    for widget in QApplication.topLevelWidgets():
        
        if widget.objectName() == name:
            return widget
    
    return None

# QApplicationにぶら下がってる、mayaのウインドウを探す
window = getTopLevelWidget('MayaWindow')

# menuBarを探して、objectとして取得
menuBar = window.findChild(QMenuBar)

他にも、HelpMenu を先に取得し、親ウィジェットを探す方法もあったりしますが、脱線するので説明割愛。

メモ

children(), findChildren() 等で取得したオブジェクトは、基本、自身とペアレント関係に無い場合は、関数や、クラスをまたいで利用できません。Internal C++ object ~ already deleted. みたいなエラーが出る時がそれです。自分は オブジェクトの寿命が切れた とか言って、よく席でひとり唸ってます。

エラーが出たら、素直に、関数内で再検索してから使いましょう。
DCC系ツールで、コード整理中に関数をまとめたりしてると、意外と良くハマるので書いておきます。

PySide

これで、準備が整いました。後は Maya ではなく、PySide の話になります。

モジュールの読み込み

自分は、Pysideを読む時は、こんな感じで書いてます。

# 分岐必要ある時は付けたりします
isPySide2 = False

try:
    from PySide2.QtWidgets import * 
    from PySide2.QtCore import *
    from PySide2.QtGui import *
    isPySide2 = True

except ImportError:
    from PySide.QtCore import *
    from PySide.QtGui import *

PySide2 を上に書いてるのは、読み込む優先度を上げる為です。

PySide をtryに書いてしますと、PySide がなかったら…、と言う意味になります。つまり、PYTHONPATHPySide が含まれてた場合、Maya の推奨のバージョン無視して PySide を使う可能性がある為です。

逆の事も言えますが、対応してない、maya2014 などで、Python2 用にわざわざ自分でコンパイルして、PYTHONPATH に、PySide2 のパスを通す奴は、まず居ないと思います。

なので、上位互換を考えるより、下位互換に配慮して、モジュールのインポートをした方が良いと思います。

QMenu

QMenu は、QDialog みたいに、自身をアプリケーションのように振る舞う事ができ、他のスレッドを待機させる事が出来る、exec_() メソッドが用意されてます。待機させる処理を、イベントループといい、スタンドアローンを作る際に、QApplication でオマジナイみたいに使ってるアレと同じです。
これを利用して、右クリックメニューや、ショートカットで起動する、簡易ランチャー的な物などを作る事が可能になります。

QMenu だと、 popup() メソッドも用意されてて、同じような動作します。こちらは、目的の位置を指定する事で、マウスカーソルの位置に表示させる事ができます。

QMenu のドキュメント見ると、exec_() は、popup() をオーバーライドしてるだけで、多分、カーソルの位置を引数で渡した場合は、同じコマンドが実行されてると思います。exec_() は引数省略出来ますが、popup() は出来ません。

QAction

QAction は、簡単にアクション(メニューコマンド)を作れるテンプレートみたいなもので、非常に便利ですが、アイコンと、チェックボックス位しかカスタマイズ出来ません。

QWidgetAction を使うと、自分で好きなウィジェットでアクションを作る事が可能です。QWidgetAction は、QActionを継承してるので、通常のアクションと同じように扱え、メニューに追加できます。

Maya メニューは、ツールオプションがアイコンで用意されてるので、QWidgetAction を利用して作られてるみたいです。

アクションの検索

なぜ、Maya の機能を使わずに、PySide だけで、こんなツールが作れるかと言うと、mel や、maya.cmds で作成したウインドウや、コントロールも、すべてQtに変換されて作られてるからです。

なので、名前を付けておけば、PySideQWidget として取得出来ます。
いったんオブジェクトとして取得できれば、後は、PySide の機能をフルに扱う事が出来ます。

Maya のコマンドは、いわば、PySide の、warpperみたいな役割をしています。ぶっちゃけ、さほど便利になってないので、wrapperと言うより、旧MELコマンドの、ウインドウや、コントロールを、Qtで再現してるだけと言った感じですが…。

存在しないFrameLayoutとかは、独自UIとして実装されてますが、作成すると親のQWidgetを取得する事が出来るので、自分の PySide ツールに組み込む事も可能です。

履歴の登録

せっかくアクションを、QAction として、オブジェクトで取得したので、自前の QMenu に入れてしまいます。

同じコマンドを何度登録しても、QAction の実態はひとつなので、同じメニューに二回目以上登録しても、順番が新しくなるだけで重複しません。この挙動は、まさに履歴にそっくりなので、そのまま利用してます。

QAction は、元々複数のウィジェットで使い回す想定の仕組みになってるので、まったく同じアクションを、QToolBar, QMenu, QSystemTrayIcon などで、同時に共存させる事が出来ます。超便利です。

melからのアクセスだと、別のメニューに登録する=移動 なので、こう言った融通が効きません。

ツール紹介

ダウンロード

以上説明した技術を使い、ツールを作ってみました。
こちらからダウンロードできます。

Dwonload / Maya / Python / ubSearchMenu
https://unitbus.github.io/pages/download

ダウンロードしたファイルは、Documents/maya/scripts に置いてください。
スクリプトエディタで以下のコマンドを実行するとメニューに追加されます。

import ubSearchMenu
ubSearchMenu.show()

ショートカットに登録

ショートカット用のコマンドも用意してあります。
カーソルの位置にメニューを出せるので、登録しておくと便利かと思います。

自分は、 Hide All UI Elements は使わないので、 [Ctrl + Space] に登録してます。Hotkey Editor > Rantime Command Editor から、 Python を選んで、下のコマンドを登録してください。

import ubSearchMenu
ubSearchMenu.popMenu()

補足

Maya のメニューは表面だけ作成されてて、アクセス時にMELコマンドで作成される仕様の物が多いです。表面的なメニューは、モジュールインポート時にロードするようにしましたが、実際に一度メニューの場所までアクセスしないと、検索に出てこないコマンドもあるかと思います。

よく使うメニューが隠れてる場合は、ScriptEditor > Echo ALL で表示される、メニュー作成コマンドを見つけて、自分で足してください。

初回起動時に、数秒ロードが入るので、遅いと感じる人は、逆に、使わないメニューコマンドをコメントアウトしちゃってください。

少し書き換えれば、他のDCCでも使えると思います。SearchMenu クラスは、PySide だけで書いてあるので、そのまま使えると思います。Maya のウィジェットを探してる部分だったりを、DCC別に合わせて書き換える感じです。

Nuke は、試してませんが、多分、同じ要領でイケると思います。

Houdini は、ほぼ自前UIなので、PySide でアクセスできる部分が少ないです。メニューバーも独自仕様なので、追加するには、houモジュール経由でアクセスするしかありません。

余談

履歴の保存とか出来れば便利かな、とも思いましたが、記事のボリュームが結構大きくなったのでやめました。需要あれば考えますが、独自のカスタマイズを望む方は、お仕事として相談して頂けると嬉しいです。笑

最後に

ちまたに溢れるCGネタを肴に盛り上がれたらと思い、Slack 始めました。
お気軽に参加してください。参加の仕方がわからない方は、Twitterのダイレクトメッセージをして貰えれば招待を送りします。

Unitbus Slack.
https://unitbus.slack.com

UnitBus Twitter.
https://twitter.com/UnitBus

お仕事の相談も募集してます。フルタイム勤務は無理ですが、細かいツールの依頼や、ワークフローの改善案など、おチカラになれそうな相談事あればご連絡ください。お仕事の話も、メールで頂くより、Slackのダイレクトメッセージで頂けると嬉しいです。