====== 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