Merge pull request #5770 from ynput/feature/OP-5196_Push-to-Project-tool-AYON-compatibility

Push to project tool: Prepare push to project tool for AYON
This commit is contained in:
Jakub Trllo 2023-10-20 12:10:03 +02:00 committed by GitHub
commit f320cdc9dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 2248 additions and 7 deletions

View file

@ -1,6 +1,6 @@
import os
from openpype import PACKAGE_DIR
from openpype import PACKAGE_DIR, AYON_SERVER_ENABLED
from openpype.lib import get_openpype_execute_args, run_detached_process
from openpype.pipeline import load
from openpype.pipeline.load import LoadError
@ -32,12 +32,22 @@ class PushToLibraryProject(load.SubsetLoaderPlugin):
raise LoadError("Please select only one item")
context = tuple(filtered_contexts)[0]
push_tool_script_path = os.path.join(
PACKAGE_DIR,
"tools",
"push_to_project",
"app.py"
)
if AYON_SERVER_ENABLED:
push_tool_script_path = os.path.join(
PACKAGE_DIR,
"tools",
"ayon_push_to_project",
"main.py"
)
else:
push_tool_script_path = os.path.join(
PACKAGE_DIR,
"tools",
"push_to_project",
"app.py"
)
project_doc = context["project"]
version_doc = context["version"]
project_name = project_doc["name"]

View file

@ -0,0 +1,6 @@
from .control import PushToContextController
__all__ = (
"PushToContextController",
)

View file

@ -0,0 +1,344 @@
import threading
from openpype.client import (
get_asset_by_id,
get_subset_by_id,
get_version_by_id,
get_representations,
)
from openpype.settings import get_project_settings
from openpype.lib import prepare_template_data
from openpype.lib.events import QueuedEventSystem
from openpype.pipeline.create import get_subset_name_template
from openpype.tools.ayon_utils.models import ProjectsModel, HierarchyModel
from .models import (
PushToProjectSelectionModel,
UserPublishValuesModel,
IntegrateModel,
)
class PushToContextController:
def __init__(self, project_name=None, version_id=None):
self._event_system = self._create_event_system()
self._projects_model = ProjectsModel(self)
self._hierarchy_model = HierarchyModel(self)
self._integrate_model = IntegrateModel(self)
self._selection_model = PushToProjectSelectionModel(self)
self._user_values = UserPublishValuesModel(self)
self._src_project_name = None
self._src_version_id = None
self._src_asset_doc = None
self._src_subset_doc = None
self._src_version_doc = None
self._src_label = None
self._submission_enabled = False
self._process_thread = None
self._process_item_id = None
self.set_source(project_name, version_id)
# Events system
def emit_event(self, topic, data=None, source=None):
"""Use implemented event system to trigger event."""
if data is None:
data = {}
self._event_system.emit(topic, data, source)
def register_event_callback(self, topic, callback):
self._event_system.add_callback(topic, callback)
def set_source(self, project_name, version_id):
"""Set source project and version.
Args:
project_name (Union[str, None]): Source project name.
version_id (Union[str, None]): Source version id.
"""
if (
project_name == self._src_project_name
and version_id == self._src_version_id
):
return
self._src_project_name = project_name
self._src_version_id = version_id
self._src_label = None
asset_doc = None
subset_doc = None
version_doc = None
if project_name and version_id:
version_doc = get_version_by_id(project_name, version_id)
if version_doc:
subset_doc = get_subset_by_id(project_name, version_doc["parent"])
if subset_doc:
asset_doc = get_asset_by_id(project_name, subset_doc["parent"])
self._src_asset_doc = asset_doc
self._src_subset_doc = subset_doc
self._src_version_doc = version_doc
if asset_doc:
self._user_values.set_new_folder_name(asset_doc["name"])
variant = self._get_src_variant()
if variant:
self._user_values.set_variant(variant)
comment = version_doc["data"].get("comment")
if comment:
self._user_values.set_comment(comment)
self._emit_event(
"source.changed",
{
"project_name": project_name,
"version_id": version_id
}
)
def get_source_label(self):
"""Get source label.
Returns:
str: Label describing source project and version as path.
"""
if self._src_label is None:
self._src_label = self._prepare_source_label()
return self._src_label
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
)
def get_user_values(self):
return self._user_values.get_data()
def set_user_value_folder_name(self, folder_name):
self._user_values.set_new_folder_name(folder_name)
self._invalidate()
def set_user_value_variant(self, variant):
self._user_values.set_variant(variant)
self._invalidate()
def set_user_value_comment(self, comment):
self._user_values.set_comment(comment)
self._invalidate()
def set_selected_project(self, project_name):
self._selection_model.set_selected_project(project_name)
self._invalidate()
def set_selected_folder(self, folder_id):
self._selection_model.set_selected_folder(folder_id)
self._invalidate()
def set_selected_task(self, task_id, task_name):
self._selection_model.set_selected_task(task_id, task_name)
def get_process_item_status(self, item_id):
return self._integrate_model.get_item_status(item_id)
# Processing methods
def submit(self, wait=True):
if not self._submission_enabled:
return
if self._process_thread is not None:
return
item_id = self._integrate_model.create_process_item(
self._src_project_name,
self._src_version_id,
self._selection_model.get_selected_project_name(),
self._selection_model.get_selected_folder_id(),
self._selection_model.get_selected_task_name(),
self._user_values.variant,
comment=self._user_values.comment,
new_folder_name=self._user_values.new_folder_name,
dst_version=1
)
self._process_item_id = item_id
self._emit_event("submit.started")
if wait:
self._submit_callback()
self._process_item_id = None
return item_id
thread = threading.Thread(target=self._submit_callback)
self._process_thread = thread
thread.start()
return item_id
def wait_for_process_thread(self):
if self._process_thread is None:
return
self._process_thread.join()
self._process_thread = None
def _prepare_source_label(self):
if not self._src_project_name or not self._src_version_id:
return "Source is not defined"
asset_doc = self._src_asset_doc
if not asset_doc:
return "Source is invalid"
folder_path_parts = list(asset_doc["data"]["parents"])
folder_path_parts.append(asset_doc["name"])
folder_path = "/".join(folder_path_parts)
subset_doc = self._src_subset_doc
version_doc = self._src_version_doc
return "Source: {}/{}/{}/v{:0>3}".format(
self._src_project_name,
folder_path,
subset_doc["name"],
version_doc["name"]
)
def _get_task_info_from_repre_docs(self, asset_doc, repre_docs):
asset_tasks = asset_doc["data"].get("tasks") or {}
found_comb = []
for repre_doc in repre_docs:
context = repre_doc["context"]
task_info = context.get("task")
if task_info is None:
continue
task_name = None
task_type = None
if isinstance(task_info, str):
task_name = task_info
asset_task_info = asset_tasks.get(task_info) or {}
task_type = asset_task_info.get("type")
elif isinstance(task_info, dict):
task_name = task_info.get("name")
task_type = task_info.get("type")
if task_name and task_type:
return task_name, task_type
if task_name:
found_comb.append((task_name, task_type))
for task_name, task_type in found_comb:
return task_name, task_type
return None, None
def _get_src_variant(self):
project_name = self._src_project_name
version_doc = self._src_version_doc
asset_doc = self._src_asset_doc
repre_docs = get_representations(
project_name, version_ids=[version_doc["_id"]]
)
task_name, task_type = self._get_task_info_from_repre_docs(
asset_doc, repre_docs
)
project_settings = get_project_settings(project_name)
subset_doc = self._src_subset_doc
family = subset_doc["data"].get("family")
if not family:
family = subset_doc["data"]["families"][0]
template = get_subset_name_template(
self._src_project_name,
family,
task_name,
task_type,
None,
project_settings=project_settings
)
template_low = template.lower()
variant_placeholder = "{variant}"
if (
variant_placeholder not in template_low
or (not task_name and "{task" in template_low)
):
return ""
idx = template_low.index(variant_placeholder)
template_s = template[:idx]
template_e = template[idx + len(variant_placeholder):]
fill_data = prepare_template_data({
"family": family,
"task": task_name
})
try:
subset_s = template_s.format(**fill_data)
subset_e = template_e.format(**fill_data)
except Exception as exc:
print("Failed format", exc)
return ""
subset_name = self._src_subset_doc["name"]
if (
(subset_s and not subset_name.startswith(subset_s))
or (subset_e and not subset_name.endswith(subset_e))
):
return ""
if subset_s:
subset_name = subset_name[len(subset_s):]
if subset_e:
subset_name = subset_name[:len(subset_e)]
return subset_name
def _check_submit_validations(self):
if not self._user_values.is_valid:
return False
if not self._selection_model.get_selected_project_name():
return False
if (
not self._user_values.new_folder_name
and not self._selection_model.get_selected_folder_id()
):
return False
return True
def _invalidate(self):
submission_enabled = self._check_submit_validations()
if submission_enabled == self._submission_enabled:
return
self._submission_enabled = submission_enabled
self._emit_event(
"submission.enabled.changed",
{"enabled": submission_enabled}
)
def _submit_callback(self):
process_item_id = self._process_item_id
if process_item_id is None:
return
self._integrate_model.integrate_item(process_item_id)
self._emit_event("submit.finished", {})
if process_item_id == self._process_item_id:
self._process_item_id = None
def _emit_event(self, topic, data=None):
if data is None:
data = {}
self.emit_event(topic, data, "controller")
def _create_event_system(self):
return QueuedEventSystem()

