def _disabled_test_createClasses( self ): win = ui.Window( title="Collector" ) col = ui.ColumnLayout( adj=1 ) for uitype in ui.typ._typetree.nodes_iter(): capuitype = capitalize( uitype ) if capuitype in [ "BaseUI", "NamedUI" ]: continue try: inst = ui.__dict__[ capuitype ]( ) except (RuntimeError, TypeError): # TypeError in case of overridden special elements # RuntimeError in case the UI element just can't be created # standalone continue # END exception handing assert isinstance( inst, ui.BaseUI ) if not isinstance( inst, ui.BaseUI ): assert isinstance( inst, ui.NamedUI ) assert hasattr( inst, '__melcmd__' ) # layouts should not stay open if isinstance( inst, ui.Layout ): inst.setParentActive() # END for each uitype col.setParentActive() assert len( win.children() ) win.delete()
def wrapUI( uinameOrList, ignore_errors = False ): """ :return: a new instance ( or list of instances ) of a suitable python UI wrapper class for the UI with the given uiname(s) :param uinameOrList: if single name, a single instance will be returned, if a list of names is given, a list of respective instances. None will be interpreted as empty list :param ignore_errors: ignore ui items that cannot be wrapped as the type is unknown. :raise RuntimeError: if uiname does not exist or is not wrapped in python """ uinames = uinameOrList islisttype = isinstance( uinameOrList, ( tuple, list, set ) ) if not islisttype: if uinameOrList is None: islisttype = True uinames = list() else: uinames = [ uinameOrList ] # END input list handling out = list() for uiname in uinames: uitype = getUIType( uiname ) clsname = capitalize( uitype ) try: out.append( _uidict[clsname]( name=uiname, wrap_only = 1 ) ) except KeyError: if not ignore_errors: raise RuntimeError( "ui module has no class named %s, failed to wrap %s" % ( clsname, uiname ) ) # END for each uiname if islisttype: return out return out[0]
def plugin_unloaded(self, pluginName): """Remove all node types registered by pluginName unless they have been registered by a third party. We cannot assume that they listen to these events, hence we just keep the record as it will not harm. In any way, we will remove any record of the plugin from our db""" self.log.debug("plugin '%s' unloaded" % pluginName) # clear our record installed_type_names = self[pluginName] del(self[pluginName]) # deregister types if possible nt = globals() for tn in installed_type_names: tnc = capitalize(tn) try: node_type = nt[tnc] except KeyError: # wasnt registered anymore ? self.log.warn("Type %s of unloaded plugin %s was already de-registered in mrv type system - skipped" % (tnc, pluginName)) continue # END handle exception # remove the type only if it was one of our unknown default types parentclsname = node_type.__base__.__name__ if not parentclsname.startswith('Unknown'): continue # END skip custom nodes typ._removeCustomType(nt, tnc)
def _disabled_test_createClasses(self): win = ui.Window(title="Collector") col = ui.ColumnLayout(adj=1) for uitype in ui.typ._typetree.nodes_iter(): capuitype = capitalize(uitype) if capuitype in ["BaseUI", "NamedUI"]: continue try: inst = ui.__dict__[capuitype]() except (RuntimeError, TypeError): # TypeError in case of overridden special elements # RuntimeError in case the UI element just can't be created # standalone continue # END exception handing assert isinstance(inst, ui.BaseUI) if not isinstance(inst, ui.BaseUI): assert isinstance(inst, ui.NamedUI) assert hasattr(inst, '__melcmd__') # layouts should not stay open if isinstance(inst, ui.Layout): inst.setParentActive() # END for each uitype col.setParentActive() assert len(win.children()) win.delete()
def wrapUI(uinameOrList, ignore_errors=False): """ :return: a new instance ( or list of instances ) of a suitable python UI wrapper class for the UI with the given uiname(s) :param uinameOrList: if single name, a single instance will be returned, if a list of names is given, a list of respective instances. None will be interpreted as empty list :param ignore_errors: ignore ui items that cannot be wrapped as the type is unknown. :raise RuntimeError: if uiname does not exist or is not wrapped in python """ uinames = uinameOrList islisttype = isinstance(uinameOrList, (tuple, list, set)) if not islisttype: if uinameOrList is None: islisttype = True uinames = list() else: uinames = [uinameOrList] # END input list handling out = list() for uiname in uinames: uitype = getUIType(uiname) clsname = capitalize(uitype) try: out.append(_uidict[clsname](name=uiname, wrap_only=1)) except KeyError: if not ignore_errors: raise RuntimeError( "ui module has no class named %s, failed to wrap %s" % (clsname, uiname)) # END for each uiname if islisttype: return out return out[0]
def initWrappers(mdict, types, metacreatorcls, force_creation=False): """ Create standin classes that will create the actual class once creation is requested. :param mdict: module dictionary object from which the latter classes will be imported from, can be obtained using ``globals()`` in the module :param types: iterable containing the names of classnames ( they will be capitalized as classes must begin with a capital letter )""" from mrv.maya.util import StandinClass # create dummy class that will generate the class once it is first being instatiated standin_instances = list() for uitype in types: clsname = capitalize(uitype) # do not overwrite hand-made classes if clsname in mdict: continue standin = StandinClass(clsname, metacreatorcls) mdict[clsname] = standin if force_creation: standin_instances.append(standin) # END for each uitype # delay forced creation as there may be hierarchical relations between # the types for standin in standin_instances: standin.createCls()
def initWrappers( mdict, types, metacreatorcls, force_creation = False, substitute_existing = False): """ Create standin classes that will create the actual class once creation is requested. :param mdict: module dictionary object from which the latter classes will be imported from, can be obtained using ``globals()`` in the module :param types: iterable containing the names of classnames ( they will be capitalized as classes must begin with a capital letter ) :param substitute_existing: if False, an existing type in mdict will be overwritten by a Standin type. This can be useful if a plug-in has registered dummy-types beforehand, and you wish to use your own now""" from mrv.maya.util import StandinClass # create dummy class that will generate the class once it is first being instatiated standin_instances = list() for uitype in types: clsname = capitalize( uitype ) # do not overwrite hand-made classes if clsname in mdict and not substitute_existing: continue standin = StandinClass( clsname, metacreatorcls ) mdict[ clsname ] = standin if force_creation: standin_instances.append(standin) # END for each uitype # delay forced creation as there may be hierarchical relations between # the types for standin in standin_instances: standin.createCls( )
def prefetchMFnMethods(): """Fetch and install all mfn methods on all types supporting a function set. This should only be done to help interactive mode, but makes absolutely no sense in the default mode of operation when everything is produced on demand. :note: Attaches docstrings as well :return: integer representing the number of generated methods""" log.info("Prefetching all MFnMethods") num_fetched = 0 for typename, mfncls in nodeTypeToMfnClsMap.iteritems(): try: nodetype = _nodesdict[capitalize(typename)] except KeyError: log.debug("MFn methods for %s exists, but type was not found in nt" % typename) continue # END handle type exceptions mfnname = mfncls.__name__ mfndb = MetaClassCreatorNodes._fetchMfnDB(nodetype, mfncls) fstatic, finst = mdb.extractMFnFunctions(mfncls) def set_method_if_possible(cls, fn, f): if not hasattr(cls, fn): type.__setattr__(cls, fn, f) # END overwrite protection # END utility for f in finst: fn = f.__name__ if fn.startswith(mfnname): fn = fn[len(mfnname)+1:] # END handle prefixed names fna = fn # alias for method try: origname, entry = mfndb.methodByName(fn) fna = entry.newname except KeyError: pass # END get alias metadata fwrapped = MetaClassCreatorNodes._wrapMfnFunc(nodetype, mfncls, fn, mfndb, mdb.PythonMFnCodeGenerator.kWithDocs) # could have been deleted if fwrapped is None: continue set_method_if_possible(nodetype, fn, fwrapped) if fna != fn: set_method_if_possible(nodetype, fna, fwrapped) # END handle aliases num_fetched += 1 # END for each instance function # END for each type/mfncls pair return num_fetched
def plugin_loaded(self, pluginName): """Retrieve plugin information from a plugin named ``pluginName``, which is assumed to be loaded. Currently the nodetypes found are added to the node-type tree to make them available. The plugin author is free to add specialized types to the tree afterwards, overwriting the default ones. We loosely determine the inheritance by differentiating them into types suggested by MFn::kPlugin<Name>Node""" import base # needs late import, TODO: reorganize modules self.log.debug("plugin '%s' loaded" % pluginName) type_names = cmds.pluginInfo(pluginName, q=1, dependNode=1) or list() self[pluginName] = type_names # register types in the system if possible dgmod = api.MDGModifier() dagmod = api.MDagModifier() transobj = None nt = globals() for tn in type_names: tnc = capitalize(tn) if tnc in nt: self.log.debug("Skipped type %s as it did already exist in module" % tnc) continue # END skip existing node types ( probably user created ) # get the type id- first try depend node, then dag node. Never actually # create the nodes in the scene, created MObjects will be discarded # once the modifiers go out of scope apitype = None try: apitype = dgmod.createNode(tn).apiType() except RuntimeError: try: # most plugin dag nodes require a transform to be created # We create a dummy for the dagmod, otherwise it would create # it for us and return the parent transform instead, which # has no child officially yet as its not part of the dag # ( so we cannot query the child from there ). if transobj is None: transobj = dagmod.createNode("transform") # END assure we have parent apitype = dagmod.createNode(tn, transobj).apiType() except RuntimeError: self.log.error("Failed to retrieve apitype of node type %s - skipped" % tnc) continue # END dag exception handling # END dg exception handling parentclsname = base._plugin_type_to_node_type_name.get(apitype, 'Unknown') typ._addCustomType( nt, parentclsname, tnc, force_creation=True )
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 addCustomTypeFromFile( hierarchyfile, **kwargs ): """Add a custom classes as defined by the given tab separated file. Call addCustomClasses afterwards to register your own base classes to the system This will be required to assure your own base classes will be used instead of auto-generated stand-in classes :param hierarchyfile: Filepath to file modeling the class hierarchy using tab-indentation. The root node has no indentation, whereas each child node adds one indentation level using tabs. :param kwargs: * force_creation: see `addCustomType` :note: all attributes of `addCustomType` are supported :note: there must be exactly one root type :return: iterator providing all class names that have been added""" dagtree = mrvmaya._dagTreeFromTupleList( mrvmaya._tupleListFromFile( hierarchyfile ) ) typ._addCustomTypeFromDagtree( globals(), dagtree, **kwargs ) return ( capitalize( nodetype ) for nodetype in dagtree.nodes_iter() )
def _toNiceName( self, name ): """ :return: nice name version of name, replacing underscores by spaces, and separating camel cases, as well as chaning to the capitalizaion of word""" name_tokens = name.split( "_" ) # parse camel case for i, token in enumerate( name_tokens ): repl_token = self.reNiceNamePattern.sub( self._replInsSpace, token ) name_tokens[ i ] = repl_token # END for each token camel case parse final_tokens = list() # split once more on inserted spaces, capitalize for token in name_tokens: final_tokens.extend( capitalize( t ) for t in token.split( " " ) ) return " ".join( final_tokens )
def test_createNodes(self): names = ["hello","bla|world","this|world|here","that|this|world|here"] nsnames = ["a:hello","blab|b:world","c:this|b:world","d:that|c:this|b:world|a:b:c:d:here"] types = ["facade", "nurbsCurve", "nurbsSurface", "subdiv"] # SIMPLE CREATION: Paths + nested namespaces for i in range(len(names)): ntype = types[i] newnode = nt.createNode(names[i], ntype) assert isinstance(newnode, getattr(nt, capitalize(ntype))) assert newnode.isValid() and newnode.isAlive() # test undo cmds.undo() assert not newnode.isValid() and newnode.isAlive() cmds.redo() assert newnode.isValid() and newnode.isAlive() newnsnode = nt.createNode(nsnames[i], ntype) assert isinstance(newnsnode, getattr(nt, capitalize(ntype))) assert newnsnode.isValid() and newnsnode.isAlive() # test undo cmds.undo() assert not newnsnode.isValid() and newnsnode.isAlive() cmds.redo() assert newnsnode.isValid() and newnsnode.isAlive() # END for each created object # EMPTY NAME and ROOT self.failUnlessRaises(RuntimeError, nt.createNode, '|', "facade") self.failUnlessRaises(RuntimeError, nt.createNode, '', "facade") # CHECK DIFFERENT ROOT TYPES depnode = nt.createNode("blablub", "facade") self.failUnlessRaises(NameError, nt.createNode, "|blablub|:this", "transform", renameOnClash = False) # DIFFERENT TYPES AT END OF PATH nt.createNode("this|mesh", "mesh") self.failUnlessRaises(NameError, nt.createNode, "this|mesh", "nurbsSurface", forceNewLeaf = False) # renameOnClash - it fails if the dep node exists first nt.createNode("node", "facade") self.failUnlessRaises(NameError, nt.createNode, "this|that|node", "mesh", renameOnClash = False) # obj exists should match dg nodes with dag node like path (as they occupy the same # namespace after all assert nt.objExists("|node") # it also clashes if the dg node is created after a dag node with the same name nt.createNode("that|nodename", "mesh") self.failUnlessRaises(NameError, nt.createNode, "nodename", "facade", renameOnClash = False) # it should be fine to have the same name in several dag levels though ! newmesh = nt.createNode("parent|nodename", "transform") newmesh1 = nt.createNode("parent|nodename|nodename", "mesh") newmesh2 = nt.createNode("otherparent|nodename|nodename", "mesh") assert newmesh != newmesh1 assert newmesh1 != newmesh2 # FORCE NEW ############## oset = nt.createNode("objset", "objectSet", forceNewLeaf = False) newoset = nt.createNode("objset", "objectSet", forceNewLeaf = True) assert oset != newoset # would expect same set to be returned sameoset = nt.createNode("objset", "objectSet", forceNewLeaf = False) assert sameoset == oset # force new and dag paths newmesh3 = nt.createNode("otherparent|nodename|nodename", "mesh", forceNewLeaf = True) assert newmesh3 != newmesh2
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]))
def type(self): """:return: the python class able to create this class :note: The return value is NOT the type string, but a class """ uitype = getUIType(self) return getattr(ui, capitalize(uitype))
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]))
def type( self ): """:return: the python class able to create this class :note: The return value is NOT the type string, but a class """ uitype = getUIType( self ) return getattr( ui, capitalize( uitype ) )
def test_createNodes( self ): names = ["hello","bla|world","this|world|here","that|this|world|here" ] nsnames = ["a:hello","blab|b:world","c:this|b:world","d:that|c:this|b:world|a:b:c:d:here"] types = [ "facade", "nurbsCurve", "nurbsSurface", "subdiv" ] # SIMPLE CREATION: Paths + nested namespaces for i in range( len( names ) ): ntype = types[i] newnode = nt.createNode( names[i], ntype ) assert isinstance( newnode, getattr( nt, capitalize( ntype ) ) ) assert newnode.isValid() and newnode.isAlive() # test undo cmds.undo() assert not newnode.isValid() and newnode.isAlive() cmds.redo() assert newnode.isValid() and newnode.isAlive() newnsnode = nt.createNode( nsnames[i], ntype ) assert isinstance( newnsnode, getattr( nt, capitalize( ntype ) ) ) assert newnsnode.isValid() and newnsnode.isAlive() # test undo cmds.undo() assert not newnsnode.isValid() and newnsnode.isAlive() cmds.redo() assert newnsnode.isValid() and newnsnode.isAlive() # END for each created object # EMPTY NAME and ROOT self.failUnlessRaises( RuntimeError, nt.createNode, '|', "facade" ) self.failUnlessRaises( RuntimeError, nt.createNode, '', "facade" ) # CHECK DIFFERENT ROOT TYPES depnode = nt.createNode( "blablub", "facade" ) self.failUnlessRaises( NameError, nt.createNode, "|blablub|:this", "transform", renameOnClash = False ) # DIFFERENT TYPES AT END OF PATH nt.createNode( "this|mesh", "mesh" ) self.failUnlessRaises( NameError, nt.createNode, "this|mesh", "nurbsSurface", forceNewLeaf = False ) # renameOnClash - it fails if the dep node exists first nt.createNode( "node", "facade" ) self.failUnlessRaises( NameError, nt.createNode, "this|that|node", "mesh", renameOnClash = False ) # obj exists should match dg nodes with dag node like path ( as they occupy the same # namespace after all assert nt.objExists( "|node" ) # it also clashes if the dg node is created after a dag node with the same name nt.createNode( "that|nodename", "mesh" ) self.failUnlessRaises( NameError, nt.createNode, "nodename", "facade", renameOnClash = False ) # it should be fine to have the same name in several dag levels though ! newmesh = nt.createNode( "parent|nodename", "transform" ) newmesh1 = nt.createNode( "parent|nodename|nodename", "mesh" ) newmesh2 = nt.createNode( "otherparent|nodename|nodename", "mesh" ) assert newmesh != newmesh1 assert newmesh1 != newmesh2 # FORCE NEW ############## oset = nt.createNode( "objset", "objectSet", forceNewLeaf = False ) newoset = nt.createNode( "objset", "objectSet", forceNewLeaf = True ) assert oset != newoset # would expect same set to be returned sameoset = nt.createNode( "objset", "objectSet", forceNewLeaf = False ) assert sameoset == oset # force new and dag paths newmesh3 = nt.createNode( "otherparent|nodename|nodename", "mesh", forceNewLeaf = True ) assert newmesh3 != newmesh2
def plugin_loaded(self, pluginName, _may_spawn_callbacks=True): """Retrieve plugin information from a plugin named ``pluginName``, which is assumed to be loaded. Currently the nodetypes found are added to the node-type tree to make them available. The plugin author is free to add specialized types to the tree afterwards, overwriting the default ones. We loosely determine the inheritance by differentiating them into types suggested by MFn::kPlugin<Name>Node""" import base # needs late import, TODO: reorganize modules self.log.debug("plugin '%s' loaded" % pluginName) type_names = cmds.pluginInfo(pluginName, q=1, dependNode=1) or list() self[pluginName] = type_names # register types in the system if possible dgmod = api.MDGModifier() dagmod = api.MDagModifier() transobj = None # HANDLE FILE LOAD SPECIAL CASE ############################### if _may_spawn_callbacks and (api.MFileIO.isOpeningFile() or api.MFileIO.isReadingFile()): messages = list() if api.MFileIO.isOpeningFile(): messages.append(api.MSceneMessage.kAfterOpen) elif api.MFileIO.isReadingFile(): # when reading files (import + ref), the nodes seem to stay (tested in maya 2012) # therefore we delay the update until after the fact # recheck after import or reference messages.extend((api.MSceneMessage.kAfterImport, api.MSceneMessage.kAfterReference)) #end handle messages plugin_info = [pluginName] for message in messages: info = list() info.append(api.MSceneMessage.addCallback(message, self._post_read_or_open_cb, info)) info.append(plugin_info) #end for each message to create return #end if we are allowed to spawn callbacks (because we are not called by one) nt = globals() for tn in type_names: tnc = capitalize(tn) if tnc in nt: self.log.debug("Skipped type %s as it did already exist in module" % tnc) continue # END skip existing node types ( probably user created ) # get the type id- first try depend node, then dag node. Never actually # create the nodes in the scene, created MObjects will be discarded # once the modifiers go out of scope # NOTE: Actually, this is not true ! During file loading, its clearly visible # that the nodes stay, may it be dg nodes or dag nodes. This of course dumps # quite a lot of data in case the MR plugin gets loaded. apitype = None try: apitype = dgmod.createNode(tn).apiType() except RuntimeError: try: # most plugin dag nodes require a transform to be created # We create a dummy for the dagmod, otherwise it would create # it for us and return the parent transform instead, which # has no child officially yet as its not part of the dag # ( so we cannot query the child from there ). if transobj is None: transobj = dagmod.createNode("transform") # END assure we have parent apitype = dagmod.createNode(tn, transobj).apiType() except RuntimeError: self.log.error("Failed to retrieve apitype of node type %s - skipped" % tnc) continue # END dag exception handling # END dg exception handling parentclsname = base._plugin_type_to_node_type_name.get(apitype, 'Unknown') typ._addCustomType( nt, parentclsname, tnc, force_creation=True )