mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into feature/AY-5635_Action-to-update-to-latest-approved-version
This commit is contained in:
commit
01f43d4551
9 changed files with 234 additions and 131 deletions
|
|
@ -448,6 +448,17 @@ DEFAULT_TOOLS_VALUES = {
|
|||
"task_types": [],
|
||||
"tasks": [],
|
||||
"template": "SK_{folder[name]}{variant}"
|
||||
},
|
||||
{
|
||||
"product_types": [
|
||||
"hda"
|
||||
],
|
||||
"hosts": [
|
||||
"houdini"
|
||||
],
|
||||
"task_types": [],
|
||||
"tasks": [],
|
||||
"template": "{folder[name]}_{variant}"
|
||||
}
|
||||
],
|
||||
"filter_creator_profiles": []
|
||||
|
|
|
|||
|
|
@ -4,10 +4,6 @@ from .pipeline import (
|
|||
containerise
|
||||
)
|
||||
|
||||
from .plugin import (
|
||||
Creator,
|
||||
)
|
||||
|
||||
from .lib import (
|
||||
lsattr,
|
||||
lsattrs,
|
||||
|
|
@ -23,8 +19,6 @@ __all__ = [
|
|||
"ls",
|
||||
"containerise",
|
||||
|
||||
"Creator",
|
||||
|
||||
# Utility functions
|
||||
"lsattr",
|
||||
"lsattrs",
|
||||
|
|
|
|||
|
|
@ -221,12 +221,8 @@ def containerise(name,
|
|||
|
||||
"""
|
||||
|
||||
# Ensure AVALON_CONTAINERS subnet exists
|
||||
subnet = hou.node(AVALON_CONTAINERS)
|
||||
if subnet is None:
|
||||
obj_network = hou.node("/obj")
|
||||
subnet = obj_network.createNode("subnet",
|
||||
node_name="AVALON_CONTAINERS")
|
||||
# Get AVALON_CONTAINERS subnet
|
||||
subnet = get_or_create_avalon_container()
|
||||
|
||||
# Create proper container name
|
||||
container_name = "{}_{}".format(name, suffix or "CON")
|
||||
|
|
@ -401,6 +397,18 @@ def on_new():
|
|||
_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():
|
||||
"""Apply the project settings from the project definition
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ import hou
|
|||
import pyblish.api
|
||||
from ayon_core.pipeline import (
|
||||
CreatorError,
|
||||
LegacyCreator,
|
||||
Creator as NewCreator,
|
||||
Creator,
|
||||
CreatedInstance,
|
||||
AYON_INSTANCE_ID,
|
||||
AVALON_INSTANCE_ID,
|
||||
|
|
@ -26,80 +25,6 @@ from .lib import imprint, read, lsattr, add_self_publish_button
|
|||
SETTINGS_CATEGORY = "houdini"
|
||||
|
||||
|
||||
class Creator(LegacyCreator):
|
||||
"""Creator plugin to create instances in Houdini
|
||||
|
||||
To support the wide range of node types for render output (Alembic, VDB,
|
||||
Mantra) the Creator needs a node type to create the correct instance
|
||||
|
||||
By default, if none is given, is `geometry`. An example of accepted node
|
||||
types: geometry, alembic, ifd (mantra)
|
||||
|
||||
Please check the Houdini documentation for more node types.
|
||||
|
||||
Tip: to find the exact node type to create press the `i` left of the node
|
||||
when hovering over a node. The information is visible under the name of
|
||||
the node.
|
||||
|
||||
Deprecated:
|
||||
This creator is deprecated and will be removed in future version.
|
||||
|
||||
"""
|
||||
defaults = ['Main']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Creator, self).__init__(*args, **kwargs)
|
||||
self.nodes = []
|
||||
|
||||
def process(self):
|
||||
"""This is the base functionality to create instances in Houdini
|
||||
|
||||
The selected nodes are stored in self to be used in an override method.
|
||||
This is currently necessary in order to support the multiple output
|
||||
types in Houdini which can only be rendered through their own node.
|
||||
|
||||
Default node type if none is given is `geometry`
|
||||
|
||||
It also makes it easier to apply custom settings per instance type
|
||||
|
||||
Example of override method for Alembic:
|
||||
|
||||
def process(self):
|
||||
instance = super(CreateEpicNode, self, process()
|
||||
# Set parameters for Alembic node
|
||||
instance.setParms(
|
||||
{"sop_path": "$HIP/%s.abc" % self.nodes[0]}
|
||||
)
|
||||
|
||||
Returns:
|
||||
hou.Node
|
||||
|
||||
"""
|
||||
try:
|
||||
if (self.options or {}).get("useSelection"):
|
||||
self.nodes = hou.selectedNodes()
|
||||
|
||||
# Get the node type and remove it from the data, not needed
|
||||
node_type = self.data.pop("node_type", None)
|
||||
if node_type is None:
|
||||
node_type = "geometry"
|
||||
|
||||
# Get out node
|
||||
out = hou.node("/out")
|
||||
instance = out.createNode(node_type, node_name=self.name)
|
||||
instance.moveToGoodPosition()
|
||||
|
||||
imprint(instance, self.data)
|
||||
|
||||
self._process(instance)
|
||||
|
||||
except hou.Error as er:
|
||||
six.reraise(
|
||||
CreatorError,
|
||||
CreatorError("Creator error: {}".format(er)),
|
||||
sys.exc_info()[2])
|
||||
|
||||
|
||||
class HoudiniCreatorBase(object):
|
||||
@staticmethod
|
||||
def cache_instance_data(shared_data):
|
||||
|
|
@ -148,7 +73,11 @@ class HoudiniCreatorBase(object):
|
|||
|
||||
@staticmethod
|
||||
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.
|
||||
|
||||
|
|
@ -157,6 +86,7 @@ class HoudiniCreatorBase(object):
|
|||
node_name (str): Name of the new node.
|
||||
parent (str): Name of the parent node.
|
||||
node_type (str, optional): Type of the node.
|
||||
pre_create_data (Optional[Dict]): Pre create data.
|
||||
|
||||
Returns:
|
||||
hou.Node: Newly created instance node.
|
||||
|
|
@ -170,7 +100,7 @@ class HoudiniCreatorBase(object):
|
|||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
class HoudiniCreator(NewCreator, HoudiniCreatorBase):
|
||||
class HoudiniCreator(Creator, HoudiniCreatorBase):
|
||||
"""Base class for most of the Houdini creator plugins."""
|
||||
selected_nodes = []
|
||||
settings_name = None
|
||||
|
|
@ -193,7 +123,12 @@ class HoudiniCreator(NewCreator, HoudiniCreatorBase):
|
|||
folder_path = instance_data["folderPath"]
|
||||
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""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 (
|
||||
CreatorError,
|
||||
get_current_project_name
|
||||
)
|
||||
from ayon_core.lib import (
|
||||
get_ayon_username,
|
||||
BoolDef
|
||||
)
|
||||
|
||||
from ayon_houdini.api import plugin
|
||||
import hou
|
||||
|
||||
|
||||
class CreateHDA(plugin.HoudiniCreator):
|
||||
|
|
@ -37,19 +43,38 @@ class CreateHDA(plugin.HoudiniCreator):
|
|||
return product_name.lower() in existing_product_names_low
|
||||
|
||||
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 we have `use selection` enabled, and we have some
|
||||
# selected nodes ...
|
||||
subnet = parent_node.collapseIntoSubnet(
|
||||
self.selected_nodes,
|
||||
subnet_name="{}_subnet".format(node_name))
|
||||
subnet.moveToGoodPosition()
|
||||
to_hda = subnet
|
||||
if self.selected_nodes[0].type().name() == "subnet":
|
||||
to_hda = self.selected_nodes[0]
|
||||
to_hda.setName("{}_subnet".format(node_name), unique_name=True)
|
||||
else:
|
||||
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:
|
||||
# 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(
|
||||
"subnet", node_name="{}_subnet".format(node_name))
|
||||
if not to_hda.type().definition():
|
||||
|
|
@ -71,7 +96,8 @@ class CreateHDA(plugin.HoudiniCreator):
|
|||
hda_node = to_hda.createDigitalAsset(
|
||||
name=type_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()
|
||||
elif self._check_existing(folder_path, node_name):
|
||||
|
|
@ -81,21 +107,92 @@ class CreateHDA(plugin.HoudiniCreator):
|
|||
else:
|
||||
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)
|
||||
|
||||
# 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
|
||||
|
||||
def create(self, product_name, instance_data, pre_create_data):
|
||||
instance_data.pop("active", None)
|
||||
|
||||
instance = super(CreateHDA, self).create(
|
||||
return super(CreateHDA, self).create(
|
||||
product_name,
|
||||
instance_data,
|
||||
pre_create_data)
|
||||
|
||||
return instance
|
||||
|
||||
def get_network_categories(self):
|
||||
# Houdini allows creating sub-network nodes inside
|
||||
# these categories.
|
||||
# Therefore this plugin can work in these categories.
|
||||
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 -*-
|
||||
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_houdini.api import (
|
||||
lib,
|
||||
pipeline,
|
||||
plugin
|
||||
)
|
||||
|
|
@ -19,42 +24,43 @@ class HdaLoader(plugin.HoudiniLoader):
|
|||
color = "orange"
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
import hou
|
||||
|
||||
# Format file name, Houdini only wants forward slashes
|
||||
file_path = self.filepath_from_context(context)
|
||||
file_path = os.path.normpath(file_path)
|
||||
file_path = file_path.replace("\\", "/")
|
||||
|
||||
# Get the root node
|
||||
obj = hou.node("/obj")
|
||||
|
||||
namespace = namespace or context["folder"]["name"]
|
||||
node_name = "{}_{}".format(namespace, name) if namespace else name
|
||||
|
||||
hou.hda.installFile(file_path)
|
||||
|
||||
# Get the type name from the HDA definition.
|
||||
hda_defs = hou.hda.definitionsInFile(file_path)
|
||||
if not hda_defs:
|
||||
raise LoadError(f"No HDA definitions found in file: {file_path}")
|
||||
|
||||
type_name = hda_defs[0].nodeTypeName()
|
||||
hda_node = obj.createNode(type_name, node_name)
|
||||
parent_node = self._create_dedicated_parent_node(hda_defs[-1])
|
||||
|
||||
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(
|
||||
node_name,
|
||||
namespace,
|
||||
[hda_node],
|
||||
context,
|
||||
self.__class__.__name__,
|
||||
suffix="",
|
||||
)
|
||||
# Imprint it manually
|
||||
data = {
|
||||
"schema": "openpype:container-2.0",
|
||||
"id": AVALON_CONTAINER_ID,
|
||||
"name": node_name,
|
||||
"namespace": namespace,
|
||||
"loader": self.__class__.__name__,
|
||||
"representation": context["representation"]["id"],
|
||||
}
|
||||
|
||||
lib.imprint(hda_node, data)
|
||||
|
||||
return hda_node
|
||||
|
||||
def update(self, container, context):
|
||||
import hou
|
||||
|
||||
repre_entity = context["representation"]
|
||||
hda_node = container["node"]
|
||||
|
|
@ -71,4 +77,45 @@ class HdaLoader(plugin.HoudiniLoader):
|
|||
|
||||
def remove(self, container):
|
||||
node = container["node"]
|
||||
parent = node.parent()
|
||||
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,
|
||||
RepairAction,
|
||||
)
|
||||
|
||||
from ayon_core.pipeline.create import get_product_name
|
||||
from ayon_houdini.api import plugin
|
||||
from ayon_houdini.api.action import SelectInvalidAction
|
||||
from ayon_core.pipeline.create import get_product_name
|
||||
|
||||
|
||||
class FixProductNameAction(RepairAction):
|
||||
|
|
@ -26,7 +25,7 @@ class ValidateSubsetName(plugin.HoudiniInstancePlugin,
|
|||
|
||||
"""
|
||||
|
||||
families = ["staticMesh"]
|
||||
families = ["staticMesh", "hda"]
|
||||
label = "Validate Product Name"
|
||||
order = ValidateContentsOrder + 0.1
|
||||
actions = [FixProductNameAction, SelectInvalidAction]
|
||||
|
|
@ -67,7 +66,13 @@ class ValidateSubsetName(plugin.HoudiniInstancePlugin,
|
|||
instance.context.data["hostName"],
|
||||
instance.data["productType"],
|
||||
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:
|
||||
|
|
@ -97,7 +102,13 @@ class ValidateSubsetName(plugin.HoudiniInstancePlugin,
|
|||
instance.context.data["hostName"],
|
||||
instance.data["productType"],
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring AYON addon 'houdini' version."""
|
||||
__version__ = "0.3.4"
|
||||
__version__ = "0.3.5"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name = "houdini"
|
||||
title = "Houdini"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
|
||||
client_dir = "ayon_houdini"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue