mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
Merge branch 'develop' into enhancement/integrate_inputlinks_no_workfile_to_debug
This commit is contained in:
commit
80f303c735
10 changed files with 352 additions and 105 deletions
|
|
@ -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,
|
||||
|
||||
|
|
@ -87,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",
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"""Library functions for publishing."""
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import inspect
|
||||
import copy
|
||||
|
|
@ -8,19 +10,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,
|
||||
|
|
@ -246,6 +248,67 @@ 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"]["tools"]["publish"]["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.
|
||||
|
|
@ -1099,14 +1162,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"],
|
||||
|
|
@ -1129,17 +1194,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.")
|
||||
|
|
|
|||
|
|
@ -259,7 +259,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
repre_thumb_created = self._create_colorspace_thumbnail(
|
||||
full_input_path,
|
||||
full_output_path,
|
||||
colorspace_data
|
||||
colorspace_data,
|
||||
thumbnail_def,
|
||||
)
|
||||
|
||||
# Try to use FFMPEG if OIIO is not supported or for cases when
|
||||
|
|
@ -400,7 +401,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
|
||||
return review_repres + other_repres
|
||||
|
||||
def _is_valid_images_repre(self, repre):
|
||||
def _is_valid_images_repre(self, repre: dict) -> bool:
|
||||
"""Check if representation contains valid image files
|
||||
|
||||
Args:
|
||||
|
|
@ -420,10 +421,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
|
||||
def _create_colorspace_thumbnail(
|
||||
self,
|
||||
src_path,
|
||||
dst_path,
|
||||
colorspace_data,
|
||||
thumbnail_def
|
||||
src_path: str,
|
||||
dst_path: str,
|
||||
colorspace_data: dict,
|
||||
thumbnail_def: ThumbnailDef,
|
||||
):
|
||||
"""Create thumbnail using OIIO tool oiiotool
|
||||
|
||||
|
|
@ -436,11 +437,12 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
config (dict)
|
||||
display (Optional[str])
|
||||
view (Optional[str])
|
||||
thumbnail_def (ThumbnailDefinition): Thumbnail definition.
|
||||
|
||||
Returns:
|
||||
str: path to created thumbnail
|
||||
"""
|
||||
self.log.info("Extracting thumbnail {}".format(dst_path))
|
||||
self.log.info(f"Extracting thumbnail {dst_path}")
|
||||
resolution_arg = self._get_resolution_args(
|
||||
"oiiotool", src_path, thumbnail_def
|
||||
)
|
||||
|
|
@ -599,10 +601,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
|
||||
def _create_frame_from_video(
|
||||
self,
|
||||
video_file_path,
|
||||
output_dir,
|
||||
thumbnail_def
|
||||
):
|
||||
video_file_path: str,
|
||||
output_dir: str,
|
||||
thumbnail_def: ThumbnailDef,
|
||||
) -> Optional[str]:
|
||||
"""Convert video file to one frame image via ffmpeg"""
|
||||
# create output file path
|
||||
base_name = os.path.basename(video_file_path)
|
||||
|
|
@ -703,10 +705,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
|
||||
def _get_resolution_args(
|
||||
self,
|
||||
application,
|
||||
input_path,
|
||||
thumbnail_def
|
||||
):
|
||||
application: str,
|
||||
input_path: str,
|
||||
thumbnail_def: ThumbnailDef,
|
||||
) -> list:
|
||||
# get settings
|
||||
if thumbnail_def.target_size["type"] == "source":
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from operator import attrgetter
|
|||
import dataclasses
|
||||
import os
|
||||
import platform
|
||||
from collections import defaultdict
|
||||
from typing import Any, Dict, List
|
||||
|
||||
import pyblish.api
|
||||
|
|
@ -13,10 +14,11 @@ except ImportError:
|
|||
from ayon_core.lib import (
|
||||
TextDef,
|
||||
BoolDef,
|
||||
NumberDef,
|
||||
UISeparatorDef,
|
||||
UILabelDef,
|
||||
EnumDef,
|
||||
filter_profiles, NumberDef
|
||||
filter_profiles,
|
||||
)
|
||||
try:
|
||||
from ayon_core.pipeline.usdlib import (
|
||||
|
|
@ -278,19 +280,23 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin,
|
|||
# level, you can add it directly from the publisher at that particular
|
||||
# order. Future publishes will then see the existing contribution and will
|
||||
# persist adding it to future bootstraps at that order
|
||||
contribution_layers: Dict[str, int] = {
|
||||
contribution_layers: Dict[str, Dict[str, int]] = {
|
||||
# asset layers
|
||||
"model": 100,
|
||||
"assembly": 150,
|
||||
"groom": 175,
|
||||
"look": 200,
|
||||
"rig": 300,
|
||||
"asset": {
|
||||
"model": 100,
|
||||
"assembly": 150,
|
||||
"groom": 175,
|
||||
"look": 200,
|
||||
"rig": 300,
|
||||
},
|
||||
# shot layers
|
||||
"layout": 200,
|
||||
"animation": 300,
|
||||
"simulation": 400,
|
||||
"fx": 500,
|
||||
"lighting": 600,
|
||||
"shot": {
|
||||
"layout": 200,
|
||||
"animation": 300,
|
||||
"simulation": 400,
|
||||
"fx": 500,
|
||||
"lighting": 600,
|
||||
}
|
||||
}
|
||||
# Default profiles to set certain instance attribute defaults based on
|
||||
# profiles in settings
|
||||
|
|
@ -305,12 +311,18 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin,
|
|||
|
||||
cls.enabled = plugin_settings.get("enabled", cls.enabled)
|
||||
|
||||
# Define contribution layers via settings
|
||||
contribution_layers = {}
|
||||
# Define contribution layers via settings by their scope
|
||||
contribution_layers = defaultdict(dict)
|
||||
for entry in plugin_settings.get("contribution_layers", []):
|
||||
contribution_layers[entry["name"]] = int(entry["order"])
|
||||
for scope in entry.get("scope", []):
|
||||
contribution_layers[scope][entry["name"]] = int(entry["order"])
|
||||
if contribution_layers:
|
||||
cls.contribution_layers = contribution_layers
|
||||
cls.contribution_layers = dict(contribution_layers)
|
||||
else:
|
||||
cls.log.warning(
|
||||
"No scoped contribution layers found in settings, falling back"
|
||||
" to CollectUSDLayerContributions plug-in defaults..."
|
||||
)
|
||||
|
||||
cls.profiles = plugin_settings.get("profiles", [])
|
||||
|
||||
|
|
@ -355,10 +367,11 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin,
|
|||
|
||||
asset_product = contribution.target_product
|
||||
layer_product = "{}_{}".format(asset_product, contribution.layer_id)
|
||||
layer_order: int = self.contribution_layers.get(
|
||||
attr_values["contribution_layer"], 0
|
||||
)
|
||||
|
||||
scope: str = attr_values["contribution_target_product_init"]
|
||||
layer_order: int = (
|
||||
self.contribution_layers[scope][attr_values["contribution_layer"]]
|
||||
)
|
||||
# Layer contribution instance
|
||||
layer_instance = self.get_or_create_instance(
|
||||
product_name=layer_product,
|
||||
|
|
@ -489,14 +502,14 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin,
|
|||
profile = {}
|
||||
|
||||
# Define defaults
|
||||
default_enabled = profile.get("contribution_enabled", True)
|
||||
default_enabled: bool = profile.get("contribution_enabled", True)
|
||||
default_contribution_layer = profile.get(
|
||||
"contribution_layer", None)
|
||||
default_apply_as_variant = profile.get(
|
||||
default_apply_as_variant: bool = profile.get(
|
||||
"contribution_apply_as_variant", False)
|
||||
default_target_product = profile.get(
|
||||
default_target_product: str = profile.get(
|
||||
"contribution_target_product", "usdAsset")
|
||||
default_init_as = (
|
||||
default_init_as: str = (
|
||||
"asset"
|
||||
if profile.get("contribution_target_product") == "usdAsset"
|
||||
else "shot")
|
||||
|
|
@ -509,6 +522,12 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin,
|
|||
visible = publish_attributes.get("contribution_enabled", True)
|
||||
variant_visible = visible and publish_attributes.get(
|
||||
"contribution_apply_as_variant", True)
|
||||
init_as: str = publish_attributes.get(
|
||||
"contribution_target_product_init", default_init_as)
|
||||
|
||||
contribution_layers = cls.contribution_layers.get(
|
||||
init_as, {}
|
||||
)
|
||||
|
||||
return [
|
||||
UISeparatorDef("usd_container_settings1"),
|
||||
|
|
@ -558,7 +577,7 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin,
|
|||
"predefined ordering.\nA higher order (further down "
|
||||
"the list) will contribute as a stronger opinion."
|
||||
),
|
||||
items=list(cls.contribution_layers.keys()),
|
||||
items=list(contribution_layers.keys()),
|
||||
default=default_contribution_layer,
|
||||
visible=visible),
|
||||
# TODO: We may want to make the visibility of this optional
|
||||
|
|
@ -619,7 +638,11 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin,
|
|||
|
||||
# Update attributes if any of the following plug-in attributes
|
||||
# change:
|
||||
keys = ["contribution_enabled", "contribution_apply_as_variant"]
|
||||
keys = {
|
||||
"contribution_enabled",
|
||||
"contribution_apply_as_variant",
|
||||
"contribution_target_product_init",
|
||||
}
|
||||
|
||||
for instance_change in event["changes"]:
|
||||
instance = instance_change["instance"]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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", []
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
@ -83,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 = []
|
||||
|
|
@ -91,12 +97,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
|
||||
|
||||
icon = None
|
||||
if filepath.replace("\\", "/") in self._blocking_crashed_paths:
|
||||
icon = self._get_blocking_icon()
|
||||
|
||||
item.setData(icon, QtCore.Qt.DecorationRole)
|
||||
|
||||
if new_items:
|
||||
parent.appendRows(new_items)
|
||||
|
|
@ -113,6 +125,16 @@ 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 +878,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 +888,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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -393,6 +395,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 +411,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,11 +671,33 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
|
||||
self._tab_on_reset = tab
|
||||
|
||||
def _update_publish_details_widget(self, force=False):
|
||||
if not force and not self._is_on_details_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: 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()
|
||||
if report_data is None:
|
||||
report_data = self._controller.get_publish_report()
|
||||
self._publish_details_widget.set_report_data(report_data)
|
||||
|
||||
def _on_help_click(self):
|
||||
|
|
@ -752,19 +781,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,26 +881,56 @@ 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:
|
||||
self._create_context_valid = valid
|
||||
self._update_footer_state()
|
||||
|
||||
def _set_blocked(self, blocked: bool) -> None:
|
||||
self._blocked_by_crashed_paths = blocked
|
||||
self._overview_widget.setEnabled(not blocked)
|
||||
self._update_footer_state()
|
||||
if not blocked:
|
||||
return
|
||||
|
||||
self.set_tab_on_reset("details")
|
||||
self._go_to_details_tab()
|
||||
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
"Failed to load plugins",
|
||||
(
|
||||
"Failed to load plugins that do prevent you from"
|
||||
" using publish tool.\n"
|
||||
"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)
|
||||
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)
|
||||
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)
|
||||
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 +998,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 +1019,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()
|
||||
|
|
|
|||
|
|
@ -74,13 +74,35 @@ class CollectFramesFixDefModel(BaseSettingsModel):
|
|||
)
|
||||
|
||||
|
||||
def usd_contribution_layer_types():
|
||||
return [
|
||||
{"value": "asset", "label": "Asset"},
|
||||
{"value": "shot", "label": "Shot"},
|
||||
]
|
||||
|
||||
|
||||
class ContributionLayersModel(BaseSettingsModel):
|
||||
_layout = "compact"
|
||||
name: str = SettingsField(title="Name")
|
||||
order: str = SettingsField(
|
||||
name: str = SettingsField(
|
||||
default="",
|
||||
regex="[A-Za-z0-9_-]+",
|
||||
title="Name")
|
||||
scope: list[str] = SettingsField(
|
||||
# This should actually be returned from a callable to `default_factory`
|
||||
# because lists are mutable. However, the frontend can't interpret
|
||||
# the callable. It will fail to apply it as the default. Specifying
|
||||
# this default directly did not show any ill side effects.
|
||||
default=["asset", "shot"],
|
||||
title="Scope",
|
||||
min_items=1,
|
||||
enum_resolver=usd_contribution_layer_types)
|
||||
order: int = SettingsField(
|
||||
default=0,
|
||||
title="Order",
|
||||
description="Higher order means a higher strength and stacks the "
|
||||
"layer on top.")
|
||||
description=(
|
||||
"Higher order means a higher strength and stacks the layer on top."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class CollectUSDLayerContributionsProfileModel(BaseSettingsModel):
|
||||
|
|
@ -1382,17 +1404,17 @@ DEFAULT_PUBLISH_VALUES = {
|
|||
"enabled": True,
|
||||
"contribution_layers": [
|
||||
# Asset layers
|
||||
{"name": "model", "order": 100},
|
||||
{"name": "assembly", "order": 150},
|
||||
{"name": "groom", "order": 175},
|
||||
{"name": "look", "order": 200},
|
||||
{"name": "rig", "order": 300},
|
||||
{"name": "model", "order": 100, "scope": ["asset"]},
|
||||
{"name": "assembly", "order": 150, "scope": ["asset"]},
|
||||
{"name": "groom", "order": 175, "scope": ["asset"]},
|
||||
{"name": "look", "order": 200, "scope": ["asset"]},
|
||||
{"name": "rig", "order": 300, "scope": ["asset"]},
|
||||
# Shot layers
|
||||
{"name": "layout", "order": 200},
|
||||
{"name": "animation", "order": 300},
|
||||
{"name": "simulation", "order": 400},
|
||||
{"name": "fx", "order": 500},
|
||||
{"name": "lighting", "order": 600},
|
||||
{"name": "layout", "order": 200, "scope": ["shot"]},
|
||||
{"name": "animation", "order": 300, "scope": ["shot"]},
|
||||
{"name": "simulation", "order": 400, "scope": ["shot"]},
|
||||
{"name": "fx", "order": 500, "scope": ["shot"]},
|
||||
{"name": "lighting", "order": 600, "scope": ["shot"]},
|
||||
],
|
||||
"profiles": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue