本記事はMaya Advent Calendar 2020 18日目の記事です。
今年も(?)完走したいので今年遭遇した現象を書きます。
※完全にデバッグしきれている記事ではありません。推測止まりになっていることをご了承ください。
事の発端
pymelで何かを処理する前に、処理対象があるかどうかを確認するときにこんな書き方をすることはありませんか?
if node.exists():
# なにかしらの処理
まさにこんな書き方でノードが存在するかどうかを確認したときに事件は起きました。
maya2017 up5の場合
- maya2017 up5
- PyMEL Version 1.0.9
import pymel.core as pm
hoge = pm.createNode('transform', n='hoge')
print hoge.exists()
# Result: True
作ったノードが存在するので結果はTrueが帰ってきますよね。今度は作ったノードを削除してから確認してみましょう。
import pymel.core as pm
hoge = pm.createNode('transform', n='hoge')
pm.delete(hoge)
print hoge.exists()
# Result: False
うんうん、それはそう。
maya2019 up3の場合
問題は同じコードをmaya2019のup3で実行したときです。
- maya2019 up3
- PyMEL Version 1.0.10
import pymel.core as pm
hoge = pm.createNode('transform', n='hoge')
pm.delete(hoge)
print hoge.exists()
# Result: True
?
True ってどういうことだ???
存在しないはずのノードに対してexists()
メソッドがTrueを返してきていることが判明しました。
今回はこれに関してちょっと調べてみようと思います。
とりあえず解決したい
ノードの存在を確認して、あったら処理する。みたいな記述はよくやると思います。
この書き方をしていたスクリプトがmaya2019 up3で正常に動作しないのは仕事的にもヤバいので、とりあえずの解決方法を探しました。
import pymel.core as pm
hoge = pm.createNode('transform', n='hoge')
pm.delete(hoge)
print hoge.exists()
# Result: True
print pm.objExists(hoge)
# Result: False
なるほど、pm.objExists()
を使えばとりあえず正しい状態を検出できそうです。
exists()メソッドの挙動を探ってみる
exists()
メソッドが何を判断してブーリアンを返すかを pymel/general.py から順に探っていきます。
def exists(self, **kwargs):
"objExists"
try:
# use __apimobject__, not __apiobject__, because that's the one
# that calls _api.isValidMObjectHandle (ie, we don't want to get
# an MDagPath, which won't do that validation)
if self.__apimobject__():
return True
except MayaObjectError:
pass
return False
self.__apimobject__()
があるかどうかで判断しているようです。では__apimobject__()
は何をもって判断しているのか?
import pymel.api as _api
def __apimobject__(self):
"Return the MObject for this attribute, if it is valid"
try:
handle = self.__apiobjects__['MObjectHandle']
except:
handle = _api.MObjectHandle(self.__apimplug__().attribute())
self.__apiobjects__['MObjectHandle'] = handle
if _api.isValidMObjectHandle(handle):
return handle.object()
raise MayaAttributeError
MObjectHandleを取得して_api.isValidMObjectHandle(handle)
の結果で判断しているみたいですね。これも見てみましょう。
pymel/api/allapi.py
# fast convenience tests on API objects
def isValidMObjectHandle(obj):
if isinstance(obj, MObjectHandle):
return obj.isValid() and obj.isAlive()
else:
return False
MObjectHndleクラスから作られたインスタンス(オブジェクト)であることをチェックしているようです。
2017, 2019の両方の環境でこのapiを実行するとともに同じFalseが帰ってきました。
検証コード
exists()
メソッドの挙動から以下のコードのどこかでmaya2017, maya2019に差がでるはず、と推測しました。
では実行してみましょう。
import pymel.api as _api
import pymel.core as pm
hoge = pm.createNode('transform', n='hoge')
pm.delete(hoge)
handle = hoge.__apiobjects__['MObjectHandle']
print handle.isValidMObjectHandle()
maya2017では False が表示されます。
maya2019ではhandle = hoge.__apiobjects__['MObjectHandle']
の部分でhandleが取得できませんでした。
このことからmaya2017ではgeneral.pyのtryが成功し、maya2019では失敗しているのでexceptが実行されているっぽいですね。
import pymel.api as _api
def __apimobject__(self):
"Return the MObject for this attribute, if it is valid"
try:
handle = self.__apiobjects__['MObjectHandle'] # maya2017はこっちのhandle?
except:
handle = _api.MObjectHandle(self.__apimplug__().attribute()) # maya2019はこっちのhandle?
self.__apiobjects__['MObjectHandle'] = handle
if _api.isValidMObjectHandle(handle):
return handle.object()
raise MayaAttributeError
exceptでhandle = _api.MObjectHandle(self.__apimplug__().attribute())
が実行されています。
このhandleがMObjectHndleインスタンス(オブジェクト)なので、isValidMObjectHandle(obj)
が、exists()
メソッドで True になると推測できます。
もう少し調べたいこと
__apimobject__()
の_api.MObjectHandle(self.__apimplug__().attribute())
あたりをもう少し詳しく調べたかったのですが、アドベントカレンダーのタイムリミットがきてしまいました。。。
時間ができたらステップ実行などでもう少し詳しくデバッグして追記します!