diff --git a/client/ayon_houdini/plugins/create/create_hda.py b/client/ayon_houdini/plugins/create/create_hda.py index 7613735f7b..c7ee04b0e6 100644 --- a/client/ayon_houdini/plugins/create/create_hda.py +++ b/client/ayon_houdini/plugins/create/create_hda.py @@ -3,14 +3,8 @@ import hou import ayon_api -from ayon_core.pipeline import ( - CreatorError, - get_current_project_name -) -from ayon_core.lib import ( - get_ayon_username, - BoolDef -) +from ayon_core.pipeline import CreatorError, get_current_project_name +from ayon_core.lib import get_ayon_username, BoolDef from ayon_houdini.api import plugin @@ -31,15 +25,16 @@ def get_tool_submenus(hda_def): """ import xml.etree.ElementTree as ET - if hda_def.hasSection('Tools.shelf'): + + if hda_def.hasSection("Tools.shelf"): sections = hda_def.sections() - ts_section = sections['Tools.shelf'].contents() + ts_section = sections["Tools.shelf"].contents() try: root = ET.fromstring(ts_section) except ET.ParseError: return None tool = root[0] - submenus = tool.findall('toolSubmenu') + submenus = tool.findall("toolSubmenu") if submenus: tool_submenus = [] for submenu in submenus: @@ -57,8 +52,7 @@ def get_tool_submenus(hda_def): return None -def set_tool_submenu(hda_def, - new_submenu='Digital Assets'): +def set_tool_submenu(hda_def, new_submenu="Digital Assets"): """Sets the tab menu entry for a node. Arguments: @@ -68,34 +62,36 @@ def set_tool_submenu(hda_def, """ context_dict = { - 'Shop': 'SHOP', - 'Cop2': 'COP2', - 'Object': 'OBJ', - 'Chop': 'CHOP', - 'Sop': 'SOP', - 'Vop': 'VOP', - 'VopNet': 'VOPNET', - 'Driver': 'ROP', - 'TOP': 'TOP', - 'Top': 'TOP', - 'Lop': 'LOP', - 'Dop': 'DOP'} + "Shop": "SHOP", + "Cop2": "COP2", + "Object": "OBJ", + "Chop": "CHOP", + "Sop": "SOP", + "Vop": "VOP", + "VopNet": "VOPNET", + "Driver": "ROP", + "TOP": "TOP", + "Top": "TOP", + "Lop": "LOP", + "Dop": "DOP", + } utils_dict = { - 'Shop': 'shoptoolutils', - 'Cop2': 'cop2toolutils', - 'Object': 'objecttoolutils', - 'Chop': 'choptoolutils', - 'Sop': 'soptoolutils', - 'Vop': 'voptoolutils', - 'VopNet': 'vopnettoolutils', - 'Driver': 'drivertoolutils', - 'TOP': 'toptoolutils', - 'Top': 'toptoolutils', - 'Lop': 'loptoolutils', - 'Dop': 'doptoolutils'} - - if hda_def.hasSection('Tools.shelf'): + "Shop": "shoptoolutils", + "Cop2": "cop2toolutils", + "Object": "objecttoolutils", + "Chop": "choptoolutils", + "Sop": "soptoolutils", + "Vop": "voptoolutils", + "VopNet": "vopnettoolutils", + "Driver": "drivertoolutils", + "TOP": "toptoolutils", + "Top": "toptoolutils", + "Lop": "loptoolutils", + "Dop": "doptoolutils", + } + + if hda_def.hasSection("Tools.shelf"): old_submenu = get_tool_submenus(hda_def)[0] else: # Add default tools shelf section @@ -118,26 +114,29 @@ def set_tool_submenu(hda_def, """ - + nodetype_category_name = hda_def.nodeType().category().name() context = context_dict[nodetype_category_name] util = utils_dict[nodetype_category_name] content = content.replace( "SOP", - f"{context}") - content = content.replace('soptoolutils', util) - hda_def.addSection('Tools.shelf', content) - old_submenu = 'Digital Assets' + f"{context}", + ) + content = content.replace("soptoolutils", util) + hda_def.addSection("Tools.shelf", content) + old_submenu = "Digital Assets" # Replace submenu tools = hda_def.sections()["Tools.shelf"] content = tools.contents() content = content.replace( f"{old_submenu}", - f"{new_submenu}" + f"{new_submenu}", ) - hda_def.addSection('Tools.shelf', content) + hda_def.addSection("Tools.shelf", content) + + # endregion @@ -162,18 +161,12 @@ def _check_existing(self, folder_path, product_name): project_name, folder_ids={folder_entity["id"]}, fields={"name"} ) existing_product_names_low = { - product_entity["name"].lower() - for product_entity in product_entities + product_entity["name"].lower() for product_entity in product_entities } return product_name.lower() in existing_product_names_low def create_instance_node( - self, - folder_path, - node_name, - parent, - node_type="geometry", - pre_create_data=None + self, folder_path, node_name, parent, node_type="geometry", pre_create_data=None ): if pre_create_data is None: pre_create_data = {} @@ -187,8 +180,8 @@ def create_instance_node( else: parent_node = self.selected_nodes[0].parent() subnet = parent_node.collapseIntoSubnet( - self.selected_nodes, - subnet_name="{}_subnet".format(node_name)) + self.selected_nodes, subnet_name="{}_subnet".format(node_name) + ) subnet.moveToGoodPosition() to_hda = subnet else: @@ -201,34 +194,66 @@ def create_instance_node( parent_node = pane.pwd() to_hda = parent_node.createNode( - "subnet", node_name="{}_subnet".format(node_name)) + "subnet", node_name="{}_subnet".format(node_name) + ) + if not to_hda.type().definition(): - # if node type has not its definition, it is not user - # created hda. We test if hda can be created from the node. + # If the node's type lacks a definition, it isn't an user-created HDA. + # We test whether an HDA can be generated from this node. if not to_hda.canCreateDigitalAsset(): - raise CreatorError( - "cannot create hda from node {}".format(to_hda)) + raise CreatorError("cannot create hda from node {}".format(to_hda)) # Pick a unique type name for HDA product per folder path per project. - type_name = ( - "{project_name}{folder_path}_{node_name}".format( - project_name=get_current_project_name(), - folder_path=folder_path.replace("/","_"), - node_name=node_name - ) + type_name = "{project_name}{folder_path}_{node_name}".format( + project_name=get_current_project_name(), + folder_path=folder_path.replace("/", "_"), + node_name=node_name, ) + source_parm_template_group = to_hda.parmTemplateGroup() + + # Identify all distinct output nodes within the initial layer of the chosen subnet + # to determine the total number of outputs required for the HDA. + output_indexes = set() + for node in [ + node for node in to_hda.children() if node.type().name() == "output" + ]: + output_index = node.parm("outputidx").eval() + output_indexes.add(output_index) + + # Find all inputs within the chosen subnet connected to anything to determine + # the input count required for the HDA. + subnet_inputs_with_connections = set() + for subnet_input in to_hda.indirectInputs(): + if subnet_input.outputs(): + subnet_inputs_with_connections.add(subnet_input) + hda_node = to_hda.createDigitalAsset( name=type_name, description=node_name, hda_file_name="$HIP/{}.hda".format(node_name), - ignore_external_references=True + ignore_external_references=True, + min_num_inputs=0, + max_num_inputs=max(len(subnet_inputs_with_connections), 1), ) + + # add the parm template that we got from the source Subnet into the hda type definition + hda_def = hda_node.type().definition() + hda_def.setMaxNumOutputs(max(len(output_indexes), 1)) + + hda_parm_template_group = hda_def.parmTemplateGroup() + + for parm_template in source_parm_template_group.entries(): + if not hda_parm_template_group.find(parm_template.name()): + hda_parm_template_group.addParmTemplate(parm_template) + + hda_def.setParmTemplateGroup(hda_parm_template_group) + hda_node.layoutChildren() elif self._check_existing(folder_path, node_name): raise CreatorError( - ("product {} is already published with different HDA" - "definition.").format(node_name)) + f"product {node_name} is already published with different HDA definition." + ) else: hda_node = to_hda @@ -263,52 +288,45 @@ def get_network_categories(self): hou.objNodeTypeCategory(), hou.sopNodeTypeCategory(), hou.topNodeTypeCategory(), - hou.vopNodeTypeCategory() + hou.vopNodeTypeCategory(), ] def get_pre_create_attr_defs(self): attrs = super(CreateHDA, self).get_pre_create_attr_defs() return attrs + [ - BoolDef("set_user", - tooltip="Set current user as the author of the HDA", - default=False, - label="Set Current User"), - BoolDef("use_project", - tooltip="Use project name as tab submenu path.\n" - "The location in TAB Menu will be\n" - "'AYON/project_name/your_HDA_name'", - default=True, - label="Use Project as menu entry"), + BoolDef( + "set_user", + tooltip="Set current user as the author of the HDA", + default=False, + label="Set Current User", + ), + BoolDef( + "use_project", + tooltip="Use project name as tab submenu path.\n" + "The location in TAB Menu will be\n" + "'AYON/project_name/your_HDA_name'", + default=True, + label="Use Project as menu entry", + ), ] def get_dynamic_data( - self, - project_name, - folder_entity, - task_entity, - variant, - host_name, - instance + self, project_name, folder_entity, task_entity, variant, host_name, instance ): """ Pass product name from product name templates as dynamic data. """ dynamic_data = super(CreateHDA, self).get_dynamic_data( - project_name, - folder_entity, - task_entity, - variant, - host_name, - instance + project_name, folder_entity, task_entity, variant, host_name, instance ) dynamic_data.update( { "asset": folder_entity["name"], "folder": { - "label": folder_entity["label"], - "name": folder_entity["name"] - } + "label": folder_entity["label"], + "name": folder_entity["name"], + }, } )