diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index d1086a1ff3..73be43444e 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -30,14 +30,14 @@ from openpype.lib import ( CLIP_ATTR_DEFS = [ EnumDef( "fps", - items={ - "from_selection": "From selection", - 23.997: "23.976", - 24: "24", - 25: "25", - 29.97: "29.97", - 30: "30" - }, + items=[ + {"value": "from_selection", "label": "From selection"}, + {"value": 23.997, "label": "23.976"}, + {"value": 24, "label": "24"}, + {"value": 25, "label": "25"}, + {"value": 29.97, "label": "29.97"}, + {"value": 30, "label": "30"} + ], label="FPS" ), NumberDef( diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index 0df7b16e64..04db0edc64 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -3,6 +3,7 @@ import re import collections import uuid import json +import copy from abc import ABCMeta, abstractmethod, abstractproperty import six @@ -418,9 +419,8 @@ class EnumDef(AbtractAttrDef): """Enumeration of single item from items. Args: - items: Items definition that can be coverted to - `collections.OrderedDict`. Dictionary represent {value: label} - relation. + items: Items definition that can be coverted using + 'prepare_enum_items'. default: Default value. Must be one key(value) from passed items. """ @@ -433,38 +433,95 @@ class EnumDef(AbtractAttrDef): " defined values on initialization." ).format(self.__class__.__name__)) - items = collections.OrderedDict(items) - if default not in items: - for _key in items.keys(): - default = _key + items = self.prepare_enum_items(items) + item_values = [item["value"] for item in items] + if default not in item_values: + for value in item_values: + default = value break super(EnumDef, self).__init__(key, default=default, **kwargs) self.items = items + self._item_values = set(item_values) def __eq__(self, other): if not super(EnumDef, self).__eq__(other): return False - if set(self.items.keys()) != set(other.items.keys()): - return False - - for key, label in self.items.items(): - if other.items[key] != label: - return False - return True + return self.items == other.items def convert_value(self, value): - if value in self.items: + if value in self._item_values: return value return self.default def serialize(self): data = super(TextDef, self).serialize() - data["items"] = list(self.items) + data["items"] = copy.deepcopy(self.items) return data + @staticmethod + def prepare_enum_items(items): + """Convert items to unified structure. + + Output is a list where each item is dictionary with 'value' + and 'label'. + + ```python + # Example output + [ + {"label": "Option 1", "value": 1}, + {"label": "Option 2", "value": 2}, + {"label": "Option 3", "value": 3} + ] + ``` + + Args: + items (Union[Dict[str, Any], List[Any], List[Dict[str, Any]]): The + items to convert. + + Returns: + List[Dict[str, Any]]: Unified structure of items. + """ + + output = [] + if isinstance(items, dict): + for value, label in items.items(): + output.append({"label": label, "value": value}) + + elif isinstance(items, (tuple, list, set)): + for item in items: + if isinstance(item, dict): + # Validate if 'value' is available + if "value" not in item: + raise KeyError("Item does not contain 'value' key.") + + if "label" not in item: + item["label"] = str(item["value"]) + elif isinstance(item, (list, tuple)): + if len(item) == 2: + value, label = item + elif len(item) == 1: + value = item[0] + label = str(value) + else: + raise ValueError(( + "Invalid items count {}." + " Expected 1 or 2. Value: {}" + ).format(len(item), str(item))) + + item = {"label": label, "value": value} + else: + item = {"label": str(item), "value": item} + output.append(item) + + else: + raise TypeError( + "Unknown type for enum items '{}'".format(type(items)) + ) + + return output class BoolDef(AbtractAttrDef): """Boolean representation. diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index fe9d5dd21b..1266c27fd7 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -1149,11 +1149,11 @@ class PlaceholderLoadMixin(object): loaders_by_name = self.builder.get_loaders_by_name() loader_items = [ - (loader_name, loader.label or loader_name) + {"value": loader_name, "label": loader.label or loader_name} for loader_name, loader in loaders_by_name.items() ] - loader_items = list(sorted(loader_items, key=lambda i: i[1])) + loader_items = list(sorted(loader_items, key=lambda i: i["label"])) options = options or {} return [ attribute_definitions.UISeparatorDef(), @@ -1165,9 +1165,9 @@ class PlaceholderLoadMixin(object): label="Asset Builder Type", default=options.get("builder_type"), items=[ - ("context_asset", "Current asset"), - ("linked_asset", "Linked assets"), - ("all_assets", "All assets") + {"label": "Current asset", "value": "context_asset"}, + {"label": "Linked assets", "value": "linked_asset"}, + {"label": "All assets", "value": "all_assets"}, ], tooltip=( "Asset Builder Type\n" diff --git a/openpype/tools/attribute_defs/widgets.py b/openpype/tools/attribute_defs/widgets.py index 1ffb3d3799..353a424b00 100644 --- a/openpype/tools/attribute_defs/widgets.py +++ b/openpype/tools/attribute_defs/widgets.py @@ -401,9 +401,8 @@ class EnumAttrWidget(_BaseAttrDefWidget): if self.attr_def.tooltip: input_widget.setToolTip(self.attr_def.tooltip) - items = self.attr_def.items - for key, label in items.items(): - input_widget.addItem(label, key) + for item in self.attr_def.items: + input_widget.addItem(item["label"], item["value"]) idx = input_widget.findData(self.attr_def.default) if idx >= 0: