mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
419 lines
14 KiB
Python
419 lines
14 KiB
Python
import os
|
|
import datetime
|
|
from Qt import QtCore, QtWidgets, QtGui
|
|
|
|
from openpype.client import (
|
|
get_asset_by_id,
|
|
get_asset_by_name,
|
|
get_workfile_info,
|
|
)
|
|
from openpype import style
|
|
from openpype import resources
|
|
from openpype.lib import (
|
|
create_workfile_doc,
|
|
save_workfile_data_to_doc,
|
|
)
|
|
from openpype.pipeline import legacy_io
|
|
from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget
|
|
from openpype.tools.utils.tasks_widget import TasksWidget
|
|
|
|
from .files_widget import FilesWidget
|
|
|
|
|
|
def file_size_to_string(file_size):
|
|
size = 0
|
|
size_ending_mapping = {
|
|
"KB": 1024 ** 1,
|
|
"MB": 1024 ** 2,
|
|
"GB": 1024 ** 3
|
|
}
|
|
ending = "B"
|
|
for _ending, _size in size_ending_mapping.items():
|
|
if file_size < _size:
|
|
break
|
|
size = file_size / _size
|
|
ending = _ending
|
|
return "{:.2f} {}".format(size, ending)
|
|
|
|
|
|
class SidePanelWidget(QtWidgets.QWidget):
|
|
save_clicked = QtCore.Signal()
|
|
published_workfile_message = (
|
|
"<b>INFO</b>: Opened published workfiles will be stored in"
|
|
" temp directory on your machine. Current temp size: <b>{}</b>."
|
|
)
|
|
|
|
def __init__(self, parent=None):
|
|
super(SidePanelWidget, self).__init__(parent)
|
|
|
|
details_label = QtWidgets.QLabel("Details", self)
|
|
details_input = QtWidgets.QPlainTextEdit(self)
|
|
details_input.setReadOnly(True)
|
|
|
|
artist_note_widget = QtWidgets.QWidget(self)
|
|
note_label = QtWidgets.QLabel("Artist note", artist_note_widget)
|
|
note_input = QtWidgets.QPlainTextEdit(artist_note_widget)
|
|
btn_note_save = QtWidgets.QPushButton("Save note", artist_note_widget)
|
|
|
|
artist_note_layout = QtWidgets.QVBoxLayout(artist_note_widget)
|
|
artist_note_layout.setContentsMargins(0, 0, 0, 0)
|
|
artist_note_layout.addWidget(note_label, 0)
|
|
artist_note_layout.addWidget(note_input, 1)
|
|
artist_note_layout.addWidget(
|
|
btn_note_save, 0, alignment=QtCore.Qt.AlignRight
|
|
)
|
|
|
|
main_layout = QtWidgets.QVBoxLayout(self)
|
|
main_layout.setContentsMargins(0, 0, 0, 0)
|
|
main_layout.addWidget(details_label, 0)
|
|
main_layout.addWidget(details_input, 1)
|
|
main_layout.addWidget(artist_note_widget, 1)
|
|
|
|
note_input.textChanged.connect(self._on_note_change)
|
|
btn_note_save.clicked.connect(self._on_save_click)
|
|
|
|
self._details_input = details_input
|
|
self._artist_note_widget = artist_note_widget
|
|
self._note_input = note_input
|
|
self._btn_note_save = btn_note_save
|
|
|
|
self._orig_note = ""
|
|
self._workfile_doc = None
|
|
|
|
def set_published_visible(self, published_visible):
|
|
self._artist_note_widget.setVisible(not published_visible)
|
|
|
|
def _on_note_change(self):
|
|
text = self._note_input.toPlainText()
|
|
self._btn_note_save.setEnabled(self._orig_note != text)
|
|
|
|
def _on_save_click(self):
|
|
self._orig_note = self._note_input.toPlainText()
|
|
self._on_note_change()
|
|
self.save_clicked.emit()
|
|
|
|
def set_context(self, asset_id, task_name, filepath, workfile_doc):
|
|
# Check if asset, task and file are selected
|
|
# NOTE workfile document is not requirement
|
|
enabled = bool(asset_id) and bool(task_name) and bool(filepath)
|
|
|
|
self._details_input.setEnabled(enabled)
|
|
self._note_input.setEnabled(enabled)
|
|
self._btn_note_save.setEnabled(enabled)
|
|
|
|
# Make sure workfile doc is overridden
|
|
self._workfile_doc = workfile_doc
|
|
# Disable inputs and remove texts if any required arguments are missing
|
|
if not enabled:
|
|
self._orig_note = ""
|
|
self._details_input.setPlainText("")
|
|
self._note_input.setPlainText("")
|
|
return
|
|
|
|
orig_note = ""
|
|
if workfile_doc:
|
|
orig_note = workfile_doc["data"].get("note") or orig_note
|
|
|
|
self._orig_note = orig_note
|
|
self._note_input.setPlainText(orig_note)
|
|
# Set as empty string
|
|
self._details_input.setPlainText("")
|
|
|
|
filestat = os.stat(filepath)
|
|
size_value = file_size_to_string(filestat.st_size)
|
|
|
|
# Append html string
|
|
datetime_format = "%b %d %Y %H:%M:%S"
|
|
creation_time = datetime.datetime.fromtimestamp(filestat.st_ctime)
|
|
modification_time = datetime.datetime.fromtimestamp(filestat.st_mtime)
|
|
lines = (
|
|
"<b>Size:</b>",
|
|
size_value,
|
|
"<b>Created:</b>",
|
|
creation_time.strftime(datetime_format),
|
|
"<b>Modified:</b>",
|
|
modification_time.strftime(datetime_format)
|
|
)
|
|
self._details_input.appendHtml("<br>".join(lines))
|
|
|
|
def get_workfile_data(self):
|
|
data = {
|
|
"note": self._note_input.toPlainText()
|
|
}
|
|
return self._workfile_doc, data
|
|
|
|
|
|
class Window(QtWidgets.QWidget):
|
|
"""Work Files Window"""
|
|
title = "Work Files"
|
|
|
|
def __init__(self, parent=None):
|
|
super(Window, self).__init__(parent=parent)
|
|
self.setWindowTitle(self.title)
|
|
icon = QtGui.QIcon(resources.get_openpype_icon_filepath())
|
|
self.setWindowIcon(icon)
|
|
self.setWindowFlags(self.windowFlags() | QtCore.Qt.Window)
|
|
|
|
# Create pages widget and set it as central widget
|
|
pages_widget = QtWidgets.QStackedWidget(self)
|
|
|
|
home_page_widget = QtWidgets.QWidget(pages_widget)
|
|
home_body_widget = QtWidgets.QWidget(home_page_widget)
|
|
|
|
assets_widget = SingleSelectAssetsWidget(
|
|
legacy_io, parent=home_body_widget
|
|
)
|
|
assets_widget.set_current_asset_btn_visibility(True)
|
|
|
|
tasks_widget = TasksWidget(legacy_io, home_body_widget)
|
|
files_widget = FilesWidget(home_body_widget)
|
|
side_panel = SidePanelWidget(home_body_widget)
|
|
|
|
pages_widget.addWidget(home_page_widget)
|
|
|
|
# Build home
|
|
home_page_layout = QtWidgets.QVBoxLayout(home_page_widget)
|
|
home_page_layout.addWidget(home_body_widget)
|
|
|
|
# Build home - body
|
|
body_layout = QtWidgets.QVBoxLayout(home_body_widget)
|
|
split_widget = QtWidgets.QSplitter(home_body_widget)
|
|
split_widget.addWidget(assets_widget)
|
|
split_widget.addWidget(tasks_widget)
|
|
split_widget.addWidget(files_widget)
|
|
split_widget.addWidget(side_panel)
|
|
split_widget.setSizes([255, 160, 455, 175])
|
|
|
|
body_layout.addWidget(split_widget)
|
|
|
|
# Add top margin for tasks to align it visually with files as
|
|
# the files widget has a filter field which tasks does not.
|
|
tasks_widget.setContentsMargins(0, 32, 0, 0)
|
|
|
|
main_layout = QtWidgets.QHBoxLayout(self)
|
|
main_layout.addWidget(pages_widget, 1)
|
|
|
|
# Set context after asset widget is refreshed
|
|
# - to do so it is necessary to wait until refresh is done
|
|
set_context_timer = QtCore.QTimer()
|
|
set_context_timer.setInterval(100)
|
|
|
|
# Connect signals
|
|
set_context_timer.timeout.connect(self._on_context_set_timeout)
|
|
assets_widget.selection_changed.connect(self._on_asset_changed)
|
|
tasks_widget.task_changed.connect(self._on_task_changed)
|
|
files_widget.file_selected.connect(self.on_file_select)
|
|
files_widget.workfile_created.connect(self.on_workfile_create)
|
|
files_widget.file_opened.connect(self._on_file_opened)
|
|
files_widget.published_visible_changed.connect(
|
|
self._on_published_change
|
|
)
|
|
side_panel.save_clicked.connect(self.on_side_panel_save)
|
|
|
|
self._set_context_timer = set_context_timer
|
|
self.home_page_widget = home_page_widget
|
|
self.pages_widget = pages_widget
|
|
self.home_body_widget = home_body_widget
|
|
self.split_widget = split_widget
|
|
|
|
self.assets_widget = assets_widget
|
|
self.tasks_widget = tasks_widget
|
|
self.files_widget = files_widget
|
|
self.side_panel = side_panel
|
|
|
|
# Force focus on the open button by default, required for Houdini.
|
|
files_widget.setFocus()
|
|
|
|
self.resize(1200, 600)
|
|
|
|
self._first_show = True
|
|
self._context_to_set = None
|
|
|
|
def ensure_visible(
|
|
self, use_context=None, save=None, on_top=None
|
|
):
|
|
if save is None:
|
|
save = True
|
|
|
|
self.set_save_enabled(save)
|
|
|
|
if self.isVisible():
|
|
use_context = False
|
|
elif use_context is None:
|
|
use_context = True
|
|
|
|
if on_top is None and self._first_show:
|
|
on_top = self.parent() is None
|
|
|
|
window_flags = self.windowFlags()
|
|
new_window_flags = window_flags
|
|
if on_top is True:
|
|
new_window_flags = window_flags | QtCore.Qt.WindowStaysOnTopHint
|
|
elif on_top is False:
|
|
new_window_flags = window_flags & ~QtCore.Qt.WindowStaysOnTopHint
|
|
|
|
if new_window_flags != window_flags:
|
|
# Note this is not propagated after initialization of widget in
|
|
# some Qt builds
|
|
self.setWindowFlags(new_window_flags)
|
|
self.show()
|
|
|
|
elif not self.isVisible():
|
|
self.show()
|
|
|
|
if use_context is None or use_context is True:
|
|
context = {
|
|
"asset": legacy_io.Session["AVALON_ASSET"],
|
|
"task": legacy_io.Session["AVALON_TASK"]
|
|
}
|
|
self.set_context(context)
|
|
|
|
# Pull window to the front.
|
|
self.raise_()
|
|
self.activateWindow()
|
|
|
|
@property
|
|
def project_name(self):
|
|
return legacy_io.Session["AVALON_PROJECT"]
|
|
|
|
def showEvent(self, event):
|
|
super(Window, self).showEvent(event)
|
|
if self._first_show:
|
|
self._first_show = False
|
|
self.refresh()
|
|
self.setStyleSheet(style.load_stylesheet())
|
|
|
|
def keyPressEvent(self, event):
|
|
"""Custom keyPressEvent.
|
|
|
|
Override keyPressEvent to do nothing so that Maya's panels won't
|
|
take focus when pressing "SHIFT" whilst mouse is over viewport or
|
|
outliner. This way users don't accidentally perform Maya commands
|
|
whilst trying to name an instance.
|
|
|
|
"""
|
|
|
|
def set_save_enabled(self, enabled):
|
|
self.files_widget.set_save_enabled(enabled)
|
|
|
|
def on_file_select(self, filepath):
|
|
asset_id = self.assets_widget.get_selected_asset_id()
|
|
task_name = self.tasks_widget.get_selected_task_name()
|
|
|
|
workfile_doc = None
|
|
if asset_id and task_name and filepath:
|
|
filename = os.path.split(filepath)[1]
|
|
project_name = legacy_io.active_project()
|
|
workfile_doc = get_workfile_info(
|
|
project_name, asset_id, task_name, filename
|
|
)
|
|
self.side_panel.set_context(
|
|
asset_id, task_name, filepath, workfile_doc
|
|
)
|
|
|
|
def on_workfile_create(self, filepath):
|
|
self._create_workfile_doc(filepath)
|
|
|
|
def _on_file_opened(self):
|
|
self.close()
|
|
|
|
def _on_published_change(self, visible):
|
|
self.side_panel.set_published_visible(visible)
|
|
|
|
def on_side_panel_save(self):
|
|
workfile_doc, data = self.side_panel.get_workfile_data()
|
|
if not workfile_doc:
|
|
filepath = self.files_widget._get_selected_filepath()
|
|
self._create_workfile_doc(filepath, force=True)
|
|
workfile_doc = self._get_current_workfile_doc()
|
|
|
|
save_workfile_data_to_doc(workfile_doc, data, legacy_io)
|
|
|
|
def _get_current_workfile_doc(self, filepath=None):
|
|
if filepath is None:
|
|
filepath = self.files_widget._get_selected_filepath()
|
|
task_name = self.tasks_widget.get_selected_task_name()
|
|
asset_id = self.assets_widget.get_selected_asset_id()
|
|
if not task_name or not asset_id or not filepath:
|
|
return
|
|
|
|
filename = os.path.split(filepath)[1]
|
|
project_name = legacy_io.active_project()
|
|
return get_workfile_info(
|
|
project_name, asset_id, task_name, filename
|
|
)
|
|
|
|
def _create_workfile_doc(self, filepath, force=False):
|
|
workfile_doc = None
|
|
if not force:
|
|
workfile_doc = self._get_current_workfile_doc(filepath)
|
|
|
|
if not workfile_doc:
|
|
workdir, filename = os.path.split(filepath)
|
|
asset_id = self.assets_widget.get_selected_asset_id()
|
|
project_name = legacy_io.active_project()
|
|
asset_doc = get_asset_by_id(project_name, asset_id)
|
|
task_name = self.tasks_widget.get_selected_task_name()
|
|
create_workfile_doc(
|
|
asset_doc, task_name, filename, workdir, legacy_io
|
|
)
|
|
|
|
def refresh(self):
|
|
# Refresh asset widget
|
|
self.assets_widget.refresh()
|
|
|
|
self._on_task_changed()
|
|
|
|
def set_context(self, context):
|
|
self._context_to_set = context
|
|
self._set_context_timer.start()
|
|
|
|
def _on_context_set_timeout(self):
|
|
if self._context_to_set is None:
|
|
self._set_context_timer.stop()
|
|
return
|
|
|
|
if self.assets_widget.refreshing:
|
|
return
|
|
|
|
self._set_context_timer.stop()
|
|
self._context_to_set, context = None, self._context_to_set
|
|
if "asset" in context:
|
|
asset_doc = get_asset_by_name(
|
|
self.project_name, context["asset"], fields=["_id"]
|
|
)
|
|
|
|
asset_id = None
|
|
if asset_doc:
|
|
asset_id = asset_doc["_id"]
|
|
# Select the asset
|
|
self.assets_widget.select_asset(asset_id)
|
|
self.tasks_widget.set_asset_id(asset_id)
|
|
|
|
if "task" in context:
|
|
self.tasks_widget.select_task_name(context["task"])
|
|
self._on_task_changed()
|
|
|
|
def _on_asset_changed(self):
|
|
asset_id = self.assets_widget.get_selected_asset_id()
|
|
if asset_id:
|
|
self.tasks_widget.setEnabled(True)
|
|
else:
|
|
# Force disable the other widgets if no
|
|
# active selection
|
|
self.tasks_widget.setEnabled(False)
|
|
self.files_widget.setEnabled(False)
|
|
|
|
self.tasks_widget.set_asset_id(asset_id)
|
|
|
|
def _on_task_changed(self):
|
|
asset_id = self.assets_widget.get_selected_asset_id()
|
|
task_name = self.tasks_widget.get_selected_task_name()
|
|
task_type = self.tasks_widget.get_selected_task_type()
|
|
|
|
asset_is_valid = asset_id is not None
|
|
self.tasks_widget.setEnabled(asset_is_valid)
|
|
|
|
self.files_widget.setEnabled(bool(task_name) and asset_is_valid)
|
|
self.files_widget.set_asset_task(asset_id, task_name, task_type)
|
|
self.files_widget.refresh()
|