3
Houses the MetaClass able to setup new types to work within the system. This can
4
be considered the heart of the node wrapping engine, but it plays together with
5
the implementation in the `base` module.
7
__docformat__ = "restructuredtext"
9
from mrv.maya.util import MetaClassCreator
10
from mrv.maya.util import MEnumeration
11
import mrv.maya as mrvmaya
12
import mrv.maya.mdb as mdb
13
from mrv.util import uncapitalize, capitalize
16
import maya.OpenMaya as api
18
from new import instancemethod
20
log = logging.getLogger("mrv.maya.nt.typ")
22
__all__ = ("MetaClassCreatorNodes", )
25
_nodesdict = None # to be set during initialization
27
nodeTypeToMfnClsMap = dict() # allows to see the most specialized compatible mfn cls for a given node type
31
targetModule = None # must be set in intialization to tell this class where to put newly created classes
35
getattrorigname = '__getattr_orig'
36
codegen = None # python code generator, to be set during initialization
41
class MetaClassCreatorNodes( MetaClassCreator ):
42
"""Builds the base hierarchy for the given classname based on our typetree
43
:todo: build classes with slots only as members are pretermined"""
46
def _fetchMfnDB( cls, newcls, mfncls, **kwargs ):
47
"""Return the mfndb for the given mfncls as existing on newcls.
48
If it does not yet exist, it will be created and attached first
50
:param kwargs: passed to MMemberMap initializer"""
52
return newcls.__dict__[ mfndbattr ]
54
mfndbpath = mdb.mfnDBPath(mfncls.__name__)
56
mfndb = mdb.MMemberMap(mfndbpath, **kwargs)
58
print IOError("Could not create MFnDB for file at %s" % mfndbpath)
60
# END handle mmap reading
61
type.__setattr__( newcls, mfndbattr, mfndb )
66
def _wrapStaticMembers( cls, newcls, mfncls ):
67
"""Find static mfnmethods - if these are available, initialize the
68
mfn database for the given function set ( ``mfncls`` ) and create properly
70
Additionally check for enumerations, and generate the respective enumeration
72
:note: As all types are initialized on startup, the staticmethods check
73
will load in quite a few function sets databases as many will have static
74
methods. There is no real way around it, but one could introduce 'packs'
75
to bundle these together and load them only once. Probably the performance
76
hit is not noticeable, but lets just say that I am aware of it
77
:note: Currently method aliases are not implemented for statics !"""
78
fstatic, finst = mdb.extractMFnFunctions(mfncls)
79
hasEnum = mdb.hasMEnumeration(mfncls)
80
if not fstatic and not hasEnum:
83
mfndb = cls._fetchMfnDB(newcls, mfncls, parse_enums=hasEnum)
85
mfnname = mfncls.__name__
88
if fn.startswith(mfnname):
89
fn = fn[len(mfnname)+1:] # cut MFnName_methodName
90
# END handle name prefix
92
static_function = cls._wrapMfnFunc(newcls, mfncls, fn, mfndb)
93
type.__setattr__(newcls, fn, staticmethod(static_function))
94
# END for each static method
95
# END handle static functions
99
for ed in mfndb.enums:
100
type.__setattr__(newcls, ed.name, MEnumeration.create(ed, mfncls))
101
# END for each enum desriptor
102
# END handle enumerations
106
def _wrapMfnFunc( cls, newcls, mfncls, funcname, mfndb, addFlags=0 ):
107
"""Create a function that makes a Node natively use its associated Maya
108
function set on calls.
110
The created function will use the api object of the instance of the call to initialize
111
a function set of type mfncls and execute the function in question.
113
The method mutation database allows to adjust the way a method is being wrapped
115
:param mfncls: Maya function set class from which to take the functions
116
:param funcname: name of the function set function to be wrapped.
117
:param mfndb: `mdb.MMemberMap`
118
:param addFlags: If set, these flags define how the method will be generated.
119
:raise KeyError: if the given function does not exist in mfncls
120
:note: if the called function starts with _api_*, a special accellerated method
121
will be returned and created allowing direct access to the mfn instance method.
122
This is unsafe if the same api object is being renamed. Also it will only be faster if
123
the same method is actually called multiple times. It can be great for speed sensitive code
124
where where the same method(s) are called repeatedly on the same set of objects
125
:return: wrapped function or None if it was deleted"""
126
flags = mfndb.flags|addFlags
127
funcname_orig = funcname # store the original for later use
129
# rewrite the function name to use the actual one
130
if funcname.startswith( "_api_" ):
131
flags |= mdb.PythonMFnCodeGenerator.kDirectCall
132
funcname = funcname[ len( "_api_" ) : ]
133
# END is special api function is requested
134
mfnfuncname = funcname # method could be remapped - if so we need to lookup the real name
137
method_descriptor = None
138
# adjust function according to DB
140
mfnfuncname, method_descriptor = mfndb.methodByName( funcname )
142
if method_descriptor.flag == mdb.MMemberMap.kDelete:
145
pass # could just be working
146
# END if entry available
148
if method_descriptor is None:
149
method_descriptor = mdb.MMethodDescriptor()
150
# END assure method descriptor is set
152
# access it directly from the class, ignoring inheritance. If the class
153
# would have overridden the function, we would get it. If it does not do that,
154
# we will end up with the first superclass implementing it.
155
# This is what we want as more specialized Function sets will do more checks
156
# hence will be slower to create. Also in case of geometry, the python api
157
# is a real bitch with empty shapes on which it does not want to operate at all
158
# as opposed to behaviour of the API.
159
mfnfunc = mfncls.__dict__[ mfnfuncname ] # will just raise on error
160
if isinstance(mfnfunc, staticmethod):
161
flags |= mdb.PythonMFnCodeGenerator.kIsStatic
163
# convert static method to real method
164
mfnfunc = type.__getattribute__(mfncls, mfnfuncname)
167
# finish compile flags
168
if api.MFnDagNode in mfncls.mro():
169
flags |= mdb.PythonMFnCodeGenerator.kIsDagNode
170
if api.MObject in newcls.mro():
171
flags |= mdb.PythonMFnCodeGenerator.kIsMObject
173
# could be cached, but we need to wait until the dict is initialized,
174
# TODO: To be done in __init__ together with the nodedict
175
newfunc = codegen.generateMFnClsMethodWrapperMethod(funcname_orig, funcname, mfncls, mfnfunc, method_descriptor, flags)
177
if not flags & mdb.PythonMFnCodeGenerator.kIsStatic:
178
newfunc.__name__ = funcname # rename the method
184
def _wrapLazyGetAttr( thiscls, newcls ):
185
""" Attach the lazy getattr wrapper to newcls """
186
# keep the original getattr
187
if hasattr( newcls, '__getattr__' ):
188
getattrorig = newcls.__dict__.get( '__getattr__', None )
190
getattrorig.__name__ = getattrorigname
191
setattr( newcls, getattrorigname, getattrorig )
193
# CREATE GET ATTR CUSTOM FUNC
194
# called if the given attribute is not available in class
195
def meta_getattr_lazy( self, attr ):
196
actualcls = None # the class finally used to store located functions
198
# MFN ATTRTIBUTE HANDLING
199
############################
200
# check all bases for and their mfncls for a suitable function
201
newclsfunc = newinstfunc = None # try to fill these
202
for basecls in newcls.mro():
203
if not hasattr( basecls, mfnclsattr ): # could be object too or user defined cls
206
mfncls = getattr( basecls, mfnclsattr )
210
# get function as well as its possibly changed name
212
mfndb = thiscls._fetchMfnDB(basecls, mfncls)
213
newclsfunc = thiscls._wrapMfnFunc( newcls, mfncls, attr, mfndb )
214
if not newclsfunc: # Function %s has been deleted - ignore
216
except KeyError: # function not available in this mfn - ignore
218
newinstfunc = instancemethod( newclsfunc, self, basecls ) # create the respective instance method !
220
break # stop here - we found it
221
# END for each basecls ( searching for mfn func )
223
# STORE MFN FUNC ( if available )
224
# store the new function on instance level !
225
# ... and on class level
227
# assure we do not call overwridden functions
228
object.__setattr__( self, attr, newinstfunc )
229
type.__setattr__( actualcls, attr, newclsfunc ) # setattr would do too, but its more dramatic this way :)
231
# END newclsfunc exists
233
# ORIGINAL ATTRIBUTE HANDLING
234
###############################
235
# still no funcion ? Continue with non-mfn search routine )
236
# try to find orignal getattrs in our base classes - if we have overwritten
237
# them we find them under a backup attribute, otherwise we check the name of the
238
# original method for our lazy tag ( we never want to call our own counterpart on a
240
getattrorigfunc = None
241
for basecls in newcls.mro():
242
if hasattr( basecls, getattrorigname ):
243
getattrorigfunc = getattr( basecls, getattrorigname )
245
# END orig getattr method check
247
# check if the getattr function itself is us or not
248
if hasattr( basecls, '__getattr__' ):
249
getattrfunc = getattr( basecls, '__getattr__' )
250
if getattrfunc.func_name != "meta_getattr_lazy":
251
getattrorigfunc = getattrfunc
253
# END default getattr method check
254
# END for each base ( searching for getattr_orig or nonoverwritten getattr )
256
if not getattrorigfunc:
257
raise AttributeError( "Could not find mfn function for attribute '%s'" % attr )
259
# pass on the call - if this method produces an output, its responsible for caching
260
# it in the instance dict
261
return getattrorigfunc( self, attr )
262
# EMD orig getattr handling
263
# END getattr_lazy func definition
267
setattr( newcls, "__getattr__", meta_getattr_lazy )
270
def __new__( metacls, name, bases, clsdict ):
271
""" Called to create the class with name """
273
def func_nameToTree( name ):
274
# first check whether an uncapitalized name is known, if not, check
275
# the capitalized version ( for special cases ), finalyl return
276
# the uncapitalized version which is the default
277
name = uncapitalize(name)
278
if nodeTypeTree.has_node(name):
281
capname = capitalize(name)
282
if nodeTypeTree.has_node(capname):
290
# ask our NodeType to MfnSet database and attach it to the cls dict
291
# ( if not yet there ). By convention, there is only one mfn per class
293
needs_static_method_initialization = False
294
if not clsdict.has_key( mfnclsattr ):
295
treeNodeTypeName = func_nameToTree( name )
296
if nodeTypeToMfnClsMap.has_key( treeNodeTypeName ):
297
mfncls = nodeTypeToMfnClsMap[ treeNodeTypeName ]
298
clsdict[ mfnclsattr ] = mfncls
300
# attach static mfn methods directly.
301
needs_static_method_initialization = True
302
# END attach mfncls to type
304
mfncls = clsdict[ mfnclsattr ]
306
# do not store any mfn if there is none set - this would override mfns of
307
# base classes although the super class is compatible to it
309
clsdict[ mfnclsattr ] = mfncls # we have at least a None mfn
310
clsdict[ apiobjattr ] = None # always have an api obj
315
newcls = super( MetaClassCreatorNodes, metacls ).__new__( nodeTypeTree, targetModule,
316
metacls, name, bases, clsdict,
317
nameToTreeFunc = func_nameToTree )
321
#####################
322
# Functions from mfn should be wrapped on demand to the respective classes as they
323
# should be generated only when used
324
# Wrap the existing __getattr__ method in an own one linking mfn methods if possible
325
# Additionally, precreate static methods
327
metacls._wrapLazyGetAttr( newcls )
329
if needs_static_method_initialization:
330
metacls._wrapStaticMembers(newcls, mfncls)
331
# END if mfncls defined
343
def prefetchMFnMethods():
344
"""Fetch and install all mfn methods on all types supporting a function set.
345
This should only be done to help interactive mode, but makes absolutely no
346
sense in the default mode of operation when everything is produced on demand.
348
:note: Attaches docstrings as well
349
:return: integer representing the number of generated methods"""
350
log.info("Prefetching all MFnMethods")
353
for typename, mfncls in nodeTypeToMfnClsMap.iteritems():
355
nodetype = _nodesdict[capitalize(typename)]
357
log.debug("MFn methods for %s exists, but type was not found in nt" % typename)
359
# END handle type exceptions
361
mfnname = mfncls.__name__
362
mfndb = MetaClassCreatorNodes._fetchMfnDB(nodetype, mfncls)
363
fstatic, finst = mdb.extractMFnFunctions(mfncls)
365
def set_method_if_possible(cls, fn, f):
366
if not hasattr(cls, fn):
367
type.__setattr__(cls, fn, f)
368
# END overwrite protection
373
if fn.startswith(mfnname):
374
fn = fn[len(mfnname)+1:]
375
# END handle prefixed names
377
fna = fn # alias for method
379
origname, entry = mfndb.methodByName(fn)
383
# END get alias metadata
385
fwrapped = MetaClassCreatorNodes._wrapMfnFunc(nodetype, mfncls, fn, mfndb, mdb.PythonMFnCodeGenerator.kWithDocs)
387
# could have been deleted
391
set_method_if_possible(nodetype, fn, fwrapped)
393
set_method_if_possible(nodetype, fna, fwrapped)
397
# END for each instance function
398
# END for each type/mfncls pair
408
def _addCustomType( targetmoduledict, parentclsname, newclsname,
409
metaclass=MetaClassCreatorNodes, **kwargs ):
410
"""Add a custom type to the system such that a node with the given type will
411
automatically be wrapped with the corresponding class name
413
:param targetmoduledict: the module's dict to which standin classes are supposed to be added
414
:param parentclsname: the name of the parent node type - if your new class
415
has several parents, you have to add the new types beginning at the first exsiting parent
416
as written in the maya/cache/nodeHierarchy.html file
417
:param newclsname: the new name of your class - it must exist targetmodule
418
:param metaclass: meta class object to be called to modify your type upon creation
419
It will not be called if the class already exist in targetModule. Its recommended to derive it
420
from the metaclass given as default value.
421
:raise KeyError: if the parentclsname does not exist"""
422
# add new type into the type hierarchy #
423
parentclsname = uncapitalize( parentclsname )
424
newclsname = uncapitalize( newclsname )
425
nodeTypeTree.add_edge( parentclsname, newclsname )
427
# create wrapper ( in case newclsname does not yet exist in target module )
428
mrvmaya.initWrappers( targetmoduledict, [ newclsname ], metaclass, **kwargs )
431
def _removeCustomType( targetmoduledict, customTypeName ):
432
"""Remove the given typename from the given target module's dictionary as
433
well as from internal caches
435
:note: does nothing if the type does not exist
436
:param targetmoduledict: dict of your module to remove the type from
437
:param customTypeName: name of the type to be removed, its expected
440
del(targetmoduledict[customTypeName])
443
# END remove from dictionary
445
customTypeName = uncapitalize(customTypeName)
446
if nodeTypeTree.has_node(customTypeName):
447
nodeTypeTree.remove_node(customTypeName)
448
# END remove from type tree
450
def _addCustomTypeFromDagtree( targetmoduledict, dagtree, metaclass=MetaClassCreatorNodes,
451
force_creation=False, **kwargs ):
452
"""As `_addCustomType`, but allows to enter the type relations using a
453
`mrv.util.DAGTree` instead of individual names. Thus multiple edges can be added at once
455
:note: special care is being taken to make force_creation work - first all the standind classes
456
are needed, then we can create them - just iterating the nodes in undefined order will not work
457
as a parent node might not be created yet
458
:note: node names in dagtree must be uncapitalized"""
459
# add edges - have to start at root
460
rootnode = dagtree.get_root()
461
def recurseOutEdges( node ): # postorder
462
for child in dagtree.children_iter( node ):
464
for edge in recurseOutEdges( child ): # step down the hierarchy
467
nodeTypeTree.add_edges_from( recurseOutEdges( rootnode ) )
468
mrvmaya.initWrappers( targetmoduledict, dagtree.nodes_iter(), metaclass, force_creation = force_creation, **kwargs )
470
def initTypeNameToMfnClsMap( ):
471
"""Fill the cache map supplying additional information about the MFNClass to use
472
when creating the classes"""
473
global nodeTypeToMfnClsMap
474
nodeTypeToMfnClsMap = mdb.createTypeNameToMfnClsMap()
476
def initNodeHierarchy( ):
477
"""Initialize the global tree of types, providing a hierarchical relationship between
478
the node typename strings"""
480
nodeTypeTree = mdb.createDagNodeHierarchy()
482
def initWrappers( targetmoduledict ):
483
"""Create Standin Classes that will delay the creation of the actual class till
484
the first instance is requested
486
:param targetmoduledict: the module's dictionary (globals()) to which to put the wrappers"""
488
mrvmaya.initWrappers( targetmoduledict, nodeTypeTree.nodes_iter(), MetaClassCreatorNodes )