mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'feature/context_dialog_selection' into feature/royalrender-integration
This commit is contained in:
commit
8a2806beef
9 changed files with 809 additions and 146 deletions
|
|
@ -282,6 +282,34 @@ def projectmanager():
|
|||
PypeCommands().launch_project_manager()
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("output_path")
|
||||
@click.option("--project", help="Define project context")
|
||||
@click.option("--asset", help="Define asset in project (project must be set)")
|
||||
@click.option(
|
||||
"--strict",
|
||||
is_flag=True,
|
||||
help="Full context must be set otherwise dialog can't be closed."
|
||||
)
|
||||
def contextselection(
|
||||
output_path,
|
||||
project,
|
||||
asset,
|
||||
strict
|
||||
):
|
||||
"""Show Qt dialog to select context.
|
||||
|
||||
Context is project name, asset name and task name. The result is stored
|
||||
into json file which path is passed in first argument.
|
||||
"""
|
||||
PypeCommands.contextselection(
|
||||
output_path,
|
||||
project,
|
||||
asset,
|
||||
strict
|
||||
)
|
||||
|
||||
|
||||
@main.command(
|
||||
context_settings=dict(
|
||||
ignore_unknown_options=True,
|
||||
|
|
|
|||
|
|
@ -310,6 +310,12 @@ class PypeCommands:
|
|||
|
||||
project_manager.main()
|
||||
|
||||
@staticmethod
|
||||
def contextselection(output_path, project_name, asset_name, strict):
|
||||
from openpype.tools.context_dialog import main
|
||||
|
||||
main(output_path, project_name, asset_name, strict)
|
||||
|
||||
def texture_copy(self, project, asset, path):
|
||||
pass
|
||||
|
||||
|
|
|
|||
10
openpype/tools/context_dialog/__init__.py
Normal file
10
openpype/tools/context_dialog/__init__.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from .window import (
|
||||
ContextDialog,
|
||||
main
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
"ContextDialog",
|
||||
"main"
|
||||
)
|
||||
423
openpype/tools/context_dialog/window.py
Normal file
423
openpype/tools/context_dialog/window.py
Normal file
|
|
@ -0,0 +1,423 @@
|
|||
import os
|
||||
import json
|
||||
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
from avalon.api import AvalonMongoDB
|
||||
|
||||
from openpype import style
|
||||
from openpype.tools.utils.lib import center_window
|
||||
from openpype.tools.utils.widgets import AssetWidget
|
||||
from openpype.tools.utils.constants import (
|
||||
TASK_NAME_ROLE,
|
||||
PROJECT_NAME_ROLE
|
||||
)
|
||||
from openpype.tools.utils.models import (
|
||||
ProjectModel,
|
||||
ProjectSortFilterProxy,
|
||||
TasksModel,
|
||||
TasksProxyModel
|
||||
)
|
||||
|
||||
|
||||
class ContextDialog(QtWidgets.QDialog):
|
||||
"""Dialog to select a context.
|
||||
|
||||
Context has 3 parts:
|
||||
- Project
|
||||
- Aseet
|
||||
- Task
|
||||
|
||||
It is possible to predefine project and asset. In that case their widgets
|
||||
will have passed preselected values and will be disabled.
|
||||
"""
|
||||
def __init__(self, parent=None):
|
||||
super(ContextDialog, self).__init__(parent)
|
||||
|
||||
self.setWindowTitle("Select Context")
|
||||
self.setWindowIcon(QtGui.QIcon(style.app_icon_path()))
|
||||
|
||||
# Enable minimize and maximize for app
|
||||
window_flags = QtCore.Qt.Window
|
||||
if not parent:
|
||||
window_flags |= QtCore.Qt.WindowStaysOnTopHint
|
||||
self.setWindowFlags(window_flags)
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
||||
dbcon = AvalonMongoDB()
|
||||
|
||||
# UI initialization
|
||||
main_splitter = QtWidgets.QSplitter(self)
|
||||
|
||||
# Left side widget containt project combobox and asset widget
|
||||
left_side_widget = QtWidgets.QWidget(main_splitter)
|
||||
|
||||
project_combobox = QtWidgets.QComboBox(left_side_widget)
|
||||
# Styled delegate to propagate stylessheet
|
||||
project_delegate = QtWidgets.QStyledItemDelegate(project_combobox)
|
||||
project_combobox.setItemDelegate(project_delegate)
|
||||
# Project model with only active projects without default item
|
||||
project_model = ProjectModel(
|
||||
dbcon,
|
||||
only_active=True,
|
||||
add_default_project=False
|
||||
)
|
||||
# Sorting proxy model
|
||||
project_proxy = ProjectSortFilterProxy()
|
||||
project_proxy.setSourceModel(project_model)
|
||||
project_combobox.setModel(project_proxy)
|
||||
|
||||
# Assets widget
|
||||
assets_widget = AssetWidget(
|
||||
dbcon, multiselection=False, parent=left_side_widget
|
||||
)
|
||||
|
||||
left_side_layout = QtWidgets.QVBoxLayout(left_side_widget)
|
||||
left_side_layout.setContentsMargins(0, 0, 0, 0)
|
||||
left_side_layout.addWidget(project_combobox)
|
||||
left_side_layout.addWidget(assets_widget)
|
||||
|
||||
# Right side of window contains only tasks
|
||||
task_view = QtWidgets.QListView(main_splitter)
|
||||
task_model = TasksModel(dbcon)
|
||||
task_proxy = TasksProxyModel()
|
||||
task_proxy.setSourceModel(task_model)
|
||||
task_view.setModel(task_proxy)
|
||||
|
||||
# Add widgets to main splitter
|
||||
main_splitter.addWidget(left_side_widget)
|
||||
main_splitter.addWidget(task_view)
|
||||
|
||||
# Set stretch of both sides
|
||||
main_splitter.setStretchFactor(0, 7)
|
||||
main_splitter.setStretchFactor(1, 3)
|
||||
|
||||
# Add confimation button to bottom right
|
||||
ok_btn = QtWidgets.QPushButton("OK", self)
|
||||
|
||||
buttons_layout = QtWidgets.QHBoxLayout()
|
||||
buttons_layout.setContentsMargins(0, 0, 0, 0)
|
||||
buttons_layout.addStretch(1)
|
||||
buttons_layout.addWidget(ok_btn, 0)
|
||||
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.addWidget(main_splitter, 1)
|
||||
main_layout.addLayout(buttons_layout, 0)
|
||||
|
||||
# Timer which will trigger asset refresh
|
||||
# - this is needed because asset widget triggers
|
||||
# finished refresh before hides spin box so we need to trigger
|
||||
# refreshing in small offset if we want re-refresh asset widget
|
||||
assets_timer = QtCore.QTimer()
|
||||
assets_timer.setInterval(50)
|
||||
assets_timer.setSingleShot(True)
|
||||
|
||||
assets_timer.timeout.connect(self._on_asset_refresh_timer)
|
||||
|
||||
project_combobox.currentIndexChanged.connect(
|
||||
self._on_project_combo_change
|
||||
)
|
||||
assets_widget.selection_changed.connect(self._on_asset_change)
|
||||
assets_widget.refresh_triggered.connect(self._on_asset_refresh_trigger)
|
||||
assets_widget.refreshed.connect(self._on_asset_widget_refresh_finished)
|
||||
task_view.selectionModel().selectionChanged.connect(
|
||||
self._on_task_change
|
||||
)
|
||||
ok_btn.clicked.connect(self._on_ok_click)
|
||||
|
||||
self._dbcon = dbcon
|
||||
|
||||
self._project_combobox = project_combobox
|
||||
self._project_model = project_model
|
||||
self._project_proxy = project_proxy
|
||||
self._project_delegate = project_delegate
|
||||
|
||||
self._assets_widget = assets_widget
|
||||
|
||||
self._task_view = task_view
|
||||
self._task_model = task_model
|
||||
self._task_proxy = task_proxy
|
||||
|
||||
self._ok_btn = ok_btn
|
||||
|
||||
self._strict = False
|
||||
|
||||
# Values set by `set_context` method
|
||||
self._set_context_project = None
|
||||
self._set_context_asset = None
|
||||
|
||||
# Requirements for asset widget refresh
|
||||
self._assets_timer = assets_timer
|
||||
self._rerefresh_assets = True
|
||||
self._assets_refreshing = False
|
||||
|
||||
# Set stylehseet and resize window on first show
|
||||
self._first_show = True
|
||||
|
||||
# Helper attributes for handling of refresh
|
||||
self._ignore_value_changes = False
|
||||
self._refresh_on_next_show = True
|
||||
|
||||
# Output of dialog
|
||||
self._context_to_store = {
|
||||
"project": None,
|
||||
"asset": None,
|
||||
"task": None
|
||||
}
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Ignore close event if is in strict state and context is not done."""
|
||||
if self._strict and not self._ok_btn.isEnabled():
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
if self._strict:
|
||||
self._confirm_values()
|
||||
super(ContextDialog, self).closeEvent(event)
|
||||
|
||||
def set_strict(self, strict):
|
||||
"""Change strictness of dialog."""
|
||||
self._strict = strict
|
||||
self._validate_strict()
|
||||
|
||||
def _set_refresh_on_next_show(self):
|
||||
"""Refresh will be called on next showEvent.
|
||||
|
||||
If window is already visible then just execute refresh.
|
||||
"""
|
||||
self._refresh_on_next_show = True
|
||||
if self.isVisible():
|
||||
self.refresh()
|
||||
|
||||
def _refresh_assets(self):
|
||||
"""Trigger refreshing of asset widget.
|
||||
|
||||
This will set mart to rerefresh asset when current refreshing is done
|
||||
or do it immidietely if asset widget is not refreshing at the time.
|
||||
"""
|
||||
if self._assets_refreshing:
|
||||
self._rerefresh_assets = True
|
||||
else:
|
||||
self._on_asset_refresh_timer()
|
||||
|
||||
def showEvent(self, event):
|
||||
"""Override show event to do some callbacks."""
|
||||
super(ContextDialog, self).showEvent(event)
|
||||
if self._first_show:
|
||||
self._first_show = False
|
||||
# Set stylesheet and resize
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
self.resize(600, 700)
|
||||
center_window(self)
|
||||
|
||||
if self._refresh_on_next_show:
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
"""Refresh all widget one by one.
|
||||
|
||||
When asset refresh is triggered we have to wait when is done so
|
||||
this method continues with `_on_asset_widget_refresh_finished`.
|
||||
"""
|
||||
# Change state of refreshing (no matter how refresh was called)
|
||||
self._refresh_on_next_show = False
|
||||
|
||||
# Ignore changes of combobox and asset widget
|
||||
self._ignore_value_changes = True
|
||||
|
||||
# Get current project name to be able set it afterwards
|
||||
select_project_name = self._dbcon.Session.get("AVALON_PROJECT")
|
||||
# Trigger project refresh
|
||||
self._project_model.refresh()
|
||||
# Sort projects
|
||||
self._project_proxy.sort(0)
|
||||
|
||||
# Disable combobox if project was passed to `set_context`
|
||||
if self._set_context_project:
|
||||
select_project_name = self._set_context_project
|
||||
self._project_combobox.setEnabled(False)
|
||||
else:
|
||||
# Find new project to select
|
||||
self._project_combobox.setEnabled(True)
|
||||
if (
|
||||
select_project_name is None
|
||||
and self._project_proxy.rowCount() > 0
|
||||
):
|
||||
index = self._project_proxy.index(0, 0)
|
||||
select_project_name = index.data(PROJECT_NAME_ROLE)
|
||||
|
||||
self._ignore_value_changes = False
|
||||
|
||||
idx = self._project_combobox.findText(select_project_name)
|
||||
if idx >= 0:
|
||||
self._project_combobox.setCurrentIndex(idx)
|
||||
self._dbcon.Session["AVALON_PROJECT"] = (
|
||||
self._project_combobox.currentText()
|
||||
)
|
||||
|
||||
# Trigger asset refresh
|
||||
self._refresh_assets()
|
||||
|
||||
def _on_asset_refresh_timer(self):
|
||||
"""This is only way how to trigger refresh asset widget.
|
||||
|
||||
Use `_refresh_assets` method to refresh asset widget.
|
||||
"""
|
||||
self._assets_widget.refresh()
|
||||
|
||||
def _on_asset_widget_refresh_finished(self):
|
||||
"""Catch when asset widget finished refreshing."""
|
||||
# If should refresh again then skip all other callbacks and trigger
|
||||
# assets timer directly.
|
||||
self._assets_refreshing = False
|
||||
if self._rerefresh_assets:
|
||||
self._rerefresh_assets = False
|
||||
self._assets_timer.start()
|
||||
return
|
||||
|
||||
self._ignore_value_changes = True
|
||||
if self._set_context_asset:
|
||||
self._dbcon.Session["AVALON_ASSET"] = self._set_context_asset
|
||||
self._assets_widget.setEnabled(False)
|
||||
self._assets_widget.select_assets(self._set_context_asset)
|
||||
self._set_asset_to_task_model()
|
||||
else:
|
||||
self._assets_widget.setEnabled(True)
|
||||
self._assets_widget.set_current_asset_btn_visibility(False)
|
||||
|
||||
# Refresh tasks
|
||||
self._task_model.refresh()
|
||||
# Sort tasks
|
||||
self._task_proxy.sort(0, QtCore.Qt.AscendingOrder)
|
||||
|
||||
self._ignore_value_changes = False
|
||||
|
||||
self._validate_strict()
|
||||
|
||||
def _on_project_combo_change(self):
|
||||
if self._ignore_value_changes:
|
||||
return
|
||||
project_name = self._project_combobox.currentText()
|
||||
|
||||
if self._dbcon.Session.get("AVALON_PROJECT") == project_name:
|
||||
return
|
||||
|
||||
self._dbcon.Session["AVALON_PROJECT"] = project_name
|
||||
|
||||
self._refresh_assets()
|
||||
self._validate_strict()
|
||||
|
||||
def _on_asset_refresh_trigger(self):
|
||||
self._assets_refreshing = True
|
||||
self._on_asset_change()
|
||||
|
||||
def _on_asset_change(self):
|
||||
"""Selected assets have changed"""
|
||||
if self._ignore_value_changes:
|
||||
return
|
||||
self._set_asset_to_task_model()
|
||||
|
||||
def _on_task_change(self):
|
||||
self._validate_strict()
|
||||
|
||||
def _set_asset_to_task_model(self):
|
||||
# filter None docs they are silo
|
||||
asset_docs = self._assets_widget.get_selected_assets()
|
||||
asset_ids = [asset_doc["_id"] for asset_doc in asset_docs]
|
||||
asset_id = None
|
||||
if asset_ids:
|
||||
asset_id = asset_ids[0]
|
||||
self._task_model.set_asset_id(asset_id)
|
||||
self._task_proxy.sort(0, QtCore.Qt.AscendingOrder)
|
||||
|
||||
def _confirm_values(self):
|
||||
"""Store values to output."""
|
||||
self._context_to_store["project"] = self.get_selected_project()
|
||||
self._context_to_store["asset"] = self.get_selected_asset()
|
||||
self._context_to_store["task"] = self.get_selected_task()
|
||||
|
||||
def _on_ok_click(self):
|
||||
# Store values to output
|
||||
self._confirm_values()
|
||||
# Close dialog
|
||||
self.accept()
|
||||
|
||||
def get_selected_project(self):
|
||||
"""Get selected project."""
|
||||
return self._project_combobox.currentText()
|
||||
|
||||
def get_selected_asset(self):
|
||||
"""Currently selected asset in asset widget."""
|
||||
asset_name = None
|
||||
for asset_doc in self._assets_widget.get_selected_assets():
|
||||
asset_name = asset_doc["name"]
|
||||
break
|
||||
return asset_name
|
||||
|
||||
def get_selected_task(self):
|
||||
"""Currently selected task."""
|
||||
task_name = None
|
||||
index = self._task_view.selectionModel().currentIndex()
|
||||
if index.isValid():
|
||||
task_name = index.data(TASK_NAME_ROLE)
|
||||
return task_name
|
||||
|
||||
def _validate_strict(self):
|
||||
if not self._strict:
|
||||
if not self._ok_btn.isEnabled():
|
||||
self._ok_btn.setEnabled(True)
|
||||
return
|
||||
|
||||
enabled = True
|
||||
if not self._set_context_project and not self.get_selected_project():
|
||||
enabled = False
|
||||
elif not self._set_context_asset and not self.get_selected_asset():
|
||||
enabled = False
|
||||
elif not self.get_selected_task():
|
||||
enabled = False
|
||||
self._ok_btn.setEnabled(enabled)
|
||||
|
||||
def set_context(self, project_name=None, asset_name=None):
|
||||
"""Set context which will be used and locked in dialog."""
|
||||
if project_name is None:
|
||||
asset_name = None
|
||||
|
||||
self._set_context_project = project_name
|
||||
self._set_context_asset = asset_name
|
||||
|
||||
self._context_to_store["project"] = project_name
|
||||
self._context_to_store["asset"] = asset_name
|
||||
|
||||
self._set_refresh_on_next_show()
|
||||
|
||||
def get_context(self):
|
||||
"""Result of dialog."""
|
||||
return self._context_to_store
|
||||
|
||||
|
||||
def main(
|
||||
path_to_store,
|
||||
project_name=None,
|
||||
asset_name=None,
|
||||
strict=True
|
||||
):
|
||||
# Run Qt application
|
||||
app = QtWidgets.QApplication.instance()
|
||||
if app is None:
|
||||
app = QtWidgets.QApplication([])
|
||||
window = ContextDialog()
|
||||
window.set_strict(strict)
|
||||
window.set_context(project_name, asset_name)
|
||||
window.show()
|
||||
app.exec_()
|
||||
|
||||
# Get result from window
|
||||
data = window.get_context()
|
||||
|
||||
# Make sure json filepath directory exists
|
||||
file_dir = os.path.dirname(path_to_store)
|
||||
if not os.path.exists(file_dir):
|
||||
os.makedirs(file_dir)
|
||||
|
||||
# Store result into json file
|
||||
with open(path_to_store, "w") as stream:
|
||||
json.dump(data, stream)
|
||||
10
openpype/tools/utils/constants.py
Normal file
10
openpype/tools/utils/constants.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from Qt import QtCore
|
||||
|
||||
|
||||
DEFAULT_PROJECT_LABEL = "< Default >"
|
||||
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 + 403
|
||||
|
|
@ -13,6 +13,16 @@ from openpype.api import get_project_settings
|
|||
from openpype.lib import filter_profiles
|
||||
|
||||
|
||||
def center_window(window):
|
||||
"""Move window to center of it's screen."""
|
||||
desktop = QtWidgets.QApplication.desktop()
|
||||
screen_idx = desktop.screenNumber(window)
|
||||
screen_geo = desktop.screenGeometry(screen_idx)
|
||||
geo = window.frameGeometry()
|
||||
geo.moveCenter(screen_geo.center())
|
||||
window.move(geo.topLeft())
|
||||
|
||||
|
||||
def format_version(value, hero_version=False):
|
||||
"""Formats integer to displayable version name"""
|
||||
label = "v{0:03d}".format(value)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,14 @@ from Qt import QtCore, QtGui
|
|||
from avalon.vendor import qtawesome
|
||||
from avalon import style, io
|
||||
from . import lib
|
||||
from .constants import (
|
||||
PROJECT_IS_ACTIVE_ROLE,
|
||||
PROJECT_NAME_ROLE,
|
||||
DEFAULT_PROJECT_LABEL,
|
||||
TASK_ORDER_ROLE,
|
||||
TASK_TYPE_ROLE,
|
||||
TASK_NAME_ROLE
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -498,3 +506,311 @@ class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel):
|
|||
return super(
|
||||
RecursiveSortFilterProxyModel, self
|
||||
).filterAcceptsRow(row, parent)
|
||||
|
||||
|
||||
class ProjectModel(QtGui.QStandardItemModel):
|
||||
def __init__(
|
||||
self, dbcon=None, only_active=True, add_default_project=False,
|
||||
*args, **kwargs
|
||||
):
|
||||
super(ProjectModel, self).__init__(*args, **kwargs)
|
||||
|
||||
self.dbcon = dbcon
|
||||
|
||||
self._only_active = only_active
|
||||
self._add_default_project = add_default_project
|
||||
|
||||
self._default_item = None
|
||||
self._items_by_name = {}
|
||||
# Model was at least once refreshed
|
||||
# - for `set_dbcon` method
|
||||
self._refreshed = False
|
||||
|
||||
def set_default_project_available(self, available=True):
|
||||
if available is None:
|
||||
available = not self._add_default_project
|
||||
|
||||
if self._add_default_project == available:
|
||||
return
|
||||
|
||||
self._add_default_project = available
|
||||
if not available and self._default_item is not None:
|
||||
root_item = self.invisibleRootItem()
|
||||
root_item.removeRow(self._default_item.row())
|
||||
self._default_item = None
|
||||
|
||||
def set_only_active(self, only_active=True):
|
||||
if only_active is None:
|
||||
only_active = not self._only_active
|
||||
|
||||
if self._only_active == only_active:
|
||||
return
|
||||
|
||||
self._only_active = only_active
|
||||
|
||||
if self._refreshed:
|
||||
self.refresh()
|
||||
|
||||
def set_dbcon(self, dbcon):
|
||||
"""Change mongo connection."""
|
||||
self.dbcon = dbcon
|
||||
# Trigger refresh if was already refreshed
|
||||
if self._refreshed:
|
||||
self.refresh()
|
||||
|
||||
def project_name_is_available(self, project_name):
|
||||
"""Check availability of project name in current items."""
|
||||
return project_name in self._items_by_name
|
||||
|
||||
def refresh(self):
|
||||
# Change '_refreshed' state
|
||||
self._refreshed = True
|
||||
new_items = []
|
||||
# Add default item to model if should
|
||||
if self._add_default_project and self._default_item is None:
|
||||
item = QtGui.QStandardItem(DEFAULT_PROJECT_LABEL)
|
||||
item.setData(None, PROJECT_NAME_ROLE)
|
||||
item.setData(True, PROJECT_IS_ACTIVE_ROLE)
|
||||
new_items.append(item)
|
||||
self._default_item = item
|
||||
|
||||
project_names = set()
|
||||
if self.dbcon is not None:
|
||||
for project_doc in self.dbcon.projects(
|
||||
projection={"name": 1, "data.active": 1},
|
||||
only_active=self._only_active
|
||||
):
|
||||
project_name = project_doc["name"]
|
||||
project_names.add(project_name)
|
||||
if project_name in self._items_by_name:
|
||||
item = self._items_by_name[project_name]
|
||||
else:
|
||||
item = QtGui.QStandardItem(project_name)
|
||||
|
||||
self._items_by_name[project_name] = item
|
||||
new_items.append(item)
|
||||
|
||||
is_active = project_doc.get("data", {}).get("active", True)
|
||||
item.setData(project_name, PROJECT_NAME_ROLE)
|
||||
item.setData(is_active, PROJECT_IS_ACTIVE_ROLE)
|
||||
|
||||
if not is_active:
|
||||
font = item.font()
|
||||
font.setItalic(True)
|
||||
item.setFont(font)
|
||||
|
||||
root_item = self.invisibleRootItem()
|
||||
for project_name in tuple(self._items_by_name.keys()):
|
||||
if project_name not in project_names:
|
||||
item = self._items_by_name.pop(project_name)
|
||||
root_item.removeRow(item.row())
|
||||
|
||||
if new_items:
|
||||
root_item.appendRows(new_items)
|
||||
|
||||
|
||||
class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ProjectSortFilterProxy, self).__init__(*args, **kwargs)
|
||||
self._filter_enabled = True
|
||||
|
||||
def lessThan(self, left_index, right_index):
|
||||
if left_index.data(PROJECT_NAME_ROLE) is None:
|
||||
return True
|
||||
|
||||
if right_index.data(PROJECT_NAME_ROLE) is None:
|
||||
return False
|
||||
|
||||
left_is_active = left_index.data(PROJECT_IS_ACTIVE_ROLE)
|
||||
right_is_active = right_index.data(PROJECT_IS_ACTIVE_ROLE)
|
||||
if right_is_active == left_is_active:
|
||||
return super(ProjectSortFilterProxy, self).lessThan(
|
||||
left_index, right_index
|
||||
)
|
||||
|
||||
if left_is_active:
|
||||
return True
|
||||
return False
|
||||
|
||||
def filterAcceptsRow(self, source_row, source_parent):
|
||||
index = self.sourceModel().index(source_row, 0, source_parent)
|
||||
if self._filter_enabled:
|
||||
result = self._custom_index_filter(index)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
return super(ProjectSortFilterProxy, self).filterAcceptsRow(
|
||||
source_row, source_parent
|
||||
)
|
||||
|
||||
def _custom_index_filter(self, index):
|
||||
is_active = bool(index.data(PROJECT_IS_ACTIVE_ROLE))
|
||||
|
||||
return is_active
|
||||
|
||||
def is_filter_enabled(self):
|
||||
return self._filter_enabled
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -14,13 +14,15 @@ from avalon.tools import lib as tools_lib
|
|||
from avalon.tools.widgets import AssetWidget
|
||||
from avalon.tools.delegates import PrettyTimeDelegate
|
||||
|
||||
from .model import (
|
||||
from openpype.tools.utils.constants import (
|
||||
TASK_NAME_ROLE,
|
||||
TASK_TYPE_ROLE,
|
||||
FilesModel,
|
||||
TASK_TYPE_ROLE
|
||||
)
|
||||
from openpype.tools.utils.models import (
|
||||
TasksModel,
|
||||
TasksProxyModel
|
||||
)
|
||||
from .model import FilesModel
|
||||
from .view import FilesView
|
||||
|
||||
from openpype.lib import (
|
||||
|
|
@ -359,6 +361,7 @@ class TasksWidget(QtWidgets.QWidget):
|
|||
self._last_selected_task = current
|
||||
|
||||
self._tasks_model.set_asset(asset_doc)
|
||||
self._tasks_proxy.sort(0, QtCore.Qt.AscendingOrder)
|
||||
|
||||
if self._last_selected_task:
|
||||
self.select_task(self._last_selected_task)
|
||||
|
|
|
|||
|
|
@ -9,10 +9,6 @@ from avalon.tools.models import TreeModel, Item
|
|||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
TASK_NAME_ROLE = QtCore.Qt.UserRole + 1
|
||||
TASK_TYPE_ROLE = QtCore.Qt.UserRole + 2
|
||||
TASK_ORDER_ROLE = QtCore.Qt.UserRole + 3
|
||||
|
||||
|
||||
class FilesModel(TreeModel):
|
||||
"""Model listing files with specified extensions in a root folder"""
|
||||
|
|
@ -155,142 +151,3 @@ class FilesModel(TreeModel):
|
|||
return "Date modified"
|
||||
|
||||
return super(FilesModel, self).headerData(section, orientation, role)
|
||||
|
||||
|
||||
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 False
|
||||
return True
|
||||
|
||||
|
||||
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._refresh_task_types()
|
||||
|
||||
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(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:
|
||||
return
|
||||
|
||||
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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue