Merge pull request #4433 from ynput/feature/creator_sorting

Publisher: Creators sorting
This commit is contained in:
Jakub Trllo 2023-02-08 18:06:44 +01:00 committed by GitHub
commit ffc3b8581a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 149 additions and 30 deletions

View file

@ -33,6 +33,8 @@ class BatchMovieCreator(TrayPublishCreator):
create_allow_context_change = False create_allow_context_change = False
version_regex = re.compile(r"^(.+)_v([0-9]+)$") version_regex = re.compile(r"^(.+)_v([0-9]+)$")
# Position batch creator after simple creators
order = 110
def __init__(self, project_settings, *args, **kwargs): def __init__(self, project_settings, *args, **kwargs):
super(BatchMovieCreator, self).__init__(project_settings, super(BatchMovieCreator, self).__init__(project_settings,

View file

@ -1432,6 +1432,53 @@ class CreateContext:
"""Access to global publish attributes.""" """Access to global publish attributes."""
return self._publish_attributes return self._publish_attributes
def get_sorted_creators(self, identifiers=None):
"""Sorted creators by 'order' attribute.
Args:
identifiers (Iterable[str]): Filter creators by identifiers. All
creators are returned if 'None' is passed.
Returns:
List[BaseCreator]: Sorted creator plugins by 'order' value.
"""
if identifiers is not None:
identifiers = set(identifiers)
creators = [
creator
for identifier, creator in self.creators.items()
if identifier in identifiers
]
else:
creators = self.creators.values()
return sorted(
creators, key=lambda creator: creator.order
)
@property
def sorted_creators(self):
"""Sorted creators by 'order' attribute.
Returns:
List[BaseCreator]: Sorted creator plugins by 'order' value.
"""
return self.get_sorted_creators()
@property
def sorted_autocreators(self):
"""Sorted auto-creators by 'order' attribute.
Returns:
List[AutoCreator]: Sorted plugins by 'order' value.
"""
return sorted(
self.autocreators.values(), key=lambda creator: creator.order
)
@classmethod @classmethod
def get_host_misssing_methods(cls, host): def get_host_misssing_methods(cls, host):
"""Collect missing methods from host. """Collect missing methods from host.
@ -1793,6 +1840,9 @@ class CreateContext:
) )
]) ])
def _remove_instance(self, instance):
self._instances_by_id.pop(instance.id, None)
def creator_removed_instance(self, instance): def creator_removed_instance(self, instance):
"""When creator removes instance context should be acknowledged. """When creator removes instance context should be acknowledged.
@ -1804,7 +1854,7 @@ class CreateContext:
from scene metadata. from scene metadata.
""" """
self._instances_by_id.pop(instance.id, None) self._remove_instance(instance)
def add_convertor_item(self, convertor_identifier, label): def add_convertor_item(self, convertor_identifier, label):
self.convertor_items_by_id[convertor_identifier] = ConvertorItem( self.convertor_items_by_id[convertor_identifier] = ConvertorItem(
@ -1848,7 +1898,7 @@ class CreateContext:
# Collect instances # Collect instances
error_message = "Collection of instances for creator {} failed. {}" error_message = "Collection of instances for creator {} failed. {}"
failed_info = [] failed_info = []
for creator in self.creators.values(): for creator in self.sorted_creators:
label = creator.label label = creator.label
identifier = creator.identifier identifier = creator.identifier
failed = False failed = False
@ -1920,7 +1970,8 @@ class CreateContext:
error_message = "Failed to run AutoCreator with identifier \"{}\". {}" error_message = "Failed to run AutoCreator with identifier \"{}\". {}"
failed_info = [] failed_info = []
for identifier, creator in self.autocreators.items(): for creator in self.sorted_autocreators:
identifier = creator.identifier
label = creator.label label = creator.label
failed = False failed = False
add_traceback = False add_traceback = False
@ -2025,19 +2076,26 @@ class CreateContext:
"""Save instance specific values.""" """Save instance specific values."""
instances_by_identifier = collections.defaultdict(list) instances_by_identifier = collections.defaultdict(list)
for instance in self._instances_by_id.values(): for instance in self._instances_by_id.values():
instance_changes = instance.changes()
if not instance_changes:
continue
identifier = instance.creator_identifier identifier = instance.creator_identifier
instances_by_identifier[identifier].append(instance) instances_by_identifier[identifier].append(
UpdateData(instance, instance_changes)
)
if not instances_by_identifier:
return
error_message = "Instances update of creator \"{}\" failed. {}" error_message = "Instances update of creator \"{}\" failed. {}"
failed_info = [] failed_info = []
for identifier, creator_instances in instances_by_identifier.items():
update_list = []
for instance in creator_instances:
instance_changes = instance.changes()
if instance_changes:
update_list.append(UpdateData(instance, instance_changes))
creator = self.creators[identifier] for creator in self.get_sorted_creators(
instances_by_identifier.keys()
):
identifier = creator.identifier
update_list = instances_by_identifier[identifier]
if not update_list: if not update_list:
continue continue
@ -2073,9 +2131,13 @@ class CreateContext:
def remove_instances(self, instances): def remove_instances(self, instances):
"""Remove instances from context. """Remove instances from context.
All instances that don't have creator identifier leading to existing
creator are just removed from context.
Args: Args:
instances(list<CreatedInstance>): Instances that should be removed instances(List[CreatedInstance]): Instances that should be removed.
from context. Remove logic is done using creator, which may require to
do other cleanup than just remove instance from context.
""" """
instances_by_identifier = collections.defaultdict(list) instances_by_identifier = collections.defaultdict(list)
@ -2083,10 +2145,21 @@ class CreateContext:
identifier = instance.creator_identifier identifier = instance.creator_identifier
instances_by_identifier[identifier].append(instance) 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)
for identifier in missing_creators:
for instance in instances_by_identifier[identifier]:
self._remove_instance(instance)
error_message = "Instances removement of creator \"{}\" failed. {}" error_message = "Instances removement of creator \"{}\" failed. {}"
failed_info = [] failed_info = []
for identifier, creator_instances in instances_by_identifier.items(): # Remove instances by creator plugin order
creator = self.creators.get(identifier) for creator in self.get_sorted_creators(
instances_by_identifier.keys()
):
identifier = creator.identifier
creator_instances = instances_by_identifier[identifier]
label = creator.label label = creator.label
failed = False failed = False
add_traceback = False add_traceback = False
@ -2129,6 +2202,7 @@ class CreateContext:
family(str): Instance family for which should be attribute family(str): Instance family for which should be attribute
definitions returned. definitions returned.
""" """
if family not in self._attr_plugins_by_family: if family not in self._attr_plugins_by_family:
import pyblish.logic import pyblish.logic
@ -2144,7 +2218,13 @@ class CreateContext:
return self._attr_plugins_by_family[family] return self._attr_plugins_by_family[family]
def _get_publish_plugins_with_attr_for_context(self): def _get_publish_plugins_with_attr_for_context(self):
"""Publish plugins attributes for Context plugins.""" """Publish plugins attributes for Context plugins.
Returns:
List[pyblish.api.Plugin]: Publish plugins that have attribute
definitions for context.
"""
plugins = [] plugins = []
for plugin in self.plugins_with_defs: for plugin in self.plugins_with_defs:
if not plugin.__instanceEnabled__: if not plugin.__instanceEnabled__:
@ -2169,7 +2249,7 @@ class CreateContext:
return self._collection_shared_data return self._collection_shared_data
def run_convertor(self, convertor_identifier): def run_convertor(self, convertor_identifier):
"""Run convertor plugin by it's idenfitifier. """Run convertor plugin by identifier.
Conversion is skipped if convertor is not available. Conversion is skipped if convertor is not available.
@ -2182,7 +2262,7 @@ class CreateContext:
convertor.convert() convertor.convert()
def run_convertors(self, convertor_identifiers): def run_convertors(self, convertor_identifiers):
"""Run convertor plugins by idenfitifiers. """Run convertor plugins by identifiers.
Conversion is skipped if convertor is not available. It is recommended Conversion is skipped if convertor is not available. It is recommended
to trigger reset after conversion to reload instances. to trigger reset after conversion to reload instances.

View file

@ -107,7 +107,11 @@ class SubsetConvertorPlugin(object):
@property @property
def create_context(self): def create_context(self):
"""Quick access to create context.""" """Quick access to create context.
Returns:
CreateContext: Context which initialized the plugin.
"""
return self._create_context return self._create_context
@ -157,6 +161,10 @@ class BaseCreator:
# Cached group label after first call 'get_group_label' # Cached group label after first call 'get_group_label'
_cached_group_label = None _cached_group_label = None
# Order in which will be plugin executed (collect & update instances)
# less == earlier -> Order '90' will be processed before '100'
order = 100
# Variable to store logger # Variable to store logger
_log = None _log = None
@ -489,6 +497,17 @@ class Creator(BaseCreator):
# - similar to instance attribute definitions # - similar to instance attribute definitions
pre_create_attr_defs = [] pre_create_attr_defs = []
@property
def show_order(self):
"""Order in which is creator shown in UI.
Returns:
int: Order in which is creator shown (less == earlier). By default
is using Creator's 'order' or processing.
"""
return self.order
@abstractmethod @abstractmethod
def create(self, subset_name, instance_data, pre_create_data): def create(self, subset_name, instance_data, pre_create_data):
"""Create new instance and store it. """Create new instance and store it.

View file

@ -24,6 +24,7 @@ CREATOR_THUMBNAIL_ENABLED_ROLE = QtCore.Qt.UserRole + 5
FAMILY_ROLE = QtCore.Qt.UserRole + 6 FAMILY_ROLE = QtCore.Qt.UserRole + 6
GROUP_ROLE = QtCore.Qt.UserRole + 7 GROUP_ROLE = QtCore.Qt.UserRole + 7
CONVERTER_IDENTIFIER_ROLE = QtCore.Qt.UserRole + 8 CONVERTER_IDENTIFIER_ROLE = QtCore.Qt.UserRole + 8
CREATOR_SORT_ROLE = QtCore.Qt.UserRole + 9
__all__ = ( __all__ = (
@ -36,6 +37,7 @@ __all__ = (
"IS_GROUP_ROLE", "IS_GROUP_ROLE",
"CREATOR_IDENTIFIER_ROLE", "CREATOR_IDENTIFIER_ROLE",
"CREATOR_THUMBNAIL_ENABLED_ROLE", "CREATOR_THUMBNAIL_ENABLED_ROLE",
"CREATOR_SORT_ROLE",
"FAMILY_ROLE", "FAMILY_ROLE",
"GROUP_ROLE", "GROUP_ROLE",
"CONVERTER_IDENTIFIER_ROLE", "CONVERTER_IDENTIFIER_ROLE",

View file

@ -832,7 +832,8 @@ class CreatorItem:
default_variants, default_variants,
create_allow_context_change, create_allow_context_change,
create_allow_thumbnail, create_allow_thumbnail,
pre_create_attributes_defs show_order,
pre_create_attributes_defs,
): ):
self.identifier = identifier self.identifier = identifier
self.creator_type = creator_type self.creator_type = creator_type
@ -846,6 +847,7 @@ class CreatorItem:
self.default_variants = default_variants self.default_variants = default_variants
self.create_allow_context_change = create_allow_context_change self.create_allow_context_change = create_allow_context_change
self.create_allow_thumbnail = create_allow_thumbnail self.create_allow_thumbnail = create_allow_thumbnail
self.show_order = show_order
self.pre_create_attributes_defs = pre_create_attributes_defs self.pre_create_attributes_defs = pre_create_attributes_defs
def get_group_label(self): def get_group_label(self):
@ -869,6 +871,7 @@ class CreatorItem:
pre_create_attr_defs = None pre_create_attr_defs = None
create_allow_context_change = None create_allow_context_change = None
create_allow_thumbnail = None create_allow_thumbnail = None
show_order = creator.order
if creator_type is CreatorTypes.artist: if creator_type is CreatorTypes.artist:
description = creator.get_description() description = creator.get_description()
detail_description = creator.get_detail_description() detail_description = creator.get_detail_description()
@ -877,6 +880,7 @@ class CreatorItem:
pre_create_attr_defs = creator.get_pre_create_attr_defs() pre_create_attr_defs = creator.get_pre_create_attr_defs()
create_allow_context_change = creator.create_allow_context_change create_allow_context_change = creator.create_allow_context_change
create_allow_thumbnail = creator.create_allow_thumbnail create_allow_thumbnail = creator.create_allow_thumbnail
show_order = creator.show_order
identifier = creator.identifier identifier = creator.identifier
return cls( return cls(
@ -892,7 +896,8 @@ class CreatorItem:
default_variants, default_variants,
create_allow_context_change, create_allow_context_change,
create_allow_thumbnail, create_allow_thumbnail,
pre_create_attr_defs show_order,
pre_create_attr_defs,
) )
def to_data(self): def to_data(self):
@ -915,6 +920,7 @@ class CreatorItem:
"default_variants": self.default_variants, "default_variants": self.default_variants,
"create_allow_context_change": self.create_allow_context_change, "create_allow_context_change": self.create_allow_context_change,
"create_allow_thumbnail": self.create_allow_thumbnail, "create_allow_thumbnail": self.create_allow_thumbnail,
"show_order": self.show_order,
"pre_create_attributes_defs": pre_create_attributes_defs, "pre_create_attributes_defs": pre_create_attributes_defs,
} }
@ -1502,9 +1508,6 @@ class BasePublisherController(AbstractPublisherController):
def _reset_attributes(self): def _reset_attributes(self):
"""Reset most of attributes that can be reset.""" """Reset most of attributes that can be reset."""
# Reset creator items
self._creator_items = None
self.publish_is_running = False self.publish_is_running = False
self.publish_has_validated = False self.publish_has_validated = False
self.publish_has_crashed = False self.publish_has_crashed = False
@ -1760,6 +1763,8 @@ class PublisherController(BasePublisherController):
self._resetting_plugins = True self._resetting_plugins = True
self._create_context.reset_plugins() self._create_context.reset_plugins()
# Reset creator items
self._creator_items = None
self._resetting_plugins = False self._resetting_plugins = False

