Beispiel #1
0
	def test_header_parser(self):
		
		# test enumeration parsing
		# has multiple enums, and multiple variants:
		# enum Type 
		# { 
		#	kInvalid = 0,  ... }
		# and without intitialization
		viewheader= mdb.headerPath('M3dView')
		enums, = mdb.CppHeaderParser.parseAndExtract(viewheader)
		assert len(enums) > 7		# could change with maya versions, 7 should be minimum
		
		for ed in enums:
			assert isinstance(ed, mdb.MEnumDescriptor)
			assert len(ed)
			assert isinstance(ed.name, basestring)
			
			# convert to MEnumeration
			enum = MEnumeration.create(ed, apiui.M3dView)
			assert isinstance(enum, MEnumeration)
			assert enum.name == ed.name
			assert len(enum) == len(ed)
			for em in ed:
				ev = getattr(enum, em)
				assert isinstance(ev, int)
				assert enum.nameByValue(ev) == em
			# END check each instance
			self.failUnlessRaises(ValueError, enum.nameByValue, -1)
Beispiel #2
0
	def _wrapStaticMembers( cls, newcls, mfncls ):
		"""Find static mfnmethods - if these are available, initialize the 
		mfn database for the given function set ( ``mfncls`` ) and create properly 
		wrapped methods. 
		Additionally check for enumerations, and generate the respective enumeration
		instances
		:note: As all types are initialized on startup, the staticmethods check 
			will load in quite a few function sets databases as many will have static 
			methods. There is no real way around it, but one could introduce 'packs'
			to bundle these together and load them only once. Probably the performance
			hit is not noticeable, but lets just say that I am aware of it
		:note: Currently method aliases are not implemented for statics !"""
		fstatic, finst = mdb.extractMFnFunctions(mfncls)
		hasEnum = mdb.hasMEnumeration(mfncls)
		if not fstatic and not hasEnum:
			return
			
		mfndb = cls._fetchMfnDB(newcls, mfncls, parse_enums=hasEnum)
		if fstatic:
			mfnname = mfncls.__name__
			for fs in fstatic:
				fn = fs.__name__
				if fn.startswith(mfnname):
					fn = fn[len(mfnname)+1:]	# cut MFnName_methodName
				# END handle name prefix
				
				static_function = cls._wrapMfnFunc(newcls, mfncls, fn, mfndb)
				type.__setattr__(newcls, fn, staticmethod(static_function))
			# END for each static method
		# END handle static functions
		
		
		if hasEnum:
			for ed in mfndb.enums:
				type.__setattr__(newcls, ed.name, MEnumeration.create(ed, mfncls))
Beispiel #3
0
 def _wrapStaticMembers( cls, newcls, mfncls ):
     """Find static mfnmethods - if these are available, initialize the 
     mfn database for the given function set ( ``mfncls`` ) and create properly 
     wrapped methods. 
     Additionally check for enumerations, and generate the respective enumeration
     instances
     :note: As all types are initialized on startup, the staticmethods check 
         will load in quite a few function sets databases as many will have static 
         methods. There is no real way around it, but one could introduce 'packs'
         to bundle these together and load them only once. Probably the performance
         hit is not noticeable, but lets just say that I am aware of it
     :note: Currently method aliases are not implemented for statics !"""
     fstatic, finst = mdb.extractMFnFunctions(mfncls)
     hasEnum = mdb.hasMEnumeration(mfncls)
     if not fstatic and not hasEnum:
         return
         
     mfndb = cls._fetchMfnDB(newcls, mfncls, parse_enums=hasEnum)
     if fstatic:
         mfnname = mfncls.__name__
         for fs in fstatic:
             fn = fs.__name__
             if fn.startswith(mfnname):
                 fn = fn[len(mfnname)+1:]    # cut MFnName_methodName
             # END handle name prefix
             
             static_function = cls._wrapMfnFunc(newcls, mfncls, fn, mfndb)
             type.__setattr__(newcls, fn, staticmethod(static_function))
         # END for each static method
     # END handle static functions
     
     
     if hasEnum:
         for ed in mfndb.enums:
             type.__setattr__(newcls, ed.name, MEnumeration.create(ed, mfncls))
