mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 08:24:53 +01:00
Merge branch 'develop' into chore/houdini_remove_legacy_creator
This commit is contained in:
commit
719f871dce
6 changed files with 230 additions and 46 deletions
|
|
@ -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": []
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue