diff --git a/client/ayon_houdini/api/lib.py b/client/ayon_houdini/api/lib.py index 762aa1395b..762a3e1cf4 100644 --- a/client/ayon_houdini/api/lib.py +++ b/client/ayon_houdini/api/lib.py @@ -106,7 +106,7 @@ def get_output_parameter(node): elif node_type == "vray_renderer": return node.parm("SettingsOutput_img_file_path") - raise TypeError("Node type '%s' not supported" % node_type) + raise TypeError(f"Node type '{node_type}' is not supported") def get_lops_rop_context_options( @@ -140,7 +140,8 @@ def get_lops_rop_context_options( end: float = ropnode.evalParm("f2") inc: float = ropnode.evalParm("f3") else: - raise ValueError("Unsupported trange value: %s" % trange) + raise ValueError(f"Unsupported trange value: {trange}" + f" for rop node: {ropnode.path()}") rop_context_options["ropcook"] = 1.0 rop_context_options["ropstart"] = start rop_context_options["ropend"] = end @@ -160,7 +161,8 @@ def get_lops_rop_context_options( elif option_type == "float": value: float = ropnode.evalParm(f"optionfloatvalue{i}") else: - raise ValueError(f"Unsupported option type: {option_type}") + raise ValueError(f"Unsupported option type: {option_type}" + f" on rop node: '{ropnode.path()}'") rop_context_options[name] = value return rop_context_options diff --git a/client/ayon_houdini/api/plugin.py b/client/ayon_houdini/api/plugin.py index a621a2f56c..11ed56a854 100644 --- a/client/ayon_houdini/api/plugin.py +++ b/client/ayon_houdini/api/plugin.py @@ -16,13 +16,27 @@ AYON_INSTANCE_ID, AVALON_INSTANCE_ID, load, - publish + publish, + PublishError ) from ayon_core.lib import BoolDef -from .lib import imprint, read, lsattr, add_self_publish_button, render_rop -from .usd import get_ayon_entity_uri_from_representation_context +from .lib import ( + imprint, + read, + lsattr, + add_self_publish_button, + render_rop, + evalParmNoFrame, + get_output_parameter +) +from .usd import get_ayon_entity_uri_from_representation_context +from .colorspace import ( + get_color_management_preferences, + get_scene_linear_colorspace, + ARenderProduct +) SETTINGS_CATEGORY = "houdini" @@ -334,6 +348,34 @@ class HoudiniInstancePlugin(pyblish.api.InstancePlugin): hosts = ["houdini"] settings_category = SETTINGS_CATEGORY + def eval_parm_no_frame(self, rop, parm, **kwargs): + try: + return evalParmNoFrame(rop, parm, **kwargs) + except Exception as exc: + raise PublishError( + f"Failed evaluating parameter '{parm}' on Rop node: {rop.path()}", + detail=str(exc) + ) + + def get_output_parameter(self, node): + try: + return get_output_parameter(node) + except Exception as exc: + raise PublishError( + f"Node type '{node}' is not supported", + detail=str(exc) + ) + + def get_scene_linear_colorspace(self): + try: + return get_scene_linear_colorspace() + except Exception as exc: + raise PublishError( + "Failed to get scene linear colorspace.", + detail=str(exc) + ) + + class HoudiniContextPlugin(pyblish.api.ContextPlugin): """Base class for Houdini context publish plugins.""" diff --git a/client/ayon_houdini/plugins/publish/collect_arnold_rop.py b/client/ayon_houdini/plugins/publish/collect_arnold_rop.py index f8a605ab02..20997dfb6a 100644 --- a/client/ayon_houdini/plugins/publish/collect_arnold_rop.py +++ b/client/ayon_houdini/plugins/publish/collect_arnold_rop.py @@ -5,7 +5,6 @@ import pyblish.api from ayon_houdini.api import plugin -from ayon_houdini.api.lib import evalParmNoFrame class CollectArnoldROPRenderProducts(plugin.HoudiniInstancePlugin): @@ -28,13 +27,13 @@ def process(self, instance): rop = hou.node(instance.data.get("instance_node")) - default_prefix = evalParmNoFrame(rop, "ar_picture") + default_prefix = self.eval_parm_no_frame(rop, "ar_picture") render_products = [] export_prefix = None export_products = [] if instance.data["splitRender"]: - export_prefix = evalParmNoFrame( + export_prefix = self.eval_parm_no_frame( rop, "ar_ass_file", pad_character="0" ) beauty_export_product = self.get_render_product_name( @@ -74,7 +73,7 @@ def process(self, instance): if rop.evalParm("ar_aov_exr_enable_layer_name{}".format(index)): label = rop.evalParm("ar_aov_exr_layer_name{}".format(index)) else: - label = evalParmNoFrame(rop, "ar_aov_label{}".format(index)) + label = self.eval_parm_no_frame(rop, "ar_aov_label{}".format(index)) # NOTE: # we don't collect the actual AOV path but rather assume diff --git a/client/ayon_houdini/plugins/publish/collect_cache_farm.py b/client/ayon_houdini/plugins/publish/collect_cache_farm.py index 53f13af366..0ee66222d2 100644 --- a/client/ayon_houdini/plugins/publish/collect_cache_farm.py +++ b/client/ayon_houdini/plugins/publish/collect_cache_farm.py @@ -1,10 +1,7 @@ import os import hou import pyblish.api -from ayon_houdini.api import ( - lib, - plugin -) +from ayon_houdini.api import plugin class CollectFarmCacheFamily(plugin.HoudiniInstancePlugin): @@ -44,7 +41,7 @@ def process(self, instance): # and CollectKarmaROPRenderProducts # Collect expected files ropnode = hou.node(instance.data["instance_node"]) - output_parm = lib.get_output_parameter(ropnode) + output_parm = self.get_output_parameter(ropnode) expected_filepath = output_parm.eval() files = instance.data.setdefault("files", list()) diff --git a/client/ayon_houdini/plugins/publish/collect_frames.py b/client/ayon_houdini/plugins/publish/collect_frames.py index a442e74835..e6c42d0e82 100644 --- a/client/ayon_houdini/plugins/publish/collect_frames.py +++ b/client/ayon_houdini/plugins/publish/collect_frames.py @@ -4,7 +4,7 @@ import hou # noqa import clique import pyblish.api -from ayon_houdini.api import lib, plugin +from ayon_houdini.api import plugin class CollectFrames(plugin.HoudiniInstancePlugin): @@ -27,7 +27,7 @@ def process(self, instance): # Evaluate the file name at the first frame. ropnode = hou.node(instance.data["instance_node"]) - output_parm = lib.get_output_parameter(ropnode) + output_parm = self.get_output_parameter(ropnode) output = output_parm.evalAtFrame(start_frame) file_name = os.path.basename(output) diff --git a/client/ayon_houdini/plugins/publish/collect_karma_rop.py b/client/ayon_houdini/plugins/publish/collect_karma_rop.py index 5ae898adae..f9cd081f81 100644 --- a/client/ayon_houdini/plugins/publish/collect_karma_rop.py +++ b/client/ayon_houdini/plugins/publish/collect_karma_rop.py @@ -4,7 +4,6 @@ import hou import pyblish.api -from ayon_houdini.api.lib import evalParmNoFrame from ayon_houdini.api import plugin @@ -28,7 +27,7 @@ def process(self, instance): rop = hou.node(instance.data.get("instance_node")) - default_prefix = evalParmNoFrame(rop, "picture") + default_prefix = self.eval_parm_no_frame(rop, "picture") render_products = [] # Default beauty AOV 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 3ba54d2f38..dabdb96903 100644 --- a/client/ayon_houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_houdini/plugins/publish/collect_local_render_instances.py @@ -12,7 +12,6 @@ ColormanagedPyblishPluginMixin ) from ayon_houdini.api import plugin -from ayon_houdini.api.colorspace import get_scene_linear_colorspace class CollectLocalRenderInstances(plugin.HoudiniInstancePlugin, @@ -87,7 +86,7 @@ def process(self, instance): # 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() + colorspace = self.get_scene_linear_colorspace() for aov_name, aov_filepaths in expected_files.items(): dynamic_data = {} diff --git a/client/ayon_houdini/plugins/publish/collect_mantra_rop.py b/client/ayon_houdini/plugins/publish/collect_mantra_rop.py index 2f4cdff320..6a86f220b1 100644 --- a/client/ayon_houdini/plugins/publish/collect_mantra_rop.py +++ b/client/ayon_houdini/plugins/publish/collect_mantra_rop.py @@ -4,7 +4,6 @@ import hou import pyblish.api -from ayon_houdini.api.lib import evalParmNoFrame from ayon_houdini.api import plugin @@ -28,13 +27,13 @@ def process(self, instance): rop = hou.node(instance.data.get("instance_node")) - default_prefix = evalParmNoFrame(rop, "vm_picture") + default_prefix = self.eval_parm_no_frame(rop, "vm_picture") render_products = [] export_prefix = None export_products = [] if instance.data["splitRender"]: - export_prefix = evalParmNoFrame( + export_prefix = self.eval_parm_no_frame( rop, "soho_diskfile", pad_character="0" ) beauty_export_product = self.get_render_product_name( @@ -74,7 +73,7 @@ def process(self, instance): aov_enabled = rop.evalParm(aov_boolean) has_aov_path = rop.evalParm(aov_name) if has_aov_path and aov_enabled == 1: - aov_prefix = evalParmNoFrame(rop, aov_name) + aov_prefix = self.eval_parm_no_frame(rop, aov_name) aov_product = self.get_render_product_name( prefix=aov_prefix, suffix=None ) diff --git a/client/ayon_houdini/plugins/publish/collect_output_node.py b/client/ayon_houdini/plugins/publish/collect_output_node.py index c7af43ec6a..d2fe7b7749 100644 --- a/client/ayon_houdini/plugins/publish/collect_output_node.py +++ b/client/ayon_houdini/plugins/publish/collect_output_node.py @@ -1,5 +1,5 @@ import pyblish.api -from ayon_core.pipeline.publish import KnownPublishError +from ayon_core.pipeline.publish import PublishError from ayon_houdini.api import plugin @@ -67,8 +67,8 @@ def process(self, instance): out_node = node.parm("startnode").evalAsNode() else: - raise KnownPublishError( - "ROP node type '{}' is not supported.".format(node_type) + raise PublishError( + f"ROP node type '{node_type}' is not supported." ) if not out_node: diff --git a/client/ayon_houdini/plugins/publish/collect_redshift_rop.py b/client/ayon_houdini/plugins/publish/collect_redshift_rop.py index 3f79438ee2..465bf273da 100644 --- a/client/ayon_houdini/plugins/publish/collect_redshift_rop.py +++ b/client/ayon_houdini/plugins/publish/collect_redshift_rop.py @@ -4,7 +4,6 @@ import hou import pyblish.api -from ayon_houdini.api.lib import evalParmNoFrame from ayon_houdini.api import plugin @@ -27,12 +26,12 @@ class CollectRedshiftROPRenderProducts(plugin.HoudiniInstancePlugin): def process(self, instance): rop = hou.node(instance.data.get("instance_node")) - default_prefix = evalParmNoFrame(rop, "RS_outputFileNamePrefix") + default_prefix = self.eval_parm_no_frame(rop, "RS_outputFileNamePrefix") beauty_suffix = rop.evalParm("RS_outputBeautyAOVSuffix") export_products = [] if instance.data["splitRender"]: - export_prefix = evalParmNoFrame( + export_prefix = self.eval_parm_no_frame( rop, "RS_archive_file", pad_character="0" ) beauty_export_product = self.get_render_product_name( @@ -80,7 +79,7 @@ def process(self, instance): continue aov_suffix = rop.evalParm(f"RS_aovSuffix_{i}") - aov_prefix = evalParmNoFrame(rop, f"RS_aovCustomPrefix_{i}") + aov_prefix = self.eval_parm_no_frame(rop, f"RS_aovCustomPrefix_{i}") if not aov_prefix: aov_prefix = default_prefix diff --git a/client/ayon_houdini/plugins/publish/collect_render_colorspace.py b/client/ayon_houdini/plugins/publish/collect_render_colorspace.py index cee60d4740..eba16c2e12 100644 --- a/client/ayon_houdini/plugins/publish/collect_render_colorspace.py +++ b/client/ayon_houdini/plugins/publish/collect_render_colorspace.py @@ -1,4 +1,9 @@ -from ayon_houdini.api import plugin, colorspace +from ayon_core.pipeline import PublishError +from ayon_houdini.api import plugin +from ayon_houdini.api.colorspace import ( + ARenderProduct, + get_color_management_preferences +) import pyblish.api @@ -32,14 +37,27 @@ def process(self, instance): "Skipping collecting of render colorspace.") return aov_name = list(expected_files[0].keys()) - render_products_data = colorspace.ARenderProduct(aov_name) + try: + render_products_data = ARenderProduct(aov_name) + except Exception as exc: + raise PublishError( + "Failed to get render products with colorspace.", + detail=str(exc) + ) instance.data["renderProducts"] = render_products_data # Required data for `create_instances_for_aov` - colorspace_data = colorspace.get_color_management_preferences() + try: + colorspace_data = get_color_management_preferences() + except Exception as exc: + raise PublishError( + "Failed to get color management preferences.", + detail=str(exc) + ) + 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() + instance.data["colorspace"] = self.get_scene_linear_colorspace() diff --git a/client/ayon_houdini/plugins/publish/collect_render_products.py b/client/ayon_houdini/plugins/publish/collect_render_products.py index 7223f1ffe9..d54b614867 100644 --- a/client/ayon_houdini/plugins/publish/collect_render_products.py +++ b/client/ayon_houdini/plugins/publish/collect_render_products.py @@ -6,6 +6,7 @@ import pyblish.api +from ayon_core.pipeline.publish import PublishError from ayon_houdini.api import plugin from ayon_houdini.api.usd import ( get_usd_render_rop_rendersettings @@ -107,10 +108,10 @@ def replace(match): filename = os.path.join(dirname, filename_base) filename = filename.replace("\\", "/") - assert "#" in filename, ( - "Couldn't resolve render product name " - "with frame number: %s" % name - ) + if "#" not in filename: + raise PublishError( + f"Couldn't resolve render product name with frame number: {name}" + ) filenames.append(filename) diff --git a/client/ayon_houdini/plugins/publish/collect_usd_layers.py b/client/ayon_houdini/plugins/publish/collect_usd_layers.py index 1073322468..50002451ba 100644 --- a/client/ayon_houdini/plugins/publish/collect_usd_layers.py +++ b/client/ayon_houdini/plugins/publish/collect_usd_layers.py @@ -5,6 +5,7 @@ import pyblish.api from ayon_core.pipeline.create import get_product_name +from ayon_core.pipeline.publish import PublishError from ayon_houdini.api import plugin import ayon_houdini.api.usd as usdlib @@ -71,7 +72,15 @@ def process(self, instance): rop_node = hou.node(instance.data["instance_node"]) save_layers = [] - for layer in usdlib.get_configured_save_layers(rop_node): + try: + layers = usdlib.get_configured_save_layers(rop_node) + except Exception as exc: + raise PublishError( + f"Failed to get USD layers on rop node '{rop_node}'", + detail=str(exc) + ) + + for layer in layers: info = layer.rootPrims.get("HoudiniLayerInfo") save_path = info.customData.get("HoudiniSavePath") @@ -147,10 +156,16 @@ def process(self, instance): # Inherit "use handles" from the source instance # TODO: Do we want to maybe copy full `publish_attributes` instead? - copy_instance_data( - instance, layer_inst, - attr="publish_attributes.CollectRopFrameRange.use_handles" - ) + try: + copy_instance_data( + instance, layer_inst, + attr="publish_attributes.CollectRopFrameRange.use_handles" + ) + except Exception as exc: + raise PublishError( + "Failed to copy instance data.", + detail=str(exc) + ) # Allow this subset to be grouped into a USD Layer on creation layer_inst.data["productGroup"] = ( diff --git a/client/ayon_houdini/plugins/publish/collect_usd_render.py b/client/ayon_houdini/plugins/publish/collect_usd_render.py index 2934cce246..d524437627 100644 --- a/client/ayon_houdini/plugins/publish/collect_usd_render.py +++ b/client/ayon_houdini/plugins/publish/collect_usd_render.py @@ -5,7 +5,6 @@ import pyblish.api from ayon_houdini.api import plugin -from ayon_houdini.api.lib import evalParmNoFrame class CollectUsdRender(plugin.HoudiniInstancePlugin): @@ -31,7 +30,7 @@ def process(self, instance): if instance.data["splitRender"]: # USD file output - lop_output = evalParmNoFrame( + lop_output = self.eval_parm_no_frame( rop, "lopoutput", pad_character="#" ) @@ -40,7 +39,7 @@ def process(self, instance): # TODO: It is possible for a user to disable this # TODO: When enabled I think only the basename of the `lopoutput` # parm is preserved, any parent folders defined are likely ignored - folder = evalParmNoFrame( + folder = self.eval_parm_no_frame( rop, "savetodirectory_directory", pad_character="#" ) diff --git a/client/ayon_houdini/plugins/publish/collect_usd_rop_layer_and_stage.py b/client/ayon_houdini/plugins/publish/collect_usd_rop_layer_and_stage.py index 9f208d51f5..de706f5a8c 100644 --- a/client/ayon_houdini/plugins/publish/collect_usd_rop_layer_and_stage.py +++ b/client/ayon_houdini/plugins/publish/collect_usd_rop_layer_and_stage.py @@ -6,6 +6,7 @@ from pxr import Sdf, Usd import pyblish.api +from ayon_core.pipeline import PublishError from ayon_houdini.api import plugin from ayon_houdini.api.lib import ( get_lops_rop_context_options, @@ -82,7 +83,13 @@ def process(self, instance): lop_node: hou.LopNode rop: hou.RopNode = hou.node(instance.data["instance_node"]) - options = get_lops_rop_context_options(rop) + try: + options = get_lops_rop_context_options(rop) + except hou.Error as exc: + raise PublishError( + f"Failed to get context options on rop: {rop.path()}", + detail=str(exc) + ) # Log the context options self.log.debug( diff --git a/client/ayon_houdini/plugins/publish/collect_vray_rop.py b/client/ayon_houdini/plugins/publish/collect_vray_rop.py index ef0349f872..da14cb89c5 100644 --- a/client/ayon_houdini/plugins/publish/collect_vray_rop.py +++ b/client/ayon_houdini/plugins/publish/collect_vray_rop.py @@ -4,7 +4,6 @@ import hou import pyblish.api -from ayon_houdini.api.lib import evalParmNoFrame from ayon_houdini.api import plugin @@ -28,14 +27,14 @@ def process(self, instance): rop = hou.node(instance.data.get("instance_node")) - default_prefix = evalParmNoFrame(rop, "SettingsOutput_img_file_path") + default_prefix = self.eval_parm_no_frame(rop, "SettingsOutput_img_file_path") render_products = [] # TODO: add render elements if render element export_prefix = None export_products = [] if instance.data["splitRender"]: - export_prefix = evalParmNoFrame( + export_prefix = self.eval_parm_no_frame( rop, "render_export_filepath", pad_character="0" ) beauty_export_product = self.get_render_product_name( diff --git a/client/ayon_houdini/plugins/publish/extract_render.py b/client/ayon_houdini/plugins/publish/extract_render.py index 5004ae53e9..6f0f08a1ca 100644 --- a/client/ayon_houdini/plugins/publish/extract_render.py +++ b/client/ayon_houdini/plugins/publish/extract_render.py @@ -3,6 +3,7 @@ import pyblish.api +from ayon_core.pipeline.publish import PublishError from ayon_houdini.api import plugin @@ -61,7 +62,14 @@ def process(self, instance): # previously rendered version. This situation breaks the publishing. # because There will be missing frames as ROP nodes typically cannot render different # frame ranges for each AOV; they always use the same frame range for all AOVs. - self.render_rop(instance) + try: + self.render_rop(instance) + except Exception as exc: + raise PublishError( + "Render failed or interrupted", + description=f"An Error occurred while rendering {rop_node.path()}", + detail=str(exc) + ) # `ExpectedFiles` is a list that includes one dict. expected_files = instance.data["expectedFiles"][0] @@ -81,7 +89,8 @@ def process(self, instance): if not os.path.exists(frame) ] if missing_frames: - # TODO: Use user friendly error reporting. - raise RuntimeError("Failed to complete render extraction. " - "Missing output files: {}".format( - missing_frames)) + missing_frames = "\n\n - ".join(missing_frames) + raise PublishError( + "Failed to complete render extraction.", + detail=f"Missing output files:\n\n - {missing_frames}" + ) diff --git a/client/ayon_houdini/plugins/publish/extract_rop.py b/client/ayon_houdini/plugins/publish/extract_rop.py index 7e5c5798cd..2ac457e7f0 100644 --- a/client/ayon_houdini/plugins/publish/extract_rop.py +++ b/client/ayon_houdini/plugins/publish/extract_rop.py @@ -2,6 +2,7 @@ import pyblish.api from ayon_core.pipeline import publish +from ayon_core.pipeline.publish import PublishError from ayon_houdini.api import plugin from ayon_houdini.api.lib import splitext @@ -34,7 +35,17 @@ def process(self, instance: pyblish.api.Instance): # This key might be absent because render targets are not yet implemented # for all product types that use this plugin. if creator_attribute.get("render_target", "local") == "local": - self.render_rop(instance) + try: + self.render_rop(instance) + except Exception as exc: + import hou + rop_node = hou.node(instance.data["instance_node"]) + raise PublishError( + "Render failed or interrupted", + description=f"An Error occurred while rendering {rop_node.path()}", + detail=str(exc) + ) + self.validate_expected_frames(instance) # In some cases representation name is not the the extension @@ -73,7 +84,11 @@ def validate_expected_frames(self, instance: pyblish.api.Instance): if not os.path.isfile(os.path.join(staging_dir, filename)) ] if missing_filenames: - raise RuntimeError(f"Missing frames: {missing_filenames}") + missing_filenames = "\n\n - ".join(missing_filenames) + raise PublishError( + "Failed to complete render extraction.", + detail=f"Missing frames:\n\n - {missing_filenames}" + ) def update_representation_data(self, instance: pyblish.api.Instance, diff --git a/client/ayon_houdini/plugins/publish/extract_usd.py b/client/ayon_houdini/plugins/publish/extract_usd.py index 90fd085b21..2e08043a73 100644 --- a/client/ayon_houdini/plugins/publish/extract_usd.py +++ b/client/ayon_houdini/plugins/publish/extract_usd.py @@ -5,6 +5,7 @@ from ayon_core.pipeline.entity_uri import construct_ayon_entity_uri from ayon_core.pipeline.publish.lib import get_instance_expected_output_path +from ayon_core.pipeline.publish import PublishError from ayon_houdini.api import plugin from ayon_houdini.api.lib import render_rop from ayon_houdini.api.usd import remap_paths @@ -45,9 +46,17 @@ def process(self, instance): mapping.update(instance_mapping) with remap_paths(ropnode, mapping): - render_rop(ropnode) - - assert os.path.exists(output), "Output does not exist: %s" % output + try: + render_rop(ropnode) + except Exception as exc: + raise PublishError( + "Render failed or interrupted", + description=f"An Error occurred while rendering {ropnode.path()}", + detail=str(exc) + ) + + if not os.path.exists(output): + PublishError(f"Output does not exist: {output}") if "representations" not in instance.data: instance.data["representations"] = [] @@ -135,5 +144,7 @@ def get_source_paths( # Single file return [os.path.join(staging, files)] - raise TypeError(f"Unsupported type for representation files: {files} " - "(supports list or str)") + raise PublishError( + f"Unsupported type for representation files: {files}" + " (supports list or str)" + ) diff --git a/client/ayon_houdini/plugins/publish/increment_current_file.py b/client/ayon_houdini/plugins/publish/increment_current_file.py index 878500f605..7d2c9ea3cd 100644 --- a/client/ayon_houdini/plugins/publish/increment_current_file.py +++ b/client/ayon_houdini/plugins/publish/increment_current_file.py @@ -4,7 +4,7 @@ from ayon_core.pipeline import registered_host from ayon_core.pipeline.publish import ( get_errored_plugins_from_context, - KnownPublishError + PublishError ) from ayon_houdini.api import plugin @@ -37,7 +37,7 @@ def process(self, context): plugin.__name__ == "HoudiniSubmitPublishDeadline" for plugin in errored_plugins ): - raise KnownPublishError( + raise PublishError( "Skipping incrementing current file because " "submission to deadline failed." ) @@ -46,8 +46,9 @@ def process(self, context): host = registered_host() current_file = host.current_file() if context.data["currentFile"] != current_file: - raise KnownPublishError( - "Collected filename mismatches from current scene name." + raise PublishError( + f"Collected filename '{context.data['currentFile']}'" + f" mismatches from current scene name '{current_file}'." ) new_filepath = version_up(current_file) diff --git a/client/ayon_houdini/plugins/publish/save_scene.py b/client/ayon_houdini/plugins/publish/save_scene.py index e0734da5d1..6921c3d5c8 100644 --- a/client/ayon_houdini/plugins/publish/save_scene.py +++ b/client/ayon_houdini/plugins/publish/save_scene.py @@ -1,6 +1,8 @@ +import inspect import pyblish.api from ayon_core.pipeline import registered_host +from ayon_core.pipeline.publish import PublishError from ayon_houdini.api import plugin @@ -16,12 +18,25 @@ def process(self, context): # Filename must not have changed since collecting host = registered_host() current_file = host.get_current_workfile() - assert context.data['currentFile'] == current_file, ( - "Collected filename from current scene name." - ) + if context.data['currentFile'] != current_file: + raise PublishError( + f"Collected filename '{context.data['currentFile']}' differs" + f" from current scene name '{current_file}'.", + description=self.get_error_description() + ) if host.workfile_has_unsaved_changes(): self.log.info("Saving current file: {}".format(current_file)) host.save_workfile(current_file) else: self.log.debug("No unsaved changes, skipping file save..") + + def get_error_description(self): + return inspect.cleandoc( + """### Scene File Name Change During Publishing + + This error occurs when you validate the scene and then save it as a new file manually, or if you open a new file and continue publishing. + + Please reset the publisher and publish without changing the scene file midway. + """ + ) diff --git a/client/ayon_houdini/plugins/publish/validate_animation_settings.py b/client/ayon_houdini/plugins/publish/validate_animation_settings.py deleted file mode 100644 index 1cc9e24dc9..0000000000 --- a/client/ayon_houdini/plugins/publish/validate_animation_settings.py +++ /dev/null @@ -1,53 +0,0 @@ -import hou - -import pyblish.api -from ayon_core.pipeline.publish import PublishValidationError - -from ayon_houdini.api import lib, plugin - - -class ValidateAnimationSettings(plugin.HoudiniInstancePlugin): - """Validate if the unexpanded string contains the frame ('$F') token - - This validator will only check the output parameter of the node if - the Valid Frame Range is not set to 'Render Current Frame' - - Rules: - If you render out a frame range it is mandatory to have the - frame token - '$F4' or similar - to ensure that each frame gets - written. If this is not the case you will override the same file - every time a frame is written out. - - Examples: - Good: 'my_vbd_cache.$F4.vdb' - Bad: 'my_vbd_cache.vdb' - - """ - - order = pyblish.api.ValidatorOrder - label = "Validate Frame Settings" - families = ["vdbcache"] - - def process(self, instance): - - invalid = self.get_invalid(instance) - if invalid: - raise PublishValidationError( - "Output settings do no match for '%s'" % instance - ) - - @classmethod - def get_invalid(cls, instance): - - node = hou.node(instance.data["instance_node"]) - # Check trange parm, 0 means Render Current Frame - frame_range = node.evalParm("trange") - if frame_range == 0: - return [] - - output_parm = lib.get_output_parameter(node) - unexpanded_str = output_parm.unexpandedString() - - if "$F" not in unexpanded_str: - cls.log.error("No frame token found in '%s'" % node.path()) - return [instance] diff --git a/client/ayon_houdini/plugins/publish/validate_file_extension.py b/client/ayon_houdini/plugins/publish/validate_file_extension.py index 1b3a58f4b3..f1599dcc6d 100644 --- a/client/ayon_houdini/plugins/publish/validate_file_extension.py +++ b/client/ayon_houdini/plugins/publish/validate_file_extension.py @@ -5,7 +5,7 @@ import pyblish.api from ayon_core.pipeline import PublishValidationError -from ayon_houdini.api import lib, plugin +from ayon_houdini.api import plugin class ValidateFileExtension(plugin.HoudiniInstancePlugin): @@ -50,7 +50,7 @@ def get_invalid(cls, instance): families = set(families) # Perform extension check - output = lib.get_output_parameter(node).eval() + output = cls.get_output_parameter(cls, node).eval() _, output_extension = os.path.splitext(output) for family in families: diff --git a/client/ayon_houdini/plugins/publish/validate_frame_token.py b/client/ayon_houdini/plugins/publish/validate_frame_token.py index 46c02ba6f2..0882a2fc83 100644 --- a/client/ayon_houdini/plugins/publish/validate_frame_token.py +++ b/client/ayon_houdini/plugins/publish/validate_frame_token.py @@ -1,8 +1,9 @@ import hou import pyblish.api +from ayon_core.pipeline.publish import PublishValidationError -from ayon_houdini.api import lib, plugin +from ayon_houdini.api import plugin class ValidateFrameToken(plugin.HoudiniInstancePlugin): @@ -31,8 +32,9 @@ def process(self, instance): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError( - "Output settings do no match for '%s'" % instance + raise PublishValidationError( + "No frame token '$F4' found in the output " + f"path of '{invalid[0].path()}'" ) @classmethod @@ -42,11 +44,11 @@ def get_invalid(cls, instance): # Check trange parm, 0 means Render Current Frame frame_range = node.evalParm("trange") if frame_range == 0: - return [] + return - output_parm = lib.get_output_parameter(node) + output_parm = cls.get_output_parameter(cls, node) unexpanded_str = output_parm.unexpandedString() if "$F" not in unexpanded_str: cls.log.error("No frame token found in '%s'" % node.path()) - return [instance] + return [node]