Beispiel #4
0
def generateNodeHierarchy( ):
	"""Generate the node-hierarchy for the current version based on all node types 
	which can be created in maya.
	
	:return: tuple(DAGTree, typeToMFnClsNameList)
	
		* DAGTree representing the type hierarchy
		* list represents typeName to MFnClassName associations
		 
	:note: should only be run as part of the upgrade process to prepare MRV for  a
		new maya release. Otherwise the nodetype tree will be read from a cache"""
	from mrv.util import DAGTree
	from mrv.util import uncapitalize, capitalize
	from mrv.maya.util import MEnumeration
	
	# init DagTree
	root = "_root_" 
	depnode = 'dependNode'
	depnode_list = [depnode]
	noderoottype = 'node'
	dagTree = DAGTree()
	dagTree.add_edge(root, noderoottype)
	dagTree.add_edge(noderoottype, depnode)
	
	apiTypeToNodeTypeMap = dict()		# apiTypeStr -> nodeTypeName
	mfnTypes = set()					# apiTypeStr of mfns used by NodeTypes
	sl = list()							# string list
	
	
	mfndep = api.MFnDependencyNode()
	def getInheritanceAndUndo(obj, modifier):
		"""Takes a prepared modifier ( doIt not yet called ) and the previously created object, 
		returning the inheritance of the obj which was retrieved before undoing
		its creation"""
		modifier.doIt()
		mfndep.setObject(obj)
		inheritance = cmds.nodeType(mfndep.name(), i=1)
		modifier.undoIt()
		return inheritance
	# END utility
	
	
	# CREATE ALL NODE TYPES
	#######################
	# query the inheritance afterwards
	for nodetype, obj, mod in _iterAllNodeTypes():
		inheritance = getInheritanceAndUndo(obj, mod)
		
		if not inheritance:
			log.error("Failed on type %s" % nodetype)
			continue
		# END handle unusual case
		
		# filter bases
		for parent, child in zip(depnode_list + inheritance[:-1], inheritance):
			dagTree.add_edge(parent, child)
		# END for each edge to add
		
		# retrieve all compatible MFnTypes - these refer to apiTypes which are 
		# also used by Nodes. Currently we have only the type of the node, fortunately, 
		# it will match very well with the MFnType, by just prepending MFn.
		# As some MFn are in other modules, we will search em all ... later
		apiTypeToNodeTypeMap[obj.apiTypeStr()] = nodetype
		
		api.MGlobal.getFunctionSetList(obj, sl)
		for mfnType in sl:
			mfnTypes.add(mfnType)
	# END for each node type
	
	# INSERT SPECIAL TYPES
	######################
	# used by the type system if it cannot classify a node at all
	dagTree.add_edge(depnode, 'unknown')
	
	# can be iterated using the DagIterator, and it should at least be a dag node, 
	# not unknown. The groundPlane is actually a special object that users shouldn't
	# touch directly
	dagTree.add_edge('transform', 'groundPlane')
	
	# although we don't handle manips directly, we must still support them if it
	# is a plugin manipulator
	dagTree.add_edge('transform', 'manipContainer')
	
	
	
	# INSERT PLUGIN TYPES
	######################
	for edge in (	(depnode, 'DependNode'),
					('shape', 'Shape'), 
					('locator', 'LocatorNode'), 
					('spring', 'SpringNode'), 
					('transform', 'TransformNode'), 
					('manipContainer', 'ManipContainer'), 
					('dynBase', 'EmitterNode'), 
					('field', 'FieldNode'), 
					('objectSet', 'ObjectSet'), 
					('geometryFilter', 'DeformerNode'), 
					(depnode, 'HwShaderNode'), 
					('ikSolver', 'IkSolver'), 
					(depnode, 'ImagePlaneNode'), 
					(depnode, 'ParticleAttributeMapperNode')	):
		dagTree.add_edge(edge[0], 'unknownPlugin'+edge[1])
	# END for each plugin edge to add
	
	
	
	# BULD TYPE-TO-MFN MAP
	######################
	# Prepare data to be put into a type separated file, it associates 
	# a nodeType or nodeApiType with the respecitve MFnClass name
	typeToMFn = set()		# list((typeName, MFnClsName), ...)
	
	# add default associations - some are not picked up due to name mismatches
	typeToMFn.add((noderoottype, 'MFn'))
	typeToMFn.add((depnode, 'MFnDependencyNode'))
	typeToMFn.add(('dagContainer', 'MFnContainerNode'))
	
	abstractMFns = ('MFnBase', )		# MFns which cannot be instantiated ans should be ignored
	failedMFnTypes = list()			# list of types we could not yet associate
	
	modsapi = apiModules()
	for mfnApiType in mfnTypes:
		mfnNodePseudoType = uncapitalize(mfnApiType[1:])	# # kSomething -> something
		nodeType = apiTypeToNodeTypeMap.get(mfnApiType, mfnNodePseudoType)
		
		# MFnSets follow their kMFnType names, but when we try to associate it with 
		# the actual nodeType . Sometimes, they follow the actual nodeType, so we 
		# have to use this one as well
		found = False
		for nt, mfnNameCandidate in ( (mfnNodePseudoType, "MFn%s" % capitalize(mfnApiType[1:])), 
									   (nodeType, "MFn%s" % capitalize(nodeType)) ):
			# ignore abstract ones
			if mfnNameCandidate in abstractMFns:
				continue
			
			for modapi in modsapi:
				if hasattr(modapi, mfnNameCandidate):
					found = True
					
					# prefer a real nodetype if we have one - it will default
					# to the pseudotype
					typeToMFn.add((nodeType, mfnNameCandidate))
					break
				# END module with given name exists
			# END for each api module
			
			if found:
				break
		# END for each nodeType/mfnNamecandidate
		
		# still not found ? Keep it, but only if there is a nodetype 
		# associated with it
		if not found and mfnApiType in apiTypeToNodeTypeMap:
			failedMFnTypes.append(mfnApiType)
		# END keep a record
	# END for each mfnType
	
	
	
	# DATA, COMPONENTS, ATTRIBUTES
	###############################
	# get inheritance of Data, Component and Attribute types
	def unMFn(name):
		return uncapitalize(name[3:])
	# END remove MFn in front of MFnSomething strings
	
	for mfnsuffix in ("data", "component", "attribute"):
		mfnsuffixcap = capitalize(mfnsuffix)
		mfnnames = list()
		for modapi in modsapi:
			mfnnames.extend( n for n in dir(modapi) if n.endswith(mfnsuffixcap) )
		# END for each api module to get information from
		
		dagTree.add_edge(root, mfnsuffix)
		
		mfnsuffix_root = [ mfnsuffix ]
		for mfnname in mfnnames:
			for modapi in modsapi:
				try:
					mfncls = getattr(modapi, mfnname)
				except AttributeError:
					continue
				# END handle multi-modules
				
				# skip classes which are just named like the super type, but 
				# don't actually inherit it
				if "MFn%s" % mfnsuffixcap not in ( p.__name__ for p in mfncls.mro() ):
					continue
				
				# add type->MFn association
				typeToMFn.add((unMFn(mfnname), mfnname))
				
				# cut object and MFnBase
				# from the names, cut the MFn and uncaptialize it: MFnData -> data
				pclsnames = [ unMFn(p.__name__) for p in list(reversed(mfncls.mro()))[2:] ]
				for parent, child in zip(pclsnames[:-1], pclsnames[1:]):
					dagTree.add_edge(parent, child)
				# END for each mfn child to add
				
				break
			# END for each api module
		# END for each name
	# END for each mfnsuffix
	
	
	# HANDLE FAILED MFN-ASSOCITAIONS
	################################
	# lets take some very special care !
	if failedMFnTypes:
		# Here we handle cases which don't follow any naming conventions really
		# Hence we have to instantiate an object of the failed type, then 
		# we instantiate all the remaining functions sets to see which one fits.
		# If the required one has the requested type, we have a match. 
		# If we have no type match, its still a valid MFn - If we haven't seen it 
		# yet, its probably a base MFn whose kType string allows deduction of the 
		# actual abtract node type which we will use instead.
		associatedMFns = ( t[1] for t in typeToMFn )
		allMFnSetNames = list()
		for modapi in modsapi:
			allMFnSetNames.extend( n for n in dir(modapi) if n.startswith('MFn') and 
															not n.endswith('Ptr') and 
															not '_' in n and 		# skip 'special' ones
															not 'Manip' in n )		# skip everything about Manipulators
		# END get all MFn names
		
		# find MFnClasses for each candidate name
		candidateMFnNames = (set(allMFnSetNames) - set(associatedMFns)) - set(abstractMFns)
		candidateMFns = list()
		for cn in list(candidateMFnNames):
			for modapi in modsapi:
				try:
					mfncls = getattr(modapi, cn)
					# ignore them if they don't derive from MFnBase
					if not hasattr(mfncls, "type"):
						log.debug("Skipped MFn %s as it didn't derive from MFnBase" % mfncls)
						candidateMFnNames.discard(cn)
						continue
					# END skip mfn
					candidateMFns.append(mfncls)
					break
				except AttributeError:
					continue
			# END for each api module
		# END for each candidate name
		
		succeededMFnNames = set()
		
		
		# PRUNE REMAINING MFNs
		# prune out all MFnClasses that can be constructed without an actual object
		enumMembers = MEnumDescriptor('Type')
		enumMembers.extend( m for m in dir(api.MFn) if m.startswith('k') )
		mfntypes = MEnumeration.create(enumMembers, api.MFn)
		
		for mfncls in candidateMFns[:]:
			try:
				mfninst = mfncls()
				if mfntypes.nameByValue(mfninst.type()) in failedMFnTypes:
					continue
				# END keep actually missing MFns
				candidateMFns.remove(mfncls)
				candidateMFnNames.remove(mfncls.__name__)
			except RuntimeError:
				continue
		# END for each possible MFn to prune
		
		# at this point, we have about 500 api types with no MFn, but only 
		# about 10 function sets, 
		# Now ... we  brute-force test our way through all of these to find 
		# matching ones ... argh
		derivedMatches = list()		# keeps tuple(kTypeStr, mfnname) of matches of derived types
		perfectMatches = list()		# keeps mfnnames of perfect matches
		for failedApiTypeStr in failedMFnTypes:
			nodeType = apiTypeToNodeTypeMap[failedApiTypeStr]
			obj, mod = _createTmpNode(nodeType)
			
			removeThisMFn = None
			for mfncls in candidateMFns:
				try:
					mfninst = mfncls(obj)
				except RuntimeError:
					continue
				# END handle incompatability
				
				apiTypeStr = mfntypes.nameByValue(mfninst.type())
				
				if apiTypeStr not in failedMFnTypes:
					removeThisMFn = mfncls
					break
				# END remove MFns that no one wants
				
				if apiTypeStr == failedApiTypeStr:
					mfnname = mfncls.__name__
					typeToMFn.add((nodeType, mfnname))
					perfectMatches.append(mfnname)
					removeThisMFn = mfncls
					break
				# END perfect match 
				
				# its matching, but its not perfectly suited for our node type
				# We keep it, and will map it later if we don't find a better match
				derivedMatches.append((apiTypeStr, mfncls.__name__))
			# END for each mfncls
			
			if removeThisMFn is not None:
				succeededMFnNames.add(removeThisMFn.__name__)
				candidateMFns.remove(removeThisMFn)
			# END remove matched MFn
			
			if not candidateMFns:
				break
			# END abort search if there is nothing left 
		# END for each failed type
		
		# HANDLE DERIVED MFns
		# prune out all derived mfs which have found a perfect match in the meanwhile
		# the rest will be added to the list
		for apiTypeStr, mfnname in filter(lambda t: t not in perfectMatches, derivedMatches):
			typeToMFn.add((apiTypeToNodeTypeMap[apiTypeStr], mfnname))
			succeededMFnNames.add(mfnname)
		# END for each apiTypeStr left ot add
		
		
		# LAST MANUAL WORK
		##################
		# SubDees, if created empty, cannot be attached to their function set
		# Hence we don't see the match, but ... we know its there, so we add it
		# ourselves
		for nodeType, mfnname in (('subdiv', 'MFnSubd'), ):
			typeToMFn.add((nodeType, mfnname))
			succeededMFnNames.add(mfnname)
		# END for each manually added type
		
		
		for mfnname in candidateMFnNames - succeededMFnNames:
			log.warn("Could not associate MFn: %s" % mfnname)
		# END provide some info
	# END special treatment
	return (dagTree, sorted(typeToMFn, key=lambda t: t[0]))
