【Maya】内製PythonツールをMaya2020対応するためにやったこと

内製のツールを試しにMaya2020で使おうとしたら動かない…!
というわけで対応した内容のまとめです。

PySide2のエラーその1

Traceback (most recent call last):
(略)
    class IconModel(QtCore.QStringListModel):
AttributeError: 'module' object has no attribute 'QStringListModel'

PySide2のバージョンが上がってなにか変わったのかなー。

というわけでMaya2019とMaya2020でバージョン確認。

import PySide2
print(PySide2.__version__)

結果はMaya2020.2は「5.12.5」、Maya2019.2は「2.0.0~alpha0」。

PySide2を使うときはQt.py経由にしているので、試しにQtのバージョンを上げてみるとエラーがでなくなりました。やったぜ。
github.com

エラーが出た環境はQt.pyが1.1.0だったので最新の1.2.6に上げました。

ちなみにうちはこれで解消しましたが、このPySide2の5.12.5にはQObjectのサブクラスで「__new__」を使うとエラーが出る問題もあるらしいので、実装次第ではもうちょっと対応が必要そうです。
[PYSIDE-1051] Unable to call QtCore.QObject.__new__ in Python 2.7 - Qt Bug Tracker

PySide2のエラーその2

これを実行すると…

from Qt import QtWidgets
QtWidgets.QFileDialog.getExistingDirectory(
            parent=self,
            options=QtWidgets.QFileDialog.DirectoryOnly | QtWidgets.QFileDialog.ShowDirsOnly
        )

こんなエラーが出ます。

# Error: ValueError: file <maya console> line 6: 'PySide2.QtWidgets.QFileDialog.getExistingDirectory' called with wrong argument values:
  PySide2.QtWidgets.QFileDialog.getExistingDirectory()
Found signature:
  PySide2.QtWidgets.QFileDialog.getExistingDirectory(PySide2.QtWidgets.QWidget=None, str='', str='', PySide2.QtWidgets.QFileDialog.Options=PySide2.QtWidgets.QFileDialog.Option.ShowDirsOnly) # 

が、これはなんかそもそも使い方が間違ってたのに、今までたまたま動いてた感…。
本来optionsには「QFileDialog.Option」のものを指定する必要があるのに、「QFileDialog.FileMode」のものを指定してしまっていたので間違ってるよーとなっているだけでした。
というわけでこう。

from Qt import QtWidgets
QtWidgets.QFileDialog.getExistingDirectory(
            parent=self,
            options=QtWidgets.QFileDialog.ShowDirsOnly
        )

PySide2のエラーその3

        button = QtWidgets.QPushButton('button')
        button.clicked.connect(lambda x="test": self._on_triggered(x))
(略)
    def _on_triggered(self, value):
        print("value", value)

こんなのを実行すると、Maya2019までは以下の結果になってました。

('value', 'test')

Maya2020だと…

('value', False)

どうして…。

どうやら5.11.2からシグナルに対してのスロットの引数量を検出する処理がlambda式使ったときうまく動かないのか、これまでと異なる動作をするようになったようです。つらい。
[PYSIDE-909] REG->5.11.2: lambda does not pass argument - Qt Bug Tracker

シグナル接続部分を以下のようにすると以前と同じ結果を得ることが出来ました。

button.clicked[bool].connect(lambda _, x="test": self._on_triggered(x))

復数の型を持つシグナルに対して受け取る型を固定する書き方をする感じですね。
えっ。これlambda使ってシグナル接続してる引数持つスロット全部にやってくの?つら…

このissueは特にパッチあたることもなくクローズされてるので、もし現行のプロジェクトで型を固定しない書き方をしている場合、Maya2020以降を使う場合はこの対応をしないといけなさそう…。

追記
BugTrackerでfunctools.partialじゃうまくいかない旨が書いてあったので試しもしなかったんですが、Twitterでご指摘いただいて試したらうまくいきました😇
というわけでシグナルに対して接続するスロットの引数を上書きしたいケースでは以下のようにfunctools.partialを使っておけばMaya2020以降も大丈夫そうです。

