From 34bdab72ee28d27567fa81c0b21cb1eb58fedf95 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 24 Jun 2024 17:29:16 +0200 Subject: [PATCH 1/7] Don't use six to define ABC class --- client/ayon_core/tools/common_models/__init__.py | 4 ++++ client/ayon_core/tools/common_models/projects.py | 6 ++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/tools/common_models/__init__.py b/client/ayon_core/tools/common_models/__init__.py index f09edfeab2..40394c4732 100644 --- a/client/ayon_core/tools/common_models/__init__.py +++ b/client/ayon_core/tools/common_models/__init__.py @@ -2,6 +2,8 @@ from .cache import CacheItem, NestedCacheItem from .projects import ( + StatusItem, + StatusStates, ProjectItem, ProjectsModel, PROJECTS_MODEL_SENDER, @@ -21,6 +23,8 @@ __all__ = ( "CacheItem", "NestedCacheItem", + "StatusItem", + "StatusStates", "ProjectItem", "ProjectsModel", "PROJECTS_MODEL_SENDER", diff --git a/client/ayon_core/tools/common_models/projects.py b/client/ayon_core/tools/common_models/projects.py index 4e8925388d..1c455a9619 100644 --- a/client/ayon_core/tools/common_models/projects.py +++ b/client/ayon_core/tools/common_models/projects.py @@ -1,8 +1,7 @@ import contextlib -from abc import ABCMeta, abstractmethod +from abc import ABC, abstractmethod import ayon_api -import six from ayon_core.style import get_default_entity_icon_color from ayon_core.lib import CacheItem, NestedCacheItem @@ -10,8 +9,7 @@ from ayon_core.lib import CacheItem, NestedCacheItem PROJECTS_MODEL_SENDER = "projects.model" -@six.add_metaclass(ABCMeta) -class AbstractHierarchyController: +class AbstractHierarchyController(ABC): @abstractmethod def emit_event(self, topic, data, source): pass From 386a627abe9e71783d67929da23f076503940eb7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 24 Jun 2024 17:31:58 +0200 Subject: [PATCH 2/7] Added helper classes and hints for state --- .../ayon_core/tools/common_models/projects.py | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/tools/common_models/projects.py b/client/ayon_core/tools/common_models/projects.py index 1c455a9619..17599c27f6 100644 --- a/client/ayon_core/tools/common_models/projects.py +++ b/client/ayon_core/tools/common_models/projects.py @@ -1,5 +1,6 @@ import contextlib from abc import ABC, abstractmethod +from typing import Literal, Dict, Any import ayon_api @@ -7,6 +8,14 @@ from ayon_core.style import get_default_entity_icon_color from ayon_core.lib import CacheItem, NestedCacheItem PROJECTS_MODEL_SENDER = "projects.model" +StatusStatesType = Literal["not_started", "in_progress", "done", "blocked"] + + +class StatusStates: + not_started = "not_started" + in_progress = "in_progress" + done = "done" + blocked = "blocked" class AbstractHierarchyController(ABC): @@ -23,18 +32,24 @@ class StatusItem: color (str): Status color in hex ("#434a56"). short (str): Short status name ("NRD"). icon (str): Icon name in MaterialIcons ("fiber_new"). - state (Literal["not_started", "in_progress", "done", "blocked"]): - Status state. + state (StatusStatesType): Status state. """ - def __init__(self, name, color, short, icon, state): - self.name = name - self.color = color - self.short = short - self.icon = icon - self.state = state + def __init__( + self, + name: str, + color: str, + short: str, + icon: str, + state: StatusStatesType + ): + self.name: str = name + self.color: str = color + self.short: str = short + self.icon: str = icon + self.state: StatusStatesType = state - def to_data(self): + def to_data(self) -> Dict[str, Any]: return { "name": self.name, "color": self.color, From 14fc4ae187eb48ab169bc94d8b66b3a475d2028f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 24 Jun 2024 17:32:21 +0200 Subject: [PATCH 3/7] added 'is_last_approved' attribute to 'VersionItem' --- .../tools/sceneinventory/models/containers.py | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/tools/sceneinventory/models/containers.py b/client/ayon_core/tools/sceneinventory/models/containers.py index 95c5322343..c3881ea40d 100644 --- a/client/ayon_core/tools/sceneinventory/models/containers.py +++ b/client/ayon_core/tools/sceneinventory/models/containers.py @@ -3,7 +3,9 @@ import collections import ayon_api from ayon_api.graphql import GraphQlQuery + from ayon_core.host import ILoadHost +from ayon_core.tools.common_models.projects import StatusStates # --- Implementation that should be in ayon-python-api --- @@ -149,26 +151,35 @@ class RepresentationInfo: class VersionItem: - def __init__(self, version_id, product_id, version, status, is_latest): - self.version = version - self.version_id = version_id - self.product_id = product_id - self.version = version - self.status = status - self.is_latest = is_latest + def __init__( + self, + version_id: str, + product_id: str, + version: int, + status: str, + is_latest: bool, + is_last_approved: bool, + ): + self.version_id: str = version_id + self.product_id: str = product_id + self.version: int = version + self.status: str = status + self.is_latest: bool = is_latest + self.is_last_approved: bool = is_last_approved @property def is_hero(self): return self.version < 0 @classmethod - def from_entity(cls, version_entity, is_latest): + def from_entity(cls, version_entity, is_latest, is_last_approved): return cls( version_id=version_entity["id"], product_id=version_entity["productId"], version=version_entity["version"], status=version_entity["status"], is_latest=is_latest, + is_last_approved=is_last_approved, ) @@ -275,6 +286,11 @@ class ContainersModel: if product_id not in self._version_items_by_product_id } if missing_ids: + status_items_by_name = { + status_item.name: status_item + for status_item in self._controller.get_project_status_items() + } + def version_sorted(entity): return entity["version"] @@ -300,9 +316,21 @@ class ContainersModel: version_entities_by_product_id.items() ): last_version = abs(version_entities[-1]["version"]) + last_approved_id = None + for version_entity in version_entities: + status_item = status_items_by_name.get( + version_entity["status"] + ) + if status_item is None: + continue + if status_item.state == StatusStates.done: + last_approved_id = version_entity["id"] + version_items_by_id = { entity["id"]: VersionItem.from_entity( - entity, abs(entity["version"]) == last_version + entity, + abs(entity["version"]) == last_version, + entity["id"] == last_approved_id ) for entity in version_entities } From db3f5c60c68e47b210dcd644ea69f754c554b8ee Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 24 Jun 2024 17:38:18 +0200 Subject: [PATCH 4/7] implemented logic to update to latest approved --- client/ayon_core/tools/sceneinventory/view.py | 84 ++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/tools/sceneinventory/view.py b/client/ayon_core/tools/sceneinventory/view.py index c8cc3299a2..017a9597f1 100644 --- a/client/ayon_core/tools/sceneinventory/view.py +++ b/client/ayon_core/tools/sceneinventory/view.py @@ -233,10 +233,18 @@ class SceneInventoryView(QtWidgets.QTreeView): has_outdated = False has_loaded_hero_versions = False has_available_hero_version = False - for version_items_by_id in version_items_by_product_id.values(): + has_outdated_approved = False + last_version_by_product_id = {} + for product_id, version_items_by_id in ( + version_items_by_product_id.items() + ): + _has_outdated_approved = False + _last_approved_version_item = None for version_item in version_items_by_id.values(): if version_item.is_hero: has_available_hero_version = True + if version_item.is_last_approved: + _last_approved_version_item = version_item if version_item.version_id not in version_ids: continue @@ -245,6 +253,17 @@ class SceneInventoryView(QtWidgets.QTreeView): elif not version_item.is_latest: has_outdated = True + elif not version_item.is_last_approved: + _has_outdated_approved = True + + if ( + _has_outdated_approved + and _last_approved_version_item is not None + ): + last_version_by_product_id[product_id] = ( + _last_approved_version_item + ) + has_outdated_approved = True switch_to_versioned = None if has_loaded_hero_versions: @@ -261,6 +280,41 @@ class SceneInventoryView(QtWidgets.QTreeView): lambda: self._on_switch_to_versioned(item_ids) ) + update_to_last_approved_action = None + if has_outdated_approved: + approved_version_by_item_id = {} + for container_item in container_items_by_id.values(): + repre_id = container_item.representation_id + repre_info = repre_info_by_id.get(repre_id) + if not repre_info or not repre_info.is_valid: + continue + version_item = last_version_by_product_id.get( + repre_info.product_id + ) + if ( + version_item is None + or version_item.id == repre_info.version_id + ): + continue + approved_version_by_item_id[container_item.item_id] = ( + version_item.version + ) + + update_icon = qtawesome.icon( + "fa.angle-double-up", + color="#00f0b4" + ) + update_to_last_approved_action = QtWidgets.QAction( + update_icon, + "Update to last approved", + menu + ) + update_to_last_approved_action.triggered.connect( + lambda: self._update_containers_to_approved_versions( + approved_version_by_item_id + ) + ) + update_to_latest_action = None if has_outdated or has_loaded_hero_versions: update_icon = qtawesome.icon( @@ -299,7 +353,9 @@ class SceneInventoryView(QtWidgets.QTreeView): # set version set_version_action = None if active_repre_id is not None: - set_version_icon = qtawesome.icon("fa.hashtag", color=DEFAULT_COLOR) + set_version_icon = qtawesome.icon( + "fa.hashtag", color=DEFAULT_COLOR + ) set_version_action = QtWidgets.QAction( set_version_icon, "Set version", @@ -323,6 +379,9 @@ class SceneInventoryView(QtWidgets.QTreeView): if switch_to_versioned: menu.addAction(switch_to_versioned) + if update_to_last_approved_action: + menu.addAction(update_to_last_approved_action) + if update_to_latest_action: menu.addAction(update_to_latest_action) @@ -970,3 +1029,24 @@ class SceneInventoryView(QtWidgets.QTreeView): """ versions = [version for _ in range(len(item_ids))] self._update_containers(item_ids, versions) + + def _update_containers_to_approved_versions( + self, approved_version_by_item_id + ): + """Helper to update items to given version (or version per item) + + If at least one item is specified this will always try to refresh + the inventory even if errors occurred on any of the items. + + Arguments: + approved_version_by_item_id (Dict[str, int]): Version to set by + item id. + + """ + versions = [] + item_ids = [] + for item_id, version in approved_version_by_item_id.items(): + item_ids.append(item_id) + versions.append(version) + + self._update_containers(item_ids, versions) From b710af2662b2df77f672d4d8c84261433caeab5b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 24 Jun 2024 18:00:54 +0200 Subject: [PATCH 5/7] removed 'Literal' --- client/ayon_core/tools/common_models/projects.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/tools/common_models/projects.py b/client/ayon_core/tools/common_models/projects.py index 17599c27f6..7ec941e6bd 100644 --- a/client/ayon_core/tools/common_models/projects.py +++ b/client/ayon_core/tools/common_models/projects.py @@ -1,6 +1,6 @@ import contextlib from abc import ABC, abstractmethod -from typing import Literal, Dict, Any +from typing import Dict, Any import ayon_api @@ -8,7 +8,6 @@ from ayon_core.style import get_default_entity_icon_color from ayon_core.lib import CacheItem, NestedCacheItem PROJECTS_MODEL_SENDER = "projects.model" -StatusStatesType = Literal["not_started", "in_progress", "done", "blocked"] class StatusStates: @@ -32,7 +31,7 @@ class StatusItem: color (str): Status color in hex ("#434a56"). short (str): Short status name ("NRD"). icon (str): Icon name in MaterialIcons ("fiber_new"). - state (StatusStatesType): Status state. + state (str): Status state. """ def __init__( @@ -41,13 +40,13 @@ class StatusItem: color: str, short: str, icon: str, - state: StatusStatesType + state: str ): self.name: str = name self.color: str = color self.short: str = short self.icon: str = icon - self.state: StatusStatesType = state + self.state: str = state def to_data(self) -> Dict[str, Any]: return { From 47fafbdc05f726d2d11ad2971ae393acbc28f6f7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 24 Jun 2024 18:15:04 +0200 Subject: [PATCH 6/7] fix logic of action discovery --- client/ayon_core/tools/sceneinventory/view.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/tools/sceneinventory/view.py b/client/ayon_core/tools/sceneinventory/view.py index 017a9597f1..22ba15fda8 100644 --- a/client/ayon_core/tools/sceneinventory/view.py +++ b/client/ayon_core/tools/sceneinventory/view.py @@ -243,18 +243,18 @@ class SceneInventoryView(QtWidgets.QTreeView): for version_item in version_items_by_id.values(): if version_item.is_hero: has_available_hero_version = True - if version_item.is_last_approved: + + elif version_item.is_last_approved: _last_approved_version_item = version_item + _has_outdated_approved = True if version_item.version_id not in version_ids: continue + if version_item.is_hero: has_loaded_hero_versions = True - elif not version_item.is_latest: has_outdated = True - elif not version_item.is_last_approved: - _has_outdated_approved = True if ( _has_outdated_approved @@ -281,8 +281,8 @@ class SceneInventoryView(QtWidgets.QTreeView): ) update_to_last_approved_action = None + approved_version_by_item_id = {} if has_outdated_approved: - approved_version_by_item_id = {} for container_item in container_items_by_id.values(): repre_id = container_item.representation_id repre_info = repre_info_by_id.get(repre_id) @@ -293,13 +293,14 @@ class SceneInventoryView(QtWidgets.QTreeView): ) if ( version_item is None - or version_item.id == repre_info.version_id + or version_item.version_id == repre_info.version_id ): continue approved_version_by_item_id[container_item.item_id] = ( version_item.version ) + if approved_version_by_item_id: update_icon = qtawesome.icon( "fa.angle-double-up", color="#00f0b4" From 403442b281b0645952fc3b9f6513e8c8c27ba88c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 24 Jun 2024 18:20:40 +0200 Subject: [PATCH 7/7] fix color of loaded version --- client/ayon_core/tools/sceneinventory/model.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/tools/sceneinventory/model.py b/client/ayon_core/tools/sceneinventory/model.py index 3e0c361535..335df87b95 100644 --- a/client/ayon_core/tools/sceneinventory/model.py +++ b/client/ayon_core/tools/sceneinventory/model.py @@ -217,7 +217,9 @@ class InventoryModel(QtGui.QStandardItemModel): version_label = format_version(version_item.version) is_hero = version_item.version < 0 is_latest = version_item.is_latest - if not is_latest: + # TODO maybe use different colors for last approved and last + # version? Or don't care about color at all? + if not is_latest and not version_item.is_last_approved: version_color = self.OUTDATED_COLOR status_name = version_item.status