From 0218afc48e6e0cf8e2773110f95835e1c348d513 Mon Sep 17 00:00:00 2001 From: ynput Date: Tue, 5 Nov 2024 09:53:55 +0100 Subject: [PATCH 1/6] init --- .../ayon_houdini/plugins/create/create_hda.py | 176 ++++++++---------- 1 file changed, 81 insertions(+), 95 deletions(-) diff --git a/client/ayon_houdini/plugins/create/create_hda.py b/client/ayon_houdini/plugins/create/create_hda.py index 7613735f7b..de1d20d950 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,34 @@ 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 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, ) 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, ) hda_node.layoutChildren() elif self._check_existing(folder_path, node_name): raise CreatorError( - ("product {} is already published with different HDA" - "definition.").format(node_name)) + ( + "product {} is already published with different HDA" "definition." + ).format(node_name) + ) else: hda_node = to_hda @@ -263,52 +256,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"], + }, } ) From f83ade57d4945b80c867e7e278e7f62ffec2ea15 Mon Sep 17 00:00:00 2001 From: Lyon Rosenblatt Date: Wed, 6 Nov 2024 08:22:01 +0100 Subject: [PATCH 2/6] Update client/ayon_houdini/plugins/create/create_hda.py Co-authored-by: Roy Nieterau --- client/ayon_houdini/plugins/create/create_hda.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/ayon_houdini/plugins/create/create_hda.py b/client/ayon_houdini/plugins/create/create_hda.py index de1d20d950..ca239c1f9e 100644 --- a/client/ayon_houdini/plugins/create/create_hda.py +++ b/client/ayon_houdini/plugins/create/create_hda.py @@ -218,9 +218,7 @@ def create_instance_node( 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 From f408ff97bf575bfa29ea4629ec251512678945c9 Mon Sep 17 00:00:00 2001 From: ynput Date: Wed, 6 Nov 2024 08:26:15 +0100 Subject: [PATCH 3/6] updates to the output detection logic and preperation for the new input detection logic --- .../ayon_houdini/plugins/create/create_hda.py | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/client/ayon_houdini/plugins/create/create_hda.py b/client/ayon_houdini/plugins/create/create_hda.py index ca239c1f9e..cb166d24d6 100644 --- a/client/ayon_houdini/plugins/create/create_hda.py +++ b/client/ayon_houdini/plugins/create/create_hda.py @@ -196,9 +196,10 @@ def create_instance_node( to_hda = parent_node.createNode( "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)) @@ -209,12 +210,45 @@ def create_instance_node( node_name=node_name, ) + intput_names = to_hda.inputs() + input_count = len(intput_names) + + source_parm_template_group = to_hda.parmTemplateGroup() + + output_index_list = set() + for node in [ + node + for node in to_hda.allSubChildren() + if node.type().name() == "output" + ]: + output_index = node.parm("outputidx").eval() + output_index_list.add(output_index) + + raise RuntimeError( + output_index_list, + "find out what inputs are connected to outputs and then use this info to fill in the max inputs ", + ) + hda_node = to_hda.createDigitalAsset( name=type_name, description=node_name, hda_file_name="$HIP/{}.hda".format(node_name), ignore_external_references=True, + min_num_inputs=0, + max_num_inputs=max(input_count, 1), ) + + hda_def = hda_node.type().definition() + hda_def.setMaxNumOutputs(len(output_index_list)) + + 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( From e65d0a7a21df1a6bf8f713660961b22f12f8daf4 Mon Sep 17 00:00:00 2001 From: ynput Date: Wed, 6 Nov 2024 09:32:14 +0100 Subject: [PATCH 4/6] implemented indirectInputs methode and subnet_input_connector output check to determen the amount of inputs a given hda should have. --- client/ayon_houdini/plugins/create/create_hda.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/client/ayon_houdini/plugins/create/create_hda.py b/client/ayon_houdini/plugins/create/create_hda.py index cb166d24d6..2ab9352784 100644 --- a/client/ayon_houdini/plugins/create/create_hda.py +++ b/client/ayon_houdini/plugins/create/create_hda.py @@ -210,9 +210,6 @@ def create_instance_node( node_name=node_name, ) - intput_names = to_hda.inputs() - input_count = len(intput_names) - source_parm_template_group = to_hda.parmTemplateGroup() output_index_list = set() @@ -224,10 +221,10 @@ def create_instance_node( output_index = node.parm("outputidx").eval() output_index_list.add(output_index) - raise RuntimeError( - output_index_list, - "find out what inputs are connected to outputs and then use this info to fill in the max inputs ", - ) + 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, @@ -235,7 +232,7 @@ def create_instance_node( hda_file_name="$HIP/{}.hda".format(node_name), ignore_external_references=True, min_num_inputs=0, - max_num_inputs=max(input_count, 1), + max_num_inputs=max(len(subnet_inputs_with_connections), 1), ) hda_def = hda_node.type().definition() From 5cf8005ca5a2e09220898de4aae28d558d2930ba Mon Sep 17 00:00:00 2001 From: ynput Date: Wed, 6 Nov 2024 09:50:43 +0100 Subject: [PATCH 5/6] changed the logic to allways create a minimum of 1 input and output on every hda --- client/ayon_houdini/plugins/create/create_hda.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_houdini/plugins/create/create_hda.py b/client/ayon_houdini/plugins/create/create_hda.py index 2ab9352784..7fca7c28ee 100644 --- a/client/ayon_houdini/plugins/create/create_hda.py +++ b/client/ayon_houdini/plugins/create/create_hda.py @@ -236,7 +236,7 @@ def create_instance_node( ) hda_def = hda_node.type().definition() - hda_def.setMaxNumOutputs(len(output_index_list)) + hda_def.setMaxNumOutputs(max(len(output_index_list), 1)) hda_parm_template_group = hda_def.parmTemplateGroup() From a958e11a1e9e0f6e7b2f77b2180e651cd4f13809 Mon Sep 17 00:00:00 2001 From: ynput Date: Tue, 12 Nov 2024 16:23:10 +0100 Subject: [PATCH 6/6] use .children not .allSubChildren to find the output nodes in the selected subnet, add docstrings for the input and parm template logic. --- client/ayon_houdini/plugins/create/create_hda.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/client/ayon_houdini/plugins/create/create_hda.py b/client/ayon_houdini/plugins/create/create_hda.py index 7fca7c28ee..c7ee04b0e6 100644 --- a/client/ayon_houdini/plugins/create/create_hda.py +++ b/client/ayon_houdini/plugins/create/create_hda.py @@ -212,15 +212,17 @@ def create_instance_node( source_parm_template_group = to_hda.parmTemplateGroup() - output_index_list = set() + # 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.allSubChildren() - if node.type().name() == "output" + node for node in to_hda.children() if node.type().name() == "output" ]: output_index = node.parm("outputidx").eval() - output_index_list.add(output_index) + 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(): @@ -235,8 +237,9 @@ def create_instance_node( 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_index_list), 1)) + hda_def.setMaxNumOutputs(max(len(output_indexes), 1)) hda_parm_template_group = hda_def.parmTemplateGroup()