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,