From 91df75f8eaaf0b6a74113c1d4facfd9be597d90f Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 30 May 2025 16:10:20 +0200 Subject: [PATCH 01/20] :sparkles: add product base type support to loaders --- client/ayon_core/pipeline/load/plugins.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 4a11b929cc..7a92ed943d 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -1,3 +1,5 @@ +"""Plugins for loading representations and products into host applications.""" +from __future__ import annotations import os import logging @@ -15,7 +17,8 @@ from .utils import get_representation_path_from_context class LoaderPlugin(list): """Load representation into host application""" - product_types = set() + product_types: set[str] = set() + product_base_types: set[str] = set() representations = set() extensions = {"*"} order = 0 @@ -122,9 +125,11 @@ class LoaderPlugin(list): plugin_repre_names = cls.get_representations() plugin_product_types = cls.product_types + plugin_product_base_types = cls.product_base_types if ( not plugin_repre_names or not plugin_product_types + or not plugin_product_base_types or not cls.extensions ): return False @@ -147,10 +152,20 @@ class LoaderPlugin(list): if "*" in plugin_product_types: return True + plugin_product_base_types = set(plugin_product_base_types) + if "*" in plugin_product_base_types: + # If plugin supports all product base types, then it is compatible + # with any product type. + return True + product_entity = context["product"] product_type = product_entity["productType"] + product_base_type = product_entity.get("productBaseType") - return product_type in plugin_product_types + if product_type in plugin_product_types: + return True + + return product_base_type in plugin_product_base_types @classmethod def get_representations(cls): From fac933c16ab73d7f893e27ab6f0a022e2dd5dcf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Fri, 30 May 2025 16:46:18 +0200 Subject: [PATCH 02/20] :recycle: make the check backwards compatible Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- client/ayon_core/pipeline/load/plugins.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 7a92ed943d..32b96e3e7a 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -128,8 +128,7 @@ class LoaderPlugin(list): plugin_product_base_types = cls.product_base_types if ( not plugin_repre_names - or not plugin_product_types - or not plugin_product_base_types + or (not plugin_product_types and not plugin_product_base_types) or not cls.extensions ): return False From 6ea717bc3624cd17da53dd676772278704ac87d3 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 6 Jun 2025 10:01:32 +0200 Subject: [PATCH 03/20] :wrench: WIP on product base type support in loader tool --- client/ayon_core/tools/loader/abstract.py | 140 +++++++++++++----- .../ayon_core/tools/loader/models/products.py | 132 +++++++++++++++-- 2 files changed, 226 insertions(+), 46 deletions(-) diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py index d0d7cd430b..741eb59f81 100644 --- a/client/ayon_core/tools/loader/abstract.py +++ b/client/ayon_core/tools/loader/abstract.py @@ -1,5 +1,6 @@ +from __future__ import annotations from abc import ABC, abstractmethod -from typing import List +from typing import List, Optional, TypedDict from ayon_core.lib.attribute_definitions import ( AbstractAttrDef, @@ -8,15 +9,62 @@ from ayon_core.lib.attribute_definitions import ( ) +IconData = TypedDict("IconData", { + "type": str, + "name": str, + "color": str +}) + +ProductBaseTypeItemData = TypedDict("ProductBaseTypeItemData", { + "name": str, + "icon": IconData +}) + + +VersionItemData = TypedDict("VersionItemData", { + "version_id": str, + "version": int, + "is_hero": bool, + "product_id": str, + "task_id": Optional[str], + "thumbnail_id": Optional[str], + "published_time": Optional[str], + "author": Optional[str], + "status": Optional[str], + "frame_range": Optional[str], + "duration": Optional[int], + "handles": Optional[str], + "step": Optional[int], + "comment": Optional[str], + "source": Optional[str] +}) + + +ProductItemData = TypedDict("ProductItemData", { + "product_id": str, + "product_type": str, + "product_base_type": str, + "product_name": str, + "product_icon": IconData, + "product_type_icon": IconData, + "product_base_type_icon": IconData, + "group_name": str, + "folder_id": str, + "folder_label": str, + "version_items": dict[str, VersionItemData], + "product_in_scene": bool +}) + + class ProductTypeItem: """Item representing product type. Args: name (str): Product type name. - icon (dict[str, Any]): Product type icon definition. + icon (IconData): Product type icon definition. """ - def __init__(self, name, icon): + def __init__(self, name: str, icon: IconData): self.name = name self.icon = icon @@ -31,6 +79,24 @@ class ProductTypeItem: return cls(**data) +class ProductBaseTypeItem: + """Item representing product base type.""" + + def __init__(self, name: str, icon: IconData): + self.name = name + self.icon = icon + + def to_data(self) -> ProductBaseTypeItemData: + return { + "name": self.name, + "icon": self.icon, + } + + @classmethod + def from_data(cls, data: ProductBaseTypeItemData): + return cls(**data) + + class ProductItem: """Product item with it versions. @@ -38,8 +104,8 @@ class ProductItem: product_id (str): Product id. product_type (str): Product type. product_name (str): Product name. - product_icon (dict[str, Any]): Product icon definition. - product_type_icon (dict[str, Any]): Product type icon definition. + product_icon (IconData): Product icon definition. + product_type_icon (IconData): Product type icon definition. product_in_scene (bool): Is product in scene (only when used in DCC). group_name (str): Group name. folder_id (str): Folder id. @@ -49,35 +115,42 @@ class ProductItem: def __init__( self, - product_id, - product_type, - product_name, - product_icon, - product_type_icon, - product_in_scene, - group_name, - folder_id, - folder_label, - version_items, + product_id: str, + product_type: str, + product_base_type: str, + product_name: str, + product_icon: IconData, + product_type_icon: IconData, + product_base_type_icon: IconData, + group_name: str, + folder_id: str, + folder_label: str, + version_items: dict[str, VersionItem], + *, + product_in_scene: bool, ): self.product_id = product_id self.product_type = product_type + self.product_base_type = product_base_type self.product_name = product_name self.product_icon = product_icon self.product_type_icon = product_type_icon + self.product_base_type_icon = product_base_type_icon self.product_in_scene = product_in_scene self.group_name = group_name self.folder_id = folder_id self.folder_label = folder_label self.version_items = version_items - def to_data(self): + def to_data(self) -> ProductItemData: return { "product_id": self.product_id, "product_type": self.product_type, + "product_base_type": self.product_base_type, "product_name": self.product_name, "product_icon": self.product_icon, "product_type_icon": self.product_type_icon, + "product_base_type_icon": self.product_base_type_icon, "product_in_scene": self.product_in_scene, "group_name": self.group_name, "folder_id": self.folder_id, @@ -124,21 +197,22 @@ class VersionItem: def __init__( self, - version_id, - version, - is_hero, - product_id, - task_id, - thumbnail_id, - published_time, - author, - status, - frame_range, - duration, - handles, - step, - comment, - source, + *, + version_id: str, + version: int, + is_hero: bool, + product_id: str, + task_id: Optional[str] = None, + thumbnail_id: Optional[str] = None, + published_time: Optional[str] = None, + author: Optional[str] = None, + status: Optional[str] = None, + frame_range: Optional[str] = None, + duration: Optional[int] = None, + handles: Optional[str] = None, + step: Optional[int] = None, + comment: Optional[str] = None, + source: Optional[str] = None, ): self.version_id = version_id self.product_id = product_id @@ -198,7 +272,7 @@ class VersionItem: def __le__(self, other): return self.__eq__(other) or self.__lt__(other) - def to_data(self): + def to_data(self) -> VersionItemData: return { "version_id": self.version_id, "product_id": self.product_id, @@ -218,7 +292,7 @@ class VersionItem: } @classmethod - def from_data(cls, data): + def from_data(cls, data: VersionItemData): return cls(**data) diff --git a/client/ayon_core/tools/loader/models/products.py b/client/ayon_core/tools/loader/models/products.py index 34acc0550c..da2b049f50 100644 --- a/client/ayon_core/tools/loader/models/products.py +++ b/client/ayon_core/tools/loader/models/products.py @@ -1,19 +1,29 @@ +"""Products model for loader tools.""" +from __future__ import annotations import collections import contextlib +from typing import TYPE_CHECKING, Iterable, Optional import arrow import ayon_api from ayon_api.operations import OperationsSession + from ayon_core.lib import NestedCacheItem from ayon_core.style import get_default_entity_icon_color from ayon_core.tools.loader.abstract import ( + IconData, ProductTypeItem, + ProductBaseTypeItem, ProductItem, VersionItem, RepreItem, ) +if TYPE_CHECKING: + from ayon_api.typing import ProductBaseTypeDict, ProductDict, VersionDict + + PRODUCTS_MODEL_SENDER = "products.model" @@ -70,9 +80,10 @@ def version_item_from_entity(version): def product_item_from_entity( - product_entity, + product_entity: ProductDict, version_entities, - product_type_items_by_name, + product_type_items_by_name: dict[str, ProductTypeItem], + product_base_type_items_by_name: dict[str, ProductBaseTypeItem], folder_label, product_in_scene, ): @@ -88,9 +99,21 @@ def product_item_from_entity( # Cache the item for future use product_type_items_by_name[product_type] = product_type_item - product_type_icon = product_type_item.icon + product_base_type = product_entity.get("productBaseType") + product_base_type_item = product_base_type_items_by_name.get( + product_base_type) + # Same as for product type item above. Not sure if this is still needed + # though. + if product_base_type_item is None: + product_base_type_item = create_default_product_base_type_item( + product_base_type) + # Cache the item for future use + product_base_type_items_by_name[product_base_type] = ( + product_base_type_item) - product_icon = { + product_type_icon = product_type_item.icon + product_base_type_icon = product_base_type_item.icon + product_icon: IconData = { "type": "awesome-font", "name": "fa.file-o", "color": get_default_entity_icon_color(), @@ -103,9 +126,11 @@ def product_item_from_entity( return ProductItem( product_id=product_entity["id"], product_type=product_type, + product_base_type=product_base_type, product_name=product_entity["name"], product_icon=product_icon, product_type_icon=product_type_icon, + product_base_type_icon=product_base_type_icon, product_in_scene=product_in_scene, group_name=group, folder_id=product_entity["folderId"], @@ -114,11 +139,12 @@ def product_item_from_entity( ) -def product_type_item_from_data(product_type_data): +def product_type_item_from_data( + product_type_data: ProductDict) -> ProductTypeItem: # TODO implement icon implementation # icon = product_type_data["icon"] # color = product_type_data["color"] - icon = { + icon: IconData = { "type": "awesome-font", "name": "fa.folder", "color": "#0091B2", @@ -127,8 +153,30 @@ def product_type_item_from_data(product_type_data): return ProductTypeItem(product_type_data["name"], icon) -def create_default_product_type_item(product_type): - icon = { +def product_base_type_item_from_data( + product_base_type_data: ProductBaseTypeDict +) -> ProductBaseTypeItem: + """Create product base type item from data. + + Args: + product_base_type_data (ProductBaseTypeDict): Product base type data. + + Returns: + ProductBaseTypeDict: Product base type item. + + """ + icon: IconData = { + "type": "awesome-font", + "name": "fa.folder", + "color": "#0091B2", + } + return ProductBaseTypeItem( + name=product_base_type_data["name"], + icon=icon) + + +def create_default_product_type_item(product_type: str) -> ProductTypeItem: + icon: IconData = { "type": "awesome-font", "name": "fa.folder", "color": "#0091B2", @@ -136,10 +184,28 @@ def create_default_product_type_item(product_type): return ProductTypeItem(product_type, icon) +def create_default_product_base_type_item( + product_base_type: str) -> ProductBaseTypeItem: + """Create default product base type item. + + Args: + product_base_type (str): Product base type name. + + Returns: + ProductBaseTypeItem: Default product base type item. + """ + icon: IconData = { + "type": "awesome-font", + "name": "fa.folder", + "color": "#0091B2", + } + return ProductBaseTypeItem(product_base_type, icon) + + class ProductsModel: """Model for products, version and representation. - All of the entities are product based. This model prepares data for UI + All the entities are product based. This model prepares data for UI and caches it for faster access. Note: @@ -161,6 +227,8 @@ class ProductsModel: # Cache helpers self._product_type_items_cache = NestedCacheItem( levels=1, default_factory=list, lifetime=self.lifetime) + self._product_base_type_items_cache = NestedCacheItem( + levels=1, default_factory=list, lifetime=self.lifetime) self._product_items_cache = NestedCacheItem( levels=2, default_factory=dict, lifetime=self.lifetime) self._repre_items_cache = NestedCacheItem( @@ -199,6 +267,31 @@ class ProductsModel: ]) return cache.get_data() + def get_product_base_type_items( + self, + project_name: Optional[str]) -> list[ProductBaseTypeItem]: + """Product base type items for project. + + Args: + project_name (optional, str): Project name. + + Returns: + list[ProductBaseTypeDict]: Product base type items. + + """ + if not project_name: + return [] + + cache = self._product_base_type_items_cache[project_name] + if not cache.is_valid: + product_base_types = ayon_api.get_project_product_base_types( + project_name) + cache.update_data([ + product_base_type_item_from_data(product_base_type) + for product_base_type in product_base_types + ]) + return cache.get_data() + def get_product_items(self, project_name, folder_ids, sender): """Product items with versions for project and folder ids. @@ -449,11 +542,12 @@ class ProductsModel: def _create_product_items( self, - project_name, - products, - versions, + project_name: str, + products: Iterable[ProductDict], + versions: Iterable[VersionDict], folder_items=None, product_type_items=None, + product_base_type_items: Optional[Iterable[ProductBaseTypeItem]] = None ): if folder_items is None: folder_items = self._controller.get_folder_items(project_name) @@ -461,6 +555,11 @@ class ProductsModel: if product_type_items is None: product_type_items = self.get_product_type_items(project_name) + if product_base_type_items is None: + product_base_type_items = self.get_product_base_type_items( + project_name + ) + loaded_product_ids = self._controller.get_loaded_product_ids() versions_by_product_id = collections.defaultdict(list) @@ -470,7 +569,13 @@ class ProductsModel: product_type_item.name: product_type_item for product_type_item in product_type_items } - output = {} + + product_base_type_items_by_name: dict[str, ProductBaseTypeItem] = { + product_base_type_item.name: product_base_type_item + for product_base_type_item in product_base_type_items + } + + output: dict[str, ProductItem] = {} for product in products: product_id = product["id"] folder_id = product["folderId"] @@ -484,6 +589,7 @@ class ProductsModel: product, versions, product_type_items_by_name, + product_base_type_items_by_name, folder_item.label, product_id in loaded_product_ids, ) From 3a2f470dce3690c335466ecc01d1ff14588753be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 6 Jun 2025 14:03:31 +0200 Subject: [PATCH 04/20] :sparkles: show product base type in the loader --- client/ayon_core/tools/loader/abstract.py | 26 +++++++-- .../ayon_core/tools/loader/models/products.py | 2 +- .../tools/loader/ui/products_model.py | 55 +++++++++++-------- .../tools/loader/ui/products_widget.py | 1 + 4 files changed, 55 insertions(+), 29 deletions(-) diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py index 741eb59f81..d6a4bf40cb 100644 --- a/client/ayon_core/tools/loader/abstract.py +++ b/client/ayon_core/tools/loader/abstract.py @@ -1,14 +1,15 @@ +"""Abstract base classes for loader tool.""" from __future__ import annotations + from abc import ABC, abstractmethod from typing import List, Optional, TypedDict from ayon_core.lib.attribute_definitions import ( AbstractAttrDef, - serialize_attr_defs, deserialize_attr_defs, + serialize_attr_defs, ) - IconData = TypedDict("IconData", { "type": str, "name": str, @@ -80,20 +81,37 @@ class ProductTypeItem: class ProductBaseTypeItem: - """Item representing product base type.""" + """Item representing the product base type.""" def __init__(self, name: str, icon: IconData): + """Initialize product base type item.""" self.name = name self.icon = icon def to_data(self) -> ProductBaseTypeItemData: + """Convert item to data dictionary. + + Returns: + ProductBaseTypeItemData: Data representation of the item. + + """ return { "name": self.name, "icon": self.icon, } @classmethod - def from_data(cls, data: ProductBaseTypeItemData): + def from_data( + cls, data: ProductBaseTypeItemData) -> ProductBaseTypeItem: + """Create item from data dictionary. + + Args: + data (ProductBaseTypeItemData): Data to create item from. + + Returns: + ProductBaseTypeItem: Item created from the provided data. + + """ return cls(**data) diff --git a/client/ayon_core/tools/loader/models/products.py b/client/ayon_core/tools/loader/models/products.py index da2b049f50..41919461d0 100644 --- a/client/ayon_core/tools/loader/models/products.py +++ b/client/ayon_core/tools/loader/models/products.py @@ -270,7 +270,7 @@ class ProductsModel: def get_product_base_type_items( self, project_name: Optional[str]) -> list[ProductBaseTypeItem]: - """Product base type items for project. + """Product base type items for the project. Args: project_name (optional, str): Project name. diff --git a/client/ayon_core/tools/loader/ui/products_model.py b/client/ayon_core/tools/loader/ui/products_model.py index cebae9bca7..24050fc0c1 100644 --- a/client/ayon_core/tools/loader/ui/products_model.py +++ b/client/ayon_core/tools/loader/ui/products_model.py @@ -16,31 +16,32 @@ TASK_ID_ROLE = QtCore.Qt.UserRole + 5 PRODUCT_ID_ROLE = QtCore.Qt.UserRole + 6 PRODUCT_NAME_ROLE = QtCore.Qt.UserRole + 7 PRODUCT_TYPE_ROLE = QtCore.Qt.UserRole + 8 -PRODUCT_TYPE_ICON_ROLE = QtCore.Qt.UserRole + 9 -PRODUCT_IN_SCENE_ROLE = QtCore.Qt.UserRole + 10 -VERSION_ID_ROLE = QtCore.Qt.UserRole + 11 -VERSION_HERO_ROLE = QtCore.Qt.UserRole + 12 -VERSION_NAME_ROLE = QtCore.Qt.UserRole + 13 -VERSION_NAME_EDIT_ROLE = QtCore.Qt.UserRole + 14 -VERSION_PUBLISH_TIME_ROLE = QtCore.Qt.UserRole + 15 -VERSION_STATUS_NAME_ROLE = QtCore.Qt.UserRole + 16 -VERSION_STATUS_SHORT_ROLE = QtCore.Qt.UserRole + 17 -VERSION_STATUS_COLOR_ROLE = QtCore.Qt.UserRole + 18 -VERSION_STATUS_ICON_ROLE = QtCore.Qt.UserRole + 19 -VERSION_AUTHOR_ROLE = QtCore.Qt.UserRole + 20 -VERSION_FRAME_RANGE_ROLE = QtCore.Qt.UserRole + 21 -VERSION_DURATION_ROLE = QtCore.Qt.UserRole + 22 -VERSION_HANDLES_ROLE = QtCore.Qt.UserRole + 23 -VERSION_STEP_ROLE = QtCore.Qt.UserRole + 24 -VERSION_AVAILABLE_ROLE = QtCore.Qt.UserRole + 25 -VERSION_THUMBNAIL_ID_ROLE = QtCore.Qt.UserRole + 26 -ACTIVE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 27 -REMOTE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 28 -REPRESENTATIONS_COUNT_ROLE = QtCore.Qt.UserRole + 29 -SYNC_ACTIVE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 30 -SYNC_REMOTE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 31 +PRODUCT_BASE_TYPE_ROLE = QtCore.Qt.UserRole + 9 +PRODUCT_TYPE_ICON_ROLE = QtCore.Qt.UserRole + 10 +PRODUCT_IN_SCENE_ROLE = QtCore.Qt.UserRole + 11 +VERSION_ID_ROLE = QtCore.Qt.UserRole + 12 +VERSION_HERO_ROLE = QtCore.Qt.UserRole + 13 +VERSION_NAME_ROLE = QtCore.Qt.UserRole + 14 +VERSION_NAME_EDIT_ROLE = QtCore.Qt.UserRole + 15 +VERSION_PUBLISH_TIME_ROLE = QtCore.Qt.UserRole + 16 +VERSION_STATUS_NAME_ROLE = QtCore.Qt.UserRole + 17 +VERSION_STATUS_SHORT_ROLE = QtCore.Qt.UserRole + 18 +VERSION_STATUS_COLOR_ROLE = QtCore.Qt.UserRole + 19 +VERSION_STATUS_ICON_ROLE = QtCore.Qt.UserRole + 20 +VERSION_AUTHOR_ROLE = QtCore.Qt.UserRole + 21 +VERSION_FRAME_RANGE_ROLE = QtCore.Qt.UserRole + 22 +VERSION_DURATION_ROLE = QtCore.Qt.UserRole + 23 +VERSION_HANDLES_ROLE = QtCore.Qt.UserRole + 24 +VERSION_STEP_ROLE = QtCore.Qt.UserRole + 25 +VERSION_AVAILABLE_ROLE = QtCore.Qt.UserRole + 26 +VERSION_THUMBNAIL_ID_ROLE = QtCore.Qt.UserRole + 27 +ACTIVE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 28 +REMOTE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 29 +REPRESENTATIONS_COUNT_ROLE = QtCore.Qt.UserRole + 30 +SYNC_ACTIVE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 31 +SYNC_REMOTE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 32 -STATUS_NAME_FILTER_ROLE = QtCore.Qt.UserRole + 32 +STATUS_NAME_FILTER_ROLE = QtCore.Qt.UserRole + 33 class ProductsModel(QtGui.QStandardItemModel): @@ -49,6 +50,7 @@ class ProductsModel(QtGui.QStandardItemModel): column_labels = [ "Product name", "Product type", + "Product base type", "Folder", "Version", "Status", @@ -79,6 +81,7 @@ class ProductsModel(QtGui.QStandardItemModel): product_name_col = column_labels.index("Product name") product_type_col = column_labels.index("Product type") + product_base_type_col = column_labels.index("Product base type") folders_label_col = column_labels.index("Folder") version_col = column_labels.index("Version") status_col = column_labels.index("Status") @@ -93,6 +96,7 @@ class ProductsModel(QtGui.QStandardItemModel): _display_role_mapping = { product_name_col: QtCore.Qt.DisplayRole, product_type_col: PRODUCT_TYPE_ROLE, + product_base_type_col: PRODUCT_BASE_TYPE_ROLE, folders_label_col: FOLDER_LABEL_ROLE, version_col: VERSION_NAME_ROLE, status_col: VERSION_STATUS_NAME_ROLE, @@ -432,6 +436,9 @@ class ProductsModel(QtGui.QStandardItemModel): model_item.setData(icon, QtCore.Qt.DecorationRole) model_item.setData(product_id, PRODUCT_ID_ROLE) model_item.setData(product_item.product_name, PRODUCT_NAME_ROLE) + model_item.setData( + product_item.product_base_type, PRODUCT_BASE_TYPE_ROLE + ) model_item.setData(product_item.product_type, PRODUCT_TYPE_ROLE) model_item.setData(product_type_icon, PRODUCT_TYPE_ICON_ROLE) model_item.setData(product_item.folder_id, FOLDER_ID_ROLE) diff --git a/client/ayon_core/tools/loader/ui/products_widget.py b/client/ayon_core/tools/loader/ui/products_widget.py index 94d95b9026..67116ad544 100644 --- a/client/ayon_core/tools/loader/ui/products_widget.py +++ b/client/ayon_core/tools/loader/ui/products_widget.py @@ -142,6 +142,7 @@ class ProductsWidget(QtWidgets.QWidget): default_widths = ( 200, # Product name 90, # Product type + 90, # Product base type 130, # Folder label 60, # Version 100, # Status From a3357a3ace0039a171c510a1e6a30ab19a231bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 6 Jun 2025 14:22:02 +0200 Subject: [PATCH 05/20] :dog: fix some linting issue --- client/ayon_core/tools/loader/ui/products_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/loader/ui/products_model.py b/client/ayon_core/tools/loader/ui/products_model.py index 24050fc0c1..8b8d4a67bf 100644 --- a/client/ayon_core/tools/loader/ui/products_model.py +++ b/client/ayon_core/tools/loader/ui/products_model.py @@ -16,7 +16,7 @@ TASK_ID_ROLE = QtCore.Qt.UserRole + 5 PRODUCT_ID_ROLE = QtCore.Qt.UserRole + 6 PRODUCT_NAME_ROLE = QtCore.Qt.UserRole + 7 PRODUCT_TYPE_ROLE = QtCore.Qt.UserRole + 8 -PRODUCT_BASE_TYPE_ROLE = QtCore.Qt.UserRole + 9 +PRODUCT_BASE_TYPE_ROLE = QtCore.Qt.UserRole + 9 PRODUCT_TYPE_ICON_ROLE = QtCore.Qt.UserRole + 10 PRODUCT_IN_SCENE_ROLE = QtCore.Qt.UserRole + 11 VERSION_ID_ROLE = QtCore.Qt.UserRole + 12 From 98eb281adc2533548fa807d23862e93e675299a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 6 Jun 2025 14:22:25 +0200 Subject: [PATCH 06/20] :recycle: hide product base types if support is disabled --- client/ayon_core/tools/loader/ui/products_widget.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/ayon_core/tools/loader/ui/products_widget.py b/client/ayon_core/tools/loader/ui/products_widget.py index 67116ad544..511c346cb9 100644 --- a/client/ayon_core/tools/loader/ui/products_widget.py +++ b/client/ayon_core/tools/loader/ui/products_widget.py @@ -4,6 +4,7 @@ from typing import Optional from qtpy import QtWidgets, QtCore +from ayon_core.pipeline.compatibility import is_supporting_product_base_type from ayon_core.tools.utils import ( RecursiveSortFilterProxyModel, DeselectableTreeView, @@ -262,6 +263,12 @@ class ProductsWidget(QtWidgets.QWidget): self._controller.is_sitesync_enabled() ) + if not is_supporting_product_base_type(): + # Hide product base type column + products_view.setColumnHidden( + products_model.product_base_type_col, True + ) + def set_name_filter(self, name): """Set filter of product name. From 74ed8bf2bb6b84499c48b395ab6ff70dacf1f2e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 9 Jun 2025 13:52:13 +0200 Subject: [PATCH 07/20] :recycle: refactor support check function name --- client/ayon_core/tools/loader/ui/products_widget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/tools/loader/ui/products_widget.py b/client/ayon_core/tools/loader/ui/products_widget.py index 511c346cb9..47046d5ec2 100644 --- a/client/ayon_core/tools/loader/ui/products_widget.py +++ b/client/ayon_core/tools/loader/ui/products_widget.py @@ -4,7 +4,7 @@ from typing import Optional from qtpy import QtWidgets, QtCore -from ayon_core.pipeline.compatibility import is_supporting_product_base_type +from ayon_core.pipeline.compatibility import is_product_base_type_supported from ayon_core.tools.utils import ( RecursiveSortFilterProxyModel, DeselectableTreeView, @@ -263,7 +263,7 @@ class ProductsWidget(QtWidgets.QWidget): self._controller.is_sitesync_enabled() ) - if not is_supporting_product_base_type(): + if not is_product_base_type_supported(): # Hide product base type column products_view.setColumnHidden( products_model.product_base_type_col, True From 26e2b45c9c4f00425a261a5c46de9dfd2451d709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Fri, 13 Jun 2025 16:41:12 +0200 Subject: [PATCH 08/20] Update client/ayon_core/tools/loader/abstract.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/tools/loader/abstract.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py index d6a4bf40cb..1c3e30a109 100644 --- a/client/ayon_core/tools/loader/abstract.py +++ b/client/ayon_core/tools/loader/abstract.py @@ -215,22 +215,21 @@ class VersionItem: def __init__( self, - *, version_id: str, version: int, is_hero: bool, product_id: str, - task_id: Optional[str] = None, - thumbnail_id: Optional[str] = None, - published_time: Optional[str] = None, - author: Optional[str] = None, - status: Optional[str] = None, - frame_range: Optional[str] = None, - duration: Optional[int] = None, - handles: Optional[str] = None, - step: Optional[int] = None, - comment: Optional[str] = None, - source: Optional[str] = None, + task_id: Optional[str], + thumbnail_id: Optional[str], + published_time: Optional[str], + author: Optional[str], + status: Optional[str], + frame_range: Optional[str], + duration: Optional[int], + handles: Optional[str], + step: Optional[int], + comment: Optional[str], + source: Optional[str], ): self.version_id = version_id self.product_id = product_id From 1d66a86d799863aa51e2161fd0dbed5e337a6426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Fri, 13 Jun 2025 16:41:22 +0200 Subject: [PATCH 09/20] Update client/ayon_core/tools/loader/abstract.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/tools/loader/abstract.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py index 1c3e30a109..804956f875 100644 --- a/client/ayon_core/tools/loader/abstract.py +++ b/client/ayon_core/tools/loader/abstract.py @@ -144,7 +144,6 @@ class ProductItem: folder_id: str, folder_label: str, version_items: dict[str, VersionItem], - *, product_in_scene: bool, ): self.product_id = product_id From f4af01f702b7f7fc339f19231e0b88a7ee56fc33 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 18 Jun 2025 18:59:39 +0200 Subject: [PATCH 10/20] :burn: remove `TypedDict` to retain compatibility with pythpn 3.7 but we should get it back (or dataclasses) when we get out of Middle Ages. --- client/ayon_core/tools/loader/abstract.py | 169 +++------------------- 1 file changed, 17 insertions(+), 152 deletions(-) diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py index 804956f875..de0a1c7dd8 100644 --- a/client/ayon_core/tools/loader/abstract.py +++ b/client/ayon_core/tools/loader/abstract.py @@ -2,7 +2,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import List, Optional, TypedDict +from typing import Any, List, Optional from ayon_core.lib.attribute_definitions import ( AbstractAttrDef, @@ -10,62 +10,16 @@ from ayon_core.lib.attribute_definitions import ( serialize_attr_defs, ) -IconData = TypedDict("IconData", { - "type": str, - "name": str, - "color": str -}) - -ProductBaseTypeItemData = TypedDict("ProductBaseTypeItemData", { - "name": str, - "icon": IconData -}) - - -VersionItemData = TypedDict("VersionItemData", { - "version_id": str, - "version": int, - "is_hero": bool, - "product_id": str, - "task_id": Optional[str], - "thumbnail_id": Optional[str], - "published_time": Optional[str], - "author": Optional[str], - "status": Optional[str], - "frame_range": Optional[str], - "duration": Optional[int], - "handles": Optional[str], - "step": Optional[int], - "comment": Optional[str], - "source": Optional[str] -}) - - -ProductItemData = TypedDict("ProductItemData", { - "product_id": str, - "product_type": str, - "product_base_type": str, - "product_name": str, - "product_icon": IconData, - "product_type_icon": IconData, - "product_base_type_icon": IconData, - "group_name": str, - "folder_id": str, - "folder_label": str, - "version_items": dict[str, VersionItemData], - "product_in_scene": bool -}) - class ProductTypeItem: """Item representing product type. Args: name (str): Product type name. - icon (IconData): Product type icon definition. + icon (dict[str, str]): Product type icon definition. """ - def __init__(self, name: str, icon: IconData): + def __init__(self, name: str, icon: dict[str, str]): self.name = name self.icon = icon @@ -83,16 +37,16 @@ class ProductTypeItem: class ProductBaseTypeItem: """Item representing the product base type.""" - def __init__(self, name: str, icon: IconData): + def __init__(self, name: str, icon: dict[str, str]): """Initialize product base type item.""" self.name = name self.icon = icon - def to_data(self) -> ProductBaseTypeItemData: + def to_data(self) -> dict[str, Any]: """Convert item to data dictionary. Returns: - ProductBaseTypeItemData: Data representation of the item. + dict[str, Any]: Data representation of the item. """ return { @@ -102,11 +56,11 @@ class ProductBaseTypeItem: @classmethod def from_data( - cls, data: ProductBaseTypeItemData) -> ProductBaseTypeItem: + cls, data: dict[str, Any]) -> ProductBaseTypeItem: """Create item from data dictionary. Args: - data (ProductBaseTypeItemData): Data to create item from. + data (dict[str, Any]): Data to create item from. Returns: ProductBaseTypeItem: Item created from the provided data. @@ -122,8 +76,8 @@ class ProductItem: product_id (str): Product id. product_type (str): Product type. product_name (str): Product name. - product_icon (IconData): Product icon definition. - product_type_icon (IconData): Product type icon definition. + product_icon (dict[str, str]): Product icon definition. + product_type_icon (dict[str, str]): Product type icon definition. product_in_scene (bool): Is product in scene (only when used in DCC). group_name (str): Group name. folder_id (str): Folder id. @@ -137,9 +91,9 @@ class ProductItem: product_type: str, product_base_type: str, product_name: str, - product_icon: IconData, - product_type_icon: IconData, - product_base_type_icon: IconData, + product_icon: dict[str, str], + product_type_icon: dict[str, str], + product_base_type_icon: dict[str, str], group_name: str, folder_id: str, folder_label: str, @@ -159,7 +113,7 @@ class ProductItem: self.folder_label = folder_label self.version_items = version_items - def to_data(self) -> ProductItemData: + def to_data(self) -> dict[str, Any]: return { "product_id": self.product_id, "product_type": self.product_type, @@ -408,10 +362,8 @@ class ActionItem: # future development of detached UI tools it would be better to be # prepared for it. raise NotImplementedError( - "{}.to_data is not implemented. Use Attribute definitions" - " from 'ayon_core.lib' instead of 'qargparse'.".format( - self.__class__.__name__ - ) + f"{self.__class__.__name__}.to_data is not implemented. Use Attribute definitions" + " from 'ayon_core.lib' instead of 'qargparse'." ) def to_data(self): @@ -470,8 +422,6 @@ class _BaseLoaderController(ABC): dict[str, Union[str, None]]: Context data. """ - pass - @abstractmethod def reset(self): """Reset all cached data to reload everything. @@ -480,8 +430,6 @@ class _BaseLoaderController(ABC): "controller.reset.finished". """ - pass - # Model wrappers @abstractmethod def get_folder_items(self, project_name, sender=None): @@ -495,8 +443,6 @@ class _BaseLoaderController(ABC): list[FolderItem]: Folder items for the project. """ - pass - # Expected selection helpers @abstractmethod def get_expected_selection_data(self): @@ -510,8 +456,6 @@ class _BaseLoaderController(ABC): dict[str, Any]: Expected selection data. """ - pass - @abstractmethod def set_expected_selection(self, project_name, folder_id): """Set expected selection. @@ -521,8 +465,6 @@ class _BaseLoaderController(ABC): folder_id (str): Id of folder to be selected. """ - pass - class BackendLoaderController(_BaseLoaderController): """Backend loader controller abstraction. @@ -542,8 +484,6 @@ class BackendLoaderController(_BaseLoaderController): source (Optional[str]): Event source. """ - pass - @abstractmethod def get_loaded_product_ids(self): """Return set of loaded product ids. @@ -552,8 +492,6 @@ class BackendLoaderController(_BaseLoaderController): set[str]: Set of loaded product ids. """ - pass - class FrontendLoaderController(_BaseLoaderController): @abstractmethod @@ -565,8 +503,6 @@ class FrontendLoaderController(_BaseLoaderController): callback (func): Callback triggered when the event is emitted. """ - pass - # Expected selection helpers @abstractmethod def expected_project_selected(self, project_name): @@ -576,8 +512,6 @@ class FrontendLoaderController(_BaseLoaderController): project_name (str): Project name. """ - pass - @abstractmethod def expected_folder_selected(self, folder_id): """Expected folder was selected in frontend. @@ -586,8 +520,6 @@ class FrontendLoaderController(_BaseLoaderController): folder_id (str): Folder id. """ - pass - # Model wrapper calls @abstractmethod def get_project_items(self, sender=None): @@ -609,8 +541,6 @@ class FrontendLoaderController(_BaseLoaderController): list[ProjectItem]: List of project items. """ - pass - @abstractmethod def get_folder_type_items(self, project_name, sender=None): """Folder type items for a project. @@ -629,7 +559,6 @@ class FrontendLoaderController(_BaseLoaderController): list[FolderTypeItem]: Folder type information. """ - pass @abstractmethod def get_task_items(self, project_name, folder_ids, sender=None): @@ -644,7 +573,6 @@ class FrontendLoaderController(_BaseLoaderController): list[TaskItem]: List of task items. """ - pass @abstractmethod def get_task_type_items(self, project_name, sender=None): @@ -664,7 +592,6 @@ class FrontendLoaderController(_BaseLoaderController): list[TaskTypeItem]: Task type information. """ - pass @abstractmethod def get_folder_labels(self, project_name, folder_ids): @@ -678,7 +605,6 @@ class FrontendLoaderController(_BaseLoaderController): dict[str, Optional[str]]: Folder labels by folder id. """ - pass @abstractmethod def get_project_status_items(self, project_name, sender=None): @@ -699,8 +625,6 @@ class FrontendLoaderController(_BaseLoaderController): list[StatusItem]: List of status items. """ - pass - @abstractmethod def get_product_items(self, project_name, folder_ids, sender=None): """Product items for folder ids. @@ -722,8 +646,6 @@ class FrontendLoaderController(_BaseLoaderController): list[ProductItem]: List of product items. """ - pass - @abstractmethod def get_product_item(self, project_name, product_id): """Receive single product item. @@ -736,8 +658,6 @@ class FrontendLoaderController(_BaseLoaderController): Union[ProductItem, None]: Product info or None if not found. """ - pass - @abstractmethod def get_product_type_items(self, project_name): """Product type items for a project. @@ -751,8 +671,6 @@ class FrontendLoaderController(_BaseLoaderController): list[ProductTypeItem]: List of product type items for a project. """ - pass - @abstractmethod def get_representation_items( self, project_name, version_ids, sender=None @@ -776,8 +694,6 @@ class FrontendLoaderController(_BaseLoaderController): list[RepreItem]: List of representation items. """ - pass - @abstractmethod def get_version_thumbnail_ids(self, project_name, version_ids): """Get thumbnail ids for version ids. @@ -790,8 +706,6 @@ class FrontendLoaderController(_BaseLoaderController): dict[str, Union[str, Any]]: Thumbnail id by version id. """ - pass - @abstractmethod def get_folder_thumbnail_ids(self, project_name, folder_ids): """Get thumbnail ids for folder ids. @@ -804,14 +718,11 @@ class FrontendLoaderController(_BaseLoaderController): dict[str, Union[str, Any]]: Thumbnail id by folder id. """ - pass - @abstractmethod def get_versions_representation_count( self, project_name, version_ids, sender=None ): - """ - Args: + """Args: project_name (str): Project name. version_ids (Iterable[str]): Version ids. sender (Optional[str]): Sender who requested the items. @@ -820,8 +731,6 @@ class FrontendLoaderController(_BaseLoaderController): dict[str, int]: Representation count by version id. """ - pass - @abstractmethod def get_thumbnail_paths( self, @@ -844,8 +753,6 @@ class FrontendLoaderController(_BaseLoaderController): dict[str, Union[str, None]]: Thumbnail path by entity id. """ - pass - # Selection model wrapper calls @abstractmethod def get_selected_project_name(self): @@ -857,8 +764,6 @@ class FrontendLoaderController(_BaseLoaderController): Union[str, None]: Selected project name. """ - pass - @abstractmethod def get_selected_folder_ids(self): """Get selected folder ids. @@ -869,7 +774,6 @@ class FrontendLoaderController(_BaseLoaderController): list[str]: Selected folder ids. """ - pass @abstractmethod def get_selected_task_ids(self): @@ -881,7 +785,6 @@ class FrontendLoaderController(_BaseLoaderController): list[str]: Selected folder ids. """ - pass @abstractmethod def set_selected_tasks(self, task_ids): @@ -891,7 +794,6 @@ class FrontendLoaderController(_BaseLoaderController): task_ids (Iterable[str]): Selected task ids. """ - pass @abstractmethod def get_selected_version_ids(self): @@ -903,7 +805,6 @@ class FrontendLoaderController(_BaseLoaderController): list[str]: Selected version ids. """ - pass @abstractmethod def get_selected_representation_ids(self): @@ -915,8 +816,6 @@ class FrontendLoaderController(_BaseLoaderController): list[str]: Selected representation ids. """ - pass - @abstractmethod def set_selected_project(self, project_name): """Set selected project. @@ -931,8 +830,6 @@ class FrontendLoaderController(_BaseLoaderController): project_name (Union[str, None]): Selected project name. """ - pass - @abstractmethod def set_selected_folders(self, folder_ids): """Set selected folders. @@ -948,8 +845,6 @@ class FrontendLoaderController(_BaseLoaderController): folder_ids (Iterable[str]): Selected folder ids. """ - pass - @abstractmethod def set_selected_versions(self, version_ids): """Set selected versions. @@ -966,8 +861,6 @@ class FrontendLoaderController(_BaseLoaderController): version_ids (Iterable[str]): Selected version ids. """ - pass - @abstractmethod def set_selected_representations(self, repre_ids): """Set selected representations. @@ -985,8 +878,6 @@ class FrontendLoaderController(_BaseLoaderController): repre_ids (Iterable[str]): Selected representation ids. """ - pass - # Load action items @abstractmethod def get_versions_action_items(self, project_name, version_ids): @@ -1000,8 +891,6 @@ class FrontendLoaderController(_BaseLoaderController): list[ActionItem]: List of action items. """ - pass - @abstractmethod def get_representations_action_items( self, project_name, representation_ids @@ -1016,8 +905,6 @@ class FrontendLoaderController(_BaseLoaderController): list[ActionItem]: List of action items. """ - pass - @abstractmethod def trigger_action_item( self, @@ -1050,8 +937,6 @@ class FrontendLoaderController(_BaseLoaderController): representation_ids (Iterable[str]): Representation ids. """ - pass - @abstractmethod def change_products_group(self, project_name, product_ids, group_name): """Change group of products. @@ -1070,8 +955,6 @@ class FrontendLoaderController(_BaseLoaderController): group_name (str): New group name. """ - pass - @abstractmethod def fill_root_in_source(self, source): """Fill root in source path. @@ -1081,8 +964,6 @@ class FrontendLoaderController(_BaseLoaderController): rootless workfile path. """ - pass - # NOTE: Methods 'is_loaded_products_supported' and # 'is_standard_projects_filter_enabled' are both based on being in host # or not. Maybe we could implement only single method 'is_in_host'? @@ -1094,8 +975,6 @@ class FrontendLoaderController(_BaseLoaderController): bool: True if it is supported. """ - pass - @abstractmethod def is_standard_projects_filter_enabled(self): """Is standard projects filter enabled. @@ -1108,8 +987,6 @@ class FrontendLoaderController(_BaseLoaderController): current context project. """ - pass - # Site sync functions @abstractmethod def is_sitesync_enabled(self, project_name=None): @@ -1127,8 +1004,6 @@ class FrontendLoaderController(_BaseLoaderController): bool: True if site sync is enabled. """ - pass - @abstractmethod def get_active_site_icon_def(self, project_name): """Active site icon definition. @@ -1141,8 +1016,6 @@ class FrontendLoaderController(_BaseLoaderController): is not enabled for the project. """ - pass - @abstractmethod def get_remote_site_icon_def(self, project_name): """Remote site icon definition. @@ -1155,8 +1028,6 @@ class FrontendLoaderController(_BaseLoaderController): is not enabled for the project. """ - pass - @abstractmethod def get_version_sync_availability(self, project_name, version_ids): """Version sync availability. @@ -1169,8 +1040,6 @@ class FrontendLoaderController(_BaseLoaderController): dict[str, tuple[int, int]]: Sync availability by version id. """ - pass - @abstractmethod def get_representations_sync_status( self, project_name, representation_ids @@ -1185,8 +1054,6 @@ class FrontendLoaderController(_BaseLoaderController): dict[str, tuple[int, int]]: Sync status by representation id. """ - pass - @abstractmethod def get_product_types_filter(self): """Return product type filter for current context. @@ -1194,5 +1061,3 @@ class FrontendLoaderController(_BaseLoaderController): Returns: ProductTypesFilter: Product type filter for current context """ - - pass From ae1bfc71f78bd43a0e930387561178c799184c43 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 18 Jun 2025 19:00:35 +0200 Subject: [PATCH 11/20] :recycle: change loader filtering --- client/ayon_core/pipeline/load/plugins.py | 81 ++++++++++++++--------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 32b96e3e7a..5f206df364 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -1,16 +1,18 @@ """Plugins for loading representations and products into host applications.""" from __future__ import annotations -import os -import logging -from ayon_core.settings import get_project_settings +import logging +import os + from ayon_core.pipeline.plugin_discover import ( + deregister_plugin, + deregister_plugin_path, discover, register_plugin, register_plugin_path, - deregister_plugin, - deregister_plugin_path ) +from ayon_core.settings import get_project_settings + from .utils import get_representation_path_from_context @@ -61,12 +63,12 @@ class LoaderPlugin(list): if not plugin_settings: return - print(">>> We have preset for {}".format(plugin_name)) + print(f">>> We have preset for {plugin_name}") for option, value in plugin_settings.items(): if option == "enabled" and value is False: print(" - is disabled by preset") else: - print(" - setting `{}`: `{}`".format(option, value)) + print(f" - setting `{option}`: `{value}`") setattr(cls, option, value) @classmethod @@ -79,7 +81,6 @@ class LoaderPlugin(list): Returns: bool: Representation has valid extension """ - if "*" in cls.extensions: return True @@ -122,10 +123,21 @@ class LoaderPlugin(list): Returns: bool: Is loader compatible for context. """ + """ + product_types: set[str] = set() + product_base_types: set[str] = set() + representations = set() + extensions = {"*"} + """ plugin_repre_names = cls.get_representations() plugin_product_types = cls.product_types plugin_product_base_types = cls.product_base_types + repre_entity = context.get("representation") + product_entity = context["product"] + + # If no representation names, product types or extensions are defined + # then loader is not compatible with any context. if ( not plugin_repre_names or (not plugin_product_types and not plugin_product_base_types) @@ -133,38 +145,45 @@ class LoaderPlugin(list): ): return False - repre_entity = context.get("representation") + # If no representation entity is provided then loader is not + # compatible with context. if not repre_entity: return False + # Check the compatibility with the representation names. plugin_repre_names = set(plugin_repre_names) - if ( + if not plugin_repre_names or ( "*" not in plugin_repre_names and repre_entity["name"] not in plugin_repre_names ): return False + # Check the compatibility with the extension of the representation. if not cls.has_valid_extension(repre_entity): return False plugin_product_types = set(plugin_product_types) - if "*" in plugin_product_types: - return True - - plugin_product_base_types = set(plugin_product_base_types) - if "*" in plugin_product_base_types: - # If plugin supports all product base types, then it is compatible - # with any product type. - return True - - product_entity = context["product"] - product_type = product_entity["productType"] + product_type = product_entity.get("productType") product_base_type = product_entity.get("productBaseType") - if product_type in plugin_product_types: + # Use product base type if defined, otherwise use product type. + product_filter = product_base_type + # If there is no product base type defined in the product entity, + # then we will use the product type. + if product_filter is None: + product_filter = product_type + + # If no product type isn't defined on the loader plugin, + # then we will use the product types. + plugin_product_filter = ( + plugin_product_base_types or plugin_product_types) + + # If wildcard is used in product types or base types, + # then we will consider the loader compatible with any product type. + if "*" in plugin_product_filter: return True - return product_base_type in plugin_product_base_types + return product_filter in plugin_product_filter @classmethod def get_representations(cls): @@ -219,19 +238,17 @@ class LoaderPlugin(list): bool: Whether the container was deleted """ - raise NotImplementedError("Loader.remove() must be " "implemented by subclass") @classmethod def get_options(cls, contexts): - """ - Returns static (cls) options or could collect from 'contexts'. + """Returns static (cls) options or could collect from 'contexts'. - Args: - contexts (list): of repre or product contexts - Returns: - (list) + Args: + contexts (list): of repre or product contexts + Returns: + (list) """ return cls.options or [] @@ -279,9 +296,7 @@ def discover_loader_plugins(project_name=None): plugin.apply_settings(project_settings) except Exception: log.warning( - "Failed to apply settings to loader {}".format( - plugin.__name__ - ), + f"Failed to apply settings to loader {plugin.__name__}", exc_info=True ) return plugins From 1c63b75a272421e2f0ebdd88df791802b9097204 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 19 Jun 2025 10:06:29 +0200 Subject: [PATCH 12/20] :recycle: make product type and product base types None by default --- client/ayon_core/pipeline/load/plugins.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 5f206df364..966b418db8 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging import os +from typing import Optional from ayon_core.pipeline.plugin_discover import ( deregister_plugin, @@ -19,8 +20,8 @@ from .utils import get_representation_path_from_context class LoaderPlugin(list): """Load representation into host application""" - product_types: set[str] = set() - product_base_types: set[str] = set() + product_types: Optional[set[str]] = None + product_base_types: Optional[set[str]] = None representations = set() extensions = {"*"} order = 0 From a3c04d232a6c91ed303ca40f1b4386601be0d21c Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 19 Jun 2025 10:07:20 +0200 Subject: [PATCH 13/20] :recycle: revert more `TypedDict` changes and fix line length --- client/ayon_core/tools/loader/abstract.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py index de0a1c7dd8..c585160672 100644 --- a/client/ayon_core/tools/loader/abstract.py +++ b/client/ayon_core/tools/loader/abstract.py @@ -242,7 +242,7 @@ class VersionItem: def __le__(self, other): return self.__eq__(other) or self.__lt__(other) - def to_data(self) -> VersionItemData: + def to_data(self) -> dict[str, Any]: return { "version_id": self.version_id, "product_id": self.product_id, @@ -262,7 +262,7 @@ class VersionItem: } @classmethod - def from_data(cls, data: VersionItemData): + def from_data(cls, data: dict[str, Any]) -> VersionItem: return cls(**data) @@ -362,8 +362,9 @@ class ActionItem: # future development of detached UI tools it would be better to be # prepared for it. raise NotImplementedError( - f"{self.__class__.__name__}.to_data is not implemented. Use Attribute definitions" - " from 'ayon_core.lib' instead of 'qargparse'." + f"{self.__class__.__name__}.to_data is not implemented. " + "Use Attribute definitions " + "from 'ayon_core.lib' instead of 'qargparse'." ) def to_data(self): From e003ef2960727bdc997da5a737f3458786237af0 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 19 Jun 2025 10:15:02 +0200 Subject: [PATCH 14/20] :fire: revert some code cleanup --- client/ayon_core/tools/loader/abstract.py | 96 ++++++++++++++++++++++- 1 file changed, 92 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py index c585160672..21f2349544 100644 --- a/client/ayon_core/tools/loader/abstract.py +++ b/client/ayon_core/tools/loader/abstract.py @@ -362,9 +362,10 @@ class ActionItem: # future development of detached UI tools it would be better to be # prepared for it. raise NotImplementedError( - f"{self.__class__.__name__}.to_data is not implemented. " - "Use Attribute definitions " - "from 'ayon_core.lib' instead of 'qargparse'." + "{}.to_data is not implemented. Use Attribute definitions" + " from 'ayon_core.lib' instead of 'qargparse'.".format( + self.__class__.__name__ + ) ) def to_data(self): @@ -423,6 +424,8 @@ class _BaseLoaderController(ABC): dict[str, Union[str, None]]: Context data. """ + pass + @abstractmethod def reset(self): """Reset all cached data to reload everything. @@ -431,6 +434,8 @@ class _BaseLoaderController(ABC): "controller.reset.finished". """ + pass + # Model wrappers @abstractmethod def get_folder_items(self, project_name, sender=None): @@ -444,6 +449,8 @@ class _BaseLoaderController(ABC): list[FolderItem]: Folder items for the project. """ + pass + # Expected selection helpers @abstractmethod def get_expected_selection_data(self): @@ -457,6 +464,8 @@ class _BaseLoaderController(ABC): dict[str, Any]: Expected selection data. """ + pass + @abstractmethod def set_expected_selection(self, project_name, folder_id): """Set expected selection. @@ -466,6 +475,8 @@ class _BaseLoaderController(ABC): folder_id (str): Id of folder to be selected. """ + pass + class BackendLoaderController(_BaseLoaderController): """Backend loader controller abstraction. @@ -485,6 +496,8 @@ class BackendLoaderController(_BaseLoaderController): source (Optional[str]): Event source. """ + pass + @abstractmethod def get_loaded_product_ids(self): """Return set of loaded product ids. @@ -493,6 +506,8 @@ class BackendLoaderController(_BaseLoaderController): set[str]: Set of loaded product ids. """ + pass + class FrontendLoaderController(_BaseLoaderController): @abstractmethod @@ -504,6 +519,8 @@ class FrontendLoaderController(_BaseLoaderController): callback (func): Callback triggered when the event is emitted. """ + pass + # Expected selection helpers @abstractmethod def expected_project_selected(self, project_name): @@ -513,6 +530,8 @@ class FrontendLoaderController(_BaseLoaderController): project_name (str): Project name. """ + pass + @abstractmethod def expected_folder_selected(self, folder_id): """Expected folder was selected in frontend. @@ -521,6 +540,8 @@ class FrontendLoaderController(_BaseLoaderController): folder_id (str): Folder id. """ + pass + # Model wrapper calls @abstractmethod def get_project_items(self, sender=None): @@ -542,6 +563,8 @@ class FrontendLoaderController(_BaseLoaderController): list[ProjectItem]: List of project items. """ + pass + @abstractmethod def get_folder_type_items(self, project_name, sender=None): """Folder type items for a project. @@ -560,6 +583,7 @@ class FrontendLoaderController(_BaseLoaderController): list[FolderTypeItem]: Folder type information. """ + pass @abstractmethod def get_task_items(self, project_name, folder_ids, sender=None): @@ -574,6 +598,7 @@ class FrontendLoaderController(_BaseLoaderController): list[TaskItem]: List of task items. """ + pass @abstractmethod def get_task_type_items(self, project_name, sender=None): @@ -593,6 +618,7 @@ class FrontendLoaderController(_BaseLoaderController): list[TaskTypeItem]: Task type information. """ + pass @abstractmethod def get_folder_labels(self, project_name, folder_ids): @@ -606,6 +632,7 @@ class FrontendLoaderController(_BaseLoaderController): dict[str, Optional[str]]: Folder labels by folder id. """ + pass @abstractmethod def get_project_status_items(self, project_name, sender=None): @@ -626,6 +653,8 @@ class FrontendLoaderController(_BaseLoaderController): list[StatusItem]: List of status items. """ + pass + @abstractmethod def get_product_items(self, project_name, folder_ids, sender=None): """Product items for folder ids. @@ -647,6 +676,8 @@ class FrontendLoaderController(_BaseLoaderController): list[ProductItem]: List of product items. """ + pass + @abstractmethod def get_product_item(self, project_name, product_id): """Receive single product item. @@ -659,6 +690,8 @@ class FrontendLoaderController(_BaseLoaderController): Union[ProductItem, None]: Product info or None if not found. """ + pass + @abstractmethod def get_product_type_items(self, project_name): """Product type items for a project. @@ -672,6 +705,8 @@ class FrontendLoaderController(_BaseLoaderController): list[ProductTypeItem]: List of product type items for a project. """ + pass + @abstractmethod def get_representation_items( self, project_name, version_ids, sender=None @@ -695,6 +730,8 @@ class FrontendLoaderController(_BaseLoaderController): list[RepreItem]: List of representation items. """ + pass + @abstractmethod def get_version_thumbnail_ids(self, project_name, version_ids): """Get thumbnail ids for version ids. @@ -707,6 +744,8 @@ class FrontendLoaderController(_BaseLoaderController): dict[str, Union[str, Any]]: Thumbnail id by version id. """ + pass + @abstractmethod def get_folder_thumbnail_ids(self, project_name, folder_ids): """Get thumbnail ids for folder ids. @@ -719,11 +758,14 @@ class FrontendLoaderController(_BaseLoaderController): dict[str, Union[str, Any]]: Thumbnail id by folder id. """ + pass + @abstractmethod def get_versions_representation_count( self, project_name, version_ids, sender=None ): - """Args: + """ + Args: project_name (str): Project name. version_ids (Iterable[str]): Version ids. sender (Optional[str]): Sender who requested the items. @@ -732,6 +774,8 @@ class FrontendLoaderController(_BaseLoaderController): dict[str, int]: Representation count by version id. """ + pass + @abstractmethod def get_thumbnail_paths( self, @@ -754,6 +798,8 @@ class FrontendLoaderController(_BaseLoaderController): dict[str, Union[str, None]]: Thumbnail path by entity id. """ + pass + # Selection model wrapper calls @abstractmethod def get_selected_project_name(self): @@ -765,6 +811,8 @@ class FrontendLoaderController(_BaseLoaderController): Union[str, None]: Selected project name. """ + pass + @abstractmethod def get_selected_folder_ids(self): """Get selected folder ids. @@ -775,6 +823,7 @@ class FrontendLoaderController(_BaseLoaderController): list[str]: Selected folder ids. """ + pass @abstractmethod def get_selected_task_ids(self): @@ -786,6 +835,7 @@ class FrontendLoaderController(_BaseLoaderController): list[str]: Selected folder ids. """ + pass @abstractmethod def set_selected_tasks(self, task_ids): @@ -795,6 +845,7 @@ class FrontendLoaderController(_BaseLoaderController): task_ids (Iterable[str]): Selected task ids. """ + pass @abstractmethod def get_selected_version_ids(self): @@ -806,6 +857,7 @@ class FrontendLoaderController(_BaseLoaderController): list[str]: Selected version ids. """ + pass @abstractmethod def get_selected_representation_ids(self): @@ -817,6 +869,8 @@ class FrontendLoaderController(_BaseLoaderController): list[str]: Selected representation ids. """ + pass + @abstractmethod def set_selected_project(self, project_name): """Set selected project. @@ -831,6 +885,8 @@ class FrontendLoaderController(_BaseLoaderController): project_name (Union[str, None]): Selected project name. """ + pass + @abstractmethod def set_selected_folders(self, folder_ids): """Set selected folders. @@ -846,6 +902,8 @@ class FrontendLoaderController(_BaseLoaderController): folder_ids (Iterable[str]): Selected folder ids. """ + pass + @abstractmethod def set_selected_versions(self, version_ids): """Set selected versions. @@ -862,6 +920,8 @@ class FrontendLoaderController(_BaseLoaderController): version_ids (Iterable[str]): Selected version ids. """ + pass + @abstractmethod def set_selected_representations(self, repre_ids): """Set selected representations. @@ -879,6 +939,8 @@ class FrontendLoaderController(_BaseLoaderController): repre_ids (Iterable[str]): Selected representation ids. """ + pass + # Load action items @abstractmethod def get_versions_action_items(self, project_name, version_ids): @@ -892,6 +954,8 @@ class FrontendLoaderController(_BaseLoaderController): list[ActionItem]: List of action items. """ + pass + @abstractmethod def get_representations_action_items( self, project_name, representation_ids @@ -906,6 +970,8 @@ class FrontendLoaderController(_BaseLoaderController): list[ActionItem]: List of action items. """ + pass + @abstractmethod def trigger_action_item( self, @@ -938,6 +1004,8 @@ class FrontendLoaderController(_BaseLoaderController): representation_ids (Iterable[str]): Representation ids. """ + pass + @abstractmethod def change_products_group(self, project_name, product_ids, group_name): """Change group of products. @@ -956,6 +1024,8 @@ class FrontendLoaderController(_BaseLoaderController): group_name (str): New group name. """ + pass + @abstractmethod def fill_root_in_source(self, source): """Fill root in source path. @@ -965,6 +1035,8 @@ class FrontendLoaderController(_BaseLoaderController): rootless workfile path. """ + pass + # NOTE: Methods 'is_loaded_products_supported' and # 'is_standard_projects_filter_enabled' are both based on being in host # or not. Maybe we could implement only single method 'is_in_host'? @@ -976,6 +1048,8 @@ class FrontendLoaderController(_BaseLoaderController): bool: True if it is supported. """ + pass + @abstractmethod def is_standard_projects_filter_enabled(self): """Is standard projects filter enabled. @@ -988,6 +1062,8 @@ class FrontendLoaderController(_BaseLoaderController): current context project. """ + pass + # Site sync functions @abstractmethod def is_sitesync_enabled(self, project_name=None): @@ -1005,6 +1081,8 @@ class FrontendLoaderController(_BaseLoaderController): bool: True if site sync is enabled. """ + pass + @abstractmethod def get_active_site_icon_def(self, project_name): """Active site icon definition. @@ -1017,6 +1095,8 @@ class FrontendLoaderController(_BaseLoaderController): is not enabled for the project. """ + pass + @abstractmethod def get_remote_site_icon_def(self, project_name): """Remote site icon definition. @@ -1029,6 +1109,8 @@ class FrontendLoaderController(_BaseLoaderController): is not enabled for the project. """ + pass + @abstractmethod def get_version_sync_availability(self, project_name, version_ids): """Version sync availability. @@ -1041,6 +1123,8 @@ class FrontendLoaderController(_BaseLoaderController): dict[str, tuple[int, int]]: Sync availability by version id. """ + pass + @abstractmethod def get_representations_sync_status( self, project_name, representation_ids @@ -1055,6 +1139,8 @@ class FrontendLoaderController(_BaseLoaderController): dict[str, tuple[int, int]]: Sync status by representation id. """ + pass + @abstractmethod def get_product_types_filter(self): """Return product type filter for current context. @@ -1062,3 +1148,5 @@ class FrontendLoaderController(_BaseLoaderController): Returns: ProductTypesFilter: Product type filter for current context """ + + pass From 9738c2cc448371b5d18941c94268fdfa98b4558b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Thu, 19 Jun 2025 16:54:19 +0200 Subject: [PATCH 15/20] Update client/ayon_core/pipeline/load/plugins.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/load/plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 966b418db8..ccee23c5c2 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -20,7 +20,7 @@ from .utils import get_representation_path_from_context class LoaderPlugin(list): """Load representation into host application""" - product_types: Optional[set[str]] = None + product_types: set[str] = set() product_base_types: Optional[set[str]] = None representations = set() extensions = {"*"} From 929418c8cbb82e3f67534884446844a70ac610ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Thu, 19 Jun 2025 16:54:36 +0200 Subject: [PATCH 16/20] Update client/ayon_core/pipeline/load/plugins.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/load/plugins.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index ccee23c5c2..0d0b073ad7 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -124,12 +124,6 @@ class LoaderPlugin(list): Returns: bool: Is loader compatible for context. """ - """ - product_types: set[str] = set() - product_base_types: set[str] = set() - representations = set() - extensions = {"*"} - """ plugin_repre_names = cls.get_representations() plugin_product_types = cls.product_types From deacb2853e429296b1603d949b8b5006c1821ef6 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 20 Jun 2025 16:13:07 +0200 Subject: [PATCH 17/20] :recycle: refactor filtering and add some tests --- client/ayon_core/pipeline/load/plugins.py | 20 +++-- .../ayon_core/pipeline/load/test_loaders.py | 88 +++++++++++++++++++ 2 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 tests/client/ayon_core/pipeline/load/test_loaders.py diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 39343b76c6..5725133432 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -129,6 +129,10 @@ class LoaderPlugin(list): plugin_repre_names = cls.get_representations() plugin_product_types = cls.product_types plugin_product_base_types = cls.product_base_types + # If product type isn't defined on the loader plugin, + # then we will use the product types. + plugin_product_filter = ( + plugin_product_base_types or plugin_product_types) repre_entity = context.get("representation") product_entity = context["product"] @@ -136,8 +140,8 @@ class LoaderPlugin(list): # then loader is not compatible with any context. if ( not plugin_repre_names - or (not plugin_product_types and not plugin_product_base_types) - or not cls.extensions + and not plugin_product_filter + and not cls.extensions ): return False @@ -148,7 +152,7 @@ class LoaderPlugin(list): # Check the compatibility with the representation names. plugin_repre_names = set(plugin_repre_names) - if not plugin_repre_names or ( + if ( "*" not in plugin_repre_names and repre_entity["name"] not in plugin_repre_names ): @@ -169,8 +173,6 @@ class LoaderPlugin(list): if product_filter is None: product_filter = product_type - # If no product type isn't defined on the loader plugin, - # then we will use the product types. plugin_product_filter = ( plugin_product_base_types or plugin_product_types) @@ -179,6 +181,14 @@ class LoaderPlugin(list): if "*" in plugin_product_filter: return True + # compatibility with legacy loader + if cls.product_base_types is None and product_base_type: + cls.log.error( + f"Loader {cls.__name__} is doesn't specify " + "`product_base_types` but product entity has " + f"`productBaseType` defined as `{product_base_type}`. " + ) + return product_filter in plugin_product_filter @classmethod diff --git a/tests/client/ayon_core/pipeline/load/test_loaders.py b/tests/client/ayon_core/pipeline/load/test_loaders.py new file mode 100644 index 0000000000..490efe1b1e --- /dev/null +++ b/tests/client/ayon_core/pipeline/load/test_loaders.py @@ -0,0 +1,88 @@ +"""Test loaders in the pipeline module.""" + +from ayon_core.pipeline.load import LoaderPlugin + + +def test_is_compatible_loader(): + """Test if a loader is compatible with a given representation.""" + from ayon_core.pipeline.load import is_compatible_loader + + # Create a mock representation context + context = { + "loader": "test_loader", + "representation": {"name": "test_representation"}, + } + + # Create a mock loader plugin + class MockLoader(LoaderPlugin): + name = "test_loader" + version = "1.0.0" + + def is_compatible_loader(self, context): + return True + + # Check compatibility + assert is_compatible_loader(MockLoader(), context) is True + + +def test_complex_is_compatible_loader(): + """Test if a loader is compatible with a complex representation.""" + from ayon_core.pipeline.load import is_compatible_loader + + # Create a mock complex representation context + context = { + "loader": "complex_loader", + "representation": { + "name": "complex_representation", + "extension": "exr" + }, + "additional_data": {"key": "value"}, + "product": { + "name": "complex_product", + "productType": "foo", + "productBaseType": "bar", + }, + } + + # Create a mock loader plugin + class ComplexLoaderA(LoaderPlugin): + name = "complex_loaderA" + + # False because the loader doesn't specify any compatibility (missing + # wildcard for product type and product base type) + assert is_compatible_loader(ComplexLoaderA(), context) is False + + class ComplexLoaderB(LoaderPlugin): + name = "complex_loaderB" + product_types = {"*"} + representations = {"*"} + + # True, it is compatible with any product type + assert is_compatible_loader(ComplexLoaderB(), context) is True + + class ComplexLoaderC(LoaderPlugin): + name = "complex_loaderC" + product_base_types = {"*"} + representations = {"*"} + + # True, it is compatible with any product base type + assert is_compatible_loader(ComplexLoaderC(), context) is True + + class ComplexLoaderD(LoaderPlugin): + name = "complex_loaderD" + product_types = {"foo"} + representations = {"*"} + + # legacy loader defining compatibility only with product type + # is compatible provided the same product type is defined in context + assert is_compatible_loader(ComplexLoaderD(), context) is False + + class ComplexLoaderE(LoaderPlugin): + name = "complex_loaderE" + product_types = {"foo"} + representations = {"*"} + + # remove productBaseType from context to simulate legacy behavior + context["product"].pop("productBaseType", None) + + assert is_compatible_loader(ComplexLoaderE(), context) is True From c1b9eff2df4a2502c5f831c348ea0763fcafddf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 23 Jun 2025 16:59:45 +0200 Subject: [PATCH 18/20] :bug: fix comment and condition --- client/ayon_core/pipeline/load/plugins.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 5725133432..62fe8150ae 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -129,10 +129,12 @@ class LoaderPlugin(list): plugin_repre_names = cls.get_representations() plugin_product_types = cls.product_types plugin_product_base_types = cls.product_base_types - # If product type isn't defined on the loader plugin, + + # If the product base type isn't defined on the loader plugin, # then we will use the product types. - plugin_product_filter = ( - plugin_product_base_types or plugin_product_types) + plugin_product_filter = plugin_product_base_types + if plugin_product_filter is None: + plugin_product_filter = plugin_product_types repre_entity = context.get("representation") product_entity = context["product"] From 2f9cd88111196ba61632dfc4e6cdf918a32775f0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 24 Jun 2025 11:44:27 +0200 Subject: [PATCH 19/20] revert conditions --- client/ayon_core/pipeline/load/plugins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 62fe8150ae..05584e60e9 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -142,8 +142,8 @@ class LoaderPlugin(list): # then loader is not compatible with any context. if ( not plugin_repre_names - and not plugin_product_filter - and not cls.extensions + or not plugin_product_filter + or not cls.extensions ): return False From 4aefacaf44e567e2695dbe3c1db2b91f43b1ea35 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 24 Jun 2025 11:44:54 +0200 Subject: [PATCH 20/20] remove unncessary product base type filters redefinitins --- client/ayon_core/pipeline/load/plugins.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 05584e60e9..dc5bb0f66f 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -127,14 +127,16 @@ class LoaderPlugin(list): """ plugin_repre_names = cls.get_representations() - plugin_product_types = cls.product_types - plugin_product_base_types = cls.product_base_types # If the product base type isn't defined on the loader plugin, # then we will use the product types. - plugin_product_filter = plugin_product_base_types + plugin_product_filter = cls.product_base_types if plugin_product_filter is None: - plugin_product_filter = plugin_product_types + plugin_product_filter = cls.product_types + + if plugin_product_filter: + plugin_product_filter = set(plugin_product_filter) + repre_entity = context.get("representation") product_entity = context["product"] @@ -164,7 +166,6 @@ class LoaderPlugin(list): if not cls.has_valid_extension(repre_entity): return False - plugin_product_types = set(plugin_product_types) product_type = product_entity.get("productType") product_base_type = product_entity.get("productBaseType") @@ -175,9 +176,6 @@ class LoaderPlugin(list): if product_filter is None: product_filter = product_type - plugin_product_filter = ( - plugin_product_base_types or plugin_product_types) - # If wildcard is used in product types or base types, # then we will consider the loader compatible with any product type. if "*" in plugin_product_filter: