====== Houdini Shortcut ======
| Node View ||
| show node view mini map | O |
| show node property corner panel | P |
| layout selected nicely | shift + L |
| align selected | hold A and drag align direction |
| duplicate selected | alt+drag selected |
| cut connection | hold Y and drag line to cut |
| netbox nodes (like node group in nuke) | shift+O |
| subnet nodes \\ (like a tiny world \\ with inputs+outputs to outside) | shift+C |
| node color palette | C |
| create note | shift+P |
| node group manager (like display layer in maya) | shift+Z |
| move connection line | alt+click drag line middle |
* object merge node: like object teleporter for far links
* ref:
* 5 Ways to Organize your Network in Houdini 17.5 (IndiePixel3D) https://www.youtube.com/watch?v=w8yBoO-qvsk
====== Houdini Pipeline Development ======
* Python 3 and built-in PySide2
* houdini has its built-in qt library (PySide2), just import its own
from hutil.Qt import QtCore, QtGui, QtWidgets
* houdini lic type check
import hou
lic = hou.licenseCategory()
if lic == hou.licenseCategoryType.Apprentice or lic == hou.licenseCategoryType.ApprenticeHD or lic == hou.licenseCategoryType.Education:
self.memoData['hda_ext'] = 'hdanc'
elif lic == hou.licenseCategoryType.Indie:
self.memoData['hda_ext'] = 'hdalc'
* core hou class
* hou.HDADefinition (https://www.sidefx.com/docs/houdini/hom/hou/HDADefinition.html)
cur_def.libraryFilePath() # get file path
cur_def.version()
cur_def.description()
cur_def.comment() # user
# set
cur_def.setVersion('2.1')
cur_def.setDescription(description)
cur_def.setComment('update details') # user
# --- check
node.type().definition() is None # check if current node is hda
node.matchesCurrentDefinition() # check whether the contents of the node are locked to its type definition
# --- action
node.allowEditingOfContents() # unlock node (Allow Editing of Contents)
node.type().definition().updateFromNode(node) # save the contents of an unlocked node to the definition (Save Operator Type)
node.matchCurrentDefinition() # reload defition (Match Current Definition )
* detect selected hda node info
import hou
if len(hou.selectedNodes()) == 1:
node = hou.selectedNodes()[0]
if node.type().definition() is not None:
hda_file_path = node.type().definition().libraryFilePath()
print(hda_file_path)
definitions = hou.hda.definitionsInFile(hda_file_path)
print(definitions)
for d in definitions:
print(str(node) + " name: " + node.type().name() )
print(str(node) + " version: " + d.version())
print(str(node) + " description: " + d.description())
print(str(node) + " comment: " + d.comment())
print(str(node) + " path: " + node.type().definition().libraryFilePath())
else:
print("Selected node is not a HDA")
else:
print("Please select 1 hda node")
====== HDA Operation (Houdini Digital Asset) ======
* HDA = houdini digital asset
* a set of node network functions as a core process
* like model asset but instead of geo, it can be anything, like shading network, rig network or anything process.
* once you add hda path into your houdini env, you can load it from tab menu like rest built-in hda asset (like nuke gizmo)
* each hda is like a programming class, it has its unique type name like class name, all node of its class are instance of the node definition (its original content network)
* once a instance of hda in the scene, it is like maya model reference, you can unlock and make additional changes (**Allow editing of contents**),
* you can save the changes back into hda files (**Save node type**), or just keep changes in the scene
* to revert back to original state, just select node, use (**Match current definition** cmd)
* hda file can contain many hda assets, but recommed one asset for one hda file
* hda asset can be inside houdini scene file, as Embedded instead of on a path file
* nested hda need to be manually updated inside
* ref:
* https://www.tokeru.com/cgwiki/index.php?title=HoudiniHDA
* VHDA = versioned houdini digital asset
* ref: create your own videos https://vimeo.com/458751335
* Houdini default menu source code: Houdini_VERSION\houdini\OPmenu.xml
* related hda menu code
#menu id: opmenu.create_hda
kwargs["node"].canCreateDigitalAsset()
#menu id: opmenu.vhda_options_create
import hou
import assettools
node = kwargs["node"]
if assettools.isSubnet(node):
return 1
else:
return 0
#menu id: opmenu.vhda_create
import hou
import assettools
node = kwargs["node"]
assettools.createNewVHDAFromSubnet(node)
* Houdini python API library path: Houdini_VERSION\houdini\python3.7libs\
#hda related files
assettools.py
C:\Program Files\Side Effects Software\Houdini X\houdini\python3.7libs\assettools.py
* node > definition > all info related
===== Simple hda operation =====
* lock hda node result_node.matchCurrentDefinition()
* unlock hda node (same like allow edit of content)
if result_node.isLockedHDA():
result_node.allowEditingOfContents()
* open type property dialog of a hda node hou.ui.openTypePropertiesDialog(result_node)
===== Complex hda operation =====
* node info
# node
if len(hou.selectedNodes()) == 1:
node = hou.selectedNodes()[0]
# node info
print(str(node) + " name: " + node.type().name() )
print(str(node) + " locked: " + node.isLockedHDA() )
* definition
# definition
if node.type().definition() is not None:
hda_def = node.type().definition()
# definition info
print(str(node) + " path: " + hda_def.libraryFilePath() )
print(str(node) + " version: " + hda_def.version())
print(str(node) + " description: " + hda_def.description())
print(str(node) + " comment: " + hda_def.comment())
# definition operation
cur_def.isInstalled()
cur_def.save(tmp_filepath, template_node=node)
* hda file
# hda file
hda_file_path = hda_def.libraryFilePath()
definition_list = hou.hda.definitionsInFile(hda_file_path)
# check hda file list loaded in memory
loaded_hda_file_list= hou.hda.loadedFiles()
print('\n'.join(loaded_hda_file_list))
# hda file load/unload
hou.hda.installFile(hda_file_path)
hou.hda.uninstallFile(hda_file_path)
# hda file reload
hou.hda.reloadAllFiles(True)
* check and remove HDA
# hda_file_path: the path of hda file
cur_path = hda_file_path
if os.path.isfile(cur_path):
# uninstall for safe
definitions = hou.hda.definitionsInFile(cur_path)
is_installed = 0
for d in definitions:
if d.isInstalled():
is_installed = 1
if is_installed:
# Uninstall the HDA
hou.hda.uninstallFile(cur_path)
# remove action
# os.remove(cur_path)
How create new ... HDA works?
- menu calls
import hou
import assettools
node = kwargs["node"] # it get node from menu click action
assettools.createNewVHDAFromSubnet(node)
- assettools.py has function createNewVHDAFromSubnet(), it basically create the dialog
- dialog VHDASaveAsDialog() class show up, the Create button do the node publish
- setMembersFromUI
- namespace_author, namespace_branch, major_version, minor_version = self.getCorrectNamespacesAndVersion()
- library_file = self.getCorrectLibraryFile()
- tablabel = constructVHDALabel(self.tablabel, self.namespace_branch if self.display_branch_in_label_enable else None)
- library_file_path = os.path.join(hou.text.expandString(self.library_dir), library_file)
- vhda_typename = constructVHDATypeName(namespace_author, namespace_branch, self.type_name, major_version, minor_version)
- continue: alert deals with lib path file contains multi- definition
- new asset
createVHDA(node,
namespace_author,
namespace_branch,
self.type_name,
major_version,
minor_version,
tablabel,
self.tabmenu_entry,
self.library_dir,
library_file)
- version up case (a versioned copy of an existing):
copyToVHDAFile(node,
namespace_author,
namespace_branch,
self.type_name,
major_version,
minor_version,
tablabel,
self.tabmenu_entry,
self.library_dir,
library_file)
- details of VHDA function (from assettolls.py)
def createVHDA(node, namespace_author, namespace_branch, name, major, minor, label, tabmenu, savedir, savefile=None):
hda_name = constructVHDATypeName(namespace_author, namespace_branch,name,major,minor)
hda_label = label
hda_filename = savefile if savefile else constructVHDAFileName(namespace_author, namespace_branch,name,major,minor)
hda_savedir = savedir
hda_filepath = os.path.join(hda_savedir, hda_filename) if hda_savedir != "Embedded" else "Embedded"
max_num_inputs = 0
# If there are inputs to the node, find the largest index of the input connections and use it as the max_num_inputs
# This will preserve the inputs at the right indexes
if len(node.inputs()) > 0:
for connection in node.inputConnections():
max_num_inputs = max(max_num_inputs,connection.inputIndex())
max_num_inputs = max_num_inputs + 1
vhda_node = node.createDigitalAsset(
name = hda_name,
hda_file_name = hda_filepath,
description = hda_label,
min_num_inputs = 0,
max_num_inputs = max_num_inputs,
save_as_embedded = hda_savedir == "Embedded",
ignore_external_references=True,
)
vhda_node.setName(name, unique_name=True)
vhda_def = vhda_node.type().definition()
# Update and save new HDA
vhda_options = vhda_def.options()
vhda_options.setSaveInitialParmsAndContents(True)
#vhda_options.setSaveSpareParms(True) ####### TO-DO
vhda_def.setOptions(vhda_options)
'''
setVHDASection(vhda_def, False if namespace_author == "" else True,
False if namespace_branch == "" else True)
'''
setToolSubmenu(vhda_def, tabmenu)
vhda_def.save(hda_filepath, vhda_node, vhda_options)
hou.hda.installFile(hda_filepath)
hou.hda.reloadAllFiles(True)
- copyToVHDAFile
def copyToVHDAFile(node, namespace_author, namespace_branch, name, major, minor, label, tabmenu, savedir, savefile=None):
hda_name = constructVHDATypeName(namespace_author, namespace_branch,name,major,minor)
hda_label = label
hda_filename = savefile if savefile else constructVHDAFileName(namespace_author, namespace_branch,name,major,minor)
hda_savedir = savedir
hda_filepath = os.path.join(hda_savedir, hda_filename) if hda_savedir != "Embedded" else "Embedded"
tmp_filepath = os.path.join(hou.text.expandString("$HOUDINI_TEMP_DIR"),"tmphda.hda")
# Update and save new HDA
vhda_def = node.type().definition()
# vhda_options = vhda_def.options()
# vhda_options.setSaveInitialParmsAndContents(True)
# vhda_options.setSaveSpareParms(True) ####### TO-DO
vhda_def.save(tmp_filepath, template_node=node)
created_definition = hou.hda.definitionsInFile(tmp_filepath)[0]
# Sets the tabmenu location only if it differs from the original
if tabmenu:
setToolSubmenu(created_definition, tabmenu)
created_definition.copyToHDAFile(hda_filepath,hda_name,hda_label)
os.remove(tmp_filepath)
hou.hda.installFile(hda_filepath)
hou.hda.reloadAllFiles(True)
node.changeNodeType(hda_name)
====== Tutorial ======
* [Houdini official] Houdini Digital Assets | 1. Introduction to HDAs (2021)
* https://www.youtube.com/watch?v=ybk8XWBnO4o