Maya Python - How to select all nodes connected to a given object?

Hi,

I’m working on a script where the user selects a series of meshes or groups in the Maya outliner and when the script is run, it returns a list of all nodes that are connected or related to any object in the selection.

  1. When I say ‘related’ these are parents or children any ‘n’ number of steps away.
  2. When I say ‘connected’ these are either direct connections, or nodes connected via other nodes through any ‘n’ number of steps. These might be construction history nodes, shader nodes (materials, file nodes, ramps, etc.)

I’ve started writing and testing the script but I’m a bit caught up on how to achieve point #2. I understand that input and output connected nodes can be accessed via listHistory() or listHistory( f=True ). However, if I select a mesh and do this, it won’t return the material assigned to the object.

That said, I can get the material by repeating the process a second time on all connected objects that are one step away.

Here is my script so far…

import maya.cmds as cmds

# returns a list of all nodes connected or related to the selection
def getAllConnectedNodes( sel ):
    # starting with an empty set
	nodes = set()
		
	# add all selected objects to the set
	for obj in sel:
		nodes.add(obj)
	
	# add all relatives of all selected objects
	for obj in sel:
		# add all children to the set
		desc = cmds.listRelatives( obj, ad=True, f=True )
		if desc:
			nodes = nodes.union( set( desc ) )
		
		# get all parents
		parents = cmds.ls( sel, long=True )[0].split( '|' )[1:-1]
		# convert parent names to long names
		parentsList = list()
		tmpStr = ""
		for token in parents:
			tmpStr = tmpStr + "|" + token
			parentsList.append( tmpStr )
        # add all parents to the set
		if parentsList:
			nodes = nodes.union( set( parentsList ) )
	
	# add all connected nodes
	for i in range(2):
		for obj in nodes:
			hist = cmds.ls( cmds.listHistory( obj ), long=True )
			ftr = cmds.ls( cmds.listHistory( obj, f=True ), long=True )
			if hist:
				nodes = nodes.union( set( hist ) )
			if ftr:
				nodes = nodes.union( set( ftr ) )

	return list(nodes)

sel = cmds.ls( selection=True, long=True )
connectedNodes = getAllConnectedNodes( sel )
cmds.select( connectedNodes )

If you notice the block of code at # add all connected nodes, if I loop through this…

  • 1 time, I don’t get the materials
  • 2 times, I get the materials and their file nodes
  • 3 times, I get all other shape nodes in my scene even if they are not related or connected to my selection

So looping through 2 times seems to work in my particular scene, but I have no idea why this is and whether this script will work on a different scene, for example, if the shader network has a greater depth than the materials in my current scene.

Is there a more reliable way to get all connections?

Ok so I think I solved my own problem.

The reason that unrelated objects (shape nodes) would become selected was due to the shadingEngine node. I found that I only need to do one call of listRelatives() for future. This will get me the shadingEngine from whatever group or mesh selected in the outliner.

Once the shadingEngine is in the set, I only need to look at input connections (history) and exclude any shape nodes from there.

There’s also an extra step at the end where I have to exclude the shadingEngine from the result selectable set. If it’s included, the script will select all members of the shadingEngine, and consequently, unrelated mesh shapes, which is not what I want.

Here is the edited script:

# returns a list of all nodes connected or related to the selection
def getAllConnectedNodes( sel ):
    # starting with an empty set
	nodes = set()
	
	# add all selected objects to the set
	for obj in sel:
		nodes.add(obj)
	
	# add all relatives of all selected objects
	for obj in sel:
		# add all children to the set
		desc = cmds.listRelatives( obj, ad=True, f=True )
		if desc:
			nodes = nodes.union( set( desc ) )
		
		# get all parents
		parents = cmds.ls( sel, long=True )[0].split( '|' )[1:-1]
		# convert parent names to long names
		parentsList = list()
		tmpStr = ""
		for token in parents:
			tmpStr = tmpStr + "|" + token
			parentsList.append( tmpStr )
        
		# add all parents to the set
		if parentsList:
			nodes = nodes.union( set( parentsList ) )
	
	# for all objects in selection
	for obj in nodes:
		# look at future to find output connections (shadingEngine)
		future = cmds.ls( cmds.listHistory( obj, f=True ), long=True )
		# add the output connections to the set
		nodes = nodes.union( set( future ) )

	# iterate through the input connections. I think this number is arbitrary?
	for i in range(3):
		# look at history to find input connections
		history = cmds.ls( cmds.listHistory( nodes ), long=True )
		for obj in history:
			# if the node is not a shape node
			if ( cmds.nodeType( obj ) != "mesh" ) and ( cmds.nodeType( obj ) != "transform" ):
				# add the input connections to the set
				nodes.add( obj )

	# make a set to hold the selectable nodes
	selectableNodes = set()

	# remove the shadingEngine from the selectable set
	for obj in nodes:
		if ( cmds.nodeType( obj ) != "shadingEngine" ):
			selectableNodes.add( obj )
	
	return list( selectableNodes )
	#return list( nodes )

sel = cmds.ls( selection=True, long=True )
connectedNodes = getAllConnectedNodes( sel )
cmds.select( connectedNodes )