mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
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:
commit
f320cdc9dd
11 changed files with 2248 additions and 7 deletions
|
|
@ -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"]
|
||||
|
|
|
|||
6
openpype/tools/ayon_push_to_project/__init__.py
Normal file
6
openpype/tools/ayon_push_to_project/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from .control import PushToContextController
|
||||
|
||||
|
||||
__all__ = (
|
||||
"PushToContextController",
|
||||
)
|
||||
344
openpype/tools/ayon_push_to_project/control.py
Normal file
344
openpype/tools/ayon_push_to_project/control.py
Normal 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()
|
||||
32
openpype/tools/ayon_push_to_project/main.py
Normal file
32
openpype/tools/ayon_push_to_project/main.py
Normal 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()
|
||||
10
openpype/tools/ayon_push_to_project/models/__init__.py
Normal file
10
openpype/tools/ayon_push_to_project/models/__init__.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from .selection import PushToProjectSelectionModel
|
||||
from .user_values import UserPublishValuesModel
|
||||
from .integrate import IntegrateModel
|
||||
|
||||
|
||||
__all__ = (
|
||||
"PushToProjectSelectionModel",
|
||||
"UserPublishValuesModel",
|
||||
"IntegrateModel",
|
||||
)
|
||||
1214
openpype/tools/ayon_push_to_project/models/integrate.py
Normal file
1214
openpype/tools/ayon_push_to_project/models/integrate.py
Normal file
File diff suppressed because it is too large
Load diff
72
openpype/tools/ayon_push_to_project/models/selection.py
Normal file
72
openpype/tools/ayon_push_to_project/models/selection.py
Normal 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
|
||||
)
|
||||
110
openpype/tools/ayon_push_to_project/models/user_values.py
Normal file
110
openpype/tools/ayon_push_to_project/models/user_values.py
Normal 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"
|
||||
)
|
||||
6
openpype/tools/ayon_push_to_project/ui/__init__.py
Normal file
6
openpype/tools/ayon_push_to_project/ui/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from .window import PushToContextSelectWindow
|
||||
|
||||
|
||||
__all__ = (
|
||||
"PushToContextSelectWindow",
|
||||
)
|
||||
432
openpype/tools/ayon_push_to_project/ui/window.py
Normal file
432
openpype/tools/ayon_push_to_project/ui/window.py
Normal 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"]
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue