prepared context dialog using AYON calls

This commit is contained in:
Jakub Trllo 2023-10-10 15:58:34 +02:00
parent 915502989c
commit 9868b09c9b
3 changed files with 1188 additions and 393 deletions

View file

@ -0,0 +1,783 @@
import os
import json
import ayon_api
from qtpy import QtWidgets, QtCore, QtGui
from openpype import style
from openpype.lib.events import QueuedEventSystem
from openpype.tools.ayon_utils.models import (
ProjectsModel,
HierarchyModel,
)
from openpype.tools.ayon_utils.widgets import (
ProjectsCombobox,
FoldersWidget,
TasksWidget,
)
from openpype.tools.utils.lib import (
center_window,
get_openpype_qt_app,
)
class SelectionModel(object):
"""Model handling selection changes.
Triggering events:
- "selection.project.changed"
- "selection.folder.changed"
- "selection.task.changed"
"""
event_source = "selection.model"
def __init__(self, controller):
self._controller = controller
self._project_name = None
self._folder_id = None
self._task_id = None
self._task_name = None
def get_selected_project_name(self):
return self._project_name
def set_selected_project(self, project_name):
self._project_name = project_name
self._controller.emit_event(
"selection.project.changed",
{"project_name": project_name},
self.event_source
)
def get_selected_folder_id(self):
return self._folder_id
def set_selected_folder(self, folder_id):
if folder_id == self._folder_id:
return
self._folder_id = folder_id
self._controller.emit_event(
"selection.folder.changed",
{
"project_name": self._project_name,
"folder_id": folder_id,
},
self.event_source
)
def get_selected_task_name(self):
return self._task_name
def get_selected_task_id(self):
return self._task_id
def set_selected_task(self, task_id, task_name):
if task_id == self._task_id:
return
self._task_name = task_name
self._task_id = task_id
self._controller.emit_event(
"selection.task.changed",
{
"project_name": self._project_name,
"folder_id": self._folder_id,
"task_name": task_name,
"task_id": task_id,
},
self.event_source
)
class ExpectedSelection:
def __init__(self, controller):
self._project_name = None
self._folder_id = None
self._project_selected = True
self._folder_selected = True
self._controller = controller
def _emit_change(self):
self._controller.emit_event(
"expected_selection_changed",
self.get_expected_selection_data(),
)
def set_expected_selection(self, project_name, folder_id):
self._project_name = project_name
self._folder_id = folder_id
self._project_selected = False
self._folder_selected = False
self._emit_change()
def get_expected_selection_data(self):
project_current = False
folder_current = False
if not self._project_selected:
project_current = True
elif not self._folder_selected:
folder_current = True
return {
"project": {
"name": self._project_name,
"current": project_current,
"selected": self._project_selected,
},
"folder": {
"id": self._folder_id,
"current": folder_current,
"selected": self._folder_selected,
},
}
def is_expected_project_selected(self, project_name):
return project_name == self._project_name and self._project_selected
def is_expected_folder_selected(self, folder_id):
return folder_id == self._folder_id and self._folder_selected
def expected_project_selected(self, project_name):
if project_name != self._project_name:
return False
self._project_selected = True
self._emit_change()
return True
def expected_folder_selected(self, folder_id):
if folder_id != self._folder_id:
return False
self._folder_selected = True
self._emit_change()
return True
class ContextDialogController:
def __init__(self):
self._event_system = None
self._projects_model = ProjectsModel(self)
self._hierarchy_model = HierarchyModel(self)
self._selection_model = SelectionModel(self)
self._expected_selection = ExpectedSelection(self)
self._confirmed = False
self._is_strict = False
self._output_path = None
self._initial_project_name = None
self._initial_folder_id = None
self._initial_folder_label = None
self._initial_project_found = True
self._initial_folder_found = True
self._initial_tasks_found = True
def reset(self):
self._emit_event("controller.reset.started")
self._confirmed = False
self._output_path = None
self._initial_project_name = None
self._initial_folder_id = None
self._initial_folder_label = None
self._initial_project_found = True
self._initial_folder_found = True
self._initial_tasks_found = True
self._projects_model.reset()
self._hierarchy_model.reset()
self._emit_event("controller.reset.finished")
def refresh(self):
self._emit_event("controller.refresh.started")
self._projects_model.reset()
self._hierarchy_model.reset()
self._emit_event("controller.refresh.finished")
# Event handling
def emit_event(self, topic, data=None, source=None):
"""Use implemented event system to trigger event."""
if data is None:
data = {}
self._get_event_system().emit(topic, data, source)
def register_event_callback(self, topic, callback):
self._get_event_system().add_callback(topic, callback)
def set_output_json_path(self, output_path):
self._output_path = output_path
def is_strict(self):
return self._is_strict
def set_strict(self, enabled):
if self._is_strict is enabled:
return
self._is_strict = enabled
self._emit_event("strict.changed", {"strict": enabled})
# Data model functions
def get_project_items(self, sender=None):
return self._projects_model.get_project_items(sender)
def get_folder_items(self, project_name, sender=None):
return self._hierarchy_model.get_folder_items(project_name, sender)
def get_task_items(self, project_name, folder_id, sender=None):
return self._hierarchy_model.get_task_items(
project_name, folder_id, sender
)
# Expected selection helpers
def set_expected_selection(self, project_name, folder_id):
return self._expected_selection.set_expected_selection(
project_name, folder_id
)
def get_expected_selection_data(self):
return self._expected_selection.get_expected_selection_data()
def expected_project_selected(self, project_name):
self._expected_selection.expected_project_selected(project_name)
def expected_folder_selected(self, folder_id):
self._expected_selection.expected_folder_selected(folder_id)
# Selection handling
def get_selected_project_name(self):
return self._selection_model.get_selected_project_name()
def set_selected_project(self, project_name):
self._selection_model.set_selected_project(project_name)
def get_selected_folder_id(self):
return self._selection_model.get_selected_folder_id()
def set_selected_folder(self, folder_id):
self._selection_model.set_selected_folder(folder_id)
def get_selected_task_name(self):
return self._selection_model.get_selected_task_name()
def get_selected_task_id(self):
return self._selection_model.get_selected_task_id()
def set_selected_task(self, task_id, task_name):
self._selection_model.set_selected_task(task_id, task_name)
def is_initial_context_valid(self):
return self._initial_folder_found and self._initial_project_found
def set_initial_context(
self, project_name=None, asset_name=None, folder_path=None
):
if project_name is None:
project_found = True
asset_name = None
folder_path = None
else:
project = ayon_api.get_project(project_name)
project_found = project is not None
folder_id = None
folder_found = True
folder_label = None
if folder_path:
folder_label = folder_path
folder = ayon_api.get_folder_by_path(project_name, folder_path)
if folder:
folder_id = folder["id"]
else:
folder_found = False
elif asset_name:
folder_label = asset_name
for folder in ayon_api.get_folders(
project_name, folder_names=[asset_name]
):
folder_id = folder["id"]
break
if not folder_id:
folder_found = False
tasks_found = True
if folder_found and (folder_path or asset_name):
tasks = list(ayon_api.get_tasks(
project_name, folder_ids=[folder_id], fields=["id"]
))
if not tasks:
tasks_found = False
self._initial_project_name = project_name
self._initial_folder_id = folder_id
self._initial_folder_label = folder_label
self._initial_folder_found = project_found
self._initial_folder_found = folder_found
self._initial_tasks_found = tasks_found
self._emit_event(
"initial.context.changed",
self.get_initial_context()
)
def get_initial_context(self):
return {
"project_name": self._initial_project_name,
"folder_id": self._initial_folder_id,
"folder_label": self._initial_folder_label,
"project_found": self._initial_project_found,
"folder_found": self._initial_folder_found,
"tasks_found": self._initial_tasks_found,
"valid": (
self._initial_project_found
and self._initial_folder_found
and self._initial_tasks_found
)
}
# Result of this tool
def get_selected_context(self):
return {
"project": None,
"project_name": None,
"asset": None,
"folder_id": None,
"folder_path": None,
"task": None,
"task_id": None,
"task_name": None,
}
def window_closed(self):
if not self._confirmed and not self._is_strict:
return
self._store_output()
def confirm_selection(self):
self._confirmed = True
self._emit_event(
"selection.confirmed",
{"confirmed": True}
)
def _store_output(self):
if not self._output_path:
return
dirpath = os.path.dirname(self._output_path)
os.makedirs(dirpath, exist_ok=True)
with open(self._output_path, "w") as stream:
json.dump(self.get_selected_context(), stream)
def _get_event_system(self):
"""Inner event system for workfiles tool controller.
Is used for communication with UI. Event system is created on demand.
Returns:
QueuedEventSystem: Event system which can trigger callbacks
for topics.
"""
if self._event_system is None:
self._event_system = QueuedEventSystem()
return self._event_system
def _emit_event(self, topic, data=None):
self.emit_event(topic, data, "controller")
class InvalidContextOverlay(QtWidgets.QFrame):
confirmed = QtCore.Signal()
def __init__(self, parent):
super(InvalidContextOverlay, self).__init__(parent)
self.setObjectName("OverlayFrame")
mid_widget = QtWidgets.QWidget(self)
label_widget = QtWidgets.QLabel(
"Requested context was not found...",
mid_widget
)
confirm_btn = QtWidgets.QPushButton("Close", mid_widget)
mid_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
mid_layout = QtWidgets.QVBoxLayout(mid_widget)
mid_layout.setContentsMargins(0, 0, 0, 0)
mid_layout.addWidget(label_widget, 0)
mid_layout.addSpacing(30)
mid_layout.addWidget(confirm_btn, 0)
main_layout = QtWidgets.QGridLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.addWidget(mid_widget, 1, 1)
main_layout.setRowStretch(0, 1)
main_layout.setRowStretch(1, 0)
main_layout.setRowStretch(2, 1)
main_layout.setColumnStretch(0, 1)
main_layout.setColumnStretch(1, 0)
main_layout.setColumnStretch(2, 1)
confirm_btn.clicked.connect(self.confirmed)
self._label_widget = label_widget
self._confirm_btn = confirm_btn
def set_context(
self,
project_name,
folder_label,
project_found,
folder_found,
tasks_found,
):
lines = []
if not project_found:
lines.extend([
"Requested project {} was not found...".format(project_name),
])
elif not folder_found:
lines.extend([
"Requested folder was not found...",
"",
"Project: {}".format(project_name),
"Folder: {}".format(folder_label),
])
elif not tasks_found:
lines.extend([
"Requested folder does not have any tasks...",
"",
"Project: {}".format(project_name),
"Folder: {}".format(folder_label),
])
else:
lines.append("Requested context was not found...")
self._label_widget.setText("<br/>".join(lines))
class ContextDialog(QtWidgets.QDialog):
"""Dialog to select a context.
Context has 3 parts:
- Project
- Asset
- 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, controller=None, parent=None):
super(ContextDialog, self).__init__(parent)
self.setWindowTitle("Select Context")
self.setWindowIcon(QtGui.QIcon(style.app_icon_path()))
if controller is None:
controller = ContextDialogController()
# 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)
# UI initialization
main_splitter = QtWidgets.QSplitter(self)
# Left side widget contains project combobox and asset widget
left_side_widget = QtWidgets.QWidget(main_splitter)
project_combobox = ProjectsCombobox(
controller,
parent=left_side_widget,
handle_expected_selection=True
)
# Assets widget
folders_widget = FoldersWidget(
controller,
parent=left_side_widget,
handle_expected_selection=True
)
left_side_layout = QtWidgets.QVBoxLayout(left_side_widget)
left_side_layout.setContentsMargins(0, 0, 0, 0)
left_side_layout.addWidget(project_combobox, 0)
left_side_layout.addWidget(folders_widget, 1)
# Right side of window contains only tasks
tasks_widget = TasksWidget(controller, parent=main_splitter)
# Add widgets to main splitter
main_splitter.addWidget(left_side_widget)
main_splitter.addWidget(tasks_widget)
# 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)
overlay_widget = InvalidContextOverlay(self)
overlay_widget.setVisible(False)
ok_btn.clicked.connect(self._on_ok_click)
project_combobox.refreshed.connect(self._on_projects_refresh)
overlay_widget.confirmed.connect(self._on_overlay_confirm)
controller.register_event_callback(
"selection.project.changed",
self._on_project_selection_change
)
controller.register_event_callback(
"selection.folder.changed",
self._on_folder_selection_change
)
controller.register_event_callback(
"selection.task.changed",
self._on_task_selection_change
)
controller.register_event_callback(
"initial.context.changed",
self._on_init_context_change
)
controller.register_event_callback(
"strict.changed",
self._on_strict_changed
)
controller.register_event_callback(
"controller.reset.finished",
self._on_controller_reset
)
controller.register_event_callback(
"controller.refresh.finished",
self._on_controller_refresh
)
# Set stylehseet and resize window on first show
self._first_show = True
self._visible = False
self._controller = controller
self._project_combobox = project_combobox
self._folders_widget = folders_widget
self._tasks_widget = tasks_widget
self._ok_btn = ok_btn
self._overlay_widget = overlay_widget
self._apply_strict_changes(self.is_strict())
def is_strict(self):
return self._controller.is_strict()
def showEvent(self, event):
"""Override show event to do some callbacks."""
super(ContextDialog, self).showEvent(event)
self._visible = True
if self._first_show:
self._first_show = False
# Set stylesheet and resize
self.setStyleSheet(style.load_stylesheet())
self.resize(600, 700)
center_window(self)
self._controller.refresh()
initial_context = self._controller.get_initial_context()
self._set_init_context(initial_context)
self._overlay_widget.resize(self.size())
def resizeEvent(self, event):
super(ContextDialog, self).resizeEvent(event)
self._overlay_widget.resize(self.size())
def closeEvent(self, event):
"""Ignore close event if is in strict state and context is not done."""
if self.is_strict() and not self._ok_btn.isEnabled():
# Allow to close window when initial context is not valid
if self._controller.is_initial_context_valid():
event.ignore()
return
if self.is_strict():
self._controller.confirm_selection()
self._visible = False
super(ContextDialog, self).closeEvent(event)
def set_strict(self, enabled):
"""Change strictness of dialog."""
self._controller.set_strict(enabled)
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`.
"""
self._controller.reset()
def get_context(self):
"""Result of dialog."""
return self._controller.get_selected_context()
def set_context(self, project_name=None, asset_name=None):
"""Set context which will be used and locked in dialog."""
self._controller.set_initial_context(project_name, asset_name)
def _on_projects_refresh(self):
initial_context = self._controller.get_initial_context()
self._controller.set_expected_selection(
initial_context["project_name"],
initial_context["folder_id"]
)
def _on_overlay_confirm(self):
self.close()
def _on_ok_click(self):
# Store values to output
self._controller.confirm_selection()
# Close dialog
self.accept()
def _on_project_selection_change(self, event):
self._on_selection_change(
event["project_name"],
)
def _on_folder_selection_change(self, event):
self._on_selection_change(
event["project_name"],
event["folder_id"],
)
def _on_task_selection_change(self, event):
self._on_selection_change(
event["project_name"],
event["folder_id"],
event["task_name"],
)
def _on_selection_change(
self, project_name, folder_id=None, task_name=None
):
self._validate_strict(project_name, folder_id, task_name)
def _on_init_context_change(self, event):
self._set_init_context(event.data)
if self._visible:
self._controller.set_expected_selection(
event["project_name"], event["folder_id"]
)
def _set_init_context(self, init_context):
project_name = init_context["project_name"]
if not init_context["valid"]:
self._overlay_widget.setVisible(True)
self._overlay_widget.set_context(
project_name,
init_context["folder_label"],
init_context["project_found"],
init_context["folder_found"],
init_context["tasks_found"]
)
return
self._overlay_widget.setVisible(False)
if project_name:
self._project_combobox.setEnabled(False)
if init_context["folder_id"]:
self._folders_widget.setEnabled(False)
else:
self._project_combobox.setEnabled(True)
self._folders_widget.setEnabled(True)
def _on_strict_changed(self, event):
self._apply_strict_changes(event["strict"])
def _on_controller_reset(self):
self._apply_strict_changes(self.is_strict())
self._project_combobox.refresh()
def _on_controller_refresh(self):
self._project_combobox.refresh()
def _apply_strict_changes(self, is_strict):
if not is_strict:
if not self._ok_btn.isEnabled():
self._ok_btn.setEnabled(True)
return
context = self._controller.get_selected_context()
self._validate_strict(
context["project_name"],
context["folder_id"],
context["task_name"]
)
def _validate_strict(self, project_name, folder_id, task_name):
if not self.is_strict():
return
enabled = True
if not project_name or not folder_id or not task_name:
enabled = False
self._ok_btn.setEnabled(enabled)
def main(
path_to_store,
project_name=None,
asset_name=None,
strict=True
):
# Run Qt application
app = get_openpype_qt_app()
controller = ContextDialogController()
controller.set_strict(strict)
controller.set_initial_context(project_name, asset_name)
controller.set_output_json_path(path_to_store)
window = ContextDialog(controller=controller)
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)

View file

@ -0,0 +1,396 @@
import os
import json
from qtpy import QtWidgets, QtCore, QtGui
from openpype import style
from openpype.pipeline import AvalonMongoDB
from openpype.tools.utils.lib import center_window, get_openpype_qt_app
from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget
from openpype.tools.utils.constants import (
PROJECT_NAME_ROLE
)
from openpype.tools.utils.tasks_widget import TasksWidget
from openpype.tools.utils.models import (
ProjectModel,
ProjectSortFilterProxy
)
class ContextDialog(QtWidgets.QDialog):
"""Dialog to select a context.
Context has 3 parts:
- Project
- Asset
- 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 contains 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 = SingleSelectAssetsWidget(
dbcon, 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
tasks_widget = TasksWidget(dbcon, main_splitter)
# Add widgets to main splitter
main_splitter.addWidget(left_side_widget)
main_splitter.addWidget(tasks_widget)
# 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)
tasks_widget.task_changed.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._tasks_widget = tasks_widget
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_asset_by_name(self._set_context_asset)
self._set_asset_to_tasks_widget()
else:
self._assets_widget.setEnabled(True)
self._assets_widget.set_current_asset_btn_visibility(False)
# Refresh tasks
self._tasks_widget.refresh()
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_tasks_widget()
def _on_task_change(self):
self._validate_strict()
def _set_asset_to_tasks_widget(self):
asset_id = self._assets_widget.get_selected_asset_id()
self._tasks_widget.set_asset_id(asset_id)
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."""
return self._assets_widget.get_selected_asset_name()
def get_selected_task(self):
"""Currently selected task."""
return self._tasks_widget.get_selected_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 = get_openpype_qt_app()
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)

