Graph Cleanup - Breakdown

Houdini Realtime Development

Breakdown

Destruction Cleanup - Houdini

Step 1

The first thing the tool does is gather a list of all nodes in the current graph view. This will be stored in a list, ordered top to bottom in terms of hierarchy. (This can prevent cases where Houdini wouldn't know what to reconnect nodes to after a child gets deleted) Afterwards it will loop through the created list and delete any node which does not have a connection to any other node, and will also make sure that node is not required by any other node in the network. This could be seen as a pre-cleanup step, which reduces the amount of data that needs to be scanned.


Destruction Cleanup - Houdini

Step 2

After the pre-cleanup in step 1, we now need to index which nodes we want to keep. The tool will first gather all "Output" nodes, and then do a recursive scan upwards in the connected network. We then know exactly which nodes to keep. However, some of the nodes in the graph are dependent on data from nodes not directly connected to the output network. This means we will need to exclude those from being deleted aswell.

Destruction Cleanup - Houdini

Step 3

In step 1 and 2 we indexed which nodes should be not be cleaned up, but we dont know yet if any of those indexed nodes rely on information from nodes not contained in the network. In this step we will scan all nodes marked for keeping for references and dependencies. The tool will do something called iterative scanning, where it will look for nodes that have certain dependencies, and nodes which are connected above it. This process will keep going until it has found all possible dependencies, updating potential candidates while scanning. Therefore called iterative rather than recursive.

Destruction Cleanup - Houdini

Step 4

Step 4 is the last step of the tool. In this tool we delete all nodes not marked for keeping. An additional option we could offer the user is to pack all "to be deleted nodes" in a subnet rather than actually deleting them. This could be integrated into the popup window asking for a confirmation. It is important to show this popup, since it is a very deconstructive action.


Install Instructions

The Graph Cleanup Shelf Tool is really easy to install. Simply copy the entire script below (Make sure you dont miss any parts!!), and then in Houdini rightclick your shelf tab. This opens up a popup window where you can press "New Tool". After pressing "New Tool", paste the script you copied in the "Script" tab, and press "Apply". Congratulations! You have now successfully installed Graph Cleanup. You are now able to use it.


Script

#### Houdini File Cleanup
##   
##    Author: Paul Ambrosiussen
##    Date Created: 16-04-2017
##    Last Update: 17-04-2017
##    Questions & Support: paul@ambrosiussen.com
##
#### Houdini File Cleanup

import hou, toolutils

ThisNode = hou.pwd()
Path = toolutils.sceneViewer().pwd()
NodeTypesAllowedToDelete = ['Sop']

#Return NodeType Category name
def ReturnNodeTypeCategory(a_node):
	return a_node.type().category().name()

#Return NodeType name
def ReturnNodeType(a_node):
	return a_node.type().name()

#Return BOOL if this node has any references or dependencies
def IsReferenced(a_node):
	return len(a_node.dependents(True)) + len(a_node.references(True)) > 0

#Return list of any dependencies and references the node argument has
def GetReferences(a_node):
	return a_node.references(True)

#Return BOOL if the argument node has any connected inputs
def HasInputs(a_node):
	return len(a_node.inputs()) > 0

#Return BOOL if the argument node has any connected outputs
def HasOutputs(a_node):
	return len(a_node.outputs()) > 0

#Delete any unconnected & unreferenced nodes in the argument list
def DeleteIsolated(a_nodeNetwork):
	t_NodesWithNoConnections = [x for x in a_nodeNetwork if not HasInputs(x) and not HasOutputs(x) and x != ThisNode]

	for node in t_NodesWithNoConnections:
		if not IsReferenced(node) and ReturnNodeType(node) != 'output' and ReturnNodeTypeCategory(node) in NodeTypesAllowedToDelete:
			if node.isNetwork():
				for item in node.allSubChildren(top_down=False):
					item.destroy()
			node.destroy()

#Delete any unfrozen node trees from bottom to top in the argument list
def DeleteUnusedChains(a_nodeNetwork):
	KeepNodes = []

	for node in [x for x in a_nodeNetwork if ReturnNodeType(x) == 'output']:
		KeepNodes.extend(ExploreDependencies(node))

	for node in [x for x in a_nodeNetwork if x != ThisNode and x not in KeepNodes]:
		if ReturnNodeTypeCategory(node) in NodeTypesAllowedToDelete:
			if node.isNetwork():
				for item in node.allSubChildren(top_down=False):
					item.destroy()
			node.destroy()

#Find any important references in the locked / frozen nodetree
def ExploreDependencies(a_node):
	t_ReturnList = []
	
	t_Stack = []
	t_Stack.append(a_node)
	
	while len(t_Stack) > 0:
		t_Node = t_Stack.pop(0)
		
		if t_Node not in t_ReturnList:
			t_ReturnList.append(t_Node)
		
		t_FamilyTree = TraversePath(t_Node)
		t_FamilyTree.extend(GetReferences(t_Node))
		t_FamilyTree += [t_Node.parent()] if t_Node.parent() != None else []
		
		if len(t_FamilyTree) == 0 and t_Node not in t_ReturnList:
			t_ReturnList.append(t_Node)
		else:
			for node in t_FamilyTree:
				if node not in t_ReturnList:
					t_Stack.append(node)		
	return t_ReturnList

#Build recursive list of the argument node. Upwards
def TraversePath(a_node):
	t_ReturnList = []
	t_Stack = []
	t_Stack.extend([x for x in a_node.inputs() if x != None])

	while len(t_Stack) > 0:
		t_Node = t_Stack.pop(0)

		if t_Node not in t_ReturnList:
			t_ReturnList.append(t_Node)

		t_MultiInputs = [x for x in t_Node.inputs() if x != None]

		if len(t_MultiInputs) == 0 and t_Node not in t_ReturnList:
			t_ReturnList.append(t_Node)
		else:
			for input in t_MultiInputs:
				if input not in t_ReturnList:
					t_Stack.append(input)
	return t_ReturnList

#The Main Function
def MainFunction():

	#Initial Cleanup
	DeleteIsolated(Path.children())

	#Main Cleanup Cycle
	DeleteUnusedChains(Path.children())

## UI ##
ConfirmWindow = hou.ui.displayConfirmation("Are you sure you want to proceed?\nThis action cannot be undone!", severity=hou.severityType.ImportantMessage, title='Graph Cleanup')

## FUNCTIONALITY CHECK ##
if ConfirmWindow == 1:
	MainFunction()