View file

@ -0,0 +1,32 @@
import click
from openpype.tools.utils import get_openpype_qt_app
from openpype.tools.ayon_push_to_project.ui import PushToContextSelectWindow
def main_show(project_name, version_id):
app = get_openpype_qt_app()
window = PushToContextSelectWindow()
window.show()
window.set_source(project_name, version_id)
app.exec_()
@click.command()
@click.option("--project", help="Source project name")
@click.option("--version", help="Source version id")
def main(project, version):
"""Run PushToProject tool to integrate version in different project.
Args:
project (str): Source project name.
version (str): Version id.
"""
main_show(project, version)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,10 @@
from .selection import PushToProjectSelectionModel
from .user_values import UserPublishValuesModel
from .integrate import IntegrateModel
__all__ = (
"PushToProjectSelectionModel",
"UserPublishValuesModel",
"IntegrateModel",
)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,72 @@
class PushToProjectSelectionModel(object):
"""Model handling selection changes.
Triggering events:
- "selection.project.changed"
- "selection.folder.changed"
- "selection.task.changed"
"""
event_source = "push-to-project.selection.model"
def __init__(self, controller):
self._controller = controller
self._project_name = None
self._folder_id = None
self._task_name = None
self._task_id = None
def get_selected_project_name(self):
return self._project_name
def set_selected_project(self, project_name):
if project_name == self._project_name:
return
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
)

View file

@ -0,0 +1,110 @@
import re
from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS
class UserPublishValuesModel:
"""Helper object to validate values required for push to different project.
Args:
controller (PushToContextController): Event system to catch
and emit events.
"""
folder_name_regex = re.compile("^[a-zA-Z0-9_.]+$")
variant_regex = re.compile("^[{}]+$".format(SUBSET_NAME_ALLOWED_SYMBOLS))
def __init__(self, controller):
self._controller = controller
self._new_folder_name = None
self._variant = None
self._comment = None
self._is_variant_valid = False
self._is_new_folder_name_valid = False
self.set_new_folder_name("")
self.set_variant("")
self.set_comment("")
@property
def new_folder_name(self):
return self._new_folder_name
@property
def variant(self):
return self._variant
@property
def comment(self):
return self._comment
@property
def is_variant_valid(self):
return self._is_variant_valid
@property
def is_new_folder_name_valid(self):
return self._is_new_folder_name_valid
@property
def is_valid(self):
return self.is_variant_valid and self.is_new_folder_name_valid
def get_data(self):
return {
"new_folder_name": self._new_folder_name,
"variant": self._variant,
"comment": self._comment,
"is_variant_valid": self._is_variant_valid,
"is_new_folder_name_valid": self._is_new_folder_name_valid,
"is_valid": self.is_valid
}
def set_variant(self, variant):
if variant == self._variant:
return
self._variant = variant
is_valid = False
if variant:
is_valid = self.variant_regex.match(variant) is not None
self._is_variant_valid = is_valid
self._controller.emit_event(
"variant.changed",
{
"variant": variant,
"is_valid": self._is_variant_valid,
},
"user_values"
)
def set_new_folder_name(self, folder_name):
if self._new_folder_name == folder_name:
return
self._new_folder_name = folder_name
is_valid = True
if folder_name:
is_valid = (
self.folder_name_regex.match(folder_name) is not None
)
self._is_new_folder_name_valid = is_valid
self._controller.emit_event(
"new_folder_name.changed",
{
"new_folder_name": self._new_folder_name,
"is_valid": self._is_new_folder_name_valid,
},
"user_values"
)
def set_comment(self, comment):
if comment == self._comment:
return
self._comment = comment
self._controller.emit_event(
"comment.changed",
{"comment": comment},
"user_values"
)

View file

@ -0,0 +1,6 @@
from .window import PushToContextSelectWindow
__all__ = (
"PushToContextSelectWindow",
)

View file

