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 1d5dee21f3..aa743b05fe 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -287,9 +287,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""" @@ -328,12 +326,11 @@ 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 - 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,37 +355,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: - 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.get("isGroup") or item.get("isMerged"): - for child in item.children(): - version_docs.append(child["version_document"]) - else: - version_docs.append(item["version_document"]) + 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) thumbnail_src_ids = [ version_doc["_id"] @@ -480,18 +456,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 8f18c01913..180dee3eb5 100644 --- a/openpype/tools/loader/lib.py +++ b/openpype/tools/loader/lib.py @@ -18,26 +18,6 @@ def change_visibility(model, view, column_name, visible): view.setColumnHidden(index, not visible) -def get_selected_items(rows, item_role): - items = [] - 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) - - else: - if item not in items: - items.append(item) - return items - - 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 96a52fce97..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( @@ -524,9 +538,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 +556,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 +564,6 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): ) }) - subset_counter += 1 self.add_child(merge_group, parent_item) return merge_group @@ -567,8 +583,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,14 +681,14 @@ 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 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 @@ -1139,7 +1154,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/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) 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