From d7a2c57fd836ae6e26bc0f7e02f3b46a81a70dd8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 31 Jul 2024 13:54:41 +0200 Subject: [PATCH 01/14] Refactor OCIO config handling, introduce fallback mechanism - Added function to extract config path from profile data - Updated global config retrieval to use new function - Introduced fallback mechanism for missing product entities --- client/ayon_core/pipeline/colorspace.py | 67 +++++++++++++++++----- server/settings/conversion.py | 38 +++++++++++++ server/settings/main.py | 75 ++++++++++++++++++++++--- 3 files changed, 156 insertions(+), 24 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 099616ff4a..1986e2d407 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -699,6 +699,33 @@ def get_ocio_config_views(config_path): ) +def _get_config_path_from_profile_data( + data, data_type, template_data) -> dict: + """Get config path from profile data. + + Args: + data (dict[str, Any]): Profile data. + data_type (str): Profile type. + template_data (dict[str, Any]): Template data. + + Returns: + dict[str, str]: Config data with path and template. + """ + template = data[data_type] + result = StringTemplate.format_strict_template( + template, template_data + ) + normalized_path = str(result.normalized()) + if not os.path.exists(normalized_path): + log.warning(f"Path was not found '{normalized_path}'.") + return None + + return { + "path": normalized_path, + "template": template + } + + def _get_global_config_data( project_name, host_name, @@ -717,7 +744,7 @@ def _get_global_config_data( 2. Custom path to ocio config. 3. Path to 'ocioconfig' representation on product. Name of product can be defined in settings. Product name can be regex but exact match is - always preferred. + always preferred. Fallback can be defined in case no product is found. None is returned when no profile is found, when path @@ -755,19 +782,8 @@ def _get_global_config_data( profile_type = profile["type"] if profile_type in ("builtin_path", "custom_path"): - template = profile[profile_type] - result = StringTemplate.format_strict_template( - template, template_data - ) - normalized_path = str(result.normalized()) - if not os.path.exists(normalized_path): - log.warning(f"Path was not found '{normalized_path}'.") - return None - - return { - "path": normalized_path, - "template": template - } + return _get_config_path_from_profile_data( + profile, profile_type, template_data) # TODO decide if this is the right name for representation repre_name = "ocioconfig" @@ -778,7 +794,21 @@ def _get_global_config_data( return None folder_path = folder_info["path"] - product_name = profile["product_name"] + # Backward compatibility for old projects + # TODO remove in future 0.4.4 onwards + product_name = profile.get("product_name") + # TODO: this should be required after backward compatibility is removed + fallback_data = None + published_product_data = profile.get("published_product") + + if product_name is None and published_product_data is None: + log.warning("Product name or published product is missing.") + return None + + if published_product_data: + product_name = published_product_data["product_name"] + fallback_data = published_product_data["fallback"] + if folder_id is None: folder_entity = ayon_api.get_folder_by_path( project_name, folder_path, fields={"id"} @@ -798,6 +828,13 @@ def _get_global_config_data( ) } if not product_entities_by_name: + # TODO: make this required in future 0.4.4 onwards + if fallback_data: + # in case no product was found we need to use fallback + fallback_type = fallback_data["type"] + return _get_config_path_from_profile_data( + fallback_data, fallback_type, template_data + ) log.debug( f"No product entities were found for folder '{folder_path}' with" f" product name filter '{product_name}'." diff --git a/server/settings/conversion.py b/server/settings/conversion.py index f513738603..b29c827cae 100644 --- a/server/settings/conversion.py +++ b/server/settings/conversion.py @@ -4,6 +4,43 @@ from .publish_plugins import DEFAULT_PUBLISH_VALUES +def _convert_imageio_configs_0_4_3(overrides): + """Imageio config settings did change to profiles since 0.4.3.""" + imageio_overrides = overrides.get("imageio") or {} + + # make sure settings are already converted to profiles + if ( + "ocio_config_profiles" not in imageio_overrides + ): + return + + ocio_config_profiles = imageio_overrides["ocio_config_profiles"] + + for inx, profile in enumerate(ocio_config_profiles): + if profile["type"] != "product_name": + continue + + # create new profile + new_profile = { + "type": "published_product", + "published_product": { + "product_name": profile["product_name"], + "fallback": { + "type": "builtin_path", + "builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", + }, + }, + "host_names": profile["host_names"], + "task_names": profile["task_names"], + "task_types": profile["task_types"], + "custom_path": profile["custom_path"], + "builtin_path": profile["builtin_path"], + } + + # replace old profile with new profile + ocio_config_profiles[inx] = new_profile + + def _convert_imageio_configs_0_3_1(overrides): """Imageio config settings did change to profiles since 0.3.1. .""" imageio_overrides = overrides.get("imageio") or {} @@ -82,5 +119,6 @@ def convert_settings_overrides( overrides: dict[str, Any], ) -> dict[str, Any]: _convert_imageio_configs_0_3_1(overrides) + _convert_imageio_configs_0_4_3(overrides) _conver_publish_plugins(overrides) return overrides diff --git a/server/settings/main.py b/server/settings/main.py index 0972ccdfb9..09c9bf0065 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -58,7 +58,14 @@ def _ocio_config_profile_types(): return [ {"value": "builtin_path", "label": "AYON built-in OCIO config"}, {"value": "custom_path", "label": "Path to OCIO config"}, - {"value": "product_name", "label": "Published product"}, + {"value": "published_product", "label": "Published product"}, + ] + + +def _fallback_ocio_config_profile_types(): + return [ + {"value": "builtin_path", "label": "AYON built-in OCIO config"}, + {"value": "custom_path", "label": "Path to OCIO config"}, ] @@ -76,6 +83,49 @@ def _ocio_built_in_paths(): ] +class FallbackProductModel(BaseSettingsModel): + _layout = "expanded" + type: str = SettingsField( + title="Fallback config type", + enum_resolver=_fallback_ocio_config_profile_types, + conditionalEnum=True, + default="builtin_path", + description=( + "Type of config which needs to be used in case published " + "product is not found." + ), + ) + builtin_path: str = SettingsField( + "ACES 1.2", + title="Built-in OCIO config", + enum_resolver=_ocio_built_in_paths, + description=( + "AYON ocio addon distributed OCIO config. " + "Activated addon in bundle is required: 'ayon_ocio' >= 1.1.1" + ), + ) + custom_path: str = SettingsField( + "", + title="OCIO config path", + description="Path to OCIO config. Anatomy formatting is supported.", + ) + + +class PublishedProductModel(BaseSettingsModel): + _layout = "expanded" + product_name: str = SettingsField( + "", + title="Product name", + description=( + "Context related published product name to get OCIO config from. " + "Partial match is supported via use of regex expression." + ), + ) + fallback: FallbackProductModel = SettingsField( + default_factory=FallbackProductModel, + ) + + class CoreImageIOConfigProfilesModel(BaseSettingsModel): _layout = "expanded" host_names: list[str] = SettingsField( @@ -102,19 +152,19 @@ class CoreImageIOConfigProfilesModel(BaseSettingsModel): "ACES 1.2", title="Built-in OCIO config", enum_resolver=_ocio_built_in_paths, + description=( + "AYON ocio addon distributed OCIO config. " + "Activated addon in bundle is required: 'ayon_ocio' >= 1.1.1" + ), ) custom_path: str = SettingsField( "", title="OCIO config path", description="Path to OCIO config. Anatomy formatting is supported.", ) - product_name: str = SettingsField( - "", - title="Product name", - description=( - "Published product name to get OCIO config from. " - "Partial match is supported." - ), + published_product: PublishedProductModel = SettingsField( + default_factory=PublishedProductModel, + title="Published product", ) @@ -294,7 +344,14 @@ def validate_json(cls, value): "type": "builtin_path", "builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", "custom_path": "", - "product_name": "", + "published_product": { + "product_name": "", + "fallback": { + "type": "builtin_path", + "builtin_path": "ACES 1.2", + "custom_path": "" + } + } } ], "file_rules": { From 9375b8bee2985f251e5aad1b6b4309caa1c4c5d1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 31 Jul 2024 13:57:47 +0200 Subject: [PATCH 02/14] Update version references for future removal and conversion functions in colorspace and conversion modules. - Update version references to 0.4.5 for future removal in colorspace module. - Update function name and version reference to 0.4.4 for imageio config conversion in the conversion module. --- client/ayon_core/pipeline/colorspace.py | 4 ++-- server/settings/conversion.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 1986e2d407..106d43d55a 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -795,7 +795,7 @@ def _get_global_config_data( folder_path = folder_info["path"] # Backward compatibility for old projects - # TODO remove in future 0.4.4 onwards + # TODO remove in future 0.4.5 onwards product_name = profile.get("product_name") # TODO: this should be required after backward compatibility is removed fallback_data = None @@ -828,7 +828,7 @@ def _get_global_config_data( ) } if not product_entities_by_name: - # TODO: make this required in future 0.4.4 onwards + # TODO: make this required in future 0.4.5 onwards if fallback_data: # in case no product was found we need to use fallback fallback_type = fallback_data["type"] diff --git a/server/settings/conversion.py b/server/settings/conversion.py index b29c827cae..d99483d21f 100644 --- a/server/settings/conversion.py +++ b/server/settings/conversion.py @@ -4,8 +4,8 @@ from .publish_plugins import DEFAULT_PUBLISH_VALUES -def _convert_imageio_configs_0_4_3(overrides): - """Imageio config settings did change to profiles since 0.4.3.""" +def _convert_imageio_configs_0_4_4(overrides): + """Imageio config settings did change to profiles since 0.4.4.""" imageio_overrides = overrides.get("imageio") or {} # make sure settings are already converted to profiles @@ -119,6 +119,6 @@ def convert_settings_overrides( overrides: dict[str, Any], ) -> dict[str, Any]: _convert_imageio_configs_0_3_1(overrides) - _convert_imageio_configs_0_4_3(overrides) + _convert_imageio_configs_0_4_4(overrides) _conver_publish_plugins(overrides) return overrides From 1e026d8fcb10e053615b1795edc5157600935e32 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 1 Aug 2024 15:50:45 +0200 Subject: [PATCH 03/14] Refactor config data retrieval logic in colorspace module - Removed redundant folder info handling - Added fallback mechanism for missing folder info --- client/ayon_core/pipeline/colorspace.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 106d43d55a..57a36286db 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -788,12 +788,6 @@ def _get_global_config_data( # TODO decide if this is the right name for representation repre_name = "ocioconfig" - folder_info = template_data.get("folder") - if not folder_info: - log.warning("Folder info is missing.") - return None - folder_path = folder_info["path"] - # Backward compatibility for old projects # TODO remove in future 0.4.5 onwards product_name = profile.get("product_name") @@ -809,6 +803,23 @@ def _get_global_config_data( product_name = published_product_data["product_name"] fallback_data = published_product_data["fallback"] + folder_info = template_data.get("folder") + if not folder_info: + log.warning("Folder info is missing.") + + # TODO: this fallback should be required after backward compatibility + # is removed + if fallback_data: + log.info("Using fallback data for ocio config path.") + # in case no product was found we need to use fallback + fallback_type = fallback_data["type"] + return _get_config_path_from_profile_data( + fallback_data, fallback_type, template_data + ) + return None + + folder_path = folder_info["path"] + if folder_id is None: folder_entity = ayon_api.get_folder_by_path( project_name, folder_path, fields={"id"} @@ -827,6 +838,7 @@ def _get_global_config_data( fields={"id", "name"} ) } + if not product_entities_by_name: # TODO: make this required in future 0.4.5 onwards if fallback_data: @@ -874,6 +886,7 @@ def _get_global_config_data( path = get_representation_path_with_anatomy(repre_entity, anatomy) template = repre_entity["attrib"]["template"] + return { "path": path, "template": template, From f9e0b05bf2dac3f596a9c53f32368219072970dd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Aug 2024 13:27:05 +0200 Subject: [PATCH 04/14] Refactor fallback handling in colorspace module Improve handling of fallback data for OCIO config path. Simplified logic and error messages for better clarity. --- client/ayon_core/pipeline/colorspace.py | 53 +++++++++---------------- 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 57a36286db..82025fabaf 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -788,35 +788,27 @@ def _get_global_config_data( # TODO decide if this is the right name for representation repre_name = "ocioconfig" - # Backward compatibility for old projects - # TODO remove in future 0.4.5 onwards - product_name = profile.get("product_name") - # TODO: this should be required after backward compatibility is removed - fallback_data = None - published_product_data = profile.get("published_product") - - if product_name is None and published_product_data is None: - log.warning("Product name or published product is missing.") + published_product_data = profile["published_product"] + product_name = published_product_data["product_name"] + fallback_data = published_product_data["fallback"] + + if product_name == "": + log.error( + "Colorspace OCIO config path cannot be set. " + "Profile is set to published product but `Product name` is empty." + ) return None - if published_product_data: - product_name = published_product_data["product_name"] - fallback_data = published_product_data["fallback"] - folder_info = template_data.get("folder") if not folder_info: log.warning("Folder info is missing.") - # TODO: this fallback should be required after backward compatibility - # is removed - if fallback_data: - log.info("Using fallback data for ocio config path.") - # in case no product was found we need to use fallback - fallback_type = fallback_data["type"] - return _get_config_path_from_profile_data( - fallback_data, fallback_type, template_data - ) - return None + log.info("Using fallback data for ocio config path.") + # in case no product was found we need to use fallback + fallback_type = fallback_data["type"] + return _get_config_path_from_profile_data( + fallback_data, fallback_type, template_data + ) folder_path = folder_info["path"] @@ -840,18 +832,11 @@ def _get_global_config_data( } if not product_entities_by_name: - # TODO: make this required in future 0.4.5 onwards - if fallback_data: - # in case no product was found we need to use fallback - fallback_type = fallback_data["type"] - return _get_config_path_from_profile_data( - fallback_data, fallback_type, template_data - ) - log.debug( - f"No product entities were found for folder '{folder_path}' with" - f" product name filter '{product_name}'." + # in case no product was found we need to use fallback + fallback_type = fallback_data["type"] + return _get_config_path_from_profile_data( + fallback_data, fallback_type, template_data ) - return None # Try to use exact match first, otherwise use first available product product_entity = product_entities_by_name.get(product_name) From d51a04c8ac2b43f2b181709c728e7bda895e7658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 9 Aug 2024 13:31:25 +0200 Subject: [PATCH 05/14] Update client/ayon_core/pipeline/colorspace.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/colorspace.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 82025fabaf..867c3ec22a 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -700,18 +700,19 @@ def get_ocio_config_views(config_path): def _get_config_path_from_profile_data( - data, data_type, template_data) -> dict: + profile, profile_type, template_data +): """Get config path from profile data. Args: - data (dict[str, Any]): Profile data. - data_type (str): Profile type. + profile(dict[str, Any]): Profile data. + profile_type (str): Profile type. template_data (dict[str, Any]): Template data. Returns: dict[str, str]: Config data with path and template. """ - template = data[data_type] + template = profile[profile_type] result = StringTemplate.format_strict_template( template, template_data ) From f87057506a63c12fcc9835e4a093f60da9e1c52c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Aug 2024 13:34:46 +0200 Subject: [PATCH 06/14] adding space --- client/ayon_core/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 867c3ec22a..1e6f98f272 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -705,7 +705,7 @@ def _get_config_path_from_profile_data( """Get config path from profile data. Args: - profile(dict[str, Any]): Profile data. + profile (dict[str, Any]): Profile data. profile_type (str): Profile type. template_data (dict[str, Any]): Template data. From 453995ac5795489bd5eb7390717021c832a321b1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 Sep 2024 14:16:39 +0200 Subject: [PATCH 07/14] Update imageio config conversion to 0.4.5 version Adjust imageio config conversion function to handle changes in settings from 0.4.4 to 0.4.5, ensuring proper profile usage and plugin conversion consistency. --- server/settings/conversion.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/settings/conversion.py b/server/settings/conversion.py index d99483d21f..634e5ab438 100644 --- a/server/settings/conversion.py +++ b/server/settings/conversion.py @@ -4,8 +4,8 @@ from .publish_plugins import DEFAULT_PUBLISH_VALUES -def _convert_imageio_configs_0_4_4(overrides): - """Imageio config settings did change to profiles since 0.4.4.""" +def _convert_imageio_configs_0_4_5(overrides): + """Imageio config settings did change to profiles since 0.4.5.""" imageio_overrides = overrides.get("imageio") or {} # make sure settings are already converted to profiles @@ -119,6 +119,6 @@ def convert_settings_overrides( overrides: dict[str, Any], ) -> dict[str, Any]: _convert_imageio_configs_0_3_1(overrides) - _convert_imageio_configs_0_4_4(overrides) + _convert_imageio_configs_0_4_5(overrides) _conver_publish_plugins(overrides) return overrides From 6085b6bd8237b7049714ecf5f03c20cfd738d086 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 Sep 2024 14:17:26 +0200 Subject: [PATCH 08/14] Refactor imageio settings conversion logic Simplified conditional check for ocio_config_profiles presence. --- server/settings/conversion.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server/settings/conversion.py b/server/settings/conversion.py index 634e5ab438..3b51e46dba 100644 --- a/server/settings/conversion.py +++ b/server/settings/conversion.py @@ -9,9 +9,7 @@ def _convert_imageio_configs_0_4_5(overrides): imageio_overrides = overrides.get("imageio") or {} # make sure settings are already converted to profiles - if ( - "ocio_config_profiles" not in imageio_overrides - ): + if "ocio_config_profiles" not in imageio_overrides: return ocio_config_profiles = imageio_overrides["ocio_config_profiles"] From 07ea80d2d70fd611b7687eba45e8d9a3fe294405 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 Sep 2024 14:23:31 +0200 Subject: [PATCH 09/14] Update fallback type field names in colorspace and settings modules The commit updates the field name from "type" to "fallback_type" for consistency in the colorspace and settings modules. --- client/ayon_core/pipeline/colorspace.py | 2 +- server/settings/main.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 1e6f98f272..8c4f97ab1c 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -806,7 +806,7 @@ def _get_global_config_data( log.info("Using fallback data for ocio config path.") # in case no product was found we need to use fallback - fallback_type = fallback_data["type"] + fallback_type = fallback_data["fallback_type"] return _get_config_path_from_profile_data( fallback_data, fallback_type, template_data ) diff --git a/server/settings/main.py b/server/settings/main.py index 09c9bf0065..249bab85fd 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -85,7 +85,7 @@ def _ocio_built_in_paths(): class FallbackProductModel(BaseSettingsModel): _layout = "expanded" - type: str = SettingsField( + fallback_type: str = SettingsField( title="Fallback config type", enum_resolver=_fallback_ocio_config_profile_types, conditionalEnum=True, @@ -347,7 +347,7 @@ def validate_json(cls, value): "published_product": { "product_name": "", "fallback": { - "type": "builtin_path", + "fallback_type": "builtin_path", "builtin_path": "ACES 1.2", "custom_path": "" } From 6aa31e8788dbeb316b2c3efcc480675aa9386cc0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 Sep 2024 14:24:52 +0200 Subject: [PATCH 10/14] fixing typo Changed function name from '_conver_publish_plugins' to '_convert_publish_plugins' for consistency and clarity. Updated the function call accordingly in 'convert_settings_overrides'. --- server/settings/conversion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/settings/conversion.py b/server/settings/conversion.py index 3b51e46dba..b933d5856f 100644 --- a/server/settings/conversion.py +++ b/server/settings/conversion.py @@ -106,7 +106,7 @@ def _convert_validate_version_0_3_3(publish_overrides): validate_version["plugin_state_profiles"] = [profile] -def _conver_publish_plugins(overrides): +def _convert_publish_plugins(overrides): if "publish" not in overrides: return _convert_validate_version_0_3_3(overrides["publish"]) @@ -118,5 +118,5 @@ def convert_settings_overrides( ) -> dict[str, Any]: _convert_imageio_configs_0_3_1(overrides) _convert_imageio_configs_0_4_5(overrides) - _conver_publish_plugins(overrides) + _convert_publish_plugins(overrides) return overrides From e299b405fc01fc6d5daac5d40a6da1b3955853c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 2 Oct 2024 10:01:33 +0200 Subject: [PATCH 11/14] Update server/settings/conversion.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- server/settings/conversion.py | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/server/settings/conversion.py b/server/settings/conversion.py index b933d5856f..aabf41f8d3 100644 --- a/server/settings/conversion.py +++ b/server/settings/conversion.py @@ -14,30 +14,19 @@ def _convert_imageio_configs_0_4_5(overrides): ocio_config_profiles = imageio_overrides["ocio_config_profiles"] - for inx, profile in enumerate(ocio_config_profiles): - if profile["type"] != "product_name": + for profile in ocio_config_profiles: + if profile.get("type") != "product_name": continue - - # create new profile - new_profile = { - "type": "published_product", - "published_product": { - "product_name": profile["product_name"], - "fallback": { - "type": "builtin_path", - "builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", - }, + + profile["type"] = "published_product" + profile["published_product"] = { + "product_name": profile.pop("product_name"), + "fallback": { + "type": "builtin_path", + "builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", }, - "host_names": profile["host_names"], - "task_names": profile["task_names"], - "task_types": profile["task_types"], - "custom_path": profile["custom_path"], - "builtin_path": profile["builtin_path"], } - # replace old profile with new profile - ocio_config_profiles[inx] = new_profile - def _convert_imageio_configs_0_3_1(overrides): """Imageio config settings did change to profiles since 0.3.1. .""" From 8b14e79629058d4c44f2ecc89f1262bb0a157825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 2 Oct 2024 10:03:28 +0200 Subject: [PATCH 12/14] Update server/settings/conversion.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- server/settings/conversion.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server/settings/conversion.py b/server/settings/conversion.py index aabf41f8d3..315f5a2027 100644 --- a/server/settings/conversion.py +++ b/server/settings/conversion.py @@ -9,11 +9,10 @@ def _convert_imageio_configs_0_4_5(overrides): imageio_overrides = overrides.get("imageio") or {} # make sure settings are already converted to profiles - if "ocio_config_profiles" not in imageio_overrides: + ocio_config_profiles = imageio_overrides.get("ocio_config_profiles") + if not ocio_config_profiles: return - ocio_config_profiles = imageio_overrides["ocio_config_profiles"] - for profile in ocio_config_profiles: if profile.get("type") != "product_name": continue From 589a642d69e85d4a9b9b6364b57cfa3a4d20624b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 2 Oct 2024 10:10:57 +0200 Subject: [PATCH 13/14] Merge branch 'develop' into enhancement/AY-6198_OCIO-fallback-for-profiles-and-templated-values --- .github/workflows/release_trigger.yml | 12 +++ client/ayon_core/hooks/pre_ocio_hook.py | 3 +- client/ayon_core/lib/path_tools.py | 5 +- client/ayon_core/pipeline/__init__.py | 2 - client/ayon_core/pipeline/constants.py | 17 ---- client/ayon_core/plugins/load/delivery.py | 18 +++- .../publish/extract_color_transcode.py | 19 +++- client/ayon_core/plugins/publish/integrate.py | 7 +- .../plugins/publish/validate_file_saved.py | 3 +- client/ayon_core/tools/publisher/window.py | 5 +- server/settings/conversion.py | 35 ++++++- server/settings/publish_plugins.py | 92 ++++++++++++++++--- 12 files changed, 172 insertions(+), 46 deletions(-) create mode 100644 .github/workflows/release_trigger.yml diff --git a/.github/workflows/release_trigger.yml b/.github/workflows/release_trigger.yml new file mode 100644 index 0000000000..01a3b3a682 --- /dev/null +++ b/.github/workflows/release_trigger.yml @@ -0,0 +1,12 @@ +name: 🚀 Release Trigger + +on: + workflow_dispatch: + +jobs: + call-release-trigger: + uses: ynput/ops-repo-automation/.github/workflows/release_trigger.yml@main + secrets: + token: ${{ secrets.YNPUT_BOT_TOKEN }} + email: ${{ secrets.CI_EMAIL }} + user: ${{ secrets.CI_USER }} diff --git a/client/ayon_core/hooks/pre_ocio_hook.py b/client/ayon_core/hooks/pre_ocio_hook.py index 6c30b267bc..7406aa42cf 100644 --- a/client/ayon_core/hooks/pre_ocio_hook.py +++ b/client/ayon_core/hooks/pre_ocio_hook.py @@ -19,7 +19,8 @@ class OCIOEnvHook(PreLaunchHook): "nuke", "hiero", "resolve", - "openrv" + "openrv", + "cinema4d" } launch_types = set() diff --git a/client/ayon_core/lib/path_tools.py b/client/ayon_core/lib/path_tools.py index a65f0f8e13..5c81fbfebf 100644 --- a/client/ayon_core/lib/path_tools.py +++ b/client/ayon_core/lib/path_tools.py @@ -81,7 +81,10 @@ def collect_frames(files): dict: {'/folder/product_v001.0001.png': '0001', ....} """ - patterns = [clique.PATTERNS["frames"]] + # clique.PATTERNS["frames"] supports only `.1001.exr` not `_1001.exr` so + # we use a customized pattern. + pattern = "[_.](?P(?P0*)\\d+)\\.\\D+\\d?$" + patterns = [pattern] collections, remainder = clique.assemble( files, minimum_items=1, patterns=patterns) diff --git a/client/ayon_core/pipeline/__init__.py b/client/ayon_core/pipeline/__init__.py index 4fcea60d5e..8e89029e7b 100644 --- a/client/ayon_core/pipeline/__init__.py +++ b/client/ayon_core/pipeline/__init__.py @@ -3,7 +3,6 @@ AVALON_INSTANCE_ID, AYON_CONTAINER_ID, AYON_INSTANCE_ID, - HOST_WORKFILE_EXTENSIONS, ) from .anatomy import Anatomy @@ -114,7 +113,6 @@ "AVALON_INSTANCE_ID", "AYON_CONTAINER_ID", "AYON_INSTANCE_ID", - "HOST_WORKFILE_EXTENSIONS", # --- Anatomy --- "Anatomy", diff --git a/client/ayon_core/pipeline/constants.py b/client/ayon_core/pipeline/constants.py index 7a08cbb3aa..e6156b3138 100644 --- a/client/ayon_core/pipeline/constants.py +++ b/client/ayon_core/pipeline/constants.py @@ -4,20 +4,3 @@ # Backwards compatibility AVALON_CONTAINER_ID = "pyblish.avalon.container" AVALON_INSTANCE_ID = "pyblish.avalon.instance" - -# TODO get extensions from host implementations -HOST_WORKFILE_EXTENSIONS = { - "blender": [".blend"], - "celaction": [".scn"], - "tvpaint": [".tvpp"], - "fusion": [".comp"], - "harmony": [".zip"], - "houdini": [".hip", ".hiplc", ".hipnc"], - "maya": [".ma", ".mb"], - "nuke": [".nk"], - "hiero": [".hrox"], - "photoshop": [".psd", ".psb"], - "premiere": [".prproj"], - "resolve": [".drp"], - "aftereffects": [".aep"] -} diff --git a/client/ayon_core/plugins/load/delivery.py b/client/ayon_core/plugins/load/delivery.py index 6a0947cc42..5c53d170eb 100644 --- a/client/ayon_core/plugins/load/delivery.py +++ b/client/ayon_core/plugins/load/delivery.py @@ -230,6 +230,11 @@ def deliver(self): self.log ] + # TODO: This will currently incorrectly detect 'resources' + # that are published along with the publish, because those should + # not adhere to the template directly but are ingested in a + # customized way. For example, maya look textures or any publish + # that directly adds files into `instance.data["transfers"]` src_paths = [] for repre_file in repre["files"]: src_path = self.anatomy.fill_root(repre_file["path"]) @@ -261,7 +266,18 @@ def deliver(self): frame = dst_frame if frame is not None: - anatomy_data["frame"] = frame + if repre["context"].get("frame"): + anatomy_data["frame"] = frame + elif repre["context"].get("udim"): + anatomy_data["udim"] = frame + else: + # Fallback + self.log.warning( + "Representation context has no frame or udim" + " data. Supplying sequence frame to '{frame}'" + " formatting data." + ) + anatomy_data["frame"] = frame new_report_items, uploaded = deliver_single_file(*args) report_items.update(new_report_items) self._update_progress(uploaded) diff --git a/client/ayon_core/plugins/publish/extract_color_transcode.py b/client/ayon_core/plugins/publish/extract_color_transcode.py index a28a761e7e..3e54d324e3 100644 --- a/client/ayon_core/plugins/publish/extract_color_transcode.py +++ b/client/ayon_core/plugins/publish/extract_color_transcode.py @@ -122,13 +122,22 @@ def process(self, instance): transcoding_type = output_def["transcoding_type"] target_colorspace = view = display = None + # NOTE: we use colorspace_data as the fallback values for + # the target colorspace. if transcoding_type == "colorspace": + # TODO: Should we fallback to the colorspace + # (which used as source above) ? + # or should we compute the target colorspace from + # current view and display ? target_colorspace = (output_def["colorspace"] or colorspace_data.get("colorspace")) - else: - view = output_def["view"] or colorspace_data.get("view") - display = (output_def["display"] or - colorspace_data.get("display")) + elif transcoding_type == "display_view": + display_view = output_def["display_view"] + view = display_view["view"] or colorspace_data.get("view") + display = ( + display_view["display"] + or colorspace_data.get("display") + ) # both could be already collected by DCC, # but could be overwritten when transcoding @@ -192,7 +201,7 @@ def process(self, instance): new_repre["files"] = new_repre["files"][0] # If the source representation has "review" tag, but its not - # part of the output defintion tags, then both the + # part of the output definition tags, then both the # representations will be transcoded in ExtractReview and # their outputs will clash in integration. if "review" in repre.get("tags", []): diff --git a/client/ayon_core/plugins/publish/integrate.py b/client/ayon_core/plugins/publish/integrate.py index d3f6c04333..e8fe09bab7 100644 --- a/client/ayon_core/plugins/publish/integrate.py +++ b/client/ayon_core/plugins/publish/integrate.py @@ -509,8 +509,11 @@ def _validate_repre_files(self, files, is_sequence_representation): if not is_sequence_representation: files = [files] - if any(os.path.isabs(fname) for fname in files): - raise KnownPublishError("Given file names contain full paths") + for fname in files: + if os.path.isabs(fname): + raise KnownPublishError( + f"Representation file names contains full paths: {fname}" + ) if not is_sequence_representation: return diff --git a/client/ayon_core/plugins/publish/validate_file_saved.py b/client/ayon_core/plugins/publish/validate_file_saved.py index d132ba8d3a..f52998cef3 100644 --- a/client/ayon_core/plugins/publish/validate_file_saved.py +++ b/client/ayon_core/plugins/publish/validate_file_saved.py @@ -36,7 +36,8 @@ class ValidateCurrentSaveFile(pyblish.api.ContextPlugin): label = "Validate File Saved" order = pyblish.api.ValidatorOrder - 0.1 - hosts = ["fusion", "houdini", "max", "maya", "nuke", "substancepainter"] + hosts = ["fusion", "houdini", "max", "maya", "nuke", "substancepainter", + "cinema4d"] actions = [SaveByVersionUpAction, ShowWorkfilesAction] def process(self, context): diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index a8ca605ecb..434c2ca602 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -439,10 +439,13 @@ def set_comment(self, comment): def make_sure_is_visible(self): if self._window_is_visible: self.setWindowState(QtCore.Qt.WindowActive) - else: self.show() + self.raise_() + self.activateWindow() + self.showNormal() + def showEvent(self, event): self._window_is_visible = True super().showEvent(event) diff --git a/server/settings/conversion.py b/server/settings/conversion.py index 315f5a2027..34820b5b32 100644 --- a/server/settings/conversion.py +++ b/server/settings/conversion.py @@ -16,7 +16,7 @@ def _convert_imageio_configs_0_4_5(overrides): for profile in ocio_config_profiles: if profile.get("type") != "product_name": continue - + profile["type"] = "published_product" profile["published_product"] = { "product_name": profile.pop("product_name"), @@ -94,10 +94,43 @@ def _convert_validate_version_0_3_3(publish_overrides): validate_version["plugin_state_profiles"] = [profile] +def _convert_oiio_transcode_0_4_5(publish_overrides): + """ExtractOIIOTranscode plugin changed in 0.4.5.""" + if "ExtractOIIOTranscode" not in publish_overrides: + return + + transcode_profiles = publish_overrides["ExtractOIIOTranscode"].get( + "profiles") + if not transcode_profiles: + return + + for profile in transcode_profiles: + outputs = profile.get("outputs") + if outputs is None: + return + + for output in outputs: + # Already new settings + if "display_view" in output: + break + + # Fix 'display' -> 'display_view' in 'transcoding_type' + transcode_type = output.get("transcoding_type") + if transcode_type == "display": + output["transcoding_type"] = "display_view" + + # Convert 'display' and 'view' to new values + output["display_view"] = { + "display": output.pop("display", ""), + "view": output.pop("view", ""), + } + + def _convert_publish_plugins(overrides): if "publish" not in overrides: return _convert_validate_version_0_3_3(overrides["publish"]) + _convert_oiio_transcode_0_4_5(overrides["publish"]) def convert_settings_overrides( diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 61972e64c4..cdcd28a9ce 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -268,13 +268,36 @@ class ExtractThumbnailModel(BaseSettingsModel): def _extract_oiio_transcoding_type(): return [ {"value": "colorspace", "label": "Use Colorspace"}, - {"value": "display", "label": "Use Display&View"} + {"value": "display_view", "label": "Use Display&View"} ] class OIIOToolArgumentsModel(BaseSettingsModel): additional_command_args: list[str] = SettingsField( - default_factory=list, title="Arguments") + default_factory=list, + title="Arguments", + description="Additional command line arguments for *oiiotool*." + ) + + +class UseDisplayViewModel(BaseSettingsModel): + _layout = "expanded" + display: str = SettingsField( + "", + title="Target Display", + description=( + "Display of the target transform. If left empty, the" + " source Display value will be used." + ) + ) + view: str = SettingsField( + "", + title="Target View", + description=( + "View of the target transform. If left empty, the" + " source View value will be used." + ) + ) class ExtractOIIOTranscodeOutputModel(BaseSettingsModel): @@ -285,22 +308,57 @@ class ExtractOIIOTranscodeOutputModel(BaseSettingsModel): description="Output name (no space)", regex=r"[a-zA-Z0-9_]([a-zA-Z0-9_\.\-]*[a-zA-Z0-9_])?$", ) - extension: str = SettingsField("", title="Extension") + extension: str = SettingsField( + "", + title="Extension", + description=( + "Target extension. If left empty, original" + " extension is used." + ), + ) transcoding_type: str = SettingsField( "colorspace", title="Transcoding type", - enum_resolver=_extract_oiio_transcoding_type + enum_resolver=_extract_oiio_transcoding_type, + conditionalEnum=True, + description=( + "Select the transcoding type for your output, choosing either " + "*Colorspace* or *Display&View* transform." + " Only one option can be applied per output definition." + ), + ) + colorspace: str = SettingsField( + "", + title="Target Colorspace", + description=( + "Choose the desired target colorspace, confirming its availability" + " in the active OCIO config. If left empty, the" + " source colorspace value will be used, resulting in no" + " colorspace conversion." + ) + ) + display_view: UseDisplayViewModel = SettingsField( + title="Use Display&View", + default_factory=UseDisplayViewModel ) - colorspace: str = SettingsField("", title="Colorspace") - display: str = SettingsField("", title="Display") - view: str = SettingsField("", title="View") + oiiotool_args: OIIOToolArgumentsModel = SettingsField( default_factory=OIIOToolArgumentsModel, title="OIIOtool arguments") - tags: list[str] = SettingsField(default_factory=list, title="Tags") + tags: list[str] = SettingsField( + default_factory=list, + title="Tags", + description=( + "Additional tags that will be added to the created representation." + "\nAdd *review* tag to create review from the transcoded" + " representation instead of the original." + ) + ) custom_tags: list[str] = SettingsField( - default_factory=list, title="Custom Tags" + default_factory=list, + title="Custom Tags", + description="Additional custom tags that will be added to the created representation." ) @@ -328,7 +386,13 @@ class ExtractOIIOTranscodeProfileModel(BaseSettingsModel): ) delete_original: bool = SettingsField( True, - title="Delete Original Representation" + title="Delete Original Representation", + description=( + "Choose to preserve or remove the original representation.\n" + "Keep in mind that if the transcoded representation includes" + " a `review` tag, it will take precedence over" + " the original for creating reviews." + ), ) outputs: list[ExtractOIIOTranscodeOutputModel] = SettingsField( default_factory=list, @@ -371,7 +435,7 @@ class ExtractReviewFFmpegModel(BaseSettingsModel): def extract_review_filter_enum(): return [ { - "value": "everytime", + "value": "everytime", # codespell:ignore everytime "label": "Always" }, { @@ -393,7 +457,7 @@ class ExtractReviewFilterModel(BaseSettingsModel): default_factory=list, title="Custom Tags" ) single_frame_filter: str = SettingsField( - "everytime", + "everytime", # codespell:ignore everytime description=( "Use output always / only if input is 1 frame" " image / only if has 2+ frames or is video" @@ -791,7 +855,7 @@ class IntegrateHeroVersionModel(BaseSettingsModel): class CleanUpModel(BaseSettingsModel): _isGroup = True - paterns: list[str] = SettingsField( + paterns: list[str] = SettingsField( # codespell:ignore paterns default_factory=list, title="Patterns (regex)" ) @@ -1225,7 +1289,7 @@ class PublishPuginsModel(BaseSettingsModel): "use_hardlinks": False }, "CleanUp": { - "paterns": [], + "paterns": [], # codespell:ignore paterns "remove_temp_renders": False }, "CleanUpFarm": { From 0b9cea926cf8d061128321708d10f395453cf18c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 2 Oct 2024 10:12:51 +0200 Subject: [PATCH 14/14] Refactor code for better readability and consistency. - Refactored code to improve readability by adjusting indentation and line breaks. - Made changes to ensure consistent formatting of the code. --- server/settings/conversion.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/settings/conversion.py b/server/settings/conversion.py index 04ab0f8d81..34820b5b32 100644 --- a/server/settings/conversion.py +++ b/server/settings/conversion.py @@ -99,7 +99,8 @@ def _convert_oiio_transcode_0_4_5(publish_overrides): if "ExtractOIIOTranscode" not in publish_overrides: return - transcode_profiles = publish_overrides["ExtractOIIOTranscode"].get("profiles") + transcode_profiles = publish_overrides["ExtractOIIOTranscode"].get( + "profiles") if not transcode_profiles: return @@ -108,7 +109,7 @@ def _convert_oiio_transcode_0_4_5(publish_overrides): if outputs is None: return - for output in outputs : + for output in outputs: # Already new settings if "display_view" in output: break