@ -0,0 +1,432 @@
from qtpy import QtWidgets, QtGui, QtCore
from openpype.style import load_stylesheet, get_app_icon_path
from openpype.tools.utils import (
PlaceholderLineEdit,
SeparatorWidget,
set_style_property,
)
from openpype.tools.ayon_utils.widgets import (
ProjectsCombobox,
FoldersWidget,
TasksWidget,
)
from openpype.tools.ayon_push_to_project.control import (
PushToContextController,
)
class PushToContextSelectWindow(QtWidgets.QWidget):
def __init__(self, controller=None):
super(PushToContextSelectWindow, self).__init__()
if controller is None:
controller = PushToContextController()
self._controller = controller
self.setWindowTitle("Push to project (select context)")
self.setWindowIcon(QtGui.QIcon(get_app_icon_path()))
main_context_widget = QtWidgets.QWidget(self)
header_widget = QtWidgets.QWidget(main_context_widget)
header_label = QtWidgets.QLabel(
controller.get_source_label(),
header_widget
)
header_layout = QtWidgets.QHBoxLayout(header_widget)
header_layout.setContentsMargins(0, 0, 0, 0)
header_layout.addWidget(header_label)
main_splitter = QtWidgets.QSplitter(
QtCore.Qt.Horizontal, main_context_widget
)
context_widget = QtWidgets.QWidget(main_splitter)
projects_combobox = ProjectsCombobox(controller, context_widget)
projects_combobox.set_select_item_visible(True)
projects_combobox.set_standard_filter_enabled(True)
context_splitter = QtWidgets.QSplitter(
QtCore.Qt.Vertical, context_widget
)
folders_widget = FoldersWidget(controller, context_splitter)
folders_widget.set_deselectable(True)
tasks_widget = TasksWidget(controller, context_splitter)
context_splitter.addWidget(folders_widget)
context_splitter.addWidget(tasks_widget)
context_layout = QtWidgets.QVBoxLayout(context_widget)
context_layout.setContentsMargins(0, 0, 0, 0)
context_layout.addWidget(projects_combobox, 0)
context_layout.addWidget(context_splitter, 1)
# --- Inputs widget ---
inputs_widget = QtWidgets.QWidget(main_splitter)
folder_name_input = PlaceholderLineEdit(inputs_widget)
folder_name_input.setPlaceholderText("< Name of new folder >")
folder_name_input.setObjectName("ValidatedLineEdit")
variant_input = PlaceholderLineEdit(inputs_widget)
variant_input.setPlaceholderText("< Variant >")
variant_input.setObjectName("ValidatedLineEdit")
comment_input = PlaceholderLineEdit(inputs_widget)
comment_input.setPlaceholderText("< Publish comment >")
inputs_layout = QtWidgets.QFormLayout(inputs_widget)
inputs_layout.setContentsMargins(0, 0, 0, 0)
inputs_layout.addRow("New folder name", folder_name_input)
inputs_layout.addRow("Variant", variant_input)
inputs_layout.addRow("Comment", comment_input)
main_splitter.addWidget(context_widget)
main_splitter.addWidget(inputs_widget)
# --- Buttons widget ---
btns_widget = QtWidgets.QWidget(self)
cancel_btn = QtWidgets.QPushButton("Cancel", btns_widget)
publish_btn = QtWidgets.QPushButton("Publish", btns_widget)
btns_layout = QtWidgets.QHBoxLayout(btns_widget)
btns_layout.setContentsMargins(0, 0, 0, 0)
btns_layout.addStretch(1)
btns_layout.addWidget(cancel_btn, 0)
btns_layout.addWidget(publish_btn, 0)
sep_1 = SeparatorWidget(parent=main_context_widget)
sep_2 = SeparatorWidget(parent=main_context_widget)
main_context_layout = QtWidgets.QVBoxLayout(main_context_widget)
main_context_layout.addWidget(header_widget, 0)
main_context_layout.addWidget(sep_1, 0)
main_context_layout.addWidget(main_splitter, 1)
main_context_layout.addWidget(sep_2, 0)
main_context_layout.addWidget(btns_widget, 0)
# NOTE This was added in hurry
# - should be reorganized and changed styles
overlay_widget = QtWidgets.QFrame(self)
overlay_widget.setObjectName("OverlayFrame")
overlay_label = QtWidgets.QLabel(overlay_widget)
overlay_label.setAlignment(QtCore.Qt.AlignCenter)
overlay_btns_widget = QtWidgets.QWidget(overlay_widget)
overlay_btns_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
# Add try again button (requires changes in controller)
overlay_try_btn = QtWidgets.QPushButton(
"Try again", overlay_btns_widget
)
overlay_close_btn = QtWidgets.QPushButton(
"Close", overlay_btns_widget
)
overlay_btns_layout = QtWidgets.QHBoxLayout(overlay_btns_widget)
overlay_btns_layout.addStretch(1)
overlay_btns_layout.addWidget(overlay_try_btn, 0)
overlay_btns_layout.addWidget(overlay_close_btn, 0)
overlay_btns_layout.addStretch(1)
overlay_layout = QtWidgets.QVBoxLayout(overlay_widget)
overlay_layout.addWidget(overlay_label, 0)
overlay_layout.addWidget(overlay_btns_widget, 0)
overlay_layout.setAlignment(QtCore.Qt.AlignCenter)
main_layout = QtWidgets.QStackedLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.addWidget(main_context_widget)
main_layout.addWidget(overlay_widget)
main_layout.setStackingMode(QtWidgets.QStackedLayout.StackAll)
main_layout.setCurrentWidget(main_context_widget)
show_timer = QtCore.QTimer()
show_timer.setInterval(0)
main_thread_timer = QtCore.QTimer()
main_thread_timer.setInterval(10)
user_input_changed_timer = QtCore.QTimer()
user_input_changed_timer.setInterval(200)
user_input_changed_timer.setSingleShot(True)
main_thread_timer.timeout.connect(self._on_main_thread_timer)
show_timer.timeout.connect(self._on_show_timer)
user_input_changed_timer.timeout.connect(self._on_user_input_timer)
folder_name_input.textChanged.connect(self._on_new_asset_change)
variant_input.textChanged.connect(self._on_variant_change)
comment_input.textChanged.connect(self._on_comment_change)
publish_btn.clicked.connect(self._on_select_click)
cancel_btn.clicked.connect(self._on_close_click)
overlay_close_btn.clicked.connect(self._on_close_click)
overlay_try_btn.clicked.connect(self._on_try_again_click)
controller.register_event_callback(
"new_folder_name.changed",
self._on_controller_new_asset_change
)
controller.register_event_callback(
"variant.changed", self._on_controller_variant_change
)
controller.register_event_callback(
"comment.changed", self._on_controller_comment_change
)
controller.register_event_callback(
"submission.enabled.changed", self._on_submission_change
)
controller.register_event_callback(
"source.changed", self._on_controller_source_change
)
controller.register_event_callback(
"submit.started", self._on_controller_submit_start
)
controller.register_event_callback(
"submit.finished", self._on_controller_submit_end
)
controller.register_event_callback(
"push.message.added", self._on_push_message
)
self._main_layout = main_layout
self._main_context_widget = main_context_widget
self._header_label = header_label
self._main_splitter = main_splitter
self._projects_combobox = projects_combobox
self._folders_widget = folders_widget
self._tasks_widget = tasks_widget
self._variant_input = variant_input
self._folder_name_input = folder_name_input
self._comment_input = comment_input
self._publish_btn = publish_btn
self._overlay_widget = overlay_widget
self._overlay_close_btn = overlay_close_btn
self._overlay_try_btn = overlay_try_btn
self._overlay_label = overlay_label
self._user_input_changed_timer = user_input_changed_timer
# Store current value on input text change
# The value is unset when is passed to controller
# The goal is to have controll over changes happened during user change
# in UI and controller auto-changes
self._variant_input_text = None
self._new_folder_name_input_text = None
self._comment_input_text = None
self._first_show = True
self._show_timer = show_timer
self._show_counter = 0
self._main_thread_timer = main_thread_timer
self._main_thread_timer_can_stop = True
self._last_submit_message = None
self._process_item_id = None
self._variant_is_valid = None
self._folder_is_valid = None
publish_btn.setEnabled(False)
overlay_close_btn.setVisible(False)
overlay_try_btn.setVisible(False)
# Support of public api function of controller
def set_source(self, project_name, version_id):
"""Set source project and version.
Call the method on controller.
Args:
project_name (Union[str, None]): Name of project.
version_id (Union[str, None]): Version id.
"""
self._controller.set_source(project_name, version_id)
def showEvent(self, event):
super(PushToContextSelectWindow, self).showEvent(event)
if self._first_show:
self._first_show = False
self._on_first_show()
def refresh(self):
user_values = self._controller.get_user_values()
new_folder_name = user_values["new_folder_name"]
variant = user_values["variant"]
self._folder_name_input.setText(new_folder_name or "")
self._variant_input.setText(variant or "")
self._invalidate_variant(user_values["is_variant_valid"])
self._invalidate_new_folder_name(
new_folder_name, user_values["is_new_folder_name_valid"]
)
self._projects_combobox.refresh()
def _on_first_show(self):
width = 740
height = 640
inputs_width = 360
self.setStyleSheet(load_stylesheet())
self.resize(width, height)
self._main_splitter.setSizes([width - inputs_width, inputs_width])
self._show_timer.start()
def _on_show_timer(self):
if self._show_counter < 3:
self._show_counter += 1
return
self._show_timer.stop()
self._show_counter = 0
self.refresh()
def _on_new_asset_change(self, text):
self._new_folder_name_input_text = text
self._user_input_changed_timer.start()
def _on_variant_change(self, text):
self._variant_input_text = text
self._user_input_changed_timer.start()
def _on_comment_change(self, text):
self._comment_input_text = text
self._user_input_changed_timer.start()
def _on_user_input_timer(self):
folder_name = self._new_folder_name_input_text
if folder_name is not None:
self._new_folder_name_input_text = None
self._controller.set_user_value_folder_name(folder_name)
variant = self._variant_input_text
if variant is not None:
self._variant_input_text = None
self._controller.set_user_value_variant(variant)
comment = self._comment_input_text
if comment is not None:
self._comment_input_text = None
self._controller.set_user_value_comment(comment)
def _on_controller_new_asset_change(self, event):
folder_name = event["new_folder_name"]
if (
self._new_folder_name_input_text is None
and folder_name != self._folder_name_input.text()
):
self._folder_name_input.setText(folder_name)
self._invalidate_new_folder_name(folder_name, event["is_valid"])
def _on_controller_variant_change(self, event):
is_valid = event["is_valid"]
variant = event["variant"]
if (
self._variant_input_text is None
and variant != self._variant_input.text()
):
self._variant_input.setText(variant)
self._invalidate_variant(is_valid)
def _on_controller_comment_change(self, event):
comment = event["comment"]
if (
self._comment_input_text is None
and comment != self._comment_input.text()
):
self._comment_input.setText(comment)
def _on_controller_source_change(self):
self._header_label.setText(self._controller.get_source_label())
def _invalidate_new_folder_name(self, folder_name, is_valid):
self._tasks_widget.setVisible(not folder_name)
if self._folder_is_valid is is_valid:
return
self._folder_is_valid = is_valid
state = ""
if folder_name:
if is_valid is True:
state = "valid"
elif is_valid is False:
state = "invalid"
set_style_property(
self._folder_name_input, "state", state
)
def _invalidate_variant(self, is_valid):
if self._variant_is_valid is is_valid:
return
self._variant_is_valid = is_valid
state = "valid" if is_valid else "invalid"
set_style_property(self._variant_input, "state", state)
def _on_submission_change(self, event):
self._publish_btn.setEnabled(event["enabled"])
def _on_close_click(self):
self.close()
def _on_select_click(self):
self._process_item_id = self._controller.submit(wait=False)
def _on_try_again_click(self):
self._process_item_id = None
self._last_submit_message = None
self._overlay_close_btn.setVisible(False)
self._overlay_try_btn.setVisible(False)
self._main_layout.setCurrentWidget(self._main_context_widget)
def _on_main_thread_timer(self):
if self._last_submit_message:
self._overlay_label.setText(self._last_submit_message)
self._last_submit_message = None
process_status = self._controller.get_process_item_status(
self._process_item_id
)
push_failed = process_status["failed"]
fail_traceback = process_status["full_traceback"]
if self._main_thread_timer_can_stop:
self._main_thread_timer.stop()
self._overlay_close_btn.setVisible(True)
if push_failed and not fail_traceback:
self._overlay_try_btn.setVisible(True)
if push_failed:
message = "Push Failed:\n{}".format(process_status["fail_reason"])
if fail_traceback:
message += "\n{}".format(fail_traceback)
self._overlay_label.setText(message)
set_style_property(self._overlay_close_btn, "state", "error")
if self._main_thread_timer_can_stop:
# Join thread in controller
self._controller.wait_for_process_thread()
# Reset process item to None
self._process_item_id = None
def _on_controller_submit_start(self):
self._main_thread_timer_can_stop = False
self._main_thread_timer.start()
self._main_layout.setCurrentWidget(self._overlay_widget)
self._overlay_label.setText("Submittion started")
def _on_controller_submit_end(self):
self._main_thread_timer_can_stop = True
def _on_push_message(self, event):
self._last_submit_message = event["message"]

View file

@ -1051,6 +1051,11 @@ class ProjectPushItemProcess:
repre_format_data["ext"] = ext[1:]
break
# Re-use 'output' from source representation
repre_output_name = repre_doc["context"].get("output")
if repre_output_name is not None:
repre_format_data["output"] = repre_output_name
template_obj = anatomy.templates_obj[template_name]["folder"]
folder_path = template_obj.format_strict(formatting_data)
repre_context = folder_path.used_values