graphic:python:houdini

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

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
  • 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
  • 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)
  • 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?

  1. menu calls
    import hou
    import assettools
     
    node = kwargs["node"] # it get node from menu click action
    assettools.createNewVHDAFromSubnet(node)
  2. assettools.py has function createNewVHDAFromSubnet(), it basically create the dialog
  3. dialog VHDASaveAsDialog() class show up, the Create button do the node publish
    1. setMembersFromUI
    2. namespace_author, namespace_branch, major_version, minor_version = self.getCorrectNamespacesAndVersion()
    3. library_file = self.getCorrectLibraryFile()
    4. tablabel = constructVHDALabel(self.tablabel, self.namespace_branch if self.display_branch_in_label_enable else None)
    5. library_file_path = os.path.join(hou.text.expandString(self.library_dir), library_file)
    6. vhda_typename = constructVHDATypeName(namespace_author, namespace_branch, self.type_name, major_version, minor_version)
    7. continue: alert deals with lib path file contains multi- definition
    8. new asset
      createVHDA(node,
         namespace_author,
         namespace_branch,
         self.type_name,
         major_version,
         minor_version,
         tablabel,
         self.tabmenu_entry,
         self.library_dir,
         library_file)
    9. 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)
  4. 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)
     
  5. 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

  • graphic/python/houdini.txt
  • Last modified: 2024/03/22 06:37
  • by ying