button.clicked.connect(partial(self._on_triggered, "test"))

※lambda式使う形でももちろん動きますが、Maya2019以前は型の固定をしなくても特に問題なく動くので、Maya2020以降に乗り換えて初めて問題ないか発覚する爆弾を抱えるよりはそもそもfunctools.partialを使っておくのが安全そうです。

2020.08.21追記
ちなみにこういうケースは影響を受けなさそう。

button.clicked.connect(lambda x="test1", y="test2": self._on_triggered(x, y))

シグナルの引数がデフォルト引数で、かつ渡ってくる引数の数がlambda式の引数の数と一致している、またはデフォルト引数でないもの(シグナルから受け取る値をそのまま使うもの)を含む場合に影響を受けるっぽい?
ので、textChangedとかの引数はデフォルト引数じゃないので特に影響を受けなさそうです。
QAction — Qt for Python
QAbstractButton — Qt for Python

typingのエラー

あんまりハマる人いないかなーとは思いつつですが、型ヒントをサポートしてくれるモジュールである「typing」でも問題が起きました。

typingが読み込まれている環境でたとえばこんなコードを実行すると…

 from collections import OrderedDict
 try:
     _COLORS = OrderedDict([
         ("red", (1, 0, 0)),
         ("green", (0, 1, 0)),
         ("blue", (0, 0, 1))
     ])
 except Exception as e:
     import traceback
     print(traceback.format_exc())

こうなる。なんでじゃ。

 Traceback (most recent call last):
   File "<maya console>", line 15, in <module>
   File "C:\Program Files\Autodesk\Maya2020\bin\python27.zip\collections.py", line 57, in __init__
     self.__update(*args, **kwds)
   File "C:\Program Files\Autodesk\Maya2020\bin\python27.zip\_abcoll.py", line 564, in update
     if isinstance(other, Mapping):
   File "C:\Program Files\Autodesk\Maya2020\bin\python27.zip\abc.py", line 144, in __instancecheck__
   (略)
  File "C:\Users\XXX\Documents\test\vendor\typing.py", line 1140, in __extrahook__
    if issubclass(subclass, scls):
  File "C:\Program Files\Autodesk\Maya2020\Python\lib\site-packages\shiboken2\files.dir\shibokensupport\typing27.py", line 1338, in __subclasscheck__
    return super(GenericMeta, self).__subclasscheck__(cls)
  File "C:\Program Files\Autodesk\Maya2020\bin\python27.zip\abc.py", line 161, in __subclasscheck__
    ok = cls.__subclasshook__(subclass)
  File "C:\Program Files\Autodesk\Maya2020\Python\lib\site-packages\shiboken2\files.dir\shibokensupport\typing27.py", line 1070, in __extrahook__
    if issubclass(subclass, scls):
  File "C:\Program Files\Autodesk\Maya2020\bin\python27.zip\abc.py", line 151, in __subclasscheck__
    if subclass in cls._abc_cache:
 RuntimeError: maximum recursion depth exceeded

というわけでログを眺めてみると、読み込んでるtyping以外に「shibokensupport\typing27.py」というやつが目に入ります。

Infinite recursion in isinstance check · Issue #582 · python/typing · GitHub
おそらくここに書いてあるtypingのインスタンスが複数あると死ぬよ、というやつに引っかかってる感じだと思われます。
Python3.7では直ってる風のことが書いてあるのですが、我々はPython2.7しか使えない呪いにかかってるのでその方向は諦めます。

というわけでMaya2020でtypingを使いたい場合、「shibokensupport.typing27」が存在すればそちらを使うようなコードを書く必要がありそうです。
とはいえtypingを使う全コードにそんなの書くのはダルすぎるので、今回は自前で追加しているtypingを書き換えて対応しました。

 try:
     from shibokensupport.typing27 import *
 except:
 (略。本来のtypingのコードを記述)

これ問題なく動きました。つらい。

おわり

とりあえずこれらの対応で概ね動いてる気はします。

Python3系が使えるようになるのが楽しみだけどまた色々やらないといけないかもと思うとしんどみがありますね。

スポンサーリンク