diff --git a/client/ayon_core/tools/common_models/__init__.py b/client/ayon_core/tools/common_models/__init__.py index ec69e20b64..77cc2dfb0f 100644 --- a/client/ayon_core/tools/common_models/__init__.py +++ b/client/ayon_core/tools/common_models/__init__.py @@ -10,6 +10,7 @@ from .projects import ( PROJECTS_MODEL_SENDER, FolderTypeItem, TaskTypeItem, + ProductTypeIconMapping, ) from .hierarchy import ( FolderItem, @@ -34,6 +35,7 @@ __all__ = ( "PROJECTS_MODEL_SENDER", "FolderTypeItem", "TaskTypeItem", + "ProductTypeIconMapping", "FolderItem", "TaskItem", diff --git a/client/ayon_core/tools/common_models/projects.py b/client/ayon_core/tools/common_models/projects.py index 034947de3a..710482a871 100644 --- a/client/ayon_core/tools/common_models/projects.py +++ b/client/ayon_core/tools/common_models/projects.py @@ -2,7 +2,7 @@ from __future__ import annotations import contextlib from abc import ABC, abstractmethod -from typing import Dict, Any +from typing import Any, Optional from dataclasses import dataclass import ayon_api @@ -51,7 +51,7 @@ class StatusItem: self.icon: str = icon self.state: str = state - def to_data(self) -> Dict[str, Any]: + def to_data(self) -> dict[str, Any]: return { "name": self.name, "color": self.color, @@ -218,6 +218,54 @@ class ProjectItem: return cls(**data) +class ProductTypeIconMapping: + def __init__( + self, + default: Optional[dict[str, str]] = None, + definitions: Optional[list[dict[str, str]]] = None, + ): + self._default = default or {} + self._definitions = definitions or [] + + self._default_def = None + self._definitions_by_name = None + + def get_icon( + self, + product_base_type: Optional[str] = None, + product_type: Optional[str] = None, + ) -> dict[str, str]: + defs = self._get_defs_by_name() + icon = defs.get(product_type) + if icon is None: + icon = defs.get(product_base_type) + if icon is None: + icon = self._get_default_def() + return icon.copy() + + def _get_default_def(self) -> dict[str, str]: + if self._default_def is None: + self._default_def = { + "type": "material-symbols", + "name": self._default.get("icon", "deployed_code"), + "color": self._default.get("color", "#cccccc"), + } + + return self._default_def + + def _get_defs_by_name(self) -> dict[str, dict[str, str]]: + if self._definitions_by_name is None: + self._definitions_by_name = { + product_base_type_def["name"]: { + "type": "material-symbols", + "name": product_base_type_def.get("icon", "deployed_code"), + "color": product_base_type_def.get("color", "#cccccc"), + } + for product_base_type_def in self._definitions + } + return self._definitions_by_name + + def _get_project_items_from_entitiy( projects: list[dict[str, Any]] ) -> list[ProjectItem]: @@ -242,6 +290,9 @@ class ProjectsModel(object): self._projects_by_name = NestedCacheItem( levels=1, default_factory=list ) + self._product_type_icons_mapping = NestedCacheItem( + levels=1, default_factory=ProductTypeIconMapping + ) self._project_statuses_cache = {} self._folder_types_cache = {} self._task_types_cache = {} @@ -255,6 +306,7 @@ class ProjectsModel(object): self._task_types_cache = {} self._projects_cache.reset() self._projects_by_name.reset() + self._product_type_icons_mapping.reset() def refresh(self): """Refresh project items. @@ -390,6 +442,27 @@ class ProjectsModel(object): self._task_type_items_getter, ) + def get_product_type_icons_mapping( + self, project_name: Optional[str] + ) -> ProductTypeIconMapping: + cache = self._product_type_icons_mapping[project_name] + if cache.is_valid: + return cache.get_data() + + project_entity = self.get_project_entity(project_name) + icons_mapping = ProductTypeIconMapping() + if project_entity: + product_base_types = ( + project_entity["config"].get("productBaseTypes", {}) + ) + icons_mapping = ProductTypeIconMapping( + product_base_types.get("default"), + product_base_types.get("definitions") + ) + + cache.update_data(icons_mapping) + return icons_mapping + def _get_project_items( self, project_name, sender, item_type, cache_obj, getter ): diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py index 87cfb1f3f2..9c7934d2db 100644 --- a/client/ayon_core/tools/loader/abstract.py +++ b/client/ayon_core/tools/loader/abstract.py @@ -9,7 +9,11 @@ from ayon_core.lib.attribute_definitions import ( deserialize_attr_defs, serialize_attr_defs, ) -from ayon_core.tools.common_models import TaskItem, TagItem +from ayon_core.tools.common_models import ( + TaskItem, + TagItem, + ProductTypeIconMapping, +) class ProductTypeItem: @@ -492,8 +496,8 @@ class BackendLoaderController(_BaseLoaderController): topic (str): Event topic name. data (Optional[dict[str, Any]]): Event data. source (Optional[str]): Event source. - """ + """ pass @abstractmethod @@ -502,14 +506,20 @@ class BackendLoaderController(_BaseLoaderController): Returns: set[str]: Set of loaded product ids. - """ + """ pass @abstractmethod - def get_project_entity( + def get_product_type_icons_mapping( self, project_name: Optional[str] - ) -> Optional[dict[str, Any]]: + ) -> ProductTypeIconMapping: + """Product type icons mapping. + + Returns: + ProductTypeIconMapping: Product type icons mapping. + + """ pass diff --git a/client/ayon_core/tools/loader/control.py b/client/ayon_core/tools/loader/control.py index 492a05e82f..c7c182b192 100644 --- a/client/ayon_core/tools/loader/control.py +++ b/client/ayon_core/tools/loader/control.py @@ -17,6 +17,7 @@ from ayon_core.tools.common_models import ( HierarchyModel, ThumbnailsModel, TagItem, + ProductTypeIconMapping, ) from .abstract import ( @@ -189,11 +190,6 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): def get_project_items(self, sender=None): return self._projects_model.get_project_items(sender) - def get_project_entity( - self, project_name: Optional[str] - ) -> Optional[dict[str, Any]]: - return self._projects_model.get_project_entity(project_name) - def get_folder_type_items(self, project_name, sender=None): return self._projects_model.get_folder_type_items( project_name, sender @@ -204,6 +200,13 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): project_name, sender ) + def get_product_type_icons_mapping( + self, project_name: Optional[str] + ) -> ProductTypeIconMapping: + return self._projects_model.get_product_type_icons_mapping( + project_name + ) + def get_folder_items(self, project_name, sender=None): return self._hierarchy_model.get_folder_items(project_name, sender) diff --git a/client/ayon_core/tools/loader/models/products.py b/client/ayon_core/tools/loader/models/products.py index 11468e4db0..7915a75bcf 100644 --- a/client/ayon_core/tools/loader/models/products.py +++ b/client/ayon_core/tools/loader/models/products.py @@ -11,6 +11,7 @@ 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.common_models import ProductTypeIconMapping from ayon_core.tools.loader.abstract import ( ProductTypeItem, ProductBaseTypeItem, @@ -29,54 +30,6 @@ if TYPE_CHECKING: PRODUCTS_MODEL_SENDER = "products.model" -class ProductBaseTypeIconMapping: - def __init__( - self, - default: Optional[dict[str, str]] = None, - definitions: Optional[list[dict[str, str]]] = None, - ): - self._default = default or {} - self._definitions = definitions or [] - - self._default_def = None - self._definitions_by_name = None - - def get_icon( - self, - product_base_type: Optional[str] = None, - product_type: Optional[str] = None, - ) -> dict[str, str]: - defs = self._get_defs_by_name() - icon = defs.get(product_type) - if icon is None: - icon = defs.get(product_base_type) - if icon is None: - icon = self._get_default_def() - return icon.copy() - - def _get_default_def(self) -> dict[str, str]: - if self._default_def is None: - self._default_def = { - "type": "material-symbols", - "name": self._default.get("icon", "deployed_code"), - "color": self._default.get("color", "#cccccc"), - } - - return self._default_def - - def _get_defs_by_name(self) -> dict[str, dict[str, str]]: - if self._definitions_by_name is None: - self._definitions_by_name = { - product_base_type_def["name"]: { - "type": "material-symbols", - "name": product_base_type_def.get("icon", "deployed_code"), - "color": product_base_type_def.get("color", "#cccccc"), - } - for product_base_type_def in self._definitions - } - return self._definitions_by_name - - def version_item_from_entity(version): version_attribs = version["attrib"] tags = version["tags"] @@ -211,8 +164,6 @@ class ProductsModel: self._product_folder_ids_mapping = collections.defaultdict(dict) # Cache helpers - self._product_type_icons_mapping = NestedCacheItem( - levels=1, default_factory=list, lifetime=self.lifetime) self._product_type_items_cache = NestedCacheItem( levels=1, default_factory=list, lifetime=self.lifetime) self._product_base_type_items_cache = NestedCacheItem( @@ -229,7 +180,6 @@ class ProductsModel: self._version_item_by_id.clear() self._product_folder_ids_mapping.clear() - self._product_type_icons_mapping.reset() self._product_type_items_cache.reset() self._product_items_cache.reset() self._repre_items_cache.reset() @@ -267,6 +217,10 @@ class ProductsModel: ) -> list[ProductBaseTypeItem]: """Product base type items for the project. + Notes: + This will be used for filtering product types in UI when + product base types are fully implemented. + Args: project_name (optional, str): Project name. @@ -510,24 +464,8 @@ class ProductsModel: def _get_product_type_icons( self, project_name: Optional[str] - ) -> ProductBaseTypeIconMapping: - cache = self._product_type_icons_mapping[project_name] - if cache.is_valid: - return cache.get_data() - - project_entity = self._controller.get_project_entity(project_name) - icons_mapping = ProductBaseTypeIconMapping() - if project_entity: - product_base_types = ( - project_entity["config"].get("productBaseTypes", {}) - ) - icons_mapping = ProductBaseTypeIconMapping( - product_base_types.get("default"), - product_base_types.get("definitions") - ) - - cache.update_data(icons_mapping) - return icons_mapping + ) -> ProductTypeIconMapping: + return self._controller.get_product_type_icons_mapping(project_name) def _get_product_items_by_id(self, project_name, product_ids): product_item_by_id = self._product_item_by_id[project_name] @@ -542,7 +480,7 @@ class ProductsModel: output.update( self._query_product_items_by_ids( - project_name, missing_product_ids + project_name, product_ids=missing_product_ids ) ) return output