View file

@ -1,396 +1,12 @@
import os
import json
from openpype import AYON_SERVER_ENABLED
from qtpy import QtWidgets, QtCore, QtGui
if AYON_SERVER_ENABLED:
from ._ayon_window import ContextDialog, main
else:
from ._openpype_window import ContextDialog, main
from openpype import style
from openpype.pipeline import AvalonMongoDB
from openpype.tools.utils.lib import center_window, get_openpype_qt_app
from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget
from openpype.tools.utils.constants import (
PROJECT_NAME_ROLE
__all__ = (
"ContextDialog",
"main",
)
from openpype.tools.utils.tasks_widget import TasksWidget
from openpype.tools.utils.models import (
ProjectModel,
ProjectSortFilterProxy
)
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 contains 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 = SingleSelectAssetsWidget(
dbcon, 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
tasks_widget = TasksWidget(dbcon, main_splitter)
# Add widgets to main splitter
main_splitter.addWidget(left_side_widget)
main_splitter.addWidget(tasks_widget)
# 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)
tasks_widget.task_changed.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._tasks_widget = tasks_widget
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_tasks_widget()
else:
self._assets_widget.setEnabled(True)
self._assets_widget.set_current_asset_btn_visibility(False)
# Refresh tasks
self._tasks_widget.refresh()
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_tasks_widget()
def _on_task_change(self):
self._validate_strict()
def _set_asset_to_tasks_widget(self):
asset_id = self._assets_widget.get_selected_asset_id()
self._tasks_widget.set_asset_id(asset_id)
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."""
return self._assets_widget.get_selected_asset_name()
def get_selected_task(self):
"""Currently selected task."""
return self._tasks_widget.get_selected_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 = get_openpype_qt_app()
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)