From edc451c916c4776dcea216e6362d56b1e1bde330 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 15 Apr 2025 18:24:19 +0200 Subject: [PATCH 01/28] add optional `aov` key to render template profile name --- server/settings/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/settings/tools.py b/server/settings/tools.py index 32c72e7a98..ed23cee69c 100644 --- a/server/settings/tools.py +++ b/server/settings/tools.py @@ -414,7 +414,7 @@ DEFAULT_TOOLS_VALUES = { "hosts": [], "task_types": [], "tasks": [], - "template": "{product[type]}{Task[name]}{Variant}" + "template": "{product[type]}{Task[name]}{Variant}<_{Aov}>" }, { "product_types": [ From b7822123289e789776c91b19a0a69b4fa3757a5c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 Oct 2025 16:04:20 +0200 Subject: [PATCH 02/28] Extracted gap filling from last version --- client/ayon_core/lib/plugin_tools.py | 97 +++++++++++++++++++ .../plugins/publish/extract_review.py | 91 +---------------- 2 files changed, 99 insertions(+), 89 deletions(-) diff --git a/client/ayon_core/lib/plugin_tools.py b/client/ayon_core/lib/plugin_tools.py index 654bc7ac4a..1bc5824b86 100644 --- a/client/ayon_core/lib/plugin_tools.py +++ b/client/ayon_core/lib/plugin_tools.py @@ -4,6 +4,15 @@ import os import logging import re import collections +from typing import Optional, Any, Tuple +import clique +import speedcopy + +import ayon_api +import pyblish.api + +from ayon_api import get_last_version_by_product_name, get_representations + log = logging.getLogger(__name__) @@ -151,3 +160,91 @@ def source_hash(filepath, *args): time = str(os.path.getmtime(filepath)) size = str(os.path.getsize(filepath)) return "|".join([file_name, time, size] + list(args)).replace(".", ",") + + +def fill_sequence_gaps_with_previous( + collection: str, + staging_dir: str, + instance: pyblish.plugin.Instance, + current_repre_name: str, + start_frame: int, + end_frame: int +) -> Tuple[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. " + "Falling back to filling from currently last rendered." + ) + 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, +): + 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, 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 (version_entity, repre_file_paths) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 04e534054e..535f8ab6cf 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -13,14 +13,13 @@ 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.lib.plugin_tools import fill_sequence_gaps_with_previous from ayon_core.lib.transcoding import ( IMAGE_EXTENSIONS, get_ffprobe_streams, @@ -511,7 +510,7 @@ class ExtractReview(pyblish.api.InstancePlugin): temp_data=temp_data ) elif fill_missing_frames == "previous_version": - new_frame_files = self.fill_sequence_gaps_with_previous( + _, new_frame_files = fill_sequence_gaps_with_previous( collection=collection, staging_dir=new_repre["stagingDir"], instance=instance, @@ -1050,92 +1049,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, From a7209e68f0b0142d138f18e88656470354f8ad0b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 Oct 2025 16:05:04 +0200 Subject: [PATCH 03/28] Push through reuseLastVersion value to deadline metadata --- client/ayon_core/pipeline/farm/pyblish_functions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index 0d8e70f9d2..4e9f9ed601 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -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") or False, + "reuseLastVersion": data.get("reuseLastVersion") or False } if data.get("renderlayer"): From 3a0ba21117d53fc8120f29a6c5a90f7da3df6e9c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 Oct 2025 16:17:31 +0200 Subject: [PATCH 04/28] Return attrib for frameStart query --- client/ayon_core/lib/plugin_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/lib/plugin_tools.py b/client/ayon_core/lib/plugin_tools.py index 1bc5824b86..1ee8f673e0 100644 --- a/client/ayon_core/lib/plugin_tools.py +++ b/client/ayon_core/lib/plugin_tools.py @@ -225,7 +225,7 @@ def _get_last_version_files( project_name, product_name, folder_entity["id"], - fields={"id"} + fields={"id", "attrib"} ) if not version_entity: From 98d8417c737ba5faaecab2dd5adfb78c4300af35 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 Oct 2025 16:36:44 +0200 Subject: [PATCH 05/28] Removed confusing message --- client/ayon_core/lib/plugin_tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/lib/plugin_tools.py b/client/ayon_core/lib/plugin_tools.py index 1ee8f673e0..ec7668bdbf 100644 --- a/client/ayon_core/lib/plugin_tools.py +++ b/client/ayon_core/lib/plugin_tools.py @@ -199,7 +199,6 @@ def fill_sequence_gaps_with_previous( 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, None) From 29baac0d306969973226f7e20a20682a6e86b8b4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 Oct 2025 16:38:43 +0200 Subject: [PATCH 06/28] Moved message where it makes sense --- client/ayon_core/plugins/publish/extract_review.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 535f8ab6cf..fac68a511a 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -520,6 +520,10 @@ class ExtractReview(pyblish.api.InstancePlugin): ) # 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, From c046588ea8e6a1caf9d610ae6569bda3e3cd4e6d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 Oct 2025 16:40:38 +0200 Subject: [PATCH 07/28] Ruff --- client/ayon_core/lib/plugin_tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/lib/plugin_tools.py b/client/ayon_core/lib/plugin_tools.py index ec7668bdbf..117083a9c9 100644 --- a/client/ayon_core/lib/plugin_tools.py +++ b/client/ayon_core/lib/plugin_tools.py @@ -8,7 +8,6 @@ from typing import Optional, Any, Tuple import clique import speedcopy -import ayon_api import pyblish.api from ayon_api import get_last_version_by_product_name, get_representations From ef0af72632b74170f1bf7b381dac093b234f4f9b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 13 Oct 2025 13:33:12 +0200 Subject: [PATCH 08/28] Updated typin Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/lib/plugin_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/lib/plugin_tools.py b/client/ayon_core/lib/plugin_tools.py index 117083a9c9..122d61c726 100644 --- a/client/ayon_core/lib/plugin_tools.py +++ b/client/ayon_core/lib/plugin_tools.py @@ -168,7 +168,7 @@ def fill_sequence_gaps_with_previous( current_repre_name: str, start_frame: int, end_frame: int -) -> Tuple[dict[str, Any], Optional[dict[int, str]]]: +) -> 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 From 951ea6286b1babfef3ab46e28fa88d8a574e1273 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 13 Oct 2025 13:33:32 +0200 Subject: [PATCH 09/28] Removed unneeded import Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/lib/plugin_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/lib/plugin_tools.py b/client/ayon_core/lib/plugin_tools.py index 122d61c726..3931e98777 100644 --- a/client/ayon_core/lib/plugin_tools.py +++ b/client/ayon_core/lib/plugin_tools.py @@ -4,7 +4,7 @@ import os import logging import re import collections -from typing import Optional, Any, Tuple +from typing import Optional, Any import clique import speedcopy From afe863e36b0d86b60676b226badc7e23e88cac73 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 13 Oct 2025 13:34:07 +0200 Subject: [PATCH 10/28] Removed unnecessary parentheses Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/lib/plugin_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/lib/plugin_tools.py b/client/ayon_core/lib/plugin_tools.py index 3931e98777..eff66fecfe 100644 --- a/client/ayon_core/lib/plugin_tools.py +++ b/client/ayon_core/lib/plugin_tools.py @@ -227,7 +227,7 @@ def _get_last_version_files( ) if not version_entity: - return (None, None) + return None, None matching_repres = get_representations( project_name, From 9a0f4b72e3e7fe2220202c4236fa848314bd89cb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 13 Oct 2025 13:34:53 +0200 Subject: [PATCH 11/28] Formatting change Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/farm/pyblish_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index 4e9f9ed601..a5053844b9 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -249,8 +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") or False, - "reuseLastVersion": data.get("reuseLastVersion") or False + "hasExplicitFrames": data.get("hasExplicitFrames", False), + "reuseLastVersion": data.get("reuseLastVersion", False), } if data.get("renderlayer"): From c3de53ae5e85a367487dc7a9d0cda3ab699589e7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 13 Oct 2025 13:35:09 +0200 Subject: [PATCH 12/28] Removed import Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/lib/plugin_tools.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/ayon_core/lib/plugin_tools.py b/client/ayon_core/lib/plugin_tools.py index eff66fecfe..3b39567207 100644 --- a/client/ayon_core/lib/plugin_tools.py +++ b/client/ayon_core/lib/plugin_tools.py @@ -8,8 +8,6 @@ from typing import Optional, Any import clique import speedcopy -import pyblish.api - from ayon_api import get_last_version_by_product_name, get_representations From 1f88b90cbabbcaaaa53392a9c1ad40a3e9ceddd0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 13 Oct 2025 13:35:28 +0200 Subject: [PATCH 13/28] Added return type Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/lib/plugin_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/lib/plugin_tools.py b/client/ayon_core/lib/plugin_tools.py index 3b39567207..dc371a9ed1 100644 --- a/client/ayon_core/lib/plugin_tools.py +++ b/client/ayon_core/lib/plugin_tools.py @@ -212,7 +212,7 @@ def fill_sequence_gaps_with_previous( 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"] From 1da4abc9bd87aefed1fadd2cb5a219c9c86a56fc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 13 Oct 2025 13:35:46 +0200 Subject: [PATCH 14/28] Fixed return Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/lib/plugin_tools.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/lib/plugin_tools.py b/client/ayon_core/lib/plugin_tools.py index dc371a9ed1..676d3e43b5 100644 --- a/client/ayon_core/lib/plugin_tools.py +++ b/client/ayon_core/lib/plugin_tools.py @@ -234,9 +234,10 @@ def _get_last_version_files( fields={"files"} ) - if not matching_repres: - return None - matching_repre = list(matching_repres)[0] + matching_repre = next(matching_repres, None) + if not matching_repre: + return None, None + repre_file_paths = [ file_info["path"] From bd4381fe9b22ede0b70bbb4b0343c9e19caa3d73 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 13 Oct 2025 13:45:24 +0200 Subject: [PATCH 15/28] Moved fill_sequence_gaps_with_previous --- client/ayon_core/lib/plugin_tools.py | 96 ------------------------ client/ayon_core/pipeline/publish/lib.py | 95 ++++++++++++++++++++++- 2 files changed, 94 insertions(+), 97 deletions(-) diff --git a/client/ayon_core/lib/plugin_tools.py b/client/ayon_core/lib/plugin_tools.py index 676d3e43b5..b19fe1e200 100644 --- a/client/ayon_core/lib/plugin_tools.py +++ b/client/ayon_core/lib/plugin_tools.py @@ -1,17 +1,9 @@ # -*- coding: utf-8 -*- """AYON plugin tools.""" import os -import logging import re import collections -from typing import Optional, Any -import clique -import speedcopy -from ayon_api import get_last_version_by_product_name, get_representations - - -log = logging.getLogger(__name__) CAPITALIZE_REGEX = re.compile(r"[a-zA-Z0-9]") @@ -157,91 +149,3 @@ def source_hash(filepath, *args): time = str(os.path.getmtime(filepath)) size = str(os.path.getsize(filepath)) return "|".join([file_name, time, size] + list(args)).replace(".", ",") - - -def fill_sequence_gaps_with_previous( - 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) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 3b82d961f8..4d555ae48b 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -7,7 +7,10 @@ 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 @@ -27,6 +30,7 @@ from .constants import ( DEFAULT_PUBLISH_TEMPLATE, DEFAULT_HERO_PUBLISH_TEMPLATE, ) +from ayon_api import get_last_version_by_product_name, get_representations if TYPE_CHECKING: from ayon_core.pipeline.traits import Representation @@ -34,6 +38,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 @@ -1143,3 +1149,90 @@ def get_trait_representations( """ return instance.data.get(TRAIT_INSTANCE_KEY, []) + + +def fill_sequence_gaps_with_previous( + 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) From 949cfc75a61b5a1c14eeec40d9153e56c7da4331 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 13 Oct 2025 13:47:14 +0200 Subject: [PATCH 16/28] Renamed fill_sequence_gaps_with_previous --- client/ayon_core/pipeline/publish/lib.py | 2 +- client/ayon_core/plugins/publish/extract_review.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 4d555ae48b..657a57226d 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -1151,7 +1151,7 @@ def get_trait_representations( return instance.data.get(TRAIT_INSTANCE_KEY, []) -def fill_sequence_gaps_with_previous( +def fill_sequence_gaps_with_previous_version( collection: str, staging_dir: str, instance: pyblish.plugin.Instance, diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index fac68a511a..fe0e7d4e3b 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -19,7 +19,7 @@ from ayon_core.lib import ( path_to_subprocess_arg, run_subprocess, ) -from ayon_core.lib.plugin_tools import fill_sequence_gaps_with_previous +from ayon_core.pipeline.publish.lib import fill_sequence_gaps_with_previous_version from ayon_core.lib.transcoding import ( IMAGE_EXTENSIONS, get_ffprobe_streams, @@ -510,7 +510,7 @@ class ExtractReview(pyblish.api.InstancePlugin): temp_data=temp_data ) elif fill_missing_frames == "previous_version": - _, new_frame_files = fill_sequence_gaps_with_previous( + _, new_frame_files = fill_sequence_gaps_with_previous_version( collection=collection, staging_dir=new_repre["stagingDir"], instance=instance, From f3300a67e42dac2698ca2a153f1e2cd220f88a85 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 13 Oct 2025 15:51:35 +0200 Subject: [PATCH 17/28] Formatting change Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/plugins/publish/extract_review.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index fe0e7d4e3b..89926d2235 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -19,7 +19,9 @@ from ayon_core.lib import ( path_to_subprocess_arg, run_subprocess, ) -from ayon_core.pipeline.publish.lib import fill_sequence_gaps_with_previous_version +from ayon_core.pipeline.publish.lib import ( + fill_sequence_gaps_with_previous_version, +) from ayon_core.lib.transcoding import ( IMAGE_EXTENSIONS, get_ffprobe_streams, From 0dc8bbe0a651f5c9a783163b7b43e2239f4005b3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 14 Oct 2025 14:13:50 +0200 Subject: [PATCH 18/28] Reorder imports Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/publish/lib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 657a57226d..96ab76f963 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -7,12 +7,13 @@ import copy import warnings import hashlib import xml.etree.ElementTree +import logging from typing import TYPE_CHECKING, Optional, Union, List, Any + import clique import speedcopy -import logging - import ayon_api +from ayon_api import get_last_version_by_product_name, get_representations import pyblish.util import pyblish.plugin import pyblish.api From 0d96e40d4075695c47905fbbf69268793388eaeb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 14 Oct 2025 15:14:07 +0200 Subject: [PATCH 19/28] Ruff --- client/ayon_core/plugins/publish/extract_review.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index fe0e7d4e3b..8a46b93060 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -507,10 +507,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 = fill_sequence_gaps_with_previous_version( + fill_output = fill_sequence_gaps_with_previous_version( collection=collection, staging_dir=new_repre["stagingDir"], instance=instance, @@ -518,6 +518,7 @@ 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( From bb1fed3dbc59eb5f226656c8b934323fa7373c3c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 14 Oct 2025 15:16:49 +0200 Subject: [PATCH 20/28] Ruff --- client/ayon_core/pipeline/publish/lib.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 657a57226d..7152ec78fa 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -12,11 +12,15 @@ 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, @@ -30,7 +34,6 @@ from .constants import ( DEFAULT_PUBLISH_TEMPLATE, DEFAULT_HERO_PUBLISH_TEMPLATE, ) -from ayon_api import get_last_version_by_product_name, get_representations if TYPE_CHECKING: from ayon_core.pipeline.traits import Representation @@ -1036,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: From 0aaac3e0d5769718960f362e1391fa7d7406e568 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 16 Oct 2025 10:38:12 +0200 Subject: [PATCH 21/28] Updates review extraction process. Refactors review extraction to remove Ftrack-specific elements. - Removes redundant data related to frame starts in Ftrack. - Simplifies review representation. - Streamlines the audio input processing. --- client/ayon_core/plugins/publish/extract_review.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 04e534054e..25c4c817ff 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -130,7 +130,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. @@ -612,8 +612,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 }) @@ -1384,15 +1382,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) From 16d5fe45fc1ad21c6a9a548ffcdef528badd2fb7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:44:35 +0200 Subject: [PATCH 22/28] use settings summary to resolve which addon versions are used --- client/ayon_core/addon/base.py | 81 ++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index 1d1562f543..9207bb74c0 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -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 From 99a9541f27cf6c26117cae17b6129be20d5397c5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 20 Oct 2025 14:55:03 +0200 Subject: [PATCH 23/28] Ruff --- client/ayon_core/plugins/publish/extract_review.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 8a46b93060..0111d02cb3 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -19,7 +19,9 @@ from ayon_core.lib import ( path_to_subprocess_arg, run_subprocess, ) -from ayon_core.pipeline.publish.lib import fill_sequence_gaps_with_previous_version +from ayon_core.pipeline.publish.lib import ( + fill_sequence_gaps_with_previous_version +) from ayon_core.lib.transcoding import ( IMAGE_EXTENSIONS, get_ffprobe_streams, From 1487c30f06655e43757650160a151c2b38951002 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 20 Oct 2025 20:27:40 +0300 Subject: [PATCH 24/28] Revert changes in `pyproject.toml` from #1215 --- pyproject.toml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 44c6a9d73c..be3e82cc67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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] From 5b8d92fb9c6528d0fdb8f81ae81045f9c38979e9 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 21 Oct 2025 08:31:10 +0000 Subject: [PATCH 25/28] [Automated] Add generated package files from main --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index bbced6b641..bbe6ee01a8 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.6.4+dev" +__version__ = "1.6.5" diff --git a/package.py b/package.py index 114f7d12ef..4a2cd6d568 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.6.4+dev" +version = "1.6.5" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index be3e82cc67..d603c9dc04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.6.4+dev" +version = "1.6.5" description = "" authors = ["Ynput Team "] readme = "README.md" From fc5199c70f8712697874a2c52af89461c8eb5024 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 21 Oct 2025 08:31:51 +0000 Subject: [PATCH 26/28] [Automated] Update version in package.py for develop --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index bbe6ee01a8..d3b3454fd1 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.6.5" +__version__ = "1.6.5+dev" diff --git a/package.py b/package.py index 4a2cd6d568..2889039502 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.6.5" +version = "1.6.5+dev" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index d603c9dc04..f43846ec2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.6.5" +version = "1.6.5+dev" description = "" authors = ["Ynput Team "] readme = "README.md" From afee12cd7ad8b6623e338ee6f9fd579a77aaffea Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 21 Oct 2025 08:33:00 +0000 Subject: [PATCH 27/28] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 27ed2217dd..646a2dd1ee 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ 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 From 363e338a613331750036d8691ac3c3252f3fa3ba Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:01:47 +0200 Subject: [PATCH 28/28] pass studio bundle name to farm --- client/ayon_core/plugins/publish/collect_farm_env_variables.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/plugins/publish/collect_farm_env_variables.py b/client/ayon_core/plugins/publish/collect_farm_env_variables.py index 39c421381d..d35f02b9df 100644 --- a/client/ayon_core/plugins/publish/collect_farm_env_variables.py +++ b/client/ayon_core/plugins/publish/collect_farm_env_variables.py @@ -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?