From 3f72115a5edeb572135694249df1a3be7f21a352 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:12:35 +0100 Subject: [PATCH 01/15] added option to filter crashed files --- client/ayon_core/pipeline/publish/lib.py | 59 ++++++++++++++++++++++++ server/settings/publish_plugins.py | 31 +++++++++++++ 2 files changed, 90 insertions(+) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index e512a0116f..6af37aa388 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -246,6 +246,65 @@ def load_help_content_from_plugin( return load_help_content_from_filepath(filepath) +def filter_crashed_publish_paths( + project_name: str, + crashed_paths: set[str], + *, + project_settings: Optional[dict[str, Any]] = None, +) -> set[str]: + """Filter crashed paths happened during plugins discovery. + + Check if plugins discovery has enabled strict mode and filter crashed + paths that happened during discover based on regexes from settings. + + Publishing should not start if any paths are returned. + + Args: + project_name (str): Project name in which context plugins discovery + happened. + crashed_paths (set[str]): Crashed paths from plugins discovery report. + project_settings (Optional[dict[str, Any]]): Project settings. + + Returns: + set[str]: Filtered crashed paths. + + """ + filtered_paths = set() + # Nothing crashed all good... + if not crashed_paths: + return filtered_paths + + if project_settings is None: + project_settings = get_project_settings(project_name) + + discover_validation = project_settings["core"]["discover_validation"] + # Strict mode is not enabled. + if not discover_validation["enabled"]: + return filtered_paths + + regexes = [ + re.compile(value, re.IGNORECASE) + for value in discover_validation["ignore_paths"] + if value + ] + is_windows = platform.system().lower() == "windows" + # Fitler path with regexes from settings + for path in crashed_paths: + # Normalize paths to use forward slashes on windows + if is_windows: + path = path.replace("\\", "/") + is_invalid = True + for regex in regexes: + if regex.match(path): + is_invalid = False + break + + if is_invalid: + filtered_paths.add(path) + + return filtered_paths + + def publish_plugins_discover( paths: Optional[list[str]] = None) -> DiscoverResult: """Find and return available pyblish plug-ins. diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index d7b794cb5b..f47cf24fea 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -34,6 +34,28 @@ class ValidateBaseModel(BaseSettingsModel): active: bool = SettingsField(True, title="Active") +class DiscoverValidationModel(BaseSettingsModel): + """Strictly validate publish plugins discovery. + + Artist won't be able to publish if path to publish plugin fails to be + imported. + + """ + _isGroup = True + enabled: bool = SettingsField( + False, + description="Enable strict mode of plugins discovery", + ) + ignore_paths: list[str] = SettingsField( + default_factory=list, + title="Ignored paths (regex)", + description=( + "Paths that do match regex will be skipped in validation." + ), + ) + + + class CollectAnatomyInstanceDataModel(BaseSettingsModel): _isGroup = True follow_workfile_version: bool = SettingsField( @@ -1188,6 +1210,11 @@ class CleanUpFarmModel(BaseSettingsModel): class PublishPuginsModel(BaseSettingsModel): + discover_validation: DiscoverValidationModel = SettingsField( + default_factory=DiscoverValidationModel, + title="Validate plugins discovery", + ) + CollectAnatomyInstanceData: CollectAnatomyInstanceDataModel = ( SettingsField( default_factory=CollectAnatomyInstanceDataModel, @@ -1308,6 +1335,10 @@ class PublishPuginsModel(BaseSettingsModel): DEFAULT_PUBLISH_VALUES = { + "discover_validation": { + "enabled": False, + "ignore_paths": [], + }, "CollectAnatomyInstanceData": { "follow_workfile_version": False }, From 09364a4f7e9adc2485e5544468117b15ef96f224 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:20:40 +0100 Subject: [PATCH 02/15] use the function in main cli publish --- client/ayon_core/pipeline/publish/lib.py | 42 +++++++++++++++++------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 6af37aa388..2777f7511c 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -8,19 +8,19 @@ import warnings import hashlib import xml.etree.ElementTree from typing import TYPE_CHECKING, Optional, Union, List, Any -import clique -import speedcopy import logging -import pyblish.util -import pyblish.plugin -import pyblish.api - from ayon_api import ( get_server_api_connection, get_representations, get_last_version_by_product_name ) +import clique +import pyblish.util +import pyblish.plugin +import pyblish.api +import speedcopy + from ayon_core.lib import ( import_filepath, Logger, @@ -1158,14 +1158,16 @@ def main_cli_publish( except ValueError: pass + context = get_global_context() + project_settings = get_project_settings(context["project_name"]) + install_ayon_plugins() if addons_manager is None: - addons_manager = AddonsManager() + addons_manager = AddonsManager(project_settings) applications_addon = addons_manager.get_enabled_addon("applications") if applications_addon is not None: - context = get_global_context() env = applications_addon.get_farm_publish_environment_variables( context["project_name"], context["folder_path"], @@ -1188,17 +1190,33 @@ def main_cli_publish( log.info("Running publish ...") discover_result = publish_plugins_discover() - publish_plugins = discover_result.plugins print(discover_result.get_report(only_errors=False)) + filtered_crashed_paths = filter_crashed_publish_paths( + context["project_name"], + set(discover_result.crashed_file_paths), + project_settings=project_settings, + ) + if filtered_crashed_paths: + joined_paths = "\n".join([ + f"- {path}" + for path in filtered_crashed_paths + ]) + log.error( + "Plugin discovery strict mode is enabled." + " Crashed plugin paths that prevent from publishing:" + f"\n{joined_paths}" + ) + sys.exit(1) + + publish_plugins = discover_result.plugins + # Error exit as soon as any error occurs. - error_format = ("Failed {plugin.__name__}: " - "{error} -- {error.traceback}") + error_format = "Failed {plugin.__name__}: {error} -- {error.traceback}" for result in pyblish.util.publish_iter(plugins=publish_plugins): if result["error"]: log.error(error_format.format(**result)) - # uninstall() sys.exit(1) log.info("Publish finished.") From 096a5a809e1225374a9f6777ec322293f698427b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:28:30 +0100 Subject: [PATCH 03/15] fix imports --- client/ayon_core/pipeline/publish/__init__.py | 1 + client/ayon_core/pipeline/publish/lib.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/client/ayon_core/pipeline/publish/__init__.py b/client/ayon_core/pipeline/publish/__init__.py index ede7fc3a35..105ab53fb0 100644 --- a/client/ayon_core/pipeline/publish/__init__.py +++ b/client/ayon_core/pipeline/publish/__init__.py @@ -29,6 +29,7 @@ from .lib import ( get_publish_template_name, publish_plugins_discover, + filter_crashed_publish_paths, load_help_content_from_plugin, load_help_content_from_filepath, diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 2777f7511c..934542bc1c 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -1,6 +1,8 @@ """Library functions for publishing.""" from __future__ import annotations import os +import platform +import re import sys import inspect import copy From c53a2f68e54e99dcfbd41f411fcd25fc2806e72b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 16 Dec 2025 18:01:10 +0100 Subject: [PATCH 04/15] fix settings load --- client/ayon_core/pipeline/publish/lib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 934542bc1c..d511ad8b45 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -279,7 +279,9 @@ def filter_crashed_publish_paths( if project_settings is None: project_settings = get_project_settings(project_name) - discover_validation = project_settings["core"]["discover_validation"] + discover_validation = ( + project_settings["core"]["publish"]["discover_validation"] + ) # Strict mode is not enabled. if not discover_validation["enabled"]: return filtered_paths From 856a58dc35ecbccd2b17e4e73f77d6047e3c8362 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 16 Dec 2025 18:03:12 +0100 Subject: [PATCH 05/15] block publisher on blocking failed plugins --- .../tools/publisher/models/publish.py | 17 ++++- client/ayon_core/tools/publisher/window.py | 73 ++++++++++++++----- 2 files changed, 71 insertions(+), 19 deletions(-) diff --git a/client/ayon_core/tools/publisher/models/publish.py b/client/ayon_core/tools/publisher/models/publish.py index 97070d106f..cd99a952e3 100644 --- a/client/ayon_core/tools/publisher/models/publish.py +++ b/client/ayon_core/tools/publisher/models/publish.py @@ -21,6 +21,7 @@ from ayon_core.pipeline.plugin_discover import DiscoverResult from ayon_core.pipeline.publish import ( get_publish_instance_label, PublishError, + filter_crashed_publish_paths, ) from ayon_core.tools.publisher.abstract import AbstractPublisherBackend @@ -107,11 +108,14 @@ class PublishReportMaker: creator_discover_result: Optional[DiscoverResult] = None, convertor_discover_result: Optional[DiscoverResult] = None, publish_discover_result: Optional[DiscoverResult] = None, + blocking_crashed_paths: Optional[list[str]] = None, ): self._create_discover_result: Union[DiscoverResult, None] = None self._convert_discover_result: Union[DiscoverResult, None] = None self._publish_discover_result: Union[DiscoverResult, None] = None + self._blocking_crashed_paths: list[str] = [] + self._all_instances_by_id: Dict[str, pyblish.api.Instance] = {} self._plugin_data_by_id: Dict[str, Any] = {} self._current_plugin_id: Optional[str] = None @@ -120,6 +124,7 @@ class PublishReportMaker: creator_discover_result, convertor_discover_result, publish_discover_result, + blocking_crashed_paths, ) def reset( @@ -127,12 +132,14 @@ class PublishReportMaker: creator_discover_result: Union[DiscoverResult, None], convertor_discover_result: Union[DiscoverResult, None], publish_discover_result: Union[DiscoverResult, None], + blocking_crashed_paths: list[str], ): """Reset report and clear all data.""" self._create_discover_result = creator_discover_result self._convert_discover_result = convertor_discover_result self._publish_discover_result = publish_discover_result + self._blocking_crashed_paths = blocking_crashed_paths self._all_instances_by_id = {} self._plugin_data_by_id = {} @@ -242,9 +249,10 @@ class PublishReportMaker: "instances": instances_details, "context": self._extract_context_data(publish_context), "crashed_file_paths": crashed_file_paths, + "blocking_crashed_paths": list(self._blocking_crashed_paths), "id": uuid.uuid4().hex, "created_at": now.isoformat(), - "report_version": "1.1.0", + "report_version": "1.1.1", } def _add_plugin_data_item(self, plugin: pyblish.api.Plugin): @@ -959,11 +967,16 @@ class PublishModel: self._publish_plugins_proxy = PublishPluginsProxy( publish_plugins ) - + blocking_crashed_paths = filter_crashed_publish_paths( + create_context.get_current_project_name(), + set(create_context.publish_discover_result.crashed_file_paths), + project_settings=create_context.get_current_project_settings(), + ) self._publish_report.reset( create_context.creator_discover_result, create_context.convertor_discover_result, create_context.publish_discover_result, + blocking_crashed_paths, ) for plugin in create_context.publish_plugins_mismatch_targets: self._publish_report.set_plugin_skipped(plugin.id) diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index 19994f9f62..2d7bcc5a2a 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -393,6 +393,9 @@ class PublisherWindow(QtWidgets.QDialog): self._publish_frame_visible = None self._tab_on_reset = None + self._create_context_valid: bool = True + self._blocked_by_crashed_paths: bool = False + self._error_messages_to_show = collections.deque() self._errors_dialog_message_timer = errors_dialog_message_timer @@ -406,6 +409,8 @@ class PublisherWindow(QtWidgets.QDialog): self._show_counter = 0 self._window_is_visible = False + self._update_footer_state() + @property def controller(self) -> AbstractPublisherFrontend: """Kept for compatibility with traypublisher.""" @@ -664,12 +669,27 @@ class PublisherWindow(QtWidgets.QDialog): self._tab_on_reset = tab + def set_current_tab(self, tab): + if tab == "create": + self._go_to_create_tab() + elif tab == "publish": + self._go_to_publish_tab() + elif tab == "report": + self._go_to_report_tab() + elif tab == "details": + self._go_to_details_tab() + + if not self._window_is_visible: + self.set_tab_on_reset(tab) + def _update_publish_details_widget(self, force=False): if not force and not self._is_on_details_tab(): return report_data = self._controller.get_publish_report() + blocked = bool(report_data["blocking_crashed_paths"]) self._publish_details_widget.set_report_data(report_data) + self._set_blocked(blocked) def _on_help_click(self): if self._help_dialog.isVisible(): @@ -752,19 +772,6 @@ class PublisherWindow(QtWidgets.QDialog): def _set_current_tab(self, identifier): self._tabs_widget.set_current_tab(identifier) - def set_current_tab(self, tab): - if tab == "create": - self._go_to_create_tab() - elif tab == "publish": - self._go_to_publish_tab() - elif tab == "report": - self._go_to_report_tab() - elif tab == "details": - self._go_to_details_tab() - - if not self._window_is_visible: - self.set_tab_on_reset(tab) - def _is_current_tab(self, identifier): return self._tabs_widget.is_current_tab(identifier) @@ -865,8 +872,39 @@ class PublisherWindow(QtWidgets.QDialog): # Reset style self._comment_input.setStyleSheet("") - def _set_footer_enabled(self, enabled): - self._save_btn.setEnabled(True) + def _set_create_context_valid(self, valid: bool) -> None: + if self._create_context_valid is valid: + return + self._create_context_valid = valid + self._update_footer_state() + + def _set_blocked(self, blocked: bool) -> None: + if self._blocked_by_crashed_paths is blocked: + return + self._blocked_by_crashed_paths = blocked + self._overview_widget.setEnabled(not blocked) + self._update_footer_state() + if not blocked: + return + + QtWidgets.QMessageBox.critical( + self, + "Failed to load plugins", + ( + "Failed to load plugins that do prevent you from" + " using publish tool." + " Please contact your TD or administrator." + ) + ) + + def _update_footer_state(self) -> None: + enabled = ( + not self._blocked_by_crashed_paths + and self._create_context_valid + ) + save_enabled = not self._blocked_by_crashed_paths + + self._save_btn.setEnabled(save_enabled) self._reset_btn.setEnabled(True) if enabled: self._stop_btn.setEnabled(False) @@ -885,6 +923,7 @@ class PublisherWindow(QtWidgets.QDialog): self._update_publish_details_widget() def _on_controller_reset(self): + self._update_publish_details_widget(force=True) self._first_reset, first_reset = False, self._first_reset if self._tab_on_reset is not None: self._tab_on_reset, new_tab = None, self._tab_on_reset @@ -952,7 +991,7 @@ class PublisherWindow(QtWidgets.QDialog): def _validate_create_instances(self): if not self._controller.is_host_valid(): - self._set_footer_enabled(True) + self._set_create_context_valid(True) return active_instances_by_id = { @@ -973,7 +1012,7 @@ class PublisherWindow(QtWidgets.QDialog): if all_valid is None: all_valid = True - self._set_footer_enabled(bool(all_valid)) + self._set_create_context_valid(bool(all_valid)) def _on_create_model_reset(self): self._validate_create_instances() From ae7726bdef849c58634cdf01e84bc21753230a39 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 16 Dec 2025 18:31:35 +0100 Subject: [PATCH 06/15] change tabs and mark blocking filepath --- .../publish_report_viewer/report_items.py | 3 ++ .../publish_report_viewer/widgets.py | 38 +++++++++++++++---- client/ayon_core/tools/publisher/window.py | 3 ++ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/report_items.py b/client/ayon_core/tools/publisher/publish_report_viewer/report_items.py index a3c5a7a2fd..24955d18c3 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/report_items.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/report_items.py @@ -139,3 +139,6 @@ class PublishReport: self.logs = logs self.crashed_plugin_paths = report_data["crashed_file_paths"] + self.blocking_crashed_paths = report_data.get( + "blocking_crashed_paths", [] + ) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index 5fa1c04dc0..74ff647538 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -7,6 +7,7 @@ from ayon_core.tools.utils import ( SeparatorWidget, IconButton, paint_image_with_color, + get_qt_icon, ) from ayon_core.resources import get_image_path from ayon_core.style import get_objected_colors @@ -46,10 +47,13 @@ def get_pretty_milliseconds(value): class PluginLoadReportModel(QtGui.QStandardItemModel): + _blocking_icon = None + def __init__(self): super().__init__() self._traceback_by_filepath = {} self._items_by_filepath = {} + self._blocking_crashed_paths = set() self._is_active = True self._need_refresh = False @@ -75,6 +79,7 @@ class PluginLoadReportModel(QtGui.QStandardItemModel): for filepath in to_remove: self._traceback_by_filepath.pop(filepath) + self._blocking_crashed_paths = set(report.blocking_crashed_paths) self._update_items() def _update_items(self): @@ -91,12 +96,18 @@ class PluginLoadReportModel(QtGui.QStandardItemModel): set(self._items_by_filepath) - set(self._traceback_by_filepath) ) for filepath in self._traceback_by_filepath: - if filepath in self._items_by_filepath: - continue - item = QtGui.QStandardItem(filepath) - new_items.append(item) - new_items_by_filepath[filepath] = item - self._items_by_filepath[filepath] = item + item = self._items_by_filepath.get(filepath) + if item is None: + item = QtGui.QStandardItem(filepath) + new_items.append(item) + new_items_by_filepath[filepath] = item + self._items_by_filepath[filepath] = item + + if filepath.replace("\\", "/") in self._blocking_crashed_paths: + item.setData( + self._get_blocking_icon(), + QtCore.Qt.DecorationRole + ) if new_items: parent.appendRows(new_items) @@ -113,6 +124,15 @@ class PluginLoadReportModel(QtGui.QStandardItemModel): item = self._items_by_filepath.pop(filepath) parent.removeRow(item.row()) + @classmethod + def _get_blocking_icon(cls): + if cls._blocking_icon is None: + cls._blocking_icon = get_qt_icon({ + "type": "material-symbols", + "name": "block", + "color": "red", + }) + return cls._blocking_icon class DetailWidget(QtWidgets.QTextEdit): def __init__(self, text, *args, **kwargs): @@ -856,7 +876,7 @@ class PublishReportViewerWidget(QtWidgets.QFrame): report = PublishReport(report_data) self.set_report(report) - def set_report(self, report): + def set_report(self, report: PublishReport) -> None: self._ignore_selection_changes = True self._report_item = report @@ -866,6 +886,10 @@ class PublishReportViewerWidget(QtWidgets.QFrame): self._logs_text_widget.set_report(report) self._plugin_load_report_widget.set_report(report) self._plugins_details_widget.set_report(report) + if report.blocking_crashed_paths: + self._details_tab_widget.setCurrentWidget( + self._plugin_load_report_widget + ) self._ignore_selection_changes = False diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index 2d7bcc5a2a..859fa96132 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -887,6 +887,9 @@ class PublisherWindow(QtWidgets.QDialog): if not blocked: return + self.set_tab_on_reset("details") + self._go_to_details_tab() + QtWidgets.QMessageBox.critical( self, "Failed to load plugins", From e8635725facb681982dc791d5cd1c3c68037e4f9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 16 Dec 2025 18:54:28 +0100 Subject: [PATCH 07/15] ruff fixes --- client/ayon_core/pipeline/publish/__init__.py | 1 + .../ayon_core/tools/publisher/publish_report_viewer/widgets.py | 1 + server/settings/publish_plugins.py | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/publish/__init__.py b/client/ayon_core/pipeline/publish/__init__.py index 105ab53fb0..179d749f48 100644 --- a/client/ayon_core/pipeline/publish/__init__.py +++ b/client/ayon_core/pipeline/publish/__init__.py @@ -88,6 +88,7 @@ __all__ = ( "get_publish_template_name", "publish_plugins_discover", + "filter_crashed_publish_paths", "load_help_content_from_plugin", "load_help_content_from_filepath", diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index 74ff647538..7e7c31db04 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -134,6 +134,7 @@ class PluginLoadReportModel(QtGui.QStandardItemModel): }) return cls._blocking_icon + class DetailWidget(QtWidgets.QTextEdit): def __init__(self, text, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 2d0ac8aa31..e9eb5f7e93 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -55,7 +55,6 @@ class DiscoverValidationModel(BaseSettingsModel): ) - class CollectAnatomyInstanceDataModel(BaseSettingsModel): _isGroup = True follow_workfile_version: bool = SettingsField( From 5404153b942db01a0efd0ccfdb79ddce1c3ffa9f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Dec 2025 09:51:05 +0100 Subject: [PATCH 08/15] Add new line character. Co-authored-by: Roy Nieterau --- client/ayon_core/tools/publisher/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index 859fa96132..50bccb8aa0 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -895,8 +895,8 @@ class PublisherWindow(QtWidgets.QDialog): "Failed to load plugins", ( "Failed to load plugins that do prevent you from" - " using publish tool." - " Please contact your TD or administrator." + " using publish tool.\n" + "Please contact your TD or administrator." ) ) From f4bd5d49f949b2ff45c6c95a5fd25427873662ea Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:38:48 +0100 Subject: [PATCH 09/15] move settings to tools --- server/settings/publish_plugins.py | 30 ------------------------------ server/settings/tools.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index e9eb5f7e93..da22e4206f 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -34,27 +34,6 @@ class ValidateBaseModel(BaseSettingsModel): active: bool = SettingsField(True, title="Active") -class DiscoverValidationModel(BaseSettingsModel): - """Strictly validate publish plugins discovery. - - Artist won't be able to publish if path to publish plugin fails to be - imported. - - """ - _isGroup = True - enabled: bool = SettingsField( - False, - description="Enable strict mode of plugins discovery", - ) - ignore_paths: list[str] = SettingsField( - default_factory=list, - title="Ignored paths (regex)", - description=( - "Paths that do match regex will be skipped in validation." - ), - ) - - class CollectAnatomyInstanceDataModel(BaseSettingsModel): _isGroup = True follow_workfile_version: bool = SettingsField( @@ -1236,11 +1215,6 @@ class CleanUpFarmModel(BaseSettingsModel): class PublishPuginsModel(BaseSettingsModel): - discover_validation: DiscoverValidationModel = SettingsField( - default_factory=DiscoverValidationModel, - title="Validate plugins discovery", - ) - CollectAnatomyInstanceData: CollectAnatomyInstanceDataModel = ( SettingsField( default_factory=CollectAnatomyInstanceDataModel, @@ -1371,10 +1345,6 @@ class PublishPuginsModel(BaseSettingsModel): DEFAULT_PUBLISH_VALUES = { - "discover_validation": { - "enabled": False, - "ignore_paths": [], - }, "CollectAnatomyInstanceData": { "follow_workfile_version": False }, diff --git a/server/settings/tools.py b/server/settings/tools.py index da3b4ebff8..9b16ca5d94 100644 --- a/server/settings/tools.py +++ b/server/settings/tools.py @@ -352,6 +352,27 @@ class CustomStagingDirProfileModel(BaseSettingsModel): ) +class DiscoverValidationModel(BaseSettingsModel): + """Strictly validate publish plugins discovery. + + Artist won't be able to publish if path to publish plugin fails to be + imported. + + """ + _isGroup = True + enabled: bool = SettingsField( + False, + description="Enable strict mode of plugins discovery", + ) + ignore_paths: list[str] = SettingsField( + default_factory=list, + title="Ignored paths (regex)", + description=( + "Paths that do match regex will be skipped in validation." + ), + ) + + class PublishToolModel(BaseSettingsModel): template_name_profiles: list[PublishTemplateNameProfile] = SettingsField( default_factory=list, @@ -369,6 +390,10 @@ class PublishToolModel(BaseSettingsModel): title="Custom Staging Dir Profiles" ) ) + discover_validation: DiscoverValidationModel = SettingsField( + default_factory=DiscoverValidationModel, + title="Validate plugins discovery", + ) comment_minimum_required_chars: int = SettingsField( 0, title="Publish comment minimum required characters", @@ -691,6 +716,10 @@ DEFAULT_TOOLS_VALUES = { "template_name": "simpleUnrealTextureHero" } ], + "discover_validation": { + "enabled": False, + "ignore_paths": [], + }, "comment_minimum_required_chars": 0, } } From 73cc4c53b4ad02f8e0e6971bd456935fd29e5970 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:53:43 +0100 Subject: [PATCH 10/15] use correct settings --- client/ayon_core/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index d511ad8b45..8492145979 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -280,7 +280,7 @@ def filter_crashed_publish_paths( project_settings = get_project_settings(project_name) discover_validation = ( - project_settings["core"]["publish"]["discover_validation"] + project_settings["core"]["tools"]["publish"]["discover_validation"] ) # Strict mode is not enabled. if not discover_validation["enabled"]: From e1dc93cb4452d9ed535009efc31d0ca366e6fe4c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:55:27 +0100 Subject: [PATCH 11/15] unset icon if is not blocking anymore --- .../tools/publisher/publish_report_viewer/widgets.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index 7e7c31db04..d595593898 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -103,11 +103,11 @@ class PluginLoadReportModel(QtGui.QStandardItemModel): new_items_by_filepath[filepath] = item self._items_by_filepath[filepath] = item + icon = None if filepath.replace("\\", "/") in self._blocking_crashed_paths: - item.setData( - self._get_blocking_icon(), - QtCore.Qt.DecorationRole - ) + icon = self._get_blocking_icon() + + item.setData(icon, QtCore.Qt.DecorationRole) if new_items: parent.appendRows(new_items) From 108286aa341cffefd135fc0ac52bffbbe06f9606 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:59:41 +0100 Subject: [PATCH 12/15] fix refresh issue --- .../ayon_core/tools/publisher/publish_report_viewer/widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index d595593898..225dd15ade 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -88,6 +88,7 @@ class PluginLoadReportModel(QtGui.QStandardItemModel): parent = self.invisibleRootItem() if not self._traceback_by_filepath: parent.removeRows(0, parent.rowCount()) + self._items_by_filepath = {} return new_items = [] From de7b49e68ff80368695c569b6a86baadc3c8974a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Dec 2025 12:31:49 +0100 Subject: [PATCH 13/15] simplify update state --- client/ayon_core/tools/publisher/window.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index 50bccb8aa0..ddbda8eedd 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -909,14 +909,9 @@ class PublisherWindow(QtWidgets.QDialog): self._save_btn.setEnabled(save_enabled) self._reset_btn.setEnabled(True) - if enabled: - self._stop_btn.setEnabled(False) - self._validate_btn.setEnabled(True) - self._publish_btn.setEnabled(True) - else: - self._stop_btn.setEnabled(enabled) - self._validate_btn.setEnabled(enabled) - self._publish_btn.setEnabled(enabled) + self._stop_btn.setEnabled(False) + self._validate_btn.setEnabled(enabled) + self._publish_btn.setEnabled(enabled) def _on_publish_reset(self): self._create_tab.setEnabled(True) From 5982ad7944b23bfcdfede55132b5e75c295d9737 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Dec 2025 12:32:19 +0100 Subject: [PATCH 14/15] call set_blocked only on reset --- client/ayon_core/tools/publisher/window.py | 27 ++++++++++++++++------ 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index ddbda8eedd..f74ca9abb9 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -1,9 +1,11 @@ +from __future__ import annotations + import os import json import time import collections import copy -from typing import Optional +from typing import Optional, Any from qtpy import QtWidgets, QtCore, QtGui @@ -682,14 +684,21 @@ class PublisherWindow(QtWidgets.QDialog): if not self._window_is_visible: self.set_tab_on_reset(tab) - def _update_publish_details_widget(self, force=False): - if not force and not self._is_on_details_tab(): + def _update_publish_details_widget( + self, + force: bool = False, + report_data: Optional[dict[str, Any]] = None, + ) -> None: + if ( + report_data is None + and not force + and not self._is_on_details_tab() + ): return - report_data = self._controller.get_publish_report() - blocked = bool(report_data["blocking_crashed_paths"]) + if report_data is None: + report_data = self._controller.get_publish_report() self._publish_details_widget.set_report_data(report_data) - self._set_blocked(blocked) def _on_help_click(self): if self._help_dialog.isVisible(): @@ -918,7 +927,11 @@ class PublisherWindow(QtWidgets.QDialog): self._set_comment_input_visiblity(True) self._set_publish_overlay_visibility(False) self._set_publish_visibility(False) - self._update_publish_details_widget() + + report_data = self._controller.get_publish_report() + blocked = bool(report_data["blocking_crashed_paths"]) + self._set_blocked(blocked) + self._update_publish_details_widget(report_data=report_data) def _on_controller_reset(self): self._update_publish_details_widget(force=True) From e462dca88986b339fd7a7860df05597820039654 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:01:54 +0100 Subject: [PATCH 15/15] always call '_update_footer_state' --- client/ayon_core/tools/publisher/window.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index f74ca9abb9..6f7444ed04 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -882,14 +882,10 @@ class PublisherWindow(QtWidgets.QDialog): self._comment_input.setStyleSheet("") def _set_create_context_valid(self, valid: bool) -> None: - if self._create_context_valid is valid: - return self._create_context_valid = valid self._update_footer_state() def _set_blocked(self, blocked: bool) -> None: - if self._blocked_by_crashed_paths is blocked: - return self._blocked_by_crashed_paths = blocked self._overview_widget.setEnabled(not blocked) self._update_footer_state()