mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
Merge pull request #1395 from ynput/enhancement/874-publisher-editorial-linked-instances-with-grouping-view
AY-7790 Create: Parenting of instances
This commit is contained in:
commit
e1781efbbc
11 changed files with 1510 additions and 810 deletions
|
|
@ -21,12 +21,14 @@ from .exceptions import (
|
|||
TemplateFillError,
|
||||
)
|
||||
from .structures import (
|
||||
ParentFlags,
|
||||
CreatedInstance,
|
||||
ConvertorItem,
|
||||
AttributeValues,
|
||||
CreatorAttributeValues,
|
||||
PublishAttributeValues,
|
||||
PublishAttributes,
|
||||
InstanceContextInfo,
|
||||
)
|
||||
from .utils import (
|
||||
get_last_versions_for_instances,
|
||||
|
|
@ -77,12 +79,14 @@ __all__ = (
|
|||
"TaskNotSetError",
|
||||
"TemplateFillError",
|
||||
|
||||
"ParentFlags",
|
||||
"CreatedInstance",
|
||||
"ConvertorItem",
|
||||
"AttributeValues",
|
||||
"CreatorAttributeValues",
|
||||
"PublishAttributeValues",
|
||||
"PublishAttributes",
|
||||
"InstanceContextInfo",
|
||||
|
||||
"get_last_versions_for_instances",
|
||||
"get_next_versions_for_instances",
|
||||
|
|
|
|||
|
|
@ -41,7 +41,12 @@ from .exceptions import (
|
|||
HostMissRequiredMethod,
|
||||
)
|
||||
from .changes import TrackChangesItem
|
||||
from .structures import PublishAttributes, ConvertorItem, InstanceContextInfo
|
||||
from .structures import (
|
||||
PublishAttributes,
|
||||
ConvertorItem,
|
||||
InstanceContextInfo,
|
||||
ParentFlags,
|
||||
)
|
||||
from .creator_plugins import (
|
||||
Creator,
|
||||
AutoCreator,
|
||||
|
|
@ -80,6 +85,7 @@ INSTANCE_ADDED_TOPIC = "instances.added"
|
|||
INSTANCE_REMOVED_TOPIC = "instances.removed"
|
||||
VALUE_CHANGED_TOPIC = "values.changed"
|
||||
INSTANCE_REQUIREMENT_CHANGED_TOPIC = "instance.requirement.changed"
|
||||
INSTANCE_PARENT_CHANGED_TOPIC = "instance.parent.changed"
|
||||
PRE_CREATE_ATTR_DEFS_CHANGED_TOPIC = "pre.create.attr.defs.changed"
|
||||
CREATE_ATTR_DEFS_CHANGED_TOPIC = "create.attr.defs.changed"
|
||||
PUBLISH_ATTR_DEFS_CHANGED_TOPIC = "publish.attr.defs.changed"
|
||||
|
|
@ -262,6 +268,8 @@ class CreateContext:
|
|||
# - right now used only for 'mandatory' but can be extended
|
||||
# in future
|
||||
"requirement_change": BulkInfo(),
|
||||
# Instance parent changed
|
||||
"parent_change": BulkInfo(),
|
||||
}
|
||||
self._bulk_order = []
|
||||
|
||||
|
|
@ -1083,6 +1091,35 @@ class CreateContext:
|
|||
INSTANCE_REQUIREMENT_CHANGED_TOPIC, callback
|
||||
)
|
||||
|
||||
def add_instance_parent_change_callback(
|
||||
self, callback: Callable
|
||||
) -> "EventCallback":
|
||||
"""Register callback to listen to instance parent changes.
|
||||
|
||||
Instance changed parent or parent flags.
|
||||
|
||||
Data structure of event:
|
||||
|
||||
```python
|
||||
{
|
||||
"instances": [CreatedInstance, ...],
|
||||
"create_context": CreateContext
|
||||
}
|
||||
```
|
||||
|
||||
Args:
|
||||
callback (Callable): Callback function that will be called when
|
||||
instance requirement changed.
|
||||
|
||||
Returns:
|
||||
EventCallback: Created callback object which can be used to
|
||||
stop listening.
|
||||
|
||||
"""
|
||||
return self._event_hub.add_callback(
|
||||
INSTANCE_PARENT_CHANGED_TOPIC, callback
|
||||
)
|
||||
|
||||
def context_data_to_store(self) -> dict[str, Any]:
|
||||
"""Data that should be stored by host function.
|
||||
|
||||
|
|
@ -1364,6 +1401,13 @@ class CreateContext:
|
|||
) as bulk_info:
|
||||
yield bulk_info
|
||||
|
||||
@contextmanager
|
||||
def bulk_instance_parent_change(self, sender: Optional[str] = None):
|
||||
with self._bulk_context(
|
||||
"parent_change", sender
|
||||
) as bulk_info:
|
||||
yield bulk_info
|
||||
|
||||
@contextmanager
|
||||
def bulk_publish_attr_defs_change(self, sender: Optional[str] = None):
|
||||
with self._bulk_context("publish_attrs_change", sender) as bulk_info:
|
||||
|
|
@ -1444,6 +1488,19 @@ class CreateContext:
|
|||
with self.bulk_instance_requirement_change() as bulk_item:
|
||||
bulk_item.append(instance_id)
|
||||
|
||||
def instance_parent_changed(self, instance_id: str) -> None:
|
||||
"""Instance parent changed.
|
||||
|
||||
Triggered by `CreatedInstance`.
|
||||
|
||||
Args:
|
||||
instance_id (Optional[str]): Instance id.
|
||||
|
||||
"""
|
||||
if self._is_instance_events_ready(instance_id):
|
||||
with self.bulk_instance_parent_change() as bulk_item:
|
||||
bulk_item.append(instance_id)
|
||||
|
||||
# --- context change callbacks ---
|
||||
def publish_attribute_value_changed(
|
||||
self, plugin_name: str, value: dict[str, Any]
|
||||
|
|
@ -2046,63 +2103,97 @@ class CreateContext:
|
|||
sender (Optional[str]): Sender of the event.
|
||||
|
||||
"""
|
||||
instance_ids_by_parent_id = collections.defaultdict(set)
|
||||
for instance in self.instances:
|
||||
instance_ids_by_parent_id[instance.parent_instance_id].add(
|
||||
instance.id
|
||||
)
|
||||
|
||||
instances_to_remove = list(instances)
|
||||
ids_to_remove = {
|
||||
instance.id
|
||||
for instance in instances_to_remove
|
||||
}
|
||||
_queue = collections.deque()
|
||||
_queue.extend(instances_to_remove)
|
||||
# Add children with parent lifetime flag
|
||||
while _queue:
|
||||
instance = _queue.popleft()
|
||||
ids_to_remove.add(instance.id)
|
||||
children_ids = instance_ids_by_parent_id[instance.id]
|
||||
for children_id in children_ids:
|
||||
if children_id in ids_to_remove:
|
||||
continue
|
||||
instance = self._instances_by_id[children_id]
|
||||
if instance.parent_flags & ParentFlags.parent_lifetime:
|
||||
instances_to_remove.append(instance)
|
||||
ids_to_remove.add(instance.id)
|
||||
_queue.append(instance)
|
||||
|
||||
instances_by_identifier = collections.defaultdict(list)
|
||||
for instance in instances:
|
||||
for instance in instances_to_remove:
|
||||
identifier = instance.creator_identifier
|
||||
instances_by_identifier[identifier].append(instance)
|
||||
|
||||
# Just remove instances from context if creator is not available
|
||||
missing_creators = set(instances_by_identifier) - set(self.creators)
|
||||
instances = []
|
||||
miss_creator_instances = []
|
||||
for identifier in missing_creators:
|
||||
instances.extend(
|
||||
instance
|
||||
for instance in instances_by_identifier[identifier]
|
||||
)
|
||||
miss_creator_instances.extend(instances_by_identifier[identifier])
|
||||
|
||||
self._remove_instances(instances, sender)
|
||||
with self.bulk_remove_instances(sender):
|
||||
self._remove_instances(miss_creator_instances, sender)
|
||||
|
||||
error_message = "Instances removement of creator \"{}\" failed. {}"
|
||||
failed_info = []
|
||||
# Remove instances by creator plugin order
|
||||
for creator in self.get_sorted_creators(
|
||||
instances_by_identifier.keys()
|
||||
):
|
||||
identifier = creator.identifier
|
||||
creator_instances = instances_by_identifier[identifier]
|
||||
error_message = "Instances removement of creator \"{}\" failed. {}"
|
||||
failed_info = []
|
||||
# Remove instances by creator plugin order
|
||||
for creator in self.get_sorted_creators(
|
||||
instances_by_identifier.keys()
|
||||
):
|
||||
identifier = creator.identifier
|
||||
# Filter instances by current state of 'CreateContext'
|
||||
# - in case instances were already removed as subroutine of
|
||||
# previous create plugin.
|
||||
creator_instances = [
|
||||
instance
|
||||
for instance in instances_by_identifier[identifier]
|
||||
if instance.id in self._instances_by_id
|
||||
]
|
||||
if not creator_instances:
|
||||
continue
|
||||
|
||||
label = creator.label
|
||||
failed = False
|
||||
add_traceback = False
|
||||
exc_info = None
|
||||
try:
|
||||
creator.remove_instances(creator_instances)
|
||||
label = creator.label
|
||||
failed = False
|
||||
add_traceback = False
|
||||
exc_info = None
|
||||
try:
|
||||
creator.remove_instances(creator_instances)
|
||||
|
||||
except CreatorError:
|
||||
failed = True
|
||||
exc_info = sys.exc_info()
|
||||
self.log.warning(
|
||||
error_message.format(identifier, exc_info[1])
|
||||
)
|
||||
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
|
||||
except: # noqa: E722
|
||||
failed = True
|
||||
add_traceback = True
|
||||
exc_info = sys.exc_info()
|
||||
self.log.warning(
|
||||
error_message.format(identifier, ""),
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
if failed:
|
||||
failed_info.append(
|
||||
prepare_failed_creator_operation_info(
|
||||
identifier, label, exc_info, add_traceback
|
||||
except CreatorError:
|
||||
failed = True
|
||||
exc_info = sys.exc_info()
|
||||
self.log.warning(
|
||||
error_message.format(identifier, exc_info[1])
|
||||
)
|
||||
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
|
||||
except: # noqa: E722
|
||||
failed = True
|
||||
add_traceback = True
|
||||
exc_info = sys.exc_info()
|
||||
self.log.warning(
|
||||
error_message.format(identifier, ""),
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
if failed:
|
||||
failed_info.append(
|
||||
prepare_failed_creator_operation_info(
|
||||
identifier, label, exc_info, add_traceback
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if failed_info:
|
||||
raise CreatorsRemoveFailed(failed_info)
|
||||
|
|
@ -2305,6 +2396,8 @@ class CreateContext:
|
|||
self._bulk_publish_attrs_change_finished(data, sender)
|
||||
elif key == "requirement_change":
|
||||
self._bulk_instance_requirement_change_finished(data, sender)
|
||||
elif key == "parent_change":
|
||||
self._bulk_instance_parent_change_finished(data, sender)
|
||||
|
||||
def _bulk_add_instances_finished(
|
||||
self,
|
||||
|
|
@ -2518,3 +2611,22 @@ class CreateContext:
|
|||
{"instances": instances},
|
||||
sender,
|
||||
)
|
||||
|
||||
def _bulk_instance_parent_change_finished(
|
||||
self,
|
||||
instance_ids: list[str],
|
||||
sender: Optional[str],
|
||||
):
|
||||
if not instance_ids:
|
||||
return
|
||||
|
||||
instances = [
|
||||
self.get_instance_by_id(instance_id)
|
||||
for instance_id in set(instance_ids)
|
||||
]
|
||||
|
||||
self._emit_event(
|
||||
INSTANCE_PARENT_CHANGED_TOPIC,
|
||||
{"instances": instances},
|
||||
sender,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import copy
|
||||
import collections
|
||||
from uuid import uuid4
|
||||
from enum import Enum
|
||||
import typing
|
||||
from typing import Optional, Dict, List, Any
|
||||
|
||||
|
|
@ -22,6 +23,23 @@ if typing.TYPE_CHECKING:
|
|||
from .creator_plugins import BaseCreator
|
||||
|
||||
|
||||
class IntEnum(int, Enum):
|
||||
"""An int-based Enum class that allows for int comparison."""
|
||||
|
||||
def __int__(self) -> int:
|
||||
return self.value
|
||||
|
||||
|
||||
class ParentFlags(IntEnum):
|
||||
# Delete instance if parent is deleted
|
||||
parent_lifetime = 1
|
||||
# Active state is propagated from parent to children
|
||||
# - the active state is propagated in collection phase
|
||||
# NOTE It might be helpful to have a function that would return "real"
|
||||
# active state for instances
|
||||
share_active = 1 << 1
|
||||
|
||||
|
||||
class ConvertorItem:
|
||||
"""Item representing convertor plugin.
|
||||
|
||||
|
|
@ -507,7 +525,9 @@ class CreatedInstance:
|
|||
if transient_data is None:
|
||||
transient_data = {}
|
||||
self._transient_data = transient_data
|
||||
self._is_mandatory = False
|
||||
self._is_mandatory: bool = False
|
||||
self._parent_instance_id: Optional[str] = None
|
||||
self._parent_flags: int = 0
|
||||
|
||||
# Create a copy of passed data to avoid changing them on the fly
|
||||
data = copy.deepcopy(data or {})
|
||||
|
|
@ -752,6 +772,39 @@ class CreatedInstance:
|
|||
self["active"] = True
|
||||
self._create_context.instance_requirement_changed(self.id)
|
||||
|
||||
@property
|
||||
def parent_instance_id(self) -> Optional[str]:
|
||||
return self._parent_instance_id
|
||||
|
||||
@property
|
||||
def parent_flags(self) -> int:
|
||||
return self._parent_flags
|
||||
|
||||
def set_parent(
|
||||
self, instance_id: Optional[str], flags: int
|
||||
) -> None:
|
||||
"""Set parent instance id and parenting flags.
|
||||
|
||||
Args:
|
||||
instance_id (Optional[str]): Parent instance id.
|
||||
flags (int): Parenting flags.
|
||||
|
||||
"""
|
||||
changed = False
|
||||
if instance_id != self._parent_instance_id:
|
||||
changed = True
|
||||
self._parent_instance_id = instance_id
|
||||
|
||||
if flags is None:
|
||||
flags = 0
|
||||
|
||||
if self._parent_flags != flags:
|
||||
self._parent_flags = flags
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
self._create_context.instance_parent_changed(self.id)
|
||||
|
||||
def changes(self):
|
||||
"""Calculate and return changes."""
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
"""
|
||||
import os
|
||||
import collections
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from ayon_core.host import IPublishHost
|
||||
from ayon_core.pipeline import registered_host
|
||||
from ayon_core.pipeline.create import CreateContext
|
||||
from ayon_core.pipeline.create import CreateContext, ParentFlags
|
||||
|
||||
|
||||
class CollectFromCreateContext(pyblish.api.ContextPlugin):
|
||||
|
|
@ -36,18 +38,51 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin):
|
|||
if project_name:
|
||||
context.data["projectName"] = project_name
|
||||
|
||||
# Separate root instances and parented instances
|
||||
instances_by_parent_id = collections.defaultdict(list)
|
||||
root_instances = []
|
||||
for created_instance in create_context.instances:
|
||||
parent_id = created_instance.parent_instance_id
|
||||
if parent_id is None:
|
||||
root_instances.append(created_instance)
|
||||
else:
|
||||
instances_by_parent_id[parent_id].append(created_instance)
|
||||
|
||||
# Traverse instances from top to bottom
|
||||
# - All instances without an existing parent are automatically
|
||||
# eliminated
|
||||
filtered_instances = []
|
||||
_queue = collections.deque()
|
||||
_queue.append((root_instances, True))
|
||||
while _queue:
|
||||
created_instances, parent_is_active = _queue.popleft()
|
||||
for created_instance in created_instances:
|
||||
is_active = created_instance["active"]
|
||||
# Use a parent's active state if parent flags defines that
|
||||
if (
|
||||
created_instance.parent_flags & ParentFlags.share_active
|
||||
and is_active
|
||||
):
|
||||
is_active = parent_is_active
|
||||
|
||||
if is_active:
|
||||
filtered_instances.append(created_instance)
|
||||
|
||||
children = instances_by_parent_id[created_instance.id]
|
||||
if children:
|
||||
_queue.append((children, is_active))
|
||||
|
||||
for created_instance in filtered_instances:
|
||||
instance_data = created_instance.data_to_store()
|
||||
if instance_data["active"]:
|
||||
thumbnail_path = thumbnail_paths_by_instance_id.get(
|
||||
created_instance.id
|
||||
)
|
||||
self.create_instance(
|
||||
context,
|
||||
instance_data,
|
||||
created_instance.transient_data,
|
||||
thumbnail_path
|
||||
)
|
||||
thumbnail_path = thumbnail_paths_by_instance_id.get(
|
||||
created_instance.id
|
||||
)
|
||||
self.create_instance(
|
||||
context,
|
||||
instance_data,
|
||||
created_instance.transient_data,
|
||||
thumbnail_path
|
||||
)
|
||||
|
||||
# Update global data to context
|
||||
context.data.update(create_context.context_data_to_store())
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@
|
|||
},
|
||||
"publisher": {
|
||||
"error": "#AA5050",
|
||||
"disabled": "#5b6779",
|
||||
"crash": "#FF6432",
|
||||
"success": "#458056",
|
||||
"warning": "#ffc671",
|
||||
|
|
|
|||
|
|
@ -1153,6 +1153,10 @@ PixmapButton:disabled {
|
|||
color: {color:publisher:error};
|
||||
}
|
||||
|
||||
#ListViewProductName[state="disabled"] {
|
||||
color: {color:publisher:disabled};
|
||||
}
|
||||
|
||||
#PublishInfoFrame {
|
||||
background: {color:bg};
|
||||
border-radius: 0.3em;
|
||||
|
|
|
|||
|
|
@ -219,6 +219,8 @@ class InstanceItem:
|
|||
is_active: bool,
|
||||
is_mandatory: bool,
|
||||
has_promised_context: bool,
|
||||
parent_instance_id: Optional[str],
|
||||
parent_flags: int,
|
||||
):
|
||||
self._instance_id: str = instance_id
|
||||
self._creator_identifier: str = creator_identifier
|
||||
|
|
@ -232,6 +234,8 @@ class InstanceItem:
|
|||
self._is_active: bool = is_active
|
||||
self._is_mandatory: bool = is_mandatory
|
||||
self._has_promised_context: bool = has_promised_context
|
||||
self._parent_instance_id: Optional[str] = parent_instance_id
|
||||
self._parent_flags: int = parent_flags
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
|
|
@ -261,6 +265,14 @@ class InstanceItem:
|
|||
def has_promised_context(self):
|
||||
return self._has_promised_context
|
||||
|
||||
@property
|
||||
def parent_instance_id(self):
|
||||
return self._parent_instance_id
|
||||
|
||||
@property
|
||||
def parent_flags(self) -> int:
|
||||
return self._parent_flags
|
||||
|
||||
def get_variant(self):
|
||||
return self._variant
|
||||
|
||||
|
|
@ -312,6 +324,8 @@ class InstanceItem:
|
|||
instance["active"],
|
||||
instance.is_mandatory,
|
||||
instance.has_promised_context,
|
||||
instance.parent_instance_id,
|
||||
instance.parent_flags,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -486,6 +500,9 @@ class CreateModel:
|
|||
self._create_context.add_instance_requirement_change_callback(
|
||||
self._cc_instance_requirement_changed
|
||||
)
|
||||
self._create_context.add_instance_parent_change_callback(
|
||||
self._cc_instance_parent_changed
|
||||
)
|
||||
|
||||
self._create_context.reset_finalization()
|
||||
|
||||
|
|
@ -566,15 +583,21 @@ class CreateModel:
|
|||
def set_instances_active_state(
|
||||
self, active_state_by_id: Dict[str, bool]
|
||||
):
|
||||
changed_ids = set()
|
||||
with self._create_context.bulk_value_changes(CREATE_EVENT_SOURCE):
|
||||
for instance_id, active in active_state_by_id.items():
|
||||
instance = self._create_context.get_instance_by_id(instance_id)
|
||||
instance["active"] = active
|
||||
if instance["active"] is not active:
|
||||
instance["active"] = active
|
||||
changed_ids.add(instance_id)
|
||||
|
||||
if not changed_ids:
|
||||
return
|
||||
|
||||
self._emit_event(
|
||||
"create.model.instances.context.changed",
|
||||
{
|
||||
"instance_ids": set(active_state_by_id.keys())
|
||||
"instance_ids": changed_ids
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -1191,6 +1214,16 @@ class CreateModel:
|
|||
{"instance_ids": instance_ids},
|
||||
)
|
||||
|
||||
def _cc_instance_parent_changed(self, event):
|
||||
instance_ids = {
|
||||
instance.id
|
||||
for instance in event.data["instances"]
|
||||
}
|
||||
self._emit_event(
|
||||
"create.model.instance.parent.changed",
|
||||
{"instance_ids": instance_ids},
|
||||
)
|
||||
|
||||
def _get_allowed_creators_pattern(self) -> Union[Pattern, None]:
|
||||
"""Provide regex pattern for configured creator labels in this context
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Generator
|
||||
|
||||
from qtpy import QtWidgets, QtCore
|
||||
|
||||
from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend
|
||||
|
|
@ -6,6 +10,7 @@ from .border_label_widget import BorderedLabelWidget
|
|||
from .card_view_widgets import InstanceCardView
|
||||
from .list_view_widgets import InstanceListView
|
||||
from .widgets import (
|
||||
AbstractInstanceView,
|
||||
CreateInstanceBtn,
|
||||
RemoveInstanceBtn,
|
||||
ChangeViewBtn,
|
||||
|
|
@ -43,10 +48,16 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
|
||||
product_view_cards = InstanceCardView(controller, product_views_widget)
|
||||
product_list_view = InstanceListView(controller, product_views_widget)
|
||||
product_list_view.set_parent_grouping(False)
|
||||
product_list_view_grouped = InstanceListView(
|
||||
controller, product_views_widget
|
||||
)
|
||||
product_list_view_grouped.set_parent_grouping(True)
|
||||
|
||||
product_views_layout = QtWidgets.QStackedLayout()
|
||||
product_views_layout.addWidget(product_view_cards)
|
||||
product_views_layout.addWidget(product_list_view)
|
||||
product_views_layout.addWidget(product_list_view_grouped)
|
||||
product_views_layout.setCurrentWidget(product_view_cards)
|
||||
|
||||
# Buttons at the bottom of product view
|
||||
|
|
@ -118,6 +129,12 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
product_list_view.double_clicked.connect(
|
||||
self.publish_tab_requested
|
||||
)
|
||||
product_list_view_grouped.selection_changed.connect(
|
||||
self._on_product_change
|
||||
)
|
||||
product_list_view_grouped.double_clicked.connect(
|
||||
self.publish_tab_requested
|
||||
)
|
||||
product_view_cards.selection_changed.connect(
|
||||
self._on_product_change
|
||||
)
|
||||
|
|
@ -159,16 +176,22 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
"create.model.instance.requirement.changed",
|
||||
self._on_instance_requirement_changed
|
||||
)
|
||||
controller.register_event_callback(
|
||||
"create.model.instance.parent.changed",
|
||||
self._on_instance_parent_changed
|
||||
)
|
||||
|
||||
self._product_content_widget = product_content_widget
|
||||
self._product_content_layout = product_content_layout
|
||||
|
||||
self._product_view_cards = product_view_cards
|
||||
self._product_list_view = product_list_view
|
||||
self._product_list_view_grouped = product_list_view_grouped
|
||||
self._product_views_layout = product_views_layout
|
||||
|
||||
self._create_btn = create_btn
|
||||
self._delete_btn = delete_btn
|
||||
self._change_view_btn = change_view_btn
|
||||
|
||||
self._product_attributes_widget = product_attributes_widget
|
||||
self._create_widget = create_widget
|
||||
|
|
@ -246,7 +269,7 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
)
|
||||
|
||||
def has_items(self):
|
||||
view = self._product_views_layout.currentWidget()
|
||||
view = self._get_current_view()
|
||||
return view.has_items()
|
||||
|
||||
def _on_create_clicked(self):
|
||||
|
|
@ -361,17 +384,18 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
def _on_instance_requirement_changed(self, event):
|
||||
self._refresh_instance_states(event["instance_ids"])
|
||||
|
||||
def _refresh_instance_states(self, instance_ids):
|
||||
current_idx = self._product_views_layout.currentIndex()
|
||||
for idx in range(self._product_views_layout.count()):
|
||||
if idx == current_idx:
|
||||
continue
|
||||
widget = self._product_views_layout.widget(idx)
|
||||
if widget.refreshed:
|
||||
widget.set_refreshed(False)
|
||||
def _on_instance_parent_changed(self, event):
|
||||
self._refresh_instance_states(event["instance_ids"])
|
||||
|
||||
current_widget = self._product_views_layout.widget(current_idx)
|
||||
current_widget.refresh_instance_states(instance_ids)
|
||||
def _refresh_instance_states(self, instance_ids):
|
||||
current_view = self._get_current_view()
|
||||
for view in self._iter_views():
|
||||
if view is current_view:
|
||||
current_view = view
|
||||
elif view.refreshed:
|
||||
view.set_refreshed(False)
|
||||
|
||||
current_view.refresh_instance_states(instance_ids)
|
||||
|
||||
def _on_convert_requested(self):
|
||||
self.convert_requested.emit()
|
||||
|
|
@ -385,7 +409,7 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
convertor plugins.
|
||||
"""
|
||||
|
||||
view = self._product_views_layout.currentWidget()
|
||||
view = self._get_current_view()
|
||||
return view.get_selected_items()
|
||||
|
||||
def get_selected_legacy_convertors(self):
|
||||
|
|
@ -400,12 +424,12 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
return convertor_identifiers
|
||||
|
||||
def _change_view_type(self):
|
||||
old_view = self._get_current_view()
|
||||
|
||||
idx = self._product_views_layout.currentIndex()
|
||||
new_idx = (idx + 1) % self._product_views_layout.count()
|
||||
|
||||
old_view = self._product_views_layout.currentWidget()
|
||||
new_view = self._product_views_layout.widget(new_idx)
|
||||
|
||||
new_view = self._get_view_by_idx(new_idx)
|
||||
if not new_view.refreshed:
|
||||
new_view.refresh()
|
||||
new_view.set_refreshed(True)
|
||||
|
|
@ -418,22 +442,52 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
new_view.set_selected_items(
|
||||
instance_ids, context_selected, convertor_identifiers
|
||||
)
|
||||
view_type = "list"
|
||||
if new_view is self._product_list_view_grouped:
|
||||
view_type = "card"
|
||||
elif new_view is self._product_list_view:
|
||||
view_type = "list-parent-grouping"
|
||||
|
||||
self._change_view_btn.set_view_type(view_type)
|
||||
self._product_views_layout.setCurrentIndex(new_idx)
|
||||
|
||||
self._on_product_change()
|
||||
|
||||
def _iter_views(self) -> Generator[AbstractInstanceView, None, None]:
|
||||
for idx in range(self._product_views_layout.count()):
|
||||
widget = self._product_views_layout.widget(idx)
|
||||
if not isinstance(widget, AbstractInstanceView):
|
||||
raise TypeError(
|
||||
"Current widget is not instance of 'AbstractInstanceView'"
|
||||
)
|
||||
yield widget
|
||||
|
||||
def _get_current_view(self) -> AbstractInstanceView:
|
||||
widget = self._product_views_layout.currentWidget()
|
||||
if isinstance(widget, AbstractInstanceView):
|
||||
return widget
|
||||
raise TypeError(
|
||||
"Current widget is not instance of 'AbstractInstanceView'"
|
||||
)
|
||||
|
||||
def _get_view_by_idx(self, idx: int) -> AbstractInstanceView:
|
||||
widget = self._product_views_layout.widget(idx)
|
||||
if isinstance(widget, AbstractInstanceView):
|
||||
return widget
|
||||
raise TypeError(
|
||||
"Current widget is not instance of 'AbstractInstanceView'"
|
||||
)
|
||||
|
||||
def _refresh_instances(self):
|
||||
if self._refreshing_instances:
|
||||
return
|
||||
|
||||
self._refreshing_instances = True
|
||||
|
||||
for idx in range(self._product_views_layout.count()):
|
||||
widget = self._product_views_layout.widget(idx)
|
||||
widget.set_refreshed(False)
|
||||
for view in self._iter_views():
|
||||
view.set_refreshed(False)
|
||||
|
||||
view = self._product_views_layout.currentWidget()
|
||||
view = self._get_current_view()
|
||||
view.refresh()
|
||||
view.set_refreshed(True)
|
||||
|
||||
|
|
@ -444,25 +498,22 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
|
||||
# Give a change to process Resize Request
|
||||
QtWidgets.QApplication.processEvents()
|
||||
# Trigger update geometry of
|
||||
widget = self._product_views_layout.currentWidget()
|
||||
widget.updateGeometry()
|
||||
# Trigger update geometry
|
||||
view.updateGeometry()
|
||||
|
||||
def _on_publish_start(self):
|
||||
"""Publish started."""
|
||||
|
||||
self._create_btn.setEnabled(False)
|
||||
self._product_attributes_wrap.setEnabled(False)
|
||||
for idx in range(self._product_views_layout.count()):
|
||||
widget = self._product_views_layout.widget(idx)
|
||||
widget.set_active_toggle_enabled(False)
|
||||
for view in self._iter_views():
|
||||
view.set_active_toggle_enabled(False)
|
||||
|
||||
def _on_controller_reset_start(self):
|
||||
"""Controller reset started."""
|
||||
|
||||
for idx in range(self._product_views_layout.count()):
|
||||
widget = self._product_views_layout.widget(idx)
|
||||
widget.set_active_toggle_enabled(True)
|
||||
for view in self._iter_views():
|
||||
view.set_active_toggle_enabled(True)
|
||||
|
||||
def _on_publish_reset(self):
|
||||
"""Context in controller has been reseted."""
|
||||
|
|
@ -477,7 +528,19 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
self._refresh_instances()
|
||||
|
||||
def _on_instances_added(self):
|
||||
view = self._get_current_view()
|
||||
is_card_view = False
|
||||
count = 0
|
||||
if isinstance(view, InstanceCardView):
|
||||
is_card_view = True
|
||||
count = view.get_current_instance_count()
|
||||
|
||||
self._refresh_instances()
|
||||
|
||||
if is_card_view and count < 10:
|
||||
new_count = view.get_current_instance_count()
|
||||
if new_count > count and new_count >= 10:
|
||||
self._change_view_type()
|
||||
|
||||
def _on_instances_removed(self):
|
||||
self._refresh_instances()
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from ayon_core.tools.flickcharm import FlickCharm
|
|||
from ayon_core.tools.utils import (
|
||||
IconButton,
|
||||
PixmapLabel,
|
||||
get_qt_icon,
|
||||
)
|
||||
from ayon_core.tools.publisher.constants import ResetKeySequence
|
||||
|
||||
|
|
@ -287,12 +288,32 @@ class RemoveInstanceBtn(PublishIconBtn):
|
|||
self.setToolTip("Remove selected instances")
|
||||
|
||||
|
||||
class ChangeViewBtn(PublishIconBtn):
|
||||
"""Create toggle view button."""
|
||||
class ChangeViewBtn(IconButton):
|
||||
"""Toggle views button."""
|
||||
def __init__(self, parent=None):
|
||||
icon_path = get_icon_path("change_view")
|
||||
super().__init__(icon_path, parent)
|
||||
self.setToolTip("Swap between views")
|
||||
super().__init__(parent)
|
||||
self.set_view_type("list")
|
||||
|
||||
def set_view_type(self, view_type):
|
||||
if view_type == "list":
|
||||
# icon_name = "data_table"
|
||||
icon_name = "dehaze"
|
||||
tooltip = "Change to list view"
|
||||
elif view_type == "card":
|
||||
icon_name = "view_agenda"
|
||||
tooltip = "Change to card view"
|
||||
else:
|
||||
icon_name = "segment"
|
||||
tooltip = "Change to parent grouping view"
|
||||
|
||||
# "format_align_right"
|
||||
# "segment"
|
||||
icon = get_qt_icon({
|
||||
"type": "material-symbols",
|
||||
"name": icon_name,
|
||||
})
|
||||
self.setIcon(icon)
|
||||
self.setToolTip(tooltip)
|
||||
|
||||
|
||||
class AbstractInstanceView(QtWidgets.QWidget):
|
||||
|
|
@ -370,6 +391,20 @@ class AbstractInstanceView(QtWidgets.QWidget):
|
|||
"{} Method 'set_active_toggle_enabled' is not implemented."
|
||||
).format(self.__class__.__name__))
|
||||
|
||||
def refresh_instance_states(self, instance_ids=None):
|
||||
"""Refresh instance states.
|
||||
|
||||
Args:
|
||||
instance_ids: Optional[Iterable[str]]: Instance ids to refresh.
|
||||
If not passed then all instances are refreshed.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError(
|
||||
f"{self.__class__.__name__} Method 'refresh_instance_states'"
|
||||
" is not implemented."
|
||||
)
|
||||
|
||||
|
||||
class ClickableLineEdit(QtWidgets.QLineEdit):
|
||||
"""QLineEdit capturing left mouse click.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue