Mayaのワークスペースコントロール

この記事は[Maya-Python Advent Calendar 2017](https://qiita.com/advent-calendar/2017/maya-python) 12月20日の記事です。

確認環境：Maya2018.1

# はじめに

Maya2017からワークスペースという概念が加わり、さらに画面レイアウトを自由自在に自分好みにカスタマイズできるようになりました。

![2017-12-10_13h50_31.png](https://qiita-image-store.s3.amazonaws.com/0/13183/16adc4e7-8f30-b035-1514-dd45b11c25ca.png)


ウインドウのドッキングが柔軟になり、例えばUV Editorにオリジナルのパネルを組み込むこともできるので、UV Editorを魔改造する必要がなくなった！これはデカイ。

![2017-12-10_13h54_48.png](https://qiita-image-store.s3.amazonaws.com/0/13183/4970f613-616f-9ee3-574c-8e162ee6ddd9.png)


しかしツールプログラマにとっては覚えなければいけない仕組みが一つ増えたことになります。

攻略していきましょう。

**なんだかダラダラと長くなってしまいました:cry:。まとめから（だけ？）読んだ方がいいかも…**

# 仕組み

[ワークスペースのドキュメント](http://help.autodesk.com/view/MAYAUL/2018/JPN/?guid=__files_GUID_F65DBAFC_21C0_47C1_ABD0_161EE77D0F6C_htm)

とりあえずはMELでの挙動を確認しましょう。

```
workspaceControl -retain false -floating true
 -uiScript "if(!`exists createCustomWorkspaceControlUI`) source customWorkspaceControlFile; createCustomWorkspaceControlUI();"
 myCustomWorkspaceControl;
```

workspaceControlコマンドでコントロール（Qt的に言うとウィジェット）を生成し、実際にウインドウを生成するコマンドをuiScriptオプションで指定しています。
ウインドウ生成関数が無ければsourceして、関数を読んでいます。ちょっと読みにくいですね…

![2017-12-10_14h37_06.png](https://qiita-image-store.s3.amazonaws.com/0/13183/a907109b-4644-7355-35ef-259c9cdf9bb2.png)

適当にアウトライナーの上にドッキングしてみました。
![2017-12-10_14h37_35.png](https://qiita-image-store.s3.amazonaws.com/0/13183/a4785ec6-6ee5-dca6-4534-5e3f158ab11a.png)

Mayaを再起動しても同じレイアウトが再現されました。これは良い。

かつてのカスタムパネルは用意しなければいけない関数が結構な数あった気がしますが、ワークスペースコントロールは手続きは非常に少なく済みそうです。


# Pythonのサンプル

devkitのサンプルを読んでみましょう（[devkitはadn-extranetだそうです](http://around-the-corner.typepad.com/adn/2017/08/maya-2018-devkit-is-on-adn-extranet-now.html)）

サンプルコードはこちら。
devkit\pythonScripts\dockableWorkspaceWidget.py

スクリプトエディタに張り付けて実行せよとのことなのでとりあえず実行したところシンプルにボタン一つのウインドウが現れました。

![2017-12-10_14h52_05.png](https://qiita-image-store.s3.amazonaws.com/0/13183/900eca85-ae41-9261-5a71-d104b07290ec.png)

ドッキングもできました。

![2017-12-10_14h52_23.png](https://qiita-image-store.s3.amazonaws.com/0/13183/4326094c-83de-9a95-7806-e64d2438575f.png)

しかしMayaを再起動するとコントロール自体（ようするに枠？）は再現されているのですが中身がありません。

![2017-12-10_14h54_29.png](https://qiita-image-store.s3.amazonaws.com/0/13183/fd99e15d-6d0f-3e15-312d-ae2348934d59.png)

なんだかなぁ…

サンプルがそのまま動かないことはよくあることなので、気にせずコードを読んでいきましょう。


# Pythonコードを読んでみる


maya.app.general.mayaMixin.MayaQWidgetDockableMixinを継承してます。出たなミックスイン。

```py:dockableWorkspaceWidget.py
class DockableWidget(MayaQWidgetDockableMixin, QWidget):
  def __init__(self, parent=None):
    super(DockableWidget, self).__init__(parent=parent)
    self.button1 = QPushButton()
```
show()するときにuiScript引数でMELと同様に生成コマンドを指定するようですね。
Mayaを再起動したときにボタンが生成されていなかったのはここに原因がありそうです。

```py:dockableWorkspaceWidget.py
      customMixinWindow.show(dockable=True, height=600, width=480, uiScript='DockableWidgetUIScript(restore=True)')
```

再起動時に以下のエラーが発生してました。importしてないんだからそりゃそうだ。サンプルによくあることです。uiScriptに渡すコマンドだけ気を付ければよさそうです。

```
 # Error: line 1: NameError: file <maya console> line 1: name 'DockableWidgetUIScript' is not defined # 
```

ところでworkspaceControlで指定するコントロール名はどうやって決定しているのでしょう？ミックスインの中まで読み進めてみます。

```mayaMixin.py
                workspaceControlName = self.objectName() + 'WorkspaceControl'
```

ウィジェットのオブジェクト名にWorkspaceControlというサフィックスを追加した文字列をワークスペースコントロール名にしているようです。


# ワークスペースコントロールを削除する

とりあえずこの手のチャレンジをするときには問題解決方法も確保しておく必要があります。
ワークスペースコントロールはMayaが記憶するので、削除する方法を調べます。

```:MELコマンド
deleteUI ワークスペースコントロール名
```

で削除できるようですので、サンプルのコントロールは削除しておきましょう。

```:MELコマンド
deleteUI customMayaMixinWindowWorkspaceControl
```


# Mayaを再起動しても復元されるように修正

さて、サンプルを少し修正しましょう。

wctest.pyというファイル名でサンプルスクリプトを保存し、uiScriptのコマンドを修正します。

```
      customMixinWindow.show(dockable=True, height=600, width=480, uiScript='import wctest; wctest.DockableWidgetUIScript(restore=True)')
```

main関数を呼ぶことで実行できます。Mayaを再起動しても再現しました。

```py:
import wctest
wctest.main()
```

初回実行時にはshowで表示し、復旧時にはカレントの親の子供にするという必要があるようです。せっかくMayaQWidgetDockableMixinを継承してるんだからお任せしたかった。ちょっとめんどくさい。

```
      restoredControl = omui.MQtUtil.getCurrentParent()
      mixinPtr = omui.MQtUtil.findControl(customMixinWindow.objectName())
      omui.MQtUtil.addWidgetToMayaLayout(long(mixinPtr), long(restoredControl))
```


# まとめ

サンプル動かしただけで疲れてしまった。
つまりPySideではGUIを構築するクラスと、再作成の関数が必要ということになると。

普通のダイアログならば安定して運用できてるので、いきなり全てのツールをワークスペースコントロールに変更するのは少し怖いです。両対応にしたいので、整理するとこんな感じになりました。

https://gist.github.com/sporty/56be027134acf30ccd9c0397b3216600

普通のダイアログを出したい時はmain関数、ワークスペースコントロールとして出したい時はuiscript関数を呼ぶという風に実装してみました。


![2017-12-16_15h52_23.png](https://qiita-image-store.s3.amazonaws.com/0/13183/c0d2ec56-1b22-db0e-3e2f-0ddbd65ebee7.png)

実際の運用では「すでにワークスペースコントロールが存在したら削除する」とかが必要になるとは思いますが、だいたいこんな感じで行こうかと思います。

めでたしめでたし。