Merge branch 'develop' into feature/909-define-basic-trait-type-using-dataclasses

This commit is contained in:
Ondřej Samohel 2025-01-29 14:54:52 +01:00 committed by GitHub
commit 085aa74976
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 106 additions and 72 deletions

View file

@ -30,7 +30,8 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook):
"aftereffects",
"wrap",
"openrv",
"cinema4d"
"cinema4d",
"silhouette",
}
launch_types = {LaunchTypes.local}

View file

@ -21,7 +21,8 @@ class OCIOEnvHook(PreLaunchHook):
"hiero",
"resolve",
"openrv",
"cinema4d"
"cinema4d",
"silhouette",
}
launch_types = set()

View file

@ -9,7 +9,7 @@ import os
import logging
import collections
from ayon_core.pipeline.constants import AVALON_INSTANCE_ID
from ayon_core.pipeline.constants import AYON_INSTANCE_ID
from .product_name import get_product_name
@ -34,7 +34,7 @@ class LegacyCreator:
# Default data
self.data = collections.OrderedDict()
# TODO use 'AYON_INSTANCE_ID' when all hosts support it
self.data["id"] = AVALON_INSTANCE_ID
self.data["id"] = AYON_INSTANCE_ID
self.data["productType"] = self.product_type
self.data["folderPath"] = folder_path
self.data["productName"] = name

View file

@ -1,6 +1,7 @@
import copy
import collections
from uuid import uuid4
import typing
from typing import Optional, Dict, List, Any
from ayon_core.lib.attribute_definitions import (
@ -17,6 +18,9 @@ from ayon_core.pipeline import (
from .exceptions import ImmutableKeyError
from .changes import TrackChangesItem
if typing.TYPE_CHECKING:
from .creator_plugins import BaseCreator
class ConvertorItem:
"""Item representing convertor plugin.
@ -444,10 +448,11 @@ class CreatedInstance:
def __init__(
self,
product_type,
product_name,
data,
creator,
product_type: str,
product_name: str,
data: Dict[str, Any],
creator: "BaseCreator",
transient_data: Optional[Dict[str, Any]] = None,
):
self._creator = creator
creator_identifier = creator.identifier
@ -462,7 +467,9 @@ class CreatedInstance:
self._members = []
# Data that can be used for lifetime of object
self._transient_data = {}
if transient_data is None:
transient_data = {}
self._transient_data = transient_data
# Create a copy of passed data to avoid changing them on the fly
data = copy.deepcopy(data or {})
@ -492,7 +499,7 @@ class CreatedInstance:
item_id = data.get("id")
# TODO use only 'AYON_INSTANCE_ID' when all hosts support it
if item_id not in {AYON_INSTANCE_ID, AVALON_INSTANCE_ID}:
item_id = AVALON_INSTANCE_ID
item_id = AYON_INSTANCE_ID
self._data["id"] = item_id
self._data["productType"] = product_type
self._data["productName"] = product_name
@ -787,16 +794,26 @@ class CreatedInstance:
self._create_context.instance_create_attr_defs_changed(self.id)
@classmethod
def from_existing(cls, instance_data, creator):
def from_existing(
cls,
instance_data: Dict[str, Any],
creator: "BaseCreator",
transient_data: Optional[Dict[str, Any]] = None,
) -> "CreatedInstance":
"""Convert instance data from workfile to CreatedInstance.
Args:
instance_data (Dict[str, Any]): Data in a structure ready for
'CreatedInstance' object.
creator (BaseCreator): Creator plugin which is creating the
instance of for which the instance belong.
"""
instance of for which the instance belongs.
transient_data (Optional[dict[str, Any]]): Instance transient
data.
Returns:
CreatedInstance: Instance object.
"""
instance_data = copy.deepcopy(instance_data)
product_type = instance_data.get("productType")
@ -809,7 +826,11 @@ class CreatedInstance:
product_name = instance_data.get("subset")
return cls(
product_type, product_name, instance_data, creator
product_type,
product_name,
instance_data,
creator,
transient_data=transient_data,
)
def attribute_value_changed(self, key, changes):

View file

@ -37,7 +37,7 @@ class ValidateCurrentSaveFile(pyblish.api.ContextPlugin):
label = "Validate File Saved"
order = pyblish.api.ValidatorOrder - 0.1
hosts = ["fusion", "houdini", "max", "maya", "nuke", "substancepainter",
"cinema4d"]
"cinema4d", "silhouette"]
actions = [SaveByVersionUpAction, ShowWorkfilesAction]
def process(self, context):

View file

@ -1171,6 +1171,8 @@ ValidationArtistMessage QLabel {
#PublishLogMessage {
font-family: "Noto Sans Mono";
border: none;
padding: 0;
}
#PublishInstanceLogsLabel {

View file

@ -1117,6 +1117,57 @@ class LogIconFrame(QtWidgets.QFrame):
painter.end()
class LogItemMessage(QtWidgets.QTextEdit):
def __init__(self, msg, parent):
super().__init__(parent)
# Set as plain text to propagate new line characters
self.setPlainText(msg)
self.setObjectName("PublishLogMessage")
self.setReadOnly(True)
self.setFrameStyle(QtWidgets.QFrame.NoFrame)
self.setLineWidth(0)
self.setMidLineWidth(0)
pal = self.palette()
pal.setColor(QtGui.QPalette.Base, QtCore.Qt.transparent)
self.setPalette(pal)
self.setContentsMargins(0, 0, 0, 0)
viewport = self.viewport()
viewport.setContentsMargins(0, 0, 0, 0)
self.setTextInteractionFlags(
QtCore.Qt.TextBrowserInteraction)
self.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor))
self.setLineWrapMode(QtWidgets.QTextEdit.WidgetWidth)
self.setWordWrapMode(
QtGui.QTextOption.WrapMode.WrapAtWordBoundaryOrAnywhere
)
self.setSizePolicy(
QtWidgets.QSizePolicy.Preferred,
QtWidgets.QSizePolicy.Maximum
)
document = self.document()
document.documentLayout().documentSizeChanged.connect(
self._adjust_minimum_size
)
document.setDocumentMargin(0.0)
self._height = None
def _adjust_minimum_size(self, size):
self._height = size.height() + (2 * self.frameWidth())
self.updateGeometry()
def sizeHint(self):
size = super().sizeHint()
if self._height is not None:
size.setHeight(self._height)
return size
def minimumSizeHint(self):
return self.sizeHint()
class LogItemWidget(QtWidgets.QWidget):
log_level_to_flag = {
10: LOG_DEBUG_VISIBLE,
@ -1132,12 +1183,7 @@ class LogItemWidget(QtWidgets.QWidget):
type_flag, level_n = self._get_log_info(log)
icon_label = LogIconFrame(
self, log["type"], level_n, log.get("is_validation_error"))
message_label = QtWidgets.QLabel(log["msg"].rstrip(), self)
message_label.setObjectName("PublishLogMessage")
message_label.setTextInteractionFlags(
QtCore.Qt.TextBrowserInteraction)
message_label.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor))
message_label.setWordWrap(True)
message_label = LogItemMessage(log["msg"].rstrip(), self)
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
@ -1290,6 +1336,7 @@ class InstanceLogsWidget(QtWidgets.QWidget):
label_widget = QtWidgets.QLabel(instance.label, self)
label_widget.setObjectName("PublishInstanceLogsLabel")
label_widget.setWordWrap(True)
logs_grid = LogsWithIconsView(instance.logs, self)
layout = QtWidgets.QVBoxLayout(self)
@ -1329,9 +1376,11 @@ class InstancesLogsView(QtWidgets.QFrame):
content_wrap_widget = QtWidgets.QWidget(scroll_area)
content_wrap_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
content_wrap_widget.setMinimumWidth(80)
content_widget = QtWidgets.QWidget(content_wrap_widget)
content_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
content_layout = QtWidgets.QVBoxLayout(content_widget)
content_layout.setContentsMargins(8, 8, 8, 8)
content_layout.setSpacing(15)

