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 initialization42 """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 @classmethod268 26947 """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 mfndb63 # END mfndb handling 64 65 @classmethod67 """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 @classmethod107 """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 newfunc181 182 183 @classmethod185 """ 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 )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 name286 # 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 335336 # 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 401402 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 )429432 """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 tree449 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 edge466 467 nodeTypeTree.add_edges_from( recurseOutEdges( rootnode ) ) 468 mrvmaya.initWrappers( targetmoduledict, dagtree.nodes_iter(), metaclass, force_creation = force_creation, **kwargs ) 469471 """Fill the cache map supplying additional information about the MFNClass to use 472 when creating the classes""" 473 global nodeTypeToMfnClsMap 474 nodeTypeToMfnClsMap = mdb.createTypeNameToMfnClsMap()475477 """Initialize the global tree of types, providing a hierarchical relationship between 478 the node typename strings""" 479 global nodeTypeTree 480 nodeTypeTree = mdb.createDagNodeHierarchy()481483 """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 |