From 71dc3650ace89b6ee77583ad43dd05690be32fa9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 23 Apr 2025 10:25:51 +0200 Subject: [PATCH] Adds explicit resolution override to publisher Adds a plugin that allows users to explicitly override the resolution settings (width, height, pixel aspect) of instances during the publishing process. This provides a way to ensure consistency and accuracy in resolution values across different tasks and product types. The plugin is configurable through the AYON settings, allowing administrators to define the available resolution options and the product types for which the override is enabled. --- .../publish/collect_explicit_resolution.py | 104 ++++++++++++++++++ server/settings/publish_plugins.py | 92 +++++++++++++++- 2 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 client/ayon_core/plugins/publish/collect_explicit_resolution.py diff --git a/client/ayon_core/plugins/publish/collect_explicit_resolution.py b/client/ayon_core/plugins/publish/collect_explicit_resolution.py new file mode 100644 index 0000000000..3ff08cbd34 --- /dev/null +++ b/client/ayon_core/plugins/publish/collect_explicit_resolution.py @@ -0,0 +1,104 @@ +import pyblish.api +from ayon_core.lib import EnumDef +from ayon_core.pipeline import colorspace +from ayon_core.pipeline import publish +from ayon_core.pipeline.publish import PublishError + + +class CollectExplicitResolution( + pyblish.api.InstancePlugin, + publish.AYONPyblishPluginMixin +): + """Collect explicit user defined resolution attributes for instances""" + + label = "Choose Explicit Resolution" + order = pyblish.api.CollectorOrder + 0.49 + settings_category = "core" + + enabled = False + + default_resolution_item = (None, "Don't override") + # Settings + product_types = [] + options = [] + + # caching resoluton items + resolution_items = None + + def process(self, instance): + """Process the instance and collect explicit resolution attributes""" + + # Get the values from the instance data + values = self.get_attr_values_from_data(instance.data) + resolution_value = values.get("explicit_resolution", None) + if resolution_value is None: + return + + # Get the width, height and pixel_aspect from the resolution value + resolution_data = self._get_resolution_values(resolution_value) + + # Set the values to the instance data + instance.data.update(resolution_data) + + def _get_resolution_values(self, resolution_value): + """ + Returns width, height and pixel_aspect from the resolution value + + Arguments: + resolution_value (str): resolution value + + Returns: + dict: dictionary with width, height and pixel_aspect + """ + resolution_items = self._get_resolution_items() + item_values = None + # check if resolution_value is in cached items + if resolution_value in resolution_items: + item_values = resolution_items[resolution_value] + + if item_values: + # if the item is in the cache, get the values from it + return { + "resolutionWidth": item_values["width"], + "resolutionHeight": item_values["height"], + "pixelAspect": item_values["pixel_aspect"] + } + else: + raise PublishError( + f"Invalid resolution value: {resolution_value}") + + @classmethod + def _get_resolution_items(cls): + if cls.resolution_items is None: + resolution_items = {} + for item in cls.options: + item_text = f"{item['width']}x{item['height']}x{item['pixel_aspect']}" + resolution_items[item_text] = item + + cls.resolution_items = resolution_items + + return cls.resolution_items + + @classmethod + def get_attr_defs_for_instance( + cls, create_context, instance + ): + if instance.product_type not in cls.product_types: + return [] + + # Get the resolution items + resolution_items = cls._get_resolution_items() + + items = [cls.default_resolution_item] + # Add all cached resolution items to the dropdown options + for item_text in resolution_items: + items.append((item_text, item_text)) + + return [ + EnumDef( + "explicit_resolution", + items, + default="Don't override", + label="Override Resolution" + ) + ] diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 5f5891e4f4..7ad6c9c506 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -1,4 +1,6 @@ +from collections.abc import Iterable from pydantic import validator +from typing import Any from ayon_server.settings import ( BaseSettingsModel, @@ -8,7 +10,7 @@ from ayon_server.settings import ( ensure_unique_names, task_types_enum, ) - +from ayon_server.exceptions import BadRequestException from ayon_server.types import ColorRGBA_uint8 @@ -157,6 +159,77 @@ class CollectUSDLayerContributionsModel(BaseSettingsModel): return value +class ResolutionOptionsModel(BaseSettingsModel): + _layout = "compact" + width: int = SettingsField( + 1920, + ge=0, + le=100000, + title="Width", + description=( + "Width resolution number value"), + placeholder="Width" + ) + height: int = SettingsField( + 1080, + title="Height", + ge=0, + le=100000, + description=( + "Height resolution number value"), + placeholder="Height" + ) + pixel_aspect: float = SettingsField( + 1.0, + title="Pixel aspect", + ge=0.0, + le=100000.0, + description=( + "Pixel Aspect resolution decimal number value"), + placeholder="Pixel aspect" + ) + + +def ensure_unique_resolution_option( + objects: Iterable[Any], field_name: str | None = None) -> None: # noqa: C901 + """Ensure a list of objects have unique option attributes. + + This function checks if the list of objects has unique 'width', + 'height' and 'pixel_aspect' properties. + """ + options = [] + for obj in objects: + item_test_text = f"{obj.width}x{obj.height}x{obj.pixel_aspect}" + if item_test_text not in options: + options.append(item_test_text) + else: + raise BadRequestException( + f"Duplicate option '{item_test_text}'") + + +class CollectExplicitResolutionModel(BaseSettingsModel): + enabled: bool = SettingsField(True, title="Enabled") + product_types: list[str] = SettingsField( + default_factory=list, + title="Product types", + description=( + "Only activate the attribute for following product types." + ) + ) + options: list[ResolutionOptionsModel] = SettingsField( + default_factory=list, + title="Resolution options", + description=( + "Options to be provided in publisher attribute" + ) + ) + + @validator("options") + def validate_unique_options(cls, value): + ensure_unique_resolution_option(value) + return value + + class AyonEntityURIModel(BaseSettingsModel): use_ayon_entity_uri: bool = SettingsField( title="Use AYON Entity URI", @@ -988,6 +1061,10 @@ class PublishPuginsModel(BaseSettingsModel): title="Collect USD Layer Contributions", ) ) + CollectExplicitResolution: CollectExplicitResolutionModel = SettingsField( + default_factory=CollectExplicitResolutionModel, + title="Collect Explicit Resolution" + ) ValidateEditorialAssetName: ValidateBaseModel = SettingsField( default_factory=ValidateBaseModel, title="Validate Editorial Asset Name" @@ -1162,6 +1239,19 @@ DEFAULT_PUBLISH_VALUES = { }, ] }, + "CollectExplicitResolution": { + "enabled": True, + "product_types": [ + "shot" + ], + "options": [ + { + "width": 2048, + "height": 1080, + "aspect_ratio": 1.5, + } + ] + }, "ValidateEditorialAssetName": { "enabled": True, "optional": False,