mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
Merge pull request #1301 from ynput/enhancement/1294-product-base-types-support-in-loading
🏛️Product base types: add support to loaders
This commit is contained in:
commit
b2ab8ef5c2
6 changed files with 386 additions and 101 deletions
|
|
@ -1,24 +1,28 @@
|
|||
"""Plugins for loading representations and products into host applications."""
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import logging
|
||||
from typing import Any, Type, Optional
|
||||
from abc import abstractmethod
|
||||
|
||||
from ayon_core.settings import get_project_settings
|
||||
from abc import abstractmethod
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, Optional, Type
|
||||
|
||||
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
|
||||
|
||||
|
||||
class LoaderPlugin(list):
|
||||
"""Load representation into host application"""
|
||||
|
||||
product_types = set()
|
||||
product_types: set[str] = set()
|
||||
product_base_types: Optional[set[str]] = None
|
||||
representations = set()
|
||||
extensions = {"*"}
|
||||
order = 0
|
||||
|
|
@ -61,12 +65,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 +83,6 @@ class LoaderPlugin(list):
|
|||
Returns:
|
||||
bool: Representation has valid extension
|
||||
"""
|
||||
|
||||
if "*" in cls.extensions:
|
||||
return True
|
||||
|
||||
|
|
@ -124,18 +127,34 @@ class LoaderPlugin(list):
|
|||
"""
|
||||
|
||||
plugin_repre_names = cls.get_representations()
|
||||
plugin_product_types = cls.product_types
|
||||
|
||||
# If the product base type isn't defined on the loader plugin,
|
||||
# then we will use the product types.
|
||||
plugin_product_filter = cls.product_base_types
|
||||
if plugin_product_filter is None:
|
||||
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"]
|
||||
|
||||
# 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
|
||||
or not plugin_product_filter
|
||||
or not cls.extensions
|
||||
):
|
||||
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 (
|
||||
"*" not in plugin_repre_names
|
||||
|
|
@ -143,17 +162,34 @@ class LoaderPlugin(list):
|
|||
):
|
||||
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:
|
||||
product_type = product_entity.get("productType")
|
||||
product_base_type = product_entity.get("productBaseType")
|
||||
|
||||
# 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 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
|
||||
|
||||
product_entity = context["product"]
|
||||
product_type = product_entity["productType"]
|
||||
# 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_type in plugin_product_types
|
||||
return product_filter in plugin_product_filter
|
||||
|
||||
@classmethod
|
||||
def get_representations(cls):
|
||||
|
|
@ -208,19 +244,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 []
|
||||
|
||||
|
|
@ -347,10 +381,8 @@ 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__
|
||||
),
|
||||
exc_info=True,
|
||||
f"Failed to apply settings to loader {plugin.__name__}",
|
||||
exc_info=True
|
||||
)
|
||||
compatible_hooks = []
|
||||
for hook_cls in sorted_hooks:
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
"""Abstract base classes for loader tool."""
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from ayon_core.lib.attribute_definitions import (
|
||||
AbstractAttrDef,
|
||||
serialize_attr_defs,
|
||||
deserialize_attr_defs,
|
||||
serialize_attr_defs,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -13,10 +16,10 @@ class ProductTypeItem:
|
|||
|
||||
Args:
|
||||
name (str): Product type name.
|
||||
icon (dict[str, Any]): Product type icon definition.
|
||||
icon (dict[str, str]): Product type icon definition.
|
||||
"""
|
||||
|
||||
def __init__(self, name, icon):
|
||||
def __init__(self, name: str, icon: dict[str, str]):
|
||||
self.name = name
|
||||
self.icon = icon
|
||||
|
||||
|
|
@ -31,6 +34,41 @@ class ProductTypeItem:
|
|||
return cls(**data)
|
||||
|
||||
|
||||
class ProductBaseTypeItem:
|
||||
"""Item representing the product base type."""
|
||||
|
||||
def __init__(self, name: str, icon: dict[str, str]):
|
||||
"""Initialize product base type item."""
|
||||
self.name = name
|
||||
self.icon = icon
|
||||
|
||||
def to_data(self) -> dict[str, Any]:
|
||||
"""Convert item to data dictionary.
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: Data representation of the item.
|
||||
|
||||
"""
|
||||
return {
|
||||
"name": self.name,
|
||||
"icon": self.icon,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_data(
|
||||
cls, data: dict[str, Any]) -> ProductBaseTypeItem:
|
||||
"""Create item from data dictionary.
|
||||
|
||||
Args:
|
||||
data (dict[str, Any]): Data to create item from.
|
||||
|
||||
Returns:
|
||||
ProductBaseTypeItem: Item created from the provided data.
|
||||
|
||||
"""
|
||||
return cls(**data)
|
||||
|
||||
|
||||
class ProductItem:
|
||||
"""Product item with it versions.
|
||||
|
||||
|
|
@ -38,8 +76,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 (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.
|
||||
|
|
@ -49,35 +87,41 @@ 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: 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,
|
||||
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) -> dict[str, Any]:
|
||||
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 +168,21 @@ 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],
|
||||
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
|
||||
|
|
@ -198,7 +242,7 @@ class VersionItem:
|
|||
def __le__(self, other):
|
||||
return self.__eq__(other) or self.__lt__(other)
|
||||
|
||||
def to_data(self):
|
||||
def to_data(self) -> dict[str, Any]:
|
||||
return {
|
||||
"version_id": self.version_id,
|
||||
"product_id": self.product_id,
|
||||
|
|
@ -218,7 +262,7 @@ class VersionItem:
|
|||
}
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data):
|
||||
def from_data(cls, data: dict[str, Any]) -> VersionItem:
|
||||
return cls(**data)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 the 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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from typing import Optional
|
|||
|
||||
from qtpy import QtWidgets, QtCore
|
||||
|
||||
from ayon_core.pipeline.compatibility import is_product_base_type_supported
|
||||
from ayon_core.tools.utils import (
|
||||
RecursiveSortFilterProxyModel,
|
||||
DeselectableTreeView,
|
||||
|
|
@ -142,6 +143,7 @@ class ProductsWidget(QtWidgets.QWidget):
|
|||
default_widths = (
|
||||
200, # Product name
|
||||
90, # Product type
|
||||
90, # Product base type
|
||||
130, # Folder label
|
||||
60, # Version
|
||||
100, # Status
|
||||
|
|
@ -261,6 +263,12 @@ class ProductsWidget(QtWidgets.QWidget):
|
|||
self._controller.is_sitesync_enabled()
|
||||
)
|
||||
|
||||
if not is_product_base_type_supported():
|
||||
# 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.
|
||||
|
||||
|
|
|
|||
88
tests/client/ayon_core/pipeline/load/test_loaders.py
Normal file
88
tests/client/ayon_core/pipeline/load/test_loaders.py
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue