diff --git a/openpype/modules/default_modules/sync_server/tray/delegates.py b/openpype/modules/default_modules/sync_server/tray/delegates.py index 461b9fffb36..5ab809a8166 100644 --- a/openpype/modules/default_modules/sync_server/tray/delegates.py +++ b/openpype/modules/default_modules/sync_server/tray/delegates.py @@ -4,6 +4,18 @@ from openpype.lib import PypeLogger from . import lib +from openpype.tools.utils.constants import ( + LOCAL_PROVIDER_ROLE, + REMOTE_PROVIDER_ROLE, + LOCAL_PROGRESS_ROLE, + REMOTE_PROGRESS_ROLE, + LOCAL_DATE_ROLE, + REMOTE_DATE_ROLE, + LOCAL_FAILED_ROLE, + REMOTE_FAILED_ROLE, + EDIT_ICON_ROLE +) + log = PypeLogger().get_logger("SyncServer") @@ -14,7 +26,7 @@ def paint(self, painter, option, index): if option.widget.selectionModel().isSelected(index) or \ option.state & QtWidgets.QStyle.State_MouseOver: - edit_icon = index.data(lib.EditIconRole) + edit_icon = index.data(EDIT_ICON_ROLE) if not edit_icon: return @@ -38,7 +50,7 @@ def createEditor(self, parent, option, index): editor = PriorityLineEdit( parent, option.widget.selectionModel().selectedRows()) - editor.setFocus(True) + editor.setFocus() return editor def setModelData(self, editor, model, index): @@ -71,19 +83,30 @@ class ImageDelegate(QtWidgets.QStyledItemDelegate): Prints icon of site and progress of synchronization """ - def __init__(self, parent=None): + def __init__(self, parent=None, side=None): super(ImageDelegate, self).__init__(parent) self.icons = {} + self.side = side def paint(self, painter, option, index): super(ImageDelegate, self).paint(painter, option, index) option = QtWidgets.QStyleOptionViewItem(option) option.showDecorationSelected = True - provider = index.data(lib.ProviderRole) - value = index.data(lib.ProgressRole) - date_value = index.data(lib.DateRole) - is_failed = index.data(lib.FailedRole) + if not self.side: + log.warning("No side provided, delegate won't work") + return + + if self.side == 'local': + provider = index.data(LOCAL_PROVIDER_ROLE) + value = index.data(LOCAL_PROGRESS_ROLE) + date_value = index.data(LOCAL_DATE_ROLE) + is_failed = index.data(LOCAL_FAILED_ROLE) + else: + provider = index.data(REMOTE_PROVIDER_ROLE) + value = index.data(REMOTE_PROGRESS_ROLE) + date_value = index.data(REMOTE_DATE_ROLE) + is_failed = index.data(REMOTE_FAILED_ROLE) if not self.icons.get(provider): resource_path = os.path.dirname(__file__) diff --git a/openpype/modules/default_modules/sync_server/tray/lib.py b/openpype/modules/default_modules/sync_server/tray/lib.py index 25c600abd23..87344be634d 100644 --- a/openpype/modules/default_modules/sync_server/tray/lib.py +++ b/openpype/modules/default_modules/sync_server/tray/lib.py @@ -1,4 +1,3 @@ -from Qt import QtCore import attr import abc import six @@ -19,14 +18,6 @@ DUMMY_PROJECT = "No project configured" -ProviderRole = QtCore.Qt.UserRole + 2 -ProgressRole = QtCore.Qt.UserRole + 4 -DateRole = QtCore.Qt.UserRole + 6 -FailedRole = QtCore.Qt.UserRole + 8 -HeaderNameRole = QtCore.Qt.UserRole + 10 -FullItemRole = QtCore.Qt.UserRole + 12 -EditIconRole = QtCore.Qt.UserRole + 14 - @six.add_metaclass(abc.ABCMeta) class AbstractColumnFilter: @@ -161,7 +152,7 @@ def translate_provider_for_icon(sync_server, project, site): return sync_server.get_provider_for_site(site=site) -def get_item_by_id(model, object_id): +def get_value_from_id_by_role(model, object_id, role): + """Return value from item with 'object_id' with 'role'.""" index = model.get_index(object_id) - item = model.data(index, FullItemRole) - return item + return model.data(index, role) diff --git a/openpype/modules/default_modules/sync_server/tray/models.py b/openpype/modules/default_modules/sync_server/tray/models.py index 713e167a6a2..80f41992cb3 100644 --- a/openpype/modules/default_modules/sync_server/tray/models.py +++ b/openpype/modules/default_modules/sync_server/tray/models.py @@ -13,6 +13,23 @@ from . import lib +from openpype.tools.utils.constants import ( + LOCAL_PROVIDER_ROLE, + REMOTE_PROVIDER_ROLE, + LOCAL_PROGRESS_ROLE, + REMOTE_PROGRESS_ROLE, + HEADER_NAME_ROLE, + EDIT_ICON_ROLE, + LOCAL_DATE_ROLE, + REMOTE_DATE_ROLE, + LOCAL_FAILED_ROLE, + REMOTE_FAILED_ROLE, + STATUS_ROLE, + PATH_ROLE, + ERROR_ROLE, + TRIES_ROLE +) + log = PypeLogger().get_logger("SyncServer") @@ -68,10 +85,68 @@ def headerData(self, section, orientation, role=Qt.DisplayRole): if orientation == Qt.Horizontal: return self.COLUMN_LABELS[section][1] - if role == lib.HeaderNameRole: + if role == HEADER_NAME_ROLE: if orientation == Qt.Horizontal: return self.COLUMN_LABELS[section][0] # return name + def data(self, index, role): + item = self._data[index.row()] + + header_value = self._header[index.column()] + if role == LOCAL_PROVIDER_ROLE: + return item.local_provider + + if role == REMOTE_PROVIDER_ROLE: + return item.remote_provider + + if role == LOCAL_PROGRESS_ROLE: + return item.local_progress + + if role == REMOTE_PROGRESS_ROLE: + return item.remote_progress + + if role == LOCAL_DATE_ROLE: + if item.created_dt: + return pretty_timestamp(item.created_dt) + + if role == REMOTE_DATE_ROLE: + if item.sync_dt: + return pretty_timestamp(item.sync_dt) + + if role == LOCAL_FAILED_ROLE: + return item.status == lib.STATUS[2] and \ + item.local_progress < 1 + + if role == REMOTE_FAILED_ROLE: + return item.status == lib.STATUS[2] and \ + item.remote_progress < 1 + + if role in (Qt.DisplayRole, Qt.EditRole): + # because of ImageDelegate + if header_value in ['remote_site', 'local_site']: + return "" + + return attr.asdict(item)[self._header[index.column()]] + + if role == EDIT_ICON_ROLE: + if self.can_edit and header_value in self.EDITABLE_COLUMNS: + return self.edit_icon + + if role == PATH_ROLE: + return item.path + + if role == ERROR_ROLE: + return item.error + + if role == TRIES_ROLE: + return item.tries + + if role == STATUS_ROLE: + return item.status + + if role == Qt.UserRole: + return item._id + @property def can_edit(self): """Returns true if some site is user local site, eg. could edit""" @@ -456,55 +531,6 @@ def __init__(self, sync_server, header, project=None, parent=None): self.timer.timeout.connect(self.tick) self.timer.start(self.REFRESH_SEC) - def data(self, index, role): - item = self._data[index.row()] - - if role == lib.FullItemRole: - return item - - header_value = self._header[index.column()] - if role == lib.ProviderRole: - if header_value == 'local_site': - return item.local_provider - if header_value == 'remote_site': - return item.remote_provider - - if role == lib.ProgressRole: - if header_value == 'local_site': - return item.local_progress - if header_value == 'remote_site': - return item.remote_progress - - if role == lib.DateRole: - if header_value == 'local_site': - if item.created_dt: - return pretty_timestamp(item.created_dt) - if header_value == 'remote_site': - if item.sync_dt: - return pretty_timestamp(item.sync_dt) - - if role == lib.FailedRole: - if header_value == 'local_site': - return item.status == lib.STATUS[2] and \ - item.local_progress < 1 - if header_value == 'remote_site': - return item.status == lib.STATUS[2] and \ - item.remote_progress < 1 - - if role in (Qt.DisplayRole, Qt.EditRole): - # because of ImageDelegate - if header_value in ['remote_site', 'local_site']: - return "" - - return attr.asdict(item)[self._header[index.column()]] - - if role == lib.EditIconRole: - if self.can_edit and header_value in self.EDITABLE_COLUMNS: - return self.edit_icon - - if role == Qt.UserRole: - return item._id - def add_page_records(self, local_site, remote_site, representations): """ Process all records from 'representation' and add them to storage. @@ -985,55 +1011,6 @@ def __init__(self, sync_server, header, _id, self.timer.timeout.connect(self.tick) self.timer.start(SyncRepresentationSummaryModel.REFRESH_SEC) - def data(self, index, role): - item = self._data[index.row()] - - if role == lib.FullItemRole: - return item - - header_value = self._header[index.column()] - if role == lib.ProviderRole: - if header_value == 'local_site': - return item.local_provider - if header_value == 'remote_site': - return item.remote_provider - - if role == lib.ProgressRole: - if header_value == 'local_site': - return item.local_progress - if header_value == 'remote_site': - return item.remote_progress - - if role == lib.DateRole: - if header_value == 'local_site': - if item.created_dt: - return pretty_timestamp(item.created_dt) - if header_value == 'remote_site': - if item.sync_dt: - return pretty_timestamp(item.sync_dt) - - if role == lib.FailedRole: - if header_value == 'local_site': - return item.status == lib.STATUS[2] and \ - item.local_progress < 1 - if header_value == 'remote_site': - return item.status == lib.STATUS[2] and \ - item.remote_progress < 1 - - if role in (Qt.DisplayRole, Qt.EditRole): - # because of ImageDelegate - if header_value in ['remote_site', 'local_site']: - return "" - - return attr.asdict(item)[self._header[index.column()]] - - if role == lib.EditIconRole: - if self.can_edit and header_value in self.EDITABLE_COLUMNS: - return self.edit_icon - - if role == Qt.UserRole: - return item._id - def add_page_records(self, local_site, remote_site, representations): """ Process all records from 'representation' and add them to storage. diff --git a/openpype/modules/default_modules/sync_server/tray/widgets.py b/openpype/modules/default_modules/sync_server/tray/widgets.py index 5e368f9e0b6..87044889b70 100644 --- a/openpype/modules/default_modules/sync_server/tray/widgets.py +++ b/openpype/modules/default_modules/sync_server/tray/widgets.py @@ -22,6 +22,20 @@ from . import lib from . import delegates +from openpype.tools.utils.constants import ( + LOCAL_PROGRESS_ROLE, + REMOTE_PROGRESS_ROLE, + HEADER_NAME_ROLE, + STATUS_ROLE, + PATH_ROLE, + LOCAL_SITE_NAME_ROLE, + REMOTE_SITE_NAME_ROLE, + LOCAL_DATE_ROLE, + REMOTE_DATE_ROLE, + ERROR_ROLE, + TRIES_ROLE +) + log = PypeLogger().get_logger("SyncServer") @@ -289,14 +303,19 @@ def _on_context_menu(self, point): if is_multi: index = self.model.get_index(list(self._selected_ids)[0]) - item = self.model.data(index, lib.FullItemRole) + local_progress = self.model.data(index, LOCAL_PROGRESS_ROLE) + remote_progress = self.model.data(index, REMOTE_PROGRESS_ROLE) + status = self.model.data(index, STATUS_ROLE) else: - item = self.model.data(point_index, lib.FullItemRole) + local_progress = self.model.data(point_index, LOCAL_PROGRESS_ROLE) + remote_progress = self.model.data(point_index, + REMOTE_PROGRESS_ROLE) + status = self.model.data(point_index, STATUS_ROLE) + can_edit = self.model.can_edit - action_kwarg_map, actions_mapping, menu = self._prepare_menu(item, - is_multi, - can_edit) + action_kwarg_map, actions_mapping, menu = self._prepare_menu( + local_progress, remote_progress, is_multi, can_edit, status) result = menu.exec_(QtGui.QCursor.pos()) if result: @@ -307,7 +326,8 @@ def _on_context_menu(self, point): self.model.refresh() - def _prepare_menu(self, item, is_multi, can_edit): + def _prepare_menu(self, local_progress, remote_progress, + is_multi, can_edit, status=None): menu = QtWidgets.QMenu(self) actions_mapping = {} @@ -316,11 +336,6 @@ def _prepare_menu(self, item, is_multi, can_edit): active_site = self.model.active_site remote_site = self.model.remote_site - local_progress = item.local_progress - remote_progress = item.remote_progress - - project = self.model.project - for site, progress in {active_site: local_progress, remote_site: remote_progress}.items(): provider = self.sync_server.get_provider_for_site(site=site) @@ -360,12 +375,6 @@ def _prepare_menu(self, item, is_multi, can_edit): actions_mapping[action] = self._change_priority menu.addAction(action) - # # temp for testing only !!! - # action = QtWidgets.QAction("Download") - # action_kwarg_map[action] = self._get_action_kwargs(active_site) - # actions_mapping[action] = self._add_site - # menu.addAction(action) - if not actions_mapping: action = QtWidgets.QAction("< No action >") actions_mapping[action] = None @@ -376,11 +385,15 @@ def _prepare_menu(self, item, is_multi, can_edit): def _pause(self, selected_ids=None): log.debug("Pause {}".format(selected_ids)) for representation_id in selected_ids: - item = lib.get_item_by_id(self.model, representation_id) - if item.status not in [lib.STATUS[0], lib.STATUS[1]]: + status = lib.get_value_from_id_by_role(self.model, + representation_id, + STATUS_ROLE) + if status not in [lib.STATUS[0], lib.STATUS[1]]: continue for site_name in [self.model.active_site, self.model.remote_site]: - check_progress = self._get_progress(item, site_name) + check_progress = self._get_progress(self.model, + representation_id, + site_name) if check_progress < 1: self.sync_server.pause_representation(self.model.project, representation_id, @@ -391,11 +404,15 @@ def _pause(self, selected_ids=None): def _unpause(self, selected_ids=None): log.debug("UnPause {}".format(selected_ids)) for representation_id in selected_ids: - item = lib.get_item_by_id(self.model, representation_id) - if item.status not in lib.STATUS[3]: + status = lib.get_value_from_id_by_role(self.model, + representation_id, + STATUS_ROLE) + if status not in lib.STATUS[3]: continue for site_name in [self.model.active_site, self.model.remote_site]: - check_progress = self._get_progress(item, site_name) + check_progress = self._get_progress(self.model, + representation_id, + site_name) if check_progress < 1: self.sync_server.unpause_representation( self.model.project, @@ -408,8 +425,11 @@ def _unpause(self, selected_ids=None): def _add_site(self, selected_ids=None, site_name=None): log.debug("Add site {}:{}".format(selected_ids, site_name)) for representation_id in selected_ids: - item = lib.get_item_by_id(self.model, representation_id) - if item.local_site == site_name or item.remote_site == site_name: + item_local_site = lib.get_value_from_id_by_role( + self.model, representation_id, LOCAL_SITE_NAME_ROLE) + item_remote_site = lib.get_value_from_id_by_role( + self.model, representation_id, REMOTE_SITE_NAME_ROLE) + if site_name in [item_local_site, item_remote_site]: # site already exists skip continue @@ -460,8 +480,8 @@ def _reset_site(self, selected_ids=None, site_name=None): """ log.debug("Reset site {}:{}".format(selected_ids, site_name)) for representation_id in selected_ids: - item = lib.get_item_by_id(self.model, representation_id) - check_progress = self._get_progress(item, site_name, True) + check_progress = self._get_progress(self.model, representation_id, + site_name, True) # do not reset if opposite side is not fully there if check_progress != 1: @@ -482,11 +502,8 @@ def _reset_site(self, selected_ids=None, site_name=None): def _open_in_explorer(self, selected_ids=None, site_name=None): log.debug("Open in Explorer {}:{}".format(selected_ids, site_name)) for selected_id in selected_ids: - item = lib.get_item_by_id(self.model, selected_id) - if not item: - return - - fpath = item.path + fpath = lib.get_value_from_id_by_role(self.model, selected_id, + PATH_ROLE) project = self.model.project fpath = self.sync_server.get_local_file_path(project, site_name, @@ -514,10 +531,17 @@ def _change_priority(self, **kwargs): self.model.is_editing = True self.table_view.openPersistentEditor(real_index) - def _get_progress(self, item, site_name, opposite=False): + def _get_progress(self, model, representation_id, + site_name, opposite=False): """Returns progress value according to site (side)""" - progress = {'local': item.local_progress, - 'remote': item.remote_progress} + local_progress = lib.get_value_from_id_by_role(model, + representation_id, + LOCAL_PROGRESS_ROLE) + remote_progress = lib.get_value_from_id_by_role(model, + representation_id, + REMOTE_PROGRESS_ROLE) + progress = {'local': local_progress, + 'remote': remote_progress} side = 'remote' if site_name == self.model.active_site: side = 'local' @@ -591,11 +615,11 @@ def __init__(self, sync_server, project=None, parent=None): table_view.viewport().setAttribute(QtCore.Qt.WA_Hover, True) column = table_view.model().get_header_index("local_site") - delegate = delegates.ImageDelegate(self) + delegate = delegates.ImageDelegate(self, side="local") table_view.setItemDelegateForColumn(column, delegate) column = table_view.model().get_header_index("remote_site") - delegate = delegates.ImageDelegate(self) + delegate = delegates.ImageDelegate(self, side="remote") table_view.setItemDelegateForColumn(column, delegate) column = table_view.model().get_header_index("priority") @@ -631,19 +655,21 @@ def __init__(self, sync_server, project=None, parent=None): self.selection_model = self.table_view.selectionModel() self.selection_model.selectionChanged.connect(self._selection_changed) - def _prepare_menu(self, item, is_multi, can_edit): + def _prepare_menu(self, local_progress, remote_progress, + is_multi, can_edit, status=None): action_kwarg_map, actions_mapping, menu = \ - super()._prepare_menu(item, is_multi, can_edit) + super()._prepare_menu(local_progress, remote_progress, + is_multi, can_edit) if can_edit and ( - item.status in [lib.STATUS[0], lib.STATUS[1]] or is_multi): + status in [lib.STATUS[0], lib.STATUS[1]] or is_multi): action = QtWidgets.QAction("Pause in queue") actions_mapping[action] = self._pause # pause handles which site_name it will pause itself action_kwarg_map[action] = {"selected_ids": self._selected_ids} menu.addAction(action) - if can_edit and (item.status == lib.STATUS[3] or is_multi): + if can_edit and (status == lib.STATUS[3] or is_multi): action = QtWidgets.QAction("Unpause in queue") actions_mapping[action] = self._unpause action_kwarg_map[action] = {"selected_ids": self._selected_ids} @@ -753,11 +779,11 @@ def __init__(self, sync_server, _id=None, project=None, parent=None): table_view.verticalHeader().hide() column = model.get_header_index("local_site") - delegate = delegates.ImageDelegate(self) + delegate = delegates.ImageDelegate(self, side="local") table_view.setItemDelegateForColumn(column, delegate) column = model.get_header_index("remote_site") - delegate = delegates.ImageDelegate(self) + delegate = delegates.ImageDelegate(self, side="remote") table_view.setItemDelegateForColumn(column, delegate) if model.can_edit: @@ -815,12 +841,14 @@ def _show_detail(self, selected_ids=None): detail_window.exec() - def _prepare_menu(self, item, is_multi, can_edit): + def _prepare_menu(self, local_progress, remote_progress, + is_multi, can_edit, status=None): """Adds view (and model) dependent actions to default ones""" action_kwarg_map, actions_mapping, menu = \ - super()._prepare_menu(item, is_multi, can_edit) + super()._prepare_menu(local_progress, remote_progress, + is_multi, can_edit, status) - if item.status == lib.STATUS[2] or is_multi: + if status == lib.STATUS[2] or is_multi: action = QtWidgets.QAction("Open error detail") actions_mapping[action] = self._show_detail action_kwarg_map[action] = {"selected_ids": self._selected_ids} @@ -835,8 +863,8 @@ def _reset_site(self, selected_ids=None, site_name=None): redo of upload/download """ for file_id in selected_ids: - item = lib.get_item_by_id(self.model, file_id) - check_progress = self._get_progress(item, site_name, True) + check_progress = self._get_progress(self.model, file_id, + site_name, True) # do not reset if opposite side is not fully there if check_progress != 1: @@ -895,20 +923,28 @@ def __init__(self, model, selected_ids, parent=None): no_errors = True for file_id in selected_ids: - item = lib.get_item_by_id(model, file_id) - if not item.created_dt or not item.sync_dt or not item.error: + created_dt = lib.get_value_from_id_by_role(model, file_id, + LOCAL_DATE_ROLE) + sync_dt = lib.get_value_from_id_by_role(model, file_id, + REMOTE_DATE_ROLE) + errors = lib.get_value_from_id_by_role(model, file_id, + ERROR_ROLE) + if not created_dt or not sync_dt or not errors: continue + tries = lib.get_value_from_id_by_role(model, file_id, + TRIES_ROLE) + no_errors = False - dt = max(item.created_dt, item.sync_dt) + dt = max(created_dt, sync_dt) txts = [] txts.append("{}: {}
".format("Last update date", pretty_timestamp(dt))) txts.append("{}: {}
".format("Retries", - str(item.tries))) + str(tries))) txts.append("{}: {}
".format("Error message", - item.error)) + errors)) text_area = QtWidgets.QTextEdit("\n\n".join(txts)) text_area.setReadOnly(True) @@ -1162,7 +1198,7 @@ def _fix_size(self): column_name = self.model.headerData(column_idx, QtCore.Qt.Horizontal, - lib.HeaderNameRole) + HEADER_NAME_ROLE) button = self.filter_buttons.get(column_name) if not button: continue diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index d7c6c162e64..d9fdf338f03 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -405,7 +405,8 @@ def _assetschanged(self): self.data["state"]["assetIds"] = asset_ids # reset repre list - self._repres_widget.set_version_ids([]) + if self._repres_widget: + self._repres_widget.set_version_ids([]) def _subsetschanged(self): asset_ids = self.data["state"]["assetIds"] @@ -495,7 +496,8 @@ def _versionschanged(self): self._thumbnail_widget.set_thumbnail(thumbnail_docs) version_ids = [doc["_id"] for doc in version_docs or []] - self._repres_widget.set_version_ids(version_ids) + if self._repres_widget: + self._repres_widget.set_version_ids(version_ids) def _set_context(self, context, refresh=True): """Set the selection in the interface using a context. diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 6e9c7bf2207..d81fc11cf2d 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -15,6 +15,12 @@ from openpype.tools.utils import lib from openpype.modules import ModulesManager +from openpype.tools.utils.constants import ( + LOCAL_PROVIDER_ROLE, + REMOTE_PROVIDER_ROLE, + LOCAL_AVAILABILITY_ROLE, + REMOTE_AVAILABILITY_ROLE +) def is_filtering_recursible(): @@ -333,7 +339,6 @@ def set_version(self, index, version): repre_info = version_data.get("repre_info") if repre_info: item["repre_info"] = repre_info - item["repre_icon"] = version_data.get("repre_icon") def _fetch(self): asset_docs = self.dbcon.find( @@ -445,14 +450,16 @@ def _fetch(self): for _subset_id, doc in last_versions_by_subset_id.items(): version_ids.add(doc["_id"]) - site = self.active_site - query = self._repre_per_version_pipeline(list(version_ids), site) + query = self._repre_per_version_pipeline(list(version_ids), + self.active_site, + self.remote_site) repre_info = {} for doc in self.dbcon.aggregate(query): if self._doc_fetching_stop: return - doc["provider"] = self.active_provider + doc["active_provider"] = self.active_provider + doc["remote_provider"] = self.remote_provider repre_info[doc["_id"]] = doc self._doc_payload["repre_info_by_version_id"] = repre_info @@ -666,8 +673,8 @@ def data(self, index, role): if not index.isValid(): return + item = index.internalPointer() if role == self.SortDescendingRole: - item = index.internalPointer() if item.get("isGroup"): # Ensure groups be on top when sorting by descending order prefix = "2" @@ -683,7 +690,6 @@ def data(self, index, role): return prefix + order if role == self.SortAscendingRole: - item = index.internalPointer() if item.get("isGroup"): # Ensure groups be on top when sorting by ascending order prefix = "0" @@ -701,14 +707,12 @@ def data(self, index, role): if role == QtCore.Qt.DisplayRole: if index.column() == self.columns_index["family"]: # Show familyLabel instead of family - item = index.internalPointer() return item.get("familyLabel", None) elif role == QtCore.Qt.DecorationRole: # Add icon to subset column if index.column() == self.columns_index["subset"]: - item = index.internalPointer() if item.get("isGroup") or item.get("isMerged"): return item["icon"] else: @@ -716,20 +720,32 @@ def data(self, index, role): # Add icon to family column if index.column() == self.columns_index["family"]: - item = index.internalPointer() return item.get("familyIcon", None) - if index.column() == self.columns_index.get("repre_info"): - item = index.internalPointer() - return item.get("repre_icon", None) - elif role == QtCore.Qt.ForegroundRole: - item = index.internalPointer() version_doc = item.get("version_document") if version_doc and version_doc.get("type") == "hero_version": if not version_doc["is_from_latest"]: return self.not_last_hero_brush + elif role == LOCAL_AVAILABILITY_ROLE: + if not item.get("isGroup"): + return item.get("repre_info_local") + else: + return None + + elif role == REMOTE_AVAILABILITY_ROLE: + if not item.get("isGroup"): + return item.get("repre_info_remote") + else: + return None + + elif role == LOCAL_PROVIDER_ROLE: + return self.active_provider + + elif role == REMOTE_PROVIDER_ROLE: + return self.remote_provider + return super(SubsetsModel, self).data(index, role) def flags(self, index): @@ -759,19 +775,25 @@ def _get_last_repre_info(self, repre_info_by_version_id, last_version_id): return data def _get_repre_dict(self, repre_info): - """Returns icon and str representation of availability""" + """Returns str representation of availability""" data = {} if repre_info: repres_str = "{}/{}".format( - int(math.floor(float(repre_info['avail_repre']))), + int(math.floor(float(repre_info['avail_repre_local']))), + int(math.floor(float(repre_info['repre_count'])))) + + data["repre_info_local"] = repres_str + + repres_str = "{}/{}".format( + int(math.floor(float(repre_info['avail_repre_remote']))), int(math.floor(float(repre_info['repre_count'])))) - data["repre_info"] = repres_str - data["repre_icon"] = self.repre_icons.get(self.active_provider) + data["repre_info_remote"] = repres_str return data - def _repre_per_version_pipeline(self, version_ids, site): + def _repre_per_version_pipeline(self, version_ids, + active_site, remote_site): query = [ {"$match": {"parent": {"$in": version_ids}, "type": "representation", @@ -780,7 +802,13 @@ def _repre_per_version_pipeline(self, version_ids, site): {'$addFields': { 'order_local': { '$filter': {'input': '$files.sites', 'as': 'p', - 'cond': {'$eq': ['$$p.name', site]} + 'cond': {'$eq': ['$$p.name', active_site]} + }} + }}, + {'$addFields': { + 'order_remote': { + '$filter': {'input': '$files.sites', 'as': 'p', + 'cond': {'$eq': ['$$p.name', remote_site]} }} }}, {'$addFields': { @@ -795,19 +823,32 @@ def _repre_per_version_pipeline(self, version_ids, site): ]} ]}, 0]} }}, + {'$addFields': { + 'progress_remote': {"$arrayElemAt": [{ + '$cond': [{'$size': "$order_remote.progress"}, + "$order_remote.progress", + # if exists created_dt count is as available + {'$cond': [ + {'$size': "$order_remote.created_dt"}, + [1], + [0] + ]} + ]}, 0]} + }}, {'$group': { # first group by repre '_id': '$_id', 'parent': {'$first': '$parent'}, - 'files_count': {'$sum': 1}, - 'files_avail': {'$sum': "$progress_local"}, - 'avail_ratio': {'$first': { - '$divide': [{'$sum': "$progress_local"}, {'$sum': 1}]}} + 'avail_ratio_local': {'$first': { + '$divide': [{'$sum': "$progress_local"}, {'$sum': 1}]}}, + 'avail_ratio_remote': {'$first': { + '$divide': [{'$sum': "$progress_remote"}, {'$sum': 1}]}} }}, {'$group': { # second group by parent, eg version_id '_id': '$parent', 'repre_count': {'$sum': 1}, # total representations # fully available representation for site - 'avail_repre': {'$sum': "$avail_ratio"} + 'avail_repre_local': {'$sum': "$avail_ratio_local"}, + 'avail_repre_remote': {'$sum': "$avail_ratio_remote"}, }}, ] return query diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 4c075382aca..08b58eebbe7 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -31,6 +31,13 @@ ) from . import lib +from openpype.tools.utils.constants import ( + LOCAL_PROVIDER_ROLE, + REMOTE_PROVIDER_ROLE, + LOCAL_AVAILABILITY_ROLE, + REMOTE_AVAILABILITY_ROLE +) + class OverlayFrame(QtWidgets.QFrame): def __init__(self, label, parent): @@ -197,6 +204,10 @@ def __init__( column = model.Columns.index("time") view.setItemDelegateForColumn(column, time_delegate) + avail_delegate = AvailabilityDelegate(self.dbcon, view) + column = model.Columns.index("repre_info") + view.setItemDelegateForColumn(column, avail_delegate) + layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(top_bar_layout) @@ -1578,3 +1589,54 @@ def _load_subsets_by_loader(loader, subset_contexts, options, )) return error_info + + +class AvailabilityDelegate(QtWidgets.QStyledItemDelegate): + """ + Prints icons and downloaded representation ration for both sides. + """ + + def __init__(self, dbcon, parent=None): + super(AvailabilityDelegate, self).__init__(parent) + self.icons = tools_lib.get_repre_icons() + + def paint(self, painter, option, index): + super(AvailabilityDelegate, self).paint(painter, option, index) + option = QtWidgets.QStyleOptionViewItem(option) + option.showDecorationSelected = True + + provider_active = index.data(LOCAL_PROVIDER_ROLE) + provider_remote = index.data(REMOTE_PROVIDER_ROLE) + + availability_active = index.data(LOCAL_AVAILABILITY_ROLE) + availability_remote = index.data(REMOTE_AVAILABILITY_ROLE) + + if not availability_active or not availability_remote: # group lines + return + + idx = 0 + height = width = 24 + for value, provider in [(availability_active, provider_active), + (availability_remote, provider_remote)]: + icon = self.icons.get(provider) + if not icon: + continue + + pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(height, width))) + padding = 10 + (70 * idx) + point = QtCore.QPoint(option.rect.x() + padding, + option.rect.y() + + (option.rect.height() - pixmap.height()) / 2) + painter.drawPixmap(point, pixmap) + + text_rect = option.rect.translated(padding + width + 10, 0) + painter.drawText( + text_rect, + option.displayAlignment, + value + ) + + idx += 1 + + def displayText(self, value, locale): + pass diff --git a/openpype/tools/utils/constants.py b/openpype/tools/utils/constants.py index 0e940a55957..5b6f4126c99 100644 --- a/openpype/tools/utils/constants.py +++ b/openpype/tools/utils/constants.py @@ -8,3 +8,22 @@ TASK_NAME_ROLE = QtCore.Qt.UserRole + 301 TASK_TYPE_ROLE = QtCore.Qt.UserRole + 302 TASK_ORDER_ROLE = QtCore.Qt.UserRole + 403 + +LOCAL_PROVIDER_ROLE = QtCore.Qt.UserRole + 500 # provider of active site +REMOTE_PROVIDER_ROLE = QtCore.Qt.UserRole + 501 # provider of remote site +LOCAL_PROGRESS_ROLE = QtCore.Qt.UserRole + 502 # percentage downld on active +REMOTE_PROGRESS_ROLE = QtCore.Qt.UserRole + 503 # percentage upload on remote +LOCAL_AVAILABILITY_ROLE = QtCore.Qt.UserRole + 504 # ratio of presence active +REMOTE_AVAILABILITY_ROLE = QtCore.Qt.UserRole + 505 +LOCAL_DATE_ROLE = QtCore.Qt.UserRole + 506 # created_dt on active site +REMOTE_DATE_ROLE = QtCore.Qt.UserRole + 507 +LOCAL_FAILED_ROLE = QtCore.Qt.UserRole + 508 +REMOTE_FAILED_ROLE = QtCore.Qt.UserRole + 509 +HEADER_NAME_ROLE = QtCore.Qt.UserRole + 510 +EDIT_ICON_ROLE = QtCore.Qt.UserRole + 511 +STATUS_ROLE = QtCore.Qt.UserRole + 512 +PATH_ROLE = QtCore.Qt.UserRole + 513 +LOCAL_SITE_NAME_ROLE = QtCore.Qt.UserRole + 514 +REMOTE_SITE_NAME_ROLE = QtCore.Qt.UserRole + 515 +ERROR_ROLE = QtCore.Qt.UserRole + 516 +TRIES_ROLE = QtCore.Qt.UserRole + 517