Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create correct Hdas from Subnetworks, keeping Parms, Outputs and Inputs. #160

Draft
wants to merge 7 commits into
base: develop
Choose a base branch
from
212 changes: 115 additions & 97 deletions client/ayon_houdini/plugins/create/create_hda.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -118,26 +114,29 @@ def set_tool_submenu(hda_def,
</tool>
</shelfDocument>
"""

nodetype_category_name = hda_def.nodeType().category().name()
context = context_dict[nodetype_category_name]
util = utils_dict[nodetype_category_name]
content = content.replace(
"<contextNetType>SOP</contextNetType>",
f"<contextNetType>{context}</contextNetType>")
content = content.replace('soptoolutils', util)
hda_def.addSection('Tools.shelf', content)
old_submenu = 'Digital Assets'
f"<contextNetType>{context}</contextNetType>",
)
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"<toolSubmenu>{old_submenu}</toolSubmenu>",
f"<toolSubmenu>{new_submenu}</toolSubmenu>"
f"<toolSubmenu>{new_submenu}</toolSubmenu>",
)

hda_def.addSection('Tools.shelf', content)
hda_def.addSection("Tools.shelf", content)


# endregion


Expand All @@ -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 = {}
Expand All @@ -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:
Expand All @@ -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

Expand Down Expand Up @@ -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"],
},
}
)

Expand Down