3
Provides classes and functions operating on the MayaAPI class database
5
:note: This module must not be auto-initialized as it assumes its parent package to
7
:note: The implementation is considered internal and may change any time unless stated
10
__docformat__ = "restructuredtext"
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
17
import maya.cmds as cmds
18
import maya.OpenMaya as api
23
from cStringIO import StringIO
29
log = logging.getLogger("mrv.maya.mdb")
31
__all__ = ("createDagNodeHierarchy", "createTypeNameToMfnClsMap", "apiModules",
32
"mfnDBPath", "cacheFilePath", "writeMfnDBCacheFiles",
33
"extractMFnFunctions", "PythonMFnCodeGenerator", "MMemberMap",
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 )
42
def createDagNodeHierarchy( ):
43
""" Parse the nodes hierarchy file and return a `DAGTree` with its data
45
mfile = nodeHierarchyFile()
46
return mrvmaya.dag_tree_from_tuple_list( mrvmaya.tuple_list_from_file( mfile ) )
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
52
:return: dict(((nodeTypeNameStr : api.MFnCls), ...)) dictionary with nodetypeName
54
typenameToClsMap = dict()
56
cfile = cacheFilePath( "nodeTypeToMfnCls", "map" )
57
fobj = open( cfile, 'r' )
58
pf = PipeSeparatedFile( fobj )
60
version = pf.beginReading( ) # don't care about version
61
for nodeTypeName, mfnTypeName in pf.readColumnLine( ):
63
for apimod in apiModules():
65
typenameToClsMap[ nodeTypeName ] = getattr( apimod, mfnTypeName )
67
break # it worked, there is only one matching class
68
except AttributeError:
70
# END for each api module
72
log.debug("Couldn't find mfn class named %s" % mfnTypeName)
73
# END for each type/mfnclass pair
76
return typenameToClsMap
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
93
return (api, apianim, apiui, apirender, apifx)
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 '.'
99
def headerPath( apiname ):
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)
111
def cacheFilePath( filename, ext, use_version = False ):
112
"""Return path to cache file from which you would initialize data structures
114
:param use_version: if true, the maya version will be appended to the filename """
115
mfile = make_path( __file__ ).parent()
118
version = cmds.about( version=1 ).split( " " )[0]
120
return mfile / ( "cache/%s%s.%s" % ( filename, version, ext ) )
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, ...))"""
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):
131
# END skip non-routines
133
if isinstance(f, staticmethod):
134
# convert static methods into callable methods by fetching them officially
135
staticmfnfuncs.append(getattr(mfncls, fn))
138
# END handle static methods
139
# END for each function in mfncls dict
141
return (staticmfnfuncs, mfnfuncs)
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 ...
148
# END for each dict name
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
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):
163
# END assure we don't get methods, like MFnName_deallocateFlag
164
mfnfile = mfnDBPath( mfnname )
168
fstatic, finst = extractMFnFunctions(mfncls)
169
mfnfuncs.extend(fstatic)
170
mfnfuncs.extend(finst)
177
db = MMemberMap( mfnfile )
179
# assure folder exists
180
folder = mfnfile.dirname()
181
if not folder.isdir(): folder.makedirs()
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)
193
db.createEntry(fname)
194
# END for each function to add
196
# finally write the change db
197
db.writeToFile( mfnfile )
198
# END for each api class
199
# END for each api module
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"""
205
mod = api.MDGModifier()
206
obj = mod.createNode(nodetype)
209
mod = api.MDagModifier()
210
tmpparent = mod.createNode("transform")
211
obj = mod.createNode(nodetype, tmpparent)
213
# END exception handling
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.
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)):
225
if 'Manip' in nodetype or nodetype.startswith('manip'):
227
# END skip manipulators
229
obj, mod = _createTmpNode(nodetype)
230
yield nodetype, obj, mod
232
log.warn("Could not create '%s'" % nodetype)
234
# END create dg/dag node exception handling
236
def generateNodeHierarchy( ):
237
"""Generate the node-hierarchy for the current version based on all node types
238
which can be created in maya.
240
:return: tuple(DAGTree, typeToMFnClsNameList)
242
* DAGTree representing the type hierarchy
243
* list represents typeName to MFnClassName associations
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
253
depnode = 'dependNode'
254
depnode_list = [depnode]
255
noderoottype = 'node'
257
dagTree.add_edge(root, noderoottype)
258
dagTree.add_edge(noderoottype, depnode)
260
apiTypeToNodeTypeMap = dict() # apiTypeStr -> nodeTypeName
261
mfnTypes = set() # apiTypeStr of mfns used by NodeTypes
262
sl = list() # string list
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
271
mfndep.setObject(obj)
272
inheritance = cmds.nodeType(mfndep.name(), i=1)
278
# CREATE ALL NODE TYPES
279
#######################
280
# query the inheritance afterwards
281
for nodetype, obj, mod in _iterAllNodeTypes():
282
inheritance = getInheritanceAndUndo(obj, mod)
285
log.error("Failed on type %s" % nodetype)
287
# END handle unusual case
290
for parent, child in zip(depnode_list + inheritance[:-1], inheritance):
291
dagTree.add_edge(parent, child)
292
# END for each edge to add
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
300
api.MGlobal.getFunctionSetList(obj, sl)
302
mfnTypes.add(mfnType)
303
# END for each node type
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')
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
313
dagTree.add_edge('transform', 'groundPlane')
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')
321
# INSERT PLUGIN TYPES
322
######################
323
for edge in ( (depnode, 'DependNode'),
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
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), ...)
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'))
353
abstractMFns = ('MFnBase', ) # MFns which cannot be instantiated ans should be ignored
354
failedMFnTypes = list() # list of types we could not yet associate
356
modsapi = apiModules()
357
for mfnApiType in mfnTypes:
358
mfnNodePseudoType = uncapitalize(mfnApiType[1:]) # # kSomething -> something
359
nodeType = apiTypeToNodeTypeMap.get(mfnApiType, mfnNodePseudoType)
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
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:
371
for modapi in modsapi:
372
if hasattr(modapi, mfnNameCandidate):
375
# prefer a real nodetype if we have one - it will default
377
typeToMFn.add((nodeType, mfnNameCandidate))
379
# END module with given name exists
380
# END for each api module
384
# END for each nodeType/mfnNamecandidate
386
# still not found ? Keep it, but only if there is a nodetype
388
if not found and mfnApiType in apiTypeToNodeTypeMap:
389
failedMFnTypes.append(mfnApiType)
391
# END for each mfnType
395
# DATA, COMPONENTS, ATTRIBUTES
396
###############################
397
# get inheritance of Data, Component and Attribute types
399
return uncapitalize(name[3:])
400
# END remove MFn in front of MFnSomething strings
402
for mfnsuffix in ("data", "component", "attribute"):
403
mfnsuffixcap = capitalize(mfnsuffix)
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
409
dagTree.add_edge(root, mfnsuffix)
411
mfnsuffix_root = [ mfnsuffix ]
412
for mfnname in mfnnames:
413
for modapi in modsapi:
415
mfncls = getattr(modapi, mfnname)
416
except AttributeError:
418
# END handle multi-modules
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() ):
425
# add type->MFn association
426
typeToMFn.add((unMFn(mfnname), mfnname))
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
436
# END for each api module
438
# END for each mfnsuffix
441
# HANDLE FAILED MFN-ASSOCITAIONS
442
################################
443
# lets take some very special care !
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
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:
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)
474
candidateMFns.append(mfncls)
476
except AttributeError:
478
# END for each api module
479
# END for each candidate name
481
succeededMFnNames = set()
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)
490
for mfncls in candidateMFns[:]:
493
if mfntypes.nameByValue(mfninst.type()) in failedMFnTypes:
495
# END keep actually missing MFns
496
candidateMFns.remove(mfncls)
497
candidateMFnNames.remove(mfncls.__name__)
500
# END for each possible MFn to prune
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)
513
for mfncls in candidateMFns:
515
mfninst = mfncls(obj)
518
# END handle incompatability
520
apiTypeStr = mfntypes.nameByValue(mfninst.type())
522
if apiTypeStr not in failedMFnTypes:
523
removeThisMFn = mfncls
525
# END remove MFns that no one wants
527
if apiTypeStr == failedApiTypeStr:
528
mfnname = mfncls.__name__
529
typeToMFn.add((nodeType, mfnname))
530
perfectMatches.append(mfnname)
531
removeThisMFn = mfncls
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
540
if removeThisMFn is not None:
541
succeededMFnNames.add(removeThisMFn.__name__)
542
candidateMFns.remove(removeThisMFn)
543
# END remove matched MFn
545
if not candidateMFns:
547
# END abort search if there is nothing left
548
# END for each failed type
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
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
564
for nodeType, mfnname in (('subdiv', 'MFnSubd'), ):
565
typeToMFn.add((nodeType, mfnname))
566
succeededMFnNames.add(mfnname)
567
# END for each manually added type
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]))
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`.
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
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 ):
596
if funcname == 'None': return None
599
return self.module_dict[funcname]
601
raise ValueError("'%s' does not exist in code generator's dictionary" % funcname )
606
def generateMFnClsMethodWrapper(self, source_method_name, target_method_name, mfn_fun_name, method_descriptor, flags=0):
608
:return: string containing the code for the wrapper method as configured by the
610
:param source_method_name: Original name of the method - this is the name under which
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")
621
class PythonMFnCodeGenerator(MFnCodeGeneratorBase):
622
"""Specialization to generate python code
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
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.
637
If set, the type we create the method for is derived from DagNode
640
If set, the method to be wrapped is considered static, no self is needed, nor
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.
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.
651
# IMPORTANT: If these change, update docs above, and test.maya.test_mdb and test.maya.performance.test_mdb !
656
kWithDocs = [ 1<<i for i in range(5) ]
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:
663
* method_descriptor.rvalfunc
665
as well as all flags except kIsStatic.
666
:raise ValueError: if flags are incompatible with each other
668
if flags & self.kIsMObject and flags & self.kIsDagNode:
669
raise ValueError("kIsMObject and kIsDagNode are mutually exclusive")
676
if method_descriptor.rvalfunc != 'None':
677
rvalfunname = method_descriptor.rvalfunc
679
sio.write("def %s(self, *args, **kwargs):\n" % target_method_name)
683
mfnset = "mfncls(self"
684
if flags & self.kIsDagNode:
685
mfnset += ".dagPath()"
686
elif not flags & self.kIsMObject:
687
mfnset += ".object()"
690
if flags & self.kDirectCall:
691
curline = "\tmfninstfunc = %s.%s\n" % (mfnset, mfn_fun_name)
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)")
700
curline = "mfn_fun(%s, *args, **kwargs)" % mfnset
702
curline = "rvalfunc(%s)" % curline
703
sio.write("\treturn %s" % curline)
704
# END handle direct call
706
return sio.getvalue()
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__
718
# handle MFnName_function
719
if mfnfuncname.startswith(mfncls.__name__):
720
mfnfuncname = mfnfuncname[len(mfncls.__name__)+1:]
723
if flags & self.kIsStatic:
724
# use the function directly
725
rvalfun = self._toRvalFunc(method_descriptor.rvalfunc)
729
fun = lambda *args, **kwargs: rvalfun(mfn_fun(*args, **kwargs))
730
fun.__name__ = target_method_name
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 !
738
# get the function into our local dict, globals are our locals
741
new_method = locals()[target_method_name]
742
# END handle static methods
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
753
#} END code generators
757
class CppHeaderParser(object):
758
"""Simplistic regex based parser which will extract information from the file
759
it was initialized with.
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)
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
774
def parseAndExtract(cls, header_filepath, parse_enums=True):
775
"""Parse the given header file and return the parsed information
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, ...), )"""
787
# read everything, but skip the license text when matching
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
794
read_method = header_filepath.text
795
# END handle newline sequence
797
header = read_method()
798
for enummatch in cls.reEnums.finditer(header, 2188):
799
ed = MEnumDescriptor(enummatch.group('name'))
801
# parse all occurrences of kSomething, including the initializer
802
members = enummatch.group('members')
804
for memmatch in cls.reEnumMembers.finditer(members):
805
ed.append(memmatch.group(1))
806
# END for each member to add
810
# END if enums should be parsed
816
return (tuple(enum_list), )
823
class MMethodDescriptor(object):
824
"""Contains meta-information about a given method according to data read from
826
__slots__ = ("flag", "rvalfunc", "newname")
828
def __init__( self, flag='', rvalfunc = None, newname="" ):
830
self.rvalfunc = rvalfunc
831
self.newname = newname
834
class MEnumDescriptor(list):
835
"""Is an ordered list of enumeration names without its values, together
836
with the name of the enumeration type"""
838
def __init__(self, name):
842
class MMemberMap( UserDict.UserDict ):
843
"""Simple accessor for MFnDatabase access
844
Direct access like db[funcname] returns an entry object with all values
847
The __globals__ entry in MFn db files allows to pass additional options.
848
Currently there are no supported flags"""
849
__slots__ = ("flags", "enums")
852
def __init__( self, filepath = None, parse_enums=False ):
853
"""intiialize self from a file if not None
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 )
859
self._filepath = filepath
861
self._initFromFile( filepath )
865
ge = self.get('__global__', None)
867
# currently we know none
871
# INITIALIZE PARSED DATA
874
self.enums, = CppHeaderParser.parseAndExtract(headerPath(filepath.namebase()))
875
# END if enumerations should be parsed
878
return "MMemberMap(%s)" % self._filepath
881
def _initFromFile( self, filepath ):
882
"""Initialize the database with values from the given file
884
:note: the file must have been written using the `writeToFile` method"""
886
fobj = open( filepath, 'r' )
888
pf = PipeSeparatedFile( fobj )
892
for tokens in pf.readColumnLine( ):
894
self[ key ] = MMethodDescriptor( flag=tokens[0], rvalfunc=tokens[2], newname=tokens[3] )
895
# END for each token in read column line
897
def writeToFile( self, filepath ):
898
"""Write our database contents to the given file"""
902
fobj = open( filepath, 'w' )
903
pf = PipeSeparatedFile( fobj )
904
pf.beginWriting( ( 4,40,20,40 ) )
906
for key in klist: # write entries
908
pf.writeTokens( ( e.flag, key,e.rvalfunc, e.newname ) )
913
def methodByName( self, funcname ):
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"""
920
return ( funcname, self[ funcname ] )
922
for mfnfuncname,entry in self.iteritems():
923
if entry.newname == funcname:
924
return ( mfnfuncname, entry )
927
raise KeyError( "Function named '%s' did not exist in db" % funcname )
929
def createEntry( self, funcname ):
930
""" Create an entry for the given function, or return the existing one
932
:return: Entry object for funcname"""
933
return self.setdefault( funcname, MMethodDescriptor() )
935
def mfnFunc( self, funcname ):
936
""":return: mfn functionname corresponding to the ( possibly renamed ) funcname """
937
return self.methodByName( funcname )[0]