| Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf-8 -*-
2 """
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.
6 """
7 __docformat__ = "restructuredtext"
8
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
14
15
16 import maya.OpenMaya as api
17
18 from new import instancemethod
19 import logging
20 log = logging.getLogger("mrv.maya.nt.typ")
21
22 __all__ = ("MetaClassCreatorNodes", )
23
24 #{ Caches
25 _nodesdict = None # to be set during initialization
26 nodeTypeTree = None
27 nodeTypeToMfnClsMap = dict() # allows to see the most specialized compatible mfn cls for a given node type
28 #} END caches
29
30 #{Globals
31 targetModule = None # must be set in intialization to tell this class where to put newly created classes
32 mfnclsattr = '_mfncls'
33 mfndbattr = '_mfndb'
34 apiobjattr = '_apiobj'
35 getattrorigname = '__getattr_orig'
36 codegen = None # python code generator, to be set during initialization
42 """Builds the base hierarchy for the given classname based on our typetree
43 :todo: build classes with slots only as members are pretermined"""
44
45 @classmethod
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
49
50 :param kwargs: passed to MMemberMap initializer"""
51 try:
52 return newcls.__dict__[ mfndbattr ]
53 except KeyError:
54 mfndbpath = mdb.mfnDBPath(mfncls.__name__)
55 try:
56 mfndb = mdb.MMemberMap(mfndbpath, **kwargs)
57 except IOError:
58 print IOError("Could not create MFnDB for file at %s" % mfndbpath)
59 raise
60 # END handle mmap reading
61 type.__setattr__( newcls, mfndbattr, mfndb )
62 return mfndb
63 # END mfndb handling
64
65 @classmethod
67 """Find static mfnmethods - if these are available, initialize the
68 mfn database for the given function set ( ``mfncls`` ) and create properly
69 wrapped methods.
70 Additionally check for enumerations, and generate the respective enumeration
71 instances
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:
81 return
82
83 mfndb = cls._fetchMfnDB(newcls, mfncls, parse_enums=hasEnum)
84 if fstatic:
85 mfnname = mfncls.__name__
86 for fs in fstatic:
87 fn = fs.__name__
88 if fn.startswith(mfnname):
89 fn = fn[len(mfnname)+1:] # cut MFnName_methodName
90 # END handle name prefix
91
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
96
97
98 if hasEnum:
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
103
104
105 @classmethod
107 """Create a function that makes a Node natively use its associated Maya
108 function set on calls.
109
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.
112
113 The method mutation database allows to adjust the way a method is being wrapped
114
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
128
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
135
136
137 method_descriptor = None
138 # adjust function according to DB
139 try:
140 mfnfuncname, method_descriptor = mfndb.methodByName( funcname )
141 # delete function ?
142 if method_descriptor.flag == mdb.MMemberMap.kDelete:
143 return None
144 except KeyError:
145 pass # could just be working
146 # END if entry available
147
148 if method_descriptor is None:
149 method_descriptor = mdb.MMethodDescriptor()
150 # END assure method descriptor is set
151
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
162
163 # convert static method to real method
164 mfnfunc = type.__getattribute__(mfncls, mfnfuncname)
165 # END handle
166
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
172
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)
176
177 if not flags & mdb.PythonMFnCodeGenerator.kIsStatic:
178 newfunc.__name__ = funcname # rename the method
179 # END handle renames
180 return newfunc
181
182
183 @classmethod
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 )
189 if getattrorig:
190 getattrorig.__name__ = getattrorigname
191 setattr( newcls, getattrorigname, getattrorig )
192
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
197
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
204 continue
205
206 mfncls = getattr( basecls, mfnclsattr )
207 if not mfncls:
208 continue
209
210 # get function as well as its possibly changed name
211 try:
212 mfndb = thiscls._fetchMfnDB(basecls, mfncls)
213 newclsfunc = thiscls._wrapMfnFunc( newcls, mfncls, attr, mfndb )
214 if not newclsfunc: # Function %s has been deleted - ignore
215 continue
216 except KeyError: # function not available in this mfn - ignore
217 continue
218 newinstfunc = instancemethod( newclsfunc, self, basecls ) # create the respective instance method !
219 actualcls = basecls
220 break # stop here - we found it
221 # END for each basecls ( searching for mfn func )
222
223 # STORE MFN FUNC ( if available )
224 # store the new function on instance level !
225 # ... and on class level
226 if newclsfunc:
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 :)
230 return newinstfunc
231 # END newclsfunc exists
232
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
239 # base class
240 getattrorigfunc = None
241 for basecls in newcls.mro():
242 if hasattr( basecls, getattrorigname ):
243 getattrorigfunc = getattr( basecls, getattrorigname )
244 break
245 # END orig getattr method check
246
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
252 break
253 # END default getattr method check
254 # END for each base ( searching for getattr_orig or nonoverwritten getattr )
255
256 if not getattrorigfunc:
257 raise AttributeError( "Could not find mfn function for attribute '%s'" % attr )
258
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
264
265 # STORE LAZY GETATTR
266 # identification !
267 setattr( newcls, "__getattr__", meta_getattr_lazy )
268
269
271 """ Called to create the class with name """
272 # will be used later
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):
279 return name
280
281 capname = capitalize(name)
282 if nodeTypeTree.has_node(capname):
283 return capname
284
285 return name
286 # END utility
287
288 # ATTACH MFNCLS
289 #################
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
292 mfncls = None
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
299
300 # attach static mfn methods directly.
301 needs_static_method_initialization = True
302 # END attach mfncls to type
303 else:
304 mfncls = clsdict[ mfnclsattr ]
305
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
308 if mfncls:
309 clsdict[ mfnclsattr ] = mfncls # we have at least a None mfn
310 clsdict[ apiobjattr ] = None # always have an api obj
311
312
313 # CREATE CLS
314 #################
315 newcls = super( MetaClassCreatorNodes, metacls ).__new__( nodeTypeTree, targetModule,
316 metacls, name, bases, clsdict,
317 nameToTreeFunc = func_nameToTree )
318
319
320 # LAZY MFN WRAPPING
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
326 if mfncls:
327 metacls._wrapLazyGetAttr( newcls )
328
329 if needs_static_method_initialization:
330 metacls._wrapStaticMembers(newcls, mfncls)
331 # END if mfncls defined
332
333
334 return newcls
335
336 # END __new__
337
338 #} END metaclasses
339
340
341 #{ Utilities
342
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.
347
348 :note: Attaches docstrings as well
349 :return: integer representing the number of generated methods"""
350 log.info("Prefetching all MFnMethods")
351
352 num_fetched = 0
353 for typename, mfncls in nodeTypeToMfnClsMap.iteritems():
354 try:
355 nodetype = _nodesdict[capitalize(typename)]
356 except KeyError:
357 log.debug("MFn methods for %s exists, but type was not found in nt" % typename)
358 continue
359 # END handle type exceptions
360
361 mfnname = mfncls.__name__
362 mfndb = MetaClassCreatorNodes._fetchMfnDB(nodetype, mfncls)
363 fstatic, finst = mdb.extractMFnFunctions(mfncls)
364
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
369 # END utility
370
371 for f in finst:
372 fn = f.__name__
373 if fn.startswith(mfnname):
374 fn = fn[len(mfnname)+1:]
375 # END handle prefixed names
376
377 fna = fn # alias for method
378 try:
379 origname, entry = mfndb.methodByName(fn)
380 fna = entry.newname
381 except KeyError:
382 pass
383 # END get alias metadata
384
385 fwrapped = MetaClassCreatorNodes._wrapMfnFunc(nodetype, mfncls, fn, mfndb, mdb.PythonMFnCodeGenerator.kWithDocs)
386
387 # could have been deleted
388 if fwrapped is None:
389 continue
390
391 set_method_if_possible(nodetype, fn, fwrapped)
392 if fna != fn:
393 set_method_if_possible(nodetype, fna, fwrapped)
394 # END handle aliases
395
396 num_fetched += 1
397 # END for each instance function
398 # END for each type/mfncls pair
399
400 return num_fetched
401
402
403
404 #} END utilities
405
406 #{ Initialization
407
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
412
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 )
426
427 # create wrapper ( in case newclsname does not yet exist in target module )
428 mrvmaya.initWrappers( targetmoduledict, [ newclsname ], metaclass, **kwargs )
429
432 """Remove the given typename from the given target module's dictionary as
433 well as from internal caches
434
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
438 to be capitalized"""
439 try:
440 del(targetmoduledict[customTypeName])
441 except KeyError:
442 pass
443 # END remove from dictionary
444
445 customTypeName = uncapitalize(customTypeName)
446 if nodeTypeTree.has_node(customTypeName):
447 nodeTypeTree.remove_node(customTypeName)
448 # END remove from type tree
449
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
454
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 ):
463 yield (node,child)
464 for edge in recurseOutEdges( child ): # step down the hierarchy
465 yield edge
466
467 nodeTypeTree.add_edges_from( recurseOutEdges( rootnode ) )
468 mrvmaya.initWrappers( targetmoduledict, dagtree.nodes_iter(), metaclass, force_creation = force_creation, **kwargs )
469
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()
475
477 """Initialize the global tree of types, providing a hierarchical relationship between
478 the node typename strings"""
479 global nodeTypeTree
480 nodeTypeTree = mdb.createDagNodeHierarchy()
481
483 """Create Standin Classes that will delay the creation of the actual class till
484 the first instance is requested
485
486 :param targetmoduledict: the module's dictionary (globals()) to which to put the wrappers"""
487 global nodeTypeTree
488 mrvmaya.initWrappers( targetmoduledict, nodeTypeTree.nodes_iter(), MetaClassCreatorNodes )
489
490 #} END initialization
491
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Apr 19 18:00:22 2011 | http://epydoc.sourceforge.net |