Copy / Paste Bundles

For your productions, bundles are still an important scene-structure-tool. Since the Houdini 18 and solaris, this is a legacy workflow which will be less important over the next years. Anyway, we found it a little bit to early to jump to solaris – we’ll stay on the known workflows for a while.

When working with a huge amount of objects we organize our scenes with bundles. There are bundles for ogl capturings, rendering bundles, simulation bundles or light bundles. The downside of bundles is, that they are not copy- and pasteable. So I decided to create two scripts – One for copying and one for pasting bundles.

Feel free to download the scripts and to try them.

 

Copy Bundles

import hou
import xml.etree.cElementTree as ET
import xml.dom.minidom as minidom

#set a counter
bNR = 0
#create an empty bundle list
crrBNodes = []
#setup elementtree root and sublement
root = ET.Element("scene")
doc = ET.SubElement(root, "bundles")
#check if an bundle is selected
if len(hou.selectedNodeBundles()) != 0:
    #loop over the selected bundles
    for currB in hou.selectedNodeBundles():
        #loop over the bundle elements
        for currNode in currB.nodes():
            #appand the current node to the bundle list
            crrBNodes.append(currNode.path())
        #appand the new bundle subelement to the elment tree
        ET.SubElement(doc, "bundle", name=str(currB.name())).text = str(crrBNodes)
        #reset the bundle list
        crrBNodes = []    
    
    #format the elementtree
    rough_string = ET.tostring(root, 'utf-8')
    reparsed = minidom.parseString(rough_string)
    formatedTree = reparsed.toprettyxml(indent="\t")
   
    #setup the output path for the xml file
    outputDIR = hou.hscriptExpression("$TEMP") + "\\BUNDLE_Copy.xml"
    #write the xml file to the users temp direction
    with open(outputDIR, "w") as f:
        f.write(formatedTree)

Paste Bundles

#import modules
import hou
import os.path
import xml.etree.cElementTree as ET

#setup the output path for the xml file
imputDIR = hou.hscriptExpression("$TEMP") + "/BUNDLE_Copy.xml"
#check if the xml file exists
if(os.path.exists(imputDIR)):
    #import the element tree from file
    tree = ET.parse(imputDIR)
    #get the root
    root = tree.getroot()
    #loop over the root
    for child in root:
        #loop over the bundles
        for elm in child:
            #create an checker variable
            oCheckBundleExists = 0
            #loop over the scene bundles
            for currB in hou.nodeBundles():
                #check if there is already a bundle with t
                if(elm.get('name') == currB.name()):
                    #if ther is an existing bundle, set the checker to 1
                    oCheckBundleExists  = 1
            #convert sting to array
            oBundleMembers = elm.text
            oBundleMembers = oBundleMembers.replace("[","")
            oBundleMembers = oBundleMembers.replace("]","")
            oBundleMembers = oBundleMembers.replace("'","")
            oBundleMembers = oBundleMembers.replace(" ","")
            oBundleMembers = oBundleMembers.split(",")
            #check if the bundle exists
            if oCheckBundleExists == 1:  
                #if it exists, create a bundle with _1
                oCurrentBundle = hou.addNodeBundle(str(elm.get('name') + "_1"))
            else:
                #if it not exists create a the bundle wit the proper name
                oCurrentBundle = hou.addNodeBundle(str(elm.get('name')))
            #Loop over the created array oBundleMembers
            for oNode in oBundleMembers:
                #assign the Node to add the the oNodeToAdd Variable
                oNodeToAdd = hou.node(oNode)
                #check if the node to add is valid
                if oNodeToAdd:
                    #if yes add the node to the current bundle
                    oCurrentBundle.addNode(oNodeToAdd)
                else:
                    #if no put an error to the console
                    print "Node not found " + oNode +  ". Skip adding to Bundele: " + str(elm.get('name'))

Instance Objects By Bundle Members

Hey!

I made a simple setup that reads the members of a bundle and copies the oppath as an instance attribute to points. When dealing with a huge amount of instances, it’s super easy to manage those instance masters. Click here to download the scene. InstanceByBundleMembers

Quick Edit: Please add the line oInstances.sort() to the Python SOP. With this line, the instaces will stay the same. Otherwhise it could be, that python reads the bundle members in another way from time to time.

Rolling Coin SOP


For a current Job I’d to make a rolling coin setup. All transforms and rotations are done on SOP level with VOPs. Feel free to grab the hip file and to try it out. Quick node, currently it’s just working with round shapes. rolling_coin_setup