View file

@ -18,9 +18,10 @@ from .tasks_widget import CreateWidgetTasksWidget
from .precreate_widget import PreCreateWidget from .precreate_widget import PreCreateWidget
from ..constants import ( from ..constants import (
VARIANT_TOOLTIP, VARIANT_TOOLTIP,
CREATOR_IDENTIFIER_ROLE,
FAMILY_ROLE, FAMILY_ROLE,
CREATOR_IDENTIFIER_ROLE,
CREATOR_THUMBNAIL_ENABLED_ROLE, CREATOR_THUMBNAIL_ENABLED_ROLE,
CREATOR_SORT_ROLE,
) )
SEPARATORS = ("---separator---", "---") SEPARATORS = ("---separator---", "---")
@ -90,12 +91,19 @@ class CreatorShortDescWidget(QtWidgets.QWidget):
self._description_label.setText(description) self._description_label.setText(description)
class CreatorsProxyModel(QtCore.QSortFilterProxyModel):
def lessThan(self, left, right):
l_show_order = left.data(CREATOR_SORT_ROLE)
r_show_order = right.data(CREATOR_SORT_ROLE)
if l_show_order == r_show_order:
return super(CreatorsProxyModel, self).lessThan(left, right)
return l_show_order < r_show_order
class CreateWidget(QtWidgets.QWidget): class CreateWidget(QtWidgets.QWidget):
def __init__(self, controller, parent=None): def __init__(self, controller, parent=None):
super(CreateWidget, self).__init__(parent) super(CreateWidget, self).__init__(parent)
self.setWindowTitle("Create new instance")
self._controller = controller self._controller = controller
self._asset_name = None self._asset_name = None
@ -141,7 +149,7 @@ class CreateWidget(QtWidgets.QWidget):
creators_view = QtWidgets.QListView(creators_view_widget) creators_view = QtWidgets.QListView(creators_view_widget)
creators_model = QtGui.QStandardItemModel() creators_model = QtGui.QStandardItemModel()
creators_sort_model = QtCore.QSortFilterProxyModel() creators_sort_model = CreatorsProxyModel()
creators_sort_model.setSourceModel(creators_model) creators_sort_model.setSourceModel(creators_model)
creators_view.setModel(creators_sort_model) creators_view.setModel(creators_sort_model)
@ -441,7 +449,8 @@ class CreateWidget(QtWidgets.QWidget):
# Add new families # Add new families
new_creators = set() new_creators = set()
for identifier, creator_item in self._controller.creator_items.items(): creator_items_by_identifier = self._controller.creator_items
for identifier, creator_item in creator_items_by_identifier.items():
if creator_item.creator_type != "artist": if creator_item.creator_type != "artist":
continue continue
@ -457,6 +466,7 @@ class CreateWidget(QtWidgets.QWidget):
self._creators_model.appendRow(item) self._creators_model.appendRow(item)
item.setData(creator_item.label, QtCore.Qt.DisplayRole) item.setData(creator_item.label, QtCore.Qt.DisplayRole)
item.setData(creator_item.show_order, CREATOR_SORT_ROLE)
item.setData(identifier, CREATOR_IDENTIFIER_ROLE) item.setData(identifier, CREATOR_IDENTIFIER_ROLE)
item.setData( item.setData(
creator_item.create_allow_thumbnail, creator_item.create_allow_thumbnail,
@ -482,8 +492,9 @@ class CreateWidget(QtWidgets.QWidget):
index = indexes[0] index = indexes[0]
identifier = index.data(CREATOR_IDENTIFIER_ROLE) identifier = index.data(CREATOR_IDENTIFIER_ROLE)
create_item = creator_items_by_identifier.get(identifier)
self._set_creator_by_identifier(identifier) self._set_creator(create_item)
def _on_plugins_refresh(self): def _on_plugins_refresh(self):
# Trigger refresh only if is visible # Trigger refresh only if is visible