mrv.maya.mdb
Covered: 345 lines
Missed: 231 lines
Skipped 365 lines
Percent: 59 %
  2
"""
  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 
  6
	be present already
  7
:note: The implementation is considered internal and may change any time unless stated
  8
	otherwise.
  9
"""
 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
 20
import UserDict
 21
import inspect
 22
import re
 23
from cStringIO import StringIO
 24
import string
 25
import sys
 26
import os
 28
import logging
 29
log = logging.getLogger("mrv.maya.mdb")
 31
__all__ = ("createDagNodeHierarchy", "createTypeNameToMfnClsMap", "apiModules", 
 32
           "mfnDBPath", "cacheFilePath", "writeMfnDBCacheFiles", 
 33
           "extractMFnFunctions", "PythonMFnCodeGenerator", "MMemberMap", 
 34
           "MMethodDescriptor" )
 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
 44
	:return: `DAGTree`"""
 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
 53
		MFn class mapping"""
 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( ):
 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
 71
		if not found:
 72
			log.debug("Couldn't find mfn class named %s" % mfnTypeName)
 74
	fobj.close()
 76
	return typenameToClsMap
 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
 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 ):
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"
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()
116
	version = ""
117
	if use_version:
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, ...))"""
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
133
		if isinstance(f, staticmethod):
135
			staticmfnfuncs.append(getattr(mfncls, fn))
136
		else:
137
			mfnfuncs.append(f)
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 ... 
147
			return True
149
	return False
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.
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
164
			mfnfile = mfnDBPath( mfnname )
167
			mfnfuncs = list()
168
			fstatic, finst = extractMFnFunctions(mfncls)
169
			mfnfuncs.extend(fstatic)
170
			mfnfuncs.extend(finst)
172
			if not mfnfuncs:
173
				continue
175
			db = MMemberMap()
176
			if mfnfile.exists():
177
				db = MMemberMap( mfnfile )
180
			folder = mfnfile.dirname()
181
			if not folder.isdir(): folder.makedirs()
185
			for func in mfnfuncs:
188
				fname = func.__name__
189
				if fname.startswith(mfnname):
190
					fname = fname[len(mfnname)+1:]	# cut MFnName_(function)
193
				db.createEntry(fname)
197
			db.writeToFile( mfnfile )
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)
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'):
226
			continue
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
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
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)
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
269
		its creation"""
270
		modifier.doIt()
271
		mfndep.setObject(obj)
272
		inheritance = cmds.nodeType(mfndep.name(), i=1)
273
		modifier.undoIt()
274
		return inheritance
281
	for nodetype, obj, mod in _iterAllNodeTypes():
282
		inheritance = getInheritanceAndUndo(obj, mod)
284
		if not inheritance:
285
			log.error("Failed on type %s" % nodetype)
286
			continue
290
		for parent, child in zip(depnode_list + inheritance[:-1], inheritance):
291
			dagTree.add_edge(parent, child)
298
		apiTypeToNodeTypeMap[obj.apiTypeStr()] = nodetype
300
		api.MGlobal.getFunctionSetList(obj, sl)
301
		for mfnType in sl:
302
			mfnTypes.add(mfnType)
308
	dagTree.add_edge(depnode, 'unknown')
313
	dagTree.add_edge('transform', 'groundPlane')
317
	dagTree.add_edge('transform', 'manipContainer')
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])
346
	typeToMFn = set()		# list((typeName, MFnClsName), ...)
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)
364
		found = False
365
		for nt, mfnNameCandidate in ( (mfnNodePseudoType, "MFn%s" % capitalize(mfnApiType[1:])), 
366
									   (nodeType, "MFn%s" % capitalize(nodeType)) ):
368
			if mfnNameCandidate in abstractMFns:
369
				continue
371
			for modapi in modsapi:
372
				if hasattr(modapi, mfnNameCandidate):
373
					found = True
377
					typeToMFn.add((nodeType, mfnNameCandidate))
378
					break
382
			if found:
383
				break
388
		if not found and mfnApiType in apiTypeToNodeTypeMap:
389
			failedMFnTypes.append(mfnApiType)
398
	def unMFn(name):
399
		return uncapitalize(name[3:])
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) )
409
		dagTree.add_edge(root, mfnsuffix)
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
422
				if "MFn%s" % mfnsuffixcap not in ( p.__name__ for p in mfncls.mro() ):
423
					continue
426
				typeToMFn.add((unMFn(mfnname), mfnname))
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)
435
				break
444
	if failedMFnTypes:
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
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)
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
474
					candidateMFns.append(mfncls)
475
					break
476
				except AttributeError:
477
					continue
481
		succeededMFnNames = set()
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[:]:
491
			try:
492
				mfninst = mfncls()
493
				if mfntypes.nameByValue(mfninst.type()) in failedMFnTypes:
494
					continue
496
				candidateMFns.remove(mfncls)
497
				candidateMFnNames.remove(mfncls.__name__)
498
			except RuntimeError:
499
				continue
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)
512
			removeThisMFn = None
513
			for mfncls in candidateMFns:
514
				try:
515
					mfninst = mfncls(obj)
516
				except RuntimeError:
517
					continue
520
				apiTypeStr = mfntypes.nameByValue(mfninst.type())
