Exemple #1
0
		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()
Exemple #2
0
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]
Exemple #3
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)
Exemple #4
0
        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()
Exemple #5
0
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]
Exemple #6
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()
Exemple #7
0
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( )
Exemple #8
0
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
Exemple #9
0
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
Exemple #10
0
	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 )
Exemple #11
0
 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
Exemple #12
0
		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
Exemple #13
0
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() )
Exemple #14
0
	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 )
Exemple #15
0
    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 
Exemple #16
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]))
Exemple #17
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))
Exemple #18
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]))
Exemple #19
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 ) )
Exemple #20
0
	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 
Exemple #21
0
 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 )