From d29cd8edcdd678370b18ae57815942c82d7e6611 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 21 Oct 2022 14:32:16 +0200 Subject: [PATCH 01/32] workflows: adding milestone creator and assigner --- .github/workflows/milestone_assign.yml | 28 ++++++++++++ .github/workflows/milestone_create.yml | 62 ++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 .github/workflows/milestone_assign.yml create mode 100644 .github/workflows/milestone_create.yml diff --git a/.github/workflows/milestone_assign.yml b/.github/workflows/milestone_assign.yml new file mode 100644 index 0000000000..b41886816b --- /dev/null +++ b/.github/workflows/milestone_assign.yml @@ -0,0 +1,28 @@ +name: Milestone - assign to PRs + +on: + pull_request_target: + types: [opened, reopened, edited] + +jobs: + run_if_release: + if: startsWith(github.base_ref, 'release/') + runs-on: ubuntu-latest + steps: + - name: 'Assign Milestone [next-minor]' + if: github.event.pull_request.milestone == null + uses: zoispag/action-assign-milestone@v1 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + milestone: 'next-minor' + + run_if_develop: + if: ${{ github.base_ref == 'develop' }} + runs-on: ubuntu-latest + steps: + - name: 'Assign Milestone [next-patch]' + if: github.event.pull_request.milestone == null + uses: zoispag/action-assign-milestone@v1 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + milestone: 'next-patch' \ No newline at end of file diff --git a/.github/workflows/milestone_create.yml b/.github/workflows/milestone_create.yml new file mode 100644 index 0000000000..b56ca81dc1 --- /dev/null +++ b/.github/workflows/milestone_create.yml @@ -0,0 +1,62 @@ +name: Milestone - create default + +on: + milestone: + types: [closed, edited] + +jobs: + generate-next-patch: + runs-on: ubuntu-latest + steps: + - name: 'Get Milestones' + uses: "WyriHaximus/github-action-get-milestones@master" + id: milestones + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + + - run: printf "name=number::%s" $(printenv MILESTONES | jq --arg MILESTONE $(printenv MILESTONE) '.[] | select(.title == $MILESTONE) | .number') + id: querymilestone + env: + MILESTONES: ${{ steps.milestones.outputs.milestones }} + MILESTONE: "next-patch" + + - name: Read output + run: | + echo "${{ steps.querymilestone.outputs.number }}" + + - name: 'Create `next-patch` milestone' + if: steps.querymilestone.outputs.number == '' + id: createmilestone + uses: "WyriHaximus/github-action-create-milestone@v1" + with: + title: 'next-patch' + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + + generate-next-minor: + runs-on: ubuntu-latest + steps: + - name: 'Get Milestones' + uses: "WyriHaximus/github-action-get-milestones@master" + id: milestones + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + + - run: printf "name=number::%s" $(printenv MILESTONES | jq --arg MILESTONE $(printenv MILESTONE) '.[] | select(.title == $MILESTONE) | .number') + id: querymilestone + env: + MILESTONES: ${{ steps.milestones.outputs.milestones }} + MILESTONE: "next-minor" + + - name: Read output + run: | + echo "${{ steps.querymilestone.outputs.number }}" + + - name: 'Create `next-minor` milestone' + if: steps.querymilestone.outputs.number == '' + id: createmilestone + uses: "WyriHaximus/github-action-create-milestone@v1" + with: + title: 'next-minor' + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file From 260573506b56d83d73ea785b335aa9134d652d96 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 21 Oct 2022 14:56:09 +0200 Subject: [PATCH 02/32] Created simple item representing conversion requirement --- openpype/pipeline/create/context.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 3e09ff287d..918bc66cb0 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -852,6 +852,29 @@ class CreatedInstance: self[key] = new_value +class LegacyInstancesItem(object): + """Item representing convertor for legacy instances. + + Args: + identifier (str): Identifier of convertor. + label (str): Label which will be shown in UI. + """ + + def __init__(self, identifier, label): + self.identifier = identifier + self.label = label + + def to_data(self): + return { + "identifier": self.identifier, + "label": self.label + } + + @classmethod + def from_data(cls, data): + return cls(data["identifier"], data["label"]) + + class CreateContext: """Context of instance creation. From 8e99d9128a622956299e9dfdd5e22f22460e63d5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 21 Oct 2022 14:56:56 +0200 Subject: [PATCH 03/32] implemented basic of convertor --- openpype/pipeline/create/creator_plugins.py | 90 +++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 97ee94c449..62562e4428 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -33,6 +33,96 @@ class CreatorError(Exception): super(CreatorError, self).__init__(message) +@six.add_metaclass(ABCMeta) +class LegacyInstanceConvertor(object): + """Helper for conversion of instances created using legacy creators. + + Conversion from legacy creators would mean to loose legacy instances, + convert them automatically or write a script which must user run. All of + these solutions are workign but will happen without asking or user must + know about them. This plugin can be used to show legacy instances in + Publisher and give user ability to run conversion script. + + Convertor logic should be very simple. Method 'find_instances' is to + look for legacy instances in scene a possibly call + pre-implemented 'add_legacy_item'. + + User will have ability to trigger conversion which is executed by calling + 'convert' which should call 'remove_legacy_item' when is done. + + It does make sense to add only one or none legacy item to create context + for convertor as it's not possible to choose which instace are converted + and which are not. + + Convertor can use 'collection_shared_data' property like creators. Also + can store any information to it's object for conversion purposes. + + Args: + create_context + """ + + def __init__(self, create_context): + self._create_context = create_context + + @abstractproperty + def identifier(self): + """Converted identifier. + + Returns: + str: Converted identifier unique for all converters in host. + """ + + pass + + @abstractmethod + def find_instances(self): + """Look for legacy instances in the scene. + + Should call 'add_legacy_item' if there is at least one item. + """ + + pass + + @abstractmethod + def convert(self): + """Conversion code.""" + + pass + + @property + def create_context(self): + """Quick access to create context.""" + + return self._create_context + + @property + def collection_shared_data(self): + """Access to shared data that can be used during 'find_instances'. + + Retruns: + Dict[str, Any]: Shared data. + + Raises: + UnavailableSharedData: When called out of collection phase. + """ + + return self._create_context.collection_shared_data + + def add_legacy_item(self, label): + """Add item to CreateContext. + + Args: + label (str): Label of item which will show in UI. + """ + + self._create_context.add_legacy_item(self.identifier, label) + + def remove_legacy_item(self): + """Remove legacy item from create context when conversion finished.""" + + self._create_context.remove_legacy_item(self.identifier) + + @six.add_metaclass(ABCMeta) class BaseCreator: """Plugin that create and modify instance data before publishing process. From 971e4a23bd67fb4ec214bed4b39f32e9f0943715 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 21 Oct 2022 15:19:40 +0200 Subject: [PATCH 04/32] split reset of plugins to more methods --- openpype/pipeline/create/context.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 918bc66cb0..565fdbdf89 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1074,6 +1074,11 @@ class CreateContext: Reloads creators from preregistered paths and can load publish plugins if it's enabled on context. """ + + self._reset_publish_plugins(discover_publish_plugins) + self._reset_creator_plugins() + + def _reset_publish_plugins(self, discover_publish_plugins): import pyblish.logic from openpype.pipeline import OpenPypePyblishPluginMixin @@ -1115,6 +1120,7 @@ class CreateContext: self.publish_plugins = plugins_by_targets self.plugins_with_defs = plugins_with_defs + def _reset_creator_plugins(self): # Prepare settings system_settings = get_system_settings() project_settings = get_project_settings(self.project_name) From cff9990c6fc59ee5d142ce14db206951a5620fdf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 21 Oct 2022 15:20:07 +0200 Subject: [PATCH 05/32] added logic to discover convertors and find legacy items --- openpype/pipeline/create/context.py | 51 +++++++++++++++++++++ openpype/pipeline/create/creator_plugins.py | 12 +++++ 2 files changed, 63 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 565fdbdf89..783b599aef 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -22,6 +22,7 @@ from .creator_plugins import ( Creator, AutoCreator, discover_creator_plugins, + discover_legacy_convertor_plugins, ) UpdateData = collections.namedtuple("UpdateData", ["instance", "changes"]) @@ -940,6 +941,9 @@ class CreateContext: # Manual creators self.manual_creators = {} + self.legacy_convertors = {} + self.legacy_items_by_id = {} + self.publish_discover_result = None self.publish_plugins_mismatch_targets = [] self.publish_plugins = [] @@ -1020,6 +1024,7 @@ class CreateContext: with self.bulk_instances_collection(): self.reset_instances() + self.find_legacy_items() self.execute_autocreators() self.reset_finalization() @@ -1077,6 +1082,7 @@ class CreateContext: self._reset_publish_plugins(discover_publish_plugins) self._reset_creator_plugins() + self._reset_legacy_convertor_plugins() def _reset_publish_plugins(self, discover_publish_plugins): import pyblish.logic @@ -1172,6 +1178,29 @@ class CreateContext: self.creators = creators + def _reset_legacy_convertor_plugins(self): + legacy_convertors = {} + for convertor_class in discover_legacy_convertor_plugins(): + if inspect.isabstract(convertor_class): + self.log.info( + "Skipping abstract Creator {}".format(str(convertor_class)) + ) + continue + + convertor_identifier = convertor_class.identifier + if convertor_identifier in legacy_convertors: + self.log.warning(( + "Duplicated Converter identifier. " + "Using first and skipping following" + )) + continue + + legacy_convertors[convertor_identifier] = ( + convertor_identifier(self) + ) + + self.legacy_convertors = legacy_convertors + def reset_context_data(self): """Reload context data using host implementation. @@ -1243,6 +1272,14 @@ class CreateContext: def creator_removed_instance(self, instance): self._instances_by_id.pop(instance.id, None) + def add_legacy_item(self, convertor_identifier, label): + self.legacy_items_by_id[convertor_identifier] = ( + LegacyInstancesItem(convertor_identifier, label) + ) + + def remove_legacy_item(self, convertor_identifier): + self.legacy_items_by_id.pop(convertor_identifier, None) + @contextmanager def bulk_instances_collection(self): """Validate context of instances in bulk. @@ -1278,6 +1315,20 @@ class CreateContext: for creator in self.creators.values(): creator.collect_instances() + def find_legacy_items(self): + self.legacy_items_by_id = {} + + for convertor in self.legacy_convertors.values(): + try: + convertor.find_instances() + except: + self.log.warning( + "Failed to find instances of convertor \"{}\"".format( + convertor.identifier + ), + exc_info=True + ) + def execute_autocreators(self): """Execute discovered AutoCreator plugins. diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 62562e4428..ff9326693e 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -559,6 +559,10 @@ def discover_creator_plugins(): return discover(BaseCreator) +def discover_legacy_convertor_plugins(): + return discover(LegacyInstanceConvertor) + + def discover_legacy_creator_plugins(): from openpype.lib import Logger @@ -616,6 +620,9 @@ def register_creator_plugin(plugin): elif issubclass(plugin, LegacyCreator): register_plugin(LegacyCreator, plugin) + elif issubclass(plugin, LegacyInstanceConvertor): + register_plugin(LegacyInstanceConvertor, plugin) + def deregister_creator_plugin(plugin): if issubclass(plugin, BaseCreator): @@ -624,12 +631,17 @@ def deregister_creator_plugin(plugin): elif issubclass(plugin, LegacyCreator): deregister_plugin(LegacyCreator, plugin) + elif issubclass(plugin, LegacyInstanceConvertor): + deregister_plugin(LegacyInstanceConvertor, plugin) + def register_creator_plugin_path(path): register_plugin_path(BaseCreator, path) register_plugin_path(LegacyCreator, path) + register_plugin_path(LegacyInstanceConvertor, path) def deregister_creator_plugin_path(path): deregister_plugin_path(BaseCreator, path) deregister_plugin_path(LegacyCreator, path) + deregister_plugin_path(LegacyInstanceConvertor, path) From 24ebd76bd90ef5705434b6ff26c34f294ce96dc5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 21 Oct 2022 17:55:56 +0200 Subject: [PATCH 06/32] fix convertor creation --- openpype/pipeline/create/context.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 783b599aef..5f39d7a0d0 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1195,9 +1195,7 @@ class CreateContext: )) continue - legacy_convertors[convertor_identifier] = ( - convertor_identifier(self) - ) + legacy_convertors[convertor_identifier] = convertor_class(self) self.legacy_convertors = legacy_convertors From 3bdaf89a791a88e0a8fed5f3938aad697b7d08d2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 21 Oct 2022 17:56:03 +0200 Subject: [PATCH 07/32] added id to legacy item --- openpype/pipeline/create/context.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 5f39d7a0d0..e0c5e49e40 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -862,18 +862,26 @@ class LegacyInstancesItem(object): """ def __init__(self, identifier, label): + self._id = str(uuid4()) self.identifier = identifier self.label = label + @property + def id(self): + return self._id + def to_data(self): return { + "id": self.id, "identifier": self.identifier, "label": self.label } @classmethod def from_data(cls, data): - return cls(data["identifier"], data["label"]) + obj = cls(data["identifier"], data["label"]) + obj._id = data["id"] + return obj class CreateContext: From e484df219d6e9cf8031a6f1268575cc2060b75d1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 21 Oct 2022 19:28:12 +0200 Subject: [PATCH 08/32] Define constant for context group --- openpype/tools/publisher/constants.py | 3 +++ .../tools/publisher/widgets/card_view_widgets.py | 15 ++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/openpype/tools/publisher/constants.py b/openpype/tools/publisher/constants.py index dc44aade45..866792aa32 100644 --- a/openpype/tools/publisher/constants.py +++ b/openpype/tools/publisher/constants.py @@ -3,6 +3,9 @@ from Qt import QtCore # ID of context item in instance view CONTEXT_ID = "context" CONTEXT_LABEL = "Options" +# Not showed anywhere - used as identifier +CONTEXT_GROUP = "__ContextGroup__" + # Allowed symbols for subset name (and variant) # - characters, numbers, unsercore and dash diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 5daf8059b0..55e2249496 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -37,7 +37,8 @@ from .widgets import ( ) from ..constants import ( CONTEXT_ID, - CONTEXT_LABEL + CONTEXT_LABEL, + CONTEXT_GROUP, ) @@ -284,7 +285,7 @@ class ContextCardWidget(CardWidget): super(ContextCardWidget, self).__init__(parent) self._id = CONTEXT_ID - self._group_identifier = "" + self._group_identifier = CONTEXT_GROUP icon_widget = PublishPixmapLabel(None, self) icon_widget.setObjectName("FamilyIconLabel") @@ -595,7 +596,7 @@ class InstanceCardView(AbstractInstanceView): instances_by_group[group_name] ) - ordered_group_names = [""] + ordered_group_names = [CONTEXT_GROUP] for idx in range(self._content_layout.count()): if idx > 0: item = self._content_layout.itemAt(idx) @@ -749,7 +750,7 @@ class InstanceCardView(AbstractInstanceView): # If start group is not set then use context item group name if start_group is None: - start_group = "" + start_group = CONTEXT_GROUP # If start instance id is not filled then use context id (similar to # group) @@ -777,7 +778,7 @@ class InstanceCardView(AbstractInstanceView): # Go through ordered groups (from top to bottom) and change selection for name in self._ordered_groups: # Prepare sorted instance widgets - if name == "": + if name == CONTEXT_GROUP: sorted_widgets = [self._context_widget] else: group_widget = self._widgets_by_group[name] @@ -916,13 +917,13 @@ class InstanceCardView(AbstractInstanceView): selected_groups = [] selected_instances = [] if context_selected: - selected_groups.append("") + selected_groups.append(CONTEXT_GROUP) selected_instances.append(CONTEXT_ID) self._context_widget.set_selected(context_selected) for group_name in self._ordered_groups: - if group_name == "": + if group_name == CONTEXT_GROUP: continue group_widget = self._widgets_by_group[group_name] From 3a6bc00a5344c1e0a2124e5a62bda8bfa4d96a2d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 21 Oct 2022 19:30:42 +0200 Subject: [PATCH 09/32] controller has access to convertor items --- openpype/tools/publisher/control.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index d2d01e7921..9abc53675d 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1234,6 +1234,14 @@ class AbstractPublisherController(object): pass + @abstractproperty + def legacy_items(self): + pass + + @abstractmethod + def convert_legacy_items(self, convertor_identifiers): + pass + @abstractmethod def set_comment(self, comment): """Set comment on pyblish context. @@ -1598,6 +1606,10 @@ class PublisherController(BasePublisherController): """Current instances in create context.""" return self._create_context.instances_by_id + @property + def legacy_items(self): + return self._create_context.legacy_items_by_id + @property def _creators(self): """All creators loaded in create context.""" @@ -1716,6 +1728,7 @@ class PublisherController(BasePublisherController): self._create_context.reset_context_data() with self._create_context.bulk_instances_collection(): self._create_context.reset_instances() + self._create_context.find_legacy_items() self._create_context.execute_autocreators() self._resetting_instances = False @@ -1841,6 +1854,12 @@ class PublisherController(BasePublisherController): variant, task_name, asset_doc, project_name, instance=instance ) + def convert_legacy_items(self, convertor_identifiers): + for convertor_identifier in convertor_identifiers: + self._create_context.run_convertor(convertor_identifier) + self._on_create_instance_change() + self.emit_card_message("Conversion finished") + def create( self, creator_identifier, subset_name, instance_data, options ): From b8e5e5e75f7ce5c85c702c757a65b2f6d9ed5e56 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 21 Oct 2022 19:31:31 +0200 Subject: [PATCH 10/32] create context has function to run convertor --- openpype/pipeline/create/context.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index e0c5e49e40..250193f511 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1500,3 +1500,8 @@ class CreateContext: "Accessed Collection shared data out of collection phase" ) return self._collection_shared_data + + def run_convertor(self, convertor_identifier): + convertor = self.legacy_convertors.get(convertor_identifier) + if convertor is not None: + convertor.convert() From e19268c4a1606cff38ab018556bc63a261624578 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 21 Oct 2022 19:31:57 +0200 Subject: [PATCH 11/32] implemented basic implementation of converter --- openpype/style/data.json | 5 +- openpype/style/style.css | 12 + openpype/tools/publisher/constants.py | 3 + .../publisher/widgets/card_view_widgets.py | 292 ++++++++++++---- .../publisher/widgets/list_view_widgets.py | 312 +++++++++++++----- .../publisher/widgets/overview_widget.py | 23 +- openpype/tools/publisher/widgets/widgets.py | 60 +++- 7 files changed, 538 insertions(+), 169 deletions(-) diff --git a/openpype/style/data.json b/openpype/style/data.json index fef69071ed..44c0d51999 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -100,7 +100,10 @@ "bg-expander": "#2C313A", "bg-expander-hover": "#2d6c9f", "bg-expander-selected-hover": "#3784c5" - } + }, + "bg-legacy": "rgb(17, 17, 17)", + "bg-legacy-hover": "rgb(41, 41, 41)", + "bg-legacy-selected": "rgba(42, 123, 174, .4)" }, "settings": { "invalid-light": "#C93636", diff --git a/openpype/style/style.css b/openpype/style/style.css index a6818a5792..983f2c886f 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -965,6 +965,18 @@ VariantInputsWidget QToolButton { background: {color:bg-view-selection}; } +#CardViewLegacyItemWidget { + background: {color:publisher:bg-legacy}; + border-radius: 0.2em; + +} +#CardViewLegacyItemWidget:hover { + background: {color:publisher:bg-legacy-hover}; +} +#CardViewLegacyItemWidget[state="selected"] { + background: {color:publisher:bg-legacy-selected}; +} + #ListViewSubsetName[state="invalid"] { color: {color:publisher:error}; } diff --git a/openpype/tools/publisher/constants.py b/openpype/tools/publisher/constants.py index 866792aa32..3c192bf8a3 100644 --- a/openpype/tools/publisher/constants.py +++ b/openpype/tools/publisher/constants.py @@ -6,6 +6,7 @@ CONTEXT_LABEL = "Options" # Not showed anywhere - used as identifier CONTEXT_GROUP = "__ContextGroup__" +LEGACY_ITEM_GROUP = "Legacy instances" # Allowed symbols for subset name (and variant) # - characters, numbers, unsercore and dash @@ -20,6 +21,8 @@ SORT_VALUE_ROLE = QtCore.Qt.UserRole + 2 IS_GROUP_ROLE = QtCore.Qt.UserRole + 3 CREATOR_IDENTIFIER_ROLE = QtCore.Qt.UserRole + 4 FAMILY_ROLE = QtCore.Qt.UserRole + 5 +GROUP_ROLE = QtCore.Qt.UserRole + 6 +LEGACY_CONVERTER_IDENTIFIER = QtCore.Qt.UserRole + 7 __all__ = ( diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 55e2249496..58a7bbc509 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -39,6 +39,7 @@ from ..constants import ( CONTEXT_ID, CONTEXT_LABEL, CONTEXT_GROUP, + LEGACY_ITEM_GROUP, ) @@ -58,15 +59,12 @@ class SelectionTypes: extend_to = SelectionType("extend_to") -class GroupWidget(QtWidgets.QWidget): - """Widget wrapping instances under group.""" - +class BaseGroupWidget(QtWidgets.QWidget): selected = QtCore.Signal(str, str, SelectionType) - active_changed = QtCore.Signal() removed_selected = QtCore.Signal() - def __init__(self, group_name, group_icons, parent): - super(GroupWidget, self).__init__(parent) + def __init__(self, group_name, parent): + super(BaseGroupWidget, self).__init__(parent) label_widget = QtWidgets.QLabel(group_name, self) @@ -87,10 +85,9 @@ class GroupWidget(QtWidgets.QWidget): layout.addLayout(label_layout, 0) self._group = group_name - self._group_icons = group_icons self._widgets_by_id = {} - self._ordered_instance_ids = [] + self._ordered_item_ids = [] self._label_widget = label_widget self._content_layout = layout @@ -105,7 +102,12 @@ class GroupWidget(QtWidgets.QWidget): return self._group - def get_selected_instance_ids(self): + def get_widget_by_item_id(self, item_id): + """Get instance widget by it's id.""" + + return self._widgets_by_id.get(item_id) + + def get_selected_item_ids(self): """Selected instance ids. Returns: @@ -140,13 +142,80 @@ class GroupWidget(QtWidgets.QWidget): return [ self._widgets_by_id[instance_id] - for instance_id in self._ordered_instance_ids + for instance_id in self._ordered_item_ids ] - def get_widget_by_instance_id(self, instance_id): - """Get instance widget by it's id.""" + def _remove_all_except(self, item_ids): + item_ids = set(item_ids) + # Remove instance widgets that are not in passed instances + for item_id in tuple(self._widgets_by_id.keys()): + if item_id in item_ids: + continue - return self._widgets_by_id.get(instance_id) + widget = self._widgets_by_id.pop(item_id) + if widget.is_selected: + self.removed_selected.emit() + + widget.setVisible(False) + self._content_layout.removeWidget(widget) + widget.deleteLater() + + def _update_ordered_item_ids(self): + ordered_item_ids = [] + for idx in range(self._content_layout.count()): + if idx > 0: + item = self._content_layout.itemAt(idx) + widget = item.widget() + if widget is not None: + ordered_item_ids.append(widget.id) + + self._ordered_item_ids = ordered_item_ids + + def _on_widget_selection(self, instance_id, group_id, selection_type): + self.selected.emit(instance_id, group_id, selection_type) + + +class LegacyItemsGroupWidget(BaseGroupWidget): + def update_items(self, items_by_id): + items_by_label = collections.defaultdict(list) + for item_id, item in items_by_id.items(): + items_by_label[item.label].append(item) + + # Remove instance widgets that are not in passed instances + self._remove_all_except(items_by_id.keys()) + + # Sort instances by subset name + sorted_labels = list(sorted(items_by_label.keys())) + + # Add new instances to widget + widget_idx = 1 + for label in sorted_labels: + for item in items_by_label[label]: + if item.id in self._widgets_by_id: + widget = self._widgets_by_id[item.id] + widget.update_item(item) + else: + widget = LegacyItemCardWidget(item, self) + widget.selected.connect(self._on_widget_selection) + self._widgets_by_id[item.id] = widget + self._content_layout.insertWidget(widget_idx, widget) + widget_idx += 1 + + self._update_ordered_item_ids() + + +class InstanceGroupWidget(BaseGroupWidget): + """Widget wrapping instances under group.""" + + active_changed = QtCore.Signal() + + def __init__(self, group_icons, *args, **kwargs): + super(InstanceGroupWidget, self).__init__(*args, **kwargs) + + self._group_icons = group_icons + + def update_icons(self, group_icons): + self._group_icons = group_icons def update_instance_values(self): """Trigger update on instance widgets.""" @@ -154,14 +223,6 @@ class GroupWidget(QtWidgets.QWidget): for widget in self._widgets_by_id.values(): widget.update_instance_values() - def confirm_remove_instance_id(self, instance_id): - """Delete widget by instance id.""" - - widget = self._widgets_by_id.pop(instance_id) - widget.setVisible(False) - self._content_layout.removeWidget(widget) - widget.deleteLater() - def update_instances(self, instances): """Update instances for the group. @@ -179,17 +240,7 @@ class GroupWidget(QtWidgets.QWidget): instances_by_subset_name[subset_name].append(instance) # Remove instance widgets that are not in passed instances - for instance_id in tuple(self._widgets_by_id.keys()): - if instance_id in instances_by_id: - continue - - widget = self._widgets_by_id.pop(instance_id) - if widget.is_selected: - self.removed_selected.emit() - - widget.setVisible(False) - self._content_layout.removeWidget(widget) - widget.deleteLater() + self._remove_all_except(instances_by_id.keys()) # Sort instances by subset name sorted_subset_names = list(sorted(instances_by_subset_name.keys())) @@ -212,18 +263,7 @@ class GroupWidget(QtWidgets.QWidget): self._content_layout.insertWidget(widget_idx, widget) widget_idx += 1 - ordered_instance_ids = [] - for idx in range(self._content_layout.count()): - if idx > 0: - item = self._content_layout.itemAt(idx) - widget = item.widget() - if widget is not None: - ordered_instance_ids.append(widget.id) - - self._ordered_instance_ids = ordered_instance_ids - - def _on_widget_selection(self, instance_id, group_id, selection_type): - self.selected.emit(instance_id, group_id, selection_type) + self._update_ordered_item_ids() class CardWidget(BaseClickableFrame): @@ -305,6 +345,41 @@ class ContextCardWidget(CardWidget): self._label_widget = label_widget +class LegacyItemCardWidget(CardWidget): + """Card for global context. + + Is not visually under group widget and is always at the top of card view. + """ + + def __init__(self, item, parent): + super(LegacyItemCardWidget, self).__init__(parent) + self.setObjectName("CardViewLegacyItemWidget") + + self._id = item.id + self.identifier = item.identifier + self._group_identifier = LEGACY_ITEM_GROUP + + icon_widget = PublishPixmapLabel(None, self) + icon_widget.setObjectName("FamilyIconLabel") + + label_widget = QtWidgets.QLabel(item.label, self) + + icon_layout = QtWidgets.QHBoxLayout() + icon_layout.setContentsMargins(5, 5, 5, 5) + icon_layout.addWidget(icon_widget) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 5, 10, 5) + layout.addLayout(icon_layout, 0) + layout.addWidget(label_widget, 1) + + self._icon_widget = icon_widget + self._label_widget = label_widget + + def update_instance_values(self): + pass + + class InstanceCardWidget(CardWidget): """Card widget representing instance.""" @@ -482,6 +557,7 @@ class InstanceCardView(AbstractInstanceView): self._content_widget = content_widget self._context_widget = None + self._legacy_items_group = None self._widgets_by_group = {} self._ordered_groups = [] @@ -514,6 +590,9 @@ class InstanceCardView(AbstractInstanceView): ): output.append(self._context_widget) + if self._legacy_items_group is not None: + output.extend(self._legacy_items_group.get_selected_widgets()) + for group_widget in self._widgets_by_group.values(): for widget in group_widget.get_selected_widgets(): output.append(widget) @@ -527,23 +606,19 @@ class InstanceCardView(AbstractInstanceView): ): output.append(CONTEXT_ID) + if self._legacy_items_group is not None: + output.extend(self._legacy_items_group.get_selected_item_ids()) + for group_widget in self._widgets_by_group.values(): - output.extend(group_widget.get_selected_instance_ids()) + output.extend(group_widget.get_selected_item_ids()) return output def refresh(self): """Refresh instances in view based on CreatedContext.""" - # Create context item if is not already existing - # - this must be as first thing to do as context item should be at the - # top - if self._context_widget is None: - widget = ContextCardWidget(self._content_widget) - widget.selected.connect(self._on_widget_selection) - self._context_widget = widget + self._make_sure_context_widget_exists() - self.selection_changed.emit() - self._content_layout.insertWidget(0, widget) + self._update_legacy_items_group() # Prepare instances by group and identifiers by group instances_by_group = collections.defaultdict(list) @@ -574,17 +649,21 @@ class InstanceCardView(AbstractInstanceView): # Keep track of widget indexes # - we start with 1 because Context item as at the top widget_idx = 1 + if self._legacy_items_group is not None: + widget_idx += 1 + for group_name in sorted_group_names: + group_icons = { + idenfier: self._controller.get_creator_icon(idenfier) + for idenfier in identifiers_by_group[group_name] + } if group_name in self._widgets_by_group: group_widget = self._widgets_by_group[group_name] - else: - group_icons = { - idenfier: self._controller.get_creator_icon(idenfier) - for idenfier in identifiers_by_group[group_name] - } + group_widget.update_icons(group_icons) - group_widget = GroupWidget( - group_name, group_icons, self._content_widget + else: + group_widget = InstanceGroupWidget( + group_icons, group_name, self._content_widget ) group_widget.active_changed.connect(self._on_active_changed) group_widget.selected.connect(self._on_widget_selection) @@ -596,6 +675,9 @@ class InstanceCardView(AbstractInstanceView): instances_by_group[group_name] ) + self._update_ordered_group_nameS() + + def _update_ordered_group_nameS(self): ordered_group_names = [CONTEXT_GROUP] for idx in range(self._content_layout.count()): if idx > 0: @@ -606,6 +688,43 @@ class InstanceCardView(AbstractInstanceView): self._ordered_groups = ordered_group_names + def _make_sure_context_widget_exists(self): + # Create context item if is not already existing + # - this must be as first thing to do as context item should be at the + # top + if self._context_widget is not None: + return + + widget = ContextCardWidget(self._content_widget) + widget.selected.connect(self._on_widget_selection) + + self._context_widget = widget + + self.selection_changed.emit() + self._content_layout.insertWidget(0, widget) + + def _update_legacy_items_group(self): + legacy_items = self._controller.legacy_items + if not legacy_items and self._legacy_items_group is None: + return + + if not legacy_items: + self._legacy_items_group.setVisible(False) + self._content_layout.removeWidget(self._legacy_items_group) + self._legacy_items_group.deleteLater() + self._legacy_items_group = None + return + + if self._legacy_items_group is None: + group_widget = LegacyItemsGroupWidget( + LEGACY_ITEM_GROUP, self._content_widget + ) + group_widget.selected.connect(self._on_widget_selection) + self._content_layout.insertWidget(1, group_widget) + self._legacy_items_group = group_widget + + self._legacy_items_group.update_items(legacy_items) + def refresh_instance_states(self): """Trigger update of instances on group widgets.""" for widget in self._widgets_by_group.values(): @@ -622,9 +741,13 @@ class InstanceCardView(AbstractInstanceView): """ if instance_id == CONTEXT_ID: new_widget = self._context_widget + else: - group_widget = self._widgets_by_group[group_name] - new_widget = group_widget.get_widget_by_instance_id(instance_id) + if group_name == LEGACY_ITEM_GROUP: + group_widget = self._legacy_items_group + else: + group_widget = self._widgets_by_group[group_name] + new_widget = group_widget.get_widget_by_item_id(instance_id) if selection_type is SelectionTypes.clear: self._select_item_clear(instance_id, group_name, new_widget) @@ -669,7 +792,10 @@ class InstanceCardView(AbstractInstanceView): if instance_id == CONTEXT_ID: remove_group = True else: - group_widget = self._widgets_by_group[group_name] + if group_name == LEGACY_ITEM_GROUP: + group_widget = self._legacy_items_group + else: + group_widget = self._widgets_by_group[group_name] if not group_widget.get_selected_widgets(): remove_group = True @@ -781,7 +907,10 @@ class InstanceCardView(AbstractInstanceView): if name == CONTEXT_GROUP: sorted_widgets = [self._context_widget] else: - group_widget = self._widgets_by_group[name] + if name == LEGACY_ITEM_GROUP: + group_widget = self._legacy_items_group + else: + group_widget = self._widgets_by_group[name] sorted_widgets = group_widget.get_ordered_widgets() # Change selection based on explicit selection if start group @@ -893,6 +1022,8 @@ class InstanceCardView(AbstractInstanceView): def get_selected_items(self): """Get selected instance ids and context.""" + + convertor_identifiers = [] instances = [] selected_widgets = self._get_selected_widgets() @@ -900,17 +1031,27 @@ class InstanceCardView(AbstractInstanceView): for widget in selected_widgets: if widget is self._context_widget: context_selected = True - else: + + elif isinstance(widget, InstanceCardWidget): instances.append(widget.id) - return instances, context_selected + elif isinstance(widget, LegacyItemCardWidget): + convertor_identifiers.append(widget.identifier) - def set_selected_items(self, instance_ids, context_selected): + return instances, context_selected, convertor_identifiers + + def set_selected_items( + self, instance_ids, context_selected, convertor_identifiers + ): s_instance_ids = set(instance_ids) - cur_ids, cur_context = self.get_selected_items() + s_convertor_identifiers = set(convertor_identifiers) + cur_ids, cur_context, cur_convertor_identifiers = ( + self.get_selected_items() + ) if ( set(cur_ids) == s_instance_ids and cur_context == context_selected + and set(cur_convertor_identifiers) == s_convertor_identifiers ): return @@ -926,11 +1067,20 @@ class InstanceCardView(AbstractInstanceView): if group_name == CONTEXT_GROUP: continue - group_widget = self._widgets_by_group[group_name] + legacy_group = group_name == LEGACY_ITEM_GROUP + if legacy_group: + group_widget = self._legacy_items_group + else: + group_widget = self._widgets_by_group[group_name] + group_selected = False for widget in group_widget.get_ordered_widgets(): select = False - if widget.id in s_instance_ids: + if legacy_group: + is_in = widget.identifier in s_convertor_identifiers + else: + is_in = widget.id in s_instance_ids + if is_in: selected_instances.append(widget.id) group_selected = True select = True diff --git a/openpype/tools/publisher/widgets/list_view_widgets.py b/openpype/tools/publisher/widgets/list_view_widgets.py index c329ca0e8c..df07470f1d 100644 --- a/openpype/tools/publisher/widgets/list_view_widgets.py +++ b/openpype/tools/publisher/widgets/list_view_widgets.py @@ -35,7 +35,10 @@ from ..constants import ( SORT_VALUE_ROLE, IS_GROUP_ROLE, CONTEXT_ID, - CONTEXT_LABEL + CONTEXT_LABEL, + GROUP_ROLE, + LEGACY_CONVERTER_IDENTIFIER, + LEGACY_ITEM_GROUP, ) @@ -330,6 +333,9 @@ class InstanceTreeView(QtWidgets.QTreeView): """Ids of selected instances.""" instance_ids = set() for index in self.selectionModel().selectedIndexes(): + if index.data(LEGACY_CONVERTER_IDENTIFIER) is not None: + continue + instance_id = index.data(INSTANCE_ID_ROLE) if instance_id is not None: instance_ids.add(instance_id) @@ -439,26 +445,36 @@ class InstanceListView(AbstractInstanceView): self._group_items = {} self._group_widgets = {} self._widgets_by_id = {} + # Group by instance id for handling of active state self._group_by_instance_id = {} self._context_item = None self._context_widget = None + self._legacy_group_item = None + self._legacy_group_widget = None + self._legacy_widgets_by_id = {} + self._legacy_items_by_id = {} + self._instance_view = instance_view self._instance_delegate = instance_delegate self._instance_model = instance_model self._proxy_model = proxy_model def _on_expand(self, index): - group_name = index.data(SORT_VALUE_ROLE) - group_widget = self._group_widgets.get(group_name) - if group_widget: - group_widget.set_expanded(True) + self._update_widget_expand_state(index, True) def _on_collapse(self, index): - group_name = index.data(SORT_VALUE_ROLE) - group_widget = self._group_widgets.get(group_name) + self._update_widget_expand_state(index, False) + + def _update_widget_expand_state(self, index, expanded): + group_name = index.data(GROUP_ROLE) + if group_name == LEGACY_ITEM_GROUP: + group_widget = self._legacy_group_widget + else: + group_widget = self._group_widgets.get(group_name) + if group_widget: - group_widget.set_expanded(False) + group_widget.set_expanded(expanded) def _on_toggle_request(self, toggle): selected_instance_ids = self._instance_view.get_selected_instance_ids() @@ -517,6 +533,16 @@ class InstanceListView(AbstractInstanceView): def refresh(self): """Refresh instances in the view.""" + # Sort view at the end of refresh + # - is turned off until any change in view happens + sort_at_the_end = False + # Create or use already existing context item + # - context widget does not change so we don't have to update anything + if self._make_sure_context_item_exists(): + sort_at_the_end = True + + self._update_legacy_items_group() + # Prepare instances by their groups instances_by_group_name = collections.defaultdict(list) group_names = set() @@ -525,75 +551,12 @@ class InstanceListView(AbstractInstanceView): group_names.add(group_label) instances_by_group_name[group_label].append(instance) - # Sort view at the end of refresh - # - is turned off until any change in view happens - sort_at_the_end = False - - # Access to root item of main model - root_item = self._instance_model.invisibleRootItem() - - # Create or use already existing context item - # - context widget does not change so we don't have to update anything - context_item = None - if self._context_item is None: - sort_at_the_end = True - context_item = QtGui.QStandardItem() - context_item.setData(0, SORT_VALUE_ROLE) - context_item.setData(CONTEXT_ID, INSTANCE_ID_ROLE) - - root_item.appendRow(context_item) - - index = self._instance_model.index( - context_item.row(), context_item.column() - ) - proxy_index = self._proxy_model.mapFromSource(index) - widget = ListContextWidget(self._instance_view) - self._instance_view.setIndexWidget(proxy_index, widget) - - self._context_widget = widget - self._context_item = context_item - # Create new groups based on prepared `instances_by_group_name` - new_group_items = [] - for group_name in group_names: - if group_name in self._group_items: - continue - - group_item = QtGui.QStandardItem() - group_item.setData(group_name, SORT_VALUE_ROLE) - group_item.setData(True, IS_GROUP_ROLE) - group_item.setFlags(QtCore.Qt.ItemIsEnabled) - self._group_items[group_name] = group_item - new_group_items.append(group_item) - - # Add new group items to root item if there are any - if new_group_items: - # Trigger sort at the end + if self._make_sure_groups_exists(group_names): sort_at_the_end = True - root_item.appendRows(new_group_items) - - # Create widget for each new group item and store it for future usage - for group_item in new_group_items: - index = self._instance_model.index( - group_item.row(), group_item.column() - ) - proxy_index = self._proxy_model.mapFromSource(index) - group_name = group_item.data(SORT_VALUE_ROLE) - widget = InstanceListGroupWidget(group_name, self._instance_view) - widget.expand_changed.connect(self._on_group_expand_request) - widget.toggle_requested.connect(self._on_group_toggle_request) - self._group_widgets[group_name] = widget - self._instance_view.setIndexWidget(proxy_index, widget) # Remove groups that are not available anymore - for group_name in tuple(self._group_items.keys()): - if group_name in group_names: - continue - - group_item = self._group_items.pop(group_name) - root_item.removeRow(group_item.row()) - widget = self._group_widgets.pop(group_name) - widget.deleteLater() + self._remove_groups_except(group_names) # Store which groups should be expanded at the end expand_groups = set() @@ -652,6 +615,7 @@ class InstanceListView(AbstractInstanceView): # Create new item and store it as new item = QtGui.QStandardItem() item.setData(instance["subset"], SORT_VALUE_ROLE) + item.setData(instance["subset"], GROUP_ROLE) item.setData(instance_id, INSTANCE_ID_ROLE) new_items.append(item) new_items_with_instance.append((item, instance)) @@ -717,13 +681,147 @@ class InstanceListView(AbstractInstanceView): self._instance_view.expand(proxy_index) + def _make_sure_context_item_exists(self): + if self._context_item is not None: + return False + + root_item = self._instance_model.invisibleRootItem() + context_item = QtGui.QStandardItem() + context_item.setData(0, SORT_VALUE_ROLE) + context_item.setData(CONTEXT_ID, INSTANCE_ID_ROLE) + + root_item.appendRow(context_item) + + index = self._instance_model.index( + context_item.row(), context_item.column() + ) + proxy_index = self._proxy_model.mapFromSource(index) + widget = ListContextWidget(self._instance_view) + self._instance_view.setIndexWidget(proxy_index, widget) + + self._context_widget = widget + self._context_item = context_item + return True + + def _update_legacy_items_group(self): + created_new_items = False + legacy_items_by_id = self._controller.legacy_items + group_item = self._legacy_group_item + if not legacy_items_by_id and group_item is None: + return created_new_items + + root_item = self._instance_model.invisibleRootItem() + if not legacy_items_by_id: + root_item.removeRow(group_item.row()) + self._legacy_group_widget.deleteLater() + self._legacy_group_widget = None + return created_new_items + + if group_item is None: + created_new_items = True + group_item = QtGui.QStandardItem() + group_item.setData(LEGACY_ITEM_GROUP, GROUP_ROLE) + group_item.setData(1, SORT_VALUE_ROLE) + group_item.setData(True, IS_GROUP_ROLE) + group_item.setFlags(QtCore.Qt.ItemIsEnabled) + + root_item.appendRow(group_item) + + index = self._instance_model.index( + group_item.row(), group_item.column() + ) + proxy_index = self._proxy_model.mapFromSource(index) + widget = InstanceListGroupWidget( + LEGACY_ITEM_GROUP, self._instance_view + ) + widget.toggle_checkbox.setVisible(False) + widget.expand_changed.connect(self._on_legacy_group_expand_request) + self._instance_view.setIndexWidget(proxy_index, widget) + + self._legacy_group_item = group_item + self._legacy_group_widget = widget + + for row in reversed(range(group_item.rowCount())): + child_item = group_item.child(row) + child_identifier = child_item.data(LEGACY_CONVERTER_IDENTIFIER) + if child_identifier not in legacy_items_by_id: + group_item.removeRows(row, 1) + + new_items = [] + for identifier, convertor_item in legacy_items_by_id.items(): + item = self._legacy_items_by_id.get(identifier) + if item is None: + created_new_items = True + item = QtGui.QStandardItem(convertor_item.label) + new_items.append(item) + item.setData(convertor_item.id, INSTANCE_ID_ROLE) + item.setData(convertor_item.label, SORT_VALUE_ROLE) + item.setData(LEGACY_ITEM_GROUP, GROUP_ROLE) + item.setData( + convertor_item.identifier, LEGACY_CONVERTER_IDENTIFIER + ) + + if new_items: + group_item.appendRows(new_items) + + return created_new_items + + def _make_sure_groups_exists(self, group_names): + new_group_items = [] + for group_name in group_names: + if group_name in self._group_items: + continue + + group_item = QtGui.QStandardItem() + group_item.setData(group_name, GROUP_ROLE) + group_item.setData(group_name, SORT_VALUE_ROLE) + group_item.setData(True, IS_GROUP_ROLE) + group_item.setFlags(QtCore.Qt.ItemIsEnabled) + self._group_items[group_name] = group_item + new_group_items.append(group_item) + + # Add new group items to root item if there are any + if not new_group_items: + return False + + # Access to root item of main model + root_item = self._instance_model.invisibleRootItem() + root_item.appendRows(new_group_items) + + # Create widget for each new group item and store it for future usage + for group_item in new_group_items: + index = self._instance_model.index( + group_item.row(), group_item.column() + ) + proxy_index = self._proxy_model.mapFromSource(index) + group_name = group_item.data(GROUP_ROLE) + widget = InstanceListGroupWidget(group_name, self._instance_view) + widget.expand_changed.connect(self._on_group_expand_request) + widget.toggle_requested.connect(self._on_group_toggle_request) + self._group_widgets[group_name] = widget + self._instance_view.setIndexWidget(proxy_index, widget) + + return True + + def _remove_groups_except(self, group_names): + # Remove groups that are not available anymore + root_item = self._instance_model.invisibleRootItem() + for group_name in tuple(self._group_items.keys()): + if group_name in group_names: + continue + + group_item = self._group_items.pop(group_name) + root_item.removeRow(group_item.row()) + widget = self._group_widgets.pop(group_name) + widget.deleteLater() + def refresh_instance_states(self): """Trigger update of all instances.""" for widget in self._widgets_by_id.values(): widget.update_instance_values() def _on_active_changed(self, changed_instance_id, new_value): - selected_instance_ids, _ = self.get_selected_items() + selected_instance_ids, _, _ = self.get_selected_items() selected_ids = set() found = False @@ -774,6 +872,16 @@ class InstanceListView(AbstractInstanceView): proxy_index = self._proxy_model.mapFromSource(group_index) self._instance_view.setExpanded(proxy_index, expanded) + def _on_legacy_group_expand_request(self, _, expanded): + group_item = self._legacy_group_item + if not group_item: + return + group_index = self._instance_model.index( + group_item.row(), group_item.column() + ) + proxy_index = self._proxy_model.mapFromSource(group_index) + self._instance_view.setExpanded(proxy_index, expanded) + def _on_group_toggle_request(self, group_name, state): if state == QtCore.Qt.PartiallyChecked: return @@ -807,10 +915,17 @@ class InstanceListView(AbstractInstanceView): tuple: Selected instance ids and boolean if context is selected. """ + instance_ids = [] + convertor_identifiers = [] context_selected = False for index in self._instance_view.selectionModel().selectedIndexes(): + convertor_identifier = index.data(LEGACY_CONVERTER_IDENTIFIER) + if convertor_identifier is not None: + convertor_identifiers.append(convertor_identifier) + continue + instance_id = index.data(INSTANCE_ID_ROLE) if not context_selected and instance_id == CONTEXT_ID: context_selected = True @@ -818,14 +933,20 @@ class InstanceListView(AbstractInstanceView): elif instance_id is not None: instance_ids.append(instance_id) - return instance_ids, context_selected + return instance_ids, context_selected, convertor_identifiers - def set_selected_items(self, instance_ids, context_selected): + def set_selected_items( + self, instance_ids, context_selected, convertor_identifiers + ): s_instance_ids = set(instance_ids) - cur_ids, cur_context = self.get_selected_items() + s_convertor_identifiers = set(convertor_identifiers) + cur_ids, cur_context, cur_convertor_identifiers = ( + self.get_selected_items() + ) if ( set(cur_ids) == s_instance_ids and cur_context == context_selected + and set(cur_convertor_identifiers) == s_convertor_identifiers ): return @@ -851,20 +972,35 @@ class InstanceListView(AbstractInstanceView): (item.child(row), list(new_parent_items)) ) - instance_id = item.data(INSTANCE_ID_ROLE) - if not instance_id: + convertor_identifier = item.data(LEGACY_CONVERTER_IDENTIFIER) + + select = False + expand_parent = True + if convertor_identifier is not None: + if convertor_identifier in s_convertor_identifiers: + select = True + else: + instance_id = item.data(INSTANCE_ID_ROLE) + if instance_id == CONTEXT_ID: + if context_selected: + select = True + expand_parent = False + + elif instance_id in s_instance_ids: + select = True + + if not select: continue - if instance_id in s_instance_ids: - select_indexes.append(item.index()) - for parent_item in parent_items: - index = parent_item.index() - proxy_index = proxy_model.mapFromSource(index) - if not view.isExpanded(proxy_index): - view.expand(proxy_index) + select_indexes.append(item.index()) + if not expand_parent: + continue - elif context_selected and instance_id == CONTEXT_ID: - select_indexes.append(item.index()) + for parent_item in parent_items: + index = parent_item.index() + proxy_index = proxy_model.mapFromSource(index) + if not view.isExpanded(proxy_index): + view.expand(proxy_index) selection_model = view.selectionModel() if not select_indexes: diff --git a/openpype/tools/publisher/widgets/overview_widget.py b/openpype/tools/publisher/widgets/overview_widget.py index 5bd3017c2a..e208786fc7 100644 --- a/openpype/tools/publisher/widgets/overview_widget.py +++ b/openpype/tools/publisher/widgets/overview_widget.py @@ -124,6 +124,9 @@ class OverviewWidget(QtWidgets.QFrame): subset_attributes_widget.instance_context_changed.connect( self._on_instance_context_change ) + subset_attributes_widget.convert_requested.connect( + self._on_convert_requested + ) # --- Controller callbacks --- controller.event_system.add_callback( @@ -201,7 +204,7 @@ class OverviewWidget(QtWidgets.QFrame): self.create_requested.emit() def _on_delete_clicked(self): - instance_ids, _ = self.get_selected_items() + instance_ids, _, _ = self.get_selected_items() # Ask user if he really wants to remove instances dialog = QtWidgets.QMessageBox(self) @@ -235,7 +238,9 @@ class OverviewWidget(QtWidgets.QFrame): if self._refreshing_instances: return - instance_ids, context_selected = self.get_selected_items() + instance_ids, context_selected, convertor_identifiers = ( + self.get_selected_items() + ) # Disable delete button if nothing is selected self._delete_btn.setEnabled(len(instance_ids) > 0) @@ -246,7 +251,7 @@ class OverviewWidget(QtWidgets.QFrame): for instance_id in instance_ids ] self._subset_attributes_widget.set_current_instances( - instances, context_selected + instances, context_selected, convertor_identifiers ) def _on_active_changed(self): @@ -314,6 +319,10 @@ class OverviewWidget(QtWidgets.QFrame): self.instance_context_changed.emit() + def _on_convert_requested(self): + _, _, convertor_identifiers = self.get_selected_items() + self._controller.convert_legacy_items(convertor_identifiers) + def get_selected_items(self): view = self._subset_views_layout.currentWidget() return view.get_selected_items() @@ -331,8 +340,12 @@ class OverviewWidget(QtWidgets.QFrame): else: new_view.refresh_instance_states() - instance_ids, context_selected = old_view.get_selected_items() - new_view.set_selected_items(instance_ids, context_selected) + instance_ids, context_selected, convertor_identifiers = ( + old_view.get_selected_items() + ) + new_view.set_selected_items( + instance_ids, context_selected, convertor_identifiers + ) self._subset_views_layout.setCurrentIndex(new_idx) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index ddbe1eb6b7..b01fed25a5 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1461,6 +1461,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget): └───────────────────────────────┘ """ instance_context_changed = QtCore.Signal() + convert_requested = QtCore.Signal() def __init__(self, controller, parent): super(SubsetAttributesWidget, self).__init__(parent) @@ -1479,9 +1480,48 @@ class SubsetAttributesWidget(QtWidgets.QWidget): # BOTTOM PART bottom_widget = QtWidgets.QWidget(self) - creator_attrs_widget = CreatorAttrsWidget( - controller, bottom_widget + + # Wrap Creator attributes to widget to be able add convert button + creator_widget = QtWidgets.QWidget(bottom_widget) + + # Convert button widget (with layout to handle stretch) + convert_widget = QtWidgets.QWidget(creator_widget) + convert_label = QtWidgets.QLabel( + ( + "Found instances created with legacy creators." + "\nDo you with to convert them?" + ), + creator_widget ) + convert_label.setWordWrap(True) + convert_label.setAlignment(QtCore.Qt.AlignCenter) + + convert_btn = QtWidgets.QPushButton( + "Convert legacy instances", convert_widget + ) + convert_separator = QtWidgets.QFrame(convert_widget) + convert_separator.setObjectName("Separator") + convert_separator.setMinimumHeight(2) + convert_separator.setMaximumHeight(2) + + convert_layout = QtWidgets.QGridLayout(convert_widget) + convert_layout.setContentsMargins(0, 0, 0, 0) + convert_layout.addWidget(convert_label, 0, 0, 1, 3) + convert_layout.addWidget(convert_btn, 1, 1) + convert_layout.addWidget(convert_separator, 2, 0, 1, 3) + convert_layout.setColumnStretch(0, 1) + convert_layout.setColumnStretch(1, 0) + convert_layout.setColumnStretch(2, 1) + + # Creator attributes widget + creator_attrs_widget = CreatorAttrsWidget( + controller, creator_widget + ) + creator_layout = QtWidgets.QVBoxLayout(creator_widget) + creator_layout.setContentsMargins(0, 0, 0, 0) + creator_layout.addWidget(convert_widget, 0) + creator_layout.addWidget(creator_attrs_widget, 1) + publish_attrs_widget = PublishPluginAttrsWidget( controller, bottom_widget ) @@ -1492,7 +1532,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget): bottom_layout = QtWidgets.QHBoxLayout(bottom_widget) bottom_layout.setContentsMargins(0, 0, 0, 0) - bottom_layout.addWidget(creator_attrs_widget, 1) + bottom_layout.addWidget(creator_widget, 1) bottom_layout.addWidget(bottom_separator, 0) bottom_layout.addWidget(publish_attrs_widget, 1) @@ -1505,6 +1545,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget): layout.addWidget(top_bottom, 0) layout.addWidget(bottom_widget, 1) + self._convertor_identifiers = None self._current_instances = None self._context_selected = False self._all_instances_valid = True @@ -1512,9 +1553,12 @@ class SubsetAttributesWidget(QtWidgets.QWidget): global_attrs_widget.instance_context_changed.connect( self._on_instance_context_changed ) + convert_btn.clicked.connect(self._on_convert_click) self._controller = controller + self._convert_widget = convert_widget + self.global_attrs_widget = global_attrs_widget self.creator_attrs_widget = creator_attrs_widget @@ -1537,7 +1581,12 @@ class SubsetAttributesWidget(QtWidgets.QWidget): self.instance_context_changed.emit() - def set_current_instances(self, instances, context_selected): + def _on_convert_click(self): + self.convert_requested.emit() + + def set_current_instances( + self, instances, context_selected, convertor_identifiers + ): """Change currently selected items. Args: @@ -1551,10 +1600,13 @@ class SubsetAttributesWidget(QtWidgets.QWidget): all_valid = False break + s_convertor_identifiers = set(convertor_identifiers) + self._convertor_identifiers = s_convertor_identifiers self._current_instances = instances self._context_selected = context_selected self._all_instances_valid = all_valid + self._convert_widget.setVisible(len(s_convertor_identifiers) > 0) self.global_attrs_widget.set_current_instances(instances) self.creator_attrs_widget.set_current_instances(instances) self.publish_attrs_widget.set_current_instances( From 45c944816c42d2593b61fc18f78ca321e6b3d120 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 21 Oct 2022 19:45:17 +0200 Subject: [PATCH 12/32] removed unused variable --- openpype/tools/publisher/widgets/card_view_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 58a7bbc509..96802087ee 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -178,7 +178,7 @@ class BaseGroupWidget(QtWidgets.QWidget): class LegacyItemsGroupWidget(BaseGroupWidget): def update_items(self, items_by_id): items_by_label = collections.defaultdict(list) - for item_id, item in items_by_id.items(): + for item in items_by_id.values(): items_by_label[item.label].append(item) # Remove instance widgets that are not in passed instances From 245c5e9afb81f231bcc884f5c503ae6c812421b8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 17:16:45 +0200 Subject: [PATCH 13/32] changed label of legacy group --- openpype/tools/publisher/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/constants.py b/openpype/tools/publisher/constants.py index 3c192bf8a3..e5969160c1 100644 --- a/openpype/tools/publisher/constants.py +++ b/openpype/tools/publisher/constants.py @@ -6,7 +6,7 @@ CONTEXT_LABEL = "Options" # Not showed anywhere - used as identifier CONTEXT_GROUP = "__ContextGroup__" -LEGACY_ITEM_GROUP = "Legacy instances" +LEGACY_ITEM_GROUP = "Incompatible subsets" # Allowed symbols for subset name (and variant) # - characters, numbers, unsercore and dash From 080deda3167c2b40fcd10582b6c4e99498cf2ff1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 17:18:06 +0200 Subject: [PATCH 14/32] fix list view update --- openpype/tools/publisher/widgets/list_view_widgets.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/list_view_widgets.py b/openpype/tools/publisher/widgets/list_view_widgets.py index df07470f1d..53951e3cba 100644 --- a/openpype/tools/publisher/widgets/list_view_widgets.py +++ b/openpype/tools/publisher/widgets/list_view_widgets.py @@ -452,7 +452,6 @@ class InstanceListView(AbstractInstanceView): self._legacy_group_item = None self._legacy_group_widget = None - self._legacy_widgets_by_id = {} self._legacy_items_by_id = {} self._instance_view = instance_view @@ -715,6 +714,7 @@ class InstanceListView(AbstractInstanceView): root_item.removeRow(group_item.row()) self._legacy_group_widget.deleteLater() self._legacy_group_widget = None + self._legacy_items_by_id = {} return created_new_items if group_item is None: @@ -745,6 +745,7 @@ class InstanceListView(AbstractInstanceView): child_item = group_item.child(row) child_identifier = child_item.data(LEGACY_CONVERTER_IDENTIFIER) if child_identifier not in legacy_items_by_id: + self._legacy_items_by_id.pop(child_identifier, None) group_item.removeRows(row, 1) new_items = [] @@ -760,6 +761,7 @@ class InstanceListView(AbstractInstanceView): item.setData( convertor_item.identifier, LEGACY_CONVERTER_IDENTIFIER ) + self._legacy_items_by_id[identifier] = item if new_items: group_item.appendRows(new_items) From 2787351f03aa7eb7c0220a8f60ba85e4b6a91166 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 17:18:23 +0200 Subject: [PATCH 15/32] change labels of the message for user --- openpype/tools/publisher/widgets/widgets.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index b01fed25a5..ec63509dfa 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1486,18 +1486,22 @@ class SubsetAttributesWidget(QtWidgets.QWidget): # Convert button widget (with layout to handle stretch) convert_widget = QtWidgets.QWidget(creator_widget) - convert_label = QtWidgets.QLabel( + convert_label = QtWidgets.QLabel(creator_widget) + # Set the label text with 'setText' to apply html + convert_label.setText( ( - "Found instances created with legacy creators." - "\nDo you with to convert them?" - ), - creator_widget + "Found old publishable subsets" + " incompatible with new publisher." + "

