Package mrv :: Package maya :: Module mdb
[hide private]
[frames] | no frames]

Source Code for Module mrv.maya.mdb

  1  # -*- coding: utf-8 -*- 
  2  """ 
  3  Provides classes and functions operating on the MayaAPI class database 
  4   
  5  :note: This module must not be auto-initialized as it assumes its parent package to  
  6          be present already 
  7  :note: The implementation is considered internal and may change any time unless stated 
  8          otherwise. 
  9  """ 
 10  __docformat__ = "restructuredtext" 
 11   
 12  from mrv.path import make_path 
 13  from mrv.util import PipeSeparatedFile 
 14  import mrv.maya.env as env 
 15  import mrv.maya as mrvmaya 
 16   
 17  import maya.cmds as cmds 
 18  import maya.OpenMaya as api 
 19   
 20  import UserDict 
 21  import inspect 
 22  import re 
 23  from cStringIO import StringIO 
 24  import string 
 25  import sys 
 26  import os 
 27   
 28  import logging 
 29  log = logging.getLogger("mrv.maya.mdb") 
 30   
 31  __all__ = ("createDagNodeHierarchy", "createTypeNameToMfnClsMap", "apiModules",  
 32             "mfnDBPath", "cacheFilePath", "writeMfnDBCacheFiles",  
 33             "extractMFnFunctions", "PythonMFnCodeGenerator", "MMemberMap",  
 34             "MMethodDescriptor" ) 
35 36 #{ Initialization 37 38 -def nodeHierarchyFile():
39 """:return: Path to the node hierarchy file of the currently active maya version""" 40 return cacheFilePath( "nodeHierarchy", "hf", use_version = 1 )
41
42 -def createDagNodeHierarchy( ):
43 """ Parse the nodes hierarchy file and return a `DAGTree` with its data 44 :return: `DAGTree`""" 45 mfile = nodeHierarchyFile() 46 return mrvmaya.dag_tree_from_tuple_list( mrvmaya.tuple_list_from_file( mfile ) )
47
48 -def createTypeNameToMfnClsMap( ):
49 """Parse a file associating node type names with the best compatible MFn function 50 set and return a dictionary with the data 51 52 :return: dict(((nodeTypeNameStr : api.MFnCls), ...)) dictionary with nodetypeName 53 MFn class mapping""" 54 typenameToClsMap = dict() 55 56 cfile = cacheFilePath( "nodeTypeToMfnCls", "map" ) 57 fobj = open( cfile, 'r' ) 58 pf = PipeSeparatedFile( fobj ) 59 60 version = pf.beginReading( ) # don't care about version 61 for nodeTypeName, mfnTypeName in pf.readColumnLine( ): 62 found = False 63 for apimod in apiModules(): 64 try: 65 typenameToClsMap[ nodeTypeName ] = getattr( apimod, mfnTypeName ) 66 found = True 67 break # it worked, there is only one matching class 68 except AttributeError: 69 pass 70 # END for each api module 71 if not found: 72 log.debug("Couldn't find mfn class named %s" % mfnTypeName) 73 # END for each type/mfnclass pair 74 fobj.close() 75 76 return typenameToClsMap
77
78 #} END initialization 79 80 81 #{ Utilities 82 83 -def apiModules():
84 """:return: tuple of api modules containing MayaAPI classes 85 :note: This takes a moment to load as it will import many api modules. Delay 86 the call as much as possible""" 87 import maya.OpenMaya as api 88 import maya.OpenMayaAnim as apianim 89 import maya.OpenMayaUI as apiui 90 import maya.OpenMayaRender as apirender 91 import maya.OpenMayaFX as apifx 92 93 return (api, apianim, apiui, apirender, apifx)
94
95 -def mfnDBPath( mfnclsname ):
96 """Generate a path to a database file containing mfn wrapping information""" 97 return make_path(cacheFilePath("mfndb/"+ mfnclsname, '', use_version=False)[:-1]) # cut the '.'
98
99 -def headerPath( apiname ):
100 """ 101 :return: Path to file containing the c++ header of the given apiclass' name. 102 The file will not be verified, hence it may be inaccessible 103 :param apiname: string name, like 'MFnBase' 104 :raise ValueError: if MAYA_LOCATION is not set""" 105 p = make_path("$MAYA_LOCATION").expand_or_raise().realpath() 106 if sys.platform == 'darwin': 107 p = p.parent().parent() / "devkit" 108 # END handle platform dependency 109 return p / ("include/maya/%s.h" % apiname)
110
111 -def cacheFilePath( filename, ext, use_version = False ):
112 """Return path to cache file from which you would initialize data structures 113 114 :param use_version: if true, the maya version will be appended to the filename """ 115 mfile = make_path( __file__ ).parent() 116 version = "" 117 if use_version: 118 version = cmds.about( version=1 ).split( " " )[0] 119 # END use version 120 return mfile / ( "cache/%s%s.%s" % ( filename, version, ext ) )
121
122 -def extractMFnFunctions(mfncls):
123 """Extract callables from mfncls, sorted into static methods and instance methods 124 :return: tuple(list(callable_staticmethod, ...), list(callable_instancemethod, ...))""" 125 mfnfuncs = list() 126 staticmfnfuncs = list() 127 mfnname = mfncls.__name__ 128 for fn, f in mfncls.__dict__.iteritems(): 129 if fn.startswith('_') or fn.endswith(mfnname) or not inspect.isroutine(f): 130 continue 131 # END skip non-routines 132 133 if isinstance(f, staticmethod): 134 # convert static methods into callable methods by fetching them officially 135 staticmfnfuncs.append(getattr(mfncls, fn)) 136 else: 137 mfnfuncs.append(f) 138 # END handle static methods 139 # END for each function in mfncls dict 140 141 return (staticmfnfuncs, mfnfuncs)
142
143 -def hasMEnumeration(mfncls):
144 """:return: True if the given mfncls has at least one enumeration""" 145 for n in mfncls.__dict__.keys(): 146 if n.startswith('k') and n[1] in string.ascii_uppercase: # a single k would kill us ... 147 return True 148 # END for each dict name 149 return False
150
151 -def writeMfnDBCacheFiles( ):
152 """Create a simple Memberlist of available mfn classes and their members 153 to allow a simple human-editable way of adjusting which methods will be added 154 to the Nodes. 155 156 :note: currently writes information about all known api modules""" 157 for apimod in apiModules(): 158 mfnclsnames = [ clsname for clsname in dir( apimod ) if clsname.startswith( "MFn" ) ] 159 for mfnname in mfnclsnames: 160 mfncls = getattr( apimod, mfnname ) 161 if not inspect.isclass(mfncls): 162 continue 163 # END assure we don't get methods, like MFnName_deallocateFlag 164 mfnfile = mfnDBPath( mfnname ) 165 166 167 mfnfuncs = list() 168 fstatic, finst = extractMFnFunctions(mfncls) 169 mfnfuncs.extend(fstatic) 170 mfnfuncs.extend(finst) 171 172 if not mfnfuncs: 173 continue 174 175 db = MMemberMap() 176 if mfnfile.exists(): 177 db = MMemberMap( mfnfile ) 178 179 # assure folder exists 180 folder = mfnfile.dirname() 181 if not folder.isdir(): folder.makedirs() 182 183 184 # write data - simple set the keys, use default flags 185 for func in mfnfuncs: 186 # it could be prefixed with the function set name - remove the prefix 187 # This happens in maya2008 + and may introduce plenty of new methods 188 fname = func.__name__ 189 if fname.startswith(mfnname): 190 fname = fname[len(mfnname)+1:] # cut MFnName_(function) 191 # END handle prefix 192 193 db.createEntry(fname) 194 # END for each function to add 195 196 # finally write the change db 197 db.writeToFile( mfnfile )
198 # END for each api class
199 # END for each api module 200 201 -def _createTmpNode(nodetype):
202 """Return tuple(mobject, modifier) for the nodetype or raise RuntimeError 203 doIt has not yet been called on the modifier, hence the mobject is temporary""" 204 try: 205 mod = api.MDGModifier() 206 obj = mod.createNode(nodetype) 207 return (obj, mod) 208 except RuntimeError: 209 mod = api.MDagModifier() 210 tmpparent = mod.createNode("transform") 211 obj = mod.createNode(nodetype, tmpparent) 212 return (obj, mod)
213 # END exception handling
214 # END utility 215 216 -def _iterAllNodeTypes( ):
217 """Returns iterator which yield tuple(nodeTypeName, MObject, modifier) triplets 218 of nodeTypes, with an MObjects instance of it, created with the given modifier, 219 one for each node type available to maya. 220 221 :note: skips manipulators as they tend to crash maya on creation ( perhaps its only 222 one which does that, but its not that important )""" 223 for nodetype in sorted(cmds.ls(nodeTypes=1)): 224 # evil crashers 225 if 'Manip' in nodetype or nodetype.startswith('manip'): 226 continue 227 # END skip manipulators 228 try: 229 obj, mod = _createTmpNode(nodetype) 230 yield nodetype, obj, mod 231 except RuntimeError: 232 log.warn("Could not create '%s'" % nodetype) 233 continue
234 # END create dg/dag node exception handling
235 236 -def generateNodeHierarchy( ):
237 """Generate the node-hierarchy for the current version based on all node types 238 which can be created in maya. 239 240 :return: tuple(DAGTree, typeToMFnClsNameList) 241 242 * DAGTree representing the type hierarchy 243 * list represents typeName to MFnClassName associations 244 245 :note: should only be run as part of the upgrade process to prepare MRV for a 246 new maya release. Otherwise the nodetype tree will be read from a cache""" 247 from mrv.util import DAGTree 248 from mrv.util import uncapitalize, capitalize 249 from mrv.maya.util import MEnumeration 250 251 # init DagTree 252 root = "_root_" 253 depnode = 'dependNode' 254 depnode_list = [depnode] 255 noderoottype = 'node' 256 dagTree = DAGTree() 257 dagTree.add_edge(root, noderoottype) 258 dagTree.add_edge(noderoottype, depnode) 259 260 apiTypeToNodeTypeMap = dict() # apiTypeStr -> nodeTypeName 261 mfnTypes = set() # apiTypeStr of mfns used by NodeTypes 262 sl = list() # string list 263 264 265 mfndep = api.MFnDependencyNode() 266 def getInheritanceAndUndo(obj, modifier): 267 """Takes a prepared modifier ( doIt not yet called ) and the previously created object, 268 returning the inheritance of the obj which was retrieved before undoing 269 its creation""" 270 modifier.doIt() 271 mfndep.setObject(obj) 272 inheritance = cmds.nodeType(mfndep.name(), i=1) 273 modifier.undoIt() 274 return inheritance
275 # END utility 276 277 278 # CREATE ALL NODE TYPES 279 ####################### 280 # query the inheritance afterwards 281 for nodetype, obj, mod in _iterAllNodeTypes(): 282 inheritance = getInheritanceAndUndo(obj, mod) 283 284 if not inheritance: 285 log.error("Failed on type %s" % nodetype) 286 continue 287 # END handle unusual case 288 289 # filter bases 290 for parent, child in zip(depnode_list + inheritance[:-1], inheritance): 291 dagTree.add_edge(parent, child) 292 # END for each edge to add 293 294 # retrieve all compatible MFnTypes - these refer to apiTypes which are 295 # also used by Nodes. Currently we have only the type of the node, fortunately, 296 # it will match very well with the MFnType, by just prepending MFn. 297 # As some MFn are in other modules, we will search em all ... later 298 apiTypeToNodeTypeMap[obj.apiTypeStr()] = nodetype 299 300 api.MGlobal.getFunctionSetList(obj, sl) 301 for mfnType in sl: 302 mfnTypes.add(mfnType) 303 # END for each node type 304 305 # INSERT SPECIAL TYPES 306 ###################### 307 # used by the type system if it cannot classify a node at all 308 dagTree.add_edge(depnode, 'unknown') 309 310 # can be iterated using the DagIterator, and it should at least be a dag node, 311 # not unknown. The groundPlane is actually a special object that users shouldn't 312 # touch directly 313 dagTree.add_edge('transform', 'groundPlane') 314 315 # although we don't handle manips directly, we must still support them if it 316 # is a plugin manipulator 317 dagTree.add_edge('transform', 'manipContainer') 318 319 320 321 # INSERT PLUGIN TYPES 322 ###################### 323 for edge in ( (depnode, 'DependNode'), 324 ('shape', 'Shape'), 325 ('locator', 'LocatorNode'), 326 ('spring', 'SpringNode'), 327 ('transform', 'TransformNode'), 328 ('manipContainer', 'ManipContainer'), 329 ('dynBase', 'EmitterNode'), 330 ('field', 'FieldNode'), 331 ('objectSet', 'ObjectSet'), 332 ('geometryFilter', 'DeformerNode'), 333 (depnode, 'HwShaderNode'), 334 ('ikSolver', 'IkSolver'), 335 (depnode, 'ImagePlaneNode'), 336 (depnode, 'ParticleAttributeMapperNode') ): 337 dagTree.add_edge(edge[0], 'unknownPlugin'+edge[1]) 338 # END for each plugin edge to add 339 340 341 342 # BULD TYPE-TO-MFN MAP 343 ###################### 344 # Prepare data to be put into a type separated file, it associates 345 # a nodeType or nodeApiType with the respecitve MFnClass name 346 typeToMFn = set() # list((typeName, MFnClsName), ...) 347 348 # add default associations - some are not picked up due to name mismatches 349 typeToMFn.add((noderoottype, 'MFn')) 350 typeToMFn.add((depnode, 'MFnDependencyNode')) 351 typeToMFn.add(('dagContainer', 'MFnContainerNode')) 352 353 abstractMFns = ('MFnBase', ) # MFns which cannot be instantiated ans should be ignored 354 failedMFnTypes = list() # list of types we could not yet associate 355 356 modsapi = apiModules() 357 for mfnApiType in mfnTypes: 358 mfnNodePseudoType = uncapitalize(mfnApiType[1:]) # # kSomething -> something 359 nodeType = apiTypeToNodeTypeMap.get(mfnApiType, mfnNodePseudoType) 360 361 # MFnSets follow their kMFnType names, but when we try to associate it with 362 # the actual nodeType . Sometimes, they follow the actual nodeType, so we 363 # have to use this one as well 364 found = False 365 for nt, mfnNameCandidate in ( (mfnNodePseudoType, "MFn%s" % capitalize(mfnApiType[1:])), 366 (nodeType, "MFn%s" % capitalize(nodeType)) ): 367 # ignore abstract ones 368 if mfnNameCandidate in abstractMFns: 369 continue 370 371 for modapi in modsapi: 372 if hasattr(modapi, mfnNameCandidate): 373 found = True 374 375 # prefer a real nodetype if we have one - it will default 376 # to the pseudotype 377 typeToMFn.add((nodeType, mfnNameCandidate)) 378 break 379 # END module with given name exists 380 # END for each api module 381 382 if found: 383 break 384 # END for each nodeType/mfnNamecandidate 385 386 # still not found ? Keep it, but only if there is a nodetype 387 # associated with it 388 if not found and mfnApiType in apiTypeToNodeTypeMap: 389 failedMFnTypes.append(mfnApiType) 390 # END keep a record 391 # END for each mfnType 392 393 394 395 # DATA, COMPONENTS, ATTRIBUTES 396 ############################### 397 # get inheritance of Data, Component and Attribute types 398 def unMFn(name): 399 return uncapitalize(name[3:]) 400 # END remove MFn in front of MFnSomething strings 401 402 for mfnsuffix in ("data", "component", "attribute"): 403 mfnsuffixcap = capitalize(mfnsuffix) 404 mfnnames = list() 405 for modapi in modsapi: 406 mfnnames.extend( n for n in dir(modapi) if n.endswith(mfnsuffixcap) ) 407 # END for each api module to get information from 408 409 dagTree.add_edge(root, mfnsuffix) 410 411 mfnsuffix_root = [ mfnsuffix ] 412 for mfnname in mfnnames: 413 for modapi in modsapi: 414 try: 415 mfncls = getattr(modapi, mfnname) 416 except AttributeError: 417 continue 418 # END handle multi-modules 419 420 # skip classes which are just named like the super type, but 421 # don't actually inherit it 422 if "MFn%s" % mfnsuffixcap not in ( p.__name__ for p in mfncls.mro() ): 423 continue 424 425 # add type->MFn association 426 typeToMFn.add((unMFn(mfnname), mfnname)) 427 428 # cut object and MFnBase 429 # from the names, cut the MFn and uncaptialize it: MFnData -> data 430 pclsnames = [ unMFn(p.__name__) for p in list(reversed(mfncls.mro()))[2:] ] 431 for parent, child in zip(pclsnames[:-1], pclsnames[1:]): 432 dagTree.add_edge(parent, child) 433 # END for each mfn child to add 434 435 break 436 # END for each api module 437 # END for each name 438 # END for each mfnsuffix 439 440 441 # HANDLE FAILED MFN-ASSOCITAIONS 442 ################################ 443 # lets take some very special care ! 444 if failedMFnTypes: 445 # Here we handle cases which don't follow any naming conventions really 446 # Hence we have to instantiate an object of the failed type, then 447 # we instantiate all the remaining functions sets to see which one fits. 448 # If the required one has the requested type, we have a match. 449 # If we have no type match, its still a valid MFn - If we haven't seen it 450 # yet, its probably a base MFn whose kType string allows deduction of the 451 # actual abtract node type which we will use instead. 452 associatedMFns = ( t[1] for t in typeToMFn ) 453 allMFnSetNames = list() 454 for modapi in modsapi: 455 allMFnSetNames.extend( n for n in dir(modapi) if n.startswith('MFn') and 456 not n.endswith('Ptr') and 457 not '_' in n and # skip 'special' ones 458 not 'Manip' in n ) # skip everything about Manipulators 459 # END get all MFn names 460 461 # find MFnClasses for each candidate name 462 candidateMFnNames = (set(allMFnSetNames) - set(associatedMFns)) - set(abstractMFns) 463 candidateMFns = list() 464 for cn in list(candidateMFnNames): 465 for modapi in modsapi: 466 try: 467 mfncls = getattr(modapi, cn) 468 # ignore them if they don't derive from MFnBase 469 if not hasattr(mfncls, "type"): 470 log.debug("Skipped MFn %s as it didn't derive from MFnBase" % mfncls) 471 candidateMFnNames.discard(cn) 472 continue 473 # END skip mfn 474 candidateMFns.append(mfncls) 475 break 476 except AttributeError: 477 continue 478 # END for each api module 479 # END for each candidate name 480 481 succeededMFnNames = set() 482 483 484 # PRUNE REMAINING MFNs 485 # prune out all MFnClasses that can be constructed without an actual object 486 enumMembers = MEnumDescriptor('Type') 487 enumMembers.extend( m for m in dir(api.MFn) if m.startswith('k') ) 488 mfntypes = MEnumeration.create(enumMembers, api.MFn) 489 490 for mfncls in candidateMFns[:]: 491 try: 492 mfninst = mfncls() 493 if mfntypes.nameByValue(mfninst.type()) in failedMFnTypes: 494 continue 495 # END keep actually missing MFns 496 candidateMFns.remove(mfncls) 497 candidateMFnNames.remove(mfncls.__name__) 498 except RuntimeError: 499 continue 500 # END for each possible MFn to prune 501 502 # at this point, we have about 500 api types with no MFn, but only 503 # about 10 function sets, 504 # Now ... we brute-force test our way through all of these to find 505 # matching ones ... argh 506 derivedMatches = list() # keeps tuple(kTypeStr, mfnname) of matches of derived types 507 perfectMatches = list() # keeps mfnnames of perfect matches 508 for failedApiTypeStr in failedMFnTypes: 509 nodeType = apiTypeToNodeTypeMap[failedApiTypeStr] 510 obj, mod = _createTmpNode(nodeType) 511 512 removeThisMFn = None 513 for mfncls in candidateMFns: 514 try: 515 mfninst = mfncls(obj) 516 except RuntimeError: 517 continue 518 # END handle incompatability 519 520 apiTypeStr = mfntypes.nameByValue(mfninst.type()) 521 522 if apiTypeStr not in failedMFnTypes: 523 removeThisMFn = mfncls 524 break 525 # END remove MFns that no one wants 526 527 if apiTypeStr == failedApiTypeStr: 528 mfnname = mfncls.__name__ 529 typeToMFn.add((nodeType, mfnname)) 530 perfectMatches.append(mfnname) 531 removeThisMFn = mfncls 532 break 533 # END perfect match 534 535 # its matching, but its not perfectly suited for our node type 536 # We keep it, and will map it later if we don't find a better match 537 derivedMatches.append((apiTypeStr, mfncls.__name__)) 538 # END for each mfncls 539 540 if removeThisMFn is not None: 541 succeededMFnNames.add(removeThisMFn.__name__) 542 candidateMFns.remove(removeThisMFn) 543 # END remove matched MFn 544 545 if not candidateMFns: 546 break 547 # END abort search if there is nothing left 548 # END for each failed type 549 550 # HANDLE DERIVED MFns 551 # prune out all derived mfs which have found a perfect match in the meanwhile 552 # the rest will be added to the list 553 for apiTypeStr, mfnname in filter(lambda t: t not in perfectMatches, derivedMatches): 554 typeToMFn.add((apiTypeToNodeTypeMap[apiTypeStr], mfnname)) 555 succeededMFnNames.add(mfnname) 556 # END for each apiTypeStr left ot add 557 558 559 # LAST MANUAL WORK 560 ################## 561 # SubDees, if created empty, cannot be attached to their function set 562 # Hence we don't see the match, but ... we know its there, so we add it 563 # ourselves 564 for nodeType, mfnname in (('subdiv', 'MFnSubd'), ): 565 typeToMFn.add((nodeType, mfnname)) 566 succeededMFnNames.add(mfnname) 567 # END for each manually added type 568 569 570 for mfnname in candidateMFnNames - succeededMFnNames: 571 log.warn("Could not associate MFn: %s" % mfnname) 572 # END provide some info 573 # END special treatment 574 return (dagTree, sorted(typeToMFn, key=lambda t: t[0])) 575
576 #} END functions 577 578 579 #{ Code Generators 580 581 -class MFnCodeGeneratorBase(object):
582 """Define the interface and common utility methods to generate a string defining 583 code for a given MFnMethod according to the meta data provided by an `MMethodDescriptor`. 584 585 Once instantiated, it can create any number of methods""" 586 __slots__ = 'module_dict'
587 - def __init__(self, module_dict):
588 """Intialize this instance""" 589 self.module_dict = module_dict
590 591 #{ Utilities
592 - def _toRvalFunc( self, funcname ):
593 """:return: None or a function which receives the return value of our actual mfn function""" 594 if not isinstance( funcname, basestring ): 595 return funcname 596 if funcname == 'None': return None 597 598 try: 599 return self.module_dict[funcname] 600 except KeyError: 601 raise ValueError("'%s' does not exist in code generator's dictionary" % funcname )
602 #} END utilities 603 604 605 #{ Interface
606 - def generateMFnClsMethodWrapper(self, source_method_name, target_method_name, mfn_fun_name, method_descriptor, flags=0):
607 """ 608 :return: string containing the code for the wrapper method as configured by the 609 method descriptor 610 :param source_method_name: Original name of the method - this is the name under which 611 it was requested. 612 :param target_method_name: Name of the method in the returned code string 613 :param mfn_fun_name: original name of the MFn function 614 :param method_descriptor: instance of `MMethodDescriptor` 615 :param flags: bit flags providing additional information, depending on the actual 616 implementation. Unsupported flags are ignored.""" 617 raise NotImplementedError("To be implemented in SubClass")
618 #} END interfacec
619 620 621 -class PythonMFnCodeGenerator(MFnCodeGeneratorBase):
622 """Specialization to generate python code 623 624 **Flags**: 625 626 * kDirectCall: 627 If set, the call return the actual mfn method in the best case, which is 628 a call as direct as it gets. A possibly negative side-effect would be that 629 it the MFnMethod caches the function set and actual MObject/MDagPath, which 630 can be dangerous if held too long 631 632 * kIsMObject: 633 If set, the type we create the method for is not derived from Node, but 634 from MObject. This hint is required in order to generate correct calling code. 635 636 * kIsDagNode: 637 If set, the type we create the method for is derived from DagNode 638 639 * kIsStatic: 640 If set, the method to be wrapped is considered static, no self is needed, nor 641 any object. 642 NOTE: This flag is likely to be removed as it should be part of the method_descriptor, 643 for now though it does not provide that information so we pass it in. 644 645 * kWithDocs: 646 If set, a doc string will be generated the method. In future, this information 647 will come from the method descriptor. Please note that docs should only be attaced 648 in interactive modes, otherwise its a waste of memory. 649 650 """ 651 # IMPORTANT: If these change, update docs above, and test.maya.test_mdb and test.maya.performance.test_mdb ! 652 kDirectCall, \ 653 kIsMObject, \ 654 kIsDagNode, \ 655 kIsStatic, \ 656 kWithDocs = [ 1<<i for i in range(5) ] 657
658 - def generateMFnClsMethodWrapper(self, source_method_name, target_method_name, mfn_fun_name, method_descriptor, flags=0):
659 """Generates code as python string which can be used to compile a function. It assumes the following 660 globals to be existing once evaluated: mfncls, mfn_fun, [rvalfunc] 661 Currently supports the following data within method_descriptor: 662 663 * method_descriptor.rvalfunc 664 665 as well as all flags except kIsStatic. 666 :raise ValueError: if flags are incompatible with each other 667 """ 668 if flags & self.kIsMObject and flags & self.kIsDagNode: 669 raise ValueError("kIsMObject and kIsDagNode are mutually exclusive") 670 # END handle flags 671 672 673 sio = StringIO() 674 675 rvalfunname = '' 676 if method_descriptor.rvalfunc != 'None': 677 rvalfunname = method_descriptor.rvalfunc 678 679 sio.write("def %s(self, *args, **kwargs):\n" % target_method_name) 680 681 682 # mfn function call 683 mfnset = "mfncls(self" 684 if flags & self.kIsDagNode: 685 mfnset += ".dagPath()" 686 elif not flags & self.kIsMObject: 687 mfnset += ".object()" 688 mfnset += ")" 689 690 if flags & self.kDirectCall: 691 curline = "\tmfninstfunc = %s.%s\n" % (mfnset, mfn_fun_name) 692 sio.write(curline) 693 694 if rvalfunname: 695 sio.write("\tmfninstfunc = lambda *args, **kwargs: rvalfun(mfninstfunc(*args, **kwargs))\n") 696 # END handle rvalfunc name 697 sio.write("\tself.%s = mfninstfunc\n" % source_method_name) 698 sio.write("\treturn mfninstfunc(*args, **kwargs)") 699 else: 700 curline = "mfn_fun(%s, *args, **kwargs)" % mfnset 701 if rvalfunname: 702 curline = "rvalfunc(%s)" % curline 703 sio.write("\treturn %s" % curline) 704 # END handle direct call 705 706 return sio.getvalue()
707 708 #{ Interface 709
710 - def generateMFnClsMethodWrapperMethod(self, source_method_name, target_method_name, mfncls, mfn_fun, method_descriptor, flags=0):
711 """:return: python function suitable to be installed on a class 712 :param mfncls: MFnFunction set class from which the method was retrieved. 713 :param mfn_fun: function as retrieved from the function set's dict. Its a bare function. 714 :note: For all other args, see `MFnCodeGeneratorBase.generateMFnClsMethodWrapper`""" 715 rvalfunc = self._toRvalFunc(method_descriptor.rvalfunc) 716 mfnfuncname = mfn_fun.__name__ 717 718 # handle MFnName_function 719 if mfnfuncname.startswith(mfncls.__name__): 720 mfnfuncname = mfnfuncname[len(mfncls.__name__)+1:] 721 722 new_method = None 723 if flags & self.kIsStatic: 724 # use the function directly 725 rvalfun = self._toRvalFunc(method_descriptor.rvalfunc) 726 if rvalfun is None: 727 new_method = mfn_fun 728 else: 729 fun = lambda *args, **kwargs: rvalfun(mfn_fun(*args, **kwargs)) 730 fun.__name__ = target_method_name 731 new_method = fun 732 # END 733 else: 734 # get the compiled code 735 codestr = self.generateMFnClsMethodWrapper(source_method_name, target_method_name, mfnfuncname, method_descriptor, flags) 736 code = compile(codestr, "mrv/%s" % (mfncls.__name__+".py"), "exec") # this operation is expensive ! 737 738 # get the function into our local dict, globals are our locals 739 eval(code, locals()) 740 741 new_method = locals()[target_method_name] 742 # END handle static methods 743 744 if flags & self.kWithDocs: 745 if hasattr(new_method, 'func_doc'): 746 new_method.func_doc = "%s.%s" % (mfncls.__name__, mfnfuncname) 747 # END attach generated doc string 748 749 return new_method
750
751 #} END interface 752 753 #} END code generators 754 755 #{ Parsers 756 757 -class CppHeaderParser(object):
758 """Simplistic regex based parser which will extract information from the file 759 it was initialized with. 760 761 For now its so simple that there is no more than one method""" 762 reEnums = re.compile( r"""^\s+ enum \s+ (?P<name>\w+) \s* \{ # enum EnumName 763 (?P<members>[\(\)/\w\s,\-+="'\.\#!<\*\\]+) # match whitespace or newlines 764 \}[ \t]*;[ \t]*$ # closing brace""", 765 re.MULTILINE|re.VERBOSE) 766 767 reEnumMembers = re.compile( """ 768 [\t ]{2,} # assure we don't get something within the comment 769 (k\w+)[ ]* # find kSomething 770 (?:=[ ]*[\w]+[ ]*)? # optionally find initializer = int|other_enum_member 771 """, re.VERBOSE) 772 773 @classmethod
774 - def parseAndExtract(cls, header_filepath, parse_enums=True):
775 """Parse the given header file and return the parsed information 776 777 :param header_filepath: Path pointing to the given header file. Its currently 778 assumed to be 7 bit ascii 779 :param parse_enums: If True, enumerations will be parsed from the file. If 780 False, the enumeration tuple in the return value will be empty. 781 :note: Currently we can only parse non-anonymous enumerations ! 782 :return: tuple(tuple(MEnumDescriptor, ...), )""" 783 enum_list = list() 784 785 # ENUMERATIONS 786 ############## 787 # read everything, but skip the license text when matching 788 if parse_enums: 789 read_method = header_filepath.bytes 790 # on windows, we have \r\n newlines, which are automatically 791 # converted to \n by the .text method. This might be a bit slower, 792 # so we only do it on windows 793 if os.name == 'nt': 794 read_method = header_filepath.text 795 # END handle newline sequence 796 797 header = read_method() 798 for enummatch in cls.reEnums.finditer(header, 2188): 799 ed = MEnumDescriptor(enummatch.group('name')) 800 801 # parse all occurrences of kSomething, including the initializer 802 members = enummatch.group('members') 803 assert members 804 for memmatch in cls.reEnumMembers.finditer(members): 805 ed.append(memmatch.group(1)) 806 # END for each member to add 807 808 enum_list.append(ed) 809 # END for each match 810 # END if enums should be parsed 811 812 # METHODS 813 ######### 814 # TODO: 815 816 return (tuple(enum_list), )
817
818 #} END parsers 819 820 821 #{ Database 822 823 -class MMethodDescriptor(object):
824 """Contains meta-information about a given method according to data read from 825 the MFnDatabase""" 826 __slots__ = ("flag", "rvalfunc", "newname") 827
828 - def __init__( self, flag='', rvalfunc = None, newname="" ):
829 self.flag = flag 830 self.rvalfunc = rvalfunc 831 self.newname = newname
832 840
841 842 -class MMemberMap( UserDict.UserDict ):
843 """Simple accessor for MFnDatabase access 844 Direct access like db[funcname] returns an entry object with all values 845 846 **Globals**: 847 The __globals__ entry in MFn db files allows to pass additional options. 848 Currently there are no supported flags""" 849 __slots__ = ("flags", "enums") 850 kDelete = 'x' 851
852 - def __init__( self, filepath = None, parse_enums=False ):
853 """intiialize self from a file if not None 854 855 :param parse_enums: if True, enumerations will be parsed. Save time by specifying 856 False in case you know that there are no enumerations""" 857 UserDict.UserDict.__init__( self ) 858 859 self._filepath = filepath 860 if filepath: 861 self._initFromFile( filepath ) 862 863 # initialize globals 864 self.flags = 0 865 ge = self.get('__global__', None) 866 if ge is not None: 867 # currently we know none 868 pass 869 # END fetch info 870 871 # INITIALIZE PARSED DATA 872 self.enums = tuple() 873 if parse_enums: 874 self.enums, = CppHeaderParser.parseAndExtract(headerPath(filepath.namebase()))
875 # END if enumerations should be parsed 876
877 - def __str__( self ):
878 return "MMemberMap(%s)" % self._filepath
879 880
881 - def _initFromFile( self, filepath ):
882 """Initialize the database with values from the given file 883 884 :note: the file must have been written using the `writeToFile` method""" 885 self.clear() 886 fobj = open( filepath, 'r' ) 887 888 pf = PipeSeparatedFile( fobj ) 889 pf.beginReading( ) 890 891 # get the entries 892 for tokens in pf.readColumnLine( ): 893 key = tokens[ 1 ] 894 self[ key ] = MMethodDescriptor( flag=tokens[0], rvalfunc=tokens[2], newname=tokens[3] )
895 # END for each token in read column line 896
897 - def writeToFile( self, filepath ):
898 """Write our database contents to the given file""" 899 klist = self.keys() 900 klist.sort() 901 902 fobj = open( filepath, 'w' ) 903 pf = PipeSeparatedFile( fobj ) 904 pf.beginWriting( ( 4,40,20,40 ) ) 905 906 for key in klist: # write entries 907 e = self[ key ] 908 pf.writeTokens( ( e.flag, key,e.rvalfunc, e.newname ) ) 909 # end for each key 910 911 fobj.close()
912
913 - def methodByName( self, funcname ):
914 """ 915 :return: Tuple( mfnfuncname, entry ) 916 original mfnclass function name paired with the 917 db entry containing more information 918 :raise KeyError: if no such function exists""" 919 try: 920 return ( funcname, self[ funcname ] ) 921 except KeyError: 922 for mfnfuncname,entry in self.iteritems(): 923 if entry.newname == funcname: 924 return ( mfnfuncname, entry ) 925 # END for each item 926 927 raise KeyError( "Function named '%s' did not exist in db" % funcname )
928
929 - def createEntry( self, funcname ):
930 """ Create an entry for the given function, or return the existing one 931 932 :return: Entry object for funcname""" 933 return self.setdefault( funcname, MMethodDescriptor() )
934
935 - def mfnFunc( self, funcname ):
936 """:return: mfn functionname corresponding to the ( possibly renamed ) funcname """ 937 return self.methodByName( funcname )[0]
938 939 #} END database 940