View file

@ -28,7 +28,7 @@ def find_free_port(
exclude_ports (list, tuple, set): List of ports that won't be
checked form entered range.
host (str): Host where will check for free ports. Set to
"localhost" by default.
"127.0.0.1" by default.
"""
if port_from is None:
port_from = 8079
@ -42,7 +42,7 @@ def find_free_port(
# Default host is localhost but it is possible to look for other hosts
if host is None:
host = "localhost"
host = "127.0.0.1"
found_port = None
while True:
@ -78,7 +78,7 @@ class WebServerManager:
self._log = None
self.port = port or 8079
self.host = host or "localhost"
self.host = host or "127.0.0.1"
self.on_stop_callbacks = []

View file

@ -1,7 +1,6 @@
from ayon_core.resources import get_image_path
from ayon_core.tools.flickcharm import FlickCharm
from qtpy import QtWidgets, QtCore, QtGui, QtSvg
from qtpy import QtWidgets, QtCore, QtGui
class DeselectableTreeView(QtWidgets.QTreeView):
@ -19,48 +18,6 @@ class DeselectableTreeView(QtWidgets.QTreeView):
QtWidgets.QTreeView.mousePressEvent(self, event)
class TreeViewSpinner(QtWidgets.QTreeView):
size = 160
def __init__(self, parent=None):
super(TreeViewSpinner, self).__init__(parent=parent)
loading_image_path = get_image_path("spinner-200.svg")
self.spinner = QtSvg.QSvgRenderer(loading_image_path)
self.is_loading = False
self.is_empty = True
def paint_loading(self, event):
rect = event.rect()
rect = QtCore.QRectF(rect.topLeft(), rect.bottomRight())
rect.moveTo(
rect.x() + rect.width() / 2 - self.size / 2,
rect.y() + rect.height() / 2 - self.size / 2
)
rect.setSize(QtCore.QSizeF(self.size, self.size))
painter = QtGui.QPainter(self.viewport())
self.spinner.render(painter, rect)
def paint_empty(self, event):
painter = QtGui.QPainter(self.viewport())
rect = event.rect()
rect = QtCore.QRectF(rect.topLeft(), rect.bottomRight())
qtext_opt = QtGui.QTextOption(
QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter
)
painter.drawText(rect, "No Data", qtext_opt)
def paintEvent(self, event):
if self.is_loading:
self.paint_loading(event)
elif self.is_empty:
self.paint_empty(event)
else:
super(TreeViewSpinner, self).paintEvent(event)
class TreeView(QtWidgets.QTreeView):
"""Ultimate TreeView with flick charm and double click signals.

View file

@ -184,9 +184,10 @@ class WorkareaModel:
return items
for filename in os.listdir(workdir):
# We want to support both files and folders. e.g. Silhoutte uses
# folders as its project files. So we do not check whether it is
# a file or not.
filepath = os.path.join(workdir, filename)
if not os.path.isfile(filepath):
continue
ext = os.path.splitext(filename)[1].lower()
if ext not in self._extensions:

View file

@ -1033,7 +1033,8 @@ DEFAULT_PUBLISH_VALUES = {
"maya",
"nuke",
"photoshop",
"substancepainter"
"substancepainter",
"silhouette",
],
"enabled": True,
"optional": False,
@ -1053,7 +1054,8 @@ DEFAULT_PUBLISH_VALUES = {
"harmony",
"photoshop",
"aftereffects",
"fusion"
"fusion",
"silhouette",
],
"enabled": True,
"optional": True,