Beispiel #5
0
def generateNodeHierarchy():
    """Generate the node-hierarchy for the current version based on all node types 
	which can be created in maya.
	
	:return: tuple(DAGTree, typeToMFnClsNameList)
	
		* DAGTree representing the type hierarchy
		* list represents typeName to MFnClassName associations
		 
	:note: should only be run as part of the upgrade process to prepare MRV for  a
		new maya release. Otherwise the nodetype tree will be read from a cache"""
    from mrv.util import DAGTree
    from mrv.util import uncapitalize, capitalize
    from mrv.maya.util import MEnumeration

    # init DagTree
    root = "_root_"
    depnode = 'dependNode'
    depnode_list = [depnode]
    noderoottype = 'node'
    dagTree = DAGTree()
    dagTree.add_edge(root, noderoottype)
    dagTree.add_edge(noderoottype, depnode)

    apiTypeToNodeTypeMap = dict()  # apiTypeStr -> nodeTypeName
    mfnTypes = set()  # apiTypeStr of mfns used by NodeTypes
    sl = list()  # string list

    mfndep = api.MFnDependencyNode()

    def getInheritanceAndUndo(obj, modifier):
        """Takes a prepared modifier ( doIt not yet called ) and the previously created object, 
		returning the inheritance of the obj which was retrieved before undoing
		its creation"""
        modifier.doIt()
        mfndep.setObject(obj)
        inheritance = cmds.nodeType(mfndep.name(), i=1)
        modifier.undoIt()
        return inheritance

    # END utility

    # CREATE ALL NODE TYPES
    #######################
    # query the inheritance afterwards
    for nodetype, obj, mod in _iterAllNodeTypes():
        inheritance = getInheritanceAndUndo(obj, mod)

        if not inheritance:
            log.error("Failed on type %s" % nodetype)
            continue
        # END handle unusual case

        # filter bases
        for parent, child in zip(depnode_list + inheritance[:-1], inheritance):
            dagTree.add_edge(parent, child)
        # END for each edge to add

        # retrieve all compatible MFnTypes - these refer to apiTypes which are
        # also used by Nodes. Currently we have only the type of the node, fortunately,
        # it will match very well with the MFnType, by just prepending MFn.
        # As some MFn are in other modules, we will search em all ... later
        apiTypeToNodeTypeMap[obj.apiTypeStr()] = nodetype

        api.MGlobal.getFunctionSetList(obj, sl)
        for mfnType in sl:
            mfnTypes.add(mfnType)
    # END for each node type

    # INSERT SPECIAL TYPES
    ######################
    # used by the type system if it cannot classify a node at all
    dagTree.add_edge(depnode, 'unknown')

    # can be iterated using the DagIterator, and it should at least be a dag node,
    # not unknown. The groundPlane is actually a special object that users shouldn't
    # touch directly
    dagTree.add_edge('transform', 'groundPlane')

    # although we don't handle manips directly, we must still support them if it
    # is a plugin manipulator
    dagTree.add_edge('transform', 'manipContainer')

    # INSERT PLUGIN TYPES
    ######################
    for edge in ((depnode, 'DependNode'), ('shape', 'Shape'), ('locator',
                                                               'LocatorNode'),
                 ('spring', 'SpringNode'), ('transform', 'TransformNode'),
                 ('manipContainer', 'ManipContainer'), ('dynBase',
                                                        'EmitterNode'),
                 ('field', 'FieldNode'), ('objectSet', 'ObjectSet'),
                 ('geometryFilter', 'DeformerNode'), (depnode, 'HwShaderNode'),
                 ('ikSolver', 'IkSolver'), (depnode, 'ImagePlaneNode'),
                 (depnode, 'ParticleAttributeMapperNode')):
        dagTree.add_edge(edge[0], 'unknownPlugin' + edge[1])
    # END for each plugin edge to add

    # BULD TYPE-TO-MFN MAP
    ######################
    # Prepare data to be put into a type separated file, it associates
    # a nodeType or nodeApiType with the respecitve MFnClass name
    typeToMFn = set()  # list((typeName, MFnClsName), ...)

    # add default associations - some are not picked up due to name mismatches
    typeToMFn.add((noderoottype, 'MFn'))
    typeToMFn.add((depnode, 'MFnDependencyNode'))
    typeToMFn.add(('dagContainer', 'MFnContainerNode'))

    abstractMFns = (
        'MFnBase', )  # MFns which cannot be instantiated ans should be ignored
    failedMFnTypes = list()  # list of types we could not yet associate

    modsapi = apiModules()
    for mfnApiType in mfnTypes:
        mfnNodePseudoType = uncapitalize(
            mfnApiType[1:])  # # kSomething -> something
        nodeType = apiTypeToNodeTypeMap.get(mfnApiType, mfnNodePseudoType)

        # MFnSets follow their kMFnType names, but when we try to associate it with
        # the actual nodeType . Sometimes, they follow the actual nodeType, so we
        # have to use this one as well
        found = False
        for nt, mfnNameCandidate in ((mfnNodePseudoType,
                                      "MFn%s" % capitalize(mfnApiType[1:])),
                                     (nodeType,
                                      "MFn%s" % capitalize(nodeType))):
            # ignore abstract ones
            if mfnNameCandidate in abstractMFns:
                continue

            for modapi in modsapi:
                if hasattr(modapi, mfnNameCandidate):
                    found = True

                    # prefer a real nodetype if we have one - it will default
                    # to the pseudotype
                    typeToMFn.add((nodeType, mfnNameCandidate))
                    break
                # END module with given name exists
            # END for each api module

            if found:
                break
        # END for each nodeType/mfnNamecandidate

        # still not found ? Keep it, but only if there is a nodetype
        # associated with it
        if not found and mfnApiType in apiTypeToNodeTypeMap:
            failedMFnTypes.append(mfnApiType)
        # END keep a record
    # END for each mfnType

    # DATA, COMPONENTS, ATTRIBUTES
    ###############################
    # get inheritance of Data, Component and Attribute types
    def unMFn(name):
        return uncapitalize(name[3:])

    # END remove MFn in front of MFnSomething strings

    for mfnsuffix in ("data", "component", "attribute"):
        mfnsuffixcap = capitalize(mfnsuffix)
        mfnnames = list()
        for modapi in modsapi:
            mfnnames.extend(n for n in dir(modapi) if n.endswith(mfnsuffixcap))
        # END for each api module to get information from

        dagTree.add_edge(root, mfnsuffix)

        mfnsuffix_root = [mfnsuffix]
        for mfnname in mfnnames:
            for modapi in modsapi:
                try:
                    mfncls = getattr(modapi, mfnname)
                except AttributeError:
                    continue
                # END handle multi-modules

                # skip classes which are just named like the super type, but
                # don't actually inherit it
                if "MFn%s" % mfnsuffixcap not in (p.__name__
                                                  for p in mfncls.mro()):
                    continue

                # add type->MFn association
                typeToMFn.add((unMFn(mfnname), mfnname))

                # cut object and MFnBase
                # from the names, cut the MFn and uncaptialize it: MFnData -> data
                pclsnames = [
                    unMFn(p.__name__) for p in list(reversed(mfncls.mro()))[2:]
                ]
                for parent, child in zip(pclsnames[:-1], pclsnames[1:]):
                    dagTree.add_edge(parent, child)
                # END for each mfn child to add

                break
            # END for each api module
        # END for each name
    # END for each mfnsuffix

    # HANDLE FAILED MFN-ASSOCITAIONS
    ################################
    # lets take some very special care !
    if failedMFnTypes:
        # Here we handle cases which don't follow any naming conventions really
        # Hence we have to instantiate an object of the failed type, then
        # we instantiate all the remaining functions sets to see which one fits.
        # If the required one has the requested type, we have a match.
        # If we have no type match, its still a valid MFn - If we haven't seen it
        # yet, its probably a base MFn whose kType string allows deduction of the
        # actual abtract node type which we will use instead.
        associatedMFns = (t[1] for t in typeToMFn)
        allMFnSetNames = list()
        for modapi in modsapi:
            allMFnSetNames.extend(
                n for n in dir(modapi)
                if n.startswith('MFn') and not n.endswith('Ptr')
                and not '_' in n and  # skip 'special' ones
                not 'Manip' in n)  # skip everything about Manipulators
        # END get all MFn names

        # find MFnClasses for each candidate name
        candidateMFnNames = (set(allMFnSetNames) -
                             set(associatedMFns)) - set(abstractMFns)
        candidateMFns = list()
        for cn in list(candidateMFnNames):
            for modapi in modsapi:
                try:
                    mfncls = getattr(modapi, cn)
                    # ignore them if they don't derive from MFnBase
                    if not hasattr(mfncls, "type"):
                        log.debug(
                            "Skipped MFn %s as it didn't derive from MFnBase" %
                            mfncls)
                        candidateMFnNames.discard(cn)
                        continue
                    # END skip mfn
                    candidateMFns.append(mfncls)
                    break
                except AttributeError:
                    continue
            # END for each api module
        # END for each candidate name

        succeededMFnNames = set()

        # PRUNE REMAINING MFNs
        # prune out all MFnClasses that can be constructed without an actual object
        enumMembers = MEnumDescriptor('Type')
        enumMembers.extend(m for m in dir(api.MFn) if m.startswith('k'))
        mfntypes = MEnumeration.create(enumMembers, api.MFn)

        for mfncls in candidateMFns[:]:
            try:
                mfninst = mfncls()
                if mfntypes.nameByValue(mfninst.type()) in failedMFnTypes:
                    continue
                # END keep actually missing MFns
                candidateMFns.remove(mfncls)
                candidateMFnNames.remove(mfncls.__name__)
            except RuntimeError:
                continue
        # END for each possible MFn to prune

        # at this point, we have about 500 api types with no MFn, but only
        # about 10 function sets,
        # Now ... we  brute-force test our way through all of these to find
        # matching ones ... argh
        derivedMatches = list(
        )  # keeps tuple(kTypeStr, mfnname) of matches of derived types
        perfectMatches = list()  # keeps mfnnames of perfect matches
        for failedApiTypeStr in failedMFnTypes:
            nodeType = apiTypeToNodeTypeMap[failedApiTypeStr]
            obj, mod = _createTmpNode(nodeType)

            removeThisMFn = None
            for mfncls in candidateMFns:
                try:
                    mfninst = mfncls(obj)
                except RuntimeError:
                    continue
                # END handle incompatability

                apiTypeStr = mfntypes.nameByValue(mfninst.type())

                if apiTypeStr not in failedMFnTypes:
                    removeThisMFn = mfncls
                    break
                # END remove MFns that no one wants

                if apiTypeStr == failedApiTypeStr:
                    mfnname = mfncls.__name__
                    typeToMFn.add((nodeType, mfnname))
                    perfectMatches.append(mfnname)
                    removeThisMFn = mfncls
                    break
                # END perfect match

                # its matching, but its not perfectly suited for our node type
                # We keep it, and will map it later if we don't find a better match
                derivedMatches.append((apiTypeStr, mfncls.__name__))
            # END for each mfncls

            if removeThisMFn is not None:
                succeededMFnNames.add(removeThisMFn.__name__)
                candidateMFns.remove(removeThisMFn)
            # END remove matched MFn

            if not candidateMFns:
                break
            # END abort search if there is nothing left
        # END for each failed type

        # HANDLE DERIVED MFns
        # prune out all derived mfs which have found a perfect match in the meanwhile
        # the rest will be added to the list
        for apiTypeStr, mfnname in filter(lambda t: t not in perfectMatches,
                                          derivedMatches):
            typeToMFn.add((apiTypeToNodeTypeMap[apiTypeStr], mfnname))
            succeededMFnNames.add(mfnname)
        # END for each apiTypeStr left ot add

        # LAST MANUAL WORK
        ##################
        # SubDees, if created empty, cannot be attached to their function set
        # Hence we don't see the match, but ... we know its there, so we add it
        # ourselves
        for nodeType, mfnname in (('subdiv', 'MFnSubd'), ):
            typeToMFn.add((nodeType, mfnname))
            succeededMFnNames.add(mfnname)
        # END for each manually added type

        for mfnname in candidateMFnNames - succeededMFnNames:
            log.warn("Could not associate MFn: %s" % mfnname)
        # END provide some info
    # END special treatment
    return (dagTree, sorted(typeToMFn, key=lambda t: t[0]))