Houdini: Jump to Material

Hi!
If the scenes get more and more complicated, material selection gets time-consuming, too. To speedup the selecting and navigating networks, I’ve build a shelf tool, that lets you jump directly to the corresponding material of an object. Please have look at my video. I’m explaining the installation and functionality. Hopefully that makes your navigation, during the shading process more comfortable.

  
#Import Modules
import toolutils
import hou

#Set Variables
selected_node = ""

#Define type arrays
MAT_Detect = ["redshift::Material","redshift_vopnet","principledshader::2.0","materialbuilder","arnold::standard_surface", "arnold_materialbuilder"]
Sel_Detect = ["geo","instance"]

#Get the Selected Nodes
selected = hou.selectedNodes()

#Check if there is something selected 
if selected:
    #Compare first selected objects type with the valid object types
    if selected[0].type().name() in Sel_Detect:
        #Set the selected node. Just the fist is valid
        selected_node = selected[0]
    #Force the user to select an object, if the current doesn't match the selection type
    else:
        #Clear the selection
        hou.clearAllSelected() 
        #Promt the user to select an new object
        prompt = toolutils.selectionPrompt(hou.objNodeTypeCategory())
        scene_viewer = toolutils.sceneViewer() 
        selected_objects = scene_viewer.selectObjects(prompt)
        #Check if an object is selected
        if selected_objects:
            #Set the first selected node to variable
            selected_node = selected_objects[0]
        else:
            #Output an Error
            hou.ui.displayMessage("Nothing Selected")    
#Force the user to select an object, because nothing is selected
else:
    #Promt the user to select an new object
    prompt = toolutils.selectionPrompt(hou.objNodeTypeCategory())
    scene_viewer = toolutils.sceneViewer() 
    selected_objects = scene_viewer.selectObjects(prompt)
    #Check if an object is selected
    if selected_objects:
        #Set the first selected node to variable
        selected_node = selected_objects[0]
    else:
        #Output an Error
        hou.ui.displayMessage("Nothing Selected")
    

#Check if the selected_node variable is not empty
if selected_node:
    #Compare selected_node's type with the valid object types
    if selected_node.type().name() not in MAT_Detect :
        #Convert Releative Pathes to Absolute Pathes
        matPath = selected_node.parm("shop_materialpath").eval()
        #Set the targetpath to the node variable
        node = selected_node.node(matPath)
        #Check if node is valid
        if node:
            #Look for child Objects
            if node.children():
                #If there are children, replace node with the fist child
                node = node.children()[0]
        else:
            #Output an Error
            hou.ui.displayMessage("Empty material path")
         
    else:
        node = selected_node
        #Look for child Objects
        if node.children():
            node = node.children()[0]

    #Check if the material node exists
    if node:        
        #Set some Variables
        pane = ""
        index = 0
        #Loop over the active panes
        while pane is not None:
            #Search for the first network editor
            pane = hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor, index) 
            #Loop over the panes
            if pane is not None:
                #Get the current state
                ID = pane.linkGroup()
                #Looking for Linkgroup1
                if str(ID) == "paneLinkType.Group1":
                    targetPane = pane
                    switchorselect = 1
                #Looking for FollowSelection
                if str(ID) == "paneLinkType.FollowSelection":
                    targetPane = pane
                    switchorselect = 0
                #Looking for Pinned
                if str(ID) == "paneLinkType.Pinned":
                    targetPane = pane
                    switchorselect = 2
            index += 1
       
        #If there is a PaneType Group1 
        if switchorselect == 1:
            #Set the current Node
            targetPane.setCurrentNode(node)
        #If the PaneTypeState is set to Follow selection
        if switchorselect == 0:
            #Selet the material
            node.setSelected(True, clear_all_selected=True)
        #If the PaneTypeState is set to Pinned
        if switchorselect == 2:
            #Retun an errror
            hou.ui.displayMessage("The PaneType is set to Pinned")

Channel Pose Constraint with retime functionality

Hey there,

since a long time we see the need for a fast and intuitive way to retime objects. Houdinis default way is to use chops as the way to go. With the help of an channel and warp node, chops enables us to do exactly what we want. The downside of this approach is, that we need to care about units, sampling rates, start/end, etc. For a typical employee it takes some minutes to set everything up.

