diff --git a/client/ayon_max/api/lib.py b/client/ayon_max/api/lib.py index 84fe59a73a..28cdac2a0d 100644 --- a/client/ayon_max/api/lib.py +++ b/client/ayon_max/api/lib.py @@ -1123,6 +1123,28 @@ def set_correct_workfile_name_for_render_output( return filepath rest = match.group("rest") or "" + + # update the changes in the context instances to reflect the new workfile name + host = registered_host() + create_context = CreateContext(host, discover_publish_plugins=False) + workfile_name_keys = { + "original_workfile_pattern", + # backward compatibility for older versions of Ayon + "AssetName" + } + + has_changes = False + for created_instance in create_context.instances: + for key in workfile_name_keys: + if key not in created_instance: + continue + if created_instance[key] != render_workfile_name: + continue + created_instance[key] = current_workfile_filename + has_changes = True + + if has_changes: + create_context.save_changes() return os.path.join( render_root, current_workfile_filename, @@ -1153,3 +1175,22 @@ def build_general_output_filename( ext = match.group("ext") filename = f"{name}_{element}..{ext}" return os.path.join(output_dir, filename) + + +def is_vray_exr_saverawfile(image_format: str, vr_settings: Any) -> bool: + """Check if V-Ray renderer is set to output raw files in EXR format. + + Args: + image_format (str): The image format being used for rendering. + vr_settings (Any): The V-Ray settings object + + Returns: + bool: True if the image format is EXR and V-Ray is set to save + raw files, False otherwise. + """ + return ( + image_format == "exr" + # safe check + and hasattr(vr_settings, "output_saverawfile") + and vr_settings.output_saverawfile + ) diff --git a/client/ayon_max/api/lib_renderproducts.py b/client/ayon_max/api/lib_renderproducts.py index 4fd4dba01a..b05bb00486 100644 --- a/client/ayon_max/api/lib_renderproducts.py +++ b/client/ayon_max/api/lib_renderproducts.py @@ -17,6 +17,7 @@ get_current_renderer, get_multipass_setting, reformat_filename, + is_vray_exr_saverawfile, ) from ayon_core.pipeline import get_current_project_name from ayon_core.settings import get_project_settings @@ -61,7 +62,8 @@ def get_render_products(self) -> Dict[str, list[str]]: # Always add beauty pass beauty_files = self.get_expected_beauty(start_frame, end_frame, extension) - render_dict["beauty"] = beauty_files + if beauty_files: + render_dict["beauty"] = beauty_files # Optionally add AOVs renderer = get_current_renderer() @@ -218,19 +220,24 @@ def get_vray_render_output( if "GPU" in str(vr_renderer) else vr_renderer ) - if ( - not is_render_element - and image_format == "exr" - and not vray_settings.output_rawfilename - ): - return getattr(vray_settings, "output_splitfilename", "") or rt.rendOutputFilename + is_save_vray_exr_rawfile = is_vray_exr_saverawfile( + image_format, + vray_settings, + ) + if not is_save_vray_exr_rawfile and not is_render_element: + # In non-raw mode V-Ray writes the beauty output to `rt.rendOutputFilename`. + return "" output_attr = ( "output_rawfilename" - if not is_render_element and image_format == "exr" + if not is_render_element + and is_save_vray_exr_rawfile else "output_splitfilename" ) - render_output = getattr(vray_settings, output_attr) - return render_output if render_output else rt.rendOutputFilename + render_output = getattr(vray_settings, output_attr, "") + if not render_output: + return rt.rendOutputFilename + + return render_output def get_arnold_render_output(self, arnold_renderer: Any, extension: str) -> str: """Get the Arnold render output filename. diff --git a/client/ayon_max/api/lib_rendersettings.py b/client/ayon_max/api/lib_rendersettings.py index 440259e7c6..b207947664 100644 --- a/client/ayon_max/api/lib_rendersettings.py +++ b/client/ayon_max/api/lib_rendersettings.py @@ -19,6 +19,7 @@ get_default_render_folder, get_multipass_setting, get_vray_settings, + is_vray_exr_saverawfile, ) @@ -140,12 +141,17 @@ def render_output(self): if img_fmt == "exr": vr_settings.output_saverawfile = True vr_settings.output_rawfilename = f"{output}.{img_fmt}" + else: + if hasattr(vr_settings, "output_saverawfile"): + vr_settings.output_saverawfile = False + if hasattr(vr_settings, "output_rawfilename"): + vr_settings.output_rawfilename = "" + rt.rendOutputFilename = f"{output}..{img_fmt}" if multipass_enabled: rt.rendOutputFilename = output_filename vr_settings.output_splitfilename = f"{output}.{img_fmt}" - else: - rt.rendOutputFilename = f"{output}_tmp..{img_fmt}" + self.render_element_layer(output, width, height, img_fmt) # TODO: supports multipass for different renderers elif renderer_name == "Redshift_Renderer": @@ -155,8 +161,10 @@ def render_output(self): rt.renderers.production.OutputExrMultipart = multipass_enabled # prevent rendering extra files when using V-Ray - rt.rendSaveFile = True if not renderer_name.startswith("V_Ray_") else False - + rt.rendSaveFile = not ( + renderer_name.startswith("V_Ray_") + and is_vray_exr_saverawfile(img_fmt, vr_settings) + ) rt.renderSceneDialog.update() def arnold_setup(self, output_dir, container, multipass_enabled): diff --git a/client/ayon_max/plugins/publish/validate_rendersettings.py b/client/ayon_max/plugins/publish/validate_rendersettings.py index cbe9a2d3ee..d2b27e7573 100644 --- a/client/ayon_max/plugins/publish/validate_rendersettings.py +++ b/client/ayon_max/plugins/publish/validate_rendersettings.py @@ -14,7 +14,8 @@ is_general_default_output_regex_matched, set_correct_workfile_name_for_render_output, build_general_output_filename, - get_vray_settings + get_vray_settings, + is_vray_exr_saverawfile, ) from ayon_max.api.validate_plugins import ValidateRenderSettingsBase @@ -212,9 +213,18 @@ def get_invalid_render_settings( continue render_element_filename = render_elem.GetRenderElementFilename(index) + render_element_dir = os.path.dirname(render_element_filename) + if render_element_dir != os.path.dirname(rt.rendOutputFilename): + invalid.append(( + "Invalid render element output directory", + f"Render element output directory {render_element_dir} " + f"does not match the beauty output directory " + f"{os.path.dirname(rt.rendOutputFilename)}.", + )) + invalid.extend( cls.get_invalid_render_element_directory( - os.path.dirname(render_element_filename), + render_element_dir, workfile_pattern, multi_camera=multicam, cameras=cameras, @@ -253,6 +263,43 @@ def repair(cls, instance: pyblish.api.Instance) -> None: cls.repair_generic_render_settings(instance, renderer_name, renderer) + @classmethod + def _get_useful_attributes_for_repair_action( + cls, + instance: pyblish.api.Instance, + renderer_name: str + ) -> tuple[str, str, bool]: + """Get the attributes needed for repairing the render settings. + + Args: + instance (pyblish.api.Instance): The instance to repair. + renderer_name (str): The renderer name. + + Returns: + tuple[str, str, bool]: A tuple containing the image format, + render directory, + and multipass enabled flag. + """ + image_format = instance.data["imageFormat"] + project_settings = instance.context.data["project_settings"] + multipass_enabled = get_multipass_setting(renderer_name, project_settings) + render_output = rt.rendOutputFilename + render_dir = os.path.dirname(render_output) + if not render_output: + render_output = reset_rendersetting(instance, project_settings) + rt.rendOutputFilename = render_output + render_dir = os.path.dirname(render_output) + if instance.data.get("sync_current_workfile_name", True): + render_dir = set_correct_workfile_name_for_render_output( + instance, + render_dir, + ) + if not render_dir: + render_output = reset_rendersetting(instance, project_settings) + rt.rendOutputFilename = render_output + render_dir = os.path.dirname(render_output) + return image_format, render_dir, multipass_enabled + @classmethod def repair_generic_render_settings( cls, @@ -267,34 +314,20 @@ def repair_generic_render_settings( renderer_name (str): The name of the renderer. renderer (rt.Renderers.current): The current renderer. """ - image_format = instance.data["imageFormat"] - renderoutput = rt.rendOutputFilename - project_settings = instance.context.data["project_settings"] - if not renderoutput: - renderoutput = reset_rendersetting(instance, project_settings) - output_dir = os.path.dirname(renderoutput) - if instance.data.get("sync_current_workfile_name", True): - output_dir = set_correct_workfile_name_for_render_output( + image_format, render_dir, multipass_enabled = ( + cls._get_useful_attributes_for_repair_action( instance, - output_dir, - ) - if not output_dir: - renderoutput = reset_rendersetting(instance, project_settings) - output_dir = os.path.dirname(renderoutput) - if renderer_name == "Redshift_Renderer": - renderer.separateAovFiles = get_multipass_setting( renderer_name, - project_settings, ) + ) + if renderer_name == "Redshift_Renderer": + renderer.separateAovFiles = multipass_enabled if image_format == "exr": - renderer.OutputExrMultipart = get_multipass_setting( - renderer_name, - project_settings, - ) + renderer.OutputExrMultipart = multipass_enabled - filename = os.path.basename(renderoutput) + filename = os.path.basename(rt.rendOutputFilename) rt.rendOutputFilename = build_general_output_filename( - output_dir, + render_dir, filename, ) cls.log.info( @@ -314,21 +347,14 @@ def repair_generic_render_settings( render_element_filename = render_elem.GetRenderElementFilename(index) r_fname = os.path.basename(render_element_filename) - output_dir = os.path.dirname(render_element_filename) - if instance.data.get("sync_current_workfile_name", True): - output_dir = set_correct_workfile_name_for_render_output( - instance, - output_dir, - ) - if not output_dir: - renderoutput = reset_rendersetting(instance, project_settings) - output_dir = os.path.dirname(renderoutput) - output_filename = build_general_output_filename(output_dir, r_fname) + output_filename = build_general_output_filename(render_dir, r_fname) render_elem.SetRenderElementFilename(index, output_filename) cls.log.info( "Render element output filename has been repaired to %s", render_elem.GetRenderElementFilename(index), ) + + rt.rendSaveFile = True rt.renderSceneDialog.update() @@ -533,6 +559,7 @@ def repair_arnold_settings( "Arnold AOV driver filename suffix has been repaired to %s.", driver.filenameSuffix, ) + rt.rendSaveFile = True class ValidateVrayRenderSetting(ValidateGenericRenderSetting): @@ -700,7 +727,8 @@ def get_invalid_render_settings( f"Expected: {multipass_enabled}, Found: {vr_settings.output_splitgbuffer}", )) - if image_format == "exr": + uses_raw_exr_output = is_vray_exr_saverawfile(image_format, vr_settings) + if uses_raw_exr_output: invalid.extend( cls._get_invalid_vray_output( vr_settings.output_rawfilename, @@ -709,24 +737,34 @@ def get_invalid_render_settings( sync_current_workfile_name=sync_current_workfile, ) ) - - if multipass_enabled: + beauty_dir = os.path.dirname(vr_settings.output_rawfilename) + else: invalid.extend( - cls._get_invalid_vray_output( - vr_settings.output_splitfilename, + cls.get_invalid_renderoutput( image_format, workfile_pattern, - sync_current_workfile_name=sync_current_workfile, + sync_current_workfile=sync_current_workfile, ) ) - else: + beauty_dir = os.path.dirname(rt.rendOutputFilename) + + if multipass_enabled: invalid.extend( - cls.get_invalid_renderoutput( + cls._get_invalid_vray_output( + vr_settings.output_splitfilename, image_format, workfile_pattern, - sync_current_workfile=sync_current_workfile, + sync_current_workfile_name=sync_current_workfile, ) ) + render_element_dir = os.path.dirname(vr_settings.output_splitfilename) + + if beauty_dir != render_element_dir: + invalid.append(( + "Invalid V-Ray render element output directory", + f"Render element output directory {render_element_dir} " + f"does not match the beauty output directory {beauty_dir}.", + )) return invalid @@ -761,97 +799,66 @@ def repair_vray_settings( renderer_name (str): The name of the renderer. vr_settings (Any): The V-Ray settings object. """ - image_format = instance.data["imageFormat"] - project_settings = instance.context.data["project_settings"] - multipass_enabled = get_multipass_setting(renderer_name, project_settings) + image_format, render_dir, multipass_enabled = ( + cls._get_useful_attributes_for_repair_action( + instance, + renderer_name, + ) + ) vr_settings.output_splitgbuffer = multipass_enabled - if image_format == "exr": - vr_settings.output_rawfilename = cls._repair_vray_output_filename( - vr_settings.output_rawfilename, - image_format, - instance, + if is_vray_exr_saverawfile(image_format, vr_settings): + vr_settings.output_saverawfile = True + split_beauty_basename = ( + os.path.basename(vr_settings.output_rawfilename) + or os.path.basename(rt.rendOutputFilename) ) - if multipass_enabled: - vr_settings.output_splitfilename = cls._repair_vray_output_filename( - vr_settings.output_splitfilename, + vr_settings.output_rawfilename = cls._repair_vray_output_filename( + split_beauty_basename, + render_dir, image_format, - instance, ) else: - render_output = rt.rendOutputFilename - render_dir = os.path.dirname(render_output) - if instance.data.get("sync_current_workfile_name", True): - render_dir = set_correct_workfile_name_for_render_output( - instance, - render_dir, - ) - if not render_dir: - render_output = reset_rendersetting(instance, project_settings) - render_dir = os.path.dirname(render_output) - filename = os.path.basename(render_output) + filename = os.path.basename(rt.rendOutputFilename) rt.rendOutputFilename = build_general_output_filename( render_dir, filename, ) + if multipass_enabled: + split_aov_basename =( + os.path.basename(vr_settings.output_splitfilename) + or os.path.basename(rt.rendOutputFilename) + ) + vr_settings.output_splitfilename = cls._repair_vray_output_filename( + split_aov_basename, + render_dir, + image_format, + ) + # Save the file when it is not set to save raw EXR output + rt.rendSaveFile = not is_vray_exr_saverawfile(image_format, vr_settings) + rt.renderSceneDialog.update() + @classmethod def _repair_vray_output_filename( cls, filename: str, + render_dir: str, image_format: str, - instance: pyblish.api.Instance, ) -> str: """Repair the V-Ray output filename for the given instance. Args: filename (str): The current V-Ray output filename. + render_dir (str): The directory where the output file will be saved. image_format (str): The image format for the output file. - instance (pyblish.api.Instance): The instance being processed. Returns: str: The repaired V-Ray output filename. """ - project_settings = instance.context.data["project_settings"] - if not filename: - filename = reset_rendersetting(instance, project_settings) - output_dir = os.path.dirname(filename) - if instance.data.get("sync_current_workfile_name", True): - output_dir = set_correct_workfile_name_for_render_output( - instance, - output_dir, - ) - if not output_dir: - filename = reset_rendersetting(instance, project_settings) - output_dir = os.path.dirname(filename) - output_filename = os.path.basename(filename) - return cls._build_vray_output_filename( - output_dir, - output_filename, - image_format, - ) - - @classmethod - def _build_vray_output_filename( - cls, - output_dir: str, - filename: str, - image_format: str, - ) -> str: - """Build the full path for the V-Ray output file. - - Args: - output_dir (str): The directory where the output file will be saved. - filename (str): The name of the output file. - image_format (str): The image format for the output file. - - Returns: - str: The full path to the repaired V-Ray output file. - """ name = os.path.splitext(filename)[0].strip(".") output_filename = f"{name}.{image_format}" - return os.path.join(output_dir, output_filename) - + return os.path.join(render_dir, output_filename) def reset_rendersetting(instance: pyblish.api.Instance, project_settings: dict) -> str: """Reset the render settings for the given instance based on project settings.