Press the update subsets button" + " to automatically update them" + " to be able to publish again." + ) ) convert_label.setWordWrap(True) convert_label.setAlignment(QtCore.Qt.AlignCenter) convert_btn = QtWidgets.QPushButton( - "Convert legacy instances", convert_widget + "Update subsets", convert_widget ) convert_separator = QtWidgets.QFrame(convert_widget) convert_separator.setObjectName("Separator") From e94cd00ad7adc36adf48f9c05a752e94611778d3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 17:18:31 +0200 Subject: [PATCH 16/32] change separator size --- openpype/tools/publisher/widgets/widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index ec63509dfa..e091e76fab 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1505,8 +1505,8 @@ class SubsetAttributesWidget(QtWidgets.QWidget): ) convert_separator = QtWidgets.QFrame(convert_widget) convert_separator.setObjectName("Separator") - convert_separator.setMinimumHeight(2) - convert_separator.setMaximumHeight(2) + convert_separator.setMinimumHeight(1) + convert_separator.setMaximumHeight(1) convert_layout = QtWidgets.QGridLayout(convert_widget) convert_layout.setContentsMargins(0, 0, 0, 0) From a98085704ff5a2ebaa205b715bf72024bea0e6bd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 17:21:39 +0200 Subject: [PATCH 17/32] added some padding and spacing --- openpype/tools/publisher/widgets/widgets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index e091e76fab..d4c2623790 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1509,7 +1509,8 @@ class SubsetAttributesWidget(QtWidgets.QWidget): convert_separator.setMaximumHeight(1) convert_layout = QtWidgets.QGridLayout(convert_widget) - convert_layout.setContentsMargins(0, 0, 0, 0) + convert_layout.setContentsMargins(5, 0, 5, 0) + convert_layout.setVerticalSpacing(10) convert_layout.addWidget(convert_label, 0, 0, 1, 3) convert_layout.addWidget(convert_btn, 1, 1) convert_layout.addWidget(convert_separator, 2, 0, 1, 3) From 271a0056bcd988a2371124879e86805cc379cbca Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 17:31:38 +0200 Subject: [PATCH 18/32] change the item look --- openpype/style/data.json | 5 +---- openpype/style/style.css | 12 ------------ .../tools/publisher/widgets/card_view_widgets.py | 5 ++--- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/openpype/style/data.json b/openpype/style/data.json index 44c0d51999..fef69071ed 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -100,10 +100,7 @@ "bg-expander": "#2C313A", "bg-expander-hover": "#2d6c9f", "bg-expander-selected-hover": "#3784c5" - }, - "bg-legacy": "rgb(17, 17, 17)", - "bg-legacy-hover": "rgb(41, 41, 41)", - "bg-legacy-selected": "rgba(42, 123, 174, .4)" + } }, "settings": { "invalid-light": "#C93636", diff --git a/openpype/style/style.css b/openpype/style/style.css index 983f2c886f..a6818a5792 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -965,18 +965,6 @@ VariantInputsWidget QToolButton { background: {color:bg-view-selection}; } -#CardViewLegacyItemWidget { - background: {color:publisher:bg-legacy}; - border-radius: 0.2em; - -} -#CardViewLegacyItemWidget:hover { - background: {color:publisher:bg-legacy-hover}; -} -#CardViewLegacyItemWidget[state="selected"] { - background: {color:publisher:bg-legacy-selected}; -} - #ListViewSubsetName[state="invalid"] { color: {color:publisher:error}; } diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 96802087ee..95fa8cd5d2 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -353,19 +353,18 @@ class LegacyItemCardWidget(CardWidget): def __init__(self, item, parent): super(LegacyItemCardWidget, self).__init__(parent) - self.setObjectName("CardViewLegacyItemWidget") self._id = item.id self.identifier = item.identifier self._group_identifier = LEGACY_ITEM_GROUP - icon_widget = PublishPixmapLabel(None, self) + icon_widget = IconValuePixmapLabel("fa.magic", self) icon_widget.setObjectName("FamilyIconLabel") label_widget = QtWidgets.QLabel(item.label, self) icon_layout = QtWidgets.QHBoxLayout() - icon_layout.setContentsMargins(5, 5, 5, 5) + icon_layout.setContentsMargins(10, 5, 5, 5) icon_layout.addWidget(icon_widget) layout = QtWidgets.QHBoxLayout(self) From 7afb2b2e9fea0ca8cc4fd3d48c16069d052c50df Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 17:56:27 +0200 Subject: [PATCH 19/32] change variable to use convertor instead of legacy --- openpype/tools/publisher/constants.py | 4 +- openpype/tools/publisher/control.py | 4 +- .../publisher/widgets/card_view_widgets.py | 74 +++++++++---------- .../publisher/widgets/list_view_widgets.py | 70 +++++++++--------- 4 files changed, 77 insertions(+), 75 deletions(-) diff --git a/openpype/tools/publisher/constants.py b/openpype/tools/publisher/constants.py index e5969160c1..8bea69c812 100644 --- a/openpype/tools/publisher/constants.py +++ b/openpype/tools/publisher/constants.py @@ -6,7 +6,7 @@ CONTEXT_LABEL = "Options" # Not showed anywhere - used as identifier CONTEXT_GROUP = "__ContextGroup__" -LEGACY_ITEM_GROUP = "Incompatible subsets" +CONVERTOR_ITEM_GROUP = "Incompatible subsets" # Allowed symbols for subset name (and variant) # - characters, numbers, unsercore and dash @@ -22,7 +22,7 @@ IS_GROUP_ROLE = QtCore.Qt.UserRole + 3 CREATOR_IDENTIFIER_ROLE = QtCore.Qt.UserRole + 4 FAMILY_ROLE = QtCore.Qt.UserRole + 5 GROUP_ROLE = QtCore.Qt.UserRole + 6 -LEGACY_CONVERTER_IDENTIFIER = QtCore.Qt.UserRole + 7 +CONVERTER_IDENTIFIER_ROLE = QtCore.Qt.UserRole + 7 __all__ = ( diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 9abc53675d..b867bddc9d 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1235,7 +1235,7 @@ class AbstractPublisherController(object): pass @abstractproperty - def legacy_items(self): + def convertor_items(self): pass @abstractmethod @@ -1607,7 +1607,7 @@ class PublisherController(BasePublisherController): return self._create_context.instances_by_id @property - def legacy_items(self): + def convertor_items(self): return self._create_context.legacy_items_by_id @property diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 95fa8cd5d2..9fd2bf0824 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -39,7 +39,7 @@ from ..constants import ( CONTEXT_ID, CONTEXT_LABEL, CONTEXT_GROUP, - LEGACY_ITEM_GROUP, + CONVERTOR_ITEM_GROUP, ) @@ -175,7 +175,7 @@ class BaseGroupWidget(QtWidgets.QWidget): self.selected.emit(instance_id, group_id, selection_type) -class LegacyItemsGroupWidget(BaseGroupWidget): +class ConvertorItemsGroupWidget(BaseGroupWidget): def update_items(self, items_by_id): items_by_label = collections.defaultdict(list) for item in items_by_id.values(): @@ -195,7 +195,7 @@ class LegacyItemsGroupWidget(BaseGroupWidget): widget = self._widgets_by_id[item.id] widget.update_item(item) else: - widget = LegacyItemCardWidget(item, self) + widget = ConvertorItemCardWidget(item, self) widget.selected.connect(self._on_widget_selection) self._widgets_by_id[item.id] = widget self._content_layout.insertWidget(widget_idx, widget) @@ -345,18 +345,18 @@ class ContextCardWidget(CardWidget): self._label_widget = label_widget -class LegacyItemCardWidget(CardWidget): +class ConvertorItemCardWidget(CardWidget): """Card for global context. Is not visually under group widget and is always at the top of card view. """ def __init__(self, item, parent): - super(LegacyItemCardWidget, self).__init__(parent) + super(ConvertorItemCardWidget, self).__init__(parent) self._id = item.id self.identifier = item.identifier - self._group_identifier = LEGACY_ITEM_GROUP + self._group_identifier = CONVERTOR_ITEM_GROUP icon_widget = IconValuePixmapLabel("fa.magic", self) icon_widget.setObjectName("FamilyIconLabel") @@ -556,7 +556,7 @@ class InstanceCardView(AbstractInstanceView): self._content_widget = content_widget self._context_widget = None - self._legacy_items_group = None + self._convertor_items_group = None self._widgets_by_group = {} self._ordered_groups = [] @@ -589,8 +589,8 @@ class InstanceCardView(AbstractInstanceView): ): output.append(self._context_widget) - if self._legacy_items_group is not None: - output.extend(self._legacy_items_group.get_selected_widgets()) + if self._convertor_items_group is not None: + output.extend(self._convertor_items_group.get_selected_widgets()) for group_widget in self._widgets_by_group.values(): for widget in group_widget.get_selected_widgets(): @@ -605,8 +605,8 @@ class InstanceCardView(AbstractInstanceView): ): output.append(CONTEXT_ID) - if self._legacy_items_group is not None: - output.extend(self._legacy_items_group.get_selected_item_ids()) + if self._convertor_items_group is not None: + output.extend(self._convertor_items_group.get_selected_item_ids()) for group_widget in self._widgets_by_group.values(): output.extend(group_widget.get_selected_item_ids()) @@ -617,7 +617,7 @@ class InstanceCardView(AbstractInstanceView): self._make_sure_context_widget_exists() - self._update_legacy_items_group() + self._update_convertor_items_group() # Prepare instances by group and identifiers by group instances_by_group = collections.defaultdict(list) @@ -648,7 +648,7 @@ class InstanceCardView(AbstractInstanceView): # Keep track of widget indexes # - we start with 1 because Context item as at the top widget_idx = 1 - if self._legacy_items_group is not None: + if self._convertor_items_group is not None: widget_idx += 1 for group_name in sorted_group_names: @@ -702,27 +702,27 @@ class InstanceCardView(AbstractInstanceView): self.selection_changed.emit() self._content_layout.insertWidget(0, widget) - def _update_legacy_items_group(self): - legacy_items = self._controller.legacy_items - if not legacy_items and self._legacy_items_group is None: + def _update_convertor_items_group(self): + convertor_items = self._controller.convertor_items + if not convertor_items and self._convertor_items_group is None: return - if not legacy_items: - self._legacy_items_group.setVisible(False) - self._content_layout.removeWidget(self._legacy_items_group) - self._legacy_items_group.deleteLater() - self._legacy_items_group = None + if not convertor_items: + self._convertor_items_group.setVisible(False) + self._content_layout.removeWidget(self._convertor_items_group) + self._convertor_items_group.deleteLater() + self._convertor_items_group = None return - if self._legacy_items_group is None: - group_widget = LegacyItemsGroupWidget( - LEGACY_ITEM_GROUP, self._content_widget + if self._convertor_items_group is None: + group_widget = ConvertorItemsGroupWidget( + CONVERTOR_ITEM_GROUP, self._content_widget ) group_widget.selected.connect(self._on_widget_selection) self._content_layout.insertWidget(1, group_widget) - self._legacy_items_group = group_widget + self._convertor_items_group = group_widget - self._legacy_items_group.update_items(legacy_items) + self._convertor_items_group.update_items(convertor_items) def refresh_instance_states(self): """Trigger update of instances on group widgets.""" @@ -742,8 +742,8 @@ class InstanceCardView(AbstractInstanceView): new_widget = self._context_widget else: - if group_name == LEGACY_ITEM_GROUP: - group_widget = self._legacy_items_group + if group_name == CONVERTOR_ITEM_GROUP: + group_widget = self._convertor_items_group else: group_widget = self._widgets_by_group[group_name] new_widget = group_widget.get_widget_by_item_id(instance_id) @@ -791,8 +791,8 @@ class InstanceCardView(AbstractInstanceView): if instance_id == CONTEXT_ID: remove_group = True else: - if group_name == LEGACY_ITEM_GROUP: - group_widget = self._legacy_items_group + if group_name == CONVERTOR_ITEM_GROUP: + group_widget = self._convertor_items_group else: group_widget = self._widgets_by_group[group_name] if not group_widget.get_selected_widgets(): @@ -906,8 +906,8 @@ class InstanceCardView(AbstractInstanceView): if name == CONTEXT_GROUP: sorted_widgets = [self._context_widget] else: - if name == LEGACY_ITEM_GROUP: - group_widget = self._legacy_items_group + if name == CONVERTOR_ITEM_GROUP: + group_widget = self._convertor_items_group else: group_widget = self._widgets_by_group[name] sorted_widgets = group_widget.get_ordered_widgets() @@ -1034,7 +1034,7 @@ class InstanceCardView(AbstractInstanceView): elif isinstance(widget, InstanceCardWidget): instances.append(widget.id) - elif isinstance(widget, LegacyItemCardWidget): + elif isinstance(widget, ConvertorItemCardWidget): convertor_identifiers.append(widget.identifier) return instances, context_selected, convertor_identifiers @@ -1066,16 +1066,16 @@ class InstanceCardView(AbstractInstanceView): if group_name == CONTEXT_GROUP: continue - legacy_group = group_name == LEGACY_ITEM_GROUP - if legacy_group: - group_widget = self._legacy_items_group + is_convertor_group = group_name == CONVERTOR_ITEM_GROUP + if is_convertor_group: + group_widget = self._convertor_items_group else: group_widget = self._widgets_by_group[group_name] group_selected = False for widget in group_widget.get_ordered_widgets(): select = False - if legacy_group: + if is_convertor_group: is_in = widget.identifier in s_convertor_identifiers else: is_in = widget.id in s_instance_ids diff --git a/openpype/tools/publisher/widgets/list_view_widgets.py b/openpype/tools/publisher/widgets/list_view_widgets.py index 53951e3cba..32d84862f0 100644 --- a/openpype/tools/publisher/widgets/list_view_widgets.py +++ b/openpype/tools/publisher/widgets/list_view_widgets.py @@ -37,8 +37,8 @@ from ..constants import ( CONTEXT_ID, CONTEXT_LABEL, GROUP_ROLE, - LEGACY_CONVERTER_IDENTIFIER, - LEGACY_ITEM_GROUP, + CONVERTER_IDENTIFIER_ROLE, + CONVERTOR_ITEM_GROUP, ) @@ -333,7 +333,7 @@ class InstanceTreeView(QtWidgets.QTreeView): """Ids of selected instances.""" instance_ids = set() for index in self.selectionModel().selectedIndexes(): - if index.data(LEGACY_CONVERTER_IDENTIFIER) is not None: + if index.data(CONVERTER_IDENTIFIER_ROLE) is not None: continue instance_id = index.data(INSTANCE_ID_ROLE) @@ -450,9 +450,9 @@ class InstanceListView(AbstractInstanceView): self._context_item = None self._context_widget = None - self._legacy_group_item = None - self._legacy_group_widget = None - self._legacy_items_by_id = {} + self._convertor_group_item = None + self._convertor_group_widget = None + self._convertor_items_by_id = {} self._instance_view = instance_view self._instance_delegate = instance_delegate @@ -467,8 +467,8 @@ class InstanceListView(AbstractInstanceView): def _update_widget_expand_state(self, index, expanded): group_name = index.data(GROUP_ROLE) - if group_name == LEGACY_ITEM_GROUP: - group_widget = self._legacy_group_widget + if group_name == CONVERTOR_ITEM_GROUP: + group_widget = self._convertor_group_widget else: group_widget = self._group_widgets.get(group_name) @@ -540,7 +540,7 @@ class InstanceListView(AbstractInstanceView): if self._make_sure_context_item_exists(): sort_at_the_end = True - self._update_legacy_items_group() + self._update_convertor_items_group() # Prepare instances by their groups instances_by_group_name = collections.defaultdict(list) @@ -702,25 +702,25 @@ class InstanceListView(AbstractInstanceView): self._context_item = context_item return True - def _update_legacy_items_group(self): + def _update_convertor_items_group(self): created_new_items = False - legacy_items_by_id = self._controller.legacy_items - group_item = self._legacy_group_item - if not legacy_items_by_id and group_item is None: + convertor_items_by_id = self._controller.convertor_items + group_item = self._convertor_group_item + if not convertor_items_by_id and group_item is None: return created_new_items root_item = self._instance_model.invisibleRootItem() - if not legacy_items_by_id: + if not convertor_items_by_id: root_item.removeRow(group_item.row()) - self._legacy_group_widget.deleteLater() - self._legacy_group_widget = None - self._legacy_items_by_id = {} + self._convertor_group_widget.deleteLater() + self._convertor_group_widget = None + self._convertor_items_by_id = {} return created_new_items if group_item is None: created_new_items = True group_item = QtGui.QStandardItem() - group_item.setData(LEGACY_ITEM_GROUP, GROUP_ROLE) + group_item.setData(CONVERTOR_ITEM_GROUP, GROUP_ROLE) group_item.setData(1, SORT_VALUE_ROLE) group_item.setData(True, IS_GROUP_ROLE) group_item.setFlags(QtCore.Qt.ItemIsEnabled) @@ -732,36 +732,38 @@ class InstanceListView(AbstractInstanceView): ) proxy_index = self._proxy_model.mapFromSource(index) widget = InstanceListGroupWidget( - LEGACY_ITEM_GROUP, self._instance_view + CONVERTOR_ITEM_GROUP, self._instance_view ) widget.toggle_checkbox.setVisible(False) - widget.expand_changed.connect(self._on_legacy_group_expand_request) + widget.expand_changed.connect( + self._on_convertor_group_expand_request + ) self._instance_view.setIndexWidget(proxy_index, widget) - self._legacy_group_item = group_item - self._legacy_group_widget = widget + self._convertor_group_item = group_item + self._convertor_group_widget = widget for row in reversed(range(group_item.rowCount())): child_item = group_item.child(row) - child_identifier = child_item.data(LEGACY_CONVERTER_IDENTIFIER) - if child_identifier not in legacy_items_by_id: - self._legacy_items_by_id.pop(child_identifier, None) + child_identifier = child_item.data(CONVERTER_IDENTIFIER_ROLE) + if child_identifier not in convertor_items_by_id: + self._convertor_items_by_id.pop(child_identifier, None) group_item.removeRows(row, 1) new_items = [] - for identifier, convertor_item in legacy_items_by_id.items(): - item = self._legacy_items_by_id.get(identifier) + for identifier, convertor_item in convertor_items_by_id.items(): + item = self._convertor_items_by_id.get(identifier) if item is None: created_new_items = True item = QtGui.QStandardItem(convertor_item.label) new_items.append(item) item.setData(convertor_item.id, INSTANCE_ID_ROLE) item.setData(convertor_item.label, SORT_VALUE_ROLE) - item.setData(LEGACY_ITEM_GROUP, GROUP_ROLE) + item.setData(CONVERTOR_ITEM_GROUP, GROUP_ROLE) item.setData( - convertor_item.identifier, LEGACY_CONVERTER_IDENTIFIER + convertor_item.identifier, CONVERTER_IDENTIFIER_ROLE ) - self._legacy_items_by_id[identifier] = item + self._convertor_items_by_id[identifier] = item if new_items: group_item.appendRows(new_items) @@ -874,8 +876,8 @@ class InstanceListView(AbstractInstanceView): proxy_index = self._proxy_model.mapFromSource(group_index) self._instance_view.setExpanded(proxy_index, expanded) - def _on_legacy_group_expand_request(self, _, expanded): - group_item = self._legacy_group_item + def _on_convertor_group_expand_request(self, _, expanded): + group_item = self._convertor_group_item if not group_item: return group_index = self._instance_model.index( @@ -923,7 +925,7 @@ class InstanceListView(AbstractInstanceView): context_selected = False for index in self._instance_view.selectionModel().selectedIndexes(): - convertor_identifier = index.data(LEGACY_CONVERTER_IDENTIFIER) + convertor_identifier = index.data(CONVERTER_IDENTIFIER_ROLE) if convertor_identifier is not None: convertor_identifiers.append(convertor_identifier) continue @@ -974,7 +976,7 @@ class InstanceListView(AbstractInstanceView): (item.child(row), list(new_parent_items)) ) - convertor_identifier = item.data(LEGACY_CONVERTER_IDENTIFIER) + convertor_identifier = item.data(CONVERTER_IDENTIFIER_ROLE) select = False expand_parent = True From be54ff4d27978079855c99e5f8f9f1d188742b53 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 18:00:26 +0200 Subject: [PATCH 20/32] rename 'convert_legacy_items' to 'trigger_convertor_items' --- openpype/tools/publisher/control.py | 4 ++-- openpype/tools/publisher/widgets/overview_widget.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index b867bddc9d..245d328be4 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1239,7 +1239,7 @@ class AbstractPublisherController(object): pass @abstractmethod - def convert_legacy_items(self, convertor_identifiers): + def trigger_convertor_items(self, convertor_identifiers): pass @abstractmethod @@ -1854,7 +1854,7 @@ class PublisherController(BasePublisherController): variant, task_name, asset_doc, project_name, instance=instance ) - def convert_legacy_items(self, convertor_identifiers): + def trigger_convertor_items(self, convertor_identifiers): for convertor_identifier in convertor_identifiers: self._create_context.run_convertor(convertor_identifier) self._on_create_instance_change() diff --git a/openpype/tools/publisher/widgets/overview_widget.py b/openpype/tools/publisher/widgets/overview_widget.py index e208786fc7..7c1755b3eb 100644 --- a/openpype/tools/publisher/widgets/overview_widget.py +++ b/openpype/tools/publisher/widgets/overview_widget.py @@ -321,7 +321,7 @@ class OverviewWidget(QtWidgets.QFrame): def _on_convert_requested(self): _, _, convertor_identifiers = self.get_selected_items() - self._controller.convert_legacy_items(convertor_identifiers) + self._controller.trigger_convertor_items(convertor_identifiers) def get_selected_items(self): view = self._subset_views_layout.currentWidget() From 81f7aa5525e52f229cf4ec340f8a125358d0afeb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 18:15:24 +0200 Subject: [PATCH 21/32] get rid of 'legacy' from variables --- openpype/pipeline/create/context.py | 44 ++++++++++----------- openpype/pipeline/create/creator_plugins.py | 33 ++++++++-------- openpype/tools/publisher/control.py | 4 +- 3 files changed, 41 insertions(+), 40 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 250193f511..56d7447a0b 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -22,7 +22,7 @@ from .creator_plugins import ( Creator, AutoCreator, discover_creator_plugins, - discover_legacy_convertor_plugins, + discover_convertor_plugins, ) UpdateData = collections.namedtuple("UpdateData", ["instance", "changes"]) @@ -853,8 +853,8 @@ class CreatedInstance: self[key] = new_value -class LegacyInstancesItem(object): - """Item representing convertor for legacy instances. +class ConvertorItem(object): + """Item representing convertor plugin. Args: identifier (str): Identifier of convertor. @@ -949,8 +949,8 @@ class CreateContext: # Manual creators self.manual_creators = {} - self.legacy_convertors = {} - self.legacy_items_by_id = {} + self.convertors_plugins = {} + self.convertor_items_by_id = {} self.publish_discover_result = None self.publish_plugins_mismatch_targets = [] @@ -1032,7 +1032,7 @@ class CreateContext: with self.bulk_instances_collection(): self.reset_instances() - self.find_legacy_items() + self.find_convertor_items() self.execute_autocreators() self.reset_finalization() @@ -1090,7 +1090,7 @@ class CreateContext: self._reset_publish_plugins(discover_publish_plugins) self._reset_creator_plugins() - self._reset_legacy_convertor_plugins() + self._reset_convertor_plugins() def _reset_publish_plugins(self, discover_publish_plugins): import pyblish.logic @@ -1186,9 +1186,9 @@ class CreateContext: self.creators = creators - def _reset_legacy_convertor_plugins(self): - legacy_convertors = {} - for convertor_class in discover_legacy_convertor_plugins(): + def _reset_convertor_plugins(self): + convertors_plugins = {} + for convertor_class in discover_convertor_plugins(): if inspect.isabstract(convertor_class): self.log.info( "Skipping abstract Creator {}".format(str(convertor_class)) @@ -1196,16 +1196,16 @@ class CreateContext: continue convertor_identifier = convertor_class.identifier - if convertor_identifier in legacy_convertors: + if convertor_identifier in convertors_plugins: self.log.warning(( "Duplicated Converter identifier. " "Using first and skipping following" )) continue - legacy_convertors[convertor_identifier] = convertor_class(self) + convertors_plugins[convertor_identifier] = convertor_class(self) - self.legacy_convertors = legacy_convertors + self.convertors_plugins = convertors_plugins def reset_context_data(self): """Reload context data using host implementation. @@ -1278,13 +1278,13 @@ class CreateContext: def creator_removed_instance(self, instance): self._instances_by_id.pop(instance.id, None) - def add_legacy_item(self, convertor_identifier, label): - self.legacy_items_by_id[convertor_identifier] = ( - LegacyInstancesItem(convertor_identifier, label) + def add_convertor_item(self, convertor_identifier, label): + self.convertor_items_by_id[convertor_identifier] = ConvertorItem( + convertor_identifier, label ) - def remove_legacy_item(self, convertor_identifier): - self.legacy_items_by_id.pop(convertor_identifier, None) + def remove_convertor_item(self, convertor_identifier): + self.convertor_items_by_id.pop(convertor_identifier, None) @contextmanager def bulk_instances_collection(self): @@ -1321,10 +1321,10 @@ class CreateContext: for creator in self.creators.values(): creator.collect_instances() - def find_legacy_items(self): - self.legacy_items_by_id = {} + def find_convertor_items(self): + self.convertor_items_by_id = {} - for convertor in self.legacy_convertors.values(): + for convertor in self.convertors_plugins.values(): try: convertor.find_instances() except: @@ -1502,6 +1502,6 @@ class CreateContext: return self._collection_shared_data def run_convertor(self, convertor_identifier): - convertor = self.legacy_convertors.get(convertor_identifier) + convertor = self.convertors_plugins.get(convertor_identifier) if convertor is not None: convertor.convert() diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index ff9326693e..2e7d8709a2 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -34,7 +34,7 @@ class CreatorError(Exception): @six.add_metaclass(ABCMeta) -class LegacyInstanceConvertor(object): +class LegacySubsetConvertor(object): """Helper for conversion of instances created using legacy creators. Conversion from legacy creators would mean to loose legacy instances, @@ -45,10 +45,10 @@ class LegacyInstanceConvertor(object): Convertor logic should be very simple. Method 'find_instances' is to look for legacy instances in scene a possibly call - pre-implemented 'add_legacy_item'. + pre-implemented 'add_convertor_item'. User will have ability to trigger conversion which is executed by calling - 'convert' which should call 'remove_legacy_item' when is done. + 'convert' which should call 'remove_convertor_item' when is done. It does make sense to add only one or none legacy item to create context for convertor as it's not possible to choose which instace are converted @@ -78,7 +78,8 @@ class LegacyInstanceConvertor(object): def find_instances(self): """Look for legacy instances in the scene. - Should call 'add_legacy_item' if there is at least one item. + Should call 'add_convertor_item' if there is at least one instance to + convert. """ pass @@ -108,19 +109,19 @@ class LegacyInstanceConvertor(object): return self._create_context.collection_shared_data - def add_legacy_item(self, label): + def add_convertor_item(self, label): """Add item to CreateContext. Args: label (str): Label of item which will show in UI. """ - self._create_context.add_legacy_item(self.identifier, label) + self._create_context.add_convertor_item(self.identifier, label) - def remove_legacy_item(self): + def remove_convertor_item(self): """Remove legacy item from create context when conversion finished.""" - self._create_context.remove_legacy_item(self.identifier) + self._create_context.remove_convertor_item(self.identifier) @six.add_metaclass(ABCMeta) @@ -559,8 +560,8 @@ def discover_creator_plugins(): return discover(BaseCreator) -def discover_legacy_convertor_plugins(): - return discover(LegacyInstanceConvertor) +def discover_convertor_plugins(): + return discover(LegacySubsetConvertor) def discover_legacy_creator_plugins(): @@ -620,8 +621,8 @@ def register_creator_plugin(plugin): elif issubclass(plugin, LegacyCreator): register_plugin(LegacyCreator, plugin) - elif issubclass(plugin, LegacyInstanceConvertor): - register_plugin(LegacyInstanceConvertor, plugin) + elif issubclass(plugin, LegacySubsetConvertor): + register_plugin(LegacySubsetConvertor, plugin) def deregister_creator_plugin(plugin): @@ -631,17 +632,17 @@ def deregister_creator_plugin(plugin): elif issubclass(plugin, LegacyCreator): deregister_plugin(LegacyCreator, plugin) - elif issubclass(plugin, LegacyInstanceConvertor): - deregister_plugin(LegacyInstanceConvertor, plugin) + elif issubclass(plugin, LegacySubsetConvertor): + deregister_plugin(LegacySubsetConvertor, plugin) def register_creator_plugin_path(path): register_plugin_path(BaseCreator, path) register_plugin_path(LegacyCreator, path) - register_plugin_path(LegacyInstanceConvertor, path) + register_plugin_path(LegacySubsetConvertor, path) def deregister_creator_plugin_path(path): deregister_plugin_path(BaseCreator, path) deregister_plugin_path(LegacyCreator, path) - deregister_plugin_path(LegacyInstanceConvertor, path) + deregister_plugin_path(LegacySubsetConvertor, path) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 245d328be4..107ddbbb93 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1608,7 +1608,7 @@ class PublisherController(BasePublisherController): @property def convertor_items(self): - return self._create_context.legacy_items_by_id + return self._create_context.convertor_items_by_id @property def _creators(self): @@ -1728,7 +1728,7 @@ class PublisherController(BasePublisherController): self._create_context.reset_context_data() with self._create_context.bulk_instances_collection(): self._create_context.reset_instances() - self._create_context.find_legacy_items() + self._create_context.find_convertor_items() self._create_context.execute_autocreators() self._resetting_instances = False From 4f70a58d5c7e9c604c1d6dabbeb80c4b74ab83b3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 18:17:27 +0200 Subject: [PATCH 22/32] renamed 'LegacySubsetConvertor' to 'SubsetConvertorPlugin' --- openpype/pipeline/create/creator_plugins.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 2e7d8709a2..584e082221 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -34,7 +34,7 @@ class CreatorError(Exception): @six.add_metaclass(ABCMeta) -class LegacySubsetConvertor(object): +class SubsetConvertorPlugin(object): """Helper for conversion of instances created using legacy creators. Conversion from legacy creators would mean to loose legacy instances, @@ -561,7 +561,7 @@ def discover_creator_plugins(): def discover_convertor_plugins(): - return discover(LegacySubsetConvertor) + return discover(SubsetConvertorPlugin) def discover_legacy_creator_plugins(): @@ -621,8 +621,8 @@ def register_creator_plugin(plugin): elif issubclass(plugin, LegacyCreator): register_plugin(LegacyCreator, plugin) - elif issubclass(plugin, LegacySubsetConvertor): - register_plugin(LegacySubsetConvertor, plugin) + elif issubclass(plugin, SubsetConvertorPlugin): + register_plugin(SubsetConvertorPlugin, plugin) def deregister_creator_plugin(plugin): @@ -632,17 +632,17 @@ def deregister_creator_plugin(plugin): elif issubclass(plugin, LegacyCreator): deregister_plugin(LegacyCreator, plugin) - elif issubclass(plugin, LegacySubsetConvertor): - deregister_plugin(LegacySubsetConvertor, plugin) + elif issubclass(plugin, SubsetConvertorPlugin): + deregister_plugin(SubsetConvertorPlugin, plugin) def register_creator_plugin_path(path): register_plugin_path(BaseCreator, path) register_plugin_path(LegacyCreator, path) - register_plugin_path(LegacySubsetConvertor, path) + register_plugin_path(SubsetConvertorPlugin, path) def deregister_creator_plugin_path(path): deregister_plugin_path(BaseCreator, path) deregister_plugin_path(LegacyCreator, path) - deregister_plugin_path(LegacySubsetConvertor, path) + deregister_plugin_path(SubsetConvertorPlugin, path) From 87671bcfd6905e7e1bf729c6aa0fef42f47d6d9c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 19:16:54 +0200 Subject: [PATCH 23/32] added style for errored card message --- openpype/style/data.json | 4 +++- openpype/style/style.css | 21 +++++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/style/data.json b/openpype/style/data.json index fef69071ed..146af84663 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -64,7 +64,9 @@ "overlay-messages": { "close-btn": "#D3D8DE", "bg-success": "#458056", - "bg-success-hover": "#55a066" + "bg-success-hover": "#55a066", + "bg-error": "#AD2E2E", + "bg-error-hover": "#C93636" }, "tab-widget": { "bg": "#21252B", diff --git a/openpype/style/style.css b/openpype/style/style.css index a6818a5792..9919973b06 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -688,22 +688,23 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { } /* Messages overlay */ -#OverlayMessageWidget { +OverlayMessageWidget { border-radius: 0.2em; - background: {color:bg-buttons}; -} - -#OverlayMessageWidget:hover { - background: {color:bg-button-hover}; -} -#OverlayMessageWidget { background: {color:overlay-messages:bg-success}; } -#OverlayMessageWidget:hover { + +OverlayMessageWidget:hover { background: {color:overlay-messages:bg-success-hover}; } -#OverlayMessageWidget QWidget { +OverlayMessageWidget[type="error"] { + background: {color:overlay-messages:bg-error}; +} +OverlayMessageWidget[type="error"]:hover { + background: {color:overlay-messages:bg-error-hover}; +} + +OverlayMessageWidget QWidget { background: transparent; } From 0fd54454192ffec16170b1cca574825955f7397f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 19:25:35 +0200 Subject: [PATCH 24/32] wrap convertor callbacks by custom exceptions --- openpype/pipeline/create/context.py | 94 +++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index b6dce4c03d..c87803c5c4 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -71,6 +71,41 @@ class HostMissRequiredMethod(Exception): super(HostMissRequiredMethod, self).__init__(msg) +class ConvertorsOperationFailed(Exception): + def __init__(self, msg, failed_info): + super(ConvertorsOperationFailed, self).__init__(msg) + self.failed_info = failed_info + + +class ConvertorsFindFailed(ConvertorsOperationFailed): + def __init__(self, failed_info): + msg = "Failed to find incompatible subsets" + super(ConvertorsFindFailed, self).__init__( + msg, failed_info + ) + + +class ConvertorsConversionFailed(ConvertorsOperationFailed): + def __init__(self, failed_info): + msg = "Failed to convert incompatible subsets" + super(ConvertorsConversionFailed, self).__init__( + msg, failed_info + ) + + +def prepare_failed_convertor_operation_info(identifier, exc_info): + exc_type, exc_value, exc_traceback = exc_info + formatted_traceback = "".join(traceback.format_exception( + exc_type, exc_value, exc_traceback + )) + + return { + "convertor_identifier": identifier, + "message": str(exc_value), + "traceback": formatted_traceback + } + + class CreatorsOperationFailed(Exception): """Raised when a creator process crashes in 'CreateContext'. @@ -1486,12 +1521,26 @@ class CreateContext: raise CreatorsCollectionFailed(failed_info) def find_convertor_items(self): + """Go through convertor plugins to look for items to convert. + + Raises: + ConvertorsFindFailed: When one or more convertors fails during + finding. + """ + self.convertor_items_by_id = {} + failed_info = [] for convertor in self.convertors_plugins.values(): try: convertor.find_instances() + except: + failed_info.append( + prepare_failed_convertor_operation_info( + convertor.identifier, sys.exc_info() + ) + ) self.log.warning( "Failed to find instances of convertor \"{}\"".format( convertor.identifier @@ -1499,6 +1548,9 @@ class CreateContext: exc_info=True ) + if failed_info: + raise ConvertorsFindFailed(failed_info) + def execute_autocreators(self): """Execute discovered AutoCreator plugins. @@ -1756,6 +1808,48 @@ class CreateContext: return self._collection_shared_data def run_convertor(self, convertor_identifier): + """Run convertor plugin by it's idenfitifier. + + Conversion is skipped if convertor is not available. + + Args: + convertor_identifier (str): Identifier of convertor. + """ + convertor = self.convertors_plugins.get(convertor_identifier) if convertor is not None: convertor.convert() + + def run_convertors(self, convertor_identifiers): + """Run convertor plugins by idenfitifiers. + + Conversion is skipped if convertor is not available. + + Args: + convertor_identifiers (Iterator[str]): Identifiers of convertors + to run. + + Raises: + ConvertorsConversionFailed: When one or more convertors fails. + """ + + failed_info = [] + for convertor_identifier in convertor_identifiers: + try: + self.run_convertor(convertor_identifier) + + except: + failed_info.append( + prepare_failed_convertor_operation_info( + convertor_identifier, sys.exc_info() + ) + ) + self.log.warning( + "Failed to convert instances of convertor \"{}\"".format( + convertor_identifier + ), + exc_info=True + ) + + if failed_info: + raise ConvertorsConversionFailed(failed_info) From 9774c507f20623697dbeae1de747ca99d990fded Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 19:26:27 +0200 Subject: [PATCH 25/32] Error message box is less creator's specific --- openpype/tools/publisher/window.py | 105 ++++++++++++++++------------- 1 file changed, 57 insertions(+), 48 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index b6bd506c18..58c73f4821 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -1,4 +1,5 @@ import collections +import copy from Qt import QtWidgets, QtCore, QtGui from openpype import ( @@ -224,10 +225,10 @@ class PublisherWindow(QtWidgets.QDialog): # Floating publish frame publish_frame = PublishFrame(controller, self.footer_border, self) - creators_dialog_message_timer = QtCore.QTimer() - creators_dialog_message_timer.setInterval(100) - creators_dialog_message_timer.timeout.connect( - self._on_creators_message_timeout + errors_dialog_message_timer = QtCore.QTimer() + errors_dialog_message_timer.setInterval(100) + errors_dialog_message_timer.timeout.connect( + self._on_errors_message_timeout ) help_btn.clicked.connect(self._on_help_click) @@ -268,16 +269,16 @@ class PublisherWindow(QtWidgets.QDialog): "show.card.message", self._on_overlay_message ) controller.event_system.add_callback( - "instances.collection.failed", self._instance_collection_failed + "instances.collection.failed", self._on_creator_error ) controller.event_system.add_callback( - "instances.save.failed", self._instance_save_failed + "instances.save.failed", self._on_creator_error ) controller.event_system.add_callback( - "instances.remove.failed", self._instance_remove_failed + "instances.remove.failed", self._on_creator_error ) controller.event_system.add_callback( - "instances.create.failed", self._instance_create_failed + "instances.create.failed", self._on_creator_error ) # Store extra header widget for TrayPublisher @@ -325,8 +326,8 @@ class PublisherWindow(QtWidgets.QDialog): self._restart_timer = None self._publish_frame_visible = None - self._creators_messages_to_show = collections.deque() - self._creators_dialog_message_timer = creators_dialog_message_timer + self._error_messages_to_show = collections.deque() + self._errors_dialog_message_timer = errors_dialog_message_timer self._set_publish_visibility(False) @@ -357,7 +358,10 @@ class PublisherWindow(QtWidgets.QDialog): self._update_publish_frame_rect() def _on_overlay_message(self, event): - self._overlay_object.add_message(event["message"]) + self._overlay_object.add_message( + event["message"], + event.get("message_type") + ) def _on_first_show(self): self.resize(self.default_width, self.default_height) @@ -604,37 +608,39 @@ class PublisherWindow(QtWidgets.QDialog): 0, window_size.height() - height ) - def add_message_dialog(self, title, failed_info): - self._creators_messages_to_show.append((title, failed_info)) - self._creators_dialog_message_timer.start() + def add_error_message_dialog(self, title, failed_info, message_start=None): + self._error_messages_to_show.append( + (title, failed_info, message_start) + ) + self._errors_dialog_message_timer.start() - def _on_creators_message_timeout(self): - if not self._creators_messages_to_show: - self._creators_dialog_message_timer.stop() + def _on_errors_message_timeout(self): + if not self._error_messages_to_show: + self._errors_dialog_message_timer.stop() return - item = self._creators_messages_to_show.popleft() - title, failed_info = item - dialog = CreatorsErrorMessageBox(title, failed_info, self) + item = self._error_messages_to_show.popleft() + title, failed_info, message_start = item + dialog = ErrorsMessageBox( + title, failed_info, message_start, self + ) dialog.exec_() dialog.deleteLater() - def _instance_collection_failed(self, event): - self.add_message_dialog(event["title"], event["failed_info"]) - - def _instance_save_failed(self, event): - self.add_message_dialog(event["title"], event["failed_info"]) - - def _instance_remove_failed(self, event): - self.add_message_dialog(event["title"], event["failed_info"]) - - def _instance_create_failed(self, event): - self.add_message_dialog(event["title"], event["failed_info"]) + def _on_creator_error(self, event): + new_failed_info = [] + for item in event["failed_info"]: + new_item = copy.deepcopy(item) + new_item["label"] = new_item.pop("creator_label") + new_item["identifier"] = new_item.pop("creator_identifier") + new_failed_info.append(new_item) + self.add_error_message_dialog(event["title"], new_failed_info, "Creator:") -class CreatorsErrorMessageBox(ErrorMessageBox): - def __init__(self, error_title, failed_info, parent): +class ErrorsMessageBox(ErrorMessageBox): + def __init__(self, error_title, failed_info, message_start, parent): self._failed_info = failed_info + self._message_start = message_start self._info_with_id = [ # Id must be string when used in tab widget {"id": str(idx), "info": info} @@ -644,7 +650,7 @@ class CreatorsErrorMessageBox(ErrorMessageBox): self._tabs_widget = None self._stack_layout = None - super(CreatorsErrorMessageBox, self).__init__(error_title, parent) + super(ErrorsMessageBox, self).__init__(error_title, parent) layout = self.layout() layout.setContentsMargins(0, 0, 0, 0) @@ -659,17 +665,21 @@ class CreatorsErrorMessageBox(ErrorMessageBox): def _get_report_data(self): output = [] for info in self._failed_info: - creator_label = info["creator_label"] - creator_identifier = info["creator_identifier"] - report_message = "Creator:" - if creator_label: - report_message += " {} ({})".format( - creator_label, creator_identifier) + item_label = info.get("label") + item_identifier = info["identifier"] + if item_label: + report_message = "{} ({})".format( + item_label, item_identifier) else: - report_message += " {}".format(creator_identifier) + report_message = "{}".format(item_identifier) + + if self._message_start: + report_message = "{} {}".format( + self._message_start, report_message + ) report_message += "\n\nError: {}".format(info["message"]) - formatted_traceback = info["traceback"] + formatted_traceback = info.get("traceback") if formatted_traceback: report_message += "\n\n{}".format(formatted_traceback) output.append(report_message) @@ -686,11 +696,10 @@ class CreatorsErrorMessageBox(ErrorMessageBox): item_id = item["id"] info = item["info"] message = info["message"] - formatted_traceback = info["traceback"] - creator_label = info["creator_label"] - creator_identifier = info["creator_identifier"] - if not creator_label: - creator_label = creator_identifier + formatted_traceback = info.get("traceback") + item_label = info.get("label") + if not item_label: + item_label = info["identifier"] msg_widget = QtWidgets.QWidget(stack_widget) msg_layout = QtWidgets.QVBoxLayout(msg_widget) @@ -710,7 +719,7 @@ class CreatorsErrorMessageBox(ErrorMessageBox): msg_layout.addStretch(1) - tabs_widget.add_tab(creator_label, item_id) + tabs_widget.add_tab(item_label, item_id) stack_layout.addWidget(msg_widget) if first: first = False From 3ab3582b0a260bb9008a4138e8c3edc1c8f67ac1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 19:26:48 +0200 Subject: [PATCH 26/32] prepare to handle convertor errors --- openpype/tools/publisher/window.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 58c73f4821..a3387043b8 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -280,6 +280,12 @@ class PublisherWindow(QtWidgets.QDialog): controller.event_system.add_callback( "instances.create.failed", self._on_creator_error ) + controller.event_system.add_callback( + "convertors.convert.failed", self._on_convertor_error + ) + controller.event_system.add_callback( + "convertors.find.failed", self._on_convertor_error + ) # Store extra header widget for TrayPublisher # - can be used to add additional widgets to header between context @@ -636,6 +642,16 @@ class PublisherWindow(QtWidgets.QDialog): new_failed_info.append(new_item) self.add_error_message_dialog(event["title"], new_failed_info, "Creator:") + def _on_convertor_error(self, event): + new_failed_info = [] + for item in event["failed_info"]: + new_item = copy.deepcopy(item) + new_item["identifier"] = new_item.pop("convertor_identifier") + new_failed_info.append(new_item) + self.add_error_message_dialog( + event["title"], new_failed_info, "Convertor:" + ) + class ErrorsMessageBox(ErrorMessageBox): def __init__(self, error_title, failed_info, message_start, parent): From f9a75ea240e1c1c9c5e9213dbbb32d4cbf354067 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 19:27:21 +0200 Subject: [PATCH 27/32] handle ConvertorsOperationFailed in controller --- openpype/tools/publisher/control.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 482227e708..7cfc89f59e 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -33,6 +33,7 @@ from openpype.pipeline.create import ( ) from openpype.pipeline.create.context import ( CreatorsOperationFailed, + ConvertorsOperationFailed, ) # Define constant for plugin orders offset @@ -1743,7 +1744,16 @@ class PublisherController(BasePublisherController): } ) - self._create_context.find_convertor_items() + try: + self._create_context.find_convertor_items() + except ConvertorsOperationFailed as exc: + self._emit_event( + "convertors.find.failed", + { + "title": "Collection of unsupported subset failed", + "failed_info": exc.failed_info + } + ) try: self._create_context.execute_autocreators() @@ -1881,8 +1891,19 @@ class PublisherController(BasePublisherController): ) def trigger_convertor_items(self, convertor_identifiers): - for convertor_identifier in convertor_identifiers: - self._create_context.run_convertor(convertor_identifier) + success = True + try: + self._create_context.run_convertors(convertor_identifiers) + + except ConvertorsOperationFailed as exc: + success = False + self._emit_event( + "convertors.convert.failed", + { + "title": "Conversion failed", + "failed_info": exc.failed_info + } + ) self._on_create_instance_change() self.emit_card_message("Conversion finished") From 22a1191ab1a4e8ce516aef216e18f0f5a0817c68 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 19:27:34 +0200 Subject: [PATCH 28/32] emit card message can accept message types --- openpype/tools/publisher/control.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 7cfc89f59e..d4dddb75d5 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1264,7 +1264,7 @@ class AbstractPublisherController(object): pass @abstractmethod - def emit_card_message(self, message): + def emit_card_message(self, message, message_type=None): """Emit a card message which can have a lifetime. This is for UI purposes. Method can be extended to more arguments @@ -1771,8 +1771,14 @@ class PublisherController(BasePublisherController): self._on_create_instance_change() - def emit_card_message(self, message): - self._emit_event("show.card.message", {"message": message}) + def emit_card_message(self, message, message_type=None): + self._emit_event( + "show.card.message", + { + "message": message, + "message_type": message_type + } + ) def get_creator_attribute_definitions(self, instances): """Collect creator attribute definitions for multuple instances. From 12a272a8eec1c63bc2aece3c5a9acbb56cee0867 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 24 Oct 2022 19:32:06 +0200 Subject: [PATCH 29/32] added different types of card messages --- openpype/tools/publisher/control.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index d4dddb75d5..18d1a5b083 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -40,6 +40,11 @@ from openpype.pipeline.create.context import ( PLUGIN_ORDER_OFFSET = 0.5 +class CardMessageTypes: + standard = None + error = "error" + + class MainThreadItem: """Callback with args and kwargs.""" @@ -1264,7 +1269,9 @@ class AbstractPublisherController(object): pass @abstractmethod - def emit_card_message(self, message, message_type=None): + def emit_card_message( + self, message, message_type=CardMessageTypes.standard + ): """Emit a card message which can have a lifetime. This is for UI purposes. Method can be extended to more arguments @@ -1771,7 +1778,9 @@ class PublisherController(BasePublisherController): self._on_create_instance_change() - def emit_card_message(self, message, message_type=None): + def emit_card_message( + self, message, message_type=CardMessageTypes.standard + ): self._emit_event( "show.card.message", { @@ -1910,8 +1919,12 @@ class PublisherController(BasePublisherController): "failed_info": exc.failed_info } ) + + if success: + self.emit_card_message("Conversion finished") + else: + self.emit_card_message("Conversion failed", CardMessageTypes.error) self._on_create_instance_change() - self.emit_card_message("Conversion finished") def create( self, creator_identifier, subset_name, instance_data, options From 6f642ab34c09c617b0a0a10adc6d1821b901f337 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 25 Oct 2022 16:56:28 +0200 Subject: [PATCH 30/32] trigger reset of controller when conversion finishes --- openpype/pipeline/create/context.py | 3 ++- openpype/tools/publisher/control.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index c87803c5c4..52a1729233 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1823,7 +1823,8 @@ class CreateContext: def run_convertors(self, convertor_identifiers): """Run convertor plugins by idenfitifiers. - Conversion is skipped if convertor is not available. + Conversion is skipped if convertor is not available. It is recommended + to trigger reset after conversion to reload instances. Args: convertor_identifiers (Iterator[str]): Identifiers of convertors diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 18d1a5b083..e05cffe20e 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1906,6 +1906,8 @@ class PublisherController(BasePublisherController): ) def trigger_convertor_items(self, convertor_identifiers): + self.save_changes() + success = True try: self._create_context.run_convertors(convertor_identifiers) @@ -1924,7 +1926,8 @@ class PublisherController(BasePublisherController): self.emit_card_message("Conversion finished") else: self.emit_card_message("Conversion failed", CardMessageTypes.error) - self._on_create_instance_change() + + self.reset() def create( self, creator_identifier, subset_name, instance_data, options @@ -1972,7 +1975,6 @@ class PublisherController(BasePublisherController): Args: instance_ids (List[str]): List of instance ids to remove. """ - # TODO expect instance ids instead of instances # QUESTION Expect that instances are really removed? In that case save # reset is not required and save changes too. self.save_changes() From 698fe8379ea78901418f4cc4f1d6f8fc941c40ae Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 25 Oct 2022 16:57:33 +0200 Subject: [PATCH 31/32] added logger to convertor --- openpype/pipeline/create/creator_plugins.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 584e082221..c69abb8861 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -61,9 +61,23 @@ class SubsetConvertorPlugin(object): create_context """ + _log = None + def __init__(self, create_context): self._create_context = create_context + @property + def log(self): + """Logger of the plugin. + + Returns: + logging.Logger: Logger with name of the plugin. + """ + + if self._log is None: + self._log = Logger.get_logger(self.__class__.__name__) + return self._log + @abstractproperty def identifier(self): """Converted identifier. From 9b74287bb2d746da8f128a707d7e80785888d571 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 26 Oct 2022 03:58:16 +0000 Subject: [PATCH 32/32] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index b1e4227030..bf36fc4b10 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.5" +__version__ = "3.14.6-nightly.1"