top of page

It's possible to save the python tools in the shelf but also to save them somewhere as txt file in the disk and from here load them in houdini.

This is easier to manage and share across multiple workstations.

​

HOW TO DO IT (Updated on 14/08/2020):

​

​

WHERE TO PLACE THE FILES:

​

1 - To be able to load the python script from a custom location you need first of all to specify the location of this scripts. To do this your HOUDINI_PATH  needs to have also this custom directory in the list of folders to check at the start of Houdini (remember to add at the end also "&" so Houdini knows he has to load default directories AND this.

​

2 - Inside the specified folder you need to have a folder structure like custom_directory/scripts/python.  Here you can place your scripts

​

3 - Now how to place the scripts. The scripts can't be randomly placed here but you need to have a folder named as you prefer. For example "folder_name".

Inside this you need to have an empty txt file (or better a txt file with .py extention), named __init__.

Then you can also place here the other .py file with your script inside. For example called "script_name.py"

​

HOW TO LOAD THEM IN HOUDINI:

​

1 -  Something important is that whatever script you place here needs to have at the top import hou and import os.

Also you need to wrap the code inside a function. For example def function_name(): 

If you don't do it, apparently it will execute two times the script the first time you use it every session. Not sure why.

Ideally you should also wrap it into a class, but even if explained very well in varomix tutorial (Python project manager part 3) I skip it for now since I don't fully understand it.

​

2 - In your custom shelf tool in Houdini create a new tool, set the language as python and write:

​

import os

​

from folder_name import script_name

reload(script_name)

​

script_name.function_name()

​

3 - Now you should be able to execute the script correctly

​

​

​

​

00. Copy all textures and set paths relative to HIP (net2hip)

import re, glob
import hou, os, shutil


### SET and CREATE PATH FOR TEXTURES ####
folder_name = "textures"
hip_path = hou.getenv("HIP")
backpath = hip_path + "/" + folder_name #change this name for change the folder name
if not os.path.exists(backpath):
    os.makedirs(backpath)
    print "created directory for textures"

### for DEBUG PURPOSES ###
# print backpath

extensions = ('.jpg','.jpeg','.exr', '.tga', '.rat')

### SINCE I SCRIPTED THIS AND I DONT TRUST MYSELF; BETTER DO A BACKUP HIPFILE ####
hou.hipFile.save()
hou.hipFile.saveAndIncrementFileName()


#### MAIN FUNCTION
def BackupAllContent(inputpath):
    # look for all the nodes
    subch = hou.node("/").allSubChildren()
    # start loop for all the nodes
    for child in subch:
        # takes all the parameters of the node
        parms_in_nodes = child.parms()
        # loop over all the parameters
        for parm in parms_in_nodes:
            # IF the parameter is a type string
            if parm.parmTemplate().type() == hou.parmTemplateType.String:
                syc_test = ""
                try:
                    ##nodes with something inside else are skipped
                    syc_test = glob.glob(hou.expandString(re.sub(r'\$F\d?', "*", parm.unexpandedString())))[0]
                except:
                    pass
                raw_texturename = parm.rawValue()
                if os.path.exists(syc_test) and parm.eval() != "*" and raw_texturename.endswith(extensions):
                    ###print parm ## DEBUG parameters and nodes with link to external geo
                    files = glob.glob(hou.expandString(re.sub(r'\$F\d?', "*", parm.unexpandedString())))
                    for fls in files:
                        ### Here there is the full path with also extention for every texture
                        ##print fls ## DEBUG textures full path
                        if os.path.exists(os.path.join(backpath, os.path.basename(fls))):
                            print os.path.basename(fls) + " is already there" ## DEBUG textures name
                        else:
                            shutil.copy(fls,backpath)
                            print os.path.basename(fls) + " has been copied"
                    texturename_variable = raw_texturename[raw_texturename.rfind("/")+1:] 
                    new_texture_path = os.path.join("$HIP/", folder_name, texturename_variable) ## merges this names/directories in one string based on operator system syntax
                    new_texture_path = new_texture_path.replace('\\','/') ##since I am on windows I have to convert \\ to /
                    parm.set(new_texture_path) #set new path
                    
                    
                    print "new texture path would be " + new_texture_path
                    print "raw value is " + parm.rawValue()
                    print "unexpandedString is " + parm.unexpandedString()
                    print "re.sub is " + re.sub(r'\$F\d', "*", parm.unexpandedString())
                    print "expanded string is " + hou.expandString(re.sub(r'\$F\d?', "*", parm.unexpandedString()))
                    print "texturename_variable is " + texturename_variable
                    print "#################################################"


### RUN CODE ##
BackupAllContent(backpath)

01. Create OUT Null

###########################################
########   CREATING AN OUT NULL  ##########
###########################################

selected_nodes = hou.selectedNodes() #return selected node

for node in selected_nodes:
        parent = node.node('..')
        out_null = parent.createNode('null','OUT') ##creates the null and set the name
        out_null.setNextInput(node) ##connect the null to the selected node
        out_null.setPosition(node.position()) ##set the position as the selected node
        out_null.move([0,-1]) ##translate the position by one unit
        out_null.setSelected(True) ##select the null
        node.setSelected(False) ##deselect the parent node

        new_color = hou.Color([1,0.8,0]) ##create the color
        out_null.setColor(new_color) ##set the color

out_null.setDisplayFlag(True)
out_null.setRenderFlag(True)

01. Cut paths

###### ON PYTHON SOP #####

#### Add and don't delete the other lines

​

for prim in geo.prims():

      path = prim.attribValue("path")

      newpath = path[:path.rfind("/")]

      prim.setAttribVAlue("path",newpath)

02. Outgeo cache

##initialize values============================================================
newcolor = hou.Color([1,0,0])
newcolorout = hou.Color([0.2,0.8,0.2])
rangetype = hou.ui.displayMessage('choose framerange', ['current', 'timeline'])

###====== unlock if is filecache=============================================
selnode = hou.selectedNodes()
for node in selnode:
    if node.type().name() == "filecache":
        node.allowEditingOfContents(True)
        #print "unlocked" ##debug line
        nodename = node.name() 
        #print nodename ##debug line
        outrop = hou.node("/out").createNode("geometry",nodename)
        tempval=1
        
    else: tempval=0

if tempval == 0:
    hou.ui.displayMessage("select filecache")
    
###== search filecache child named rop_geometry ==============================
for child in node.children():
    if child.type().name() == "rop_geometry":
        ropgeo = child

###======= settings filecache =================================================

fileref = node.parm("file") ##temporary line until you dont know how setup paths
node.parm("loadfromdisk").set(True)
node.setColor(newcolor)

if rangetype==0:
    node.parm("trange").set('off')
else: node.parm("trange").set('normal')

##==== settings on geometry rop ===============================================
outrop.parm("soppath").set(ropgeo.path())
outrop.parm("sopoutput").set(fileref) ##temporary line until you dont know how setup paths
outrop.setColor(newcolorout)
if rangetype==0:
   outrop.parm("trange").set('off')
else: outrop.parm("trange").set('normal')

print "done"

03. FBX converter, not mine. I've added materials creation

# 256 Pipeline tools
# Convert FBX subnetwork to Geometry node
# Import FBX into Houdini, select FBX subnetwork, run script in Python Source Editor

import hou
# Get selected FBX container and scene root
FBX = hou.selectedNodes()
OBJ = hou.node('/obj/')

def checkConditions():
    '''
    Check if environment conditions allows to run script without errors
    '''
    if not FBX:  # If user select anything
        print '>> Nothing selected! Select FBX subnetwork!'
        return 0

def convert_FBX():
    '''
    Create Geometry node and import all FBX part inside
    '''
    # Create Geometry node to store FBX parts
    geometry = OBJ.createNode('geo', run_init_scripts = False)
    geometry.setName('GEO_{}'.format(FBX.name()))
    geometry.moveToGoodPosition()
    # Get all paerts inside FBX container
    geometry_FBX = [node for node in FBX.children() if node.type().name() == 'geo']
    
    #CREATE MATNET
    #create matnet for placing materials inside
    matnet = geometry.createNode('matnet')
    
    # Create merge node for parts
    merge = geometry.createNode('merge')
    merge.setName('merge_parts')
    
    lst_mat = []
    
    # Replicate FBX structure in Geometry node
    for geo in geometry_FBX: 
        # Create Object Merge node
        objectMerge = geometry.createNode('object_merge')
        objectMerge.setName(geo.name())
        # Set path to FBX part object
        objectMerge.parm('objpath1').set(geo.path())
        objectMerge.parm('xformtype').set(1)
        # Create Material node
        material = geometry.createNode('material')
        material.setName('MAT_{}'.format(geo.name()))
        # Link Material to Object Merge
        material.setNextInput(objectMerge)
        # Link part to Merge
        merge.setNextInput(material)
        
        # SET MATERIALS FROM FBX (MINE)
        #evaluate material on the object
        mat_ref = geo.parm("shop_materialpath").eval()        
        #strip out just name and not "/"
        mat_name = mat_ref[mat_ref.rfind("/")+1:]
        #set TEMPORARY material name
        material.parm("shop_materialpath1").set(mat_name)

    for newnode in geometry.children():
        if newnode.type().name() == "material":
            newmatname = newnode.parm("shop_materialpath1")
            lst_mat.append(newmatname)
            
            #QUI HO CREATE UNA LISTA DEI VARI MATERIALI MA NON FUNZIONA PERCHE CREA ANCHE DOPPIONI
#            if lst_mat.find(newmatname)!=-1:
#                lst_mat.append(newmatname)
    
#    #QUI HO CREATE UNA LISTA DEI VARI MATERIALI MA NON FUNZIONA PERCHE CREA ANCHE DOPPIONI
#    #for shadername in lst_mat:
#        #create materials
#        #shader = matnet.createNode("principledshader::2.0")
#        #set mat name extended inside matnet
#        #mat_name_ext = "../" + matnet.name() +"/"+ mat_name
#        #set material name
#        #material.parm("shop_materialpath1").set(mat_name_ext)
#        

    
    # Set Merge Node flags to Render
    merge.setDisplayFlag(1)
    merge.setRenderFlag(1)
    # Layout geometry content in Nwtwork View 
    geometry.layoutChildren()

# Check if everything is fine and run script
if checkConditions() != 0:
    # Get FBX network
    FBX = FBX[0]
    # run conversion
    convert_FBX()
    print '>> CONVERSION DONE!'

04. as_filecache - callback buttons

In the button callback script. "spawn_outrop" matches the def function in the main script

​

hou.pwd().hm().spawn_outrop(kwargs["node"],kwargs["parm"])

##### CREATING CONTROL NULL ######
##################################
def spawn_control(node, parms):
    import hou
    
    control_name = "CONTROL"
    control_path = "/obj/" + control_name
    creation_place = hou.node("/obj")
    
    ### look if already exists
    if hou.node(control_path)==None:
        null = creation_place.createNode("null",control_name)
        null.moveToGoodPosition()
        
        
        ### HIDE INTERFACE OF NULL
        null_tg = null.parmTemplateGroup()
        null_tg.hideFolder("Transform",True)
        null_tg.hideFolder("Render",True)
        null_tg.hideFolder("Misc",True)
        
        #setting the template
        null.setParmTemplateGroup(null_tg)
    
        ### CREATE PARAMETER #####
        parm_group = null.parmTemplateGroup()
        parm_folder = hou.FolderParmTemplate("folder","CONTROLS")
        parm_folder.addParmTemplate(hou.IntParmTemplate("version","Version",1))
        parm_group.append(parm_folder)
        null.setParmTemplateGroup(parm_group)
    

    control = hou.node(control_path)
    control_version = control.parm("version")
    
    node_version = node.parm("version")
    node.parm("version").set(control_version)


##### SPAWN OUT GEOMETRY ROP #####
##################################

def spawn_outrop(node, parms):
    import hou
        
    ### CREATE OUT GEOMETRY ROP WITH THE SAME NAME
    ### node is not a string yet
    node_name = node.name()
    outrop = hou.node("/out").createNode("geometry", node_name)
    
    ### SET RELATIVE REFERENCES TO CURRENT NODE
    
    ### get current node path
    node_outpath = node.parm("file") ## this is the parameter, not the actual string value
    path_val = node_outpath.unexpandedString() ## unexpanded string of file parameter
    outrop.parm("sopoutput").set(node_outpath) ## set reference path in outrop
    
    ### node soppath
    node_soppath = node.path()
    outrop.parm("soppath").set(node_soppath) ## set path to node
    
    ### frame range
    node_trange = node.parm("trange")
    outrop.parm("trange").set(node_trange) ## set trange in outrop  
    
    

05. Free Cam Script by Johannes Heintz

This is a small python script to free cameras from alembics. It also can be used to scale cameras.

How to use it: Create a new Shelf tool. Set it be a Python script. Select the camera you want to bake/free and execute the shelf tool. You will find the baked camera at /obj/

# Free Cam Script created by Johannes Heintz

def freeCam():

        selected = hou.selectedNodes()[0]

        newCam = hou.node('/obj').createNode('cam', selected.name() + '_baked')

        hou.setFrame(int(hou.playbar.playbackRange()[0]))

        newCam.parmTuple("res").set(selected.parmTuple("res").eval())

        scale = float(hou.ui.readInput("scale", initial_contents="1")[1])

 

for f in range(int(hou.playbar.playbackRange()[0]), int(hou.playbar.playbackRange()[1]) + 1):

        hou.setFrame(f)

        transform = selected.worldTransform()

        explode = transform.explode()

        translate = explode["translate"] * scale

        rotate = explode["rotate"]

        newCam.parm("tx").setKeyframe(hou.Keyframe(translate[0]))

        newCam.parm("ty").setKeyframe(hou.Keyframe(translate[1]))

        newCam.parm("tz").setKeyframe(hou.Keyframe(translate[2]))

        newCam.parm("rx").setKeyframe(hou.Keyframe(rotate[0]))

        newCam.parm("ry").setKeyframe(hou.Keyframe(rotate[1]))

        newCam.parm("rz").setKeyframe(hou.Keyframe(rotate[2]))

        newCam.parm("focal").setKeyframe(hou.Keyframe(selected.parm("focal").eval()))

        newCam.parm("focus").setKeyframe(hou.Keyframe(selected.parm("focus").eval() * scale))

 

freeCam()

05. Slip hipname in sequence and version

Splitting names for projects 

hipname = hou.expandString("$HIPNAME")
name = hipname[:hipname.rfind("_")]
version = hipname[hipname.rfind("v"):]
return name
return version

06. Converting image sequence to mp4 through FFmpeg (simple version)

Need to have FFmpeg installed 

import os
import re
import glob

#######################
####### FUNCTIONS #####
#######################

# find first frame

def getSeqNum(iterFile):
    num = re.findall(r'\d+', iterFile)[-1]
    return num

def getSeqInfoUnderDir(plateDir):
    imageFiles = os.listdir(plateDir)
    imageFiles = sorted(imageFiles, key=getSeqNum)
    
    firstFrame = int(re.findall(r"\d+", imageFiles[0])[-1])
    
    return firstFrame


##############################
#### SELECT IMAGE SEQUENCE ###
##############################
hip_path = hou.getenv("HIP")
default_dir = hip_path + "/../../../../../../"
title_text = "Select Input Image Sequence"
input = hou.ui.selectFile(start_directory = default_dir, title = title_text, collapse_sequences=True) ## select image sequence


## decompose input
dir_path = input[:input.rfind("/")]
root_path = dir_path + "/.."### The .. is for going up one folder
image_name = input.split("/")[-1] ## take image name
image_name = image_name[:image_name.rfind("_")] ## remove $F and extention
video_name = image_name + ".mp4"

output = root_path + "/" + video_name

##### CONVERT STRINGS ####

## convert extention from $F4 to %04d
input = input.replace("$F4","%04d") # replace $F4 first and in case it doesnt exist means that there is $F
input = input.replace("$F","%04d") # replace $F


## find first frame of image sequence
firstFrame = str(getSeqInfoUnderDir(dir_path))

#############################
#### RUN HSCRIPT COMMAND ####
#############################

# ALMOST WORKING #### 
hscript_command = 'unix ffmpeg -f image2 -framerate 24 -start_number ' + firstFrame + ' -i'+ " \"" + input + '\" -vf "scale=iw*2:ih*2" \"' + output +"\"" ## WORKS BUT SCALED DOUBLE SIZE

## basic command with input strings
hou.hscript(hscript_command)

print "-----------------------------------"
print "INPUT FILE     " + input
print "ROOT PATH:     " + dir_path
print "FIRST FRAME    " + firstFrame
print "VIDEO_NAME:    " + video_name
print "OUTPUT PATH:   " + output
print "HSCRIPT LINE   " + hscript_command
print "-----------------------------------"

07. Simple GUI with checkboxes and options for simple operations ( PyQt)

import re, glob, sys
import hou, os, shutil

from PySide2 import QtCore
from PySide2 import QtWidgets
from PySide2 import QtGui
from PySide2 import QtUiTools


class CheckBoxWidget(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        self.setupUi()

    def setupUi(self):
        vbox = QtWidgets.QVBoxLayout(self)
        self.label = QtWidgets.QLabel("Last clicked button: None")
    vbox.addWidget(self.label)

    
    self.cb = cb = QtWidgets.QCheckBox("CheckBox")
    vbox.addWidget(cb)
    #cb.stateChanged.connect(self.TestCheckBox) # connect the checkbox to the TestCheckBox, triggers checking on and off the checkbox

    self.button = button = QtWidgets.QPushButton('BACKUP CONTENT')
    vbox.addWidget(button)

    button.clicked.connect(self.ButtonPushed) ## connect button to  ButtonPushed

    def ButtonPushed(self):
        print("____button pushed______")
    if self.cb.isChecked():
        print("isChecked")
    else:
        print("not Checked")


    def TestCheckBox(self, checked):
        checkBox = self.sender()
        if checked:
            print("Checked")
        else:
            print("Unchecked")

    

dialog = CheckBoxWidget()
dialog.show()

09. Deadline Monitor Script remote control slave

from System.IO import *
from Deadline.Scripting import *

from DeadlineUI.Controls.Scripting.DeadlineScriptDialog import DeadlineScriptDialog

scriptDialog = None

def __main__():
    global scriptDialog
    
    scriptDialog = DeadlineScriptDialog()
    scriptDialog.SetSize( 450, 68 )
    scriptDialog.AllowResizingDialog( False )
    scriptDialog.SetTitle( "Start Slave" )
    
    scriptDialog.AddGrid()
    scriptDialog.AddHorizontalSpacerToGrid( "DummyLabel1", 0, 0 )
    startButton = scriptDialog.AddControlToGrid( "StartButton", "ButtonControl", "Start", 0, 1, expand=False )
    startButton.ValueModified.connect(StartButtonPressed)
    closeButton = scriptDialog.AddControlToGrid( "CloseButton", "ButtonControl", "Close", 0, 3, expand=False )
    closeButton.ValueModified.connect(CloseButtonPressed)
    scriptDialog.EndGrid()
    
    scriptDialog.ShowDialog( True )

def StartButtonPressed( *args ):
    global scriptDialog
    
    selectedSlaveInfoSettings = MonitorUtils.GetSelectedSlaveInfoSettings()
    
    # Get the list of selected machine names from the slave info settings.
    machineNames = SlaveUtils.GetMachineNameOrIPAddresses(selectedSlaveInfoSettings)
    for machineName in machineNames:
    SlaveUtils.SendRemoteCommand( machineName, "LaunchSlave")
    scriptDialog.CloseDialog()
            

def CloseButtonPressed( *args ):
    global scriptDialog
    scriptDialog.CloseDialog()

10. Convert Camera from Imported alembic to /obj level

import hou
import os

cameras = hou.nodeType(hou.objNodeTypeCategory(),"cam").instances()
selnode = hou.selectedNodes()[0]
selnode_path = selnode.path()
path_obj = "/obj/"

def convertCam():
    for cam in cameras:
   cam_name = cam.name()
   parent = cam.parent()
   parent_name = parent.name()
   
   if(selnode_path in cam.path()):
       print "cam name is: " + cam_name
       print "parent name is: " + parent_name

        new_cam = hou.node(path_obj).createNode("cam", parent_name + "_" + cam_name)
       new_cam.moveToGoodPosition()
       
       #copy some parameters from original cam
       parm_list = ["focal", "aperture", "resx", "resy"]
       for parameter in parm_list:
           read_parm = cam.parm(parameter).eval()
           new_cam.parm(parameter).set(read_parm)
           print parameter
       
       #create string on top for camer path
       #parm_group = new_cam.parmTemplateGroup()
       #parm_folder = hou.FolderParmTemplate("folder","Reference Cam")
       #reference_cam = parm_folder.addParmTemplate(hou.StringParmTemplate("reference_cam","reference_cam",1))
       #parm_group.append(parm_folder)
       #new_cam.setParmTemplateGroup(parm_group)
       
       cam_path = cam.path()
       refcam_parm = hou.StringParmTemplate("reference_cam", "reference_cam", 1)
       grp = new_cam.parmTemplateGroup()
       grp.insertBefore("stdswitcher3", refcam_parm)
       new_cam.setParmTemplateGroup(grp)
       new_cam.parm("reference_cam").set(cam_path)

        new_cam.parm("tx").setExpression('vtorigin("",chs("reference_cam"))[0]')
       new_cam.parm("ty").setExpression('vtorigin("",chs("reference_cam"))[1]')
       new_cam.parm("tz").setExpression('vtorigin("",chs("reference_cam"))[2]')
       new_cam.parm("rx").setExpression('vrorigin("",chs("reference_cam"))[0]')
       new_cam.parm("ry").setExpression('vrorigin("",chs("reference_cam"))[1]')
       new_cam.parm("rz").setExpression('vrorigin("",chs("reference_cam"))[2]')

bottom of page