Merge branch 'develop' into chore/houdini_remove_legacy_creator

This commit is contained in:
Jakub Trllo 2024-06-25 11:10:35 +02:00 committed by GitHub
commit 719f871dce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 230 additions and 46 deletions

View file

@ -448,6 +448,17 @@ DEFAULT_TOOLS_VALUES = {
"task_types": [], "task_types": [],
"tasks": [], "tasks": [],
"template": "SK_{folder[name]}{variant}" "template": "SK_{folder[name]}{variant}"
},
{
"product_types": [
"hda"
],
"hosts": [
"houdini"
],
"task_types": [],
"tasks": [],
"template": "{folder[name]}_{variant}"
} }
], ],
"filter_creator_profiles": [] "filter_creator_profiles": []

View file

@ -221,12 +221,8 @@ def containerise(name,
""" """
# Ensure AVALON_CONTAINERS subnet exists # Get AVALON_CONTAINERS subnet
subnet = hou.node(AVALON_CONTAINERS) subnet = get_or_create_avalon_container()
if subnet is None:
obj_network = hou.node("/obj")
subnet = obj_network.createNode("subnet",
node_name="AVALON_CONTAINERS")
# Create proper container name # Create proper container name
container_name = "{}_{}".format(name, suffix or "CON") container_name = "{}_{}".format(name, suffix or "CON")
@ -401,6 +397,18 @@ def on_new():
_enforce_start_frame() _enforce_start_frame()
def get_or_create_avalon_container() -> "hou.OpNode":
avalon_container = hou.node(AVALON_CONTAINERS)
if avalon_container:
return avalon_container
parent_path, name = AVALON_CONTAINERS.rsplit("/", 1)
parent = hou.node(parent_path)
return parent.createNode(
"subnet", node_name=name
)
def _set_context_settings(): def _set_context_settings():
"""Apply the project settings from the project definition """Apply the project settings from the project definition

View file

@ -73,7 +73,11 @@ class HoudiniCreatorBase(object):
@staticmethod @staticmethod
def create_instance_node( def create_instance_node(
folder_path, node_name, parent, node_type="geometry" folder_path,
node_name,
parent,
node_type="geometry",
pre_create_data=None
): ):
"""Create node representing instance. """Create node representing instance.
@ -82,6 +86,7 @@ class HoudiniCreatorBase(object):
node_name (str): Name of the new node. node_name (str): Name of the new node.
parent (str): Name of the parent node. parent (str): Name of the parent node.
node_type (str, optional): Type of the node. node_type (str, optional): Type of the node.
pre_create_data (Optional[Dict]): Pre create data.
Returns: Returns:
hou.Node: Newly created instance node. hou.Node: Newly created instance node.
@ -118,7 +123,12 @@ class HoudiniCreator(Creator, HoudiniCreatorBase):
folder_path = instance_data["folderPath"] folder_path = instance_data["folderPath"]
instance_node = self.create_instance_node( instance_node = self.create_instance_node(
folder_path, product_name, "/out", node_type) folder_path,
product_name,
"/out",
node_type,
pre_create_data
)
self.customize_node_look(instance_node) self.customize_node_look(instance_node)

View file

@ -1,13 +1,19 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Creator plugin for creating publishable Houdini Digital Assets.""" """Creator plugin for creating publishable Houdini Digital Assets."""
import ayon_api import hou
from assettools import setToolSubmenu
import ayon_api
from ayon_core.pipeline import ( from ayon_core.pipeline import (
CreatorError, CreatorError,
get_current_project_name get_current_project_name
) )
from ayon_core.lib import (
get_ayon_username,
BoolDef
)
from ayon_houdini.api import plugin from ayon_houdini.api import plugin
import hou
class CreateHDA(plugin.HoudiniCreator): class CreateHDA(plugin.HoudiniCreator):
@ -37,19 +43,38 @@ class CreateHDA(plugin.HoudiniCreator):
return product_name.lower() in existing_product_names_low return product_name.lower() in existing_product_names_low
def create_instance_node( def create_instance_node(
self, folder_path, node_name, parent, node_type="geometry" self,
folder_path,
node_name,
parent,
node_type="geometry",
pre_create_data=None
): ):
if pre_create_data is None:
pre_create_data = {}
parent_node = hou.node("/obj")
if self.selected_nodes: if self.selected_nodes:
# if we have `use selection` enabled, and we have some # if we have `use selection` enabled, and we have some
# selected nodes ... # selected nodes ...
subnet = parent_node.collapseIntoSubnet( if self.selected_nodes[0].type().name() == "subnet":
self.selected_nodes, to_hda = self.selected_nodes[0]
subnet_name="{}_subnet".format(node_name)) to_hda.setName("{}_subnet".format(node_name), unique_name=True)
subnet.moveToGoodPosition() else:
to_hda = subnet parent_node = self.selected_nodes[0].parent()
subnet = parent_node.collapseIntoSubnet(
self.selected_nodes,
subnet_name="{}_subnet".format(node_name))
subnet.moveToGoodPosition()
to_hda = subnet
else: else:
# Use Obj as the default path
parent_node = hou.node("/obj")
# Find and return the NetworkEditor pane tab with the minimum index
pane = hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor)
if isinstance(pane, hou.NetworkEditor):
# Use the NetworkEditor pane path as the parent path.
parent_node = pane.pwd()
to_hda = parent_node.createNode( 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 not to_hda.type().definition():
@ -71,7 +96,8 @@ class CreateHDA(plugin.HoudiniCreator):
hda_node = to_hda.createDigitalAsset( hda_node = to_hda.createDigitalAsset(
name=type_name, name=type_name,
description=node_name, description=node_name,
hda_file_name="$HIP/{}.hda".format(node_name) hda_file_name="$HIP/{}.hda".format(node_name),
ignore_external_references=True
) )
hda_node.layoutChildren() hda_node.layoutChildren()
elif self._check_existing(folder_path, node_name): elif self._check_existing(folder_path, node_name):
@ -81,21 +107,92 @@ class CreateHDA(plugin.HoudiniCreator):
else: else:
hda_node = to_hda hda_node = to_hda
hda_node.setName(node_name) # If user tries to create the same HDA instance more than
# once, then all of them will have the same product name and
# point to the same hda_file_name. But, their node names will
# be incremented.
hda_node.setName(node_name, unique_name=True)
self.customize_node_look(hda_node) self.customize_node_look(hda_node)
# Set Custom settings.
hda_def = hda_node.type().definition()
if pre_create_data.get("set_user"):
hda_def.setUserInfo(get_ayon_username())
if pre_create_data.get("use_project"):
setToolSubmenu(hda_def, "AYON/{}".format(self.project_name))
return hda_node return hda_node
def create(self, product_name, instance_data, pre_create_data): def create(self, product_name, instance_data, pre_create_data):
instance_data.pop("active", None) instance_data.pop("active", None)
instance = super(CreateHDA, self).create( return super(CreateHDA, self).create(
product_name, product_name,
instance_data, instance_data,
pre_create_data) pre_create_data)
return instance
def get_network_categories(self): def get_network_categories(self):
# Houdini allows creating sub-network nodes inside
# these categories.
# Therefore this plugin can work in these categories.
return [ return [
hou.objNodeTypeCategory() hou.chopNodeTypeCategory(),
hou.cop2NodeTypeCategory(),
hou.dopNodeTypeCategory(),
hou.ropNodeTypeCategory(),
hou.lopNodeTypeCategory(),
hou.objNodeTypeCategory(),
hou.sopNodeTypeCategory(),
hou.topNodeTypeCategory(),
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"),
]
def get_dynamic_data(
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
)
dynamic_data.update(
{
"asset": folder_entity["name"],
"folder": {
"label": folder_entity["label"],
"name": folder_entity["name"]
}
}
)
return dynamic_data

View file

@ -1,8 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os import os
from ayon_core.pipeline import get_representation_path import hou
from ayon_core.pipeline import (
get_representation_path,
AVALON_CONTAINER_ID
)
from ayon_core.pipeline.load import LoadError from ayon_core.pipeline.load import LoadError
from ayon_houdini.api import ( from ayon_houdini.api import (
lib,
pipeline, pipeline,
plugin plugin
) )
@ -19,42 +24,43 @@ class HdaLoader(plugin.HoudiniLoader):
color = "orange" color = "orange"
def load(self, context, name=None, namespace=None, data=None): def load(self, context, name=None, namespace=None, data=None):
import hou
# Format file name, Houdini only wants forward slashes # Format file name, Houdini only wants forward slashes
file_path = self.filepath_from_context(context) file_path = self.filepath_from_context(context)
file_path = os.path.normpath(file_path) file_path = os.path.normpath(file_path)
file_path = file_path.replace("\\", "/") file_path = file_path.replace("\\", "/")
# Get the root node
obj = hou.node("/obj")
namespace = namespace or context["folder"]["name"] namespace = namespace or context["folder"]["name"]
node_name = "{}_{}".format(namespace, name) if namespace else name node_name = "{}_{}".format(namespace, name) if namespace else name
hou.hda.installFile(file_path) hou.hda.installFile(file_path)
# Get the type name from the HDA definition.
hda_defs = hou.hda.definitionsInFile(file_path) hda_defs = hou.hda.definitionsInFile(file_path)
if not hda_defs: if not hda_defs:
raise LoadError(f"No HDA definitions found in file: {file_path}") raise LoadError(f"No HDA definitions found in file: {file_path}")
type_name = hda_defs[0].nodeTypeName() parent_node = self._create_dedicated_parent_node(hda_defs[-1])
hda_node = obj.createNode(type_name, node_name)
self[:] = [hda_node] # Get the type name from the HDA definition.
type_name = hda_defs[-1].nodeTypeName()
hda_node = parent_node.createNode(type_name, node_name)
hda_node.moveToGoodPosition()
return pipeline.containerise( # Imprint it manually
node_name, data = {
namespace, "schema": "openpype:container-2.0",
[hda_node], "id": AVALON_CONTAINER_ID,
context, "name": node_name,
self.__class__.__name__, "namespace": namespace,
suffix="", "loader": self.__class__.__name__,
) "representation": context["representation"]["id"],
}
lib.imprint(hda_node, data)
return hda_node
def update(self, container, context): def update(self, container, context):
import hou
repre_entity = context["representation"] repre_entity = context["representation"]
hda_node = container["node"] hda_node = container["node"]
@ -71,4 +77,45 @@ class HdaLoader(plugin.HoudiniLoader):
def remove(self, container): def remove(self, container):
node = container["node"] node = container["node"]
parent = node.parent()
node.destroy() node.destroy()
if parent.path() == pipeline.AVALON_CONTAINERS:
return
# Remove parent if empty.
if not parent.children():
parent.destroy()
def _create_dedicated_parent_node(self, hda_def):
# Get the root node
parent_node = pipeline.get_or_create_avalon_container()
node = None
node_type = None
if hda_def.nodeTypeCategory() == hou.objNodeTypeCategory():
return parent_node
elif hda_def.nodeTypeCategory() == hou.chopNodeTypeCategory():
node_type, node_name = "chopnet", "MOTION"
elif hda_def.nodeTypeCategory() == hou.cop2NodeTypeCategory():
node_type, node_name = "cop2net", "IMAGES"
elif hda_def.nodeTypeCategory() == hou.dopNodeTypeCategory():
node_type, node_name = "dopnet", "DOPS"
elif hda_def.nodeTypeCategory() == hou.ropNodeTypeCategory():
node_type, node_name = "ropnet", "ROPS"
elif hda_def.nodeTypeCategory() == hou.lopNodeTypeCategory():
node_type, node_name = "lopnet", "LOPS"
elif hda_def.nodeTypeCategory() == hou.sopNodeTypeCategory():
node_type, node_name = "geo", "SOPS"
elif hda_def.nodeTypeCategory() == hou.topNodeTypeCategory():
node_type, node_name = "topnet", "TOPS"
# TODO: Create a dedicated parent node based on Vop Node vex context.
elif hda_def.nodeTypeCategory() == hou.vopNodeTypeCategory():
node_type, node_name = "matnet", "MATSandVOPS"
node = parent_node.node(node_name)
if not node:
node = parent_node.createNode(node_type, node_name)
node.moveToGoodPosition()
return node

View file

@ -10,10 +10,9 @@ from ayon_core.pipeline.publish import (
ValidateContentsOrder, ValidateContentsOrder,
RepairAction, RepairAction,
) )
from ayon_core.pipeline.create import get_product_name
from ayon_houdini.api import plugin from ayon_houdini.api import plugin
from ayon_houdini.api.action import SelectInvalidAction from ayon_houdini.api.action import SelectInvalidAction
from ayon_core.pipeline.create import get_product_name
class FixProductNameAction(RepairAction): class FixProductNameAction(RepairAction):
@ -26,7 +25,7 @@ class ValidateSubsetName(plugin.HoudiniInstancePlugin,
""" """
families = ["staticMesh"] families = ["staticMesh", "hda"]
label = "Validate Product Name" label = "Validate Product Name"
order = ValidateContentsOrder + 0.1 order = ValidateContentsOrder + 0.1
actions = [FixProductNameAction, SelectInvalidAction] actions = [FixProductNameAction, SelectInvalidAction]
@ -67,7 +66,13 @@ class ValidateSubsetName(plugin.HoudiniInstancePlugin,
instance.context.data["hostName"], instance.context.data["hostName"],
instance.data["productType"], instance.data["productType"],
variant=instance.data["variant"], variant=instance.data["variant"],
dynamic_data={"asset": folder_entity["name"]} dynamic_data={
"asset": folder_entity["name"],
"folder": {
"label": folder_entity["label"],
"name": folder_entity["name"]
}
}
) )
if instance.data.get("productName") != product_name: if instance.data.get("productName") != product_name:
@ -97,7 +102,13 @@ class ValidateSubsetName(plugin.HoudiniInstancePlugin,
instance.context.data["hostName"], instance.context.data["hostName"],
instance.data["productType"], instance.data["productType"],
variant=instance.data["variant"], variant=instance.data["variant"],
dynamic_data={"asset": folder_entity["name"]} dynamic_data={
"asset": folder_entity["name"],
"folder": {
"label": folder_entity["label"],
"name": folder_entity["name"]
}
}
) )
instance.data["productName"] = product_name instance.data["productName"] = product_name