From 2acf100f152442823154d5bcfd53a795a0f8e82f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Nov 2021 21:09:31 +0100 Subject: [PATCH 01/12] removed unused TasksModel and TasksProxyModel --- openpype/tools/utils/models.py | 160 --------------------------------- 1 file changed, 160 deletions(-) diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index c488743f36e..819eda00b4c 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -654,163 +654,3 @@ def is_filter_enabled(self): def set_filter_enabled(self, value): self._filter_enabled = value self.invalidateFilter() - - -class TasksModel(QtGui.QStandardItemModel): - """A model listing the tasks combined for a list of assets""" - def __init__(self, dbcon, parent=None): - super(TasksModel, self).__init__(parent=parent) - self.dbcon = dbcon - self._default_icon = qtawesome.icon( - "fa.male", - color=style.colors.default - ) - self._no_tasks_icon = qtawesome.icon( - "fa.exclamation-circle", - color=style.colors.mid - ) - self._cached_icons = {} - self._project_task_types = {} - - self._last_asset_id = None - - self.refresh() - - def refresh(self): - if self.dbcon.Session.get("AVALON_PROJECT"): - self._refresh_task_types() - self.set_asset_id(self._last_asset_id) - else: - self.clear() - - def _refresh_task_types(self): - # Get the project configured icons from database - project = self.dbcon.find_one( - {"type": "project"}, - {"config.tasks"} - ) - tasks = project["config"].get("tasks") or {} - self._project_task_types = tasks - - def _try_get_awesome_icon(self, icon_name): - icon = None - if icon_name: - try: - icon = qtawesome.icon( - "fa.{}".format(icon_name), - color=style.colors.default - ) - - except Exception: - pass - return icon - - def headerData(self, section, orientation, role): - # Show nice labels in the header - if ( - role == QtCore.Qt.DisplayRole - and orientation == QtCore.Qt.Horizontal - ): - if section == 0: - return "Tasks" - - return super(TasksModel, self).headerData(section, orientation, role) - - def _get_icon(self, task_icon, task_type_icon): - if task_icon in self._cached_icons: - return self._cached_icons[task_icon] - - icon = self._try_get_awesome_icon(task_icon) - if icon is not None: - self._cached_icons[task_icon] = icon - return icon - - if task_type_icon in self._cached_icons: - icon = self._cached_icons[task_type_icon] - self._cached_icons[task_icon] = icon - return icon - - icon = self._try_get_awesome_icon(task_type_icon) - if icon is None: - icon = self._default_icon - - self._cached_icons[task_icon] = icon - self._cached_icons[task_type_icon] = icon - - return icon - - def set_asset_id(self, asset_id): - asset_doc = None - if asset_id: - asset_doc = self.dbcon.find_one( - {"_id": asset_id}, - {"data.tasks": True} - ) - self.set_asset(asset_doc) - - def set_asset(self, asset_doc): - """Set assets to track by their database id - - Arguments: - asset_doc (dict): Asset document from MongoDB. - """ - self.clear() - - if not asset_doc: - self._last_asset_id = None - return - - self._last_asset_id = asset_doc["_id"] - - asset_tasks = asset_doc.get("data", {}).get("tasks") or {} - items = [] - for task_name, task_info in asset_tasks.items(): - task_icon = task_info.get("icon") - task_type = task_info.get("type") - task_order = task_info.get("order") - task_type_info = self._project_task_types.get(task_type) or {} - task_type_icon = task_type_info.get("icon") - icon = self._get_icon(task_icon, task_type_icon) - - label = "{} ({})".format(task_name, task_type or "type N/A") - item = QtGui.QStandardItem(label) - item.setData(task_name, TASK_NAME_ROLE) - item.setData(task_type, TASK_TYPE_ROLE) - item.setData(task_order, TASK_ORDER_ROLE) - item.setData(icon, QtCore.Qt.DecorationRole) - item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable) - items.append(item) - - if not items: - item = QtGui.QStandardItem("No task") - item.setData(self._no_tasks_icon, QtCore.Qt.DecorationRole) - item.setFlags(QtCore.Qt.NoItemFlags) - items.append(item) - - self.invisibleRootItem().appendRows(items) - - -class TasksProxyModel(QtCore.QSortFilterProxyModel): - def lessThan(self, x_index, y_index): - x_order = x_index.data(TASK_ORDER_ROLE) - y_order = y_index.data(TASK_ORDER_ROLE) - if x_order is not None and y_order is not None: - if x_order < y_order: - return True - if x_order > y_order: - return False - - elif x_order is None and y_order is not None: - return True - - elif y_order is None and x_order is not None: - return False - - x_name = x_index.data(QtCore.Qt.DisplayRole) - y_name = y_index.data(QtCore.Qt.DisplayRole) - if x_name == y_name: - return True - - if x_name == tuple(sorted((x_name, y_name)))[0]: - return True - return False From 3ac669d9a54da86151e8ac13f72ac49f93740f64 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Nov 2021 21:10:14 +0100 Subject: [PATCH 02/12] moved task roles to tasks_widget as it's only place where are used --- openpype/tools/utils/constants.py | 4 ---- openpype/tools/utils/models.py | 5 +---- openpype/tools/utils/tasks_widget.py | 10 +++++----- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/openpype/tools/utils/constants.py b/openpype/tools/utils/constants.py index 33bdf43c088..8f12c573211 100644 --- a/openpype/tools/utils/constants.py +++ b/openpype/tools/utils/constants.py @@ -5,10 +5,6 @@ PROJECT_NAME_ROLE = QtCore.Qt.UserRole + 101 PROJECT_IS_ACTIVE_ROLE = QtCore.Qt.UserRole + 102 -TASK_NAME_ROLE = QtCore.Qt.UserRole + 301 -TASK_TYPE_ROLE = QtCore.Qt.UserRole + 302 -TASK_ORDER_ROLE = QtCore.Qt.UserRole + 303 - 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 diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index 819eda00b4c..ffcdc7a820c 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -11,10 +11,7 @@ from .constants import ( PROJECT_IS_ACTIVE_ROLE, PROJECT_NAME_ROLE, - DEFAULT_PROJECT_LABEL, - TASK_ORDER_ROLE, - TASK_TYPE_ROLE, - TASK_NAME_ROLE + DEFAULT_PROJECT_LABEL ) log = logging.getLogger(__name__) diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py index 513402b455a..419e77c780f 100644 --- a/openpype/tools/utils/tasks_widget.py +++ b/openpype/tools/utils/tasks_widget.py @@ -4,11 +4,11 @@ from avalon.vendor import qtawesome from .views import DeselectableTreeView -from .constants import ( - TASK_ORDER_ROLE, - TASK_TYPE_ROLE, - TASK_NAME_ROLE -) + + +TASK_NAME_ROLE = QtCore.Qt.UserRole + 1 +TASK_TYPE_ROLE = QtCore.Qt.UserRole + 2 +TASK_ORDER_ROLE = QtCore.Qt.UserRole + 3 class TasksModel(QtGui.QStandardItemModel): From 145e335a48a75b29bc3bf2c494dff67d4607f89c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Nov 2021 21:33:22 +0100 Subject: [PATCH 03/12] added double clicked signal emitting --- openpype/tools/utils/assets_widget.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py index 9ebb62456f5..041bb1ef1c3 100644 --- a/openpype/tools/utils/assets_widget.py +++ b/openpype/tools/utils/assets_widget.py @@ -564,6 +564,8 @@ class AssetsWidget(QtWidgets.QWidget): refreshed = QtCore.Signal() # on view selection change selection_changed = QtCore.Signal() + # It was double clicked on view + double_clicked = QtCore.Signal() def __init__(self, dbcon, parent=None): super(AssetsWidget, self).__init__(parent=parent) @@ -618,6 +620,7 @@ def __init__(self, dbcon, parent=None): refresh_btn.clicked.connect(self.refresh) current_asset_btn.clicked.connect(self.set_current_session_asset) model.refreshed.connect(self._on_model_refresh) + view.doubleClicked.connect(self.double_clicked) self._current_asset_btn = current_asset_btn self._model = model From 85ae741d3d790cd826e7d0381caca01e62ba215b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Nov 2021 21:38:44 +0100 Subject: [PATCH 04/12] houdini usd has it's own dialog using new assets widget --- openpype/hosts/houdini/api/usd.py | 93 ++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 33 deletions(-) diff --git a/openpype/hosts/houdini/api/usd.py b/openpype/hosts/houdini/api/usd.py index 6f808779eab..a992f1d0823 100644 --- a/openpype/hosts/houdini/api/usd.py +++ b/openpype/hosts/houdini/api/usd.py @@ -3,9 +3,10 @@ import contextlib import logging -from Qt import QtCore, QtGui -from openpype.tools.utils.widgets import AssetWidget -from avalon import style, io +from Qt import QtWidgets, QtCore, QtGui +from avalon import io +from openpype import style +from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget from pxr import Sdf @@ -13,6 +14,60 @@ log = logging.getLogger(__name__) +class SelectAssetDialog(QtWidgets.QWidget): + """Frameless assets dialog to select asset with double click. + + Args: + parm: Parameter where selected asset name is set. + """ + def __init__(self, parm): + self.setWindowTitle("Pick Asset") + self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Popup) + + assets_widget = SingleSelectAssetsWidget(io, parent=self) + + layout = QtWidgets.QHBoxLayout(self) + layout.addWidget(assets_widget) + + assets_widget.double_clicked.connect(self._set_parameter) + self._assets_widget = assets_widget + self._parm = parm + + def _set_parameter(self): + name = self._assets_widget.get_selected_asset_name() + self._parm.set(name) + self.close() + + def _on_show(self): + pos = QtGui.QCursor.pos() + # Select the current asset if there is any + select_id = None + name = self._parm.eval() + if name: + db_asset = io.find_one( + {"name": name, "type": "asset"}, + {"_id": True} + ) + if db_asset: + select_id = db_asset["_id"] + + # Set stylesheet + self.setStyleSheet(style.load_stylesheet()) + # Refresh assets (is threaded) + self._assets_widget.refresh() + # Select asset - must be done after refresh + if select_id is not None: + self._assets_widget.select_asset(select_id) + + # Show cursor (top right of window) near cursor + self.resize(250, 400) + self.move(self.mapFromGlobal(pos) - QtCore.QPoint(self.width(), 0)) + + def showEvent(self, event): + super(SelectAssetDialog, self).showEvent(event) + self._on_show() + + def pick_asset(node): """Show a user interface to select an Asset in the project @@ -21,43 +76,15 @@ def pick_asset(node): """ - pos = QtGui.QCursor.pos() - parm = node.parm("asset_name") if not parm: log.error("Node has no 'asset' parameter: %s", node) return - # Construct the AssetWidget as a frameless popup so it automatically + # Construct a frameless popup so it automatically # closes when clicked outside of it. global tool - tool = AssetWidget(io) - tool.setContentsMargins(5, 5, 5, 5) - tool.setWindowTitle("Pick Asset") - tool.setStyleSheet(style.load_stylesheet()) - tool.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Popup) - tool.refresh() - - # Select the current asset if there is any - name = parm.eval() - if name: - db_asset = io.find_one({"name": name, "type": "asset"}) - if db_asset: - silo = db_asset.get("silo") - if silo: - tool.set_silo(silo) - tool.select_assets([name], expand=True) - - # Show cursor (top right of window) near cursor - tool.resize(250, 400) - tool.move(tool.mapFromGlobal(pos) - QtCore.QPoint(tool.width(), 0)) - - def set_parameter_callback(index): - name = index.data(tool.model.DocumentRole)["name"] - parm.set(name) - tool.close() - - tool.view.doubleClicked.connect(set_parameter_callback) + tool = SelectAssetDialog(parm) tool.show() From ba5fe7674656a4a70dc24d679ef5eb469e2311c0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Nov 2021 21:40:05 +0100 Subject: [PATCH 05/12] removed unused AssetWidget --- openpype/tools/utils/widgets.py | 287 -------------------------------- 1 file changed, 287 deletions(-) diff --git a/openpype/tools/utils/widgets.py b/openpype/tools/utils/widgets.py index 493255071da..8346db211bf 100644 --- a/openpype/tools/utils/widgets.py +++ b/openpype/tools/utils/widgets.py @@ -38,293 +38,6 @@ def showEvent(self, event): self.setPalette(filter_palette) -class AssetWidget(QtWidgets.QWidget): - """A Widget to display a tree of assets with filter - - To list the assets of the active project: - >>> # widget = AssetWidget() - >>> # widget.refresh() - >>> # widget.show() - - """ - - refresh_triggered = QtCore.Signal() # on model refresh - refreshed = QtCore.Signal() - selection_changed = QtCore.Signal() # on view selection change - current_changed = QtCore.Signal() # on view current index change - - def __init__(self, dbcon, multiselection=False, parent=None): - super(AssetWidget, self).__init__(parent=parent) - - self.dbcon = dbcon - - # Tree View - model = AssetModel(dbcon=self.dbcon, parent=self) - proxy = RecursiveSortFilterProxyModel() - proxy.setSourceModel(model) - proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) - - view = AssetsView(self) - view.setModel(proxy) - if multiselection: - asset_delegate = AssetDelegate() - view.setSelectionMode(view.ExtendedSelection) - view.setItemDelegate(asset_delegate) - - icon = qtawesome.icon("fa.arrow-down", color=style.colors.light) - set_current_asset_btn = QtWidgets.QPushButton(icon, "") - set_current_asset_btn.setToolTip("Go to Asset from current Session") - # Hide by default - set_current_asset_btn.setVisible(False) - - icon = qtawesome.icon("fa.refresh", color=style.colors.light) - refresh = QtWidgets.QPushButton(icon, "", parent=self) - refresh.setToolTip("Refresh items") - - filter_input = QtWidgets.QLineEdit(self) - filter_input.setPlaceholderText("Filter assets..") - - # Header - header_layout = QtWidgets.QHBoxLayout() - header_layout.addWidget(filter_input) - header_layout.addWidget(set_current_asset_btn) - header_layout.addWidget(refresh) - - # Layout - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(4) - layout.addLayout(header_layout) - layout.addWidget(view) - - # Signals/Slots - filter_input.textChanged.connect(proxy.setFilterFixedString) - - selection = view.selectionModel() - selection.selectionChanged.connect(self.selection_changed) - selection.currentChanged.connect(self.current_changed) - refresh.clicked.connect(self.refresh) - set_current_asset_btn.clicked.connect(self.set_current_session_asset) - - self.set_current_asset_btn = set_current_asset_btn - self.model = model - self.proxy = proxy - self.view = view - - self.model_selection = {} - - def set_current_asset_btn_visibility(self, visible=None): - """Hide set current asset button. - - Not all tools support using of current context asset. - """ - if visible is None: - visible = not self.set_current_asset_btn.isVisible() - self.set_current_asset_btn.setVisible(visible) - - def _refresh_model(self): - # Store selection - self._store_model_selection() - time_start = time.time() - - self.set_loading_state( - loading=True, - empty=True - ) - - def on_refreshed(has_item): - self.set_loading_state(loading=False, empty=not has_item) - self._restore_model_selection() - self.model.refreshed.disconnect() - self.refreshed.emit() - print("Duration: %.3fs" % (time.time() - time_start)) - - # Connect to signal - self.model.refreshed.connect(on_refreshed) - # Trigger signal before refresh is called - self.refresh_triggered.emit() - # Refresh model - self.model.refresh() - - def refresh(self): - self._refresh_model() - - def get_active_asset(self): - """Return the asset item of the current selection.""" - current = self.view.currentIndex() - return current.data(self.model.ItemRole) - - def get_active_asset_document(self): - """Return the asset document of the current selection.""" - current = self.view.currentIndex() - return current.data(self.model.DocumentRole) - - def get_active_index(self): - return self.view.currentIndex() - - def get_selected_assets(self): - """Return the documents of selected assets.""" - selection = self.view.selectionModel() - rows = selection.selectedRows() - assets = [row.data(self.model.DocumentRole) for row in rows] - - # NOTE: skip None object assumed they are silo (backwards comp.) - return [asset for asset in assets if asset] - - def select_assets(self, assets, expand=True, key="name"): - """Select assets by item key. - - Args: - assets (list): List of asset values that can be found under - specified `key` - expand (bool): Whether to also expand to the asset in the view - key (string): Key that specifies where to look for `assets` values - - Returns: - None - - Default `key` is "name" in that case `assets` should contain single - asset name or list of asset names. (It is good idea to use "_id" key - instead of name in that case `assets` must contain `ObjectId` object/s) - It is expected that each value in `assets` will be found only once. - If the filters according to the `key` and `assets` correspond to - the more asset, only the first found will be selected. - - """ - - if not isinstance(assets, (tuple, list)): - assets = [assets] - - # convert to list - tuple cant be modified - assets = set(assets) - - # Clear selection - selection_model = self.view.selectionModel() - selection_model.clearSelection() - - # Select - mode = selection_model.Select | selection_model.Rows - for index in lib.iter_model_rows( - self.proxy, column=0, include_root=False - ): - # stop iteration if there are no assets to process - if not assets: - break - - value = index.data(self.model.ItemRole).get(key) - if value not in assets: - continue - - # Remove processed asset - assets.discard(value) - - selection_model.select(index, mode) - if expand: - # Expand parent index - self.view.expand(self.proxy.parent(index)) - - # Set the currently active index - self.view.setCurrentIndex(index) - - def set_loading_state(self, loading, empty): - if self.view.is_loading != loading: - if loading: - self.view.spinner.repaintNeeded.connect( - self.view.viewport().update - ) - else: - self.view.spinner.repaintNeeded.disconnect() - - self.view.is_loading = loading - self.view.is_empty = empty - - def _store_model_selection(self): - index = self.view.currentIndex() - current = None - if index and index.isValid(): - current = index.data(self.model.ObjectIdRole) - - expanded = set() - model = self.view.model() - for index in lib.iter_model_rows( - model, column=0, include_root=False - ): - if self.view.isExpanded(index): - value = index.data(self.model.ObjectIdRole) - expanded.add(value) - - selection_model = self.view.selectionModel() - - selected = None - selected_rows = selection_model.selectedRows() - if selected_rows: - selected = set( - row.data(self.model.ObjectIdRole) - for row in selected_rows - ) - - self.model_selection = { - "expanded": expanded, - "selected": selected, - "current": current - } - - def _restore_model_selection(self): - model = self.view.model() - not_set = object() - expanded = self.model_selection.pop("expanded", not_set) - selected = self.model_selection.pop("selected", not_set) - current = self.model_selection.pop("current", not_set) - - if ( - expanded is not_set - or selected is not_set - or current is not_set - ): - return - - if expanded: - for index in lib.iter_model_rows( - model, column=0, include_root=False - ): - is_expanded = index.data(self.model.ObjectIdRole) in expanded - self.view.setExpanded(index, is_expanded) - - if not selected and not current: - self.set_current_session_asset() - return - - current_index = None - selected_indexes = [] - # Go through all indices, select the ones with similar data - for index in lib.iter_model_rows( - model, column=0, include_root=False - ): - object_id = index.data(self.model.ObjectIdRole) - if object_id in selected: - selected_indexes.append(index) - - if not current_index and object_id == current: - current_index = index - - if current_index: - self.view.setCurrentIndex(current_index) - - if not selected_indexes: - return - selection_model = self.view.selectionModel() - flags = selection_model.Select | selection_model.Rows - for index in selected_indexes: - # Ensure item is visible - self.view.scrollTo(index) - selection_model.select(index, flags) - - def set_current_session_asset(self): - asset_name = self.dbcon.Session.get("AVALON_ASSET") - if asset_name: - self.select_assets([asset_name]) - - class OptionalMenu(QtWidgets.QMenu): """A subclass of `QtWidgets.QMenu` to work with `OptionalAction` From e6a396ba3f5d4f61b4f98aadefeed32bae051508 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Nov 2021 21:46:14 +0100 Subject: [PATCH 06/12] removed unused asset related classes --- openpype/tools/utils/delegates.py | 172 +------------------ openpype/tools/utils/models.py | 277 ------------------------------ openpype/tools/utils/views.py | 23 --- openpype/tools/utils/widgets.py | 4 +- 4 files changed, 2 insertions(+), 474 deletions(-) diff --git a/openpype/tools/utils/delegates.py b/openpype/tools/utils/delegates.py index 96353c44c69..1caed732d85 100644 --- a/openpype/tools/utils/delegates.py +++ b/openpype/tools/utils/delegates.py @@ -8,10 +8,7 @@ from avalon.lib import HeroVersionType from openpype.style import get_objected_colors -from .models import ( - AssetModel, - TreeModel -) +from .models import TreeModel from . import lib if Qt.__binding__ == "PySide": @@ -22,173 +19,6 @@ log = logging.getLogger(__name__) -class AssetDelegate(QtWidgets.QItemDelegate): - bar_height = 3 - - def __init__(self, *args, **kwargs): - super(AssetDelegate, self).__init__(*args, **kwargs) - asset_view_colors = get_objected_colors()["loader"]["asset-view"] - self._selected_color = ( - asset_view_colors["selected"].get_qcolor() - ) - self._hover_color = ( - asset_view_colors["hover"].get_qcolor() - ) - self._selected_hover_color = ( - asset_view_colors["selected-hover"].get_qcolor() - ) - - def sizeHint(self, option, index): - result = super(AssetDelegate, self).sizeHint(option, index) - height = result.height() - result.setHeight(height + self.bar_height) - - return result - - def paint(self, painter, option, index): - # Qt4 compat - if Qt.__binding__ in ("PySide", "PyQt4"): - option = QStyleOptionViewItemV4(option) - - painter.save() - - item_rect = QtCore.QRect(option.rect) - item_rect.setHeight(option.rect.height() - self.bar_height) - - subset_colors = index.data(AssetModel.subsetColorsRole) - subset_colors_width = 0 - if subset_colors: - subset_colors_width = option.rect.width() / len(subset_colors) - - subset_rects = [] - counter = 0 - for subset_c in subset_colors: - new_color = None - new_rect = None - if subset_c: - new_color = QtGui.QColor(*subset_c) - - new_rect = QtCore.QRect( - option.rect.left() + (counter * subset_colors_width), - option.rect.top() + ( - option.rect.height() - self.bar_height - ), - subset_colors_width, - self.bar_height - ) - subset_rects.append((new_color, new_rect)) - counter += 1 - - # Background - if option.state & QtWidgets.QStyle.State_Selected: - if len(subset_colors) == 0: - item_rect.setTop(item_rect.top() + (self.bar_height / 2)) - - if option.state & QtWidgets.QStyle.State_MouseOver: - bg_color = self._selected_hover_color - else: - bg_color = self._selected_color - else: - item_rect.setTop(item_rect.top() + (self.bar_height / 2)) - if option.state & QtWidgets.QStyle.State_MouseOver: - bg_color = self._hover_color - else: - bg_color = QtGui.QColor() - bg_color.setAlpha(0) - - # When not needed to do a rounded corners (easier and without - # painter restore): - # painter.fillRect( - # item_rect, - # QtGui.QBrush(bg_color) - # ) - pen = painter.pen() - pen.setStyle(QtCore.Qt.NoPen) - pen.setWidth(0) - painter.setPen(pen) - painter.setBrush(QtGui.QBrush(bg_color)) - painter.drawRoundedRect(option.rect, 3, 3) - - if option.state & QtWidgets.QStyle.State_Selected: - for color, subset_rect in subset_rects: - if not color or not subset_rect: - continue - painter.fillRect(subset_rect, QtGui.QBrush(color)) - - painter.restore() - painter.save() - - # Icon - icon_index = index.model().index( - index.row(), index.column(), index.parent() - ) - # - Default icon_rect if not icon - icon_rect = QtCore.QRect( - item_rect.left(), - item_rect.top(), - # To make sure it's same size all the time - option.rect.height() - self.bar_height, - option.rect.height() - self.bar_height - ) - icon = index.model().data(icon_index, QtCore.Qt.DecorationRole) - - if icon: - mode = QtGui.QIcon.Normal - if not (option.state & QtWidgets.QStyle.State_Enabled): - mode = QtGui.QIcon.Disabled - elif option.state & QtWidgets.QStyle.State_Selected: - mode = QtGui.QIcon.Selected - - if isinstance(icon, QtGui.QPixmap): - icon = QtGui.QIcon(icon) - option.decorationSize = icon.size() / icon.devicePixelRatio() - - elif isinstance(icon, QtGui.QColor): - pixmap = QtGui.QPixmap(option.decorationSize) - pixmap.fill(icon) - icon = QtGui.QIcon(pixmap) - - elif isinstance(icon, QtGui.QImage): - icon = QtGui.QIcon(QtGui.QPixmap.fromImage(icon)) - option.decorationSize = icon.size() / icon.devicePixelRatio() - - elif isinstance(icon, QtGui.QIcon): - state = QtGui.QIcon.Off - if option.state & QtWidgets.QStyle.State_Open: - state = QtGui.QIcon.On - actualSize = option.icon.actualSize( - option.decorationSize, mode, state - ) - option.decorationSize = QtCore.QSize( - min(option.decorationSize.width(), actualSize.width()), - min(option.decorationSize.height(), actualSize.height()) - ) - - state = QtGui.QIcon.Off - if option.state & QtWidgets.QStyle.State_Open: - state = QtGui.QIcon.On - - icon.paint( - painter, icon_rect, - QtCore.Qt.AlignLeft, mode, state - ) - - # Text - text_rect = QtCore.QRect( - icon_rect.left() + icon_rect.width() + 2, - item_rect.top(), - item_rect.width(), - item_rect.height() - ) - - painter.drawText( - text_rect, QtCore.Qt.AlignVCenter, - index.data(QtCore.Qt.DisplayRole) - ) - - painter.restore() - - class VersionDelegate(QtWidgets.QStyledItemDelegate): """A delegate that display version integer formatted as version string.""" diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index ffcdc7a820c..94694483ab6 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -200,283 +200,6 @@ def add_child(self, child): self._children.append(child) -class AssetModel(TreeModel): - """A model listing assets in the silo in the active project. - - The assets are displayed in a treeview, they are visually parented by - a `visualParent` field in the database containing an `_id` to a parent - asset. - - """ - - Columns = ["label"] - Name = 0 - Deprecated = 2 - ObjectId = 3 - - DocumentRole = QtCore.Qt.UserRole + 2 - ObjectIdRole = QtCore.Qt.UserRole + 3 - subsetColorsRole = QtCore.Qt.UserRole + 4 - - doc_fetched = QtCore.Signal(bool) - refreshed = QtCore.Signal(bool) - - # Asset document projection - asset_projection = { - "type": 1, - "schema": 1, - "name": 1, - "silo": 1, - "data.visualParent": 1, - "data.label": 1, - "data.tags": 1, - "data.icon": 1, - "data.color": 1, - "data.deprecated": 1 - } - - def __init__(self, dbcon=None, parent=None, asset_projection=None): - super(AssetModel, self).__init__(parent=parent) - if dbcon is None: - dbcon = io - self.dbcon = dbcon - self.asset_colors = {} - - # Projections for Mongo queries - # - let ability to modify them if used in tools that require more than - # defaults - if asset_projection: - self.asset_projection = asset_projection - - self.asset_projection = asset_projection - - self._doc_fetching_thread = None - self._doc_fetching_stop = False - self._doc_payload = {} - - self.doc_fetched.connect(self.on_doc_fetched) - - self.refresh() - - def _add_hierarchy(self, assets, parent=None, silos=None): - """Add the assets that are related to the parent as children items. - - This method does *not* query the database. These instead are queried - in a single batch upfront as an optimization to reduce database - queries. Resulting in up to 10x speed increase. - - Args: - assets (dict): All assets in the currently active silo stored - by key/value - - Returns: - None - - """ - # Reset colors - self.asset_colors = {} - - if silos: - # WARNING: Silo item "_id" is set to silo value - # mainly because GUI issue with perserve selection and expanded row - # and because of easier hierarchy parenting (in "assets") - for silo in silos: - item = Item({ - "_id": silo, - "name": silo, - "label": silo, - "type": "silo" - }) - self.add_child(item, parent=parent) - self._add_hierarchy(assets, parent=item) - - parent_id = parent["_id"] if parent else None - current_assets = assets.get(parent_id, list()) - - for asset in current_assets: - # get label from data, otherwise use name - data = asset.get("data", {}) - label = data.get("label", asset["name"]) - tags = data.get("tags", []) - - # store for the asset for optimization - deprecated = "deprecated" in tags - - item = Item({ - "_id": asset["_id"], - "name": asset["name"], - "label": label, - "type": asset["type"], - "tags": ", ".join(tags), - "deprecated": deprecated, - "_document": asset - }) - self.add_child(item, parent=parent) - - # Add asset's children recursively if it has children - if asset["_id"] in assets: - self._add_hierarchy(assets, parent=item) - - self.asset_colors[asset["_id"]] = [] - - def on_doc_fetched(self, was_stopped): - if was_stopped: - self.stop_fetch_thread() - return - - self.beginResetModel() - - assets_by_parent = self._doc_payload.get("assets_by_parent") - silos = self._doc_payload.get("silos") - if assets_by_parent is not None: - # Build the hierarchical tree items recursively - self._add_hierarchy( - assets_by_parent, - parent=None, - silos=silos - ) - - self.endResetModel() - - has_content = bool(assets_by_parent) or bool(silos) - self.refreshed.emit(has_content) - - self.stop_fetch_thread() - - def fetch(self): - self._doc_payload = self._fetch() or {} - # Emit doc fetched only if was not stopped - self.doc_fetched.emit(self._doc_fetching_stop) - - def _fetch(self): - if not self.dbcon.Session.get("AVALON_PROJECT"): - return - - project_doc = self.dbcon.find_one( - {"type": "project"}, - {"_id": True} - ) - if not project_doc: - return - - # Get all assets sorted by name - db_assets = self.dbcon.find( - {"type": "asset"}, - self.asset_projection - ).sort("name", 1) - - # Group the assets by their visual parent's id - assets_by_parent = collections.defaultdict(list) - for asset in db_assets: - if self._doc_fetching_stop: - return - parent_id = asset.get("data", {}).get("visualParent") - assets_by_parent[parent_id].append(asset) - - return { - "assets_by_parent": assets_by_parent, - "silos": None - } - - def stop_fetch_thread(self): - if self._doc_fetching_thread is not None: - self._doc_fetching_stop = True - while self._doc_fetching_thread.isRunning(): - time.sleep(0.001) - self._doc_fetching_thread = None - - def refresh(self, force=False): - """Refresh the data for the model.""" - # Skip fetch if there is already other thread fetching documents - if self._doc_fetching_thread is not None: - if not force: - return - self.stop_fetch_thread() - - # Clear model items - self.clear() - - # Fetch documents from mongo - # Restart payload - self._doc_payload = {} - self._doc_fetching_stop = False - self._doc_fetching_thread = lib.create_qthread(self.fetch) - self._doc_fetching_thread.start() - - def flags(self, index): - return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable - - def setData(self, index, value, role=QtCore.Qt.EditRole): - if not index.isValid(): - return False - - if role == self.subsetColorsRole: - asset_id = index.data(self.ObjectIdRole) - self.asset_colors[asset_id] = value - - if Qt.__binding__ in ("PyQt4", "PySide"): - self.dataChanged.emit(index, index) - else: - self.dataChanged.emit(index, index, [role]) - - return True - - return super(AssetModel, self).setData(index, value, role) - - def data(self, index, role): - if not index.isValid(): - return - - item = index.internalPointer() - if role == QtCore.Qt.DecorationRole: - column = index.column() - if column == self.Name: - # Allow a custom icon and custom icon color to be defined - data = item.get("_document", {}).get("data", {}) - icon = data.get("icon", None) - if icon is None and item.get("type") == "silo": - icon = "database" - color = data.get("color", style.colors.default) - - if icon is None: - # Use default icons if no custom one is specified. - # If it has children show a full folder, otherwise - # show an open folder - has_children = self.rowCount(index) > 0 - icon = "folder" if has_children else "folder-o" - - # Make the color darker when the asset is deprecated - if item.get("deprecated", False): - color = QtGui.QColor(color).darker(250) - - try: - key = "fa.{0}".format(icon) # font-awesome key - icon = qtawesome.icon(key, color=color) - return icon - except Exception as exception: - # Log an error message instead of erroring out completely - # when the icon couldn't be created (e.g. invalid name) - log.error(exception) - - return - - if role == QtCore.Qt.ForegroundRole: # font color - if "deprecated" in item.get("tags", []): - return QtGui.QColor(style.colors.light).darker(250) - - if role == self.ObjectIdRole: - return item.get("_id", None) - - if role == self.DocumentRole: - return item.get("_document", None) - - if role == self.subsetColorsRole: - asset_id = item.get("_id", None) - return self.asset_colors.get(asset_id) or [] - - return super(AssetModel, self).data(index, role) - - class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel): """Filters to the regex if any of the children matches allow parent""" def filterAcceptsRow(self, row, parent): diff --git a/openpype/tools/utils/views.py b/openpype/tools/utils/views.py index 89e49fe142d..97aaf622a4c 100644 --- a/openpype/tools/utils/views.py +++ b/openpype/tools/utils/views.py @@ -61,26 +61,3 @@ def paintEvent(self, event): self.paint_empty(event) else: super(TreeViewSpinner, self).paintEvent(event) - - -class AssetsView(TreeViewSpinner, DeselectableTreeView): - """Item view. - This implements a context menu. - """ - - def __init__(self, parent=None): - super(AssetsView, self).__init__(parent) - self.setIndentation(15) - self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) - self.setHeaderHidden(True) - - def mousePressEvent(self, event): - index = self.indexAt(event.pos()) - if not index.isValid(): - modifiers = QtWidgets.QApplication.keyboardModifiers() - if modifiers == QtCore.Qt.ShiftModifier: - return - elif modifiers == QtCore.Qt.ControlModifier: - return - - super(AssetsView, self).mousePressEvent(event) diff --git a/openpype/tools/utils/widgets.py b/openpype/tools/utils/widgets.py index 8346db211bf..ea0d23470c7 100644 --- a/openpype/tools/utils/widgets.py +++ b/openpype/tools/utils/widgets.py @@ -9,9 +9,7 @@ from avalon import style from openpype.style import get_objected_colors -from .models import AssetModel, RecursiveSortFilterProxyModel -from .views import AssetsView -from .delegates import AssetDelegate +from .models import RecursiveSortFilterProxyModel log = logging.getLogger(__name__) From e4dff2f17192c461795df397665a2bbb1f100a35 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Nov 2021 21:52:02 +0100 Subject: [PATCH 07/12] removed unused imports --- openpype/tools/utils/models.py | 2 -- openpype/tools/utils/widgets.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index 94694483ab6..df3eee41a22 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -1,7 +1,5 @@ import re -import time import logging -import collections import Qt from Qt import QtCore, QtGui diff --git a/openpype/tools/utils/widgets.py b/openpype/tools/utils/widgets.py index ea0d23470c7..cea9f4ea3ec 100644 --- a/openpype/tools/utils/widgets.py +++ b/openpype/tools/utils/widgets.py @@ -1,12 +1,10 @@ import logging -import time from . import lib from Qt import QtWidgets, QtCore, QtGui from avalon.vendor import qtawesome, qargparse -from avalon import style from openpype.style import get_objected_colors from .models import RecursiveSortFilterProxyModel From 7e7a9c42cb5adbf6020811c8d4117642056a4bf7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Nov 2021 21:52:26 +0100 Subject: [PATCH 08/12] use right import of qt_app_context function --- openpype/tools/assetcreator/app.py | 14 +++++--------- openpype/tools/libraryloader/app.py | 2 +- openpype/tools/loader/app.py | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/openpype/tools/assetcreator/app.py b/openpype/tools/assetcreator/app.py index 5c2553e81ee..58697e8aa3a 100644 --- a/openpype/tools/assetcreator/app.py +++ b/openpype/tools/assetcreator/app.py @@ -1,16 +1,12 @@ import os import sys -import json from subprocess import Popen -try: - import ftrack_api_old as ftrack_api -except Exception: - import ftrack_api + +import ftrack_api +from Qt import QtWidgets, QtCore from openpype.api import get_current_project_settings -from openpype import lib as pypelib -from avalon.vendor.Qt import QtWidgets, QtCore +from openpype.tools.utils.lib import qt_app_context from avalon import io, api, style, schema -from avalon.tools import lib as parentlib from . import widget, model module = sys.modules[__name__] @@ -630,7 +626,7 @@ def show(parent=None, debug=False, context=None): if debug is True: io.install() - with parentlib.application(): + with qt_app_context(): window = Window(parent, context) window.setStyleSheet(style.load_stylesheet()) window.show() diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index d0d07a316cc..6ad5bfa16a5 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -555,7 +555,7 @@ def show( import traceback sys.excepthook = lambda typ, val, tb: traceback.print_last() - with tools_lib.application(): + with tools_lib.qt_app_context(): window = LibraryLoaderWindow( parent, icon, show_projects, show_libraries ) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 0c7844c4fb8..b6becc3e9fe 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -631,7 +631,7 @@ def show(debug=False, parent=None, use_context=False): api.Session["AVALON_PROJECT"] = any_project["name"] module.project = any_project["name"] - with lib.application(): + with lib.qt_app_context(): window = LoaderWindow(parent) window.show() From 93225171f7a84907b733862e83b98975292c7d91 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Nov 2021 22:27:02 +0100 Subject: [PATCH 09/12] removed icon argument from library loader --- openpype/tools/libraryloader/app.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index 6ad5bfa16a5..d030aa903db 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -31,7 +31,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): message_timeout = 5000 def __init__( - self, parent=None, icon=None, show_projects=False, show_libraries=True + self, parent=None, show_projects=False, show_libraries=True ): super(LibraryLoaderWindow, self).__init__(parent) @@ -517,10 +517,7 @@ def closeEvent(self, event): return super(LibraryLoaderWindow, self).closeEvent(event) -def show( - debug=False, parent=None, icon=None, - show_projects=False, show_libraries=True -): +def show(debug=False, parent=None, show_projects=False, show_libraries=True): """Display Loader GUI Arguments: @@ -557,7 +554,7 @@ def show( with tools_lib.qt_app_context(): window = LibraryLoaderWindow( - parent, icon, show_projects, show_libraries + parent, show_projects, show_libraries ) window.show() From 975df8b4bf638f65b25cce361cef67fa5c47c308 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Nov 2021 22:31:32 +0100 Subject: [PATCH 10/12] use PlaceholderLineEdit in subsets widget --- openpype/tools/loader/widgets.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index c7138d8f726..db86d1cd4e1 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -16,7 +16,10 @@ VersionDelegate, PrettyTimeDelegate ) -from openpype.tools.utils.widgets import OptionalMenu +from openpype.tools.utils.widgets import ( + OptionalMenu, + PlaceholderLineEdit +) from openpype.tools.utils.views import ( TreeViewSpinner, DeselectableTreeView @@ -175,7 +178,7 @@ def __init__( family_proxy = FamiliesFilterProxyModel() family_proxy.setSourceModel(proxy) - subset_filter = QtWidgets.QLineEdit(self) + subset_filter = PlaceholderLineEdit(self) subset_filter.setPlaceholderText("Filter subsets..") group_checkbox = QtWidgets.QCheckBox("Enable Grouping", self) From ff3e6a8a1a687714c0212764e43f8c5645f5d98d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Nov 2021 22:39:17 +0100 Subject: [PATCH 11/12] removed unused imports --- openpype/tools/utils/widgets.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/tools/utils/widgets.py b/openpype/tools/utils/widgets.py index cea9f4ea3ec..ea80636d1a3 100644 --- a/openpype/tools/utils/widgets.py +++ b/openpype/tools/utils/widgets.py @@ -1,14 +1,10 @@ import logging -from . import lib - from Qt import QtWidgets, QtCore, QtGui -from avalon.vendor import qtawesome, qargparse +from avalon.vendor import qtawesome, qargparse from openpype.style import get_objected_colors -from .models import RecursiveSortFilterProxyModel - log = logging.getLogger(__name__) From 96e30dd989453b6ba729c85ebfd049db90d17c33 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 25 Nov 2021 12:27:26 +0100 Subject: [PATCH 12/12] fix check of thumbnail id --- openpype/tools/loader/widgets.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index db86d1cd4e1..ea45fd43646 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -813,8 +813,9 @@ def set_thumbnail(self, doc_id=None): {"_id": doc_id}, {"data.thumbnail_id"} ) - - thumbnail_id = doc.get("data", {}).get("thumbnail_id") + thumbnail_id = None + if doc: + thumbnail_id = doc.get("data", {}).get("thumbnail_id") if thumbnail_id == self.current_thumb_id: if self.current_thumbnail is None: self.set_pixmap()