Maya らしい マイクロインタラクション でツールを作る

Maya AdbentCalender 2018 7日目の記事です。体調不良と~~スマブラ~~が重なって投稿が遅くなりましたが、なんとか書ききれたので、必要最低限にコードの視認性を意識したGUIの組み方をまとめました。
@amanatsu-knit さん、遅れてすみませんでした。

## 表題に至った経緯
VFXの現場ではMaya以外にHoudiniやNukeを併用するので、カスタムツールを作るために独自uiを覚える意味をあまり見出せません。それらのアプリケーションがパイプラインに組み込まれないゲーム業界だと、Qtで作る意味があんまりなかったりします。

今年に入っていくつか maya.cmds で組まれたツールを引き継いで感じた事は、QtでMayaらしいui開発をするためには「雑用」が多くなるという現実です。ボタンが少ししかないツールのためにレイアウトを頑張って親子付したり、ウィンドウの重複を避ける、実行した時のパラメータでもう一回開くなど… shibokenも面倒です。

堅実で使い慣れたUIが求められることが多い現場で、スタイルシートやスタイルまで操れるQtを駆使するのは骨が折れます。[^1] そこで適当に書いてもそれっぽくなる maya のビルトイン ui メソッドを使ってツールを構築してみます。

カッコ良いツールの書き方を求めている方はこのカレンダーに素晴らしい記事が上がっている(日本のTAすごい)なので、そちらを参照してください。

## 実装
すべてのGUIツールの foundation ui を作ります。先に目的について述べましたが、Mayaのデザインルールに則って作ることで、個々人の美的感覚に左右されなくなるのは大きなメリットです。[^2]

基本となる要素は以下の5つです。経験上、以下項目を実装すれば文句を言われることはありません。[^3]

1. Helpメニュー
2. FrameLayout
3. 底面にボタン３つ
4. 重複しないウインドウ
5. optionVar によるパラメータ復元


ちなみにMELでは組みません。というか、MELで組んだGUIで保守しやすいコードを見たことがありません。エンドユーザー側では綺麗なguiに見えても、実装コードを他の担当者が引き継ぐのはほぼ無理です。へいしゃは独自ラッパーがあるので通常それを使って ui を組みますが、流石にソース出したら怒られるので、頑張って`pymel`で書きます。

## コード
```python:widget.py
# -*- coding: utf-8 -*-
from pymel import core as pm
from pymel.core import uitypes as ui


def pressed(*args):
    if args:
        print(args)
    else:
        print("Other func")


class Widget(object):
    """
    0.Hide 重複しない
    1.Menu Help
    2.FrameLayout
    3.Buttons 底面に3つ
    4.OptionVar による復元
    """

    def __init__(self):
        self.name = "Foundation"
        self.size = [400, 320]
        self.options = pm.optionVar
        self.text = None

    def show(self):
        # 0. 重複しない
        if pm.window(self.name, ex=True):
            pm.deleteUI(self.name)

        with pm.window(self.name, t=self.name + "Widget", wh=self.size, menuBar=True):

            # 1. メニュー
            with ui.Menu(label="Help"):
                ui.MenuItem(label='Help on %s' % self.name, c=pm.Callback(pressed, "Help"))

            # 2. FrameLayout + メイン
            with ui.AutoLayout(ratios=[3, 1]):
                with ui.FrameLayout(l="Params"):
                    with ui.AutoLayout():
                        self.text = ui.TextFieldGrp(label="Value")

                # 3. 底面ボタン
                with ui.AutoLayout(orientation="horizontal"):
                    ui.Button(label='Other', c=pm.Callback(pressed))
                    ui.Button(label='Apply', c=pm.Callback(self.apply))
                    ui.Button(label='Close', c=pm.Callback(pm.deleteUI, self.name))

        # 4. optionVar による値の復元
        self.text.setText(self.options["text"])

    def apply(self, *args):
        """ここは引数を渡すだけ"""
        text = self.text.getText()
        pressed(text)
        self.options["text"] = text


if __name__ == "__main__":
    window = Widget()
    window.show()
```

with statement を使うことで、parent を気にせず視認性が上がるのがポイントです。
ui の command フラグに実行する関数を渡す方法はいくつかあるのですが、Callback を使った書き方がロジックとguiを分離しやすいため、これを採用しています。
AutoLayout は formLayout をいい感じにしてくれる便利なメソッドなのでよく使います。


## まとめ
所属セクションに求められる役割に従って、最も適した道具を自由に使えると良いですよね。

派手さより学習コストが低い、ボタン押すだけみたいなツールが好まれるのは技術者としてつまらなく思うこともあります。そんなツール Maya が提供しろ、と他のツールを触ってみたらすぐわかりそうなものなんですが何かと手がかかるからこそ専任のTAを欲している現実も否めません。

そういうツールばかり作っているとエンジニア力が伸びないので、Qtなどで学習しつつ納期や要求に柔軟に対応出来るTAが求められる時代だと思います。



[^1]: TAの本来の役割は斬新なツールを作ることではない。

[^2]: 付け加えていうなら、Qtの書き方が個々の独学で(TAは大体独学ですが)個々人のバラバラなオブジェクトモデリングを引き継ぐのは難しい。

[^3]: 時代に逆行してる感じがしなくもないが、最低限ルールを守って作れば、美しさや使いやすさ、新しさを評価するよりも使い慣れた感を重要視する場面や環境でおすすめ。