Our approach is much more simple and pragmatic. With the help of vtorigin and vrorigin we grab the world transforms of the target object, and retime the fcurves with the chf function. It’s as simple as that. Limited functionality, but no more worries about sampling or framerates. Here is a video that is demonstrating the script:

 

  
import toolutils
import hou

selected_node = ""
selected = hou.selectedNodes()

if selected:
    selected_node = selected[0]
else:
    scene_viewer = toolutils.sceneViewer() 
    selected_objects = scene_viewer.selectObjects("Select the Object to Constrain. To finish press Enter. \n If nothing is selected, an Object will be generated.",allowed_types = ["*"])
    if selected_objects:
        selected_node = selected_objects[0]
    else:
        obj = hou.node("/obj")
        selected_node = obj.createNode("null", "globalTransform")
    
if selected_node:
    hou.clearAllSelected() 
    scene_viewer = toolutils.sceneViewer() 
    target_objects = scene_viewer.selectObjects("Select the Goalobject to Constrain to. To finish press Enter.",allowed_types = ["*"])
    if target_objects:
        target_object = target_objects[0]        
    else:
        hou.ui.displayMessage("Nothing Selected. Abort constraining")

if target_object:
    parm_group = selected_node.parmTemplateGroup()
    parm_folder = hou.FolderParmTemplate("extras", "Extras")
    parm_folder.addParmTemplate(hou.FloatParmTemplate("currentFrame", "Current Frame", 1))
    parm_folder.addParmTemplate(hou.FloatParmTemplate("target_t", "Target Translate", 3))
    parm_folder.addParmTemplate(hou.FloatParmTemplate("target_r", "Target Rotate", 3))
    parm_group.append(parm_folder)
    selected_node.setParmTemplateGroup(parm_group)
    
    selected_node.parm("target_tx").setExpression('vtorigin("", "' + str(target_object.path()) + '")[0]' )
    selected_node.parm("target_ty").setExpression('vtorigin("", "' + str(target_object.path()) + '")[1]' )
    selected_node.parm("target_tz").setExpression('vtorigin("", "' + str(target_object.path()) + '")[2]' )
    selected_node.parm("target_rx").setExpression('vrorigin("", "' + str(target_object.path()) + '")[0]' )
    selected_node.parm("target_ry").setExpression('vrorigin("", "' + str(target_object.path()) + '")[1]' )
    selected_node.parm("target_rz").setExpression('vrorigin("", "' + str(target_object.path()) + '")[2]' )
    
    selected_node.parm("currentFrame").setExpression('$FF' )
    
    selected_node.parm("tx").setExpression('chf("target_tx", ch("currentFrame"))' )
    selected_node.parm("ty").setExpression('chf("target_ty", ch("currentFrame"))' )
    selected_node.parm("tz").setExpression('chf("target_tz", ch("currentFrame"))' )
    selected_node.parm("rx").setExpression('chf("target_rx", ch("currentFrame"))' )
    selected_node.parm("ry").setExpression('chf("target_ry", ch("currentFrame"))' )
    selected_node.parm("rz").setExpression('chf("target_rz", ch("currentFrame"))' )
    
    hou.clearAllSelected() 

Houdini: Animation Editor with just animated channels

Hey!

Everyone who is coming from a Maya/Softimage background is wondering why the Houdini ChannelList has this strange scoping behavior. It loads by default all parameters with auto-add into the ChannelList.

To get only the animated ones there is an option:

I have prepared a script, if you want to stay with the default behavior (Add Parameters With Auto-Add to Channel List flag), but want the option to load the Animation Editor with just the animated parms.

First select the Nodes with the animation you want to edit. Then run the script. You’ll notice the script scopes/unscopes the channels, and leave you with a clean animation editor.

The script was a nice finger exercise 🙂

 

  
import hou

if hou.selectedItems():
    oSel = hou.selectedItems()
    for oObj in oSel:
        for parm in oObj.parms():
            if parm.isTimeDependent():
                parm.setScope(1)
            else:
                parm.setScope(0)
                try:
                    if str(parm.expression()) == "linear()":
                        parm.setScope(1)
                    if str(parm.expression()) == "bezier()":
                        parm.setScope(1)
                    if str(parm.expression()) == "constant()":
                        parm.setScope(1)
                except:
                    parm.setScope(0)
else:
    print "nothing selected"

animationEditor = hou.ui.curDesktop().createFloatingPanel(hou.paneTabType.ChannelEditor)
animationEditor.setName("Animation Editor - Selection")