# # Produced by: # Graham Thompson # captainhammy@gmail.com # www.captainhammy.com # # Name: nodeutils.py # # Comments: Perform opertions on Houdini nodes. # # Version: 1.1 # # Compatibility: Houdini 11.0 # # Changes: # # 1.1: # Added functions that are suitable for calling from event # handler scripts. These functions help with coloring nodes # based on their types, tool settings or names. Also added a # function that can modify display/render flags on wiring. # import hou import cPickle manager_color = (0, 0.533, 0) # green generator_color = (1, 0, 0) # red # Common node type colors. These colors affect these types in all contexts. # Node names are matched identically with keys. common_type_colors = { "null": (0, 0.4, 1), # blue "switch": (1, 0.4, 1), # pink } # Specific/general node type colors based on context. By default, node names are matched # by a search result. "hlight", "indirectlight", etc will all be matched to "light". To # match identically, change the last argument to the checkDicionaryForTypeName() call # to False. node_type_colors = { "object": { "null": (0, 0.4, 1), # blue "geo": (0, 0.4, 0), # green "light": (1, 0.8, 0), # yellow "ambient": (1, 0.8, 0), # yellow "cam": (0.4, 0, 0.6), # purple "bone": (0.867, 0, 0), # red }, "dop": { }, } # Specific tab menu folders based on context. For example, all the nodes who # are located in the "Forces" tab in a DOP context will be colored orange. tool_menu_colors = { "dop": { "Solvers": (0, 0.3, 1), # blue "Objects": (0.4, 1, 0.4), # green "Forces": (1, 0.4, 0), # orange }, "sop": { "Manipulate": (1, 0.8, 0), # yellow. "Import": (0.2, 0, 0.4), # purple. }, "vop": { "Geometry": (0.4, 1, 0.4), # green "Utility": (1, 0.4, 0), # orange }, } # Name -> Color mappings. name_colors = { 'OUT': (1, 0.8, 0), # yellow 'RENDER': (0.867, 0, 0), # red 'DISPLAY': (0.4, 1, 0.4), # green 'DOP': (0, 0.8, 1), # light blue } def getNodeTypeTool(node_type): """ Get the hou.Tool entry for a particular node type. """ tools = hou.shelves.tools() tool_name = "%s_%s" % (node_type.category().name().lower(), node_type.name()) return tools.get(tool_name) def __checkDictionaryForTypeName(type_dictionary, type_name, soft_check=True): """ Check a dictionary for a specific node type name. soft_check: Use a soft check by searching for the specified type name in the string rather than an absolute string match. This allows generic names like 'light' to encompass several nodes at once. """ if soft_check: for key, color in type_dictionary.iteritems(): if type_name.find(key) != -1: return color return None else: # Try for a direct match of the type name. return type_dictionary.get(type_name) def colorNodeByTypeOrTool(node): """ Color a node based on its node type or tool settings. """ # Node type information. node_type = node.type() node_type_name = node_type.name() type_category_name = node_type.category().name().lower() # Start with no node color: node_color = None # Manager nodes (sopnet, chopnet, ropnet, etc). if node_type.isManager(): node_color = manager_color # Common node types (nulls, switches, etc) are colored the same across # different contexts. elif node_type_name in common_type_colors: node_color = common_type_colors[node_type_name] else: # Get the color mappings for this context. type_colors = node_type_colors.get(type_category_name) if type_colors is not None: # Check to see if there are any mappings for this type. node_color = __checkDictionaryForTypeName(type_colors, node_type_name, True) # If there were no mappings for the specific type, check for the tab # menu. if node_color is None: # Get the tool for the node type. tool = getNodeTypeTool(node_type) # Get a tuple of possible tab menu locations this tool will appear. menu_locations = tool.toolMenuLocations() # Get any possible colored menu locations for this context. menu_colors = tool_menu_colors.get(type_category_name) if menu_colors is not None: # Iterate over all the possible locations for the tool. for location in menu_locations: # Try and get the color of the location. node_color = menu_colors.get(location) # If we got a color then use it. if node_color is not None: break # If there have been no previous assignments, color generator nodes. if node_color is None and node_type.isGenerator(): node_color = generator_color # If we found a color mapping, set the color of the node. if node_color is not None: node.setColor(hou.Color(node_color)) def colorNodeByName(node): """ Color a node based on its name. """ node_name = node.name() node_color = name_colors.get(node_name) if node_color is not None: node.setColor(hou.Color(node_color)) def changeFlagsOnWiring(node, input_index): """ Attempt to change display and/or render flags upon rewiring of nodes. If a node is connected to a node that has its display and/or render flag set then the newly connected node will now be the node with those flags set if the source node is connected to the first input. """ # Nodes inside locked digital assets that use Subnet Indirect Inputs # receive an input changed event when those parent inputs are changed, # so we make sure we are not in a locked asset. if not node.isInsideLockedHDA(): # Only care about the first input. if input_index == 0: # Get the node connector for the first input. input_connector = node.inputConnectors()[0] # Make sure we are connecting, not disconnecting. if len(input_connector) != 0: input_connection = input_connector[0] # Get the connected node. input_node = input_connection.inputNode() # Guard against node types that don't use these flags. if hasattr(node, "setDisplayFlag"): if input_node.isDisplayFlagSet(): node.setDisplayFlag(True) if hasattr(node, "setRenderFlag"): if input_node.isRenderFlagSet(): node.setRenderFlag(True) def copyInputsToNode(node, target, ignore_missing=False): """ Copy all the input connections from this node to the target node. ignore_missing: If the target node does not have enough inputs then skip this connection. """ input_connections = node.inputConnections() num_target_inputs = len(target.inputConnectors()) if num_target_inputs is 0: raise hou.OperationFailed("Target node has no inputs.") for connection in input_connections: index = connection.inputIndex() if index > (num_target_inputs - 1): if ignore_missing: continue else: raise hou.InvalidInput("Target node has too few inputs.") target.setInput(index, connection.inputNode()) def moveOutputsToNode(node, target): """ Move all the output connections from this node to the target node. """ output_connections = node.outputConnections() for connection in output_connections: node = connection.outputNode() node.setInput(connection.inputIndex(), target) def swapPredecessor(node): """ Swap a node with its predecessor node. This operation will fail if: - this node has no inputs - this node has nothing connected to the first input - the predecessor node has no inputs. """ # Make sure we can switch this node. if node.type().maxNumInputs() is 0: raise hou.OperationFailed("This node takes no inputs.") elif len(node.inputConnectors()[0]) is 0: raise hou.OperationFailed("This node must have a connection in the first input.") # Get the node to switch with. predecessor = node.inputs()[0] # Make sure the predecessor can be switched. if predecessor.type().maxNumInputs() is 0: raise hou.OperationFailed("The predecessor node has no inputs.") # Move the outputs from the node to the predecessor. moveOutputsToNode(node, predecessor) # Swap the positions of the nodes. position = predecessor.position() predecessor.setPosition(node.position()) node.setPosition(position) # If the predecessor node has an input connection in the first input # then move it to the other node. if len(predecessor.inputConnectors()[0]) is not 0: node.setFirstInput(predecessor.inputs()[0]) # If not then set the nodes first input to None. else: node.setFirstInput(None) # Connect the node to the predecessor. predecessor.setFirstInput(node) def writeToUserData(node, user_data_name, data): """ Store a data structure in a node's user data dictionary. The structure to be stored must support pickling. """ node.setUserData(user_data_name, cPickle.dumps(data)) def readFromUserData(node, user_data_name): """ Read a data structure stored in a node's user data dictionary. """ return cPickle.loads(node.userData(user_data_name)) def getNodeTypeTool(node_type): """ Get the hou.Tool entry for a particular node type. """ tools = hou.shelves.tools() tool_name = "%s_%s" % (node_type.category().name().lower(), node_type.name()) return tools.get(tool_name) def getExternalReferences(node_or_nodes, group=False, missing=False, only_nodes=False, only_files=False, recurse=True, all_file_parms=False): """ Shows all/missing external references in a node. This function is a wrapper around opextern with a bit more control. node_or_nodes: A single instance, or a list of hou.Node objects. group: Group frame ranges together; files like butterfly$F.pic will be shown as 'bufferfly$F.pic [1-7]'. missing: Check each file to see if it exists, and only return ones with missing files. only_nodes: Return only the nodes with have external references (or have missing external references, if missing=True. recurse: Recurse into networks and sub networks. only_files: Return only the external files. If missing=True, this will only return the missing files. all_file_parms: Return all file parameters of a node that is found to have an external reference. This includes all blank file parms. """ # Get the function options. args = [""] if group: args.append("g") if missing: args.append("M") if only_nodes: args.append("n") if only_files: args.append("r") if recurse: args.append("R") # If just a single node was specified, convert it to a list. if isinstance(node_or_nodes, hou.Node): node_or_nodes = (node_or_nodes,) # Get paths to all the nodes. node_paths = map(hou.Node.path, node_or_nodes) # Call opextern with our args and paths to get the result. output = hou.hscript("opextern %s %s" % (" -".join(args), " ".join(node_paths)) )[0] # If only showing missing, check to see if there are any or not. if missing: # Return an empty tuple if there are no missing references. if "No missing external" in output: return () # Lists of parms to return. parms = [] # Split the output string into lines, discarding the last. output = output.split("\n")[:-1] # For each string entry. # Return the file list. if only_files: return tuple(output) # Return a list of hou.Node objects from the node names. elif only_nodes: return tuple(map(hou.node, output)) # Get the hou.Node objects. nodes = map(hou.node, [ref.split()[0] for ref in output]) for node in nodes: for parm in node.parms(): # If the parameter is a String parameter. if isinstance(parm.parmTemplate(), hou.StringParmTemplate): # If the String parameter is a file reference. if parm.parmTemplate().stringType() \ == hou.stringParmType.FileReference: # If we want all the file parms, append it to the list. if all_file_parms: parms.append(parm) # We only want used file parms. else: val = parm.eval() # If the the parameter isn't empty. if val != "": # We are looking for missing so test if it # exists. if missing: # It doesn't exist so add it. if not os.path.exists(val): parms.append(parm) # We want our used file parameter. else: parms.append(parm) # Return the list of parameters. return tuple(parms)