どなたもPymelのタイプを拡張したいと思えばひとまずクラスを継承すればいい。。たぶん。。と考えてしまうでしょう。
しかしPymelノード生成の仕組みでは下記の様な継承を書いてみたら期待した通の結果があまり出てこないと思います。
# -*- coding: utf-8 -*-
import pymel.core as pm
class TestExtension(pm.nt.Transform):
pass
def main():
# this node creation will fail
trn = TestExtension(name="testtest")
print(trn, type(trn))
main()
しかしPymelタイプ拡張のために用意されてあったVirtualClassという機能を利用すれば作ることができる。
VirtualClass仕様ドキュメントのまとめ
- Virtualタイプを実装するのに、テスト対象のMayaノードがそのVirtualClassに属すべきかどうかを判断するための_isVirtual()というclass/static メソッドを用意する必要がある。
- 必須ではなかったが、Pynode生成前後の処理やその最中の引数Dict調整の為に_preCreate(), _create() と _postCreate() class/staticメソッドを使うこともできる。
- 拡張タイプは定義した後、PymelのNode factoryに登録しなければPymelが認識してくれません。
各VirtualClassメソッドの簡単説明:
_isVirtual ()
pm.ls等を実行した際、そのノードのベースタイプに対する登録したすれべのVirtualタイプにこの関数が呼ばれる。現在のノードがVirtualタイプかどうかTrue/Falseを返すの関数です。一番先にTrueと返すVirtualタイプにそのノードがそのVirtualタイプだとみなす。すべての_isVirtual()テストが失敗すれば、そのノードが以前通りベースタイプのままとなる。
パラメーター: MObject, このisVirtualが属するクラスのstr名
戻り値:bool
* この関数はPymelノードが生成される前に呼ばれるため、この中のコードはMayaApi、CmdsまたはMelコマンドしか使えません。
* テストが行われる度にこの関数は(テスト対象ノードx登録したVirtualタイプ)回呼ばれますので、パフォーマンスに影響が大きい
_preCreate()
ノードが生成される前に呼ばれる関数。
生成される前に、ここでkwargsと_postCreateへ渡す(別の)kwargsを調整できる。
戻り値が dict1, dict2 2つのdictの場合、2個目のdictが_postCreateの引数として渡される。
パラメーター: kwargs
戻り値①:kwargs
戻り値②:kwargs, kwargs_for_postcreate
_create()
今回のサンプルコードには使われていないですが、既定のノード生成のコマンドをここで調整できる。
戻り値:strのノード名又はそのノードのclass.__init__が処理できるデータ
_postCreate()
ノードが生成された後に呼ばれる。
ここでノードにカスタムアトリビュート追加、設定、リネームすることなどができる。
パラメーター: kwargs (_preCreateで渡されるなら) 又は None
戻り値:None
さて上記の関数でタイプを拡張した例を作ってみよう。この拡張タイプは __vtypeid__[タイプ名] という文字列アトリビュート(Mayaノードの)があるかどうかとその中身でVirtualタイプを判断する。タイプ判断は_isVirtualで行い、新規ノードのアトリビュート追加は_postCreateで行われる。
Pymelの継承可能の最低限Virtualタイプ
# -*- coding: utf-8 -*-
from __future__ import print_function, absolute_import, division
import pymel.core as pm
from pymel.internal.factories import virtualClasses
import pprint
# -------------------------------------------------------------------------------
class TypedGrp(pm.nt.Transform):
@classmethod
def _isVirtual(cls, obj, name):
# obj is either an MObject or an MDagPath, depending on whether this class is a subclass of DependNode or DagNode, respectively.
# we use MFnDependencyNode below because it works with either and we only need to test attribute existence.
fn = pm.api.MFnDependencyNode(obj)
try:
# NOTE: MFnDependencyNode.hasAttribute fails if the attribute does not exist, so we have to try/except it.
# the _jointClassID is stored on subclass of CustomJointBase
test_result = fn.hasAttribute(cls._gen_typeid(cls._typeID))
print("is virtual class", obj, name, test_result)
# first call:
# is virtual class <maya.OpenMaya.MObject; proxy of <Swig Object of type 'MObject *' at 0x0000020E12093C00> > samplegroup False
# seocnd call:
# is virtual class <maya.OpenMaya.MObject; proxy of <Swig Object of type 'MObject *' at 0x0000020E11F76690> > None True
return test_result
except:
pass
print("is not virtual class", obj, name)
return False
@classmethod
def _preCreateVirtual(cls, **kwargs):
print("precreatevirtual")
postKwargs = {"a_key": "some_value"}
return kwargs, postKwargs
@classmethod
def _postCreateVirtual(cls, newNode, **kwargs):
print("postcreatevirtual")
print("argument recieved in post create virtual", newNode, kwargs)
# argument recieved in post create virtual samplegroup {'a_key': 'some_value'}
# add the identifying attribute. the attribute name will be set on subclasses of this class
newNode.addAttr(cls._gen_typeid(cls._typeID), dataType="string")
@classmethod
def _gen_typeid(cls, name):
return u"__vtypeid__{0}".format(name)
class A_GRP(TypedGrp):
_typeID = 'TYPE_A'
class B_GRP(TypedGrp):
_typeID = 'TYPE_B'
def main():
try:
print("registering virtual class")
virtualClasses.register(A_GRP, nameRequired=False)
a_grp1 = A_GRP(name="samplegroup")
print("newly created nodes, ", a_grp1)
finally:
print("unregistering virtual class")
virtualClasses.unregister(A_GRP)
main()
完全に既定タイプをVirtualで置換する例
今回は _isVirtual() をTrueに固定することで、ノードが必ずVirtualタイプとみなされ規定タイプと完全に置換。
# -*- coding: utf-8 -*-
from __future__ import print_function, absolute_import, division
import pymel.core as pm
from pymel.internal.factories import virtualClasses
import pprint
class GlobalPymelTransformClassReplacement(pm.nt.Transform):
@classmethod
def _isVirtual(cls, obj, name):
# virtual class determination always return true = this class replaces vanilla Transform class
# in all cases
return True
@classmethod
def _preCreateVirtual(cls, **kwargs):
postKwargs = {}
return kwargs, postKwargs
@classmethod
def _postCreateVirtual(cls, newNode, **kwargs):
pass
def main():
try:
virtualClasses.register(GlobalPymelTransformClassReplacement, nameRequired=False)
a_grp1 = GlobalPymelTransformClassReplacement(name="samplegroup")
vanilla_grp = pm.nt.Transform(name="normaltransformnode")
print("newly created nodes, ", [a_grp1, vanilla_grp])
print("pm.ls by custom transform type")
pprint.pprint(pm.ls(type=GlobalPymelTransformClassReplacement))
print("pm.ls by the original Transform type still returns everything as custom transform type")
pprint.pprint(pm.ls(type=pm.nt.Transform))
finally:
virtualClasses.unregister(GlobalPymelTransformClassReplacement)
応用例
isinstanceでVirtualと普通のタイプを比較してみる。
今までのサンプルが文字列アトリビュートでタイプ判断していたが今回はノード名Prefixで判断するようにしたVirtualタイプも作る。
# -*- coding: utf-8 -*-
from __future__ import print_function, absolute_import, division
import pymel.core as pm
from pymel.internal.factories import virtualClasses
import pprint
class TypedTransformBase(pm.nt.Transform):
_default_name = "TypedTransform"
@classmethod
def _isVirtual(cls, obj, name):
dag_node = pm.api.MFnDependencyNode(obj)
try:
return dag_node.hasAttribute(cls._gen_typeid(cls._typeID))
except:
pass
return False
@classmethod
def _preCreateVirtual(cls, **kwargs ):
# give a default name if no name supplied
if 'name' not in kwargs and 'n' not in kwargs:
kwargs['name'] = cls._default_name
postKwargs = {}
return kwargs, postKwargs
@classmethod
def _postCreateVirtual(cls, newNode, **kwargs ):
newNode.addAttr( cls._gen_typeid(cls._typeID), dataType = "string")
@classmethod
def _gen_typeid(cls, name):
return u"__vtypeid__{0}".format(name)
class ATransform(TypedTransformBase):
_typeID = 'A_SET'
class BTransform(TypedTransformBase):
_typeID = 'B_SET'
class PrefixTransformBase(pm.nt.Transform):
_prefix = "REPLACEME"
@classmethod
def _isVirtual(cls, obj, name):
# assume no namespace and no '|' character!
dag_node = pm.api.MFnDependencyNode(obj)
return dag_node.name().startswith(cls._prefix)
@classmethod
def _postCreateVirtual(cls, newNode, **kwargs ):
# assumes no namespace and no '|' character!
newNode.rename(cls._prefix+newNode.name())
class APrefixTrn(PrefixTransformBase):
_prefix = "A_TRN_"
class BPrefixTrn(PrefixTransformBase):
_prefix = "B_TRN_"
def main():
try:
virtualClasses.register(ATransform, nameRequired=False)
virtualClasses.register(BTransform, nameRequired=False)
virtualClasses.register(APrefixTrn, nameRequired=False)
virtualClasses.register(BPrefixTrn, nameRequired=False)
# attribute based virtual class example
a_grp1 = ATransform()
a_grp2 = ATransform()
b_grp = BTransform()
print("is a_grp1 an instace of ATransform?", isinstance(a_grp1, ATransform))
print("is a_grp2 also an instance of ATransform?", isinstance(a_grp2, ATransform))
print("is a_grp1 a derived class instance of TypedTransformBase?",
issubclass(type(a_grp1), TypedTransformBase))
print("Is b_grp an isinstance of ATransform?", isinstance(b_grp, ATransform))
print("Is b_grp BTransform isinstance?", isinstance(b_grp, BTransform))
# name prefix based virtual class example
c_grp1 = APrefixTrn(name="samplename")
c_grp2 = BPrefixTrn(name="anothersamplename")
print("newly created prefix transform: ", c_grp1, c_grp2)
print(type(n) for n in [c_grp1,c_grp2])
# Manually naming a normal Transform object so it would be recognized as APrefixTrn
c_grp3 = pm.nt.Transform()
print("vanilla transform type.., ", c_grp3, type(c_grp3))
c_grp3.rename("B_TRN_manuallyrenamed")
# Renaming c_grp3 to BPrefixTrn type manually does not updates the pymel wrapper type
print("renamed c_grp3:", c_grp3, type(c_grp3))
# But would show up correctly if it were listed again (pymel wrapper has been recreated)
c_grp3 = pm.ls(c_grp3)[0]
print("re-listed c_grp3, ", c_grp3, type(c_grp3))
print("listing all transform based classes")
pprint.pprint(pm.ls(type=pm.nt.Transform))
'''
output:
[nt.APrefixTrn(u'A_TRN_samplename'),
nt.BPrefixTrn(u'B_TRN_anothersamplename'),
nt.BPrefixTrn(u'B_TRN_manuallyrenamed'),
nt.ATransform(u'TypedTransform'),
nt.ATransform(u'TypedTransform1'),
nt.BTransform(u'TypedTransform2'),
nt.Transform(u'front'),
nt.Transform(u'persp'),
nt.Transform(u'side'),
nt.Transform(u'top')]
'''
finally:
virtualClasses.unregister(ATransform)
virtualClasses.unregister(BTransform)
virtualClasses.unregister(APrefixTrn)
virtualClasses.unregister(BPrefixTrn)