From 4ae8f277b99298018b999c1539e051fd82eb4de7 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 29 May 2024 22:29:13 +0300 Subject: [PATCH 01/17] add 'pyblish_debug_stepper' tool --- .../pyblish_debug_stepper.py | 269 ++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py diff --git a/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py b/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py new file mode 100644 index 0000000000..e6cf3c69d6 --- /dev/null +++ b/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py @@ -0,0 +1,269 @@ +""" +Brought from https://gist.github.com/BigRoy/1972822065e38f8fae7521078e44eca2 +Code Credits: [BigRoy](https://github.com/BigRoy) + +Requirement: + This tool requires some modification in ayon-core. + Add the following two lines in sa similar fashion to this commit + https://github.com/ynput/OpenPype/commit/6a0ce21aa1f8cb17452fe066aa15134d22fda440 + i.e. Add them just after + https://github.com/ynput/ayon-core/blob/8366d2e8b4003a252b8da822f7e38c6db08292b4/client/ayon_core/tools/publisher/control.py#L2483-L2487 + + ``` + result["context"] = self._publish_context + pyblish.api.emit("pluginProcessedCustom", result=result) + ``` + + This modification should be temporary till the following PR get merged and released. + https://github.com/pyblish/pyblish-base/pull/401 + +How it works: + It registers a callback function `on_plugin_processed` + when event `pluginProcessedCustom` is emitted. + The logic of this function is roughly: + 1. Pauses the publishing. + 2. Collects some info about the plugin. + 3. Shows that info to the tool's window. + 4. Continues publishing on clicking `step` button. + +How to use it: + 1. Launch the tool from AYON experimental tools window. + 2. Launch the publisher tool and click validate. + 3. Click Step to run plugins one by one. + +Note: + It won't work when triggering validation from code as our custom event lives inside ayon-core. + But, It should work when the mentioned PR above (#401) get merged and released. + +""" + +import copy +import json +from qtpy import QtWidgets, QtCore, QtGui + +import pyblish.api +from ayon_core import style + +TAB = 4* " " +HEADER_SIZE = "15px" + +KEY_COLOR = QtGui.QColor("#ffffff") +NEW_KEY_COLOR = QtGui.QColor("#00ff00") +VALUE_TYPE_COLOR = QtGui.QColor("#ffbbbb") +NEW_VALUE_TYPE_COLOR = QtGui.QColor("#ff4444") +VALUE_COLOR = QtGui.QColor("#777799") +NEW_VALUE_COLOR = QtGui.QColor("#DDDDCC") +CHANGED_VALUE_COLOR = QtGui.QColor("#CCFFCC") + +MAX_VALUE_STR_LEN = 100 + + +def failsafe_deepcopy(data): + """Allow skipping the deepcopy for unsupported types""" + try: + return copy.deepcopy(data) + except TypeError: + if isinstance(data, dict): + return { + key: failsafe_deepcopy(value) + for key, value in data.items() + } + elif isinstance(data, list): + return data.copy() + return data + + +class DictChangesModel(QtGui.QStandardItemModel): + # TODO: Replace this with a QAbstractItemModel + def __init__(self, *args, **kwargs): + super(DictChangesModel, self).__init__(*args, **kwargs) + self._data = {} + + columns = ["Key", "Type", "Value"] + self.setColumnCount(len(columns)) + for i, label in enumerate(columns): + self.setHeaderData(i, QtCore.Qt.Horizontal, label) + + def _update_recursive(self, data, parent, previous_data): + for key, value in data.items(): + + # Find existing item or add new row + parent_index = parent.index() + for row in range(self.rowCount(parent_index)): + # Update existing item if it exists + index = self.index(row, 0, parent_index) + if index.data() == key: + item = self.itemFromIndex(index) + type_item = self.itemFromIndex(self.index(row, 1, parent_index)) + value_item = self.itemFromIndex(self.index(row, 2, parent_index)) + break + else: + item = QtGui.QStandardItem(key) + type_item = QtGui.QStandardItem() + value_item = QtGui.QStandardItem() + parent.appendRow([item, type_item, value_item]) + + # Key + key_color = NEW_KEY_COLOR if key not in previous_data else KEY_COLOR + item.setData(key_color, QtCore.Qt.ForegroundRole) + + # Type + type_str = type(value).__name__ + type_color = VALUE_TYPE_COLOR + if key in previous_data and type(previous_data[key]).__name__ != type_str: + type_color = NEW_VALUE_TYPE_COLOR + + type_item.setText(type_str) + type_item.setData(type_color, QtCore.Qt.ForegroundRole) + + # Value + value_changed = False + if key not in previous_data or previous_data[key] != value: + value_changed = True + value_color = NEW_VALUE_COLOR if value_changed else VALUE_COLOR + + value_item.setData(value_color, QtCore.Qt.ForegroundRole) + if value_changed: + value_str = str(value) + if len(value_str) > MAX_VALUE_STR_LEN: + value_str = value_str[:MAX_VALUE_STR_LEN] + "..." + value_item.setText(value_str) + # Preferably this is deferred to only when the data gets requested + # since this formatting can be slow for very large data sets like + # project settings and system settings + # This will also be MUCH MUCH faster if we don't clear the items on each update + # but only updated/add/remove changed items so that this also runs much less often + value_item.setData(json.dumps(value, default=str, indent=4), QtCore.Qt.ToolTipRole) + + + if isinstance(value, dict): + previous_value = previous_data.get(key, {}) + if previous_data.get(key) != value: + # Update children if the value is not the same as before + self._update_recursive(value, parent=item, previous_data=previous_value) + else: + # TODO: Ensure all children are updated to be not marked as 'changed' + # in the most optimal way possible + self._update_recursive(value, parent=item, previous_data=previous_value) + + self._data = data + + def update(self, data): + parent = self.invisibleRootItem() + + data = failsafe_deepcopy(data) + previous_data = self._data + self._update_recursive(data, parent, previous_data) + self._data = data # store previous data for next update + + +class DebugUI(QtWidgets.QDialog): + + def __init__(self, parent=None): + super(DebugUI, self).__init__(parent=parent) + self.setStyleSheet(style.load_stylesheet()) + + self._set_window_title() + self.setWindowFlags( + QtCore.Qt.Window + | QtCore.Qt.CustomizeWindowHint + | QtCore.Qt.WindowTitleHint + | QtCore.Qt.WindowMinimizeButtonHint + | QtCore.Qt.WindowCloseButtonHint + | QtCore.Qt.WindowStaysOnTopHint + ) + + layout = QtWidgets.QVBoxLayout(self) + text_edit = QtWidgets.QTextEdit() + text_edit.setFixedHeight(65) + font = QtGui.QFont("NONEXISTENTFONT") + font.setStyleHint(font.TypeWriter) + text_edit.setFont(font) + text_edit.setLineWrapMode(text_edit.NoWrap) + + step = QtWidgets.QPushButton("Step") + step.setEnabled(False) + + model = DictChangesModel() + proxy = QtCore.QSortFilterProxyModel() + proxy.setSourceModel(model) + view = QtWidgets.QTreeView() + view.setModel(proxy) + view.setSortingEnabled(True) + + layout.addWidget(text_edit) + layout.addWidget(view) + layout.addWidget(step) + + step.clicked.connect(self.on_step) + + self._pause = False + self.model = model + self.proxy = proxy + self.view = view + self.text = text_edit + self.step = step + self.resize(700, 500) + + self._previous_data = {} + + + + def _set_window_title(self, plugin=None): + title = "Pyblish Debug Stepper" + if plugin is not None: + plugin_label = plugin.label or plugin.__name__ + title += f" | {plugin_label}" + self.setWindowTitle(title) + + def pause(self, state): + self._pause = state + self.step.setEnabled(state) + + def on_step(self): + self.pause(False) + + def showEvent(self, event): + print("Registering callback..") + pyblish.api.register_callback("pluginProcessedCustom", # "pluginProcessed" + self.on_plugin_processed) + + def hideEvent(self, event): + self.pause(False) + print("Deregistering callback..") + pyblish.api.deregister_callback("pluginProcessedCustom", # "pluginProcessed" + self.on_plugin_processed) + + def on_plugin_processed(self, result): + self.pause(True) + + self._set_window_title(plugin=result["plugin"]) + + print(10*"<" ,result["plugin"].__name__, 10*">") + + plugin_order = result["plugin"].order + plugin_name = result["plugin"].__name__ + duration = result['duration'] + plugin_instance = result["instance"] + context = result["context"] + + msg = "" + msg += f"Order: {plugin_order}
" + msg += f"Plugin: {plugin_name}" + if plugin_instance is not None: + msg += f" -> instance: {plugin_instance}" + msg += "
" + msg += f"Duration: {duration} ms
" + self.text.setHtml(msg) + + data = { + "context": context.data + } + for instance in context: + data[instance.name] = instance.data + self.model.update(data) + + app = QtWidgets.QApplication.instance() + while self._pause: + # Allow user interaction with the UI + app.processEvents() From 3de28f870d54415fc0d14e454f770772cf11d554 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 24 Jun 2024 23:12:23 +0300 Subject: [PATCH 02/17] Add DebugExprimentalTool to ExperimentalTools --- .../tools/experimental_tools/tools_def.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/client/ayon_core/tools/experimental_tools/tools_def.py b/client/ayon_core/tools/experimental_tools/tools_def.py index 7def3551de..e3acfa72ab 100644 --- a/client/ayon_core/tools/experimental_tools/tools_def.py +++ b/client/ayon_core/tools/experimental_tools/tools_def.py @@ -1,4 +1,5 @@ import os +from .pyblish_debug_stepper import DebugUI # Constant key under which local settings are stored LOCAL_EXPERIMENTAL_KEY = "experimental_tools" @@ -95,6 +96,12 @@ class ExperimentalTools: "hiero", "resolve", ] + ), + ExperimentalHostTool( + "DebugExprimentalTool", + "Pyblish Debug Stepper", + "Debug Pyblish plugins step by step.", + self._show_pyblish_debugger, ) ] @@ -162,6 +169,13 @@ class ExperimentalTools: local_settings.get(LOCAL_EXPERIMENTAL_KEY) ) or {} + # Enable the following tools by default. + # Because they will always be disabled due + # to the fact their settings don't exist. + experimental_settings.update({ + "DebugExprimentalTool": True, + }) + for identifier, eperimental_tool in self.tools_by_identifier.items(): enabled = experimental_settings.get(identifier, False) eperimental_tool.set_enabled(enabled) @@ -175,3 +189,7 @@ class ExperimentalTools: ) self._publisher_tool.show() + + def _show_pyblish_debugger(self): + window = DebugUI(parent=self._parent_widget) + window.show() From 36caeba8dbd5ed3da3f736bebed52330f90b12a9 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 24 Jun 2024 23:26:34 +0300 Subject: [PATCH 03/17] Update pyblish debug stepper docstring --- .../pyblish_debug_stepper.py | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py b/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py index e6cf3c69d6..51b7ba2c06 100644 --- a/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py +++ b/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py @@ -3,37 +3,25 @@ Brought from https://gist.github.com/BigRoy/1972822065e38f8fae7521078e44eca2 Code Credits: [BigRoy](https://github.com/BigRoy) Requirement: - This tool requires some modification in ayon-core. - Add the following two lines in sa similar fashion to this commit - https://github.com/ynput/OpenPype/commit/6a0ce21aa1f8cb17452fe066aa15134d22fda440 - i.e. Add them just after - https://github.com/ynput/ayon-core/blob/8366d2e8b4003a252b8da822f7e38c6db08292b4/client/ayon_core/tools/publisher/control.py#L2483-L2487 - - ``` - result["context"] = self._publish_context - pyblish.api.emit("pluginProcessedCustom", result=result) - ``` - - This modification should be temporary till the following PR get merged and released. - https://github.com/pyblish/pyblish-base/pull/401 + It requires pyblish version >= 1.8.12 How it works: - It registers a callback function `on_plugin_processed` - when event `pluginProcessedCustom` is emitted. - The logic of this function is roughly: - 1. Pauses the publishing. - 2. Collects some info about the plugin. - 3. Shows that info to the tool's window. - 4. Continues publishing on clicking `step` button. + This tool makes use of pyblish event `pluginProcessed` to: + 1. Pause the publishing. + 2. Collect some info about the plugin. + 3. Show that info to the tool's window. + 4. Continue publishing on clicking `step` button. How to use it: 1. Launch the tool from AYON experimental tools window. 2. Launch the publisher tool and click validate. 3. Click Step to run plugins one by one. -Note: - It won't work when triggering validation from code as our custom event lives inside ayon-core. - But, It should work when the mentioned PR above (#401) get merged and released. +Note : + Pyblish debugger also works when triggering the validation or + publishing from code. + Here's an example about validating from code: + https://github.com/MustafaJafar/ayon-recipes/blob/main/validate_from_code.py """ @@ -225,13 +213,13 @@ class DebugUI(QtWidgets.QDialog): def showEvent(self, event): print("Registering callback..") - pyblish.api.register_callback("pluginProcessedCustom", # "pluginProcessed" + pyblish.api.register_callback("pluginProcessed", self.on_plugin_processed) def hideEvent(self, event): self.pause(False) print("Deregistering callback..") - pyblish.api.deregister_callback("pluginProcessedCustom", # "pluginProcessed" + pyblish.api.deregister_callback("pluginProcessed", self.on_plugin_processed) def on_plugin_processed(self, result): From f5006273ae69f6cb6404522488ae3141ed376200 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 5 Jul 2024 19:33:23 +0300 Subject: [PATCH 04/17] fix typo --- client/ayon_core/tools/experimental_tools/tools_def.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/tools/experimental_tools/tools_def.py b/client/ayon_core/tools/experimental_tools/tools_def.py index e3acfa72ab..30e5211b41 100644 --- a/client/ayon_core/tools/experimental_tools/tools_def.py +++ b/client/ayon_core/tools/experimental_tools/tools_def.py @@ -98,7 +98,7 @@ class ExperimentalTools: ] ), ExperimentalHostTool( - "DebugExprimentalTool", + "pyblish_debug_stepper", "Pyblish Debug Stepper", "Debug Pyblish plugins step by step.", self._show_pyblish_debugger, @@ -173,12 +173,12 @@ class ExperimentalTools: # Because they will always be disabled due # to the fact their settings don't exist. experimental_settings.update({ - "DebugExprimentalTool": True, + "pyblish_debug_stepper": True, }) - for identifier, eperimental_tool in self.tools_by_identifier.items(): + for identifier, experimental_tool in self.tools_by_identifier.items(): enabled = experimental_settings.get(identifier, False) - eperimental_tool.set_enabled(enabled) + experimental_tool.set_enabled(enabled) def _show_publisher(self): if self._publisher_tool is None: From b905dfe4bbc68e32cd76bb9fdea226c9c68e2a29 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 24 Sep 2024 02:41:30 +0200 Subject: [PATCH 05/17] Support PySide6 --- .../tools/experimental_tools/pyblish_debug_stepper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py b/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py index 51b7ba2c06..1db0a3d9f4 100644 --- a/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py +++ b/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py @@ -165,9 +165,9 @@ class DebugUI(QtWidgets.QDialog): text_edit = QtWidgets.QTextEdit() text_edit.setFixedHeight(65) font = QtGui.QFont("NONEXISTENTFONT") - font.setStyleHint(font.TypeWriter) + font.setStyleHint(QtGui.QFont.TypeWriter) text_edit.setFont(font) - text_edit.setLineWrapMode(text_edit.NoWrap) + text_edit.setLineWrapMode(QtWidgets.QTextEdit.NoWrap) step = QtWidgets.QPushButton("Step") step.setEnabled(False) From c9ea3dddd06dcdb15714f977095f49b513ea3f66 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 24 Sep 2024 02:44:36 +0200 Subject: [PATCH 06/17] Cosmetics --- .../pyblish_debug_stepper.py | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py b/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py index 1db0a3d9f4..0f1120b8f6 100644 --- a/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py +++ b/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py @@ -82,8 +82,8 @@ class DictChangesModel(QtGui.QStandardItemModel): index = self.index(row, 0, parent_index) if index.data() == key: item = self.itemFromIndex(index) - type_item = self.itemFromIndex(self.index(row, 1, parent_index)) - value_item = self.itemFromIndex(self.index(row, 2, parent_index)) + type_item = self.itemFromIndex(self.index(row, 1, parent_index)) # noqa + value_item = self.itemFromIndex(self.index(row, 2, parent_index)) # noqa break else: item = QtGui.QStandardItem(key) @@ -92,13 +92,16 @@ class DictChangesModel(QtGui.QStandardItemModel): parent.appendRow([item, type_item, value_item]) # Key - key_color = NEW_KEY_COLOR if key not in previous_data else KEY_COLOR + key_color = NEW_KEY_COLOR if key not in previous_data else KEY_COLOR # noqa item.setData(key_color, QtCore.Qt.ForegroundRole) # Type type_str = type(value).__name__ type_color = VALUE_TYPE_COLOR - if key in previous_data and type(previous_data[key]).__name__ != type_str: + if ( + key in previous_data + and type(previous_data[key]).__name__ != type_str + ): type_color = NEW_VALUE_TYPE_COLOR type_item.setText(type_str) @@ -116,23 +119,31 @@ class DictChangesModel(QtGui.QStandardItemModel): if len(value_str) > MAX_VALUE_STR_LEN: value_str = value_str[:MAX_VALUE_STR_LEN] + "..." value_item.setText(value_str) - # Preferably this is deferred to only when the data gets requested - # since this formatting can be slow for very large data sets like - # project settings and system settings - # This will also be MUCH MUCH faster if we don't clear the items on each update - # but only updated/add/remove changed items so that this also runs much less often - value_item.setData(json.dumps(value, default=str, indent=4), QtCore.Qt.ToolTipRole) + # Preferably this is deferred to only when the data gets + # requested since this formatting can be slow for very large + # data sets like project settings and system settings + # This will also be MUCH faster if we don't clear the + # items on each update but only updated/add/remove changed + # items so that this also runs much less often + value_item.setData( + json.dumps(value, default=str, indent=4), + QtCore.Qt.ToolTipRole + ) if isinstance(value, dict): previous_value = previous_data.get(key, {}) if previous_data.get(key) != value: # Update children if the value is not the same as before - self._update_recursive(value, parent=item, previous_data=previous_value) + self._update_recursive(value, + parent=item, + previous_data=previous_value) else: - # TODO: Ensure all children are updated to be not marked as 'changed' - # in the most optimal way possible - self._update_recursive(value, parent=item, previous_data=previous_value) + # TODO: Ensure all children are updated to be not marked + # as 'changed' in the most optimal way possible + self._update_recursive(value, + parent=item, + previous_data=previous_value) self._data = data @@ -195,8 +206,6 @@ class DebugUI(QtWidgets.QDialog): self._previous_data = {} - - def _set_window_title(self, plugin=None): title = "Pyblish Debug Stepper" if plugin is not None: @@ -227,7 +236,7 @@ class DebugUI(QtWidgets.QDialog): self._set_window_title(plugin=result["plugin"]) - print(10*"<" ,result["plugin"].__name__, 10*">") + print(10*"<", result["plugin"].__name__, 10*">") plugin_order = result["plugin"].order plugin_name = result["plugin"].__name__ From b946ed64f33f3c1e19c351b44f8c1d55cf395738 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 24 Sep 2024 02:51:49 +0200 Subject: [PATCH 07/17] Add simple filter field to quickly filter to certain keys only --- .../tools/experimental_tools/pyblish_debug_stepper.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py b/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py index 0f1120b8f6..33de4bf036 100644 --- a/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py +++ b/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py @@ -185,12 +185,18 @@ class DebugUI(QtWidgets.QDialog): model = DictChangesModel() proxy = QtCore.QSortFilterProxyModel() + proxy.setRecursiveFilteringEnabled(True) proxy.setSourceModel(model) view = QtWidgets.QTreeView() view.setModel(proxy) view.setSortingEnabled(True) + filter_field = QtWidgets.QLineEdit() + filter_field.setPlaceholderText("Filter keys...") + filter_field.textChanged.connect(proxy.setFilterFixedString) + layout.addWidget(text_edit) + layout.addWidget(filter_field) layout.addWidget(view) layout.addWidget(step) @@ -198,6 +204,7 @@ class DebugUI(QtWidgets.QDialog): self._pause = False self.model = model + self.filter = filter_field self.proxy = proxy self.view = view self.text = text_edit From 36541c5aae0023f479f343521fc25ee0e7010134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Fri, 11 Oct 2024 17:45:41 +0200 Subject: [PATCH 08/17] :art: add unreal to hosts unreal can do local rendering/publishing and without it, it is missing thumbnail. --- client/ayon_core/plugins/publish/extract_thumbnail.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index 4ffabf6028..37bbac8898 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -36,7 +36,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "traypublisher", "substancepainter", "nuke", - "aftereffects" + "aftereffects", + "unreal" ] enabled = False From f1a1e77134b5101d87715506d0ebc5591058b01f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:23:11 +0200 Subject: [PATCH 09/17] added new function to calculate representation delivery data --- client/ayon_core/pipeline/delivery.py | 80 ++++++++++++++++++++++- client/ayon_core/plugins/load/delivery.py | 33 ++++++---- 2 files changed, 100 insertions(+), 13 deletions(-) diff --git a/client/ayon_core/pipeline/delivery.py b/client/ayon_core/pipeline/delivery.py index 029775e1db..174e81c194 100644 --- a/client/ayon_core/pipeline/delivery.py +++ b/client/ayon_core/pipeline/delivery.py @@ -3,11 +3,21 @@ import os import copy import shutil import glob -import clique import collections +from typing import List, Dict, Any, Iterable + +import clique +import ayon_api from ayon_core.lib import create_hard_link +from .anatomy import Anatomy +from .template_data import ( + get_general_template_data, + get_folder_template_data, + get_task_template_data, +) + def _copy_file(src_path, dst_path): """Hardlink file if possible(to save space), copy if not. @@ -327,3 +337,71 @@ def deliver_sequence( uploaded += 1 return report_items, uploaded + + +def _merge_data(data, new_data): + queue = collections.deque() + queue.append((data, new_data)) + while queue: + q_data, q_new_data = queue.popleft() + for key, value in q_new_data.items(): + if key in q_data and isinstance(value, dict): + queue.append((q_data[key], value)) + continue + q_data[key] = value + + +def get_representations_delivery_template_data( + project_name: str, + representation_ids: Iterable[str], +) -> Dict[str, Dict[str, Any]]: + representation_ids = set(representation_ids) + + output = { + repre_id: {} + for repre_id in representation_ids + } + if not representation_ids: + return output + + project_entity = ayon_api.get_project(project_name) + + general_template_data = get_general_template_data() + + repres_hierarchy = ayon_api.get_representations_hierarchy( + project_name, + representation_ids, + project_fields=set(), + folder_fields={"path", "folderType"}, + task_fields={"name", "taskType"}, + product_fields={"name", "productType"}, + version_fields={"version", "productId"}, + representation_fields=None, + ) + for repre_id, repre_hierarchy in repres_hierarchy.items(): + repre_entity = repre_hierarchy.representation + if repre_entity is None: + continue + + template_data = repre_entity["context"] + template_data.update(copy.deepcopy(general_template_data)) + template_data.update(get_folder_template_data( + repre_hierarchy.folder, project_name + )) + if repre_hierarchy.task: + template_data.update(get_task_template_data( + project_entity, repre_hierarchy.task + )) + + product_entity = repre_hierarchy.product + version_entity = repre_hierarchy.version + template_data.update({ + "product": { + "name": product_entity["name"], + "type": product_entity["productType"], + }, + "version": version_entity["version"], + }) + _merge_data(template_data, repre_entity["context"]) + output[repre_id] = template_data + return output diff --git a/client/ayon_core/plugins/load/delivery.py b/client/ayon_core/plugins/load/delivery.py index 5c53d170eb..3c9f1b9691 100644 --- a/client/ayon_core/plugins/load/delivery.py +++ b/client/ayon_core/plugins/load/delivery.py @@ -200,20 +200,29 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): format_dict = get_format_dict(self.anatomy, self.root_line_edit.text()) renumber_frame = self.renumber_frame.isChecked() frame_offset = self.first_frame_start.value() + filtered_repres = [] + repre_ids = set() for repre in self._representations: - if repre["name"] not in selected_repres: - continue + if repre["name"] in selected_repres: + filtered_repres.append(repre) + repre_ids.add(repre["id"]) + template_data_by_repre_id = get_representations_template_data( + self.anatomy.project_name, repre_ids + ) + for repre in filtered_repres: repre_path = get_representation_path_with_anatomy( repre, self.anatomy ) - anatomy_data = copy.deepcopy(repre["context"]) - new_report_items = check_destination_path(repre["id"], - self.anatomy, - anatomy_data, - datetime_data, - template_name) + template_data = template_data_by_repre_id[repre["id"]] + new_report_items = check_destination_path( + repre["id"], + self.anatomy, + template_data, + datetime_data, + template_name + ) report_items.update(new_report_items) if new_report_items: @@ -224,7 +233,7 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): repre, self.anatomy, template_name, - anatomy_data, + template_data, format_dict, report_items, self.log @@ -267,9 +276,9 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): if frame is not None: if repre["context"].get("frame"): - anatomy_data["frame"] = frame + template_data["frame"] = frame elif repre["context"].get("udim"): - anatomy_data["udim"] = frame + template_data["udim"] = frame else: # Fallback self.log.warning( @@ -277,7 +286,7 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): " data. Supplying sequence frame to '{frame}'" " formatting data." ) - anatomy_data["frame"] = frame + template_data["frame"] = frame new_report_items, uploaded = deliver_single_file(*args) report_items.update(new_report_items) self._update_progress(uploaded) From ebdd757c669c6d72421abdb3df59742ad3eb4a03 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 15 Oct 2024 13:55:24 +0200 Subject: [PATCH 10/17] fix imports --- client/ayon_core/pipeline/delivery.py | 3 +-- client/ayon_core/plugins/load/delivery.py | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/pipeline/delivery.py b/client/ayon_core/pipeline/delivery.py index 174e81c194..2a2adf984a 100644 --- a/client/ayon_core/pipeline/delivery.py +++ b/client/ayon_core/pipeline/delivery.py @@ -4,14 +4,13 @@ import copy import shutil import glob import collections -from typing import List, Dict, Any, Iterable +from typing import Dict, Any, Iterable import clique import ayon_api from ayon_core.lib import create_hard_link -from .anatomy import Anatomy from .template_data import ( get_general_template_data, get_folder_template_data, diff --git a/client/ayon_core/plugins/load/delivery.py b/client/ayon_core/plugins/load/delivery.py index 3c9f1b9691..e1cd136b26 100644 --- a/client/ayon_core/plugins/load/delivery.py +++ b/client/ayon_core/plugins/load/delivery.py @@ -1,23 +1,22 @@ -import copy import platform from collections import defaultdict import ayon_api from qtpy import QtWidgets, QtCore, QtGui -from ayon_core.pipeline import load, Anatomy from ayon_core import resources, style - from ayon_core.lib import ( format_file_size, collect_frames, get_datetime_data, ) +from ayon_core.pipeline import load, Anatomy from ayon_core.pipeline.load import get_representation_path_with_anatomy from ayon_core.pipeline.delivery import ( get_format_dict, check_destination_path, - deliver_single_file + deliver_single_file, + get_representations_template_data, ) From e175121d0a30ccef65fcc1478c05740badc1dd99 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 15 Oct 2024 13:55:38 +0200 Subject: [PATCH 11/17] fix typo --- client/ayon_core/plugins/load/delivery.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/load/delivery.py b/client/ayon_core/plugins/load/delivery.py index e1cd136b26..559950c997 100644 --- a/client/ayon_core/plugins/load/delivery.py +++ b/client/ayon_core/plugins/load/delivery.py @@ -350,8 +350,8 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): def _get_selected_repres(self): """Returns list of representation names filtered from checkboxes.""" selected_repres = [] - for repre_name, chckbox in self._representation_checkboxes.items(): - if chckbox.isChecked(): + for repre_name, checkbox in self._representation_checkboxes.items(): + if checkbox.isChecked(): selected_repres.append(repre_name) return selected_repres From d15740e33f0d3d652ff4c9bed1b82d852d316acc Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:30:50 +0200 Subject: [PATCH 12/17] fix import --- client/ayon_core/plugins/load/delivery.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/plugins/load/delivery.py b/client/ayon_core/plugins/load/delivery.py index 559950c997..406040d936 100644 --- a/client/ayon_core/plugins/load/delivery.py +++ b/client/ayon_core/plugins/load/delivery.py @@ -16,7 +16,7 @@ from ayon_core.pipeline.delivery import ( get_format_dict, check_destination_path, deliver_single_file, - get_representations_template_data, + get_representations_delivery_template_data, ) @@ -206,8 +206,10 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): filtered_repres.append(repre) repre_ids.add(repre["id"]) - template_data_by_repre_id = get_representations_template_data( - self.anatomy.project_name, repre_ids + template_data_by_repre_id = ( + get_representations_delivery_template_data( + self.anatomy.project_name, repre_ids + ) ) for repre in filtered_repres: repre_path = get_representation_path_with_anatomy( From ceedd7fbcd4489229aea76f6d64e89f76d233ea3 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 15 Oct 2024 14:22:34 +0000 Subject: [PATCH 13/17] [Automated] Add generated package files to main --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index 458129f367..e9ce613942 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.1+dev" +__version__ = "1.0.2" diff --git a/package.py b/package.py index c059eed423..a9c66833cc 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.1+dev" +version = "1.0.2" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index 0a7d0d76c9..293be52d6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.1+dev" +version = "1.0.2" description = "" authors = ["Ynput Team "] readme = "README.md" From 50cad97cad63474ff5a51d2fc2214cfb45d2766c Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 15 Oct 2024 14:23:14 +0000 Subject: [PATCH 14/17] [Automated] Update version in package.py for develop --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index e9ce613942..8fa97eb9cb 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.2" +__version__ = "1.0.2+dev" diff --git a/package.py b/package.py index a9c66833cc..b5cd0a1903 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.2" +version = "1.0.2+dev" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index 293be52d6e..8b03020f6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.2" +version = "1.0.2+dev" description = "" authors = ["Ynput Team "] readme = "README.md" From b239cdd8916e07c51bbed7ce7d024a911b554a4b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 16 Oct 2024 00:06:40 +0200 Subject: [PATCH 15/17] Fix single frame publishing (from e.g. Maya) Also fixes it for other hosts that use instance.data[`expectedFiles`] with the value being `list[dict[str, list[str]]]` (Basically the files per AOV, where the list of filenames is `list[str]` but the integrator and other areas really want a single `str` insteaf of `list[str]` if it's a single frame) --- client/ayon_core/pipeline/farm/pyblish_functions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index af90903bd8..98951b2766 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -788,15 +788,15 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, colorspace = product.colorspace break - if isinstance(files, (list, tuple)): - files = [os.path.basename(f) for f in files] + if isinstance(collected_files, (list, tuple)): + collected_files = [os.path.basename(f) for f in collected_files] else: - files = os.path.basename(files) + collected_files = os.path.basename(collected_files) rep = { "name": ext, "ext": ext, - "files": files, + "files": collected_files, "frameStart": int(skeleton["frameStartHandle"]), "frameEnd": int(skeleton["frameEndHandle"]), # If expectedFile are absolute, we need only filenames From 1eb25ef945ffe0fe279f0ad4e4e54ba818f85e00 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 16 Oct 2024 09:34:12 +0000 Subject: [PATCH 16/17] [Automated] Add generated package files to main --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index 8fa97eb9cb..3da4af0b4e 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.2+dev" +__version__ = "1.0.3" diff --git a/package.py b/package.py index b5cd0a1903..a5319dd139 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.2+dev" +version = "1.0.3" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index 8b03020f6d..9b2b13ffa8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.2+dev" +version = "1.0.3" description = "" authors = ["Ynput Team "] readme = "README.md" From 47b5d90495563be2660d8b10b1b0479c3d1330ad Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 16 Oct 2024 09:34:49 +0000 Subject: [PATCH 17/17] [Automated] Update version in package.py for develop --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index 3da4af0b4e..9a951d7fd4 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.3" +__version__ = "1.0.3+dev" diff --git a/package.py b/package.py index a5319dd139..5d5218748c 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.3" +version = "1.0.3+dev" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index 9b2b13ffa8..ebf08be4a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.3" +version = "1.0.3+dev" description = "" authors = ["Ynput Team "] readme = "README.md"