Python
Qt
maya

Mayaのツール開発で書くパターン集

この記事は、MayaPython アドベントカレンダー 9日目の記事です。
Mayaのツール開発でよく使っているフレーズをまとめて見ました。

デザインパターンのことは書いていませんので、間違えて入ってこられた方はご了承下さい。

Maya 編

sys.pathを通す / pyc 作らない

sys
import sys
sys.dont_write_bytecode = True

libpath = "//server/dev/app/maya/tools/python"
if not libpath in sys.path:
    sys.path.append(libpath)

個人環境であれば気にすることはないですが、
スタジオでツールを共有するシチュエーションでは、よく使います。

Qt.pyを使う

https://github.com/mottosso/Qt.py

PySide2, PySide, PyQt5, PyQt4 をラッピングしてくれるサードパーティモジュールです。
例外処理で長ったらしいファイルヘッダーを自前で書くべきではありません。
シングルファイルなので、ツールに組み込んで配布しやすいのも良いですね。

私は、C++ like に書きたいので、スターインポートしています。

Qt
from Qt.QtGui import *
from Qt.QtCore import *
from Qt.QtWidgets import *

shiboken or shiboken2 (sip) の wrapInstance も以下のコードで共用出来ます。

from Qt import QtCompat
QtCompat.wrapInstance()

Houdini と同じく、Qt.py をプリインストールしてくれれば、わざわざ布教する必要もないのですが...
技術ブログ界隈で使っておられる方が多いようなので、これからの標準でしょうか。

シーンを元に戻す

undo
import maya.cmds as cmds

cmds.undoInfo(ock=1)
cmds.refresh(su=1)

# 書きたい処理

cmds.refresh(su=0)
cmds.undoInfo(cck=1)
cmds.undo()

スクリプト処理前に戻ります。
シーン状態を整理するので、エクスポートの時に使うことが多いかも。

スタンドアロン or スクリプトを区別する

# Qt.py import 済み状態で

if not QApplication.instance():
    app = QApplication(sys.argv)

    # view を呼ぶ処理

    sys.exit(app.exec_())

else:
    # view を呼ぶ処理

PySide を、Mayaスクリプトエディタでもスタンドアロンでも起動できるようにします。
view と controller を適切に分離すれば、スタンドアロン経由の mayapy 起動なんてこともできますね。

ロジック編

私がよく書くパターンを紹介します。

疑わしきは try except

traceback
try:
    ...
except:
    print (traceback.format_exc())

文字列を定義するよりわかりやすいエラーメッセージが見えるので使います。

for と if を減らす

forとifを減らすことは、可読性をあげつつメンテナンスしやすいコードになるので、
意識して減らします。いくつか方法があります。

1. itertools.prodect 使う

import itertools

for m, n in itertools.product(members, numbers):
    ...

要素数が同じ配列同士なら zip 関数ですね。itertools は色々なパターンの
アルゴリズムが入っているので、for文の中で条件分岐するようなコードがあれば、
itertools の出番かもしれません。車輪の再発明をする必要はありません。

2. all or any

if all([f for f in exp_files if os.path.exists(f)]):
    ...

リスト内包表記と組み合わせて、要素の中身を全て洗います。
for文とif文だけで書くと、面倒くさくて読みにくいコードになりそう。

パス置換

import os
string.replace(os.sep, "/")

Windows環境はよく使いますね。

Python3

python3
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

Mayaのツール開発でデバッガーが付いたエディタで開発する方が少数である以上、
print文が多く含まれることが予測されるツールは、未来の Python3 でエラーになります。
問題は未然に防いでおきたいので、print 関数で書いておきます。

参考: https://docs.python.jp/3.6/howto/pyporting.html#prevent-compatibility-regressions

書き方編

もはや Python の書き方じゃないかと突っ込まれそうですが、
MayaでPythonを書いているとありがちな内容なのでまとめます。

引数を2個以上記入する際は改行する

import maya.cmds as cmds

cmds.setAttr(
    "{}.dso".format(shape),
    "{}/{}/{}.ass".format(
        os.path.dirname(os.path.dirname(dir)), "element/ASS", file
    ),
    typ="string"
)

MELプロシージャは、Bad Python と言えるほどに引数が多いので、
視認性を考えて改行します。リスト内包表記も同様です。

None 回避の or

import maya.cmds as cmds
from Qt.QtGui import *

cmds.file(q=1, sn=1) or QFileDialog.getOpenFileName()

if 文で None チェックするより書き方がスマートです。

for item in cmds.listRelatives() or []:
    ...

listRelativeslistConnections など要素が None になる可能性がある関数でエラーが起きません。

リストのアンパックに _ を使う

使わないトランスフォームなど、変数名を割り当てずに明示的に使用しないことを表現するために、
Swift like なアンダースコアを使います。

a, _ = cmds.listRelatives()

インデックスによるリストへのアクセスと使い分けます。

運用編

ツールを試して貰う際に起きやすい問題に対処します。

察しない。ログを出す

ローカル環境でうまくいっていたことが他環境でうまくいかないことが多いので、
Mayaのバージョン、チェックしているファイル、作成日時など、参考にできる情報は全てログに流します。

cmds.about(q=1, v=1), cmds.about(q=1, cd=1), cmds.about(q=1, ct=1), cmds.file(q=1, sn=1)

logging モジュールなり、print するなり。 進んでいる職場ではチケットシステムに統合されているのでしょうか?

絵で説得する

ロジックは完璧なのに求める絵が違うことが、稀に発生します。
それはアーティストにとってエラーと同義なので、早い段階で絵を共有しておくと問題になりにくいです。

自前のコードを載せようと思いましたが、既に開発済みのものがあるので、有難く使わせて頂きます。
https://github.com/guncys-inc/saveScreenShot

プラグインのチェック

plugins
# 2016 later
for p in cmds.unknownPlugin(q=1, l=1) or []:
    cmds.unknownPlugin(p, r=1)

個々の環境の違いで、消耗したくないですね。

まとめ

過去に解決済みの課題を同じようなロジックで書くことが、割とあります。
特にテクニカルは共同でコードを書く経験が少ないので、車輪の再発明をしがちです。

既知のものが多いかと思いますが、1つでも参考になることがあれば幸いです。
皆様が持っている知識があれば、ぜひ教えてください。