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]')