From 30b8908d6bf105fbd77133087cb0273f3d0c279b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Feb 2022 18:13:41 +0100 Subject: [PATCH 1/5] fix selected items --- openpype/tools/loader/app.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 1d5dee21f3..2443489f3c 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -1,4 +1,5 @@ import sys +import collections from Qt import QtWidgets, QtCore from avalon import api, io, pipeline @@ -378,15 +379,19 @@ class LoaderWindow(QtWidgets.QDialog): version_docs = [] if rows: + items = collections.deque() for index in rows: if not index or not index.isValid(): continue item = index.data(subsets.model.ItemRole) - if item is None: - continue + if item is not None: + items.append(item) + + while items: + item = items.popleft() if item.get("isGroup") or item.get("isMerged"): for child in item.children(): - version_docs.append(child["version_document"]) + items.append(child) else: version_docs.append(item["version_document"]) From 83b19949f960112fcbb4f676a7d2c2e635a8dfe6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Feb 2022 18:14:19 +0100 Subject: [PATCH 2/5] added ability to get selected items under group item --- openpype/tools/loader/lib.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/openpype/tools/loader/lib.py b/openpype/tools/loader/lib.py index 8f18c01913..857b40faf7 100644 --- a/openpype/tools/loader/lib.py +++ b/openpype/tools/loader/lib.py @@ -1,4 +1,5 @@ import inspect +import collections from Qt import QtGui from avalon.vendor import qtawesome @@ -19,23 +20,21 @@ def change_visibility(model, view, column_name, visible): def get_selected_items(rows, item_role): - items = [] + output = [] + items = collections.deque() for row_index in rows: item = row_index.data(item_role) - if item.get("isGroup"): - continue - - elif item.get("isMerged"): - for idx in range(row_index.model().rowCount(row_index)): - child_index = row_index.child(idx, 0) - item = child_index.data(item_role) - if item not in items: - items.append(item) + items.append(item) + while items: + item = items.popleft() + if item.get("isGroup") or item.get("isMerged"): + for child in item.children(): + items.append(child) else: - if item not in items: - items.append(item) - return items + if item not in output: + output.append(item) + return output def get_options(action, loader, parent, repre_contexts): From cd6dc59f1440104868deb09908d1796f19752515 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Feb 2022 18:14:48 +0100 Subject: [PATCH 3/5] asset underline colors can be cleared again --- openpype/tools/loader/app.py | 5 ++--- openpype/tools/loader/model.py | 15 ++++++--------- openpype/tools/utils/assets_widget.py | 13 ++++++++----- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 2443489f3c..619dcbcb06 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -288,9 +288,7 @@ class LoaderWindow(QtWidgets.QDialog): on selection change so they match current selection. """ # TODO do not touch inner attributes of asset widget - last_asset_ids = self.data["state"]["assetIds"] - if last_asset_ids: - self._assets_widget.clear_underlines() + self._assets_widget.clear_underlines() def _assetschanged(self): """Selected assets have changed""" @@ -329,6 +327,7 @@ class LoaderWindow(QtWidgets.QDialog): asset_ids = self.data["state"]["assetIds"] # Skip setting colors if not asset multiselection if not asset_ids or len(asset_ids) < 2: + self.clear_assets_underlines() self._versionschanged() return diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 96a52fce97..1f9223f596 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -524,9 +524,13 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): return self._fill_subset_items( - asset_docs_by_id, subset_docs_by_id, last_versions_by_subset_id, + asset_docs_by_id, + subset_docs_by_id, + last_versions_by_subset_id, repre_info_by_version_id ) + self.endResetModel() + self.refreshed.emit(True) def create_multiasset_group( self, subset_name, asset_ids, subset_counter, parent_item=None @@ -538,7 +542,6 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): merge_group.update({ "subset": "{} ({})".format(subset_name, len(asset_ids)), "isMerged": True, - "childRow": 0, "subsetColor": subset_color, "assetIds": list(asset_ids), "icon": qtawesome.icon( @@ -547,7 +550,6 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): ) }) - subset_counter += 1 self.add_child(merge_group, parent_item) return merge_group @@ -567,8 +569,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): group_item = Item() group_item.update({ "subset": group_name, - "isGroup": True, - "childRow": 0 + "isGroup": True }) group_item.update(group_data) @@ -666,9 +667,6 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): index = self.index(item.row(), 0, parent_index) self.set_version(index, last_version) - self.endResetModel() - self.refreshed.emit(True) - def data(self, index, role): if not index.isValid(): return @@ -1139,7 +1137,6 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): "_id": doc["_id"], "name": doc["name"], "isMerged": True, - "childRow": 0, "active_site_name": self.active_site, "remote_site_name": self.remote_site, "icon": qtawesome.icon( diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py index 55e34285fc..be9f442d10 100644 --- a/openpype/tools/utils/assets_widget.py +++ b/openpype/tools/utils/assets_widget.py @@ -303,7 +303,7 @@ class AssetModel(QtGui.QStandardItemModel): self._doc_fetched.connect(self._on_docs_fetched) - self._items_with_color_by_id = {} + self._item_ids_with_color = set() self._items_by_asset_id = {} self._last_project_name = None @@ -382,9 +382,11 @@ class AssetModel(QtGui.QStandardItemModel): self._stop_fetch_thread() def clear_underlines(self): - for asset_id in tuple(self._items_with_color_by_id.keys()): - item = self._items_with_color_by_id.pop(asset_id) - item.setData(None, ASSET_UNDERLINE_COLORS_ROLE) + for asset_id in set(self._item_ids_with_color): + self._item_ids_with_color.remove(asset_id) + item = self._items_by_asset_id.get(asset_id) + if item is not None: + item.setData(None, ASSET_UNDERLINE_COLORS_ROLE) def set_underline_colors(self, colors_by_asset_id): self.clear_underlines() @@ -394,12 +396,13 @@ class AssetModel(QtGui.QStandardItemModel): if item is None: continue item.setData(colors, ASSET_UNDERLINE_COLORS_ROLE) + self._item_ids_with_color.add(asset_id) def _clear_items(self): root_item = self.invisibleRootItem() root_item.removeRows(0, root_item.rowCount()) self._items_by_asset_id = {} - self._items_with_color_by_id = {} + self._item_ids_with_color = set() def _on_docs_fetched(self): # Make sure refreshing did not change From 19bfb53ad9455a8276e6ddeea3b29a3145e94982 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 16 Feb 2022 10:38:05 +0100 Subject: [PATCH 4/5] don't use ItemRole in loader --- openpype/tools/libraryloader/app.py | 39 +++--------- openpype/tools/loader/app.py | 56 +++--------------- openpype/tools/loader/lib.py | 18 ------ openpype/tools/loader/model.py | 19 +++++- openpype/tools/loader/widgets.py | 92 ++++++++++++++++++----------- 5 files changed, 92 insertions(+), 132 deletions(-) diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index d030aa903d..9f8845f30f 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -396,9 +396,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): self._versionschanged() return - selected_subsets = self._subsets_widget.selected_subsets( - _merged=True, _other=False - ) + selected_subsets = self._subsets_widget.get_selected_merge_items() asset_colors = {} asset_ids = [] @@ -423,35 +421,14 @@ class LibraryLoaderWindow(QtWidgets.QDialog): self._versionschanged() def _versionschanged(self): - selection = self._subsets_widget.view.selectionModel() - - # Active must be in the selected rows otherwise we - # assume it's not actually an "active" current index. - version_docs = None + items = self._subsets_widget.get_selected_subsets() version_doc = None - active = selection.currentIndex() - rows = selection.selectedRows(column=active.column()) - if active and active in rows: - item = active.data(self._subsets_widget.model.ItemRole) - if ( - item is not None - and not (item.get("isGroup") or item.get("isMerged")) - ): - version_doc = item["version_document"] - - if rows: - version_docs = [] - for index in rows: - if not index or not index.isValid(): - continue - item = index.data(self._subsets_widget.model.ItemRole) - if ( - item is None - or item.get("isGroup") - or item.get("isMerged") - ): - continue - version_docs.append(item["version_document"]) + version_docs = [] + for item in items: + doc = item["version_document"] + version_docs.append(doc) + if version_doc is None: + version_doc = doc self._version_info_widget.set_version(version_doc) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 619dcbcb06..6f67f0c641 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -331,9 +331,7 @@ class LoaderWindow(QtWidgets.QDialog): self._versionschanged() return - selected_subsets = self._subsets_widget.selected_subsets( - _merged=True, _other=False - ) + selected_subsets = self._subsets_widget.get_selected_merge_items() asset_colors = {} asset_ids = [] @@ -358,41 +356,16 @@ class LoaderWindow(QtWidgets.QDialog): self._versionschanged() def _versionschanged(self): - subsets = self._subsets_widget - selection = subsets.view.selectionModel() - - # Active must be in the selected rows otherwise we - # assume it's not actually an "active" current index. + items = self._subsets_widget.get_selected_subsets() version_doc = None - active = selection.currentIndex() - rows = selection.selectedRows(column=active.column()) - if active: - if active in rows: - item = active.data(subsets.model.ItemRole) - if ( - item is not None and - not (item.get("isGroup") or item.get("isMerged")) - ): - version_doc = item["version_document"] - self._version_info_widget.set_version(version_doc) - version_docs = [] - if rows: - items = collections.deque() - for index in rows: - if not index or not index.isValid(): - continue - item = index.data(subsets.model.ItemRole) - if item is not None: - items.append(item) + for item in items: + doc = item["version_document"] + version_docs.append(doc) + if version_doc is None: + version_doc = doc - while items: - item = items.popleft() - if item.get("isGroup") or item.get("isMerged"): - for child in item.children(): - items.append(child) - else: - version_docs.append(item["version_document"]) + self._version_info_widget.set_version(version_doc) thumbnail_src_ids = [ version_doc["_id"] @@ -484,18 +457,7 @@ class LoaderWindow(QtWidgets.QDialog): self.echo("Grouping not enabled.") return - selected = [] - merged_items = [] - for item in subsets.selected_subsets(_merged=True): - if item.get("isMerged"): - merged_items.append(item) - else: - selected.append(item) - - for merged_item in merged_items: - for child_item in merged_item.children(): - selected.append(child_item) - + selected = self._subsets_widget.get_selected_subsets() if not selected: self.echo("No selected subset.") return diff --git a/openpype/tools/loader/lib.py b/openpype/tools/loader/lib.py index 857b40faf7..60e7672340 100644 --- a/openpype/tools/loader/lib.py +++ b/openpype/tools/loader/lib.py @@ -19,24 +19,6 @@ def change_visibility(model, view, column_name, visible): view.setColumnHidden(index, not visible) -def get_selected_items(rows, item_role): - output = [] - items = collections.deque() - for row_index in rows: - item = row_index.data(item_role) - items.append(item) - - while items: - item = items.popleft() - if item.get("isGroup") or item.get("isMerged"): - for child in item.children(): - items.append(child) - else: - if item not in output: - output.append(item) - return output - - def get_options(action, loader, parent, repre_contexts): """Provides dialog to select value from loader provided options. diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 1f9223f596..f34eb157ab 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -1,6 +1,7 @@ import copy import re import math +from uuid import uuid4 from avalon import ( style, @@ -22,6 +23,8 @@ from openpype.tools.utils.constants import ( REMOTE_AVAILABILITY_ROLE ) +ITEM_ID_ROLE = QtCore.Qt.UserRole + 90 + def is_filtering_recursible(): """Does Qt binding support recursive filtering for QSortFilterProxyModel? @@ -179,6 +182,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self._icons = { "subset": qtawesome.icon("fa.file-o", color=style.colors.default) } + self._items_by_id = {} self._doc_fetching_thread = None self._doc_fetching_stop = False @@ -188,6 +192,15 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self.refresh() + def get_item_by_id(self, item_id): + return self._items_by_id.get(item_id) + + def add_child(self, new_item, *args, **kwargs): + item_id = str(uuid4()) + new_item["id"] = item_id + self._items_by_id[item_id] = new_item + super(SubsetsModel, self).add_child(new_item, *args, **kwargs) + def set_assets(self, asset_ids): self._asset_ids = asset_ids self.refresh() @@ -486,7 +499,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): def refresh(self): self.stop_fetch_thread() self.clear() - + self._items_by_id = {} self.reset_sync_server() if not self._asset_ids: @@ -497,6 +510,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): def on_doc_fetched(self): self.clear() + self._items_by_id = {} self.beginResetModel() asset_docs_by_id = self._doc_payload.get( @@ -672,6 +686,9 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): return item = index.internalPointer() + if role == ITEM_ID_ROLE: + return item["id"] + if role == self.SortDescendingRole: if item.get("isGroup"): # Ensure groups be on top when sorting by descending order diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index ed130f765c..f60341af2f 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -34,7 +34,8 @@ from .model import ( SubsetFilterProxyModel, FamiliesFilterProxyModel, RepresentationModel, - RepresentationSortProxyModel + RepresentationSortProxyModel, + ITEM_ID_ROLE ) from . import lib @@ -351,6 +352,59 @@ class SubsetWidget(QtWidgets.QWidget): lib.change_visibility(self.model, self.view, "repre_info", enabled) + def get_selected_items(self): + selection_model = self.view.selectionModel() + indexes = selection_model.selectedIndexes() + + item_ids = set() + for index in indexes: + item_id = index.data(ITEM_ID_ROLE) + if item_id is not None: + item_ids.add(item_id) + + output = [] + for item_id in item_ids: + item = self.model.get_item_by_id(item_id) + if item is not None: + output.append(item) + return output + + def get_selected_merge_items(self): + output = [] + items = collections.deque(self.get_selected_items()) + + item_ids = set() + while items: + item = items.popleft() + if item.get("isGroup"): + for child in item.children(): + items.appendleft(child) + + elif item.get("isMerged"): + item_id = item["id"] + if item_id not in item_ids: + item_ids.add(item_id) + output.append(item) + + return output + + def get_selected_subsets(self): + output = [] + items = collections.deque(self.get_selected_items()) + + item_ids = set() + while items: + item = items.popleft() + if item.get("isGroup") or item.get("isMerged"): + for child in item.children(): + items.appendleft(child) + else: + item_id = item["id"] + if item_id not in item_ids: + item_ids.add(item_id) + output.append(item) + return output + def on_context_menu(self, point): """Shows menu with loader actions on Right-click. @@ -367,10 +421,7 @@ class SubsetWidget(QtWidgets.QWidget): return # Get selected subsets without groups - selection = self.view.selectionModel() - rows = selection.selectedRows(column=0) - - items = lib.get_selected_items(rows, self.model.ItemRole) + items = self.get_selected_subsets() # Get all representation->loader combinations available for the # index under the cursor, so we can list the user the options. @@ -539,35 +590,6 @@ class SubsetWidget(QtWidgets.QWidget): box = LoadErrorMessageBox(error_info, self) box.show() - def selected_subsets(self, _groups=False, _merged=False, _other=True): - selection = self.view.selectionModel() - rows = selection.selectedRows(column=0) - - subsets = list() - if not any([_groups, _merged, _other]): - self.echo(( - "This is a BUG: Selected_subsets args must contain" - " at least one value set to True" - )) - return subsets - - for row in rows: - item = row.data(self.model.ItemRole) - if item.get("isGroup"): - if not _groups: - continue - - elif item.get("isMerged"): - if not _merged: - continue - else: - if not _other: - continue - - subsets.append(item) - - return subsets - def group_subsets(self, name, asset_ids, items): field = "data.subsetGroup" @@ -1279,7 +1301,7 @@ class RepresentationWidget(QtWidgets.QWidget): selection = self.tree_view.selectionModel() rows = selection.selectedRows(column=0) - items = lib.get_selected_items(rows, self.model.ItemRole) + items = self.get_selected_subsets() selected_side = self._get_selected_side(point_index, rows) From 4748e111a2a15d88b68ac23b613555a9570e1dde Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 16 Feb 2022 10:40:03 +0100 Subject: [PATCH 5/5] hound fixes --- openpype/tools/loader/app.py | 1 - openpype/tools/loader/lib.py | 1 - 2 files changed, 2 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 6f67f0c641..aa743b05fe 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -1,5 +1,4 @@ import sys -import collections from Qt import QtWidgets, QtCore from avalon import api, io, pipeline diff --git a/openpype/tools/loader/lib.py b/openpype/tools/loader/lib.py index 60e7672340..180dee3eb5 100644 --- a/openpype/tools/loader/lib.py +++ b/openpype/tools/loader/lib.py @@ -1,5 +1,4 @@ import inspect -import collections from Qt import QtGui from avalon.vendor import qtawesome