def _addCustomType( targetmoduledict, parentclsname, newclsname, metaclass=MetaClassCreatorNodes, **kwargs ): """Add a custom type to the system such that a node with the given type will automatically be wrapped with the corresponding class name :param targetmoduledict: the module's dict to which standin classes are supposed to be added :param parentclsname: the name of the parent node type - if your new class has several parents, you have to add the new types beginning at the first exsiting parent as written in the maya/cache/nodeHierarchy.html file :param newclsname: the new name of your class - it must exist targetmodule :param metaclass: meta class object to be called to modify your type upon creation It will not be called if the class already exist in targetModule. Its recommended to derive it from the metaclass given as default value. :raise KeyError: if the parentclsname does not exist""" # add new type into the type hierarchy # parentclsname = uncapitalize( parentclsname ) newclsname = uncapitalize( newclsname ) nodeTypeTree.add_edge( parentclsname, newclsname ) # create wrapper ( in case newclsname does not yet exist in target module ) mrvmaya.initWrappers( targetmoduledict, [ newclsname ], metaclass, **kwargs )
def func_nameToTree( name ): # first check whether an uncapitalized name is known, if not, check # the capitalized version ( for special cases ), finalyl return # the uncapitalized version which is the default name = uncapitalize(name) if nodeTypeTree.has_node(name): return name capname = capitalize(name) if nodeTypeTree.has_node(capname): return capname return name
def _removeCustomType( targetmoduledict, customTypeName ): """Remove the given typename from the given target module's dictionary as well as from internal caches :note: does nothing if the type does not exist :param targetmoduledict: dict of your module to remove the type from :param customTypeName: name of the type to be removed, its expected to be capitalized""" try: del(targetmoduledict[customTypeName]) except KeyError: pass # END remove from dictionary customTypeName = uncapitalize(customTypeName) if nodeTypeTree.has_node(customTypeName): nodeTypeTree.remove_node(customTypeName)
def __new__( metacls, name, bases, clsdict ): """ Called to create the class with name """ # HANDLE MEL COMMAND ####################### cmdname = uncapitalize( name ) if hasattr( mcmds, cmdname ): melcmd = getattr( mcmds, cmdname ) clsmelcmd = staticmethod( melcmd ) clsdict['__melcmd__'] = clsmelcmd else: pass # don't bother, can be one of our own classes that will #raise ValueError( "Did not find command for " + cmdname ) # HANDLE PROPERTIES #################### # read the properties attribute to find names to automatically create # query and edit properties propertynames = clsdict.get( "_properties_", list() ) for pname in propertynames: attrname = "p_%s" % pname # allow user overrides if attrname not in clsdict: clsdict[ attrname ] = propertyQE( pname ) # END for each property # HANDLE EVENTS ################## # read the event description and create Event instances that will # register themselves on first use, allowing multiple listeners per maya event eventnames = clsdict.get( "_events_", list() ) event_kwargs = dict() if clsdict.get( "strong_event_handlers", False ): event_kwargs[ "weak" ] = False for ename in eventnames: attrname = "e_%s" % ename # allow user overrides if attrname not in clsdict: clsdict[ attrname ] = EventSenderUI._UIEvent( ename, **event_kwargs ) # END for each event name newcls = super( MetaClassCreatorUI, metacls ).__new__( _typetree, _uipackage, metacls, name, bases, clsdict ) return newcls
def unMFn(name): return uncapitalize(name[3:])
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]))
class Scene(util.Singleton, util.EventSender): """Singleton Class allowing access to the maya scene You can register all events available in MSceneMessage easily usnig the following syntax: >>> scene.beforeSoftwareRender = myFunctionObject """ kFileTypeMap = { "": "mayaAscii", # treat untitled scenes as ma ".ma": "mayaAscii", ".mb": "mayaBinary" } #{ Events sender_as_argument = False # create events from 'kEventName', creating a corresponding event named # 'eventName' for eidName, eid in ((n, v) for n, v in inspect.getmembers(api.MSceneMessage) if n.startswith('k')): locals()[util.uncapitalize(eidName[1:])] = _SceneEvent(eid) # END for each message id to create #} END events #{ Edit Methods @classmethod def open(cls, scenepath=None, force=False, **kwargs): """ Open the scene at the given scenepath :param scenepath: The path to the file to be opened If None, the currently loaded file will reopened :param force: if True, the new scene will be loaded although currently loaded contains unsaved changes :param kwargs: passed to *cmds.file* :return: a Path to the loaded scene""" if not scenepath: scenepath = cls.name() # NOTE: it will return the last loaded reference instead of the loaded file - lets fix this ! sourcePath = make_path(scenepath) kwargs.pop('open', kwargs.pop('o', None)) kwargs.pop('force', kwargs.pop('f', None)) lastReference = cmds.file(sourcePath.abspath(), open=1, force=force, **kwargs) return make_path(sourcePath) @classmethod def new(cls, force=False, **kwargs): """ Create a new scene :param force: if True, the new scene will be created even though there are unsaved modifications :param kwargs: passed to *cmds.file* :return: Path with name of the new file""" kwargs.pop('new', kwargs.pop('n', None)) kwargs.pop('force', kwargs.pop('f', None)) return make_path(cmds.file(new=True, force=force, **kwargs)) @classmethod def rename(cls, scenepath): """Rename the currently loaded file to be the file at scenepath :param scenepath: string or Path pointing describing the new location of the scene. :return: Path to scenepath :note: as opposed to the normal file -rename it will also adjust the extension :raise RuntimeError: if the scene's extension is not supported.""" scenepath = make_path(scenepath) try: cmds.file(rename=scenepath.expandvars()) cmds.file(type=cls.kFileTypeMap[scenepath.ext()]) except KeyError: raise RuntimeError("Unsupported filetype of: " + scenepath) # END exception handling return scenepath @classmethod def save(cls, scenepath=None, autodeleteUnknown=False, **kwargs): """Save the currently opened scene under scenepath in the respective format :param scenepath: if None, the currently opened scene will be saved, otherwise the name will be changed. Paths leading to the file will automatically be created. :param autodeleteUnknown: if true, unknown nodes will automatically be deleted before an attempt is made to change the maya file's type :param kwargs: passed to cmds.file :return: Path at which the scene has been saved.""" if scenepath is None or scenepath == "": scenepath = cls.name() scenepath = make_path(scenepath) curscene = cls.name() try: filetype = cls.kFileTypeMap[scenepath.ext()] curscenetype = cls.kFileTypeMap[curscene.ext()] except KeyError: raise RuntimeError("Unsupported filetype of: " + scenepath) # is it a save as ? if curscene != scenepath: cls.rename(scenepath) # assure path exists parentdir = scenepath.dirname() if not parentdir.exists(): parentdir.makedirs() # END assure parent path exists # delete unknown before changing types ( would result in an error otherwise ) if autodeleteUnknown and curscenetype != filetype: cls.deleteUnknownNodes() # END handle unkonwn nodes # safe the file kwargs.pop('save', kwargs.pop('s', None)) kwargs.pop('type', kwargs.pop('typ', None)) return make_path(cmds.file(save=True, type=filetype, **kwargs)) @classmethod def export(cls, outputFile, nodeListOrIterable=None, **kwargs): """Export the given nodes or everything into the file at path :param outputFile: Path object or path string to which the data should be written to. Parent directories will be created as needed :param nodeListOrIterable: if None, everything will be exported. Otherwise it may be an MSelectionList ( recommended ), or a list of Nodes, MObjects or MDagPaths :param kwargs: passed to cmds.file, see the mel docs for modifying flags :return: Path to which the data was exported""" outputFile = make_path(outputFile) if not outputFile.dirname().isdir(): outputFile.dirname().makedirs() # END create parent dirs prev_selection = None if nodeListOrIterable is None: kwargs['exportAll'] = True else: # export selected mode kwargs['exportSelected'] = True prev_selection = api.MSelectionList() api.MGlobal.getActiveSelectionList(prev_selection) import nt nt.select(nt.toSelectionList(nodeListOrIterable)) # END handle nodes typ = kwargs.pop( 'type', kwargs.pop('typ', cls.kFileTypeMap.get(outputFile.ext(), None))) if typ is None: raise RuntimeError("Invalid type in %s" % outputFile) # END handle type try: cmds.file(outputFile, type=typ, **kwargs) return outputFile finally: if prev_selection is not None: api.MGlobal.setActiveSelectionList(prev_selection) # END if we have a selection to restore # END handle selection #} END edit methods #{ Utilities @classmethod def deleteUnknownNodes(cls): """Deletes all unknown nodes in the scene :note: only do this if you are about to change the type of the scene during save or export - otherwise the operation would fail if there are still unknown nodes in the scene""" unknownNodes = cmds.ls(type="unknown") # using mel is the faatest here if unknownNodes: cmds.delete(unknownNodes) #} END utilities #{ Query Methods @classmethod def name(cls): return make_path(cmds.file(q=1, exn=1)) @classmethod def isModified(cls): return cmds.file(q=1, amf=True)
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]))