Houdini Environment Setup
Overview
Houdini environment setup for you and your team so that you can deliver tools and integrate scripts easily. Automate the setup in a team setting and add callbacks to manage scene preferences and source control.
What is the Houdini environment?
Houdini has specific environment variables that defines where it looks for scripts and resources.
Add your own paths to these variables to store scripts and resources wherever we want.
Houdini environment scripts
Historically, the most common way to modify the Houdini environment is through a file in the preference directory, namely houdini.env. Houdini evaluates this file during boot and thus presents an opportunity to inject paths for our tool environment. The problem is that this file gets extremely messy when everything uses this file to bootstrap.
“What if I want to organize my add ons so I can easily turn things on and off? ”
“As a tech artist, I don’t want to deal with pre existing configuration information when I modify the end user’s Houdini environment.”
Houdini Packages
Luckily there is a new alternative and a better way – namely packages and as of Houdini 17.5.467 and Houdini 18.0.315 the packages functionality can also daisy chain. In my mind, SideFx has achieved nirvana of environment modification.
A Houdini package is a .json file that exists inside a “packages” directory to modify the Houdini environment. The package syntax supports relative paths, os environment variables, version and os checks, so you can get very specific and complex with your packages. Read more about the specifics in the SideFx documentation on this.
Create a .json file in a packages directory in the Houdini preference directory.
In the package file, specify a path (HOUDINI_PATH) to a “Houdini site” folder. To the right you see the structure of the site folder I use.
The sub folder names “otls”, “presets”, “python_panels”, “python2.7libs”, “scripts”, “toolbar”, and “packages” are some commonly key-worded folders that automatically gets added to the Houdini environment during startup as a result of adding the path in the package.
Manually implementing a package
First, go to the Houdini user preference directory and create a folder called “packages”.
Next, create a .json file inside the packages folder you just created (I name mine LCG.json). Put the content below in the .json file, and change the paths how you see fit.
1 2 3 4 |
{ "path" : "C:/Framework/LCG/houdini" "package_path" : "C:/Framework/LCG/houdini/packages" } |
Here is the really cool thing. If you are part of a team, using source control to distribute tools and assets, the data we put in the end users preference folder is extremely minimal and never really have to change.
We add a path (adding to the “HOUDINI_PATH” variable) to our distributed Houdini site and trigger the site’s packages via the package_path variable. We add complexity and additions to those packages as things need to change and distribute them via source control. The end user takes no action to receive these updates – their environment will just update.
Verifying the Houdini environment
Start up Houdini, copy and paste the code below into the python shell and run it to verify that the paths have been added to your environment.
1 2 |
print(hou.getenv('HOUDINI_PATH')) # C:/Framework/LCG/houdini;& |
Thanks to Houdini packages, we append, prepend or replace the Houdini environment without dealing with houdini.env. Create as many packages as you want. One per project/team/game/freelance job – the sky is the limit.
Now let’s talk about the things we can add “for free” to the environment at this point. By just creating these key worded folders in the HOUDINI_PATH, we add certain types resources to the environment. These are some of the most useful I have found so far….
“I want to automate the creation of this setup for my team(s)? ”
“I want the Houdini site and my tools stored in any random location for each member of the team.
Automate install & uninstall with Houdini Digital Assets
Set this up is via a Houdini Digital Asset (HDA) – an extremely capable and versatile technology that is a core part of Houdini and the future is being written on it’s shoulders. It is flexible, easy to maintain and simple to implement.
Think of an HDA as a tool container. If you come from Maya, you can also think of it as a reference because anything that you put into an HDA is in a way referenced into every scene that you add this HDA node into. Change the HDA – all the instances of this node will update. Another key feature of the HDA that we will use for our purposes here, is the “on create” callback. It allows us to execute a Python script when the node is created.
Create the install.hda
First, create the digital asset that will act as our installer.
- In obj context, create a null.
- With the null selected, click the Create Subnet button
- Right click on the newly created subnet and select Create Digital Asset…
- In the Creation Dialog for Operator Name, put your digital assets into a unique namespace (lcg), followed by nodeName, and version number. The Label is the human readable label. Save this particular digital asset outside of the otl folder, because in the end, the user browse to this digital asset to install our framework.
-
Dive inside of the digital asset and delete the null. We only need the shell of the HDA for the installer.
Basic Properties
Select some cool icon – I choose the plug because it seems fitting to what this HDA will do.
Parameters Properties
Create 2 string parameters. Name them “houdini_path” & “package_path”. These will show the user, the paths we added. For info display, I like to disable the parameters so they will not be able to be edited by the end user – we make them read only.
We do this by writing an expression on the “Disable When” – we use something that will always be true – { 0 < 1 }
Scripts Properties
Go to the scripts area and on the “Event Handler” drop down, select “Python Module” and “On Created”. This creates 2 internal Python modules to the HDA (make sure that the “Edit as” drop down is set to Python for the On Created callback module). Check the Python snippets below as far as what code goes into each module.
1 2 3 4 5 |
# kwargs is the dictionary that carries all useful information that is being executed by the event. # kwargs["node"] is the hda node itself # .hdaModule() is the internal "python module" that we created. # We will call an on_created function and pass the node. kwargs["node"].hdaModule().on_created(kwargs["node"]) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
import os import sys import hou import PySide2.QtWidgets def on_created(node): # Set default node name, action (for pop up window) color, and shape node_name = 'LCG_Installed' action = 'Install' installed_node_color = hou.Color((1.0, 0.982754, 0.299337)) updated_node_color = hou.Color((1.0, 0.279376, 0.0)) node.setColor(installed_node_color) node.setUserData('nodeshape', 'light') # This is the path to this HDA filepath = node.type().definition().libraryFilePath() # From that we know where the houdini root folder and the package folder we want to add is houdini_path = '{lcg_root}/houdini'.format(lcg_root=filepath.split('/Setup/')[0]) package_path = '{houdini_path}/packages'.format(houdini_path=houdini_path) # Get the json template from the Extra Files section package_content = node.type().definition().sections()['PACKAGE_TEMPLATE'].contents() package_content = package_content.replace('@___HOUDINI_PATH___@', houdini_path) package_content = package_content.replace('@___PACKAGE_PATH___@', package_path) # Set this HDA's string parms to show what the additions will be node.parm('houdini_path').set(houdini_path) node.parm('package_path').set(package_path) # Get the user prefs package directory and create the directory if it does not exist package_directory = '{user_pref_dir}/packages'.format(user_pref_dir=hou.getenv('HOUDINI_USER_PREF_DIR')) if not os.path.exists(package_directory): os.makedirs(package_directory) # Build the path to the package file. If the package file already exists set the action and color to updated module_filepath = '{package_directory}/LCG.json'.format(package_directory=package_directory) if os.path.exists(module_filepath): node_name = 'LCG_Updated' action = 'Update' node.setColor(updated_node_color) # Write the package file with open(module_filepath, 'w') as module_file: module_file.write(package_content) # Get the message template from the Extra Files section message = node.type().definition().sections()['SUCCESS_MESSAGE'].contents() message = message.replace('@___HOUDINI_PATH___@', houdini_path) message = message.replace('@___PACKAGE_PATH___@', package_path) message = message.replace('@__NODE_NAME__@', node_name) # Set the node name node.setName(node_name) # Show message window PySide2.QtWidgets.QMessageBox.information(hou.qt.mainWindow(), '{action} Successful'.format(action=action), message) |
Extra files properties
We get the package json code from the “Extra Files” section of the HDA as well as the formatted success message for the pop up window.
Go to the “Extra Files” tab -> type “PACKAGE_TEMPLATE” in the “Section Name” field and click the “Add Empty Section” button. Repeat this process for a SUCCESS_MESSAGE section.
1 2 3 4 |
{ "path" : "@___HOUDINI_PATH___@", "package_path" : "@___PACKAGE_PATH___@" } |
The SUCCESS_MESSAGE section looks like the below. Since this can contain HTML tags, it was practically impossible to show it in text form on this web site 🙂
Execution flow of the hda
When the node is created, the “on_created” Python function will trigger. The function creates the package file (and folder if it does not exist) and messages what happened to the user.
Since the script queries where it is being run from, put your tool framework anywhere and this will “just work” since the user will browse to this HDA.
In a Houdini session, click file->Import Houdini digital asset…
Super cool tip – double click the Installer HDA outside a Houdini session, and the latest version of Houdini will automatically launch and create the node. We figure out the root path, where the user is running our framework from and build the path to our Houdini folder.
This is actually all we need to get our entire frame work up and running, but we need to drop in a key script to really get it to sing.
Framework Structure
For our example, our framework structure looks like the below image.
- Houdini site that we have added via the package mechanism.
- Python script that will be automatically run when Houdini starts up. Add our framework’s python root to the system path so that we can import and run our own python code.
- Another key worded folder and Python script. This script will trigger before the scene is saved. Perfect opportunity to add logic and handle automatic source control check outs of your hip files.
- Python root. Notice, the other blue source folder called lib. We will also add it to the sys.path – contains third party modules and packages.
- Location to save our installer.hda file.
Below you will find the Python code that is in pythonrc.py. This is the python file that runs when Houdini starts up. Find all the other key worded scripts you can drop in to make things happen post save or when Houdini opens a scene. SideFx documentation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import os import sys import inspect import hou print('pythonrc.py triggered') tool_root = os.path.dirname(inspect.getfile(inspect.currentframe()).replace('\\', '/').replace('/python2.7libs/pythonrc.pyc', '').replace('/python2.7libs/pythonrc.py', '')) python_root = '%s/python' % tool_root if python_root not in sys.path: sys.path.append(python_root) lib_root = '%s/lcg/lib' % python_root if lib_root not in sys.path: sys.path.append(lib_root) houdini_program_directory = hou.getenv('HFS') pyside2_root = '%s/python27/lib/site-packages-ui-forced/PySide2' % houdini_program_directory.replace('\\', '/') if pyside2_root not in sys.path: sys.path.append(pyside2_root) # hou.putenv('RANDOM_VARIABLE_NAME', 'I love Houdini') # hou.allowEnvironmentToOverwriteVariable("JOB", True) # os.environ["JOB"] = someJobPath # hou.putenv("JOB",os.environ["JOB"]) |
Uninstalling
Create a copy of the install hda and make the name changes shown below. To bring up the initial window, right click on the install HDA and choose -> Show in asset manager. Then right click -> Copy…
The callback on_create python function is shown below and I tweaked the success message to fit the action of uninstalling.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import os import hou import PySide2.QtWidgets def on_created(node): # Set default node name, action (for pop up window) color, and shape node_name = 'LCG_Uninstalled' action = 'Uninstall' uninstalled_node_color = hou.Color((1.0, 0, 0)) node.setColor(uninstalled_node_color) node.setUserData('nodeshape', 'light') # Get the user prefs package directory and create the directory if it does not exist package_directory = '{user_pref_dir}/packages'.format(user_pref_dir=hou.getenv('HOUDINI_USER_PREF_DIR')) if not os.path.exists(package_directory): return # Build the path to the package file. If the package file already exists set the action and color to updated package_filepath = '{package_directory}/LCG.json'.format(package_directory=package_directory) if os.path.exists(package_filepath): os.remove(package_filepath) # Get the message template from the Extra Files section message = node.type().definition().sections()['SUCCESS_MESSAGE'].contents() # Set the node name node.setName(node_name) # Show message window PySide2.QtWidgets.QMessageBox.information( hou.qt.mainWindow(), '{action} Successful'.format(action=action), message ) |
Steps to installing / uninstalling
Conclusion
This approach is simple to implement and very easy to maintain. Since we are automatically calling all the packages inside our Houdini site packages directory, it is very simple to add in new hda directories (OTL_SCAN_PATH) whenever we need to.
I hope this article is useful to you. If it is, please drop me a comment below. Feel free to share your own experience and how you go about dealing with the Houdini environment setup.
Cheers,