522
				if apiTypeStr not in failedMFnTypes:
523
					removeThisMFn = mfncls
524
					break
527
				if apiTypeStr == failedApiTypeStr:
528
					mfnname = mfncls.__name__
529
					typeToMFn.add((nodeType, mfnname))
530
					perfectMatches.append(mfnname)
531
					removeThisMFn = mfncls
532
					break
537
				derivedMatches.append((apiTypeStr, mfncls.__name__))
540
			if removeThisMFn is not None:
541
				succeededMFnNames.add(removeThisMFn.__name__)
542
				candidateMFns.remove(removeThisMFn)
545
			if not candidateMFns:
546
				break
553
		for apiTypeStr, mfnname in filter(lambda t: t not in perfectMatches, derivedMatches):
554
			typeToMFn.add((apiTypeToNodeTypeMap[apiTypeStr], mfnname))
555
			succeededMFnNames.add(mfnname)
564
		for nodeType, mfnname in (('subdiv', 'MFnSubd'), ):
565
			typeToMFn.add((nodeType, mfnname))
566
			succeededMFnNames.add(mfnname)
570
		for mfnname in candidateMFnNames - succeededMFnNames:
571
			log.warn("Could not associate MFn: %s" % mfnname)
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 ):
595
			return funcname
596
		if funcname == 'None': return None
598
		try:
599
			return self.module_dict[funcname]
600
		except KeyError:
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):
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")
621
class PythonMFnCodeGenerator(MFnCodeGeneratorBase):
622
	"""Specialization to generate python code
624
	**Flags**:
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
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.
636
	 * kIsDagNode:
637
	 	If set, the type we create the method for is derived from DagNode
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.
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.
650
	"""
652
	kDirectCall, \
653
	kIsMObject, \
654
	kIsDagNode, \
655
	kIsStatic, \
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
667
		"""
668
		if flags & self.kIsMObject and flags & self.kIsDagNode:
669
			raise ValueError("kIsMObject and kIsDagNode are mutually exclusive")
673
		sio = StringIO()
675
		rvalfunname = ''
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()"
688
		mfnset += ")"
690
		if flags & self.kDirectCall:
691
			curline = "\tmfninstfunc = %s.%s\n" % (mfnset, mfn_fun_name)
692
			sio.write(curline)
694
			if rvalfunname:
695
				sio.write("\tmfninstfunc = lambda *args, **kwargs: rvalfun(mfninstfunc(*args, **kwargs))\n")
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)
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__
719
		if mfnfuncname.startswith(mfncls.__name__):
720
			mfnfuncname = mfnfuncname[len(mfncls.__name__)+1:]
722
		new_method = None
723
		if flags & self.kIsStatic:
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
733
		else:
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 !
739
			eval(code, locals())
741
			new_method = locals()[target_method_name]
744
		if flags & self.kWithDocs:
745
			if hasattr(new_method, 'func_doc'):
746
				new_method.func_doc = "%s.%s" % (mfncls.__name__, mfnfuncname)
749
		return new_method
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
771
								""", re.VERBOSE)
773
	@classmethod
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, ...), )"""
783
		enum_list = list()
788
		if parse_enums:
789
			read_method = header_filepath.bytes
793
			if os.name == 'nt':
794
				read_method = header_filepath.text
797
			header = read_method()
798
			for enummatch in cls.reEnums.finditer(header, 2188):
799
				ed = MEnumDescriptor(enummatch.group('name'))
802
				members = enummatch.group('members')
803
				assert members
804
				for memmatch in cls.reEnumMembers.finditer(members):
805
					ed.append(memmatch.group(1))
808
				enum_list.append(ed)
816
		return (tuple(enum_list), )
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")
828
	def __init__( self, flag='', rvalfunc = None, newname="" ):
829
		self.flag = flag
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"""
837
	__slots__ = "name"
838
	def __init__(self, name):
839
		self.name = 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
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'
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
860
		if filepath:
861
			self._initFromFile( filepath )
864
		self.flags = 0
865
		ge = self.get('__global__', None)
866
		if ge is not None:
868
			pass
872
		self.enums = tuple()
873
		if parse_enums:
874
			self.enums, = CppHeaderParser.parseAndExtract(headerPath(filepath.namebase()))
877
	def __str__( self ):
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"""
885
		self.clear()
886
		fobj = open( filepath, 'r' )
888
		pf = PipeSeparatedFile( fobj )
889
		pf.beginReading( )
892
		for tokens in pf.readColumnLine( ):
893
			key = tokens[ 1 ]
894
			self[ key ] = MMethodDescriptor( flag=tokens[0], rvalfunc=tokens[2], newname=tokens[3] )
897
	def writeToFile( self, filepath ):
898
		"""Write our database contents to the given file"""
899
		klist = self.keys()
900
		klist.sort()
902
		fobj = open( filepath, 'w' )
903
		pf = PipeSeparatedFile( fobj )
904
		pf.beginWriting( ( 4,40,20,40 ) )
906
		for key in klist:							# write entries
907
			e = self[ key ]
908
			pf.writeTokens( ( e.flag, key,e.rvalfunc, e.newname ) )
911
		fobj.close()
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 )
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]