From 7651ebd8521fd7f2408aaebb262a877ca8840d81 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Mar 2022 16:40:11 +0100 Subject: [PATCH 01/11] added option to save as to current context --- openpype/tools/workfiles/files_widget.py | 82 +++++++++++++++++------- 1 file changed, 59 insertions(+), 23 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index d2b8a76952..74729e5346 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -155,18 +155,33 @@ class FilesWidget(QtWidgets.QWidget): # Home Page # Build buttons widget for files widget btns_widget = QtWidgets.QWidget(self) - btn_save = QtWidgets.QPushButton("Save As", btns_widget) - btn_browse = QtWidgets.QPushButton("Browse", btns_widget) - btn_open = QtWidgets.QPushButton("Open", btns_widget) - btn_view_published = QtWidgets.QPushButton("View", btns_widget) + workarea_btns_widget = QtWidgets.QWidget(btns_widget) + btn_save = QtWidgets.QPushButton("Save As", workarea_btns_widget) + btn_browse = QtWidgets.QPushButton("Browse", workarea_btns_widget) + btn_open = QtWidgets.QPushButton("Open", workarea_btns_widget) + + workarea_btns_layout = QtWidgets.QHBoxLayout(workarea_btns_widget) + workarea_btns_layout.setContentsMargins(0, 0, 0, 0) + workarea_btns_layout.addWidget(btn_open, 1) + workarea_btns_layout.addWidget(btn_browse, 1) + workarea_btns_layout.addWidget(btn_save, 1) + + publish_btns_widget = QtWidgets.QWidget(btns_widget) + btn_view_published = QtWidgets.QPushButton("View", publish_btns_widget) + btn_save_as_published = QtWidgets.QPushButton( + "Save As", publish_btns_widget + ) + + publish_btns_layout = QtWidgets.QHBoxLayout(publish_btns_widget) + publish_btns_layout.setContentsMargins(0, 0, 0, 0) + publish_btns_layout.addWidget(btn_view_published, 1) + publish_btns_layout.addWidget(btn_save_as_published, 1) btns_layout = QtWidgets.QHBoxLayout(btns_widget) btns_layout.setContentsMargins(0, 0, 0, 0) - btns_layout.addWidget(btn_open, 1) - btns_layout.addWidget(btn_browse, 1) - btns_layout.addWidget(btn_save, 1) - btns_layout.addWidget(btn_view_published, 1) + btns_layout.addWidget(workarea_btns_widget, 1) + btns_layout.addWidget(publish_btns_widget, 1) # Build files widgets for home page main_layout = QtWidgets.QVBoxLayout(self) @@ -189,13 +204,16 @@ class FilesWidget(QtWidgets.QWidget): self.on_file_select ) publish_files_view.doubleClickedLeft.connect( - self._on_view_published_pressed + self._on_published_view_pressed ) btn_open.pressed.connect(self._on_workarea_open_pressed) btn_browse.pressed.connect(self.on_browse_pressed) - btn_save.pressed.connect(self.on_save_as_pressed) - btn_view_published.pressed.connect(self._on_view_published_pressed) + btn_save.pressed.connect(self._on_save_as_pressed) + btn_view_published.pressed.connect(self._on_published_view_pressed) + btn_save_as_published.pressed.connect( + self._on_published_save_as_pressed + ) # Store attributes self._published_checkbox = published_checkbox @@ -211,7 +229,8 @@ class FilesWidget(QtWidgets.QWidget): self._publish_files_model = publish_files_model self._publish_proxy_model = publish_proxy_model - self._btns_widget = btns_widget + self._workarea_btns_widget = workarea_btns_widget + self._publish_btns_widget = publish_btns_widget self._btn_open = btn_open self._btn_browse = btn_browse self._btn_save = btn_save @@ -222,7 +241,7 @@ class FilesWidget(QtWidgets.QWidget): # Hide publish files widgets publish_files_view.setVisible(False) - btn_view_published.setVisible(False) + publish_btns_widget.setVisible(False) @property def published_enabled(self): @@ -232,12 +251,10 @@ class FilesWidget(QtWidgets.QWidget): published_enabled = self.published_enabled self._workarea_files_view.setVisible(not published_enabled) - self._btn_open.setVisible(not published_enabled) - self._btn_browse.setVisible(not published_enabled) - self._btn_save.setVisible(not published_enabled) + self._workarea_btns_widget.setVisible(not published_enabled) self._publish_files_view.setVisible(published_enabled) - self._btn_view_published.setVisible(published_enabled) + self._publish_btns_widget.setVisible(published_enabled) self._update_filtering() self._update_asset_task() @@ -462,11 +479,16 @@ class FilesWidget(QtWidgets.QWidget): if work_file: self.open_file(work_file) - def on_save_as_pressed(self): + def _on_save_as_pressed(self): + self._save_as_with_dialog() + + def _save_as_with_dialog(self): work_filename = self.get_filename() if not work_filename: return + src_path = self._get_selected_filepath() + # Trigger before save event emit_event( "workfile.save.before", @@ -486,13 +508,20 @@ class FilesWidget(QtWidgets.QWidget): log.debug("Initializing Work Directory: %s", self._workfiles_root) os.makedirs(self._workfiles_root) - # Update session if context has changed - self._enter_session() # Prepare full path to workfile and save it filepath = os.path.join( os.path.normpath(self._workfiles_root), work_filename ) - self.host.save_file(filepath) + + # Update session if context has changed + self._enter_session() + + if not self.published_enabled: + self.host.save_file(filepath) + else: + shutil.copy(src_path, filepath) + self.host.open_file(filepath) + # Create extra folders create_workdir_extra_folders( self._workdir_path, @@ -510,9 +539,12 @@ class FilesWidget(QtWidgets.QWidget): self.workfile_created.emit(filepath) # Refresh files model - self.refresh() + if self.published_enabled: + self._published_checkbox.setChecked(False) + else: + self.refresh() - def _on_view_published_pressed(self): + def _on_published_view_pressed(self): filepath = self._get_selected_filepath() if not filepath or not os.path.exists(filepath): return @@ -522,6 +554,10 @@ class FilesWidget(QtWidgets.QWidget): # Change state back to workarea self._published_checkbox.setChecked(False) + def _on_published_save_as_pressed(self): + self._save_as_with_dialog() + + def on_file_select(self): self.file_selected.emit(self._get_selected_filepath()) From bf0bc2436e1f0a085ca30381e2b5285cadf70320 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Mar 2022 16:46:38 +0100 Subject: [PATCH 02/11] added option to save as to context --- openpype/tools/workfiles/files_widget.py | 81 +++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 74729e5346..1faafe2bdb 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -172,11 +172,23 @@ class FilesWidget(QtWidgets.QWidget): btn_save_as_published = QtWidgets.QPushButton( "Save As", publish_btns_widget ) + btn_save_as_to_published = QtWidgets.QPushButton( + "Save As (to context)", publish_btns_widget + ) + btn_select_context_published = QtWidgets.QPushButton( + "Select context", publish_btns_widget + ) + btn_cancel_published = QtWidgets.QPushButton( + "Cancel", publish_btns_widget + ) publish_btns_layout = QtWidgets.QHBoxLayout(publish_btns_widget) publish_btns_layout.setContentsMargins(0, 0, 0, 0) publish_btns_layout.addWidget(btn_view_published, 1) publish_btns_layout.addWidget(btn_save_as_published, 1) + publish_btns_layout.addWidget(btn_save_as_to_published, 1) + publish_btns_layout.addWidget(btn_cancel_published, 1) + publish_btns_layout.addWidget(btn_select_context_published, 1) btns_layout = QtWidgets.QHBoxLayout(btns_widget) btns_layout.setContentsMargins(0, 0, 0, 0) @@ -214,6 +226,15 @@ class FilesWidget(QtWidgets.QWidget): btn_save_as_published.pressed.connect( self._on_published_save_as_pressed ) + btn_save_as_to_published.pressed.connect( + self._on_publish_save_as_to_pressed + ) + btn_select_context_published.pressed.connect( + self._on_publish_select_context_pressed + ) + btn_cancel_published.pressed.connect( + self._on_publish_cancel_pressed + ) # Store attributes self._published_checkbox = published_checkbox @@ -234,7 +255,12 @@ class FilesWidget(QtWidgets.QWidget): self._btn_open = btn_open self._btn_browse = btn_browse self._btn_save = btn_save + self._btn_view_published = btn_view_published + self._btn_save_as_published = btn_save_as_published + self._btn_save_as_to_published = btn_save_as_to_published + self._btn_select_context_published = btn_select_context_published + self._btn_cancel_published = btn_cancel_published # Create a proxy widget for files widget self.setFocusProxy(btn_open) @@ -242,6 +268,10 @@ class FilesWidget(QtWidgets.QWidget): # Hide publish files widgets publish_files_view.setVisible(False) publish_btns_widget.setVisible(False) + btn_select_context_published.setVisible(False) + btn_cancel_published.setVisible(False) + + self._publish_context_select_mode = False @property def published_enabled(self): @@ -285,12 +315,15 @@ class FilesWidget(QtWidgets.QWidget): self._update_asset_task() def _update_asset_task(self): - if self.published_enabled: + if self.published_enabled and not self._publish_context_select_mode: self._publish_files_model.set_context( self._asset_id, self._task_name ) has_valid_items = self._publish_files_model.has_valid_items() self._btn_view_published.setEnabled(has_valid_items) + self._btn_save_as_published.setEnabled(has_valid_items) + self._btn_save_as_to_published.setEnabled(has_valid_items) + else: # Define a custom session so we can query the work root # for a "Work area" that is not our current Session. @@ -308,6 +341,13 @@ class FilesWidget(QtWidgets.QWidget): has_valid_items = self._workarea_files_model.has_valid_items() self._btn_browse.setEnabled(has_valid_items) self._btn_open.setEnabled(has_valid_items) + + if self._publish_context_select_mode: + self._btn_select_context_published.setEnabled( + bool(self._asset_id) and bool(self._task_name) + ) + return + # Manually trigger file selection if not has_valid_items: self.on_file_select() @@ -557,6 +597,45 @@ class FilesWidget(QtWidgets.QWidget): def _on_published_save_as_pressed(self): self._save_as_with_dialog() + def _set_publish_context_select_mode(self, enabled): + self._publish_context_select_mode = enabled + + # Show buttons related to context selection + self._btn_cancel_published.setVisible(enabled) + self._btn_select_context_published.setVisible(enabled) + # Change enabled state based on select context + self._btn_select_context_published.setEnabled( + bool(self._asset_id) and bool(self._task_name) + ) + + self._btn_view_published.setVisible(not enabled) + self._btn_save_as_published.setVisible(not enabled) + self._btn_save_as_to_published.setVisible(not enabled) + + # Change views and disable workarea view if enabled + self._workarea_files_view.setEnabled(not enabled) + if self.published_enabled: + self._workarea_files_view.setVisible(enabled) + self._publish_files_view.setVisible(not enabled) + else: + self._workarea_files_view.setVisible(True) + self._publish_files_view.setVisible(False) + + # Disable filter widgets + self._published_checkbox.setEnabled(not enabled) + self._filter_input.setEnabled(not enabled) + + def _on_publish_save_as_to_pressed(self): + self._set_publish_context_select_mode(True) + + def _on_publish_select_context_pressed(self): + self._save_as_with_dialog() + self._set_publish_context_select_mode(False) + self._update_asset_task() + + def _on_publish_cancel_pressed(self): + self._set_publish_context_select_mode(False) + self._update_asset_task() def on_file_select(self): self.file_selected.emit(self._get_selected_filepath()) From eac1394be8b1644468f8b6427c5abe37bedd3f97 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 31 Mar 2022 12:12:56 +0200 Subject: [PATCH 03/11] hide published checkbox if save is not enabled --- openpype/tools/workfiles/files_widget.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 1faafe2bdb..1f93d15e22 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -305,6 +305,9 @@ class FilesWidget(QtWidgets.QWidget): def set_save_enabled(self, enabled): self._btn_save.setEnabled(enabled) + if not enabled and self._published_checkbox.isChecked(): + self._published_checkbox.setChecked(False) + self._published_checkbox.setVisible(enabled) def set_asset_task(self, asset_id, task_name, task_type): if asset_id != self._asset_id: From 8e382b9c52feb0bcc45c235a5121088552e7d01c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 31 Mar 2022 12:17:19 +0200 Subject: [PATCH 04/11] removed View option from published files --- openpype/tools/workfiles/files_widget.py | 46 +--- openpype/tools/workfiles/lib.py | 272 ----------------------- openpype/tools/workfiles/window.py | 65 ++---- 3 files changed, 27 insertions(+), 356 deletions(-) delete mode 100644 openpype/tools/workfiles/lib.py diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 1f93d15e22..80a94cc1bd 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -26,7 +26,6 @@ from .model import ( DATE_MODIFIED_ROLE, ) from .save_as_dialog import SaveAsDialog -from .lib import TempPublishFiles log = logging.getLogger(__name__) @@ -49,7 +48,6 @@ class FilesWidget(QtWidgets.QWidget): """A widget displaying files that allows to save and open files.""" file_selected = QtCore.Signal(str) file_opened = QtCore.Signal() - publish_file_viewed = QtCore.Signal() workfile_created = QtCore.Signal(str) published_visible_changed = QtCore.Signal(bool) @@ -71,9 +69,6 @@ class FilesWidget(QtWidgets.QWidget): self._workfiles_root = None self._workdir_path = None self.host = api.registered_host() - temp_publish_files = TempPublishFiles() - temp_publish_files.cleanup() - self._temp_publish_files = temp_publish_files # Whether to automatically select the latest modified # file on a refresh of the files model. @@ -168,15 +163,14 @@ class FilesWidget(QtWidgets.QWidget): workarea_btns_layout.addWidget(btn_save, 1) publish_btns_widget = QtWidgets.QWidget(btns_widget) - btn_view_published = QtWidgets.QPushButton("View", publish_btns_widget) btn_save_as_published = QtWidgets.QPushButton( - "Save As", publish_btns_widget + "Copy & Open", publish_btns_widget ) - btn_save_as_to_published = QtWidgets.QPushButton( - "Save As (to context)", publish_btns_widget + btn_change_context = QtWidgets.QPushButton( + "Choose different context", publish_btns_widget ) btn_select_context_published = QtWidgets.QPushButton( - "Select context", publish_btns_widget + "Copy & Open", publish_btns_widget ) btn_cancel_published = QtWidgets.QPushButton( "Cancel", publish_btns_widget @@ -184,9 +178,8 @@ class FilesWidget(QtWidgets.QWidget): publish_btns_layout = QtWidgets.QHBoxLayout(publish_btns_widget) publish_btns_layout.setContentsMargins(0, 0, 0, 0) - publish_btns_layout.addWidget(btn_view_published, 1) publish_btns_layout.addWidget(btn_save_as_published, 1) - publish_btns_layout.addWidget(btn_save_as_to_published, 1) + publish_btns_layout.addWidget(btn_change_context, 1) publish_btns_layout.addWidget(btn_cancel_published, 1) publish_btns_layout.addWidget(btn_select_context_published, 1) @@ -215,19 +208,15 @@ class FilesWidget(QtWidgets.QWidget): workarea_files_view.selectionModel().selectionChanged.connect( self.on_file_select ) - publish_files_view.doubleClickedLeft.connect( - self._on_published_view_pressed - ) btn_open.pressed.connect(self._on_workarea_open_pressed) btn_browse.pressed.connect(self.on_browse_pressed) btn_save.pressed.connect(self._on_save_as_pressed) - btn_view_published.pressed.connect(self._on_published_view_pressed) btn_save_as_published.pressed.connect( self._on_published_save_as_pressed ) - btn_save_as_to_published.pressed.connect( - self._on_publish_save_as_to_pressed + btn_change_context.pressed.connect( + self._on_publish_change_context_pressed ) btn_select_context_published.pressed.connect( self._on_publish_select_context_pressed @@ -256,9 +245,8 @@ class FilesWidget(QtWidgets.QWidget): self._btn_browse = btn_browse self._btn_save = btn_save - self._btn_view_published = btn_view_published self._btn_save_as_published = btn_save_as_published - self._btn_save_as_to_published = btn_save_as_to_published + self._btn_change_context = btn_change_context self._btn_select_context_published = btn_select_context_published self._btn_cancel_published = btn_cancel_published @@ -323,9 +311,8 @@ class FilesWidget(QtWidgets.QWidget): self._asset_id, self._task_name ) has_valid_items = self._publish_files_model.has_valid_items() - self._btn_view_published.setEnabled(has_valid_items) self._btn_save_as_published.setEnabled(has_valid_items) - self._btn_save_as_to_published.setEnabled(has_valid_items) + self._btn_change_context.setEnabled(has_valid_items) else: # Define a custom session so we can query the work root @@ -587,16 +574,6 @@ class FilesWidget(QtWidgets.QWidget): else: self.refresh() - def _on_published_view_pressed(self): - filepath = self._get_selected_filepath() - if not filepath or not os.path.exists(filepath): - return - item = self._temp_publish_files.add_file(filepath) - self.host.open_file(item.filepath) - self.publish_file_viewed.emit() - # Change state back to workarea - self._published_checkbox.setChecked(False) - def _on_published_save_as_pressed(self): self._save_as_with_dialog() @@ -611,9 +588,8 @@ class FilesWidget(QtWidgets.QWidget): bool(self._asset_id) and bool(self._task_name) ) - self._btn_view_published.setVisible(not enabled) self._btn_save_as_published.setVisible(not enabled) - self._btn_save_as_to_published.setVisible(not enabled) + self._btn_change_context.setVisible(not enabled) # Change views and disable workarea view if enabled self._workarea_files_view.setEnabled(not enabled) @@ -628,7 +604,7 @@ class FilesWidget(QtWidgets.QWidget): self._published_checkbox.setEnabled(not enabled) self._filter_input.setEnabled(not enabled) - def _on_publish_save_as_to_pressed(self): + def _on_publish_change_context_pressed(self): self._set_publish_context_select_mode(True) def _on_publish_select_context_pressed(self): diff --git a/openpype/tools/workfiles/lib.py b/openpype/tools/workfiles/lib.py deleted file mode 100644 index 21a7485b7b..0000000000 --- a/openpype/tools/workfiles/lib.py +++ /dev/null @@ -1,272 +0,0 @@ -import os -import shutil -import uuid -import time -import json -import logging -import contextlib - -import appdirs - - -class TempPublishFilesItem(object): - """Object representing copied workfile in app temp folder. - - Args: - item_id (str): Id of item used as subfolder. - data (dict): Metadata about temp files. - directory (str): Path to directory where files are copied to. - """ - - def __init__(self, item_id, data, directory): - self._id = item_id - self._directory = directory - self._filepath = os.path.join(directory, data["filename"]) - - @property - def directory(self): - return self._directory - - @property - def filepath(self): - return self._filepath - - @property - def id(self): - return self._id - - @property - def size(self): - if os.path.exists(self.filepath): - s = os.stat(self.filepath) - return s.st_size - return 0 - - -class TempPublishFiles(object): - """Directory where published workfiles are copied when opened. - - Directory is located in appdirs on the machine. Folder contains file - with metadata about stored files. Each item in metadata has id, filename - and expiration time. When expiration time is higher then current time the - item is removed from metadata and it's files are deleted. Files of items - are stored in subfolder named by item's id. - - Metadata file can be in theory opened and modified by multiple processes, - threads at one time. For those cases is created simple lock file which - is created before modification begins and is removed when modification - ends. Existence of the file means that it should not be modified by - any other process at the same time. - - Metadata example: - ``` - { - "96050b4a-8974-4fca-8179-7c446c478d54": { - "created": 1647880725.555, - "expiration": 1647884325.555, - "filename": "cg_pigeon_workfileModeling_v025.ma" - }, - ... - } - ``` - - ## Why is this needed - Combination of more issues. Temp files are not automatically removed by - OS on windows so using tempfiles in TEMP would lead to kill disk space of - machine. There are also cases when someone wants to open multiple files - in short period of time and want to manually remove those files so keeping - track of temporary copied files in pre-defined structure is needed. - """ - minute_in_seconds = 60 - hour_in_seconds = 60 * minute_in_seconds - day_in_seconds = 24 * hour_in_seconds - - def __init__(self): - root_dir = appdirs.user_data_dir( - "published_workfiles_temp", "openpype" - ) - if not os.path.exists(root_dir): - os.makedirs(root_dir) - - metadata_path = os.path.join(root_dir, "metadata.json") - lock_path = os.path.join(root_dir, "lock.json") - - self._root_dir = root_dir - self._metadata_path = metadata_path - self._lock_path = lock_path - self._log = None - - @property - def log(self): - if self._log is None: - self._log = logging.getLogger(self.__class__.__name__) - return self._log - - @property - def life_time(self): - """How long will be new item kept in temp in seconds. - - Returns: - int: Lifetime of temp item. - """ - return int(self.hour_in_seconds) - - @property - def size(self): - """File size of existing items.""" - size = 0 - for item in self.get_items(): - size += item.size - return size - - def add_file(self, src_path): - """Add workfile to temp directory. - - This will create new item and source path is copied to it's directory. - """ - filename = os.path.basename(src_path) - - item_id = str(uuid.uuid4()) - dst_dirpath = os.path.join(self._root_dir, item_id) - if not os.path.exists(dst_dirpath): - os.makedirs(dst_dirpath) - - dst_path = os.path.join(dst_dirpath, filename) - shutil.copy(src_path, dst_path) - - now = time.time() - item_data = { - "filename": filename, - "expiration": now + self.life_time, - "created": now - } - with self._modify_data() as data: - data[item_id] = item_data - - return TempPublishFilesItem(item_id, item_data, dst_dirpath) - - @contextlib.contextmanager - def _modify_data(self): - """Create lock file when data in metadata file are modified.""" - start_time = time.time() - timeout = 3 - while os.path.exists(self._lock_path): - time.sleep(0.01) - if start_time > timeout: - self.log.warning(( - "Waited for {} seconds to free lock file. Overriding lock." - ).format(timeout)) - - with open(self._lock_path, "w") as stream: - json.dump({"pid": os.getpid()}, stream) - - try: - data = self._get_data() - yield data - with open(self._metadata_path, "w") as stream: - json.dump(data, stream) - - finally: - os.remove(self._lock_path) - - def _get_data(self): - output = {} - if not os.path.exists(self._metadata_path): - return output - - try: - with open(self._metadata_path, "r") as stream: - output = json.load(stream) - except Exception: - self.log.warning("Failed to read metadata file.", exc_info=True) - return output - - def cleanup(self, check_expiration=True): - """Cleanup files based on metadata. - - Items that passed expiration are removed when this is called. Or all - files are removed when `check_expiration` is set to False. - - Args: - check_expiration (bool): All items and files are removed when set - to True. - """ - data = self._get_data() - now = time.time() - remove_ids = set() - all_ids = set() - for item_id, item_data in data.items(): - all_ids.add(item_id) - if check_expiration and now < item_data["expiration"]: - continue - - remove_ids.add(item_id) - - for item_id in remove_ids: - try: - self.remove_id(item_id) - except Exception: - self.log.warning( - "Failed to remove temp publish item \"{}\"".format( - item_id - ), - exc_info=True - ) - - # Remove unknown folders/files - for filename in os.listdir(self._root_dir): - if filename in all_ids: - continue - - full_path = os.path.join(self._root_dir, filename) - if full_path in (self._metadata_path, self._lock_path): - continue - - try: - shutil.rmtree(full_path) - except Exception: - self.log.warning( - "Couldn't remove arbitrary path \"{}\"".format(full_path), - exc_info=True - ) - - def clear(self): - self.cleanup(False) - - def get_items(self): - """Receive all items from metadata file. - - Returns: - list: Info about each item in metadata. - """ - output = [] - data = self._get_data() - for item_id, item_data in data.items(): - item_path = os.path.join(self._root_dir, item_id) - output.append(TempPublishFilesItem(item_id, item_data, item_path)) - return output - - def remove_id(self, item_id): - """Remove files of item and then remove the item from metadata.""" - filepath = os.path.join(self._root_dir, item_id) - if os.path.exists(filepath): - shutil.rmtree(filepath) - - with self._modify_data() as data: - data.pop(item_id, None) - - -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) diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index 8654a18036..73e63d30b5 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -14,7 +14,22 @@ from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget from openpype.tools.utils.tasks_widget import TasksWidget from .files_widget import FilesWidget -from .lib import TempPublishFiles, file_size_to_string + + +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): @@ -44,67 +59,25 @@ class SidePanelWidget(QtWidgets.QWidget): btn_note_save, 0, alignment=QtCore.Qt.AlignRight ) - publish_temp_widget = QtWidgets.QWidget(self) - publish_temp_info_label = QtWidgets.QLabel( - self.published_workfile_message.format( - file_size_to_string(0) - ), - publish_temp_widget - ) - publish_temp_info_label.setWordWrap(True) - - btn_clear_temp = QtWidgets.QPushButton( - "Clear temp", publish_temp_widget - ) - - publish_temp_layout = QtWidgets.QVBoxLayout(publish_temp_widget) - publish_temp_layout.setContentsMargins(0, 0, 0, 0) - publish_temp_layout.addWidget(publish_temp_info_label, 0) - publish_temp_layout.addWidget( - btn_clear_temp, 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) - main_layout.addWidget(publish_temp_widget, 0) note_input.textChanged.connect(self._on_note_change) btn_note_save.clicked.connect(self._on_save_click) - btn_clear_temp.clicked.connect(self._on_clear_temp_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._publish_temp_info_label = publish_temp_info_label - self._publish_temp_widget = publish_temp_widget - self._orig_note = "" self._workfile_doc = None - publish_temp_widget.setVisible(False) - def set_published_visible(self, published_visible): self._artist_note_widget.setVisible(not published_visible) - self._publish_temp_widget.setVisible(published_visible) - if published_visible: - self.refresh_publish_temp_sizes() - - def refresh_publish_temp_sizes(self): - temp_publish_files = TempPublishFiles() - text = self.published_workfile_message.format( - file_size_to_string(temp_publish_files.size) - ) - self._publish_temp_info_label.setText(text) - - def _on_clear_temp_click(self): - temp_publish_files = TempPublishFiles() - temp_publish_files.clear() - self.refresh_publish_temp_sizes() def _on_note_change(self): text = self._note_input.toPlainText() @@ -225,9 +198,6 @@ class Window(QtWidgets.QMainWindow): 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.publish_file_viewed.connect( - self._on_publish_file_viewed - ) files_widget.published_visible_changed.connect( self._on_published_change ) @@ -292,9 +262,6 @@ class Window(QtWidgets.QMainWindow): def _on_file_opened(self): self.close() - def _on_publish_file_viewed(self): - self.side_panel.refresh_publish_temp_sizes() - def _on_published_change(self, visible): self.side_panel.set_published_visible(visible) From 34656d9b79536155eca3a5729cf6d0b449c6633d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 31 Mar 2022 12:29:29 +0200 Subject: [PATCH 05/11] fix ampresand in button label --- openpype/tools/workfiles/files_widget.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 80a94cc1bd..f29223b321 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -95,7 +95,7 @@ class FilesWidget(QtWidgets.QWidget): extensions = set(self.host.file_extensions()) views_widget = QtWidgets.QWidget(self) - # Workarea view + # --- Workarea view --- workarea_files_model = WorkAreaFilesModel(extensions) # Create proxy model for files to be able sort and filter @@ -113,13 +113,14 @@ class FilesWidget(QtWidgets.QWidget): # Date modified delegate workarea_time_delegate = PrettyTimeDelegate() workarea_files_view.setItemDelegateForColumn(1, workarea_time_delegate) - workarea_files_view.setIndentation(3) # smaller indentation + # smaller indentation + workarea_files_view.setIndentation(3) # Default to a wider first filename column it is what we mostly care # about and the date modified is relatively small anyway. workarea_files_view.setColumnWidth(0, 330) - # Publish files view + # --- Publish files view --- publish_files_model = PublishFilesModel(extensions, io, self.anatomy) publish_proxy_model = QtCore.QSortFilterProxyModel() @@ -136,7 +137,8 @@ class FilesWidget(QtWidgets.QWidget): # Date modified delegate publish_time_delegate = PrettyTimeDelegate() publish_files_view.setItemDelegateForColumn(1, publish_time_delegate) - publish_files_view.setIndentation(3) # smaller indentation + # smaller indentation + publish_files_view.setIndentation(3) # Default to a wider first filename column it is what we mostly care # about and the date modified is relatively small anyway. @@ -164,13 +166,13 @@ class FilesWidget(QtWidgets.QWidget): publish_btns_widget = QtWidgets.QWidget(btns_widget) btn_save_as_published = QtWidgets.QPushButton( - "Copy & Open", publish_btns_widget + "Copy && Open", publish_btns_widget ) btn_change_context = QtWidgets.QPushButton( "Choose different context", publish_btns_widget ) btn_select_context_published = QtWidgets.QPushButton( - "Copy & Open", publish_btns_widget + "Copy && Open", publish_btns_widget ) btn_cancel_published = QtWidgets.QPushButton( "Cancel", publish_btns_widget From aa8438a6ad2e947038af43bef93d1dd686267aea Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 31 Mar 2022 12:29:50 +0200 Subject: [PATCH 06/11] define extensions of save as dialog with argument --- openpype/tools/workfiles/files_widget.py | 7 +++++++ openpype/tools/workfiles/save_as_dialog.py | 11 +++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index f29223b321..6e90dea982 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -449,11 +449,18 @@ class FilesWidget(QtWidgets.QWidget): """ session = self._get_session() + if self.published_enabled: + filepath = self._get_selected_filepath() + extensions = [os.path.splitext(filepath)[1]] + else: + extensions = self.host.file_extensions() + window = SaveAsDialog( parent=self, root=self._workfiles_root, anatomy=self.anatomy, template_key=self.template_key, + extensions=extensions, session=session ) window.exec_() diff --git a/openpype/tools/workfiles/save_as_dialog.py b/openpype/tools/workfiles/save_as_dialog.py index e616a325cc..f5ae393d0f 100644 --- a/openpype/tools/workfiles/save_as_dialog.py +++ b/openpype/tools/workfiles/save_as_dialog.py @@ -193,7 +193,9 @@ class SaveAsDialog(QtWidgets.QDialog): """ - def __init__(self, parent, root, anatomy, template_key, session=None): + def __init__( + self, parent, root, anatomy, template_key, extensions, session=None + ): super(SaveAsDialog, self).__init__(parent=parent) self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) @@ -201,6 +203,7 @@ class SaveAsDialog(QtWidgets.QDialog): self.host = api.registered_host() self.root = root self.work_file = None + self._extensions = extensions if not session: # Fallback to active session @@ -257,7 +260,7 @@ class SaveAsDialog(QtWidgets.QDialog): # Add styled delegate to use stylesheets ext_delegate = QtWidgets.QStyledItemDelegate() ext_combo.setItemDelegate(ext_delegate) - ext_combo.addItems(self.host.file_extensions()) + ext_combo.addItems(self._extensions) # Build inputs inputs_layout = QtWidgets.QFormLayout(inputs_widget) @@ -336,7 +339,7 @@ class SaveAsDialog(QtWidgets.QDialog): def get_existing_comments(self): matcher = CommentMatcher(self.anatomy, self.template_key, self.data) - host_extensions = set(self.host.file_extensions()) + host_extensions = set(self._extensions) comments = set() if os.path.isdir(self.root): for fname in os.listdir(self.root): @@ -392,7 +395,7 @@ class SaveAsDialog(QtWidgets.QDialog): return anatomy_filled[self.template_key]["file"] def refresh(self): - extensions = self.host.file_extensions() + extensions = list(self._extensions) extension = self.data["ext"] if extension is None: # Define saving file extension From 37900da59d1c5ba718c20cc3266c3bb62c9ee987 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 31 Mar 2022 14:32:40 +0200 Subject: [PATCH 07/11] added overlay guiding to select context --- openpype/style/style.css | 8 ++++++ openpype/tools/workfiles/files_widget.py | 31 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index df83600973..b5f6962eee 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -1269,6 +1269,14 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: #21252B; } +/* Workfiles */ +#WorkfilesPublishedContextSelect { + background: rgba(0, 0, 0, 127); +} +#WorkfilesPublishedContextSelect QLabel { + font-size: 17pt; +} + /* Tray */ #TrayRestartButton { background: {color:restart-btn-bg}; diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 6e90dea982..55abd39b36 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -44,6 +44,31 @@ class FilesView(QtWidgets.QTreeView): return super(FilesView, self).mouseDoubleClickEvent(event) +class SelectContextOverlay(QtWidgets.QFrame): + def __init__(self, parent): + super(SelectContextOverlay, self).__init__(parent) + + self.setObjectName("WorkfilesPublishedContextSelect") + label_widget = QtWidgets.QLabel( + "Please select context on Left side
<", + self + ) + label_widget.setAlignment(QtCore.Qt.AlignCenter) + + layout = QtWidgets.QHBoxLayout(self) + layout.addWidget(label_widget, 1, QtCore.Qt.AlignCenter) + + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + parent.installEventFilter(self) + + def eventFilter(self, obj, event): + if event.type() == QtCore.QEvent.Resize: + self.resize(obj.size()) + + return super(SelectContextOverlay, self).eventFilter(obj, event) + + class FilesWidget(QtWidgets.QWidget): """A widget displaying files that allows to save and open files.""" file_selected = QtCore.Signal(str) @@ -144,6 +169,9 @@ class FilesWidget(QtWidgets.QWidget): # about and the date modified is relatively small anyway. publish_files_view.setColumnWidth(0, 330) + publish_context_overlay = SelectContextOverlay(views_widget) + publish_context_overlay.setVisible(False) + views_layout = QtWidgets.QHBoxLayout(views_widget) views_layout.setContentsMargins(0, 0, 0, 0) views_layout.addWidget(workarea_files_view, 1) @@ -241,6 +269,8 @@ class FilesWidget(QtWidgets.QWidget): self._publish_files_model = publish_files_model self._publish_proxy_model = publish_proxy_model + self._publish_context_overlay = publish_context_overlay + self._workarea_btns_widget = workarea_btns_widget self._publish_btns_widget = publish_btns_widget self._btn_open = btn_open @@ -590,6 +620,7 @@ class FilesWidget(QtWidgets.QWidget): self._publish_context_select_mode = enabled # Show buttons related to context selection + self._publish_context_overlay.setVisible(enabled) self._btn_cancel_published.setVisible(enabled) self._btn_select_context_published.setVisible(enabled) # Change enabled state based on select context From 14da7f74c6ddedc4eeaa9fc3fb5b093bf630b2e8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 31 Mar 2022 14:33:02 +0200 Subject: [PATCH 08/11] don't cancel save as publishing on cancel save as dialog --- openpype/tools/workfiles/files_widget.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 55abd39b36..6cfd3dd651 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -554,7 +554,7 @@ class FilesWidget(QtWidgets.QWidget): def _save_as_with_dialog(self): work_filename = self.get_filename() if not work_filename: - return + return None src_path = self._get_selected_filepath() @@ -612,6 +612,7 @@ class FilesWidget(QtWidgets.QWidget): self._published_checkbox.setChecked(False) else: self.refresh() + return filepath def _on_published_save_as_pressed(self): self._save_as_with_dialog() @@ -648,9 +649,10 @@ class FilesWidget(QtWidgets.QWidget): self._set_publish_context_select_mode(True) def _on_publish_select_context_pressed(self): - self._save_as_with_dialog() - self._set_publish_context_select_mode(False) - self._update_asset_task() + result = self._save_as_with_dialog() + if result is not None: + self._set_publish_context_select_mode(False) + self._update_asset_task() def _on_publish_cancel_pressed(self): self._set_publish_context_select_mode(False) From 6e592e4df62dd5f8969fbe8e29a730ce999a8d57 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 31 Mar 2022 14:56:07 +0200 Subject: [PATCH 09/11] changed label --- openpype/tools/workfiles/files_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 6cfd3dd651..edfcb17722 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -50,7 +50,7 @@ class SelectContextOverlay(QtWidgets.QFrame): self.setObjectName("WorkfilesPublishedContextSelect") label_widget = QtWidgets.QLabel( - "Please select context on Left side
<", + "Please choose context on the left
<", self ) label_widget.setAlignment(QtCore.Qt.AlignCenter) From dded71a5d41d90c55bace8e06732a8c8f558a667 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 5 Apr 2022 10:29:28 +0200 Subject: [PATCH 10/11] moved "published" checkbox after filter --- openpype/tools/workfiles/files_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index edfcb17722..b4ff830459 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -113,8 +113,8 @@ class FilesWidget(QtWidgets.QWidget): filter_layout = QtWidgets.QHBoxLayout(filter_widget) filter_layout.setContentsMargins(0, 0, 0, 0) - filter_layout.addWidget(published_checkbox, 0) filter_layout.addWidget(filter_input, 1) + filter_layout.addWidget(published_checkbox, 0) # Create the Files models extensions = set(self.host.file_extensions()) From 1bb36035f94487abaff9abd9b4efd3423c4c9e83 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 5 Apr 2022 10:29:51 +0200 Subject: [PATCH 11/11] swapped cancel and copy & open buttons --- openpype/tools/workfiles/files_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index b4ff830459..56af7752da 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -210,8 +210,8 @@ class FilesWidget(QtWidgets.QWidget): publish_btns_layout.setContentsMargins(0, 0, 0, 0) publish_btns_layout.addWidget(btn_save_as_published, 1) publish_btns_layout.addWidget(btn_change_context, 1) - publish_btns_layout.addWidget(btn_cancel_published, 1) publish_btns_layout.addWidget(btn_select_context_published, 1) + publish_btns_layout.addWidget(btn_cancel_published, 1) btns_layout = QtWidgets.QHBoxLayout(btns_widget) btns_layout.setContentsMargins(0, 0, 0, 0)