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:
- 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
- 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)