Merge branch 'develop' into enhancement/1416-loader-actions

This commit is contained in:
Jakub Trllo 2025-10-22 16:21:18 +02:00 committed by GitHub
commit e0f3a6f5d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 305 additions and 203 deletions

View file

@ -35,6 +35,10 @@ body:
label: Version
description: What version are you running? Look to AYON Tray
options:
- 1.6.5
- 1.6.4
- 1.6.3
- 1.6.2
- 1.6.1
- 1.6.0
- 1.5.3

View file

@ -2,7 +2,6 @@
"""Base class for AYON addons."""
from __future__ import annotations
import copy
import os
import sys
import time
@ -13,6 +12,7 @@ import collections
import warnings
from uuid import uuid4
from abc import ABC, abstractmethod
from urllib.parse import urlencode
from types import ModuleType
import typing
from typing import Optional, Any, Union
@ -136,39 +136,47 @@ def load_addons(force: bool = False) -> None:
time.sleep(0.1)
def _get_ayon_bundle_data() -> Optional[dict[str, Any]]:
def _get_ayon_bundle_data() -> tuple[
dict[str, Any], Optional[dict[str, Any]]
]:
studio_bundle_name = os.environ.get("AYON_STUDIO_BUNDLE_NAME")
project_bundle_name = os.getenv("AYON_BUNDLE_NAME")
bundles = ayon_api.get_bundles()["bundles"]
project_bundle = next(
studio_bundle = next(
(
bundle
for bundle in bundles
if bundle["name"] == project_bundle_name
if bundle["name"] == studio_bundle_name
),
None
)
studio_bundle = None
if studio_bundle_name and project_bundle_name != studio_bundle_name:
studio_bundle = next(
if studio_bundle is None:
raise RuntimeError(f"Failed to find bundle '{studio_bundle_name}'.")
project_bundle = None
if project_bundle_name and project_bundle_name != studio_bundle_name:
project_bundle = next(
(
bundle
for bundle in bundles
if bundle["name"] == studio_bundle_name
if bundle["name"] == project_bundle_name
),
None
)
if project_bundle and studio_bundle:
addons = copy.deepcopy(studio_bundle["addons"])
addons.update(project_bundle["addons"])
project_bundle["addons"] = addons
return project_bundle
if project_bundle is None:
raise RuntimeError(
f"Failed to find project bundle '{project_bundle_name}'."
)
return studio_bundle, project_bundle
def _get_ayon_addons_information(
bundle_info: dict[str, Any]
) -> list[dict[str, Any]]:
studio_bundle: dict[str, Any],
project_bundle: Optional[dict[str, Any]],
) -> dict[str, str]:
"""Receive information about addons to use from server.
Todos:
@ -181,22 +189,20 @@ def _get_ayon_addons_information(
list[dict[str, Any]]: List of addon information to use.
"""
output = []
bundle_addons = bundle_info["addons"]
addons = ayon_api.get_addons_info()["addons"]
for addon in addons:
name = addon["name"]
versions = addon.get("versions")
addon_version = bundle_addons.get(name)
if addon_version is None or not versions:
continue
version = versions.get(addon_version)
if version:
version = copy.deepcopy(version)
version["name"] = name
version["version"] = addon_version
output.append(version)
return output
key_values = {
"summary": "true",
"bundle_name": studio_bundle["name"],
}
if project_bundle:
key_values["project_bundle_name"] = project_bundle["name"]
query = urlencode(key_values)
response = ayon_api.get(f"settings?{query}")
return {
addon["name"]: addon["version"]
for addon in response.data["addons"]
}
def _load_ayon_addons(log: logging.Logger) -> list[ModuleType]:
@ -214,8 +220,8 @@ def _load_ayon_addons(log: logging.Logger) -> list[ModuleType]:
"""
all_addon_modules = []
bundle_info = _get_ayon_bundle_data()
addons_info = _get_ayon_addons_information(bundle_info)
studio_bundle, project_bundle = _get_ayon_bundle_data()
addons_info = _get_ayon_addons_information(studio_bundle, project_bundle)
if not addons_info:
return all_addon_modules
@ -227,17 +233,16 @@ def _load_ayon_addons(log: logging.Logger) -> list[ModuleType]:
dev_addons_info = {}
if dev_mode_enabled:
# Get dev addons info only when dev mode is enabled
dev_addons_info = bundle_info.get("addonDevelopment", dev_addons_info)
dev_addons_info = studio_bundle.get(
"addonDevelopment", dev_addons_info
)
addons_dir_exists = os.path.exists(addons_dir)
if not addons_dir_exists:
log.warning(
f"Addons directory does not exists. Path \"{addons_dir}\"")
for addon_info in addons_info:
addon_name = addon_info["name"]
addon_version = addon_info["version"]
for addon_name, addon_version in addons_info.items():
# core addon does not have any addon object
if addon_name == "core":
continue

View file

@ -1,11 +1,9 @@
# -*- coding: utf-8 -*-
"""AYON plugin tools."""
import os
import logging
import re
import collections
log = logging.getLogger(__name__)
CAPITALIZE_REGEX = re.compile(r"[a-zA-Z0-9]")

View file

@ -420,11 +420,14 @@ def get_review_info_by_layer_name(channel_names):
channel = last_part[0].upper()
rgba_by_layer_name[layer_name][channel] = channel_name
# Put empty layer to the beginning of the list
# Put empty layer or 'rgba' to the beginning of the list
# - if input has R, G, B, A channels they should be used for review
if "" in layer_names_order:
layer_names_order.remove("")
layer_names_order.insert(0, "")
# NOTE They are iterated in reversed order because they're inserted to
# the beginning of 'layer_names_order' -> last added will be first.
for name in reversed(["", "rgba"]):
if name in layer_names_order:
layer_names_order.remove(name)
layer_names_order.insert(0, name)
output = []
for layer_name in layer_names_order:

View file

@ -249,7 +249,8 @@ def create_skeleton_instance(
# map inputVersions `ObjectId` -> `str` so json supports it
"inputVersions": list(map(str, data.get("inputVersions", []))),
"colorspace": data.get("colorspace"),
"hasExplicitFrames": data.get("hasExplicitFrames")
"hasExplicitFrames": data.get("hasExplicitFrames", False),
"reuseLastVersion": data.get("reuseLastVersion", False),
}
if data.get("renderlayer"):

View file

@ -7,13 +7,20 @@ import copy
import warnings
import hashlib
import xml.etree.ElementTree
from typing import TYPE_CHECKING, Optional, Union, List
from typing import TYPE_CHECKING, Optional, Union, List, Any
import clique
import speedcopy
import logging
import ayon_api
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
)
from ayon_core.lib import (
import_filepath,
Logger,
@ -34,6 +41,8 @@ if TYPE_CHECKING:
TRAIT_INSTANCE_KEY: str = "representations_with_traits"
log = logging.getLogger(__name__)
def get_template_name_profiles(
project_name, project_settings=None, logger=None
@ -1030,7 +1039,7 @@ def main_cli_publish(
# NOTE: ayon-python-api does not have public api function to find
# out if is used service user. So we need to have try > except
# block.
con = ayon_api.get_server_api_connection()
con = get_server_api_connection()
try:
con.set_default_service_username(username)
except ValueError:
@ -1143,3 +1152,90 @@ def get_trait_representations(
"""
return instance.data.get(TRAIT_INSTANCE_KEY, [])
def fill_sequence_gaps_with_previous_version(
collection: str,
staging_dir: str,
instance: pyblish.plugin.Instance,
current_repre_name: str,
start_frame: int,
end_frame: int
) -> tuple[Optional[dict[str, Any]], Optional[dict[int, str]]]:
"""Tries to replace missing frames from ones from last version"""
used_version_entity, repre_file_paths = _get_last_version_files(
instance, current_repre_name
)
if repre_file_paths is None:
# issues in getting last version files
return (None, None)
prev_collection = clique.assemble(
repre_file_paths,
patterns=[clique.PATTERNS["frames"]],
minimum_items=1
)[0][0]
prev_col_format = prev_collection.format("{head}{padding}{tail}")
added_files = {}
anatomy = instance.context.data["anatomy"]
col_format = collection.format("{head}{padding}{tail}")
for frame in range(start_frame, end_frame + 1):
if frame in collection.indexes:
continue
hole_fpath = os.path.join(staging_dir, col_format % frame)
previous_version_path = prev_col_format % frame
previous_version_path = anatomy.fill_root(previous_version_path)
if not os.path.exists(previous_version_path):
log.warning(
"Missing frame should be replaced from "
f"'{previous_version_path}' but that doesn't exist. "
)
return (None, None)
log.warning(
f"Replacing missing '{hole_fpath}' with "
f"'{previous_version_path}'"
)
speedcopy.copyfile(previous_version_path, hole_fpath)
added_files[frame] = hole_fpath
return (used_version_entity, added_files)
def _get_last_version_files(
instance: pyblish.plugin.Instance,
current_repre_name: str,
) -> tuple[Optional[dict[str, Any]], Optional[list[str]]]:
product_name = instance.data["productName"]
project_name = instance.data["projectEntity"]["name"]
folder_entity = instance.data["folderEntity"]
version_entity = get_last_version_by_product_name(
project_name,
product_name,
folder_entity["id"],
fields={"id", "attrib"}
)
if not version_entity:
return None, None
matching_repres = get_representations(
project_name,
version_ids=[version_entity["id"]],
representation_names=[current_repre_name],
fields={"files"}
)
matching_repre = next(matching_repres, None)
if not matching_repre:
return None, None
repre_file_paths = [
file_info["path"]
for file_info in matching_repre["files"]
]
return (version_entity, repre_file_paths)

View file

@ -32,6 +32,7 @@ class CollectCoreJobEnvVars(pyblish.api.ContextPlugin):
for key in [
"AYON_BUNDLE_NAME",
"AYON_STUDIO_BUNDLE_NAME",
"AYON_USE_STAGING",
"AYON_IN_TESTS",
# NOTE Not sure why workdir is needed?

View file

@ -13,6 +13,8 @@ import copy
import pyblish.api
from ayon_core.pipeline.publish import get_publish_template_name
class CollectResourcesPath(pyblish.api.InstancePlugin):
"""Generate directory path where the files and resources will be stored.
@ -77,16 +79,29 @@ class CollectResourcesPath(pyblish.api.InstancePlugin):
# This is for cases of Deprecated anatomy without `folder`
# TODO remove when all clients have solved this issue
template_data.update({
"frame": "FRAME_TEMP",
"representation": "TEMP"
})
template_data.update({"frame": "FRAME_TEMP", "representation": "TEMP"})
publish_templates = anatomy.get_template_item(
"publish", "default", "directory"
task_name = task_type = None
task_entity = instance.data.get("taskEntity")
if task_entity:
task_name = task_entity["name"]
task_type = task_entity["taskType"]
template_name = get_publish_template_name(
project_name=instance.context.data["projectName"],
host_name=instance.context.data["hostName"],
product_type=instance.data["productType"],
task_name=task_name,
task_type=task_type,
project_settings=instance.context.data["project_settings"],
logger=self.log,
)
publish_template = anatomy.get_template_item(
"publish", template_name, "directory")
publish_folder = os.path.normpath(
publish_templates.format_strict(template_data)
publish_template.format_strict(template_data)
)
resources_folder = os.path.join(publish_folder, "resources")

View file

@ -13,14 +13,15 @@ import clique
import speedcopy
import pyblish.api
from ayon_api import get_last_version_by_product_name, get_representations
from ayon_core.lib import (
get_ffmpeg_tool_args,
filter_profiles,
path_to_subprocess_arg,
run_subprocess,
)
from ayon_core.pipeline.publish.lib import (
fill_sequence_gaps_with_previous_version
)
from ayon_core.lib.transcoding import (
IMAGE_EXTENSIONS,
get_ffprobe_streams,
@ -130,7 +131,7 @@ def frame_to_timecode(frame: int, fps: float) -> str:
class ExtractReview(pyblish.api.InstancePlugin):
"""Extracting Review mov file for Ftrack
"""Extracting Reviewable medias
Compulsory attribute of representation is tags list with "review",
otherwise the representation is ignored.
@ -508,10 +509,10 @@ class ExtractReview(pyblish.api.InstancePlugin):
resolution_width=temp_data.resolution_width,
resolution_height=temp_data.resolution_height,
extension=temp_data.input_ext,
temp_data=temp_data
temp_data=temp_data,
)
elif fill_missing_frames == "previous_version":
new_frame_files = self.fill_sequence_gaps_with_previous(
fill_output = fill_sequence_gaps_with_previous_version(
collection=collection,
staging_dir=new_repre["stagingDir"],
instance=instance,
@ -519,8 +520,13 @@ class ExtractReview(pyblish.api.InstancePlugin):
start_frame=temp_data.frame_start,
end_frame=temp_data.frame_end,
)
_, new_frame_files = fill_output
# fallback to original workflow
if new_frame_files is None:
self.log.warning(
"Falling back to filling from currently "
"last rendered."
)
new_frame_files = (
self.fill_sequence_gaps_from_existing(
collection=collection,
@ -612,8 +618,6 @@ class ExtractReview(pyblish.api.InstancePlugin):
"name": "{}_{}".format(output_name, output_ext),
"outputName": output_name,
"outputDef": output_def,
"frameStartFtrack": temp_data.output_frame_start,
"frameEndFtrack": temp_data.output_frame_end,
"ffmpeg_cmd": subprcs_cmd
})
@ -1050,92 +1054,6 @@ class ExtractReview(pyblish.api.InstancePlugin):
return all_args
def fill_sequence_gaps_with_previous(
self,
collection: str,
staging_dir: str,
instance: pyblish.plugin.Instance,
current_repre_name: str,
start_frame: int,
end_frame: int
) -> Optional[dict[int, str]]:
"""Tries to replace missing frames from ones from last version"""
repre_file_paths = self._get_last_version_files(
instance, current_repre_name)
if repre_file_paths is None:
# issues in getting last version files, falling back
return None
prev_collection = clique.assemble(
repre_file_paths,
patterns=[clique.PATTERNS["frames"]],
minimum_items=1
)[0][0]
prev_col_format = prev_collection.format("{head}{padding}{tail}")
added_files = {}
anatomy = instance.context.data["anatomy"]
col_format = collection.format("{head}{padding}{tail}")
for frame in range(start_frame, end_frame + 1):
if frame in collection.indexes:
continue
hole_fpath = os.path.join(staging_dir, col_format % frame)
previous_version_path = prev_col_format % frame
previous_version_path = anatomy.fill_root(previous_version_path)
if not os.path.exists(previous_version_path):
self.log.warning(
"Missing frame should be replaced from "
f"'{previous_version_path}' but that doesn't exist. "
"Falling back to filling from currently last rendered."
)
return None
self.log.warning(
f"Replacing missing '{hole_fpath}' with "
f"'{previous_version_path}'"
)
speedcopy.copyfile(previous_version_path, hole_fpath)
added_files[frame] = hole_fpath
return added_files
def _get_last_version_files(
self,
instance: pyblish.plugin.Instance,
current_repre_name: str,
):
product_name = instance.data["productName"]
project_name = instance.data["projectEntity"]["name"]
folder_entity = instance.data["folderEntity"]
version_entity = get_last_version_by_product_name(
project_name,
product_name,
folder_entity["id"],
fields={"id"}
)
if not version_entity:
return None
matching_repres = get_representations(
project_name,
version_ids=[version_entity["id"]],
representation_names=[current_repre_name],
fields={"files"}
)
if not matching_repres:
return None
matching_repre = list(matching_repres)[0]
repre_file_paths = [
file_info["path"]
for file_info in matching_repre["files"]
]
return repre_file_paths
def fill_sequence_gaps_with_blanks(
self,
collection: str,
@ -1384,15 +1302,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
return audio_in_args, audio_filters, audio_out_args
for audio in audio_inputs:
# NOTE modified, always was expected "frameStartFtrack" which is
# STRANGE?!!! There should be different key, right?
# TODO use different frame start!
offset_seconds = 0
frame_start_ftrack = instance.data.get("frameStartFtrack")
if frame_start_ftrack is not None:
offset_frames = frame_start_ftrack - audio["offset"]
offset_seconds = offset_frames / temp_data.fps
if offset_seconds > 0:
audio_in_args.append(
"-ss {}".format(offset_seconds)

View file

@ -6,6 +6,7 @@ import re
import pyblish.api
from ayon_core.lib import (
get_oiio_tool_args,
get_ffmpeg_tool_args,
get_ffprobe_data,
@ -15,7 +16,11 @@ from ayon_core.lib import (
path_to_subprocess_arg,
run_subprocess,
)
from ayon_core.lib.transcoding import oiio_color_convert
from ayon_core.lib.transcoding import (
oiio_color_convert,
get_oiio_input_and_channel_args,
get_oiio_info_for_input,
)
from ayon_core.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS
@ -210,6 +215,12 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
full_output_path = os.path.join(dst_staging, jpeg_file)
colorspace_data = repre.get("colorspaceData")
# NOTE We should find out what is happening here. Why don't we
# use oiiotool all the time if it is available? Only possible
# reason might be that video files should be converted using
# ffmpeg, but other then that, we should use oiio all the time.
# - We should also probably get rid of the ffmpeg settings...
# only use OIIO if it is supported and representation has
# colorspace data
if oiio_supported and colorspace_data:
@ -219,7 +230,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
)
# If the input can read by OIIO then use OIIO method for
# conversion otherwise use ffmpeg
repre_thumb_created = self._create_thumbnail_oiio(
repre_thumb_created = self._create_colorspace_thumbnail(
full_input_path,
full_output_path,
colorspace_data
@ -229,17 +240,16 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
# oiiotool isn't available or representation is not having
# colorspace data
if not repre_thumb_created:
if oiio_supported:
self.log.debug(
"Converting with FFMPEG because input"
" can't be read by OIIO."
)
repre_thumb_created = self._create_thumbnail_ffmpeg(
full_input_path, full_output_path
)
# Skip representation and try next one if wasn't created
# Skip representation and try next one if wasn't created
if not repre_thumb_created and oiio_supported:
repre_thumb_created = self._create_thumbnail_oiio(
full_input_path, full_output_path
)
if not repre_thumb_created:
continue
@ -382,7 +392,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
return ext in IMAGE_EXTENSIONS or ext in VIDEO_EXTENSIONS
def _create_thumbnail_oiio(
def _create_colorspace_thumbnail(
self,
src_path,
dst_path,
@ -455,9 +465,50 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
return True
def _create_thumbnail_oiio(self, src_path, dst_path):
self.log.debug(f"Extracting thumbnail with OIIO: {dst_path}")
try:
resolution_arg = self._get_resolution_arg("oiiotool", src_path)
except RuntimeError:
self.log.warning(
"Failed to create thumbnail using oiio", exc_info=True
)
return False
input_info = get_oiio_info_for_input(src_path, logger=self.log)
input_arg, channels_arg = get_oiio_input_and_channel_args(input_info)
oiio_cmd = get_oiio_tool_args(
"oiiotool",
input_arg, src_path,
# Tell oiiotool which channels should be put to top stack
# (and output)
"--ch", channels_arg,
# Use first subimage
"--subimage", "0"
)
oiio_cmd.extend(resolution_arg)
oiio_cmd.extend(("-o", dst_path))
self.log.debug("Running: {}".format(" ".join(oiio_cmd)))
try:
run_subprocess(oiio_cmd, logger=self.log)
return True
except Exception:
self.log.warning(
"Failed to create thumbnail using oiiotool",
exc_info=True
)
return False
def _create_thumbnail_ffmpeg(self, src_path, dst_path):
self.log.debug("Extracting thumbnail with FFMPEG: {}".format(dst_path))
resolution_arg = self._get_resolution_arg("ffmpeg", src_path)
try:
resolution_arg = self._get_resolution_arg("ffmpeg", src_path)
except RuntimeError:
self.log.warning(
"Failed to create thumbnail using ffmpeg", exc_info=True
)
return False
ffmpeg_path_args = get_ffmpeg_tool_args("ffmpeg")
ffmpeg_args = self.ffmpeg_args or {}

View file

@ -6,7 +6,12 @@ import json
import tempfile
from string import Formatter
import opentimelineio_contrib.adapters.ffmpeg_burnins as ffmpeg_burnins
try:
from otio_burnins_adapter import ffmpeg_burnins
except ImportError:
import opentimelineio_contrib.adapters.ffmpeg_burnins as ffmpeg_burnins
from PIL import ImageFont
from ayon_core.lib import (
get_ffmpeg_tool_args,
get_ffmpeg_codec_args,
@ -36,6 +41,39 @@ TIMECODE_KEY = "{timecode}"
SOURCE_TIMECODE_KEY = "{source_timecode}"
def _drawtext(align, resolution, text, options):
"""
:rtype: {'x': int, 'y': int}
"""
x_pos = "0"
if align in (ffmpeg_burnins.TOP_CENTERED, ffmpeg_burnins.BOTTOM_CENTERED):
x_pos = "w/2-tw/2"
elif align in (ffmpeg_burnins.TOP_RIGHT, ffmpeg_burnins.BOTTOM_RIGHT):
ifont = ImageFont.truetype(options["font"], options["font_size"])
if hasattr(ifont, "getbbox"):
left, top, right, bottom = ifont.getbbox(text)
box_size = right - left, bottom - top
else:
box_size = ifont.getsize(text)
x_pos = resolution[0] - (box_size[0] + options["x_offset"])
elif align in (ffmpeg_burnins.TOP_LEFT, ffmpeg_burnins.BOTTOM_LEFT):
x_pos = options["x_offset"]
if align in (
ffmpeg_burnins.TOP_CENTERED,
ffmpeg_burnins.TOP_RIGHT,
ffmpeg_burnins.TOP_LEFT
):
y_pos = "%d" % options["y_offset"]
else:
y_pos = "h-text_h-%d" % (options["y_offset"])
return {"x": x_pos, "y": y_pos}
ffmpeg_burnins._drawtext = _drawtext
def _get_ffprobe_data(source):
"""Reimplemented from otio burnins to be able use full path to ffprobe
:param str source: source media file

View file

@ -41,7 +41,7 @@ class ScrollMessageBox(QtWidgets.QDialog):
"""
def __init__(self, icon, title, messages, cancelable=False):
super(ScrollMessageBox, self).__init__()
super().__init__()
self.setWindowTitle(title)
self.icon = icon
@ -49,8 +49,6 @@ class ScrollMessageBox(QtWidgets.QDialog):
self.setWindowFlags(QtCore.Qt.WindowTitleHint)
layout = QtWidgets.QVBoxLayout(self)
scroll_widget = QtWidgets.QScrollArea(self)
scroll_widget.setWidgetResizable(True)
content_widget = QtWidgets.QWidget(self)
@ -63,14 +61,8 @@ class ScrollMessageBox(QtWidgets.QDialog):
content_layout.addWidget(label_widget)
message_len = max(message_len, len(message))
# guess size of scrollable area
# WARNING: 'desktop' method probably won't work in PySide6
desktop = QtWidgets.QApplication.desktop()
max_width = desktop.availableGeometry().width()
scroll_widget.setMinimumWidth(
min(max_width, message_len * 6)
)
layout.addWidget(scroll_widget)
# Set minimum width
scroll_widget.setMinimumWidth(360)
buttons = QtWidgets.QDialogButtonBox.Ok
if cancelable:
@ -86,7 +78,9 @@ class ScrollMessageBox(QtWidgets.QDialog):
btn.clicked.connect(self._on_copy_click)
btn_box.addButton(btn, QtWidgets.QDialogButtonBox.NoRole)
layout.addWidget(btn_box)
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.addWidget(scroll_widget, 1)
main_layout.addWidget(btn_box, 0)
def _on_copy_click(self):
clipboard = QtWidgets.QApplication.clipboard()
@ -104,7 +98,7 @@ class SimplePopup(QtWidgets.QDialog):
on_clicked = QtCore.Signal()
def __init__(self, parent=None, *args, **kwargs):
super(SimplePopup, self).__init__(parent=parent, *args, **kwargs)
super().__init__(parent=parent, *args, **kwargs)
# Set default title
self.setWindowTitle("Popup")
@ -161,7 +155,7 @@ class SimplePopup(QtWidgets.QDialog):
geo = self._calculate_window_geometry()
self.setGeometry(geo)
return super(SimplePopup, self).showEvent(event)
return super().showEvent(event)
def _on_clicked(self):
"""Callback for when the 'show' button is clicked.
@ -228,9 +222,7 @@ class PopupUpdateKeys(SimplePopup):
on_clicked_state = QtCore.Signal(bool)
def __init__(self, parent=None, *args, **kwargs):
super(PopupUpdateKeys, self).__init__(
parent=parent, *args, **kwargs
)
super().__init__(parent=parent, *args, **kwargs)
layout = self.layout()

View file

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring AYON addon 'core' version."""
__version__ = "1.6.1+dev"
__version__ = "1.6.5+dev"

View file

@ -15,8 +15,7 @@ qtawesome = "0.7.3"
[ayon.runtimeDependencies]
aiohttp-middlewares = "^2.0.0"
Click = "^8"
OpenTimelineIO = "0.17.0"
otio-burnins-adapter = "1.0.0"
OpenTimelineIO = "0.16.0"
opencolorio = "^2.3.2,<2.4.0"
Pillow = "9.5.0"
websocket-client = ">=0.40.0,<2"

View file

@ -1,6 +1,6 @@
name = "core"
title = "Core"
version = "1.6.1+dev"
version = "1.6.5+dev"
client_dir = "ayon_core"

View file

@ -5,7 +5,7 @@
[tool.poetry]
name = "ayon-core"
version = "1.6.1+dev"
version = "1.6.5+dev"
description = ""
authors = ["Ynput Team <team@ynput.io>"]
readme = "README.md"
@ -27,17 +27,6 @@ codespell = "^2.2.6"
semver = "^3.0.2"
mypy = "^1.14.0"
mock = "^5.0.0"
tomlkit = "^0.13.2"
requests = "^2.32.3"
mkdocs-material = "^9.6.7"
mkdocs-autoapi = "^0.4.0"
mkdocstrings-python = "^1.16.2"
mkdocs-minify-plugin = "^0.8.0"
markdown-checklist = "^0.4.4"
mdx-gh-links = "^0.4"
pymdown-extensions = "^10.14.3"
mike = "^2.1.3"
mkdocstrings-shell = "^1.0.2"
nxtools = "^1.6"
[tool.poetry.group.test.dependencies]

View file

@ -454,7 +454,7 @@ DEFAULT_TOOLS_VALUES = {
"hosts": [],
"task_types": [],
"tasks": [],
"template": "{product[type]}{Task[name]}{Variant}"
"template": "{product[type]}{Task[name]}{Variant}<_{Aov}>"
},
{
"product_types": [