diff --git a/client/ayon_houdini/api/colorspace.py b/client/ayon_houdini/api/colorspace.py index ec6e4c2091..31c112c247 100644 --- a/client/ayon_houdini/api/colorspace.py +++ b/client/ayon_houdini/api/colorspace.py @@ -1,59 +1,55 @@ +from typing import List + import attr import hou from ayon_houdini.api.lib import get_color_management_preferences -from ayon_core.pipeline.colorspace import get_display_view_colorspace_name +from ayon_core.pipeline.colorspace import ( + get_display_view_colorspace_name, + get_ocio_config_colorspaces +) + @attr.s class LayerMetadata(object): """Data class for Render Layer metadata.""" - frameStart = attr.ib() - frameEnd = attr.ib() + products: "List[RenderProduct]" = attr.ib() @attr.s class RenderProduct(object): - """Getting Colorspace as - Specific Render Product Parameter for submitting - publish job. - - """ + """Specific Render Product Parameter for submitting.""" colorspace = attr.ib() # colorspace - view = attr.ib() productName = attr.ib(default=None) class ARenderProduct(object): + """This is the minimal data structure required to get + `ayon_core.pipeline.farm.pyblish_functions.create_instances_for_aov` to + work with deadline addon's job submissions.""" + # TODO: The exact data structure should actually be defined in core for all + # addons to align. + def __init__(self, aov_names: List[str]): + colorspace = get_scene_linear_colorspace() + products = [ + RenderProduct(colorspace=colorspace, productName=aov_name) + for aov_name in aov_names + ] + self.layer_data = LayerMetadata(products=products) - def __init__(self): - """Constructor.""" - # Initialize - self.layer_data = self._get_layer_data() - self.layer_data.products = self.get_colorspace_data() - - def _get_layer_data(self): - return LayerMetadata( - frameStart=int(hou.playbar.frameRange()[0]), - frameEnd=int(hou.playbar.frameRange()[1]), - ) - - def get_colorspace_data(self): - """To be implemented by renderer class. - This should return a list of RenderProducts. +def get_scene_linear_colorspace(): + """Return colorspace name for Houdini's OCIO config scene linear role. - Returns: - list: List of RenderProduct + By default, renderers in Houdini render output images in the scene linear + role colorspace. - """ - data = get_color_management_preferences() - colorspace_data = [ - RenderProduct( - colorspace=data["display"], - view=data["view"], - productName="" - ) - ] - return colorspace_data + Returns: + Optional[str]: The colorspace name for the 'scene_linear' role in + the OCIO config Houdini is currently set to. + """ + ocio_config_path = hou.Color.ocio_configPath() + colorspaces = get_ocio_config_colorspaces(ocio_config_path) + return colorspaces["roles"].get("scene_linear", {}).get("colorspace") def get_default_display_view_colorspace(): diff --git a/client/ayon_houdini/api/hda_utils.py b/client/ayon_houdini/api/hda_utils.py index 65d665c0f6..617f121258 100644 --- a/client/ayon_houdini/api/hda_utils.py +++ b/client/ayon_houdini/api/hda_utils.py @@ -27,6 +27,7 @@ from ayon_core.style import load_stylesheet from ayon_houdini.api import lib +from .usd import get_ayon_entity_uri_from_representation_context from qtpy import QtCore, QtWidgets, QtGui import hou @@ -179,11 +180,19 @@ def set_representation(node, representation_id: str): context = get_representation_context(project_name, repre_entity) update_info(node, context) - path = get_representation_path_from_context(context) - # Load fails on UNC paths with backslashes and also - # fails to resolve @sourcename var with backslashed - # paths correctly. So we force forward slashes - path = path.replace("\\", "/") + + if node.parm("use_ayon_entity_uri"): + use_ayon_entity_uri = node.evalParm("use_ayon_entity_uri") + else: + use_ayon_entity_uri = False + if use_ayon_entity_uri: + path = get_ayon_entity_uri_from_representation_context(context) + else: + path = get_representation_path_from_context(context) + # Load fails on UNC paths with backslashes and also + # fails to resolve @sourcename var with backslashed + # paths correctly. So we force forward slashes + path = path.replace("\\", "/") with _unlocked_parm(file_parm): file_parm.set(path) @@ -255,7 +264,7 @@ def on_representation_id_changed(node): set_representation(node, repre_id) -def on_representation_parms_changed(node): +def on_representation_parms_changed(node, force=False): """ Usually used as callback to the project, folder, product, version and representation parms which on change - would result in a different @@ -263,6 +272,9 @@ def on_representation_parms_changed(node): Args: node (hou.Node): Node to update. + force (Optional[bool]): Whether to force the callback to retrigger + even if the representation id already matches. For example, when + needing to resolve the filepath in a different way. """ project_name = node.evalParm("project_name") or get_current_project_name() representation_id = get_representation_id( @@ -278,7 +290,7 @@ def on_representation_parms_changed(node): else: representation_id = str(representation_id) - if node.evalParm("representation") != representation_id: + if force or node.evalParm("representation") != representation_id: node.parm("representation").set(representation_id) node.parm("representation").pressButton() # trigger callback diff --git a/client/ayon_houdini/api/lib.py b/client/ayon_houdini/api/lib.py index e90234ccb2..df0cb6c277 100644 --- a/client/ayon_houdini/api/lib.py +++ b/client/ayon_houdini/api/lib.py @@ -580,12 +580,41 @@ def replace(match): def get_color_management_preferences(): """Get default OCIO preferences""" - return { + + preferences = { "config": hou.Color.ocio_configPath(), "display": hou.Color.ocio_defaultDisplay(), "view": hou.Color.ocio_defaultView() } + # Note: For whatever reason they are cases where `view` may be an empty + # string even though a valid default display is set where `PyOpenColorIO` + # does correctly return the values. + # Workaround to get the correct default view + if preferences["config"] and not preferences["view"]: + log.debug( + "Houdini `hou.Color.ocio_defaultView()` returned empty value." + " Falling back to `PyOpenColorIO` to get the default view.") + try: + import PyOpenColorIO + except ImportError: + log.warning( + "Unable to workaround empty return value of " + "`hou.Color.ocio_defaultView()` because `PyOpenColorIO` is " + "not available.") + return preferences + + config_path = preferences["config"] + config = PyOpenColorIO.Config.CreateFromFile(config_path) + display = config.getDefaultDisplay() + assert display == preferences["display"], \ + "Houdini default OCIO display must match config default display" + view = config.getDefaultView(display) + preferences["display"] = display + preferences["view"] = view + + return preferences + def get_obj_node_output(obj_node): """Find output node. diff --git a/client/ayon_houdini/api/plugin.py b/client/ayon_houdini/api/plugin.py index 8a2344febb..9aa4810d83 100644 --- a/client/ayon_houdini/api/plugin.py +++ b/client/ayon_houdini/api/plugin.py @@ -20,6 +20,7 @@ from ayon_core.lib import BoolDef from .lib import imprint, read, lsattr, add_self_publish_button +from .usd import get_ayon_entity_uri_from_representation_context SETTINGS_CATEGORY = "houdini" @@ -316,6 +317,13 @@ class HoudiniLoader(load.LoaderPlugin): hosts = ["houdini"] settings_category = SETTINGS_CATEGORY + use_ayon_entity_uri = False + + def filepath_from_context(cls, context): + if cls.use_ayon_entity_uri: + return get_ayon_entity_uri_from_representation_context(context) + + return super(HoudiniLoader, cls).filepath_from_context(context) class HoudiniInstancePlugin(pyblish.api.InstancePlugin): diff --git a/client/ayon_houdini/api/usd.py b/client/ayon_houdini/api/usd.py index a416d581c3..f1febf28b7 100644 --- a/client/ayon_houdini/api/usd.py +++ b/client/ayon_houdini/api/usd.py @@ -7,6 +7,7 @@ from typing import List import hou +import ayon_api from pxr import Usd, Sdf, Tf, Vt, UsdRender log = logging.getLogger(__name__) @@ -377,3 +378,42 @@ def get_schema_type_names(type_name: str) -> List[str]: results.append(schema_type_name) return results + + +def get_ayon_entity_uri_from_representation_context(context: dict) -> str: + """Resolve AYON Entity URI from representation context. + + Note: + The representation context is the `get_representation_context` dict + containing the `project`, `folder, `representation` and so forth. + It is not the representation entity `context` key. + + Arguments: + context (dict): The representation context. + + Raises: + RuntimeError: Unable to resolve to a single valid URI. + + Returns: + str: The AYON entity URI. + + """ + project_name = context["project"]["name"] + representation_id = context["representation"]["id"] + response = ayon_api.post( + f"projects/{project_name}/uris", + entityType="representation", + ids=[representation_id]) + if response.status_code != 200: + raise RuntimeError( + f"Unable to resolve AYON entity URI for '{project_name}' " + f"representation id '{representation_id}': {response.text}" + ) + uris = response.data["uris"] + if len(uris) != 1: + raise RuntimeError( + f"Unable to resolve AYON entity URI for '{project_name}' " + f"representation id '{representation_id}' to single URI. " + f"Received data: {response.data}" + ) + return uris[0]["uri"] diff --git a/client/ayon_houdini/plugins/load/load_usd_layer.py b/client/ayon_houdini/plugins/load/load_usd_layer.py index fb302fd943..feb8eb99e0 100644 --- a/client/ayon_houdini/plugins/load/load_usd_layer.py +++ b/client/ayon_houdini/plugins/load/load_usd_layer.py @@ -1,5 +1,4 @@ from ayon_core.pipeline import ( - get_representation_path, AVALON_CONTAINER_ID, ) from ayon_houdini.api import ( @@ -22,14 +21,13 @@ class USDSublayerLoader(plugin.HoudiniLoader): icon = "code-fork" color = "orange" - def load(self, context, name=None, namespace=None, data=None): + use_ayon_entity_uri = False - import os + 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 @@ -60,18 +58,17 @@ def load(self, context, name=None, namespace=None, data=None): return container def update(self, container, context): - repre_entity = context["representation"] node = container["node"] # Update the file path - file_path = get_representation_path(repre_entity) + file_path = self.filepath_from_context(context) file_path = file_path.replace("\\", "/") # Update attributes node.setParms( { "filepath1": file_path, - "representation": repre_entity["id"], + "representation": context["representation"]["id"], } ) diff --git a/client/ayon_houdini/plugins/load/load_usd_reference.py b/client/ayon_houdini/plugins/load/load_usd_reference.py index 690f6ce187..ef5fc754be 100644 --- a/client/ayon_houdini/plugins/load/load_usd_reference.py +++ b/client/ayon_houdini/plugins/load/load_usd_reference.py @@ -1,5 +1,4 @@ from ayon_core.pipeline import ( - get_representation_path, AVALON_CONTAINER_ID, ) from ayon_houdini.api import ( @@ -22,14 +21,13 @@ class USDReferenceLoader(plugin.HoudiniLoader): icon = "code-fork" color = "orange" - def load(self, context, name=None, namespace=None, data=None): + use_ayon_entity_uri = False - import os + 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 @@ -60,18 +58,17 @@ def load(self, context, name=None, namespace=None, data=None): return container def update(self, container, context): - repre_entity = context["representation"] node = container["node"] # Update the file path - file_path = get_representation_path(repre_entity) + file_path = self.filepath_from_context(context) file_path = file_path.replace("\\", "/") # Update attributes node.setParms( { "filepath1": file_path, - "representation": repre_entity["id"], + "representation": context["representation"]["id"], } ) diff --git a/client/ayon_houdini/plugins/load/load_usd_sop.py b/client/ayon_houdini/plugins/load/load_usd_sop.py index 347e3283de..3592c9c2ed 100644 --- a/client/ayon_houdini/plugins/load/load_usd_sop.py +++ b/client/ayon_houdini/plugins/load/load_usd_sop.py @@ -1,5 +1,3 @@ -import os - from ayon_houdini.api import ( pipeline, plugin @@ -16,12 +14,13 @@ class SopUsdImportLoader(plugin.HoudiniLoader): icon = "code-fork" color = "orange" + use_ayon_entity_uri = False + 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 diff --git a/client/ayon_houdini/plugins/publish/collect_arnold_rop.py b/client/ayon_houdini/plugins/publish/collect_arnold_rop.py index 10c6d91d26..bebdb789de 100644 --- a/client/ayon_houdini/plugins/publish/collect_arnold_rop.py +++ b/client/ayon_houdini/plugins/publish/collect_arnold_rop.py @@ -4,11 +4,8 @@ import hou import pyblish.api -from ayon_houdini.api import colorspace, plugin -from ayon_houdini.api.lib import ( - get_color_management_preferences, - evalParmNoFrame -) +from ayon_houdini.api import plugin +from ayon_houdini.api.lib import evalParmNoFrame class CollectArnoldROPRenderProducts(plugin.HoudiniInstancePlugin): @@ -100,7 +97,6 @@ def process(self, instance): self.log.debug("Found render product: {}".format(product)) instance.data["files"] = list(render_products) - instance.data["renderProducts"] = colorspace.ARenderProduct() # For now by default do NOT try to publish the rendered output instance.data["publishJobState"] = "Suspended" @@ -110,12 +106,6 @@ def process(self, instance): instance.data["expectedFiles"] = list() instance.data["expectedFiles"].append(files_by_aov) - # update the colorspace data - colorspace_data = get_color_management_preferences() - instance.data["colorspaceConfig"] = colorspace_data["config"] - instance.data["colorspaceDisplay"] = colorspace_data["display"] - instance.data["colorspaceView"] = colorspace_data["view"] - def get_render_product_name(self, prefix, suffix): """Return the output filename using the AOV prefix and suffix""" diff --git a/client/ayon_houdini/plugins/publish/collect_karma_rop.py b/client/ayon_houdini/plugins/publish/collect_karma_rop.py index 60fec9d2e0..87b194ae8e 100644 --- a/client/ayon_houdini/plugins/publish/collect_karma_rop.py +++ b/client/ayon_houdini/plugins/publish/collect_karma_rop.py @@ -4,14 +4,8 @@ import hou import pyblish.api -from ayon_houdini.api.lib import ( - evalParmNoFrame, - get_color_management_preferences -) -from ayon_houdini.api import ( - colorspace, - plugin -) +from ayon_houdini.api.lib import evalParmNoFrame +from ayon_houdini.api import plugin class CollectKarmaROPRenderProducts(plugin.HoudiniInstancePlugin): @@ -63,7 +57,6 @@ def process(self, instance): filenames = list(render_products) instance.data["files"] = filenames - instance.data["renderProducts"] = colorspace.ARenderProduct() for product in render_products: self.log.debug("Found render product: %s" % product) @@ -72,12 +65,6 @@ def process(self, instance): instance.data["expectedFiles"] = list() instance.data["expectedFiles"].append(files_by_aov) - # update the colorspace data - colorspace_data = get_color_management_preferences() - instance.data["colorspaceConfig"] = colorspace_data["config"] - instance.data["colorspaceDisplay"] = colorspace_data["display"] - instance.data["colorspaceView"] = colorspace_data["view"] - def get_render_product_name(self, prefix, suffix): product_name = prefix if suffix: diff --git a/client/ayon_houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_houdini/plugins/publish/collect_local_render_instances.py index 931a79535b..35c0e5afff 100644 --- a/client/ayon_houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_houdini/plugins/publish/collect_local_render_instances.py @@ -4,12 +4,15 @@ from ayon_core.pipeline.farm.patterning import match_aov_pattern from ayon_core.pipeline.publish import ( get_plugin_settings, - apply_plugin_settings_automatically + apply_plugin_settings_automatically, + ColormanagedPyblishPluginMixin ) from ayon_houdini.api import plugin +from ayon_houdini.api.colorspace import get_scene_linear_colorspace -class CollectLocalRenderInstances(plugin.HoudiniInstancePlugin): +class CollectLocalRenderInstances(plugin.HoudiniInstancePlugin, + ColormanagedPyblishPluginMixin): """Collect instances for local render. Agnostic Local Render Collector. @@ -49,9 +52,9 @@ def apply_settings(cls, project_settings): # get aov_filter from deadline settings cls.aov_filter = project_settings["deadline"]["publish"]["ProcessSubmittedJobOnFarm"]["aov_filter"] cls.aov_filter = { - item["name"]: item["value"] - for item in cls.aov_filter - } + item["name"]: item["value"] + for item in cls.aov_filter + } def process(self, instance): @@ -74,6 +77,14 @@ def process(self, instance): instance.data["productName"] ) + # NOTE: The assumption that the output image's colorspace is the + # scene linear role may be incorrect. Certain renderers, like + # Karma allow overriding explicitly the output colorspace of the + # image. Such override are currently not considered since these + # would need to be detected in a renderer-specific way and the + # majority of production scenarios these would not be overridden. + # TODO: Support renderer-specific explicit colorspace overrides + colorspace = get_scene_linear_colorspace() for aov_name, aov_filepaths in expectedFiles.items(): product_name = product_group @@ -108,6 +119,21 @@ def process(self, instance): if len(aov_filenames) == 1: aov_filenames = aov_filenames[0] + representation = { + "stagingDir": staging_dir, + "ext": ext, + "name": ext, + "tags": ["review"] if preview else [], + "files": aov_filenames, + "frameStart": instance.data["frameStartHandle"], + "frameEnd": instance.data["frameEndHandle"] + } + + # Set the colorspace for the representation + self.set_representation_colorspace(representation, + context, + colorspace=colorspace) + aov_instance.data.update({ # 'label': label, "task": instance.data["task"], @@ -120,17 +146,7 @@ def process(self, instance): "productGroup": product_group, "families": ["render.local.hou", "review"], "instance_node": instance.data["instance_node"], - "representations": [ - { - "stagingDir": staging_dir, - "ext": ext, - "name": ext, - "tags": ["review"] if preview else [], - "files": aov_filenames, - "frameStart": instance.data["frameStartHandle"], - "frameEnd": instance.data["frameEndHandle"] - } - ] + "representations": [representation] }) # Skip integrating original render instance. diff --git a/client/ayon_houdini/plugins/publish/collect_mantra_rop.py b/client/ayon_houdini/plugins/publish/collect_mantra_rop.py index f7feeee63b..f26768c3e9 100644 --- a/client/ayon_houdini/plugins/publish/collect_mantra_rop.py +++ b/client/ayon_houdini/plugins/publish/collect_mantra_rop.py @@ -4,14 +4,8 @@ import hou import pyblish.api -from ayon_houdini.api.lib import ( - evalParmNoFrame, - get_color_management_preferences -) -from ayon_houdini.api import ( - colorspace, - plugin -) +from ayon_houdini.api.lib import evalParmNoFrame +from ayon_houdini.api import plugin class CollectMantraROPRenderProducts(plugin.HoudiniInstancePlugin): @@ -108,7 +102,6 @@ def process(self, instance): filenames = list(render_products) instance.data["files"] = filenames - instance.data["renderProducts"] = colorspace.ARenderProduct() # For now by default do NOT try to publish the rendered output instance.data["publishJobState"] = "Suspended" @@ -118,12 +111,6 @@ def process(self, instance): instance.data["expectedFiles"] = list() instance.data["expectedFiles"].append(files_by_aov) - # update the colorspace data - colorspace_data = get_color_management_preferences() - instance.data["colorspaceConfig"] = colorspace_data["config"] - instance.data["colorspaceDisplay"] = colorspace_data["display"] - instance.data["colorspaceView"] = colorspace_data["view"] - def get_render_product_name(self, prefix, suffix): product_name = prefix if suffix: diff --git a/client/ayon_houdini/plugins/publish/collect_redshift_rop.py b/client/ayon_houdini/plugins/publish/collect_redshift_rop.py index 96cb6ebeaf..bd43805497 100644 --- a/client/ayon_houdini/plugins/publish/collect_redshift_rop.py +++ b/client/ayon_houdini/plugins/publish/collect_redshift_rop.py @@ -4,14 +4,8 @@ import hou import pyblish.api -from ayon_houdini.api.lib import ( - evalParmNoFrame, - get_color_management_preferences -) -from ayon_houdini.api import ( - colorspace, - plugin -) +from ayon_houdini.api.lib import evalParmNoFrame +from ayon_houdini.api import plugin class CollectRedshiftROPRenderProducts(plugin.HoudiniInstancePlugin): @@ -119,7 +113,6 @@ def process(self, instance): filenames = list(render_products) instance.data["files"] = filenames - instance.data["renderProducts"] = colorspace.ARenderProduct() # For now by default do NOT try to publish the rendered output instance.data["publishJobState"] = "Suspended" @@ -129,12 +122,6 @@ def process(self, instance): instance.data["expectedFiles"] = [] instance.data["expectedFiles"].append(files_by_aov) - # update the colorspace data - colorspace_data = get_color_management_preferences() - instance.data["colorspaceConfig"] = colorspace_data["config"] - instance.data["colorspaceDisplay"] = colorspace_data["display"] - instance.data["colorspaceView"] = colorspace_data["view"] - def get_render_product_name(self, prefix, suffix): """Return the output filename using the AOV prefix and suffix""" diff --git a/client/ayon_houdini/plugins/publish/collect_render_colorspace.py b/client/ayon_houdini/plugins/publish/collect_render_colorspace.py new file mode 100644 index 0000000000..1166243ce3 --- /dev/null +++ b/client/ayon_houdini/plugins/publish/collect_render_colorspace.py @@ -0,0 +1,41 @@ +from ayon_houdini.api import plugin, colorspace + +import pyblish.api + + +class CollectHoudiniRenderColorspace(plugin.HoudiniInstancePlugin): + """Collect Colorspace data for render output images. + + This currently assumes that all render products are in 'scene_linear' + colorspace role - which is the default behavior for renderers in Houdini. + """ + + label = "Collect Render Colorspace" + order = pyblish.api.CollectorOrder + 0.15 + families = ["mantra_rop", + "karma_rop", + "redshift_rop", + "arnold_rop", + "vray_rop", + "usdrender"] + + def process(self, instance): + # Set the required data for `ayon_core.pipeline.farm.pyblish_functions` + # functions used for farm publish job processing. + + # Define render products for `create_instances_for_aov` + # which uses it in `_create_instances_for_aov()` to match the render + # product's name to aovs to define the colorspace. + expected_files = instance.data["expectedFiles"] + aov_name = list(expected_files[0].keys()) + render_products_data = colorspace.ARenderProduct(aov_name) + instance.data["renderProducts"] = render_products_data + + # Required data for `create_instances_for_aov` + colorspace_data = colorspace.get_color_management_preferences() + instance.data["colorspaceConfig"] = colorspace_data["config"] + instance.data["colorspaceDisplay"] = colorspace_data["display"] + instance.data["colorspaceView"] = colorspace_data["view"] + + # Used in `create_skeleton_instance()` + instance.data["colorspace"] = colorspace.get_scene_linear_colorspace() diff --git a/client/ayon_houdini/plugins/publish/collect_usd_render.py b/client/ayon_houdini/plugins/publish/collect_usd_render.py index a6e7572a18..2934cce246 100644 --- a/client/ayon_houdini/plugins/publish/collect_usd_render.py +++ b/client/ayon_houdini/plugins/publish/collect_usd_render.py @@ -4,14 +4,8 @@ import hou import pyblish.api -from ayon_houdini.api import ( - colorspace, - plugin -) -from ayon_houdini.api.lib import ( - evalParmNoFrame, - get_color_management_preferences -) +from ayon_houdini.api import plugin +from ayon_houdini.api.lib import evalParmNoFrame class CollectUsdRender(plugin.HoudiniInstancePlugin): @@ -23,9 +17,6 @@ class CollectUsdRender(plugin.HoudiniInstancePlugin): Provides: instance -> ifdFile - instance -> colorspaceConfig - instance -> colorspaceDisplay - instance -> colorspaceView """ @@ -75,12 +66,5 @@ def replace_to_f(match): if "$F" not in export_file: instance.data["splitRenderFrameDependent"] = False - # update the colorspace data - colorspace_data = get_color_management_preferences() - instance.data["colorspaceConfig"] = colorspace_data["config"] - instance.data["colorspaceDisplay"] = colorspace_data["display"] - instance.data["colorspaceView"] = colorspace_data["view"] - # stub required data for Submit Publish Job publish plug-in instance.data["attachTo"] = [] - instance.data["renderProducts"] = colorspace.ARenderProduct() diff --git a/client/ayon_houdini/plugins/publish/collect_vray_rop.py b/client/ayon_houdini/plugins/publish/collect_vray_rop.py index 2f9c2bb18e..ed360b37a5 100644 --- a/client/ayon_houdini/plugins/publish/collect_vray_rop.py +++ b/client/ayon_houdini/plugins/publish/collect_vray_rop.py @@ -4,14 +4,8 @@ import hou import pyblish.api -from ayon_houdini.api.lib import ( - evalParmNoFrame, - get_color_management_preferences -) -from ayon_houdini.api import ( - colorspace, - plugin -) +from ayon_houdini.api.lib import evalParmNoFrame +from ayon_houdini.api import plugin class CollectVrayROPRenderProducts(plugin.HoudiniInstancePlugin): @@ -89,7 +83,6 @@ def process(self, instance): self.log.debug("Found render product: %s" % product) filenames = list(render_products) instance.data["files"] = filenames - instance.data["renderProducts"] = colorspace.ARenderProduct() # For now by default do NOT try to publish the rendered output instance.data["publishJobState"] = "Suspended" @@ -100,12 +93,6 @@ def process(self, instance): instance.data["expectedFiles"].append(files_by_aov) self.log.debug("expectedFiles:{}".format(files_by_aov)) - # update the colorspace data - colorspace_data = get_color_management_preferences() - instance.data["colorspaceConfig"] = colorspace_data["config"] - instance.data["colorspaceDisplay"] = colorspace_data["display"] - instance.data["colorspaceView"] = colorspace_data["view"] - def get_render_product_name(self, prefix, suffix=""): """Return the beauty output filename if render element enabled """ diff --git a/client/ayon_houdini/startup/husdplugins/outputprocessors/remap_to_publish.py b/client/ayon_houdini/startup/husdplugins/outputprocessors/remap_to_publish.py index 52e02f4160..1b4ded0bb0 100644 --- a/client/ayon_houdini/startup/husdplugins/outputprocessors/remap_to_publish.py +++ b/client/ayon_houdini/startup/husdplugins/outputprocessors/remap_to_publish.py @@ -5,6 +5,9 @@ from husd.outputprocessor import OutputProcessor +_COMPATIBILITY_PLACEHOLDER = object() + + class AYONRemapPaths(OutputProcessor): """Remap paths based on a mapping dict on rop node.""" @@ -38,11 +41,18 @@ def parameters(): return group.asDialogScript() - def beginSave(self, config_node, config_overrides, lop_node, t): - super(AYONRemapPaths, self).beginSave(config_node, - config_overrides, - lop_node, - t) + def beginSave(self, + config_node, + config_overrides, + lop_node, + t, + # Added in Houdini 20.5.182 + stage_variables=_COMPATIBILITY_PLACEHOLDER): + + args = [config_node, config_overrides, lop_node, t] + if stage_variables is not _COMPATIBILITY_PLACEHOLDER: + args.append(stage_variables) + super(AYONRemapPaths, self).beginSave(*args) value = config_node.evalParm("ayon_remap_paths_remap_json") mapping = json.loads(value) diff --git a/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript b/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript index c73479e728..f18c00d472 100644 --- a/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript +++ b/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript @@ -131,6 +131,15 @@ default { "" } parmtag { "script_callback_language" "python" } } + parm { + name "use_ayon_entity_uri" + label "Use AYON Entity URI" + help "When enabled, loads the filepath using the AYON Entity URI instead of the resolved filepath." + type toggle + default { "0" } + parmtag { "script_callback" "hou.phm().on_representation_parms_changed(kwargs['node'], force=True)" } + parmtag { "script_callback_language" "python" } + } parm { name "primpath1" label "Primitive Root" diff --git a/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnCreated b/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnCreated index bd09a7838b..7e07b7f9d6 100644 --- a/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnCreated +++ b/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnCreated @@ -3,3 +3,11 @@ hda_module = node.hdaModule() hda_module.setup_flag_changed_callback(node) node.parm("file").lock(True) + +# Get attribute defaults from settings +# TODO: Clean this up and re-use more from HDA utils lib +from ayon_core.settings import get_current_project_settings +settings = get_current_project_settings() +load_settings = settings["houdini"].get("load", {}).get("LOPLoadAssetLoader", {}) +use_ayon_entity_uri = load_settings.get("use_ayon_entity_uri", False) +node.parm("use_ayon_entity_uri").set(use_ayon_entity_uri) diff --git a/client/ayon_houdini/startup/otls/ayon_lop_load_shot.hda/ayon_8_8Lop_1load__shot_8_81.0/DialogScript b/client/ayon_houdini/startup/otls/ayon_lop_load_shot.hda/ayon_8_8Lop_1load__shot_8_81.0/DialogScript index 980e555efb..140a1f0cb9 100644 --- a/client/ayon_houdini/startup/otls/ayon_lop_load_shot.hda/ayon_8_8Lop_1load__shot_8_81.0/DialogScript +++ b/client/ayon_houdini/startup/otls/ayon_lop_load_shot.hda/ayon_8_8Lop_1load__shot_8_81.0/DialogScript @@ -125,6 +125,15 @@ default { "" } parmtag { "script_callback_language" "python" } } + parm { + name "use_ayon_entity_uri" + label "Use AYON Entity URI" + help "When enabled, loads the filepath using the AYON Entity URI instead of the resolved filepath." + type toggle + default { "0" } + parmtag { "script_callback" "hou.phm().on_representation_parms_changed(kwargs['node'], force=True)" } + parmtag { "script_callback_language" "python" } + } groupcollapsible { name "extra_options" label "Load Options" diff --git a/client/ayon_houdini/startup/otls/ayon_lop_load_shot.hda/ayon_8_8Lop_1load__shot_8_81.0/OnCreated b/client/ayon_houdini/startup/otls/ayon_lop_load_shot.hda/ayon_8_8Lop_1load__shot_8_81.0/OnCreated index bd09a7838b..cac4c01b49 100644 --- a/client/ayon_houdini/startup/otls/ayon_lop_load_shot.hda/ayon_8_8Lop_1load__shot_8_81.0/OnCreated +++ b/client/ayon_houdini/startup/otls/ayon_lop_load_shot.hda/ayon_8_8Lop_1load__shot_8_81.0/OnCreated @@ -3,3 +3,11 @@ hda_module = node.hdaModule() hda_module.setup_flag_changed_callback(node) node.parm("file").lock(True) + +# Get attribute defaults from settings +# TODO: Clean this up and re-use more from HDA utils lib +from ayon_core.settings import get_current_project_settings +settings = get_current_project_settings() +load_settings = settings["houdini"].get("load", {}).get("LOPLoadShotLoader", {}) +use_ayon_entity_uri = load_settings.get("use_ayon_entity_uri", False) +node.parm("use_ayon_entity_uri").set(use_ayon_entity_uri) diff --git a/server/settings/load.py b/server/settings/load.py new file mode 100644 index 0000000000..2c290cbcbb --- /dev/null +++ b/server/settings/load.py @@ -0,0 +1,32 @@ +from ayon_server.settings import BaseSettingsModel, SettingsField + + +# Load Plugins +class LoadUseAYONEntityURIModel(BaseSettingsModel): + use_ayon_entity_uri: bool = SettingsField( + False, + title="Use AYON Entity URI", + description=( + "Use the AYON Entity URI on load instead of the resolved filepath " + "so that the AYON USD Resolver will resovle the paths at runtime. " + "This should be enabled when using the AYON USD Resolver." + ) + ) + + +class LoadPluginsModel(BaseSettingsModel): + LOPLoadAssetLoader: LoadUseAYONEntityURIModel = SettingsField( + default_factory=LoadUseAYONEntityURIModel, + title="LOP Load Asset") + LOPLoadShotLoader: LoadUseAYONEntityURIModel = SettingsField( + default_factory=LoadUseAYONEntityURIModel, + title="LOP Load Shot") + USDSublayerLoader: LoadUseAYONEntityURIModel = SettingsField( + default_factory=LoadUseAYONEntityURIModel, + title="USD Sublayer Loader") + USDReferenceLoader: LoadUseAYONEntityURIModel = SettingsField( + default_factory=LoadUseAYONEntityURIModel, + title="USD Reference Loader") + SopUsdImportLoader: LoadUseAYONEntityURIModel = SettingsField( + default_factory=LoadUseAYONEntityURIModel, + title="USD SOP Import Loader") diff --git a/server/settings/main.py b/server/settings/main.py index 9444519867..8c6bfddfc5 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -16,6 +16,9 @@ PublishPluginsModel, DEFAULT_HOUDINI_PUBLISH_SETTINGS, ) +from .load import ( + LoadPluginsModel, +) from .templated_workfile_build import ( TemplatedWorkfileBuildModel ) @@ -42,6 +45,10 @@ class HoudiniSettings(BaseSettingsModel): default_factory=PublishPluginsModel, title="Publish Plugins", ) + load: LoadPluginsModel = SettingsField( + default_factory=LoadPluginsModel, + title="Loader Plugins", + ) templated_workfile_build: TemplatedWorkfileBuildModel = SettingsField( title="Templated Workfile Build", default_factory=TemplatedWorkfileBuildModel