From 8e281f4efef4917fbe965f7e49979577e4e6e226 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 10 Jul 2023 13:17:30 +0200 Subject: [PATCH 001/111] simplification of subprocess calls --- openpype/pipeline/colorspace.py | 63 +++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 3f2d4891c17..2ca78f35201 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -206,8 +206,9 @@ def validate_imageio_colorspace_in_config(config_path, colorspace_name): return True +# TODO: remove this in future - backward compatibility def get_data_subprocess(config_path, data_type): - """Get data via subprocess + """[Deprecated] Get data via subprocess Wrapper for Python 2 hosts. @@ -221,8 +222,46 @@ def get_data_subprocess(config_path, data_type): "config", data_type, "--in_path", config_path, "--out_path", tmp_json_path + ] + log.info("Executing: {}".format(" ".join(args))) + + process_kwargs = { + "logger": log + } + + run_openpype_process(*args, **process_kwargs) + # return all colorspaces + return_json_data = open(tmp_json_path).read() + return json.loads(return_json_data) + + +def get_wrapped_with_subprocess(command_group, command, **kwargs): + """Get data via subprocess + + Wrapper for Python 2 hosts. + + Args: + command_group (str): command group name + command (str): command name + **kwargs: command arguments + + Returns: + Any[dict, None]: data + """ + with _make_temp_json_file() as tmp_json_path: + # Prepare subprocess arguments + args = [ + "run", get_ocio_config_script_path(), + command_group, command ] + + for key_, value_ in kwargs.items(): + args.extend(("--{}".format(key_), value_)) + + args.append("--out_path") + args.append(tmp_json_path) + log.info("Executing: {}".format(" ".join(args))) process_kwargs = { @@ -260,15 +299,18 @@ def get_ocio_config_colorspaces(config_path): if not compatibility_check(): # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess - return get_colorspace_data_subprocess(config_path) + return get_wrapped_with_subprocess( + "config", "get_colorspace", in_path=config_path + ) from openpype.scripts.ocio_wrapper import _get_colorspace_data return _get_colorspace_data(config_path) +# TODO: remove this in future - backward compatibility def get_colorspace_data_subprocess(config_path): - """Get colorspace data via subprocess + """[Deprecated] Get colorspace data via subprocess Wrapper for Python 2 hosts. @@ -278,7 +320,9 @@ def get_colorspace_data_subprocess(config_path): Returns: dict: colorspace and family in couple """ - return get_data_subprocess(config_path, "get_colorspace") + return get_wrapped_with_subprocess( + "config", "get_colorspace", in_path=config_path + ) def get_ocio_config_views(config_path): @@ -296,15 +340,18 @@ def get_ocio_config_views(config_path): if not compatibility_check(): # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess - return get_views_data_subprocess(config_path) + return get_wrapped_with_subprocess( + "config", "get_views", in_path=config_path + ) from openpype.scripts.ocio_wrapper import _get_views_data return _get_views_data(config_path) +# TODO: remove this in future - backward compatibility def get_views_data_subprocess(config_path): - """Get viewers data via subprocess + """[Deprecated] Get viewers data via subprocess Wrapper for Python 2 hosts. @@ -314,7 +361,9 @@ def get_views_data_subprocess(config_path): Returns: dict: `display/viewer` and viewer data """ - return get_data_subprocess(config_path, "get_views") + return get_wrapped_with_subprocess( + "config", "get_views", in_path=config_path + ) def get_imageio_config( From 739c2e15bc58c447cf25ef9a9f9204a0aaf05344 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 10 Jul 2023 13:18:34 +0200 Subject: [PATCH 002/111] adding support for OCIO v2 file rules --- openpype/pipeline/colorspace.py | 31 ++++++++++ openpype/scripts/ocio_wrapper.py | 99 ++++++++++++++++++++++++++++---- 2 files changed, 119 insertions(+), 11 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 2ca78f35201..26e12871f80 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -132,6 +132,37 @@ def get_imageio_colorspace_from_filepath( return colorspace_name +def get_colorspace_from_filepath(config_path, filepath): + """Get colorspace from file path wrapper. + + Wrapper function for getting colorspace from file path. + + Args: + config_path (str): path leading to config.ocio file + filepath (str): path leading to a file + + Returns: + Any[str, None]: matching colorspace name + """ + if not compatibility_check(): + # python environment is not compatible with PyOpenColorIO + # needs to be run in subprocess + result_data = get_wrapped_with_subprocess( + "colorspace", "get_colorspace_from_filepath", + config_path=config_path, + filepath=filepath + ) + if result_data: + return result_data[0] + + from openpype.scripts.ocio_wrapper import _get_colorspace_from_filepath + + result_data = _get_colorspace_from_filepath(config_path, filepath) + + if result_data: + return result_data[0] + + def parse_colorspace_from_filepath( path, host_name, project_name, config_data=None, diff --git a/openpype/scripts/ocio_wrapper.py b/openpype/scripts/ocio_wrapper.py index 16558642c63..1c862163476 100644 --- a/openpype/scripts/ocio_wrapper.py +++ b/openpype/scripts/ocio_wrapper.py @@ -27,7 +27,7 @@ @click.group() def main(): - pass + pass # noqa: WPS100 @main.group() @@ -37,7 +37,17 @@ def config(): Example of use: > pyton.exe ./ocio_wrapper.py config *args """ - pass + pass # noqa: WPS100 + + +@main.group() +def colorspace(): + """Colorspace related commands group + + Example of use: + > pyton.exe ./ocio_wrapper.py config *args + """ + pass # noqa: WPS100 @config.command( @@ -70,8 +80,8 @@ def get_colorspace(in_path, out_path): out_data = _get_colorspace_data(in_path) - with open(json_path, "w") as f: - json.dump(out_data, f) + with open(json_path, "w") as f_: + json.dump(out_data, f_) print(f"Colorspace data are saved to '{json_path}'") @@ -97,8 +107,8 @@ def _get_colorspace_data(config_path): config = ocio.Config().CreateFromFile(str(config_path)) return { - c.getName(): c.getFamily() - for c in config.getColorSpaces() + c_.getName(): c_.getFamily() + for c_ in config.getColorSpaces() } @@ -132,8 +142,8 @@ def get_views(in_path, out_path): out_data = _get_views_data(in_path) - with open(json_path, "w") as f: - json.dump(out_data, f) + with open(json_path, "w") as f_: + json.dump(out_data, f_) print(f"Viewer data are saved to '{json_path}'") @@ -157,7 +167,7 @@ def _get_views_data(config_path): config = ocio.Config().CreateFromFile(str(config_path)) - data = {} + data_ = {} for display in config.getDisplays(): for view in config.getViews(display): colorspace = config.getDisplayViewColorSpaceName(display, view) @@ -165,13 +175,80 @@ def _get_views_data(config_path): if colorspace == "": colorspace = display - data[f"{display}/{view}"] = { + data_[f"{display}/{view}"] = { "display": display, "view": view, "colorspace": colorspace } - return data + return data_ + + +@colorspace.command( + name="get_colorspace_from_filepath", + help=( + "return colorspace from filepath " + "--config_path - ocio config file path (input arg is required) " + "--filepath - any file path (input arg is required) " + "--out_path - temp json file path (input arg is required)" + ) +) +@click.option("--config_path", required=True, + help="path where to read ocio config file", + type=click.Path(exists=True)) +@click.option("--filepath", required=True, + help="path to file to get colorspace from", + type=click.Path(exists=True)) +@click.option("--out_path", required=True, + help="path where to write output json file", + type=click.Path()) +def get_colorspace_from_filepath(config_path, filepath, out_path): + """Get colorspace from file path wrapper. + + Python 2 wrapped console command + + Args: + config_path (str): config file path string + filepath (str): path string leading to file + out_path (str): temp json file path string + + Example of use: + > pyton.exe ./ocio_wrapper.py colorspace get_colorspace_from_filepath \ + --config_path= --filepath= --out_path= + """ + json_path = Path(out_path) + + colorspace = _get_colorspace_from_filepath(config_path, filepath) + + with open(json_path, "w") as f_: + json.dump(colorspace, f_) + + print(f"Colorspace name is saved to '{json_path}'") + + +def _get_colorspace_from_filepath(config_path, filepath): + """Return found colorspace data found in v2 file rules. + + Args: + config_path (str): path string leading to config.ocio + filepath (str): path string leading to v2 file rules + + Raises: + IOError: Input config does not exist. + + Returns: + dict: aggregated available colorspaces + """ + config_path = Path(config_path) + + if not config_path.is_file(): + raise IOError( + f"Input path `{config_path}` should be `config.ocio` file") + + config = ocio.Config().CreateFromFile(str(config_path)) + colorspace = config.getColorSpaceFromFilepath(str(filepath)) + + return colorspace if __name__ == '__main__': From 0114993456108652b8995ac87531462e79e065b6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 10 Jul 2023 13:57:20 +0200 Subject: [PATCH 003/111] compatibility to config version --- openpype/pipeline/colorspace.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 26e12871f80..0b23c2b4e36 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -124,6 +124,10 @@ def get_imageio_colorspace_from_filepath( )) return None + if compatibility_check_config_version(config_data["path"], major=2): + colorspace_name = get_colorspace_from_filepath( + config_data["path"], path) + # validate matching colorspace with config if validate and config_data: validate_imageio_colorspace_in_config( @@ -312,6 +316,32 @@ def compatibility_check(): import PyOpenColorIO # noqa: F401 except ImportError: return False + + # compatible + return True + + +def compatibility_check_config_version(config_path, major=1, minor=None): + """Making sure PyOpenColorIO config version is compatible""" + try: + import PyOpenColorIO as ocio + config = ocio.Config().CreateFromFile(str(config_path)) + + config_version_major = config.getMajorVersion() + config_version_minor = config.getMinorVersion() + print(config_version_major, config_version_minor) + + # check major version + if config_version_major != major: + return False + # check minor version + if minor and config_version_minor != minor: + return False + + except ImportError: + return False + + # compatible return True From 9c18402ac70952a39247d681322777951b1fe97c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 10 Jul 2023 16:42:52 +0200 Subject: [PATCH 004/111] updating config version validation - cashing python version validation so it is overwritable by tests - implementing python compatibility override into tests - adding tests for ocio v2 filerules --- openpype/pipeline/colorspace.py | 52 +++++++++------ openpype/scripts/ocio_wrapper.py | 64 +++++++++++++++++- .../unit/openpype/pipeline/test_colorspace.py | 65 +++++++++++++++++++ 3 files changed, 161 insertions(+), 20 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 0b23c2b4e36..72c140195e9 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -19,6 +19,7 @@ class CashedData: remapping = None + python3compatible = None @contextlib.contextmanager @@ -118,16 +119,23 @@ def get_imageio_colorspace_from_filepath( if ext_match and file_match: colorspace_name = file_rule["colorspace"] + # if no file rule matched, try to get colorspace + # from filepath with OCIO v2 way + # QUESTION: should we override file rules from our settings and + # in ocio v2 only focus on file rules set in config file? + if ( + compatibility_check_config_version(config_data["path"], major=2) + and not colorspace_name + ): + colorspace_name = get_colorspace_from_filepath( + config_data["path"], path) + if not colorspace_name: log.info("No imageio file rule matched input path: '{}'".format( path )) return None - if compatibility_check_config_version(config_data["path"], major=2): - colorspace_name = get_colorspace_from_filepath( - config_data["path"], path) - # validate matching colorspace with config if validate and config_data: validate_imageio_colorspace_in_config( @@ -312,33 +320,39 @@ def get_wrapped_with_subprocess(command_group, command, **kwargs): def compatibility_check(): """Making sure PyOpenColorIO is importable""" + if CashedData.python3compatible is not None: + return CashedData.python3compatible + try: import PyOpenColorIO # noqa: F401 + CashedData.python3compatible = True except ImportError: - return False + CashedData.python3compatible = False # compatible - return True + return CashedData.python3compatible def compatibility_check_config_version(config_path, major=1, minor=None): """Making sure PyOpenColorIO config version is compatible""" - try: - import PyOpenColorIO as ocio - config = ocio.Config().CreateFromFile(str(config_path)) - config_version_major = config.getMajorVersion() - config_version_minor = config.getMinorVersion() - print(config_version_major, config_version_minor) + if not compatibility_check(): + # python environment is not compatible with PyOpenColorIO + # needs to be run in subprocess + version_data = get_wrapped_with_subprocess( + "config", "get_version", config_path=config_path + ) - # check major version - if config_version_major != major: - return False - # check minor version - if minor and config_version_minor != minor: - return False + from openpype.scripts.ocio_wrapper import _get_version_data - except ImportError: + version_data = _get_version_data(config_path) + + # check major version + if version_data["major"] != major: + return False + + # check minor version + if minor and version_data["minor"] != minor: return False # compatible diff --git a/openpype/scripts/ocio_wrapper.py b/openpype/scripts/ocio_wrapper.py index 1c862163476..4332ea5b01e 100644 --- a/openpype/scripts/ocio_wrapper.py +++ b/openpype/scripts/ocio_wrapper.py @@ -184,6 +184,68 @@ def _get_views_data(config_path): return data_ +@config.command( + name="get_version", + help=( + "return major and minor version from config file " + "--config_path input arg is required" + "--out_path input arg is required" + ) +) +@click.option("--config_path", required=True, + help="path where to read ocio config file", + type=click.Path(exists=True)) +@click.option("--out_path", required=True, + help="path where to write output json file", + type=click.Path()) +def get_version(config_path, out_path): + """Get version of config. + + Python 2 wrapped console command + + Args: + config_path (str): ocio config file path string + out_path (str): temp json file path string + + Example of use: + > pyton.exe ./ocio_wrapper.py config get_version \ + --config_path= --out_path= + """ + json_path = Path(out_path) + + out_data = _get_version_data(config_path) + + with open(json_path, "w") as f_: + json.dump(out_data, f_) + + print(f"Config version data are saved to '{json_path}'") + + +def _get_version_data(config_path): + """Return major and minor version info. + + Args: + config_path (str): path string leading to config.ocio + + Raises: + IOError: Input config does not exist. + + Returns: + dict: minor and major keys with values + """ + config_path = Path(config_path) + + if not config_path.is_file(): + raise IOError("Input path should be `config.ocio` file") + + config = ocio.Config().CreateFromFile(str(config_path)) + + return { + "major": config.getMajorVersion(), + "minor": config.getMinorVersion() + } + + @colorspace.command( name="get_colorspace_from_filepath", help=( @@ -198,7 +260,7 @@ def _get_views_data(config_path): type=click.Path(exists=True)) @click.option("--filepath", required=True, help="path to file to get colorspace from", - type=click.Path(exists=True)) + type=click.Path()) @click.option("--out_path", required=True, help="path where to write output json file", type=click.Path()) diff --git a/tests/unit/openpype/pipeline/test_colorspace.py b/tests/unit/openpype/pipeline/test_colorspace.py index c22acee2d42..a6fcc68055e 100644 --- a/tests/unit/openpype/pipeline/test_colorspace.py +++ b/tests/unit/openpype/pipeline/test_colorspace.py @@ -185,5 +185,70 @@ def test_file_rules(self, project_settings): assert expected_hiero == hiero_file_rules, ( f"Not matching file rules {expected_hiero}") + def test_get_imageio_colorspace_from_filepath_p3(self, project_settings): + """Test Colorspace from filepath with python 3 compatibility mode + + Also test ocio v2 file rules + """ + nuke_filepath = "renderCompMain_baking_h264.mp4" + hiero_filepath = "prerenderCompMain.mp4" + + expected_nuke = "Camera Rec.709" + expected_hiero = "Gamma 2.2 Rec.709 - Texture" + + nuke_colorspace = colorspace.get_imageio_colorspace_from_filepath( + nuke_filepath, + "nuke", + "test_project", + project_settings=project_settings + ) + assert expected_nuke == nuke_colorspace, ( + f"Not matching colorspace {expected_nuke}") + + hiero_colorspace = colorspace.get_imageio_colorspace_from_filepath( + hiero_filepath, + "hiero", + "test_project", + project_settings=project_settings + ) + assert expected_hiero == hiero_colorspace, ( + f"Not matching colorspace {expected_hiero}") + + def test_get_imageio_colorspace_from_filepath_python2mode( + self, project_settings): + """Test Colorspace from filepath with python 2 compatibility mode + + Also test ocio v2 file rules + """ + nuke_filepath = "renderCompMain_baking_h264.mp4" + hiero_filepath = "prerenderCompMain.mp4" + + expected_nuke = "Camera Rec.709" + expected_hiero = "Gamma 2.2 Rec.709 - Texture" + + # switch to python 2 compatibility mode + colorspace.CashedData.python3compatible = False + + nuke_colorspace = colorspace.get_imageio_colorspace_from_filepath( + nuke_filepath, + "nuke", + "test_project", + project_settings=project_settings + ) + assert expected_nuke == nuke_colorspace, ( + f"Not matching colorspace {expected_nuke}") + + hiero_colorspace = colorspace.get_imageio_colorspace_from_filepath( + hiero_filepath, + "hiero", + "test_project", + project_settings=project_settings + ) + assert expected_hiero == hiero_colorspace, ( + f"Not matching colorspace {expected_hiero}") + + # return to python 3 compatibility mode + colorspace.CashedData.python3compatible = None + test_case = TestPipelineColorspace() From edc260073b62afb56c2304e747bb739274665630 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 10 Jul 2023 16:49:22 +0200 Subject: [PATCH 005/111] updating testing package gdrive hash --- tests/unit/openpype/pipeline/test_colorspace.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/openpype/pipeline/test_colorspace.py b/tests/unit/openpype/pipeline/test_colorspace.py index a6fcc68055e..e63ca510f24 100644 --- a/tests/unit/openpype/pipeline/test_colorspace.py +++ b/tests/unit/openpype/pipeline/test_colorspace.py @@ -26,12 +26,12 @@ class TestPipelineColorspace(TestPipeline): Example: cd to OpenPype repo root dir - poetry run python ./start.py runtests ../tests/unit/openpype/pipeline - """ + poetry run python ./start.py runtests /tests/unit/openpype/pipeline/test_colorspace.py + """ # noqa: E501 TEST_FILES = [ ( - "1Lf-mFxev7xiwZCWfImlRcw7Fj8XgNQMh", + "1csqimz8bbNcNgxtEXklLz6GRv91D3KgA", "test_pipeline_colorspace.zip", "" ) From 97477d2049f8b5493ef0c7f03aaceb6e3ea9a034 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jul 2023 16:11:54 +0200 Subject: [PATCH 006/111] todos for parseColorSpaceFromString --- openpype/pipeline/colorspace.py | 2 ++ openpype/scripts/ocio_wrapper.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 72c140195e9..13b235d5dd5 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -123,6 +123,8 @@ def get_imageio_colorspace_from_filepath( # from filepath with OCIO v2 way # QUESTION: should we override file rules from our settings and # in ocio v2 only focus on file rules set in config file? + # TODO: do the ocio v compatibility check inside of wrapper script + # because of implementation `parseColorSpaceFromString` if ( compatibility_check_config_version(config_data["path"], major=2) and not colorspace_name diff --git a/openpype/scripts/ocio_wrapper.py b/openpype/scripts/ocio_wrapper.py index 4332ea5b01e..1feedde627a 100644 --- a/openpype/scripts/ocio_wrapper.py +++ b/openpype/scripts/ocio_wrapper.py @@ -308,6 +308,8 @@ def _get_colorspace_from_filepath(config_path, filepath): f"Input path `{config_path}` should be `config.ocio` file") config = ocio.Config().CreateFromFile(str(config_path)) + + # TODO: use `parseColorSpaceFromString` instead if ocio v1 colorspace = config.getColorSpaceFromFilepath(str(filepath)) return colorspace From 44d8327aa9c92246c3aec2e588034bad9d83c27c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 28 Aug 2023 12:43:28 +0200 Subject: [PATCH 007/111] adding deprecated decorators --- openpype/pipeline/colorspace.py | 53 ++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 02a6b90f253..c90fb299f9e 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -2,9 +2,12 @@ import re import os import json -import platform import contextlib +import functools +import platform import tempfile +import warnings + from openpype import PACKAGE_DIR from openpype.settings import get_project_settings from openpype.lib import ( @@ -22,6 +25,51 @@ class CashedData: python3compatible = None +class DeprecatedWarning(DeprecationWarning): + pass + + +def deprecated(new_destination): + """Mark functions as deprecated. + + It will result in a warning being emitted when the function is used. + """ + + func = None + if callable(new_destination): + func = new_destination + new_destination = None + + def _decorator(decorated_func): + if new_destination is None: + warning_message = ( + " Please check content of deprecated function to figure out" + " possible replacement." + ) + else: + warning_message = " Please replace your usage with '{}'.".format( + new_destination + ) + + @functools.wraps(decorated_func) + def wrapper(*args, **kwargs): + warnings.simplefilter("always", DeprecatedWarning) + warnings.warn( + ( + "Call to deprecated function '{}'" + "\nFunction was moved or removed.{}" + ).format(decorated_func.__name__, warning_message), + category=DeprecatedWarning, + stacklevel=4 + ) + return decorated_func(*args, **kwargs) + return wrapper + + if func is None: + return _decorator + return _decorator(func) + + @contextlib.contextmanager def _make_temp_json_file(): """Wrapping function for json temp file @@ -252,6 +300,7 @@ def validate_imageio_colorspace_in_config(config_path, colorspace_name): # TODO: remove this in future - backward compatibility +@deprecated("get_wrapped_with_subprocess") def get_data_subprocess(config_path, data_type): """[Deprecated] Get data via subprocess @@ -386,6 +435,7 @@ def get_ocio_config_colorspaces(config_path): # TODO: remove this in future - backward compatibility +@deprecated("get_wrapped_with_subprocess") def get_colorspace_data_subprocess(config_path): """[Deprecated] Get colorspace data via subprocess @@ -427,6 +477,7 @@ def get_ocio_config_views(config_path): # TODO: remove this in future - backward compatibility +@deprecated("get_wrapped_with_subprocess") def get_views_data_subprocess(config_path): """[Deprecated] Get viewers data via subprocess From c2212a6cc111f9499b41701dcbad0e1b5809ae07 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 28 Aug 2023 13:21:24 +0200 Subject: [PATCH 008/111] revert unreal submodule --- openpype/hosts/unreal/integration | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/integration b/openpype/hosts/unreal/integration index ff15c700771..63266607ceb 160000 --- a/openpype/hosts/unreal/integration +++ b/openpype/hosts/unreal/integration @@ -1 +1 @@ -Subproject commit ff15c700771e719cc5f3d561ac5d6f7590623986 +Subproject commit 63266607ceb972a61484f046634ddfc9eb0b5757 From e10ca74b1b93ddebb9786b04e22dde1d6de137c4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 28 Aug 2023 14:00:52 +0200 Subject: [PATCH 009/111] changing signature of `parse_colorspace_from_filepath` --- openpype/pipeline/colorspace.py | 47 ++++++++++--------- .../unit/openpype/pipeline/test_colorspace.py | 4 +- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index c90fb299f9e..9ea9d1f8885 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -171,8 +171,6 @@ def get_imageio_colorspace_from_filepath( # from filepath with OCIO v2 way # QUESTION: should we override file rules from our settings and # in ocio v2 only focus on file rules set in config file? - # TODO: do the ocio v compatibility check inside of wrapper script - # because of implementation `parseColorSpaceFromString` if ( compatibility_check_config_version(config_data["path"], major=2) and not colorspace_name @@ -226,51 +224,56 @@ def get_colorspace_from_filepath(config_path, filepath): def parse_colorspace_from_filepath( - path, host_name, project_name, - config_data=None, - project_settings=None + filepath, colorspaces=None, config_path=None ): - """Parse colorspace name from filepath + """Parse colorspace name from list of filepaths An input path can have colorspace name used as part of name or as folder name. + # add example python code block + + Example: + >>> config_path = "path/to/config.ocio" + >>> colorspaces = get_ocio_config_colorspaces(config_path) + >>> colorspace = parse_colorspace_from_filepath( + "path/to/file/acescg/file.exr", + colorspaces=colorspaces + ) + >>> print(colorspace) + acescg + Args: - path (str): path string - host_name (str): host name - project_name (str): project name - config_data (dict, optional): config path and template in dict. - Defaults to None. - project_settings (dict, optional): project settings. Defaults to None. + filepath (str): path string + colorspaces (Optional[dict[str]]): list of colorspaces + config_path (Optional[str]): path to config.ocio file Returns: str: name of colorspace """ - if not config_data: - project_settings = project_settings or get_project_settings( - project_name + if not colorspaces and not config_path: + raise ValueError( + "You need to provide `config_path` if you don't " + "want to provide input `colorspaces`." ) - config_data = get_imageio_config( - project_name, host_name, project_settings) - config_path = config_data["path"] + colorspaces = colorspaces or get_ocio_config_colorspaces(config_path) # match file rule from path colorspace_name = None - colorspaces = get_ocio_config_colorspaces(config_path) for colorspace_key in colorspaces: # check underscored variant of colorspace name # since we are reformatting it in integrate.py - if colorspace_key.replace(" ", "_") in path: + if colorspace_key.replace(" ", "_") in filepath: colorspace_name = colorspace_key break - if colorspace_key in path: + if colorspace_key in filepath: colorspace_name = colorspace_key break if not colorspace_name: log.info("No matching colorspace in config '{}' for path: '{}'".format( - config_path, path + config_path, filepath )) return None diff --git a/tests/unit/openpype/pipeline/test_colorspace.py b/tests/unit/openpype/pipeline/test_colorspace.py index e63ca510f24..8ae98f7cf8f 100644 --- a/tests/unit/openpype/pipeline/test_colorspace.py +++ b/tests/unit/openpype/pipeline/test_colorspace.py @@ -132,14 +132,14 @@ def test_parse_colorspace_from_filepath( path_1 = "renderCompMain_ACES2065-1.####.exr" expected_1 = "ACES2065-1" ret_1 = colorspace.parse_colorspace_from_filepath( - path_1, "nuke", "test_project", project_settings=project_settings + path_1, config_path=config_path_asset ) assert ret_1 == expected_1, f"Not matching colorspace {expected_1}" path_2 = "renderCompMain_BMDFilm_WideGamut_Gen5.mov" expected_2 = "BMDFilm WideGamut Gen5" ret_2 = colorspace.parse_colorspace_from_filepath( - path_2, "nuke", "test_project", project_settings=project_settings + path_2, config_path=config_path_asset ) assert ret_2 == expected_2, f"Not matching colorspace {expected_2}" From c99aa746a2d63b092593fd24f0cda9caa397cd50 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 28 Aug 2023 14:01:36 +0200 Subject: [PATCH 010/111] docstring update --- openpype/pipeline/colorspace.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 9ea9d1f8885..e675bdb2e19 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -226,13 +226,11 @@ def get_colorspace_from_filepath(config_path, filepath): def parse_colorspace_from_filepath( filepath, colorspaces=None, config_path=None ): - """Parse colorspace name from list of filepaths + """Parse colorspace name from filepath An input path can have colorspace name used as part of name or as folder name. - # add example python code block - Example: >>> config_path = "path/to/config.ocio" >>> colorspaces = get_ocio_config_colorspaces(config_path) From ba237487a65176f2ee58e08533c65115b5a9c19d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 28 Aug 2023 15:53:36 +0200 Subject: [PATCH 011/111] cashing OCIO config version data --- openpype/pipeline/colorspace.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index e675bdb2e19..30bd685b139 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -23,6 +23,7 @@ class CashedData: remapping = None python3compatible = None + config_version_data = None class DeprecatedWarning(DeprecationWarning): @@ -395,16 +396,17 @@ def compatibility_check_config_version(config_path, major=1, minor=None): "config", "get_version", config_path=config_path ) - from openpype.scripts.ocio_wrapper import _get_version_data + if not CashedData.config_version_data: + from openpype.scripts.ocio_wrapper import _get_version_data - version_data = _get_version_data(config_path) + CashedData.config_version_data = _get_version_data(config_path) # check major version - if version_data["major"] != major: + if CashedData.config_version_data["major"] != major: return False # check minor version - if minor and version_data["minor"] != minor: + if minor and CashedData.config_version_data["minor"] != minor: return False # compatible From 06930318c2762b0f80d60ef07dd3d38a79e7b318 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 28 Aug 2023 16:01:25 +0200 Subject: [PATCH 012/111] fixing logic of the cashing --- openpype/pipeline/colorspace.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 30bd685b139..c1dc47245a8 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -389,17 +389,19 @@ def compatibility_check(): def compatibility_check_config_version(config_path, major=1, minor=None): """Making sure PyOpenColorIO config version is compatible""" - if not compatibility_check(): - # python environment is not compatible with PyOpenColorIO - # needs to be run in subprocess - version_data = get_wrapped_with_subprocess( - "config", "get_version", config_path=config_path - ) - if not CashedData.config_version_data: - from openpype.scripts.ocio_wrapper import _get_version_data + if compatibility_check(): + from openpype.scripts.ocio_wrapper import _get_version_data + + CashedData.config_version_data = _get_version_data(config_path) + + else: + # python environment is not compatible with PyOpenColorIO + # needs to be run in subprocess + CashedData.config_version_data = get_wrapped_with_subprocess( + "config", "get_version", config_path=config_path + ) - CashedData.config_version_data = _get_version_data(config_path) # check major version if CashedData.config_version_data["major"] != major: From 6a36d713139ccd4e21b44c0c247a6adda236c863 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 28 Aug 2023 16:26:36 +0200 Subject: [PATCH 013/111] adding colorspace parsing form filepath as fallback --- openpype/pipeline/colorspace.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index c1dc47245a8..eb3c4c3b944 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -170,8 +170,6 @@ def get_imageio_colorspace_from_filepath( # if no file rule matched, try to get colorspace # from filepath with OCIO v2 way - # QUESTION: should we override file rules from our settings and - # in ocio v2 only focus on file rules set in config file? if ( compatibility_check_config_version(config_data["path"], major=2) and not colorspace_name @@ -179,6 +177,11 @@ def get_imageio_colorspace_from_filepath( colorspace_name = get_colorspace_from_filepath( config_data["path"], path) + # use parse colorspace from filepath as fallback + colorspace_name = colorspace_name or parse_colorspace_from_filepath( + path, config_path=config_data["path"] + ) + if not colorspace_name: log.info("No imageio file rule matched input path: '{}'".format( path @@ -196,7 +199,8 @@ def get_imageio_colorspace_from_filepath( def get_colorspace_from_filepath(config_path, filepath): """Get colorspace from file path wrapper. - Wrapper function for getting colorspace from file path. + Wrapper function for getting colorspace from file path + with use of OCIO v2 file-rules. Args: config_path (str): path leading to config.ocio file From 7bb23b1bc0d7af7e6260d135a1ef9c77c0f35810 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 28 Aug 2023 16:35:03 +0200 Subject: [PATCH 014/111] utilising Cache for config colorspaces --- openpype/pipeline/colorspace.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index eb3c4c3b944..70b82d6be27 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -24,6 +24,7 @@ class CashedData: remapping = None python3compatible = None config_version_data = None + ocio_config_colorspaces = {} class DeprecatedWarning(DeprecationWarning): @@ -155,7 +156,7 @@ def get_imageio_colorspace_from_filepath( # match file rule from path colorspace_name = None - for _frule_name, file_rule in file_rules.items(): + for _, file_rule in file_rules.items(): pattern = file_rule["pattern"] extension = file_rule["ext"] ext_match = re.match( @@ -431,16 +432,22 @@ def get_ocio_config_colorspaces(config_path): Returns: dict: colorspace and family in couple """ - if not compatibility_check(): - # python environment is not compatible with PyOpenColorIO - # needs to be run in subprocess - return get_wrapped_with_subprocess( - "config", "get_colorspace", in_path=config_path - ) + if not CashedData.ocio_config_colorspaces.get(config_path): + if not compatibility_check(): + # python environment is not compatible with PyOpenColorIO + # needs to be run in subprocess + CashedData.ocio_config_colorspaces[config_path] = \ + get_wrapped_with_subprocess( + "config", "get_colorspace", in_path=config_path + ) + else: + from openpype.scripts.ocio_wrapper import _get_colorspace_data + + CashedData.ocio_config_colorspaces[config_path] = \ + _get_colorspace_data(config_path) - from openpype.scripts.ocio_wrapper import _get_colorspace_data + return CashedData.ocio_config_colorspaces[config_path] - return _get_colorspace_data(config_path) # TODO: remove this in future - backward compatibility From ab872146a9bc52068dd4abb6c009160e1a79120c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 28 Aug 2023 16:36:01 +0200 Subject: [PATCH 015/111] removing line --- openpype/pipeline/colorspace.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 70b82d6be27..8378da5b989 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -449,7 +449,6 @@ def get_ocio_config_colorspaces(config_path): return CashedData.ocio_config_colorspaces[config_path] - # TODO: remove this in future - backward compatibility @deprecated("get_wrapped_with_subprocess") def get_colorspace_data_subprocess(config_path): From b60043e94589b0a1dfdb7853deb6560b5255827b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 28 Aug 2023 16:38:34 +0200 Subject: [PATCH 016/111] hound --- openpype/pipeline/colorspace.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 8378da5b989..1251308eb37 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -407,7 +407,6 @@ def compatibility_check_config_version(config_path, major=1, minor=None): "config", "get_version", config_path=config_path ) - # check major version if CashedData.config_version_data["major"] != major: return False From a4e876792a28709a398433a7e047e40c865c4244 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 30 Aug 2023 15:42:05 +0200 Subject: [PATCH 017/111] typo fix --- openpype/pipeline/colorspace.py | 46 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 1251308eb37..8025022c594 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -20,7 +20,7 @@ log = Logger.get_logger(__name__) -class CashedData: +class CachedData: remapping = None python3compatible = None config_version_data = None @@ -378,41 +378,41 @@ def get_wrapped_with_subprocess(command_group, command, **kwargs): def compatibility_check(): """Making sure PyOpenColorIO is importable""" - if CashedData.python3compatible is not None: - return CashedData.python3compatible + if CachedData.python3compatible is not None: + return CachedData.python3compatible try: import PyOpenColorIO # noqa: F401 - CashedData.python3compatible = True + CachedData.python3compatible = True except ImportError: - CashedData.python3compatible = False + CachedData.python3compatible = False # compatible - return CashedData.python3compatible + return CachedData.python3compatible def compatibility_check_config_version(config_path, major=1, minor=None): """Making sure PyOpenColorIO config version is compatible""" - if not CashedData.config_version_data: + if not CachedData.config_version_data: if compatibility_check(): from openpype.scripts.ocio_wrapper import _get_version_data - CashedData.config_version_data = _get_version_data(config_path) + CachedData.config_version_data = _get_version_data(config_path) else: # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess - CashedData.config_version_data = get_wrapped_with_subprocess( + CachedData.config_version_data = get_wrapped_with_subprocess( "config", "get_version", config_path=config_path ) # check major version - if CashedData.config_version_data["major"] != major: + if CachedData.config_version_data["major"] != major: return False # check minor version - if minor and CashedData.config_version_data["minor"] != minor: + if minor and CachedData.config_version_data["minor"] != minor: return False # compatible @@ -431,21 +431,21 @@ def get_ocio_config_colorspaces(config_path): Returns: dict: colorspace and family in couple """ - if not CashedData.ocio_config_colorspaces.get(config_path): + if not CachedData.ocio_config_colorspaces.get(config_path): if not compatibility_check(): # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess - CashedData.ocio_config_colorspaces[config_path] = \ + CachedData.ocio_config_colorspaces[config_path] = \ get_wrapped_with_subprocess( "config", "get_colorspace", in_path=config_path ) else: from openpype.scripts.ocio_wrapper import _get_colorspace_data - CashedData.ocio_config_colorspaces[config_path] = \ + CachedData.ocio_config_colorspaces[config_path] = \ _get_colorspace_data(config_path) - return CashedData.ocio_config_colorspaces[config_path] + return CachedData.ocio_config_colorspaces[config_path] # TODO: remove this in future - backward compatibility @@ -730,15 +730,15 @@ def get_remapped_colorspace_to_native( Union[str, None]: native colorspace name defined in remapping or None """ - CashedData.remapping.setdefault(host_name, {}) - if CashedData.remapping[host_name].get("to_native") is None: + CachedData.remapping.setdefault(host_name, {}) + if CachedData.remapping[host_name].get("to_native") is None: remapping_rules = imageio_host_settings["remapping"]["rules"] - CashedData.remapping[host_name]["to_native"] = { + CachedData.remapping[host_name]["to_native"] = { rule["ocio_name"]: rule["host_native_name"] for rule in remapping_rules } - return CashedData.remapping[host_name]["to_native"].get( + return CachedData.remapping[host_name]["to_native"].get( ocio_colorspace_name) @@ -756,15 +756,15 @@ def get_remapped_colorspace_from_native( Union[str, None]: Ocio colorspace name defined in remapping or None. """ - CashedData.remapping.setdefault(host_name, {}) - if CashedData.remapping[host_name].get("from_native") is None: + CachedData.remapping.setdefault(host_name, {}) + if CachedData.remapping[host_name].get("from_native") is None: remapping_rules = imageio_host_settings["remapping"]["rules"] - CashedData.remapping[host_name]["from_native"] = { + CachedData.remapping[host_name]["from_native"] = { rule["host_native_name"]: rule["ocio_name"] for rule in remapping_rules } - return CashedData.remapping[host_name]["from_native"].get( + return CachedData.remapping[host_name]["from_native"].get( host_native_colorspace_name) From 3292114e2109656e3fb92d6443db52838007181c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 30 Aug 2023 15:46:59 +0200 Subject: [PATCH 018/111] better error message --- openpype/pipeline/colorspace.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 8025022c594..9c77723d122 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -257,8 +257,7 @@ def parse_colorspace_from_filepath( """ if not colorspaces and not config_path: raise ValueError( - "You need to provide `config_path` if you don't " - "want to provide input `colorspaces`." + "Must provide `config_path` if `colorspaces` is not provided." ) colorspaces = colorspaces or get_ocio_config_colorspaces(config_path) From c89d384e5a0033d436763751d3be9c85ea639ce2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 30 Aug 2023 16:55:36 +0200 Subject: [PATCH 019/111] optimisation of regex search suggestion from https://github.com/ynput/OpenPype/pull/5273#discussion_r1309372596 --- openpype/pipeline/colorspace.py | 47 +++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 9c77723d122..47227a0e3b2 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -255,24 +255,49 @@ def parse_colorspace_from_filepath( Returns: str: name of colorspace """ + def _get_colorspace_match_regex(colorspaces): + """Return a regex patter + + Allows to search a colorspace match in a filename + + Args: + colorspaces (list): List of colorspace names + + Returns: + re.Pattern: regex pattern + """ + pattern = "|".join( + # Allow to match spaces also as underscores because the + # integrator replaces spaces with underscores in filenames + re.escape(colorspace).replace(r"\ ", r"[_ ]") for colorspace in + # Sort by longest first so the regex matches longer matches + # over smaller matches, e.g. matching 'Output - sRGB' over 'sRGB' + sorted(colorspaces, key=len, reverse=True) + ) + return re.compile(pattern) + if not colorspaces and not config_path: raise ValueError( "Must provide `config_path` if `colorspaces` is not provided." ) + colorspace_name = None colorspaces = colorspaces or get_ocio_config_colorspaces(config_path) + underscored_colorspaces = { + key.replace(" ", "_"): key for key in colorspaces + if " " in key + } - # match file rule from path - colorspace_name = None - for colorspace_key in colorspaces: - # check underscored variant of colorspace name - # since we are reformatting it in integrate.py - if colorspace_key.replace(" ", "_") in filepath: - colorspace_name = colorspace_key - break - if colorspace_key in filepath: - colorspace_name = colorspace_key - break + # match colorspace from filepath + regex_pattern = _get_colorspace_match_regex(colorspaces) + match = regex_pattern.search(filepath) + colorspace = match.group(0) if match else None + + if colorspace: + colorspace_name = colorspace + + if colorspace in underscored_colorspaces: + colorspace_name = underscored_colorspaces[colorspace] if not colorspace_name: log.info("No matching colorspace in config '{}' for path: '{}'".format( From 3928e26c45f91b1d2113268c8d0c4a889db93cbf Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 30 Aug 2023 17:02:59 +0200 Subject: [PATCH 020/111] turn public to non-public function --- openpype/pipeline/colorspace.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 47227a0e3b2..118466bc922 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -213,7 +213,7 @@ def get_colorspace_from_filepath(config_path, filepath): if not compatibility_check(): # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess - result_data = get_wrapped_with_subprocess( + result_data = _get_wrapped_with_subprocess( "colorspace", "get_colorspace_from_filepath", config_path=config_path, filepath=filepath @@ -331,7 +331,7 @@ def validate_imageio_colorspace_in_config(config_path, colorspace_name): # TODO: remove this in future - backward compatibility -@deprecated("get_wrapped_with_subprocess") +@deprecated("_get_wrapped_with_subprocess") def get_data_subprocess(config_path, data_type): """[Deprecated] Get data via subprocess @@ -361,7 +361,7 @@ def get_data_subprocess(config_path, data_type): return json.loads(return_json_data) -def get_wrapped_with_subprocess(command_group, command, **kwargs): +def _get_wrapped_with_subprocess(command_group, command, **kwargs): """Get data via subprocess Wrapper for Python 2 hosts. @@ -427,7 +427,7 @@ def compatibility_check_config_version(config_path, major=1, minor=None): else: # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess - CachedData.config_version_data = get_wrapped_with_subprocess( + CachedData.config_version_data = _get_wrapped_with_subprocess( "config", "get_version", config_path=config_path ) @@ -460,7 +460,7 @@ def get_ocio_config_colorspaces(config_path): # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess CachedData.ocio_config_colorspaces[config_path] = \ - get_wrapped_with_subprocess( + _get_wrapped_with_subprocess( "config", "get_colorspace", in_path=config_path ) else: @@ -473,7 +473,7 @@ def get_ocio_config_colorspaces(config_path): # TODO: remove this in future - backward compatibility -@deprecated("get_wrapped_with_subprocess") +@deprecated("_get_wrapped_with_subprocess") def get_colorspace_data_subprocess(config_path): """[Deprecated] Get colorspace data via subprocess @@ -485,7 +485,7 @@ def get_colorspace_data_subprocess(config_path): Returns: dict: colorspace and family in couple """ - return get_wrapped_with_subprocess( + return _get_wrapped_with_subprocess( "config", "get_colorspace", in_path=config_path ) @@ -505,7 +505,7 @@ def get_ocio_config_views(config_path): if not compatibility_check(): # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess - return get_wrapped_with_subprocess( + return _get_wrapped_with_subprocess( "config", "get_views", in_path=config_path ) @@ -515,7 +515,7 @@ def get_ocio_config_views(config_path): # TODO: remove this in future - backward compatibility -@deprecated("get_wrapped_with_subprocess") +@deprecated("_get_wrapped_with_subprocess") def get_views_data_subprocess(config_path): """[Deprecated] Get viewers data via subprocess @@ -527,7 +527,7 @@ def get_views_data_subprocess(config_path): Returns: dict: `display/viewer` and viewer data """ - return get_wrapped_with_subprocess( + return _get_wrapped_with_subprocess( "config", "get_views", in_path=config_path ) From 7db4be1a482bbe63e24f1a0176ed35e260cc7bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 30 Aug 2023 17:04:24 +0200 Subject: [PATCH 021/111] Update openpype/pipeline/colorspace.py Co-authored-by: Roy Nieterau --- openpype/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 118466bc922..fa9f1241303 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -156,7 +156,7 @@ def get_imageio_colorspace_from_filepath( # match file rule from path colorspace_name = None - for _, file_rule in file_rules.items(): + for file_rule in file_rules.values(): pattern = file_rule["pattern"] extension = file_rule["ext"] ext_match = re.match( From d570a2bff19fdaa6f0af909c5e450275684f1ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 30 Aug 2023 17:05:48 +0200 Subject: [PATCH 022/111] Update openpype/pipeline/colorspace.py Co-authored-by: Roy Nieterau --- openpype/pipeline/colorspace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index fa9f1241303..20ca60b10e0 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -357,8 +357,8 @@ def get_data_subprocess(config_path, data_type): run_openpype_process(*args, **process_kwargs) # return all colorspaces - return_json_data = open(tmp_json_path).read() - return json.loads(return_json_data) + with open(tmp_json_path, "r") as f: + return json.load(f) def _get_wrapped_with_subprocess(command_group, command, **kwargs): From e8c58f13d7a1b394aae3ad60a7ec2bc290b38511 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 30 Aug 2023 17:08:42 +0200 Subject: [PATCH 023/111] suggestion for json return from file --- openpype/pipeline/colorspace.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 20ca60b10e0..c7eb778fa2c 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -357,8 +357,8 @@ def get_data_subprocess(config_path, data_type): run_openpype_process(*args, **process_kwargs) # return all colorspaces - with open(tmp_json_path, "r") as f: - return json.load(f) + with open(tmp_json_path, "r") as f_: + return json.load(f_) def _get_wrapped_with_subprocess(command_group, command, **kwargs): @@ -396,8 +396,8 @@ def _get_wrapped_with_subprocess(command_group, command, **kwargs): run_openpype_process(*args, **process_kwargs) # return all colorspaces - return_json_data = open(tmp_json_path).read() - return json.loads(return_json_data) + with open(tmp_json_path, "r") as f_: + return json.load(f_) def compatibility_check(): From f1cf4fcda6a205f8c89573f3f5892acb8c5d51e0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 30 Aug 2023 17:23:01 +0200 Subject: [PATCH 024/111] removing deprecated code with backward compatibility --- openpype/pipeline/colorspace.py | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index c7eb778fa2c..9f1c2971889 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -340,25 +340,9 @@ def get_data_subprocess(config_path, data_type): Args: config_path (str): path leading to config.ocio file """ - with _make_temp_json_file() as tmp_json_path: - # Prepare subprocess arguments - args = [ - "run", get_ocio_config_script_path(), - "config", data_type, - "--in_path", config_path, - "--out_path", tmp_json_path - ] - log.info("Executing: {}".format(" ".join(args))) - - process_kwargs = { - "logger": log - } - - run_openpype_process(*args, **process_kwargs) - - # return all colorspaces - with open(tmp_json_path, "r") as f_: - return json.load(f_) + return _get_wrapped_with_subprocess( + "config", data_type, in_path=config_path, + ) def _get_wrapped_with_subprocess(command_group, command, **kwargs): From a6b8fdfe33dc5dcf1eb1120a65f5ad264b3b1ef4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 30 Aug 2023 22:43:06 +0200 Subject: [PATCH 025/111] refactor function names and code logic for colorspace from filepath --- openpype/pipeline/colorspace.py | 118 +++++++++++++++++++++---------- openpype/scripts/ocio_wrapper.py | 10 +-- 2 files changed, 84 insertions(+), 44 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 9f1c2971889..f1acd18a708 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -115,27 +115,92 @@ def get_ocio_config_script_path(): ) -def get_imageio_colorspace_from_filepath( - path, host_name, project_name, +def get_colorspace_name_from_filepath( + filepath, host_name, project_name, config_data=None, file_rules=None, project_settings=None, validate=True ): """Get colorspace name from filepath + Args: + filepath (str): path string, file rule pattern is tested on it + host_name (str): host name + project_name (str): project name + config_data (Optional[dict]): config path and template in dict. + Defaults to None. + file_rules (Optional[dict]): file rule data from settings. + Defaults to None. + project_settings (Optional[dict]): project settings. Defaults to None. + validate (Optional[bool]): should resulting colorspace be validated + with config file? Defaults to True. + + Returns: + str: name of colorspace + """ + # use ImageIO file rules + colorspace_name = get_imageio_file_rules_colorspace_from_filepath( + filepath, host_name, project_name, + config_data=config_data, file_rules=file_rules, + project_settings=project_settings + ) + + # try to get colorspace from OCIO v2 file rules + if ( + not colorspace_name + and compatibility_check_config_version(config_data["path"], major=2) + ): + colorspace_name = get_config_file_rules_colorspace_from_filepath( + config_data["path"], filepath) + + # use parse colorspace from filepath as fallback + colorspace_name = colorspace_name or parse_colorspace_from_filepath( + filepath, config_path=config_data["path"] + ) + + if not colorspace_name: + log.info("No imageio file rule matched input path: '{}'".format( + filepath + )) + return None + + # validate matching colorspace with config + if validate and config_data: + validate_imageio_colorspace_in_config( + config_data["path"], colorspace_name) + + return colorspace_name + + +# TODO: remove this in future - backward compatibility +@deprecated("get_imageio_file_rules_colorspace_from_filepath") +def get_imageio_colorspace_from_filepath(*args, **kwargs): + return get_imageio_file_rules_colorspace_from_filepath(*args, **kwargs) + +# TODO: remove this in future - backward compatibility +@deprecated("get_imageio_file_rules_colorspace_from_filepath") +def get_colorspace_from_filepath(*args, **kwargs): + return get_imageio_file_rules_colorspace_from_filepath(*args, **kwargs) + + +def get_imageio_file_rules_colorspace_from_filepath( + filepath, host_name, project_name, + config_data=None, file_rules=None, + project_settings=None +): + """Get colorspace name from filepath + ImageIO Settings file rules are tested for matching rule. Args: - path (str): path string, file rule pattern is tested on it + filepath (str): path string, file rule pattern is tested on it host_name (str): host name project_name (str): project name - config_data (dict, optional): config path and template in dict. + config_data (Optional[dict]): config path and template in dict. Defaults to None. - file_rules (dict, optional): file rule data from settings. + file_rules (Optional[dict]): file rule data from settings. Defaults to None. - project_settings (dict, optional): project settings. Defaults to None. - validate (bool, optional): should resulting colorspace be validated - with config file? Defaults to True. + project_settings (Optional[dict]): project settings. Defaults to None. Returns: str: name of colorspace @@ -160,44 +225,19 @@ def get_imageio_colorspace_from_filepath( pattern = file_rule["pattern"] extension = file_rule["ext"] ext_match = re.match( - r".*(?=.{})".format(extension), path + r".*(?=.{})".format(extension), filepath ) file_match = re.search( - pattern, path + pattern, filepath ) if ext_match and file_match: colorspace_name = file_rule["colorspace"] - # if no file rule matched, try to get colorspace - # from filepath with OCIO v2 way - if ( - compatibility_check_config_version(config_data["path"], major=2) - and not colorspace_name - ): - colorspace_name = get_colorspace_from_filepath( - config_data["path"], path) - - # use parse colorspace from filepath as fallback - colorspace_name = colorspace_name or parse_colorspace_from_filepath( - path, config_path=config_data["path"] - ) - - if not colorspace_name: - log.info("No imageio file rule matched input path: '{}'".format( - path - )) - return None - - # validate matching colorspace with config - if validate and config_data: - validate_imageio_colorspace_in_config( - config_data["path"], colorspace_name) - return colorspace_name -def get_colorspace_from_filepath(config_path, filepath): +def get_config_file_rules_colorspace_from_filepath(config_path, filepath): """Get colorspace from file path wrapper. Wrapper function for getting colorspace from file path @@ -214,16 +254,16 @@ def get_colorspace_from_filepath(config_path, filepath): # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess result_data = _get_wrapped_with_subprocess( - "colorspace", "get_colorspace_from_filepath", + "colorspace", "get_config_file_rules_colorspace_from_filepath", config_path=config_path, filepath=filepath ) if result_data: return result_data[0] - from openpype.scripts.ocio_wrapper import _get_colorspace_from_filepath + from openpype.scripts.ocio_wrapper import _get_config_file_rules_colorspace_from_filepath - result_data = _get_colorspace_from_filepath(config_path, filepath) + result_data = _get_config_file_rules_colorspace_from_filepath(config_path, filepath) if result_data: return result_data[0] diff --git a/openpype/scripts/ocio_wrapper.py b/openpype/scripts/ocio_wrapper.py index 1feedde627a..1515cb4e40d 100644 --- a/openpype/scripts/ocio_wrapper.py +++ b/openpype/scripts/ocio_wrapper.py @@ -247,7 +247,7 @@ def _get_version_data(config_path): @colorspace.command( - name="get_colorspace_from_filepath", + name="get_config_file_rules_colorspace_from_filepath", help=( "return colorspace from filepath " "--config_path - ocio config file path (input arg is required) " @@ -264,7 +264,7 @@ def _get_version_data(config_path): @click.option("--out_path", required=True, help="path where to write output json file", type=click.Path()) -def get_colorspace_from_filepath(config_path, filepath, out_path): +def get_config_file_rules_colorspace_from_filepath(config_path, filepath, out_path): """Get colorspace from file path wrapper. Python 2 wrapped console command @@ -275,12 +275,12 @@ def get_colorspace_from_filepath(config_path, filepath, out_path): out_path (str): temp json file path string Example of use: - > pyton.exe ./ocio_wrapper.py colorspace get_colorspace_from_filepath \ + > pyton.exe ./ocio_wrapper.py colorspace get_config_file_rules_colorspace_from_filepath \ --config_path= --filepath= --out_path= """ json_path = Path(out_path) - colorspace = _get_colorspace_from_filepath(config_path, filepath) + colorspace = _get_config_file_rules_colorspace_from_filepath(config_path, filepath) with open(json_path, "w") as f_: json.dump(colorspace, f_) @@ -288,7 +288,7 @@ def get_colorspace_from_filepath(config_path, filepath, out_path): print(f"Colorspace name is saved to '{json_path}'") -def _get_colorspace_from_filepath(config_path, filepath): +def _get_config_file_rules_colorspace_from_filepath(config_path, filepath): """Return found colorspace data found in v2 file rules. Args: From f5d145ea23eb6b6463e1fea0ed07b4b1e0ffe934 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 31 Aug 2023 10:42:57 +0200 Subject: [PATCH 026/111] hound and todos --- openpype/pipeline/colorspace.py | 11 +++++++++-- openpype/scripts/ocio_wrapper.py | 10 +++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index f1acd18a708..e315633d416 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -261,9 +261,11 @@ def get_config_file_rules_colorspace_from_filepath(config_path, filepath): if result_data: return result_data[0] - from openpype.scripts.ocio_wrapper import _get_config_file_rules_colorspace_from_filepath + # TODO: refactor this so it is not imported but part of this file + from openpype.scripts.ocio_wrapper import _get_config_file_rules_colorspace_from_filepath # noqa: E501 - result_data = _get_config_file_rules_colorspace_from_filepath(config_path, filepath) + result_data = _get_config_file_rules_colorspace_from_filepath( + config_path, filepath) if result_data: return result_data[0] @@ -424,6 +426,7 @@ def _get_wrapped_with_subprocess(command_group, command, **kwargs): return json.load(f_) +# TODO: this should be part of ocio_wrapper.py def compatibility_check(): """Making sure PyOpenColorIO is importable""" if CachedData.python3compatible is not None: @@ -439,11 +442,13 @@ def compatibility_check(): return CachedData.python3compatible +# TODO: this should be part of ocio_wrapper.py def compatibility_check_config_version(config_path, major=1, minor=None): """Making sure PyOpenColorIO config version is compatible""" if not CachedData.config_version_data: if compatibility_check(): + # TODO: refactor this so it is not imported but part of this file from openpype.scripts.ocio_wrapper import _get_version_data CachedData.config_version_data = _get_version_data(config_path) @@ -488,6 +493,7 @@ def get_ocio_config_colorspaces(config_path): "config", "get_colorspace", in_path=config_path ) else: + # TODO: refactor this so it is not imported but part of this file from openpype.scripts.ocio_wrapper import _get_colorspace_data CachedData.ocio_config_colorspaces[config_path] = \ @@ -533,6 +539,7 @@ def get_ocio_config_views(config_path): "config", "get_views", in_path=config_path ) + # TODO: refactor this so it is not imported but part of this file from openpype.scripts.ocio_wrapper import _get_views_data return _get_views_data(config_path) diff --git a/openpype/scripts/ocio_wrapper.py b/openpype/scripts/ocio_wrapper.py index 1515cb4e40d..56399f10a22 100644 --- a/openpype/scripts/ocio_wrapper.py +++ b/openpype/scripts/ocio_wrapper.py @@ -264,7 +264,9 @@ def _get_version_data(config_path): @click.option("--out_path", required=True, help="path where to write output json file", type=click.Path()) -def get_config_file_rules_colorspace_from_filepath(config_path, filepath, out_path): +def get_config_file_rules_colorspace_from_filepath( + config_path, filepath, out_path +): """Get colorspace from file path wrapper. Python 2 wrapped console command @@ -275,12 +277,14 @@ def get_config_file_rules_colorspace_from_filepath(config_path, filepath, out_pa out_path (str): temp json file path string Example of use: - > pyton.exe ./ocio_wrapper.py colorspace get_config_file_rules_colorspace_from_filepath \ + > pyton.exe ./ocio_wrapper.py \ + colorspace get_config_file_rules_colorspace_from_filepath \ --config_path= --filepath= --out_path= """ json_path = Path(out_path) - colorspace = _get_config_file_rules_colorspace_from_filepath(config_path, filepath) + colorspace = _get_config_file_rules_colorspace_from_filepath( + config_path, filepath) with open(json_path, "w") as f_: json.dump(colorspace, f_) From abedafaff808a02088ef09ef748fa64e8604abaa Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 31 Aug 2023 10:56:56 +0200 Subject: [PATCH 027/111] fixing name of variable in tests --- tests/unit/openpype/pipeline/test_colorspace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/openpype/pipeline/test_colorspace.py b/tests/unit/openpype/pipeline/test_colorspace.py index 8ae98f7cf8f..338627098c7 100644 --- a/tests/unit/openpype/pipeline/test_colorspace.py +++ b/tests/unit/openpype/pipeline/test_colorspace.py @@ -227,7 +227,7 @@ def test_get_imageio_colorspace_from_filepath_python2mode( expected_hiero = "Gamma 2.2 Rec.709 - Texture" # switch to python 2 compatibility mode - colorspace.CashedData.python3compatible = False + colorspace.CachedData.python3compatible = False nuke_colorspace = colorspace.get_imageio_colorspace_from_filepath( nuke_filepath, @@ -248,7 +248,7 @@ def test_get_imageio_colorspace_from_filepath_python2mode( f"Not matching colorspace {expected_hiero}") # return to python 3 compatibility mode - colorspace.CashedData.python3compatible = None + colorspace.CachedData.python3compatible = None test_case = TestPipelineColorspace() From f7ce6406f94703dab8c47850c49dc6bd8c2d4920 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 31 Aug 2023 11:46:05 +0200 Subject: [PATCH 028/111] adding contextual settings method to be shared between two functions --- openpype/pipeline/colorspace.py | 52 +++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index e315633d416..a4901b7dfd4 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -138,6 +138,16 @@ def get_colorspace_name_from_filepath( Returns: str: name of colorspace """ + project_settings, config_data, file_rules = _get_context_settings( + host_name, project_name, + config_data=config_data, file_rules=file_rules, + project_settings=project_settings + ) + + if not config_data: + # in case global or host color management is not enabled + return None + # use ImageIO file rules colorspace_name = get_imageio_file_rules_colorspace_from_filepath( filepath, host_name, project_name, @@ -183,6 +193,28 @@ def get_colorspace_from_filepath(*args, **kwargs): return get_imageio_file_rules_colorspace_from_filepath(*args, **kwargs) +def _get_context_settings( + host_name, project_name, + config_data=None, file_rules=None, + project_settings=None +): + project_settings = project_settings or get_project_settings( + project_name + ) + + config_data = config_data or get_imageio_config( + project_name, host_name, project_settings) + + # in case host color management is not enabled + if not config_data: + return (None, None, None) + + file_rules = file_rules or get_imageio_file_rules( + project_name, host_name, project_settings) + + return project_settings, config_data, file_rules + + def get_imageio_file_rules_colorspace_from_filepath( filepath, host_name, project_name, config_data=None, file_rules=None, @@ -205,19 +237,15 @@ def get_imageio_file_rules_colorspace_from_filepath( Returns: str: name of colorspace """ - if not any([config_data, file_rules]): - project_settings = project_settings or get_project_settings( - project_name - ) - config_data = get_imageio_config( - project_name, host_name, project_settings) - - # in case host color management is not enabled - if not config_data: - return None + project_settings, config_data, file_rules = _get_context_settings( + host_name, project_name, + config_data=config_data, file_rules=file_rules, + project_settings=project_settings + ) - file_rules = get_imageio_file_rules( - project_name, host_name, project_settings) + if not config_data: + # in case global or host color management is not enabled + return None # match file rule from path colorspace_name = None From 1e6f855e7420a123539b37afa5cc5c580412c09c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 31 Aug 2023 11:46:48 +0200 Subject: [PATCH 029/111] fixing tests --- tests/unit/openpype/pipeline/test_colorspace.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/openpype/pipeline/test_colorspace.py b/tests/unit/openpype/pipeline/test_colorspace.py index 338627098c7..435ea709abc 100644 --- a/tests/unit/openpype/pipeline/test_colorspace.py +++ b/tests/unit/openpype/pipeline/test_colorspace.py @@ -196,7 +196,7 @@ def test_get_imageio_colorspace_from_filepath_p3(self, project_settings): expected_nuke = "Camera Rec.709" expected_hiero = "Gamma 2.2 Rec.709 - Texture" - nuke_colorspace = colorspace.get_imageio_colorspace_from_filepath( + nuke_colorspace = colorspace.get_colorspace_name_from_filepath( nuke_filepath, "nuke", "test_project", @@ -205,7 +205,7 @@ def test_get_imageio_colorspace_from_filepath_p3(self, project_settings): assert expected_nuke == nuke_colorspace, ( f"Not matching colorspace {expected_nuke}") - hiero_colorspace = colorspace.get_imageio_colorspace_from_filepath( + hiero_colorspace = colorspace.get_colorspace_name_from_filepath( hiero_filepath, "hiero", "test_project", @@ -229,7 +229,7 @@ def test_get_imageio_colorspace_from_filepath_python2mode( # switch to python 2 compatibility mode colorspace.CachedData.python3compatible = False - nuke_colorspace = colorspace.get_imageio_colorspace_from_filepath( + nuke_colorspace = colorspace.get_colorspace_name_from_filepath( nuke_filepath, "nuke", "test_project", @@ -238,7 +238,7 @@ def test_get_imageio_colorspace_from_filepath_python2mode( assert expected_nuke == nuke_colorspace, ( f"Not matching colorspace {expected_nuke}") - hiero_colorspace = colorspace.get_imageio_colorspace_from_filepath( + hiero_colorspace = colorspace.get_colorspace_name_from_filepath( hiero_filepath, "hiero", "test_project", From dbd03c6c5a2b851ca6a8708cf6e290fd498e5192 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 14 Sep 2023 08:38:51 +0100 Subject: [PATCH 030/111] Missing "data" field and enabling of audio --- openpype/hosts/maya/plugins/load/load_audio.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index 265b15f4ae1..eaaf81d8737 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -61,6 +61,14 @@ def update(self, container, representation): path = get_representation_path(representation) cmds.setAttr("{}.filename".format(audio_node), path, type="string") + + cmds.timeControl( + mel.eval("$tmpVar=$gPlayBackSlider"), + edit=True, + sound=audio_node, + displaySound=True + ) + cmds.setAttr( container["objectName"] + ".representation", str(representation["_id"]), @@ -76,7 +84,7 @@ def update(self, container, representation): project_name, version["parent"], fields=["parent"] ) asset = get_asset_by_id( - project_name, subset["parent"], fields=["parent"] + project_name, subset["parent"], fields=["parent", "data"] ) source_start = 1 - asset["data"]["frameStart"] From 4d22b6cf4b31465833bc239b16e46ec2b1d1f942 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 14 Sep 2023 11:53:06 +0100 Subject: [PATCH 031/111] Update openpype/hosts/maya/plugins/load/load_audio.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/load/load_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index eaaf81d8737..7114d92daa0 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -84,7 +84,7 @@ def update(self, container, representation): project_name, version["parent"], fields=["parent"] ) asset = get_asset_by_id( - project_name, subset["parent"], fields=["parent", "data"] + project_name, subset["parent"], fields=["parent", "data.frameStart", "data.frameEnd"] ) source_start = 1 - asset["data"]["frameStart"] From a029031b58cd95663e018d3e8dd38a23e8475cdb Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 14 Sep 2023 11:53:53 +0100 Subject: [PATCH 032/111] Update openpype/hosts/maya/plugins/load/load_audio.py --- openpype/hosts/maya/plugins/load/load_audio.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index 7114d92daa0..9c2fdfb6d39 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -84,7 +84,8 @@ def update(self, container, representation): project_name, version["parent"], fields=["parent"] ) asset = get_asset_by_id( - project_name, subset["parent"], fields=["parent", "data.frameStart", "data.frameEnd"] + project_name, subset["parent"], + fields=["parent", "data.frameStart", "data.frameEnd"] ) source_start = 1 - asset["data"]["frameStart"] From 887127828887d0642d1e77c2f92b7f3a441135a7 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 14 Sep 2023 11:54:14 +0100 Subject: [PATCH 033/111] Update openpype/hosts/maya/plugins/load/load_audio.py --- openpype/hosts/maya/plugins/load/load_audio.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index 9c2fdfb6d39..17c7d442ae2 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -84,7 +84,8 @@ def update(self, container, representation): project_name, version["parent"], fields=["parent"] ) asset = get_asset_by_id( - project_name, subset["parent"], + project_name, + subset["parent"], fields=["parent", "data.frameStart", "data.frameEnd"] ) From 3903071f21f2d2e7ec426d287fe1713c8cf7122b Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 14 Sep 2023 11:55:50 +0100 Subject: [PATCH 034/111] Check current sound on timeline --- .../hosts/maya/plugins/load/load_audio.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index 17c7d442ae2..6e2f2e89bc4 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -59,15 +59,23 @@ def update(self, container, representation): assert audio_nodes is not None, "Audio node not found." audio_node = audio_nodes[0] + current_sound = cmds.timeControl( + mel.eval("$tmpVar=$gPlayBackSlider"), + query=True, + sound=True + ) + activate_sound = current_sound == audio_node + path = get_representation_path(representation) cmds.setAttr("{}.filename".format(audio_node), path, type="string") - cmds.timeControl( - mel.eval("$tmpVar=$gPlayBackSlider"), - edit=True, - sound=audio_node, - displaySound=True - ) + if activate_sound: + cmds.timeControl( + mel.eval("$tmpVar=$gPlayBackSlider"), + edit=True, + sound=audio_node, + displaySound=True + ) cmds.setAttr( container["objectName"] + ".representation", From 39dad459d588486a5711cecf79419c24d99524ed Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 14 Sep 2023 15:16:28 +0100 Subject: [PATCH 035/111] Update openpype/hosts/maya/plugins/load/load_audio.py --- openpype/hosts/maya/plugins/load/load_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index 6e2f2e89bc4..fedb985e0bb 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -60,7 +60,7 @@ def update(self, container, representation): audio_node = audio_nodes[0] current_sound = cmds.timeControl( - mel.eval("$tmpVar=$gPlayBackSlider"), + mel.eval("$gPlayBackSlider=$gPlayBackSlider"), query=True, sound=True ) From 2c2aef60de3b9e66e3efb79cf2d01578dcf829df Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 14 Sep 2023 15:16:52 +0100 Subject: [PATCH 036/111] Update openpype/hosts/maya/plugins/load/load_audio.py --- openpype/hosts/maya/plugins/load/load_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index fedb985e0bb..7750d41e973 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -71,7 +71,7 @@ def update(self, container, representation): if activate_sound: cmds.timeControl( - mel.eval("$tmpVar=$gPlayBackSlider"), + mel.eval("$gPlayBackSlider=$gPlayBackSlider"), edit=True, sound=audio_node, displaySound=True From a4b1797b2abc2869d15f343f8f00894783fa5270 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 14 Sep 2023 15:18:13 +0100 Subject: [PATCH 037/111] tmpVar > gPlayBackSlider --- openpype/hosts/maya/plugins/load/load_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index 7750d41e973..d3a670398be 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -30,7 +30,7 @@ def load(self, context, name, namespace, data): file=context["representation"]["data"]["path"], offset=start_frame ) cmds.timeControl( - mel.eval("$tmpVar=$gPlayBackSlider"), + mel.eval("$gPlayBackSlider=$gPlayBackSlider"), edit=True, sound=sound_node, displaySound=True From 2c2b5a35057ed10d060f5fc645b9ed53f5e5560c Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 14 Sep 2023 15:39:00 +0100 Subject: [PATCH 038/111] Update openpype/hosts/maya/plugins/load/load_audio.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/load/load_audio.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index d3a670398be..2da5a6f1c22 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -70,6 +70,7 @@ def update(self, container, representation): cmds.setAttr("{}.filename".format(audio_node), path, type="string") if activate_sound: + # maya by default deactivates it from timeline on file change cmds.timeControl( mel.eval("$gPlayBackSlider=$gPlayBackSlider"), edit=True, From d38cf8258954523f7025015670bfe6b7130c6d08 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 18 Sep 2023 08:49:51 +0100 Subject: [PATCH 039/111] Fix file path fetching from context. --- openpype/hosts/maya/plugins/load/load_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index 2da5a6f1c22..2ea0b134d12 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -27,7 +27,7 @@ def load(self, context, name, namespace, data): start_frame = cmds.playbackOptions(query=True, min=True) sound_node = cmds.sound( - file=context["representation"]["data"]["path"], offset=start_frame + file=self.filepath_from_context(context), offset=start_frame ) cmds.timeControl( mel.eval("$gPlayBackSlider=$gPlayBackSlider"), From 7c1d81d62c267ad04c36f33c94ecb1aec3f569aa Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 18 Sep 2023 08:50:11 +0100 Subject: [PATCH 040/111] Improve loader label. --- openpype/hosts/maya/plugins/load/load_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index 2ea0b134d12..ecf98303d23 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -18,7 +18,7 @@ class AudioLoader(load.LoaderPlugin): """Specific loader of audio.""" families = ["audio"] - label = "Import audio" + label = "Load audio" representations = ["wav"] icon = "volume-up" color = "orange" From c03326f5d8d0c45427d5cb24187c390d2b8c5113 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 19 Sep 2023 19:19:40 +0800 Subject: [PATCH 041/111] Jakub's comment on the review plugin --- openpype/hosts/nuke/api/plugin.py | 5 + .../publish/extract_review_data_mov.py | 2 +- openpype/settings/ayon_settings.py | 21 ++- .../defaults/project_settings/nuke.json | 54 +++++++ .../schemas/schema_nuke_publish.json | 145 ++++++++++++++++++ .../nuke/server/settings/publish_plugins.py | 10 +- .../settings_project_global.md | 12 +- website/docs/pype2/admin_presets_plugins.md | 3 +- 8 files changed, 236 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index a0e1525cd06..adbe43e4815 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -21,6 +21,9 @@ CreatedInstance, get_current_task_name ) +from openpype.lib.transcoding import ( + VIDEO_EXTENSIONS +) from .lib import ( INSTANCE_DATA_KNOB, Knobby, @@ -801,6 +804,8 @@ def __init__(self, self.log.info("File info was set...") self.file = self.fhead + self.name + ".{}".format(self.ext) + if self.ext != VIDEO_EXTENSIONS: + self.file = os.path.basename(self.path_in) self.path = os.path.join( self.staging_dir, self.file).replace("\\", "/") diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py index 956d1a54a34..1568a2de9be 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py @@ -8,7 +8,7 @@ from openpype.hosts.nuke.api.lib import maintained_selection -class ExtractReviewDataMov(publish.Extractor): +class ExtractReviewDataBakingStreams(publish.Extractor): """Extracts movie and thumbnail with baked in luts must be run after extract_render_local.py diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index 9a4f0607e09..b7fcaa1216d 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -748,7 +748,19 @@ def _convert_nuke_project_settings(ayon_settings, output): ) new_review_data_outputs = {} - for item in ayon_publish["ExtractReviewDataMov"]["outputs"]: + outputs_settings = None + # just in case that the users having old presets in outputs setting + deprecrated_review_settings = ayon_publish["ExtractReviewDataMov"] + current_review_settings = ( + ayon_publish["ExtractReviewDataBakingStreams"] + ) + if deprecrated_review_settings["outputs"] == ( + current_review_settings["outputs"]): + outputs_settings = current_review_settings["outputs"] + else: + outputs_settings = deprecrated_review_settings["outputs"] + + for item in outputs_settings: item_filter = item["filter"] if "product_names" in item_filter: item_filter["subsets"] = item_filter.pop("product_names") @@ -767,7 +779,12 @@ def _convert_nuke_project_settings(ayon_settings, output): name = item.pop("name") new_review_data_outputs[name] = item - ayon_publish["ExtractReviewDataMov"]["outputs"] = new_review_data_outputs + + if deprecrated_review_settings["outputs"] == ( + current_review_settings["outputs"]): + current_review_settings["outputs"] = new_review_data_outputs + else: + deprecrated_review_settings["outputs"] = new_review_data_outputs collect_instance_data = ayon_publish["CollectInstanceData"] if "sync_workfile_version_on_product_types" in collect_instance_data: diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 7961e771130..fac78dbcd5c 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -501,6 +501,60 @@ } } }, + "ExtractReviewDataBakingStreams": { + "enabled": true, + "viewer_lut_raw": false, + "outputs": { + "baking": { + "filter": { + "task_types": [], + "families": [], + "subsets": [] + }, + "read_raw": false, + "viewer_process_override": "", + "bake_viewer_process": true, + "bake_viewer_input_process": true, + "reformat_nodes_config": { + "enabled": false, + "reposition_nodes": [ + { + "node_class": "Reformat", + "knobs": [ + { + "type": "text", + "name": "type", + "value": "to format" + }, + { + "type": "text", + "name": "format", + "value": "HD_1080" + }, + { + "type": "text", + "name": "filter", + "value": "Lanczos6" + }, + { + "type": "bool", + "name": "black_outside", + "value": true + }, + { + "type": "bool", + "name": "pbb", + "value": false + } + ] + } + ] + }, + "extension": "mov", + "add_custom_tags": [] + } + } + }, "ExtractSlateFrame": { "viewer_lut_raw": false, "key_value_mapping": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json index f006392bef7..0f366d55ba1 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json @@ -371,6 +371,151 @@ ] }, + { + "type": "label", + "label": "^ Settings and for ExtractReviewDataMov is deprecated and will be soon removed.
Please use ExtractReviewDataBakingStreams instead." + }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "ExtractReviewDataBakingStreams", + "label": "ExtractReviewDataBakingStreams", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "viewer_lut_raw", + "label": "Viewer LUT raw" + }, + { + "key": "outputs", + "label": "Output Definitions", + "type": "dict-modifiable", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "dict", + "collapsible": false, + "key": "filter", + "label": "Filtering", + "children": [ + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "subsets", + "label": "Subsets", + "type": "list", + "object_type": "text" + } + ] + }, + { + "type": "separator" + }, + { + "type": "boolean", + "key": "read_raw", + "label": "Read colorspace RAW", + "default": false + }, + { + "type": "text", + "key": "viewer_process_override", + "label": "Viewer Process colorspace profile override" + }, + { + "type": "boolean", + "key": "bake_viewer_process", + "label": "Bake Viewer Process" + }, + { + "type": "boolean", + "key": "bake_viewer_input_process", + "label": "Bake Viewer Input Process (LUTs)" + }, + { + "type": "separator" + }, + { + "key": "reformat_nodes_config", + "type": "dict", + "label": "Reformat Nodes", + "collapsible": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Reposition knobs supported only.
You can add multiple reformat nodes
and set their knobs. Order of reformat
nodes is important. First reformat node
will be applied first and last reformat
node will be applied last." + }, + { + "key": "reposition_nodes", + "type": "list", + "label": "Reposition nodes", + "object_type": { + "type": "dict", + "children": [ + { + "key": "node_class", + "label": "Node class", + "type": "text" + }, + { + "type": "schema_template", + "name": "template_nuke_knob_inputs", + "template_data": [ + { + "label": "Node knobs", + "key": "knobs" + } + ] + } + ] + } + } + ] + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "extension", + "label": "Write node file type" + }, + { + "key": "add_custom_tags", + "label": "Add custom tags", + "type": "list", + "object_type": "text" + } + ] + } + } + + ] + }, { "type": "dict", "collapsible": true, diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py index c78685534f3..423448219d6 100644 --- a/server_addon/nuke/server/settings/publish_plugins.py +++ b/server_addon/nuke/server/settings/publish_plugins.py @@ -165,7 +165,7 @@ class BakingStreamModel(BaseSettingsModel): title="Custom tags", default_factory=list) -class ExtractReviewDataMovModel(BaseSettingsModel): +class ExtractReviewBakingStreamsModel(BaseSettingsModel): enabled: bool = Field(title="Enabled") viewer_lut_raw: bool = Field(title="Viewer lut raw") outputs: list[BakingStreamModel] = Field( @@ -266,9 +266,9 @@ class PublishPuginsModel(BaseSettingsModel): title="Extract Review Data Lut", default_factory=ExtractReviewDataLutModel ) - ExtractReviewDataMov: ExtractReviewDataMovModel = Field( - title="Extract Review Data Mov", - default_factory=ExtractReviewDataMovModel + ExtractReviewDataBakingStreams: ExtractReviewBakingStreamsModel = Field( + title="Extract Review Data Baking Streams", + default_factory=ExtractReviewBakingStreamsModel ) ExtractSlateFrame: ExtractSlateFrameModel = Field( title="Extract Slate Frame", @@ -410,7 +410,7 @@ class PublishPuginsModel(BaseSettingsModel): "ExtractReviewDataLut": { "enabled": False }, - "ExtractReviewDataMov": { + "ExtractReviewDataBakingStreams": { "enabled": True, "viewer_lut_raw": False, "outputs": [ diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 5ddf247d98a..9092ccdcdf7 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -189,10 +189,10 @@ A profile may generate multiple outputs from a single input. Each output must de - Profile filtering defines which group of output definitions is used but output definitions may require more specific filters on their own. - They may filter by subset name (regex can be used) or publish families. Publish families are more complex as are based on knowing code base. - Filtering by custom tags -> this is used for targeting to output definitions from other extractors using settings (at this moment only Nuke bake extractor can target using custom tags). - - Nuke extractor settings path: `project_settings/nuke/publish/ExtractReviewDataMov/outputs/baking/add_custom_tags` + - Nuke extractor settings path: `project_settings/nuke/publish/ExtractReviewDataBakingStreams/outputs/baking/add_custom_tags` - Filtering by input length. Input may be video, sequence or single image. It is possible that `.mp4` should be created only when input is video or sequence and to create review `.png` when input is single frame. In some cases the output should be created even if it's single frame or multi frame input. - + ### Extract Burnin Plugin is responsible for adding burnins into review representations. @@ -226,13 +226,13 @@ A burnin profile may set multiple burnin outputs from one input. The burnin's na | **Bottom Centered** | Bottom center content. | str | "{username}" | | **Bottom Right** | Bottom right corner content. | str | "{frame_start}-{current_frame}-{frame_end}" | -Each burnin profile can be configured with additional family filtering and can -add additional tags to the burnin representation, these can be configured under +Each burnin profile can be configured with additional family filtering and can +add additional tags to the burnin representation, these can be configured under the profile's **Additional filtering** section. :::note Filename suffix -The filename suffix is appended to filename of the source representation. For -example, if the source representation has suffix **"h264"** and the burnin +The filename suffix is appended to filename of the source representation. For +example, if the source representation has suffix **"h264"** and the burnin suffix is **"client"** then the final suffix is **"h264_client"**. ::: diff --git a/website/docs/pype2/admin_presets_plugins.md b/website/docs/pype2/admin_presets_plugins.md index 6a057f4bb45..a869ead8196 100644 --- a/website/docs/pype2/admin_presets_plugins.md +++ b/website/docs/pype2/admin_presets_plugins.md @@ -534,8 +534,7 @@ Plugin responsible for generating thumbnails with colorspace controlled by Nuke. } ``` -### `ExtractReviewDataMov` - +### `ExtractReviewDataBakingStreams` `viewer_lut_raw` **true** will publish the baked mov file without any colorspace conversion. It will be baked with the workfile workspace. This can happen in case the Viewer input process uses baked screen space luts. #### baking with controlled colorspace From e0fba9713d37c4cf73210c2c37179f9403b2399a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 19 Sep 2023 19:22:52 +0800 Subject: [PATCH 042/111] hound --- openpype/settings/ayon_settings.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index b7fcaa1216d..0b72d267f72 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -755,7 +755,8 @@ def _convert_nuke_project_settings(ayon_settings, output): ayon_publish["ExtractReviewDataBakingStreams"] ) if deprecrated_review_settings["outputs"] == ( - current_review_settings["outputs"]): + current_review_settings["outputs"] + ): outputs_settings = current_review_settings["outputs"] else: outputs_settings = deprecrated_review_settings["outputs"] @@ -781,7 +782,8 @@ def _convert_nuke_project_settings(ayon_settings, output): new_review_data_outputs[name] = item if deprecrated_review_settings["outputs"] == ( - current_review_settings["outputs"]): + current_review_settings["outputs"] + ): current_review_settings["outputs"] = new_review_data_outputs else: deprecrated_review_settings["outputs"] = new_review_data_outputs From 5b2e9da304697aea73af8cac3616d895e4bdd0d0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Sep 2023 14:52:19 +0200 Subject: [PATCH 043/111] (nuke): fix set colorspace on writes --- openpype/hosts/nuke/api/lib.py | 48 +++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 41e6a27cef3..8626151beb7 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2320,23 +2320,51 @@ def set_writes_colorspace(self): # get data from avalon knob avalon_knob_data = read_avalon_data(node) + node_data = get_node_data(node, INSTANCE_DATA_KNOB) if avalon_knob_data.get("id") != "pyblish.avalon.instance": + + if ( + # backward compatibility + # TODO: remove this once old avalon data api will be removed + avalon_knob_data + and avalon_knob_data.get("id") != "pyblish.avalon.instance" + ): + continue + elif ( + node_data + and node_data.get("id") != "pyblish.avalon.instance" + ): continue - if "creator" not in avalon_knob_data: + if ( + # backward compatibility + # TODO: remove this once old avalon data api will be removed + avalon_knob_data + and "creator" not in avalon_knob_data + ): + continue + elif ( + node_data + and "creator_identifier" not in node_data + ): continue - # establish families - families = [avalon_knob_data["family"]] - if avalon_knob_data.get("families"): - families.append(avalon_knob_data.get("families")) - nuke_imageio_writes = get_imageio_node_setting( - node_class=avalon_knob_data["families"], - plugin_name=avalon_knob_data["creator"], - subset=avalon_knob_data["subset"] - ) + nuke_imageio_writes = None + if avalon_knob_data: + # establish families + families = [avalon_knob_data["family"]] + if avalon_knob_data.get("families"): + families.append(avalon_knob_data.get("families")) + + nuke_imageio_writes = get_imageio_node_setting( + node_class=avalon_knob_data["families"], + plugin_name=avalon_knob_data["creator"], + subset=avalon_knob_data["subset"] + ) + elif node_data: + nuke_imageio_writes = get_write_node_template_attr(node) log.debug("nuke_imageio_writes: `{}`".format(nuke_imageio_writes)) From 961013e9afd5074b768ee73fe0a10464bdb62cd0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Sep 2023 15:03:18 +0200 Subject: [PATCH 044/111] Nuke: adding print of name of node which is processed --- openpype/hosts/nuke/api/lib.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 8626151beb7..fb2b5d0f450 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2316,14 +2316,13 @@ def set_writes_colorspace(self): ''' Adds correct colorspace to write node dict ''' - for node in nuke.allNodes(filter="Group"): + for node in nuke.allNodes(filter="Group", group=self._root_node): + log.info("Setting colorspace to `{}`".format(node.name())) # get data from avalon knob avalon_knob_data = read_avalon_data(node) node_data = get_node_data(node, INSTANCE_DATA_KNOB) - if avalon_knob_data.get("id") != "pyblish.avalon.instance": - if ( # backward compatibility # TODO: remove this once old avalon data api will be removed @@ -2350,7 +2349,6 @@ def set_writes_colorspace(self): ): continue - nuke_imageio_writes = None if avalon_knob_data: # establish families From 3d7479b65ec4a5b862633536791ac0773d5cdd67 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Sep 2023 15:43:26 +0200 Subject: [PATCH 045/111] nuke: extract review data mov read node with expression --- openpype/hosts/nuke/api/plugin.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index a0e1525cd06..1e318e17cf2 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -869,6 +869,11 @@ def generate_mov(self, farm=False, **kwargs): r_node["origlast"].setValue(self.last_frame) r_node["colorspace"].setValue(self.write_colorspace) + # do not rely on defaults, set explicitly + # to be sure it is set correctly + r_node["frame_mode"].setValue("expression") + r_node["frame"].setValue("") + if read_raw: r_node["raw"].setValue(1) From b8e054a50cac66c4e671da0261093be68db555b2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Sep 2023 17:01:07 +0200 Subject: [PATCH 046/111] renaming variable to make more sense --- openpype/pipeline/colorspace.py | 12 ++++++------ tests/unit/openpype/pipeline/test_colorspace.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index ae16c136352..454c23a55e9 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -24,7 +24,7 @@ class CachedData: remapping = None - python3compatible = None + has_compatible_ocio_package = None config_version_data = None ocio_config_colorspaces = {} @@ -459,17 +459,17 @@ def _get_wrapped_with_subprocess(command_group, command, **kwargs): # TODO: this should be part of ocio_wrapper.py def compatibility_check(): """Making sure PyOpenColorIO is importable""" - if CachedData.python3compatible is not None: - return CachedData.python3compatible + if CachedData.has_compatible_ocio_package is not None: + return CachedData.has_compatible_ocio_package try: import PyOpenColorIO # noqa: F401 - CachedData.python3compatible = True + CachedData.has_compatible_ocio_package = True except ImportError: - CachedData.python3compatible = False + CachedData.has_compatible_ocio_package = False # compatible - return CachedData.python3compatible + return CachedData.has_compatible_ocio_package # TODO: this should be part of ocio_wrapper.py diff --git a/tests/unit/openpype/pipeline/test_colorspace.py b/tests/unit/openpype/pipeline/test_colorspace.py index 435ea709abc..493be786a3f 100644 --- a/tests/unit/openpype/pipeline/test_colorspace.py +++ b/tests/unit/openpype/pipeline/test_colorspace.py @@ -227,7 +227,7 @@ def test_get_imageio_colorspace_from_filepath_python2mode( expected_hiero = "Gamma 2.2 Rec.709 - Texture" # switch to python 2 compatibility mode - colorspace.CachedData.python3compatible = False + colorspace.CachedData.has_compatible_ocio_package = False nuke_colorspace = colorspace.get_colorspace_name_from_filepath( nuke_filepath, From da7ffb84fba2041f7415537e5031485f5835f8e8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Sep 2023 17:10:27 +0200 Subject: [PATCH 047/111] improving regex patter for underscored pattern --- openpype/pipeline/colorspace.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 454c23a55e9..677656c02f1 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -341,7 +341,7 @@ def _get_colorspace_match_regex(colorspaces): pattern = "|".join( # Allow to match spaces also as underscores because the # integrator replaces spaces with underscores in filenames - re.escape(colorspace).replace(r"\ ", r"[_ ]") for colorspace in + re.escape(colorspace) for colorspace in # Sort by longest first so the regex matches longer matches # over smaller matches, e.g. matching 'Output - sRGB' over 'sRGB' sorted(colorspaces, key=len, reverse=True) @@ -355,22 +355,20 @@ def _get_colorspace_match_regex(colorspaces): colorspace_name = None colorspaces = colorspaces or get_ocio_config_colorspaces(config_path) - underscored_colorspaces = { - key.replace(" ", "_"): key for key in colorspaces + underscored_colorspaces = list({ + key.replace(" ", "_") for key in colorspaces if " " in key - } + }) # match colorspace from filepath - regex_pattern = _get_colorspace_match_regex(colorspaces) + regex_pattern = _get_colorspace_match_regex( + colorspaces + underscored_colorspaces) match = regex_pattern.search(filepath) colorspace = match.group(0) if match else None if colorspace: colorspace_name = colorspace - if colorspace in underscored_colorspaces: - colorspace_name = underscored_colorspaces[colorspace] - if not colorspace_name: log.info("No matching colorspace in config '{}' for path: '{}'".format( config_path, filepath From 6e7cde73be7c3309deb78f10324d8779e354f5cc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Sep 2023 17:17:45 +0200 Subject: [PATCH 048/111] second part of previous commit --- openpype/pipeline/colorspace.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 677656c02f1..1bb46245375 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -353,29 +353,28 @@ def _get_colorspace_match_regex(colorspaces): "Must provide `config_path` if `colorspaces` is not provided." ) - colorspace_name = None colorspaces = colorspaces or get_ocio_config_colorspaces(config_path) - underscored_colorspaces = list({ - key.replace(" ", "_") for key in colorspaces + underscored_colorspaces = { + key.replace(" ", "_"): key for key in colorspaces if " " in key - }) + } # match colorspace from filepath regex_pattern = _get_colorspace_match_regex( - colorspaces + underscored_colorspaces) + colorspaces + underscored_colorspaces.keys()) match = regex_pattern.search(filepath) colorspace = match.group(0) if match else None if colorspace: - colorspace_name = colorspace + return colorspace - if not colorspace_name: - log.info("No matching colorspace in config '{}' for path: '{}'".format( - config_path, filepath - )) - return None + if colorspace in underscored_colorspaces: + return underscored_colorspaces[colorspace] - return colorspace_name + log.info("No matching colorspace in config '{}' for path: '{}'".format( + config_path, filepath + )) + return None def validate_imageio_colorspace_in_config(config_path, colorspace_name): From d045b8322304dbcbf86e63d7c767561c5f7cd800 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Sep 2023 17:20:57 +0200 Subject: [PATCH 049/111] reversing order for underscored first --- openpype/pipeline/colorspace.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 1bb46245375..4ece96cfff0 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -365,12 +365,12 @@ def _get_colorspace_match_regex(colorspaces): match = regex_pattern.search(filepath) colorspace = match.group(0) if match else None - if colorspace: - return colorspace - if colorspace in underscored_colorspaces: return underscored_colorspaces[colorspace] + if colorspace: + return colorspace + log.info("No matching colorspace in config '{}' for path: '{}'".format( config_path, filepath )) From 49cfebb1639389447d0e4973b0f1d42630beec7d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Sep 2023 17:23:34 +0200 Subject: [PATCH 050/111] log can be added as explicit arg --- openpype/pipeline/colorspace.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 4ece96cfff0..a1e86dbd641 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -442,11 +442,7 @@ def _get_wrapped_with_subprocess(command_group, command, **kwargs): log.info("Executing: {}".format(" ".join(args))) - process_kwargs = { - "logger": log - } - - run_openpype_process(*args, **process_kwargs) + run_openpype_process(*args, logger=log) # return all colorspaces with open(tmp_json_path, "r") as f_: From aefbc7ef47e46ff91cd85df045801ad7ad6349bd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Sep 2023 17:24:36 +0200 Subject: [PATCH 051/111] removing extra space --- openpype/pipeline/colorspace.py | 8 ++++---- tests/unit/openpype/pipeline/test_colorspace.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index a1e86dbd641..0cc2f35a49a 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -24,7 +24,7 @@ class CachedData: remapping = None - has_compatible_ocio_package = None + has_compatible_ocio_package = None config_version_data = None ocio_config_colorspaces = {} @@ -452,14 +452,14 @@ def _get_wrapped_with_subprocess(command_group, command, **kwargs): # TODO: this should be part of ocio_wrapper.py def compatibility_check(): """Making sure PyOpenColorIO is importable""" - if CachedData.has_compatible_ocio_package is not None: + if CachedData.has_compatible_ocio_package is not None: return CachedData.has_compatible_ocio_package try: import PyOpenColorIO # noqa: F401 - CachedData.has_compatible_ocio_package = True + CachedData.has_compatible_ocio_package = True except ImportError: - CachedData.has_compatible_ocio_package = False + CachedData.has_compatible_ocio_package = False # compatible return CachedData.has_compatible_ocio_package diff --git a/tests/unit/openpype/pipeline/test_colorspace.py b/tests/unit/openpype/pipeline/test_colorspace.py index 493be786a3f..85faa8ff5dc 100644 --- a/tests/unit/openpype/pipeline/test_colorspace.py +++ b/tests/unit/openpype/pipeline/test_colorspace.py @@ -227,7 +227,7 @@ def test_get_imageio_colorspace_from_filepath_python2mode( expected_hiero = "Gamma 2.2 Rec.709 - Texture" # switch to python 2 compatibility mode - colorspace.CachedData.has_compatible_ocio_package = False + colorspace.CachedData.has_compatible_ocio_package = False nuke_colorspace = colorspace.get_colorspace_name_from_filepath( nuke_filepath, From 89d3a3ae228c8ed4f8a1e9b7e5e6cea2c6b65539 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Sep 2023 17:28:27 +0200 Subject: [PATCH 052/111] caching per config path compatibility check --- openpype/pipeline/colorspace.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 0cc2f35a49a..446849b76ef 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -25,7 +25,7 @@ class CachedData: remapping = None has_compatible_ocio_package = None - config_version_data = None + config_version_data = {} ocio_config_colorspaces = {} @@ -469,26 +469,26 @@ def compatibility_check(): def compatibility_check_config_version(config_path, major=1, minor=None): """Making sure PyOpenColorIO config version is compatible""" - if not CachedData.config_version_data: + if not CachedData.config_version_data.get(config_path): if compatibility_check(): # TODO: refactor this so it is not imported but part of this file from openpype.scripts.ocio_wrapper import _get_version_data - CachedData.config_version_data = _get_version_data(config_path) + CachedData.config_version_data[config_path] = _get_version_data(config_path) else: # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess - CachedData.config_version_data = _get_wrapped_with_subprocess( + CachedData.config_version_data[config_path] = _get_wrapped_with_subprocess( "config", "get_version", config_path=config_path ) # check major version - if CachedData.config_version_data["major"] != major: + if CachedData.config_version_data[config_path]["major"] != major: return False # check minor version - if minor and CachedData.config_version_data["minor"] != minor: + if minor and CachedData.config_version_data[config_path]["minor"] != minor: return False # compatible From 2f89cadd8a82fb1590a2bd064c24ddc27e42e893 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Sep 2023 17:29:54 +0200 Subject: [PATCH 053/111] hound --- openpype/pipeline/colorspace.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 446849b76ef..2dd618a1f26 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -474,14 +474,16 @@ def compatibility_check_config_version(config_path, major=1, minor=None): # TODO: refactor this so it is not imported but part of this file from openpype.scripts.ocio_wrapper import _get_version_data - CachedData.config_version_data[config_path] = _get_version_data(config_path) + CachedData.config_version_data[config_path] = \ + _get_version_data(config_path) else: # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess - CachedData.config_version_data[config_path] = _get_wrapped_with_subprocess( - "config", "get_version", config_path=config_path - ) + CachedData.config_version_data[config_path] = \ + _get_wrapped_with_subprocess( + "config", "get_version", config_path=config_path + ) # check major version if CachedData.config_version_data[config_path]["major"] != major: From da1d62f8931d38b2cae119e655575b67db69dba7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Sep 2023 17:31:21 +0200 Subject: [PATCH 054/111] hound --- openpype/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 2dd618a1f26..44cff34c67c 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -483,7 +483,7 @@ def compatibility_check_config_version(config_path, major=1, minor=None): CachedData.config_version_data[config_path] = \ _get_wrapped_with_subprocess( "config", "get_version", config_path=config_path - ) + ) # check major version if CachedData.config_version_data[config_path]["major"] != major: From 174ef45b0b068ccfc9382a254af64ea0d4c5b009 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 22 Sep 2023 15:07:58 +0800 Subject: [PATCH 055/111] jakub's comment on apply_settings and fix the bug of not being extracted the review --- openpype/hosts/nuke/api/plugin.py | 11 ++- ...ov.py => extract_review_baking_streams.py} | 30 +++++++- .../nuke/server/settings/publish_plugins.py | 71 +++++++++++++++++++ server_addon/nuke/server/version.py | 2 +- 4 files changed, 109 insertions(+), 5 deletions(-) rename openpype/hosts/nuke/plugins/publish/{extract_review_data_mov.py => extract_review_baking_streams.py} (82%) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index adbe43e4815..a814615164c 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -804,8 +804,13 @@ def __init__(self, self.log.info("File info was set...") self.file = self.fhead + self.name + ".{}".format(self.ext) - if self.ext != VIDEO_EXTENSIONS: - self.file = os.path.basename(self.path_in) + if ".{}".format(self.ext) not in VIDEO_EXTENSIONS: + filename = os.path.basename(self.path_in) + self.file = filename + if ".{}".format(self.ext) not in self.file: + wrg_ext = filename.split(".")[-1] + self.file = filename.replace(wrg_ext, self.ext) + self.path = os.path.join( self.staging_dir, self.file).replace("\\", "/") @@ -926,7 +931,7 @@ def generate_mov(self, farm=False, **kwargs): self.log.debug("Path: {}".format(self.path)) write_node["file"].setValue(str(self.path)) write_node["file_type"].setValue(str(self.ext)) - + self.log.debug("{0}".format(self.ext)) # Knobs `meta_codec` and `mov64_codec` are not available on centos. # TODO shouldn't this come from settings on outputs? try: diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py b/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py similarity index 82% rename from openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py rename to openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py index 1568a2de9be..59a3f659c9b 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py @@ -16,7 +16,7 @@ class ExtractReviewDataBakingStreams(publish.Extractor): """ order = pyblish.api.ExtractorOrder + 0.01 - label = "Extract Review Data Mov" + label = "Extract Review Data Baking Streams" families = ["review"] hosts = ["nuke"] @@ -25,6 +25,34 @@ class ExtractReviewDataBakingStreams(publish.Extractor): viewer_lut_raw = None outputs = {} + @classmethod + def apply_settings(cls, project_settings): + """just in case there are some old presets + in deprecrated ExtractReviewDataMov Plugins + """ + nuke_publish = project_settings["nuke"]["publish"] + deprecrated_review_settings = nuke_publish["ExtractReviewDataMov"] + current_review_settings = ( + nuke_publish["ExtractReviewDataBakingStreams"] + ) + if deprecrated_review_settings["viewer_lut_raw"] == ( + current_review_settings["viewer_lut_raw"] + ): + cls.viewer_lut_raw = ( + current_review_settings["viewer_lut_raw"] + ) + else: + cls.viewer_lut_raw = ( + deprecrated_review_settings["viewer_lut_raw"] + ) + + if deprecrated_review_settings["outputs"] == ( + current_review_settings["outputs"] + ): + cls.outputs = current_review_settings["outputs"] + else: + cls.outputs = deprecrated_review_settings["outputs"] + def process(self, instance): families = set(instance.data["families"]) diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py index 423448219d6..6459dd7225d 100644 --- a/server_addon/nuke/server/settings/publish_plugins.py +++ b/server_addon/nuke/server/settings/publish_plugins.py @@ -165,6 +165,18 @@ class BakingStreamModel(BaseSettingsModel): title="Custom tags", default_factory=list) +class ExtractReviewDataMovModel(BaseSettingsModel): + """[deprecated] use Extract Review Data Baking + Streams instead. + """ + enabled: bool = Field(title="Enabled") + viewer_lut_raw: bool = Field(title="Viewer lut raw") + outputs: list[BakingStreamModel] = Field( + default_factory=list, + title="Baking streams" + ) + + class ExtractReviewBakingStreamsModel(BaseSettingsModel): enabled: bool = Field(title="Enabled") viewer_lut_raw: bool = Field(title="Viewer lut raw") @@ -266,6 +278,10 @@ class PublishPuginsModel(BaseSettingsModel): title="Extract Review Data Lut", default_factory=ExtractReviewDataLutModel ) + ExtractReviewDataMov: ExtractReviewDataMovModel = Field( + title="Extract Review Data Mov", + default_factory=ExtractReviewDataMovModel + ) ExtractReviewDataBakingStreams: ExtractReviewBakingStreamsModel = Field( title="Extract Review Data Baking Streams", default_factory=ExtractReviewBakingStreamsModel @@ -410,6 +426,61 @@ class PublishPuginsModel(BaseSettingsModel): "ExtractReviewDataLut": { "enabled": False }, + "ExtractReviewDataMov": { + "enabled": True, + "viewer_lut_raw": False, + "outputs": [ + { + "name": "baking", + "filter": { + "task_types": [], + "product_types": [], + "product_names": [] + }, + "read_raw": False, + "viewer_process_override": "", + "bake_viewer_process": True, + "bake_viewer_input_process": True, + "reformat_nodes_config": { + "enabled": False, + "reposition_nodes": [ + { + "node_class": "Reformat", + "knobs": [ + { + "type": "text", + "name": "type", + "text": "to format" + }, + { + "type": "text", + "name": "format", + "text": "HD_1080" + }, + { + "type": "text", + "name": "filter", + "text": "Lanczos6" + }, + { + "type": "bool", + "name": "black_outside", + "boolean": True + }, + { + "type": "bool", + "name": "pbb", + "boolean": False + } + ] + } + ] + }, + "extension": "mov", + "add_custom_tags": [] + } + ] + }, "ExtractReviewDataBakingStreams": { "enabled": True, "viewer_lut_raw": False, diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py index b3f4756216d..ae7362549b3 100644 --- a/server_addon/nuke/server/version.py +++ b/server_addon/nuke/server/version.py @@ -1 +1 @@ -__version__ = "0.1.2" +__version__ = "0.1.3" From dd2255f8fd13919d8e3d2d2df02652a515f00a57 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 22 Sep 2023 20:39:33 +0800 Subject: [PATCH 056/111] jakub's comment --- openpype/hosts/nuke/api/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index a814615164c..fe7b52cd8a2 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -808,8 +808,8 @@ def __init__(self, filename = os.path.basename(self.path_in) self.file = filename if ".{}".format(self.ext) not in self.file: - wrg_ext = filename.split(".")[-1] - self.file = filename.replace(wrg_ext, self.ext) + original_ext = filename.split(".")[-1] + self.file = filename.replace(original_ext, self.ext) self.path = os.path.join( self.staging_dir, self.file).replace("\\", "/") From d744a486d64134455ffc636fef92ce09c9742b5f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 22 Sep 2023 21:38:30 +0800 Subject: [PATCH 057/111] edit the settings where deprecated_setting used when it enabled; current_setting adopted when deprecated_setting diabled in extract_reiew_baking_streams --- .../publish/extract_review_baking_streams.py | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py b/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py index 59a3f659c9b..d9ae673c2cc 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py @@ -28,30 +28,18 @@ class ExtractReviewDataBakingStreams(publish.Extractor): @classmethod def apply_settings(cls, project_settings): """just in case there are some old presets - in deprecrated ExtractReviewDataMov Plugins + in deprecated ExtractReviewDataMov Plugins """ nuke_publish = project_settings["nuke"]["publish"] - deprecrated_review_settings = nuke_publish["ExtractReviewDataMov"] - current_review_settings = ( - nuke_publish["ExtractReviewDataBakingStreams"] - ) - if deprecrated_review_settings["viewer_lut_raw"] == ( - current_review_settings["viewer_lut_raw"] - ): - cls.viewer_lut_raw = ( - current_review_settings["viewer_lut_raw"] - ) + deprecated_setting = nuke_publish["ExtractReviewDataMov"] + current_setting = nuke_publish["ExtractReviewDataBakingStreams"] + if not deprecated_setting["enabled"]: + if current_setting["enabled"]: + cls.viewer_lut_raw = current_setting["viewer_lut_raw"] + cls.outputs = current_setting["outputs"] else: - cls.viewer_lut_raw = ( - deprecrated_review_settings["viewer_lut_raw"] - ) - - if deprecrated_review_settings["outputs"] == ( - current_review_settings["outputs"] - ): - cls.outputs = current_review_settings["outputs"] - else: - cls.outputs = deprecrated_review_settings["outputs"] + cls.viewer_lut_raw = deprecated_setting["viewer_lut_raw"] + cls.outputs = deprecated_setting["outputs"] def process(self, instance): families = set(instance.data["families"]) From d498afbf489eefbe3a2733a17d6f8ebe988abf79 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 22 Sep 2023 15:24:17 +0100 Subject: [PATCH 058/111] New family ue_yeticache, new creator and extractor --- .../plugins/create/create_unreal_yeticache.py | 39 ++++++++++++ .../plugins/publish/collect_yeti_cache.py | 2 +- .../publish/extract_unreal_yeticache.py | 62 +++++++++++++++++++ .../plugins/publish/collect_resources_path.py | 3 +- openpype/plugins/publish/integrate.py | 3 +- 5 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 openpype/hosts/maya/plugins/create/create_unreal_yeticache.py create mode 100644 openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py diff --git a/openpype/hosts/maya/plugins/create/create_unreal_yeticache.py b/openpype/hosts/maya/plugins/create/create_unreal_yeticache.py new file mode 100644 index 00000000000..8ff3dccea25 --- /dev/null +++ b/openpype/hosts/maya/plugins/create/create_unreal_yeticache.py @@ -0,0 +1,39 @@ +from openpype.hosts.maya.api import ( + lib, + plugin +) +from openpype.lib import NumberDef + + +class CreateYetiCache(plugin.MayaCreator): + """Output for procedural plugin nodes of Yeti """ + + identifier = "io.openpype.creators.maya.unrealyeticache" + label = "Unreal Yeti Cache" + family = "ue_yeticache" + icon = "pagelines" + + def get_instance_attr_defs(self): + + defs = [ + NumberDef("preroll", + label="Preroll", + minimum=0, + default=0, + decimals=0) + ] + + # Add animation data without step and handles + defs.extend(lib.collect_animation_defs()) + remove = {"step", "handleStart", "handleEnd"} + defs = [attr_def for attr_def in defs if attr_def.key not in remove] + + # Add samples after frame range + defs.append( + NumberDef("samples", + label="Samples", + default=3, + decimals=0) + ) + + return defs diff --git a/openpype/hosts/maya/plugins/publish/collect_yeti_cache.py b/openpype/hosts/maya/plugins/publish/collect_yeti_cache.py index 4dcda290502..426e330f896 100644 --- a/openpype/hosts/maya/plugins/publish/collect_yeti_cache.py +++ b/openpype/hosts/maya/plugins/publish/collect_yeti_cache.py @@ -39,7 +39,7 @@ class CollectYetiCache(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.45 label = "Collect Yeti Cache" - families = ["yetiRig", "yeticache"] + families = ["yetiRig", "yeticache", "ue_yeticache"] hosts = ["maya"] def process(self, instance): diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py b/openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py new file mode 100644 index 00000000000..2279d521110 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py @@ -0,0 +1,62 @@ +import os +import json + +from maya import cmds + +from openpype.pipeline import publish + + +class ExtractYetiCache(publish.Extractor): + """Producing Yeti cache files using scene time range. + + This will extract Yeti cache file sequence and fur settings. + """ + + label = "Extract Yeti Cache" + hosts = ["maya"] + families = ["ue_yeticache"] + + def process(self, instance): + + yeti_nodes = cmds.ls(instance, type="pgYetiMaya") + if not yeti_nodes: + raise RuntimeError("No pgYetiMaya nodes found in the instance") + + # Define extract output file path + dirname = self.staging_dir(instance) + + # Collect information for writing cache + start_frame = instance.data["frameStartHandle"] + end_frame = instance.data["frameEndHandle"] + preroll = instance.data["preroll"] + if preroll > 0: + start_frame -= preroll + + kwargs = {} + samples = instance.data.get("samples", 0) + if samples == 0: + kwargs.update({"sampleTimes": "0.0 1.0"}) + else: + kwargs.update({"samples": samples}) + + self.log.debug(f"Writing out cache {start_frame} - {end_frame}") + filename = "{0}.abc".format(instance.name) + path = os.path.join(dirname, filename) + cmds.pgYetiCommand(yeti_nodes, + writeAlembic=path, + range=(start_frame, end_frame), + asUnrealAbc=True, + **kwargs) + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': 'abc', + 'ext': 'abc', + 'files': filename, + 'stagingDir': dirname + } + instance.data["representations"].append(representation) + + self.log.debug(f"Extracted {instance} to {dirname}") diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py index f96dd0ae181..6840509f794 100644 --- a/openpype/plugins/publish/collect_resources_path.py +++ b/openpype/plugins/publish/collect_resources_path.py @@ -62,7 +62,8 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): "effect", "staticMesh", "skeletalMesh", - "xgen" + "xgen", + "ue_yeticache" ] def process(self, instance): diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 7e48155b9e4..24fbc0d8e71 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -139,7 +139,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "simpleUnrealTexture", "online", "uasset", - "blendScene" + "blendScene", + "ue_yeticache" ] default_template_name = "publish" From 9781104f4e69f0d557fc4ec7e32afa8d26583e11 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 22 Sep 2023 15:49:38 +0100 Subject: [PATCH 059/111] Implemented Unreal loader --- .../unreal/plugins/load/load_yeticache.py | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 openpype/hosts/unreal/plugins/load/load_yeticache.py diff --git a/openpype/hosts/unreal/plugins/load/load_yeticache.py b/openpype/hosts/unreal/plugins/load/load_yeticache.py new file mode 100644 index 00000000000..59dbdbee31a --- /dev/null +++ b/openpype/hosts/unreal/plugins/load/load_yeticache.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- +"""Loader for Yeti Cache.""" +import os + +from openpype.pipeline import ( + get_representation_path, + AYON_CONTAINER_ID +) +from openpype.hosts.unreal.api import plugin +from openpype.hosts.unreal.api import pipeline as unreal_pipeline +import unreal # noqa + + +class YetiLoader(plugin.Loader): + """Load Yeti Cache""" + + families = ["ue_yeticache"] + label = "Import Yeti" + representations = ["abc"] + icon = "pagelines" + color = "orange" + + @staticmethod + def get_task(filename, asset_dir, asset_name, replace): + task = unreal.AssetImportTask() + options = unreal.AbcImportSettings() + + task.set_editor_property('filename', filename) + task.set_editor_property('destination_path', asset_dir) + task.set_editor_property('destination_name', asset_name) + task.set_editor_property('replace_existing', replace) + task.set_editor_property('automated', True) + task.set_editor_property('save', True) + + task.options = options + + return task + + def load(self, context, name, namespace, options): + """Load and containerise representation into Content Browser. + + This is two step process. First, import FBX to temporary path and + then call `containerise()` on it - this moves all content to new + directory and then it will create AssetContainer there and imprint it + with metadata. This will mark this path as container. + + Args: + context (dict): application context + name (str): subset name + namespace (str): in Unreal this is basically path to container. + This is not passed here, so namespace is set + by `containerise()` because only then we know + real path. + data (dict): Those would be data to be imprinted. This is not used + now, data are imprinted by `containerise()`. + + Returns: + list(str): list of container content + + """ + # Create directory for asset and Ayon container + root = "/Game/Ayon/Assets" + asset = context.get('asset').get('name') + suffix = "_CON" + asset_name = f"{asset}_{name}" if asset else f"{name}" + version = context.get('version') + # Check if version is hero version and use different name + if not version.get("name") and version.get('type') == "hero_version": + name_version = f"{name}_hero" + else: + name_version = f"{name}_v{version.get('name'):03d}" + + tools = unreal.AssetToolsHelpers().get_asset_tools() + asset_dir, container_name = tools.create_unique_asset_name( + f"{root}/{asset}/{name_version}", suffix="") + + container_name += suffix + + if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir): + unreal.EditorAssetLibrary.make_directory(asset_dir) + + path = self.filepath_from_context(context) + task = self.get_task(path, asset_dir, asset_name, False) + + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) # noqa: E501 + + # Create Asset Container + unreal_pipeline.create_container( + container=container_name, path=asset_dir) + + data = { + "schema": "ayon:container-2.0", + "id": AYON_CONTAINER_ID, + "asset": asset, + "namespace": asset_dir, + "container_name": container_name, + "asset_name": asset_name, + "loader": str(self.__class__.__name__), + "representation": context["representation"]["_id"], + "parent": context["representation"]["parent"], + "family": context["representation"]["context"]["family"] + } + unreal_pipeline.imprint(f"{asset_dir}/{container_name}", data) + + asset_content = unreal.EditorAssetLibrary.list_assets( + asset_dir, recursive=True, include_folder=True + ) + + for a in asset_content: + unreal.EditorAssetLibrary.save_asset(a) + + return asset_content + + def update(self, container, representation): + name = container["asset_name"] + source_path = get_representation_path(representation) + destination_path = container["namespace"] + + task = self.get_task(source_path, destination_path, name, True) + + # do import fbx and replace existing data + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + + container_path = f'{container["namespace"]}/{container["objectName"]}' + # update metadata + unreal_pipeline.imprint( + container_path, + { + "representation": str(representation["_id"]), + "parent": str(representation["parent"]) + }) + + asset_content = unreal.EditorAssetLibrary.list_assets( + destination_path, recursive=True, include_folder=True + ) + + for a in asset_content: + unreal.EditorAssetLibrary.save_asset(a) + + def remove(self, container): + path = container["namespace"] + parent_path = os.path.dirname(path) + + unreal.EditorAssetLibrary.delete_directory(path) + + asset_content = unreal.EditorAssetLibrary.list_assets( + parent_path, recursive=False + ) + + if len(asset_content) == 0: + unreal.EditorAssetLibrary.delete_directory(parent_path) From 41b207d657f8c4da054248791a946f0c86657000 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 22 Sep 2023 15:58:27 +0100 Subject: [PATCH 060/111] Hound fixes --- openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py b/openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py index 2279d521110..816e125c330 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py @@ -1,5 +1,4 @@ import os -import json from maya import cmds From 21d547a085c1b06fc9f26829920b24dd0068d01b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sun, 24 Sep 2023 12:49:30 +0800 Subject: [PATCH 061/111] introduce the function for checking the filename to see if it consists of the frame hashes element --- openpype/hosts/nuke/api/lib.py | 18 ++++++++++++++++++ openpype/hosts/nuke/api/plugin.py | 9 +++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 41e6a27cef3..ed517b472c0 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3397,3 +3397,21 @@ def create_viewer_profile_string(viewer, display=None, path_like=False): if path_like: return "{}/{}".format(display, viewer) return "{} ({})".format(viewer, display) + +def get_file_with_name_and_hashes(original_path, name): + """Function to get the ranmed filename with frame hashes + + Args: + original_path (str): the filename with frame hashes + name (str): the name of the tags + + Returns: + filename: the renamed filename with the tag + """ + filename = os.path.basename(original_path) + fhead = filename.split(".")[0] + if "#" in fhead: + fhead = fhead.replace("#", "")[:-1] + new_fhead = "{}.{}".format(fhead, name) + filename = filename.replace(fhead, new_fhead) + return filename diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index fe7b52cd8a2..b7927738d6d 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -38,7 +38,8 @@ get_node_data, get_view_process_node, get_viewer_config_from_string, - deprecated + deprecated, + get_file_with_name_and_hashes ) from .pipeline import ( list_instances, @@ -805,11 +806,12 @@ def __init__(self, self.file = self.fhead + self.name + ".{}".format(self.ext) if ".{}".format(self.ext) not in VIDEO_EXTENSIONS: - filename = os.path.basename(self.path_in) + filename = get_file_with_name_and_hashes( + self.path_in, self.name) self.file = filename if ".{}".format(self.ext) not in self.file: original_ext = filename.split(".")[-1] - self.file = filename.replace(original_ext, self.ext) + self.file = filename.replace(original_ext, ext) self.path = os.path.join( self.staging_dir, self.file).replace("\\", "/") @@ -931,7 +933,6 @@ def generate_mov(self, farm=False, **kwargs): self.log.debug("Path: {}".format(self.path)) write_node["file"].setValue(str(self.path)) write_node["file_type"].setValue(str(self.ext)) - self.log.debug("{0}".format(self.ext)) # Knobs `meta_codec` and `mov64_codec` are not available on centos. # TODO shouldn't this come from settings on outputs? try: From 6f858a80ca4839bb581685d48d4d5d6304ac5403 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sun, 24 Sep 2023 12:50:42 +0800 Subject: [PATCH 062/111] hound --- openpype/hosts/nuke/api/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index ed517b472c0..2c5838ffd3e 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3398,6 +3398,7 @@ def create_viewer_profile_string(viewer, display=None, path_like=False): return "{}/{}".format(display, viewer) return "{} ({})".format(viewer, display) + def get_file_with_name_and_hashes(original_path, name): """Function to get the ranmed filename with frame hashes From 5f7f4f08d1cf1ac38c4016e3c4d4bb2211b73767 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 25 Sep 2023 14:23:17 +0800 Subject: [PATCH 063/111] fix the slate in --- openpype/hosts/nuke/api/plugin.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index b7927738d6d..f587d109c14 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -591,6 +591,11 @@ def get_file_info(self): # get first and last frame self.first_frame = min(self.collection.indexes) self.last_frame = max(self.collection.indexes) + + first = self.instance.data.get("frameStartHandle", None) + if first: + if first > self.first_frame: + self.first_frame = first else: self.fname = os.path.basename(self.path_in) self.fhead = os.path.splitext(self.fname)[0] + "." From 4c854600cbbc3a7d4d1475893ab3f900d2c92605 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 25 Sep 2023 10:07:37 +0100 Subject: [PATCH 064/111] Renamed family to yeticacheUE --- openpype/hosts/maya/plugins/create/create_unreal_yeticache.py | 2 +- openpype/hosts/maya/plugins/publish/collect_yeti_cache.py | 2 +- openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py | 2 +- openpype/hosts/unreal/plugins/load/load_yeticache.py | 2 +- openpype/plugins/publish/collect_resources_path.py | 2 +- openpype/plugins/publish/integrate.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_unreal_yeticache.py b/openpype/hosts/maya/plugins/create/create_unreal_yeticache.py index 8ff3dccea25..defa6ed6d97 100644 --- a/openpype/hosts/maya/plugins/create/create_unreal_yeticache.py +++ b/openpype/hosts/maya/plugins/create/create_unreal_yeticache.py @@ -10,7 +10,7 @@ class CreateYetiCache(plugin.MayaCreator): identifier = "io.openpype.creators.maya.unrealyeticache" label = "Unreal Yeti Cache" - family = "ue_yeticache" + family = "yeticacheUE" icon = "pagelines" def get_instance_attr_defs(self): diff --git a/openpype/hosts/maya/plugins/publish/collect_yeti_cache.py b/openpype/hosts/maya/plugins/publish/collect_yeti_cache.py index 426e330f896..7a1516997a7 100644 --- a/openpype/hosts/maya/plugins/publish/collect_yeti_cache.py +++ b/openpype/hosts/maya/plugins/publish/collect_yeti_cache.py @@ -39,7 +39,7 @@ class CollectYetiCache(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.45 label = "Collect Yeti Cache" - families = ["yetiRig", "yeticache", "ue_yeticache"] + families = ["yetiRig", "yeticache", "yeticacheUE"] hosts = ["maya"] def process(self, instance): diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py b/openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py index 816e125c330..e72146a8713 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py @@ -13,7 +13,7 @@ class ExtractYetiCache(publish.Extractor): label = "Extract Yeti Cache" hosts = ["maya"] - families = ["ue_yeticache"] + families = ["yeticacheUE"] def process(self, instance): diff --git a/openpype/hosts/unreal/plugins/load/load_yeticache.py b/openpype/hosts/unreal/plugins/load/load_yeticache.py index 59dbdbee31a..328f92d0205 100644 --- a/openpype/hosts/unreal/plugins/load/load_yeticache.py +++ b/openpype/hosts/unreal/plugins/load/load_yeticache.py @@ -14,7 +14,7 @@ class YetiLoader(plugin.Loader): """Load Yeti Cache""" - families = ["ue_yeticache"] + families = ["yeticacheUE"] label = "Import Yeti" representations = ["abc"] icon = "pagelines" diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py index 6840509f794..57a612c5ae9 100644 --- a/openpype/plugins/publish/collect_resources_path.py +++ b/openpype/plugins/publish/collect_resources_path.py @@ -63,7 +63,7 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): "staticMesh", "skeletalMesh", "xgen", - "ue_yeticache" + "yeticacheUE" ] def process(self, instance): diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 24fbc0d8e71..ce24dad1b5c 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -140,7 +140,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "online", "uasset", "blendScene", - "ue_yeticache" + "yeticacheUE" ] default_template_name = "publish" From 51875f592e95a30f84563835fa63d7b4c31e3dc9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 25 Sep 2023 11:21:02 +0200 Subject: [PATCH 065/111] instance data keys should not be optional --- openpype/hosts/nuke/api/plugin.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 1e318e17cf2..4ce314f6fb1 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -580,8 +580,9 @@ def __init__(self, def get_file_info(self): if self.collection: # get path - self.fname = os.path.basename(self.collection.format( - "{head}{padding}{tail}")) + self.fname = os.path.basename( + self.collection.format("{head}{padding}{tail}") + ) self.fhead = self.collection.format("{head}") # get first and last frame @@ -590,8 +591,8 @@ def get_file_info(self): else: self.fname = os.path.basename(self.path_in) self.fhead = os.path.splitext(self.fname)[0] + "." - self.first_frame = self.instance.data.get("frameStartHandle", None) - self.last_frame = self.instance.data.get("frameEndHandle", None) + self.first_frame = self.instance.data["frameStartHandle"] + self.last_frame = self.instance.data["frameEndHandle"] if "#" in self.fhead: self.fhead = self.fhead.replace("#", "")[:-1] From 4289997553340bccb7bb682d6c14ac4b51ca6939 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 25 Sep 2023 11:21:28 +0200 Subject: [PATCH 066/111] slate frame exception - in case it already exists --- openpype/hosts/nuke/api/plugin.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 4ce314f6fb1..6d5d7eddf13 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -588,6 +588,12 @@ def get_file_info(self): # get first and last frame self.first_frame = min(self.collection.indexes) self.last_frame = max(self.collection.indexes) + + # make sure slate frame is not included + frame_start_handle = self.instance.data["frameStartHandle"] + if frame_start_handle > self.first_frame: + self.first_frame = frame_start_handle + else: self.fname = os.path.basename(self.path_in) self.fhead = os.path.splitext(self.fname)[0] + "." From 50bae2d049092f7f43e9ef619f1b65ee0c14db25 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 25 Sep 2023 10:34:53 +0100 Subject: [PATCH 067/111] Check if Groom plugin is active when loading in Unreal --- .../unreal/plugins/load/load_yeticache.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/openpype/hosts/unreal/plugins/load/load_yeticache.py b/openpype/hosts/unreal/plugins/load/load_yeticache.py index 328f92d0205..92f080d7c53 100644 --- a/openpype/hosts/unreal/plugins/load/load_yeticache.py +++ b/openpype/hosts/unreal/plugins/load/load_yeticache.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """Loader for Yeti Cache.""" import os +import json from openpype.pipeline import ( get_representation_path, @@ -36,6 +37,28 @@ def get_task(filename, asset_dir, asset_name, replace): return task + @staticmethod + def is_groom_module_active(): + """ + Check if Groom plugin is active. + + This is a workaround, because the Unreal python API don't have + any method to check if plugin is active. + """ + prj_file = unreal.Paths.get_project_file_path() + + with open(prj_file, "r") as fp: + data = json.load(fp) + + plugins = data.get("Plugins") + + if not plugins: + return False + + plugin_names = [p.get("Name") for p in plugins] + + return "HairStrands" in plugin_names + def load(self, context, name, namespace, options): """Load and containerise representation into Content Browser. @@ -58,6 +81,10 @@ def load(self, context, name, namespace, options): list(str): list of container content """ + # Check if Groom plugin is active + if not self.is_groom_module_active(): + raise RuntimeError("Groom plugin is not activated.") + # Create directory for asset and Ayon container root = "/Game/Ayon/Assets" asset = context.get('asset').get('name') From 5cf8fdbb6ca60327634ed5b8110a395410d5be51 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 25 Sep 2023 10:45:28 +0100 Subject: [PATCH 068/111] Do not use version in the import folder --- .../plugins/create/create_unreal_yeticache.py | 2 +- .../unreal/plugins/load/load_yeticache.py | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_unreal_yeticache.py b/openpype/hosts/maya/plugins/create/create_unreal_yeticache.py index defa6ed6d97..c9f9cd9ba80 100644 --- a/openpype/hosts/maya/plugins/create/create_unreal_yeticache.py +++ b/openpype/hosts/maya/plugins/create/create_unreal_yeticache.py @@ -9,7 +9,7 @@ class CreateYetiCache(plugin.MayaCreator): """Output for procedural plugin nodes of Yeti """ identifier = "io.openpype.creators.maya.unrealyeticache" - label = "Unreal Yeti Cache" + label = "Unreal - Yeti Cache" family = "yeticacheUE" icon = "pagelines" diff --git a/openpype/hosts/unreal/plugins/load/load_yeticache.py b/openpype/hosts/unreal/plugins/load/load_yeticache.py index 92f080d7c53..bb6c2d78cc1 100644 --- a/openpype/hosts/unreal/plugins/load/load_yeticache.py +++ b/openpype/hosts/unreal/plugins/load/load_yeticache.py @@ -90,18 +90,20 @@ def load(self, context, name, namespace, options): asset = context.get('asset').get('name') suffix = "_CON" asset_name = f"{asset}_{name}" if asset else f"{name}" - version = context.get('version') - # Check if version is hero version and use different name - if not version.get("name") and version.get('type') == "hero_version": - name_version = f"{name}_hero" - else: - name_version = f"{name}_v{version.get('name'):03d}" tools = unreal.AssetToolsHelpers().get_asset_tools() asset_dir, container_name = tools.create_unique_asset_name( - f"{root}/{asset}/{name_version}", suffix="") + f"{root}/{asset}/{name}", suffix="") + + unique_number = 1 + while unreal.EditorAssetLibrary.does_directory_exist( + f"{asset_dir}_{unique_number:02}" + ): + unique_number += 1 + + asset_dir = f"{asset_dir}_{unique_number:02}" + container_name = f"{container_name}_{unique_number:02}{suffix}" - container_name += suffix if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir): unreal.EditorAssetLibrary.make_directory(asset_dir) From 24e6b756ec44976667500129df08cb3b7fae495b Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 25 Sep 2023 10:46:33 +0100 Subject: [PATCH 069/111] Hound fix --- openpype/hosts/unreal/plugins/load/load_yeticache.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/unreal/plugins/load/load_yeticache.py b/openpype/hosts/unreal/plugins/load/load_yeticache.py index bb6c2d78cc1..22f5029bacd 100644 --- a/openpype/hosts/unreal/plugins/load/load_yeticache.py +++ b/openpype/hosts/unreal/plugins/load/load_yeticache.py @@ -104,7 +104,6 @@ def load(self, context, name, namespace, options): asset_dir = f"{asset_dir}_{unique_number:02}" container_name = f"{container_name}_{unique_number:02}{suffix}" - if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir): unreal.EditorAssetLibrary.make_directory(asset_dir) From 41e81ef7a87faced226f2bcc6b904e58de5f78e0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 25 Sep 2023 18:03:22 +0800 Subject: [PATCH 070/111] rename the function and the elaborate the docstring --- openpype/hosts/nuke/api/lib.py | 6 ++++-- openpype/hosts/nuke/api/plugin.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 2c5838ffd3e..d95839bb8d3 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3399,8 +3399,10 @@ def create_viewer_profile_string(viewer, display=None, path_like=False): return "{} ({})".format(viewer, display) -def get_file_with_name_and_hashes(original_path, name): - """Function to get the ranmed filename with frame hashes +def get_head_filename_without_hashes(original_path, name): + """Function to get the ranmed head filename without frame hashes + To avoid the system being confused on finding the filename with + frame hashes if the head of the filename has the hashed symbol Args: original_path (str): the filename with frame hashes diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 97e7c3ad8c0..1550a60f320 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -39,7 +39,7 @@ get_view_process_node, get_viewer_config_from_string, deprecated, - get_file_with_name_and_hashes + get_head_filename_without_hashes ) from .pipeline import ( list_instances, @@ -813,7 +813,7 @@ def __init__(self, self.file = self.fhead + self.name + ".{}".format(self.ext) if ".{}".format(self.ext) not in VIDEO_EXTENSIONS: - filename = get_file_with_name_and_hashes( + filename = get_head_filename_without_hashes( self.path_in, self.name) self.file = filename if ".{}".format(self.ext) not in self.file: From 22ce181f2de6b1ed001a135442c1884a71882599 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 25 Sep 2023 18:24:48 +0800 Subject: [PATCH 071/111] make sure the deprecated setting used when it enabled while the current setting is used when the deprecrated setting diabled --- openpype/settings/ayon_settings.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index a66e1b6ec67..b43e0b7c5fd 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -754,10 +754,9 @@ def _convert_nuke_project_settings(ayon_settings, output): current_review_settings = ( ayon_publish["ExtractReviewDataBakingStreams"] ) - if deprecrated_review_settings["outputs"] == ( - current_review_settings["outputs"] - ): - outputs_settings = current_review_settings["outputs"] + if not deprecrated_review_settings["enabled"]: + if current_review_settings["enabled"]: + outputs_settings = current_review_settings["outputs"] else: outputs_settings = deprecrated_review_settings["outputs"] From 3da4bac77db45edff25df7e4958a301ff5775cbc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 25 Sep 2023 18:26:16 +0800 Subject: [PATCH 072/111] typo in lib --- openpype/hosts/nuke/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index d95839bb8d3..3617133d2b4 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3400,7 +3400,7 @@ def create_viewer_profile_string(viewer, display=None, path_like=False): def get_head_filename_without_hashes(original_path, name): - """Function to get the ranmed head filename without frame hashes + """Function to get the renamed head filename without frame hashes To avoid the system being confused on finding the filename with frame hashes if the head of the filename has the hashed symbol From e0ce8013f46a1cf1c46774f43697930c09a7fd6a Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 25 Sep 2023 11:32:44 +0100 Subject: [PATCH 073/111] Use f-string --- openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py b/openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py index e72146a8713..963285093e4 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_yeticache.py @@ -39,7 +39,7 @@ def process(self, instance): kwargs.update({"samples": samples}) self.log.debug(f"Writing out cache {start_frame} - {end_frame}") - filename = "{0}.abc".format(instance.name) + filename = f"{instance.name}.abc" path = os.path.join(dirname, filename) cmds.pgYetiCommand(yeti_nodes, writeAlembic=path, From 0e594b39cdf836b8ce41637efba825a3863deae4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 25 Sep 2023 12:52:51 +0200 Subject: [PATCH 074/111] no need to check `config_data` exits in this section of code. --- openpype/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 44cff34c67c..a77dc5763a0 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -177,7 +177,7 @@ def get_colorspace_name_from_filepath( return None # validate matching colorspace with config - if validate and config_data: + if validate: validate_imageio_colorspace_in_config( config_data["path"], colorspace_name) From 5d1f2b0d9ed3ff44f1f924870e9a829c9ee4ee12 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 25 Sep 2023 13:01:43 +0200 Subject: [PATCH 075/111] typo --- openpype/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index a77dc5763a0..a67457b1bf6 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -328,7 +328,7 @@ def parse_colorspace_from_filepath( str: name of colorspace """ def _get_colorspace_match_regex(colorspaces): - """Return a regex patter + """Return a regex pattern Allows to search a colorspace match in a filename From 8b76238b2d8199e2af541365e6ef074ef1c95825 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 25 Sep 2023 19:25:44 +0800 Subject: [PATCH 076/111] fixing get_head_filename_without_hashes not being able to get multiple hashes & some strip fix --- openpype/hosts/nuke/api/lib.py | 8 ++++++-- openpype/hosts/nuke/api/plugin.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 3617133d2b4..29e7c88c711 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3406,15 +3406,19 @@ def get_head_filename_without_hashes(original_path, name): Args: original_path (str): the filename with frame hashes + e.g. "renderAssetMain.####.exr" name (str): the name of the tags + e.g. "baking" Returns: filename: the renamed filename with the tag + e.g. "renderAssetMain.baking.####.exr" """ filename = os.path.basename(original_path) fhead = filename.split(".")[0] - if "#" in fhead: - fhead = fhead.replace("#", "")[:-1] + tmp_fhead = re.sub("\d", "#", fhead) + if "#" in tmp_fhead: + fhead = tmp_fhead.replace("#", "").rstrip(".") new_fhead = "{}.{}".format(fhead, name) filename = filename.replace(fhead, new_fhead) return filename diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 1550a60f320..ca310689433 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -817,7 +817,7 @@ def __init__(self, self.path_in, self.name) self.file = filename if ".{}".format(self.ext) not in self.file: - original_ext = filename.split(".")[-1] + original_ext = os.path.splitext(filename)[-1].strip(".") # noqa self.file = filename.replace(original_ext, ext) self.path = os.path.join( From e2509a9447c5900f4c469a2c81e89c9a79a2524e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 25 Sep 2023 19:29:35 +0800 Subject: [PATCH 077/111] hound --- openpype/hosts/nuke/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 29e7c88c711..7b1aaa8fe04 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3415,8 +3415,8 @@ def get_head_filename_without_hashes(original_path, name): e.g. "renderAssetMain.baking.####.exr" """ filename = os.path.basename(original_path) - fhead = filename.split(".")[0] - tmp_fhead = re.sub("\d", "#", fhead) + fhead = os.path.splitext(filename)[0].strip(".") + tmp_fhead = re.sub(r"\d", "#", fhead) if "#" in tmp_fhead: fhead = tmp_fhead.replace("#", "").rstrip(".") new_fhead = "{}.{}".format(fhead, name) From c77faa4be4f0db9c2d6f8d083bae70c41f0fc776 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Sep 2023 13:42:22 +0200 Subject: [PATCH 078/111] Fix audio node source in - source out on updating audio version --- .../hosts/maya/plugins/load/load_audio.py | 47 ++++++++----------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index ecf98303d23..a8a878d49d5 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -1,12 +1,6 @@ from maya import cmds, mel -from openpype.client import ( - get_asset_by_id, - get_subset_by_id, - get_version_by_id, -) from openpype.pipeline import ( - get_current_project_name, load, get_representation_path, ) @@ -67,7 +61,26 @@ def update(self, container, representation): activate_sound = current_sound == audio_node path = get_representation_path(representation) - cmds.setAttr("{}.filename".format(audio_node), path, type="string") + + cmds.sound( + audio_node, + edit=True, + file=path + ) + + # The source start + end does not automatically update itself to the + # length of thew new audio file, even though maya does do that when + # when creating a new audio node. So to update we compute it manually. + # This would however override any source start and source end a user + # might have done on the original audio node after load. + audio_frame_count = cmds.getAttr("{}.frameCount".format(audio_node)) + audio_sample_rate = cmds.getAttr("{}.sampleRate".format(audio_node)) + duration_in_seconds = audio_frame_count / audio_sample_rate + fps = mel.eval('currentTimeUnitToFPS()') # workfile FPS + source_start = 0 + source_end = (duration_in_seconds * fps) + cmds.setAttr("{}.sourceStart".format(audio_node), source_start) + cmds.setAttr("{}.sourceEnd".format(audio_node), source_end) if activate_sound: # maya by default deactivates it from timeline on file change @@ -84,26 +97,6 @@ def update(self, container, representation): type="string" ) - # Set frame range. - project_name = get_current_project_name() - version = get_version_by_id( - project_name, representation["parent"], fields=["parent"] - ) - subset = get_subset_by_id( - project_name, version["parent"], fields=["parent"] - ) - asset = get_asset_by_id( - project_name, - subset["parent"], - fields=["parent", "data.frameStart", "data.frameEnd"] - ) - - source_start = 1 - asset["data"]["frameStart"] - source_end = asset["data"]["frameEnd"] - - cmds.setAttr("{}.sourceStart".format(audio_node), source_start) - cmds.setAttr("{}.sourceEnd".format(audio_node), source_end) - def switch(self, container, representation): self.update(container, representation) From 905038f6e86ef5c8116f852cab7547d7ea8d6ac8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Sep 2023 13:42:39 +0200 Subject: [PATCH 079/111] Fix typo --- openpype/hosts/maya/plugins/load/load_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index a8a878d49d5..90cadb31b17 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -70,7 +70,7 @@ def update(self, container, representation): # The source start + end does not automatically update itself to the # length of thew new audio file, even though maya does do that when - # when creating a new audio node. So to update we compute it manually. + # creating a new audio node. So to update we compute it manually. # This would however override any source start and source end a user # might have done on the original audio node after load. audio_frame_count = cmds.getAttr("{}.frameCount".format(audio_node)) From b0ae4257f95f56a4d5510a48516945e5bfb4edbb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 25 Sep 2023 15:23:02 +0200 Subject: [PATCH 080/111] missing `allowed_exts` issue and unit tests fix --- openpype/pipeline/colorspace.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index a67457b1bf6..28000504967 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -27,6 +27,9 @@ class CachedData: has_compatible_ocio_package = None config_version_data = {} ocio_config_colorspaces = {} + allowed_exts = { + ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) + } class DeprecatedWarning(DeprecationWarning): @@ -361,7 +364,7 @@ def _get_colorspace_match_regex(colorspaces): # match colorspace from filepath regex_pattern = _get_colorspace_match_regex( - colorspaces + underscored_colorspaces.keys()) + list(colorspaces) + list(underscored_colorspaces)) match = regex_pattern.search(filepath) colorspace = match.group(0) if match else None From 92bc7c12f9241eb4a3c40c7c543ac84c30e78a92 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 25 Sep 2023 17:23:34 +0200 Subject: [PATCH 081/111] fixing missing assetEntity --- openpype/plugins/publish/collect_sequence_frame_data.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_sequence_frame_data.py b/openpype/plugins/publish/collect_sequence_frame_data.py index 6c2bfbf3584..5fecc654464 100644 --- a/openpype/plugins/publish/collect_sequence_frame_data.py +++ b/openpype/plugins/publish/collect_sequence_frame_data.py @@ -28,6 +28,10 @@ def process(self, instance): def get_frame_data_from_repre_sequence(self, instance): repres = instance.data.get("representations") + parent_entity = ( + instance.context.data.get("assetEntity") + or instance.context.data["projectEntity"] + ) if repres: first_repre = repres[0] if "ext" not in first_repre: @@ -52,5 +56,5 @@ def get_frame_data_from_repre_sequence(self, instance): "frameEnd": repres_frames[-1], "handleStart": 0, "handleEnd": 0, - "fps": instance.context.data["assetEntity"]["data"]["fps"] + "fps": parent_entity["data"]["fps"] } From a73ba98209aa18fe6174a135e986962aac3d2ab0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 25 Sep 2023 17:29:14 +0200 Subject: [PATCH 082/111] assetEntity is not on context data --- openpype/plugins/publish/collect_sequence_frame_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_sequence_frame_data.py b/openpype/plugins/publish/collect_sequence_frame_data.py index 5fecc654464..1c456563e63 100644 --- a/openpype/plugins/publish/collect_sequence_frame_data.py +++ b/openpype/plugins/publish/collect_sequence_frame_data.py @@ -29,7 +29,7 @@ def process(self, instance): def get_frame_data_from_repre_sequence(self, instance): repres = instance.data.get("representations") parent_entity = ( - instance.context.data.get("assetEntity") + instance.data.get("assetEntity") or instance.context.data["projectEntity"] ) if repres: From 1bd07bd15b1bf238e186b23b7d112e9fe4637737 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 25 Sep 2023 18:01:24 +0200 Subject: [PATCH 083/111] OP-6874 - remove trailing underscore in subset name (#5647) If {layer} placeholder is at the end of subset name template and not used (for example in auto_image where separating it by layer doesn't make any sense) trailing '_' was kept. This updates cleaning logic and extracts it as it might be similar in regular `image` instance. --- openpype/hosts/photoshop/lib.py | 17 +++++++++++++ .../plugins/create/create_flatten_image.py | 14 ++--------- .../photoshop/plugins/create/create_image.py | 3 ++- .../unit/openpype/hosts/photoshop/test_lib.py | 25 +++++++++++++++++++ 4 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 tests/unit/openpype/hosts/photoshop/test_lib.py diff --git a/openpype/hosts/photoshop/lib.py b/openpype/hosts/photoshop/lib.py index ae7a33b7b6e..9f603a70d22 100644 --- a/openpype/hosts/photoshop/lib.py +++ b/openpype/hosts/photoshop/lib.py @@ -1,5 +1,8 @@ +import re + import openpype.hosts.photoshop.api as api from openpype.client import get_asset_by_name +from openpype.lib import prepare_template_data from openpype.pipeline import ( AutoCreator, CreatedInstance @@ -78,3 +81,17 @@ def create(self, options=None): existing_instance["asset"] = asset_name existing_instance["task"] = task_name existing_instance["subset"] = subset_name + + +def clean_subset_name(subset_name): + """Clean all variants leftover {layer} from subset name.""" + dynamic_data = prepare_template_data({"layer": "{layer}"}) + for value in dynamic_data.values(): + if value in subset_name: + subset_name = (subset_name.replace(value, "") + .replace("__", "_") + .replace("..", ".")) + # clean trailing separator as Main_ + pattern = r'[\W_]+$' + replacement = '' + return re.sub(pattern, replacement, subset_name) diff --git a/openpype/hosts/photoshop/plugins/create/create_flatten_image.py b/openpype/hosts/photoshop/plugins/create/create_flatten_image.py index e4229788bd2..afde77fdb46 100644 --- a/openpype/hosts/photoshop/plugins/create/create_flatten_image.py +++ b/openpype/hosts/photoshop/plugins/create/create_flatten_image.py @@ -2,7 +2,7 @@ from openpype.lib import BoolDef import openpype.hosts.photoshop.api as api -from openpype.hosts.photoshop.lib import PSAutoCreator +from openpype.hosts.photoshop.lib import PSAutoCreator, clean_subset_name from openpype.pipeline.create import get_subset_name from openpype.lib import prepare_template_data from openpype.client import get_asset_by_name @@ -129,14 +129,4 @@ def get_subset_name( self.family, variant, task_name, asset_doc, project_name, host_name, dynamic_data=dynamic_data ) - return self._clean_subset_name(subset_name) - - def _clean_subset_name(self, subset_name): - """Clean all variants leftover {layer} from subset name.""" - dynamic_data = prepare_template_data({"layer": "{layer}"}) - for value in dynamic_data.values(): - if value in subset_name: - return (subset_name.replace(value, "") - .replace("__", "_") - .replace("..", ".")) - return subset_name + return clean_subset_name(subset_name) diff --git a/openpype/hosts/photoshop/plugins/create/create_image.py b/openpype/hosts/photoshop/plugins/create/create_image.py index af20d456e0f..4f2e90886ac 100644 --- a/openpype/hosts/photoshop/plugins/create/create_image.py +++ b/openpype/hosts/photoshop/plugins/create/create_image.py @@ -10,6 +10,7 @@ from openpype.lib import prepare_template_data from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS from openpype.hosts.photoshop.api.pipeline import cache_and_get_instances +from openpype.hosts.photoshop.lib import clean_subset_name class ImageCreator(Creator): @@ -88,6 +89,7 @@ def create(self, subset_name_from_ui, data, pre_create_data): layer_fill = prepare_template_data({"layer": layer_name}) subset_name = subset_name.format(**layer_fill) + subset_name = clean_subset_name(subset_name) if group.long_name: for directory in group.long_name[::-1]: @@ -184,7 +186,6 @@ def apply_settings(self, project_settings): self.mark_for_review = plugin_settings["mark_for_review"] self.enabled = plugin_settings["enabled"] - def get_detail_description(self): return """Creator for Image instances diff --git a/tests/unit/openpype/hosts/photoshop/test_lib.py b/tests/unit/openpype/hosts/photoshop/test_lib.py new file mode 100644 index 00000000000..ad4feb42ae4 --- /dev/null +++ b/tests/unit/openpype/hosts/photoshop/test_lib.py @@ -0,0 +1,25 @@ +import pytest + +from openpype.hosts.photoshop.lib import clean_subset_name + +""" +Tests cleanup of unused layer placeholder ({layer}) from subset name. +Layer differentiation might be desired in subset name, but in some cases it +might be used (in `auto_image` - only single image without layer diff., +single image instance created without toggled use of subset name etc.) +""" + + +def test_no_layer_placeholder(): + clean_subset = clean_subset_name("imageMain") + assert "imageMain" == clean_subset + + +@pytest.mark.parametrize("subset_name", + ["imageMain{Layer}", + "imageMain_{layer}", # trailing _ + "image{Layer}Main", + "image{LAYER}Main"]) +def test_not_used_layer_placeholder(subset_name): + clean_subset = clean_subset_name(subset_name) + assert "imageMain" == clean_subset From 0595afe8a3240747cd852e3fc4073f58382ccd86 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 26 Sep 2023 00:34:44 +0800 Subject: [PATCH 084/111] add % check on the fhead in the lib.py --- openpype/hosts/nuke/api/lib.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index af07092daf2..8790794fcd0 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3432,19 +3432,27 @@ def get_head_filename_without_hashes(original_path, name): Args: original_path (str): the filename with frame hashes - e.g. "renderAssetMain.####.exr" + e.g. "renderCompositingMain.####.exr" name (str): the name of the tags e.g. "baking" Returns: filename: the renamed filename with the tag - e.g. "renderAssetMain.baking.####.exr" + e.g. "renderCompositingMain.baking.####.exr" """ filename = os.path.basename(original_path) fhead = os.path.splitext(filename)[0].strip(".") - tmp_fhead = re.sub(r"\d", "#", fhead) - if "#" in tmp_fhead: - fhead = tmp_fhead.replace("#", "").rstrip(".") + if "#" in fhead: + fhead = re.sub("#+", "", fhead).rstrip(".") + elif "%" in fhead: + # use regex to convert %04d to {:0>4} + padding = re.search("%(\\d)+d", fhead) + padding = padding.group(1) if padding else 1 + fhead = re.sub( + "%.*d", + "{{:0>{}}}".format(padding), + fhead + ).rstip(".") new_fhead = "{}.{}".format(fhead, name) filename = filename.replace(fhead, new_fhead) return filename From 65bfe023c7d2b8a571185a49e78ef7b775d30fc9 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 26 Sep 2023 00:41:04 +0800 Subject: [PATCH 085/111] transform the files with frame hashes to the list of filenames when publishing --- openpype/hosts/nuke/api/lib.py | 24 ++++++++++++++++++++++++ openpype/hosts/nuke/api/plugin.py | 7 ++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 8790794fcd0..9e41dbe8bc3 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3456,3 +3456,27 @@ def get_head_filename_without_hashes(original_path, name): new_fhead = "{}.{}".format(fhead, name) filename = filename.replace(fhead, new_fhead) return filename + + +def get_filenames_without_hash(filename, frame_start, frame_end): + """Get filenames without frame hash + i.e. "renderCompositingMain.baking.0001.exr" + + Args: + filename (str): filename with frame hash + frame_start (str): start of the frame + frame_end (str): end of the frame + + Returns: + filenames(list): list of filename + """ + filenames = [] + for frame in range(int(frame_start), (int(frame_end) + 1)): + if "#" in filename: + # use regex to convert #### to {:0>4} + def replace(match): + return "{{:0>{}}}".format(len(match.group())) + filename_without_hashes = re.sub("#+", replace, filename) + new_filename = filename_without_hashes.format(frame) + filenames.append(new_filename) + return filenames diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index ca310689433..348a0b5d76b 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -39,7 +39,8 @@ get_view_process_node, get_viewer_config_from_string, deprecated, - get_head_filename_without_hashes + get_head_filename_without_hashes, + get_filenames_without_hash ) from .pipeline import ( list_instances, @@ -638,6 +639,10 @@ def get_representation_data( "frameStart": self.first_frame, "frameEnd": self.last_frame, }) + if ".{}".format(self.ext) not in VIDEO_EXTENSIONS: + filenames = get_filenames_without_hash( + self.file, self.first_frame, self.last_frame) + repre["files"] = filenames if self.multiple_presets: repre["outputName"] = self.name From 4b1c9077e6c0f109c3315ddc88dd74f09a301bfe Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 26 Sep 2023 13:42:01 +0200 Subject: [PATCH 086/111] fix workfiles tool save button (#5653) --- openpype/tools/ayon_workfiles/models/workfiles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/ayon_workfiles/models/workfiles.py b/openpype/tools/ayon_workfiles/models/workfiles.py index 316d8b2a16a..4d989ed22c3 100644 --- a/openpype/tools/ayon_workfiles/models/workfiles.py +++ b/openpype/tools/ayon_workfiles/models/workfiles.py @@ -48,7 +48,7 @@ def get_task_template_data(project_entity, task): return {} short_name = None task_type_name = task["taskType"] - for task_type_info in project_entity["config"]["taskTypes"]: + for task_type_info in project_entity["taskTypes"]: if task_type_info["name"] == task_type_name: short_name = task_type_info["shortName"] break From 40755fce119f388efa85e4cab5c94da749b54dbf Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Sep 2023 18:56:01 +0200 Subject: [PATCH 087/111] Increase timout for deadline test (#5654) DL picks up jobs quite slow, so bump up delay. --- tests/integration/hosts/maya/test_deadline_publish_in_maya.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/hosts/maya/test_deadline_publish_in_maya.py b/tests/integration/hosts/maya/test_deadline_publish_in_maya.py index c5bf526f524..93321779444 100644 --- a/tests/integration/hosts/maya/test_deadline_publish_in_maya.py +++ b/tests/integration/hosts/maya/test_deadline_publish_in_maya.py @@ -32,7 +32,7 @@ class TestDeadlinePublishInMaya(MayaDeadlinePublishTestClass): # keep empty to locate latest installed variant or explicit APP_VARIANT = "" - TIMEOUT = 120 # publish timeout + TIMEOUT = 180 # publish timeout def test_db_asserts(self, dbcon, publish_finished): """Host and input data dependent expected results in DB.""" From 16bcbc155867c0daef9c990484035c6ba0f16ec2 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 27 Sep 2023 03:24:58 +0000 Subject: [PATCH 088/111] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index d1ebde3d046..c8ae6dffd8f 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.17.1-nightly.2" +__version__ = "3.17.1-nightly.3" From 3ddfb13e2aae05e4df2b3a6da7900600d8649d1d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 27 Sep 2023 03:25:45 +0000 Subject: [PATCH 089/111] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 87d904fc848..a2edd28f5b7 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 OpenPype Tray options: + - 3.17.1-nightly.3 - 3.17.1-nightly.2 - 3.17.1-nightly.1 - 3.17.0 @@ -134,7 +135,6 @@ body: - 3.14.10-nightly.6 - 3.14.10-nightly.5 - 3.14.10-nightly.4 - - 3.14.10-nightly.3 validations: required: true - type: dropdown From 4ae8e7fa774e06ebc690998f2dd7d623a9e8c044 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Sep 2023 11:39:15 +0200 Subject: [PATCH 090/111] removing project entity redundancy --- .../plugins/publish/collect_sequence_frame_data.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/collect_sequence_frame_data.py b/openpype/plugins/publish/collect_sequence_frame_data.py index 1c456563e63..33ff3281a2c 100644 --- a/openpype/plugins/publish/collect_sequence_frame_data.py +++ b/openpype/plugins/publish/collect_sequence_frame_data.py @@ -28,10 +28,12 @@ def process(self, instance): def get_frame_data_from_repre_sequence(self, instance): repres = instance.data.get("representations") - parent_entity = ( - instance.data.get("assetEntity") - or instance.context.data["projectEntity"] - ) + parent_entity = instance.data.get("assetEntity") + + if not parent_entity: + self.log.warning("Cannot find parent entity data") + return + if repres: first_repre = repres[0] if "ext" not in first_repre: @@ -40,7 +42,7 @@ def get_frame_data_from_repre_sequence(self, instance): return files = first_repre["files"] - collections, remainder = clique.assemble(files) + collections, _ = clique.assemble(files) if not collections: # No sequences detected and we can't retrieve # frame range From fbafc420aaaced3501be9e2b24f8025c85809c8c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Sep 2023 12:28:42 +0200 Subject: [PATCH 091/111] reverting enhancing UX of sequence or asset frame data collection --- ...> collect_frame_data_from_asset_entity.py} | 23 +++++++------------ .../publish/collect_sequence_frame_data.py | 21 +++++++++-------- 2 files changed, 19 insertions(+), 25 deletions(-) rename openpype/hosts/traypublisher/plugins/publish/{collect_missing_frame_range_asset_entity.py => collect_frame_data_from_asset_entity.py} (51%) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_missing_frame_range_asset_entity.py b/openpype/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py similarity index 51% rename from openpype/hosts/traypublisher/plugins/publish/collect_missing_frame_range_asset_entity.py rename to openpype/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py index 72379ea4e12..f2e24d88ebc 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_missing_frame_range_asset_entity.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py @@ -2,18 +2,18 @@ from openpype.pipeline import OptionalPyblishPluginMixin -class CollectMissingFrameDataFromAssetEntity( +class CollectFrameDataFromAssetEntity( pyblish.api.InstancePlugin, OptionalPyblishPluginMixin ): - """Collect Missing Frame Range data From Asset Entity + """Collect Frame Data From AssetEntity found in context Frame range data will only be collected if the keys are not yet collected for the instance. """ order = pyblish.api.CollectorOrder + 0.491 - label = "Collect Missing Frame Data From Asset Entity" + label = "Collect Frame Data From Asset" families = ["plate", "pointcache", "vdbcache", "online", "render"] @@ -23,7 +23,9 @@ class CollectMissingFrameDataFromAssetEntity( def process(self, instance): if not self.is_active(instance.data): return - missing_keys = [] + + asset_data = instance.data["assetEntity"]["data"] + for key in ( "fps", "frameStart", @@ -31,14 +33,5 @@ def process(self, instance): "handleStart", "handleEnd" ): - if key not in instance.data: - missing_keys.append(key) - keys_set = [] - for key in missing_keys: - asset_data = instance.data["assetEntity"]["data"] - if key in asset_data: - instance.data[key] = asset_data[key] - keys_set.append(key) - if keys_set: - self.log.debug(f"Frame range data {keys_set} " - "has been collected from asset entity.") + instance.data[key] = asset_data[key] + self.log.debug(f"Collected Frame range data '{key}':{asset_data[key]} ") diff --git a/openpype/plugins/publish/collect_sequence_frame_data.py b/openpype/plugins/publish/collect_sequence_frame_data.py index 33ff3281a2c..d8ad5d0a21a 100644 --- a/openpype/plugins/publish/collect_sequence_frame_data.py +++ b/openpype/plugins/publish/collect_sequence_frame_data.py @@ -9,7 +9,7 @@ class CollectSequenceFrameData(pyblish.api.InstancePlugin): start and end frame respectively """ - order = pyblish.api.CollectorOrder + 0.2 + order = pyblish.api.CollectorOrder + 0.490 label = "Collect Sequence Frame Data" families = ["plate", "pointcache", "vdbcache", "online", @@ -18,21 +18,22 @@ class CollectSequenceFrameData(pyblish.api.InstancePlugin): def process(self, instance): frame_data = self.get_frame_data_from_repre_sequence(instance) + if not frame_data: # if no dict data skip collecting the frame range data return + for key, value in frame_data.items(): - if key not in instance.data: - instance.data[key] = value - self.log.debug(f"Collected Frame range data '{key}':{value} ") + instance.data[key] = value + self.log.debug(f"Collected Frame range data '{key}':{value} ") + + test_keys = {key: value for key, value in instance.data.items() if key in frame_data} + self.log.debug(f"Final Instance frame data: {test_keys}") + def get_frame_data_from_repre_sequence(self, instance): repres = instance.data.get("representations") - parent_entity = instance.data.get("assetEntity") - - if not parent_entity: - self.log.warning("Cannot find parent entity data") - return + asset_data = instance.data["assetEntity"]["data"] if repres: first_repre = repres[0] @@ -58,5 +59,5 @@ def get_frame_data_from_repre_sequence(self, instance): "frameEnd": repres_frames[-1], "handleStart": 0, "handleEnd": 0, - "fps": parent_entity["data"]["fps"] + "fps": asset_data["fps"] } From 5b1cbfaa6743c6bd4f9b6be4e86b3a0854dbb3c9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Sep 2023 12:35:36 +0200 Subject: [PATCH 092/111] removing debug prints --- .../plugins/publish/collect_frame_data_from_asset_entity.py | 3 ++- openpype/plugins/publish/collect_sequence_frame_data.py | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py b/openpype/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py index f2e24d88ebc..5ba84bc05b4 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py @@ -34,4 +34,5 @@ def process(self, instance): "handleEnd" ): instance.data[key] = asset_data[key] - self.log.debug(f"Collected Frame range data '{key}':{asset_data[key]} ") + self.log.debug( + f"Collected Frame range data '{key}':{asset_data[key]} ") diff --git a/openpype/plugins/publish/collect_sequence_frame_data.py b/openpype/plugins/publish/collect_sequence_frame_data.py index d8ad5d0a21a..f9ac869ec3e 100644 --- a/openpype/plugins/publish/collect_sequence_frame_data.py +++ b/openpype/plugins/publish/collect_sequence_frame_data.py @@ -27,9 +27,6 @@ def process(self, instance): instance.data[key] = value self.log.debug(f"Collected Frame range data '{key}':{value} ") - test_keys = {key: value for key, value in instance.data.items() if key in frame_data} - self.log.debug(f"Final Instance frame data: {test_keys}") - def get_frame_data_from_repre_sequence(self, instance): repres = instance.data.get("representations") From e90d227a234fec278b214c3ed4086350403eda80 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Sep 2023 13:37:01 +0200 Subject: [PATCH 093/111] reverting the functionality - sequencial original frame data should be optional plugin - sequential data are added if activated - asset data frame data are not optional anymore and are added only if missing --- .../collect_frame_data_from_asset_entity.py | 27 +++++++++---------- .../publish/collect_sequence_frame_data.py | 18 ++++++++++--- .../project_settings/traypublisher.json | 4 +-- .../schema_project_traypublisher.json | 4 +-- 4 files changed, 31 insertions(+), 22 deletions(-) rename openpype/{ => hosts/traypublisher}/plugins/publish/collect_sequence_frame_data.py (82%) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py b/openpype/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py index 5ba84bc05b4..2950076cd02 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py @@ -1,11 +1,7 @@ import pyblish.api -from openpype.pipeline import OptionalPyblishPluginMixin -class CollectFrameDataFromAssetEntity( - pyblish.api.InstancePlugin, - OptionalPyblishPluginMixin -): +class CollectFrameDataFromAssetEntity(pyblish.api.InstancePlugin): """Collect Frame Data From AssetEntity found in context Frame range data will only be collected if the keys @@ -18,14 +14,9 @@ class CollectFrameDataFromAssetEntity( "vdbcache", "online", "render"] hosts = ["traypublisher"] - optional = True def process(self, instance): - if not self.is_active(instance.data): - return - - asset_data = instance.data["assetEntity"]["data"] - + missing_keys = [] for key in ( "fps", "frameStart", @@ -33,6 +24,14 @@ def process(self, instance): "handleStart", "handleEnd" ): - instance.data[key] = asset_data[key] - self.log.debug( - f"Collected Frame range data '{key}':{asset_data[key]} ") + if key not in instance.data: + missing_keys.append(key) + keys_set = [] + for key in missing_keys: + asset_data = instance.data["assetEntity"]["data"] + if key in asset_data: + instance.data[key] = asset_data[key] + keys_set.append(key) + if keys_set: + self.log.debug(f"Frame range data {keys_set} " + "has been collected from asset entity.") diff --git a/openpype/plugins/publish/collect_sequence_frame_data.py b/openpype/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py similarity index 82% rename from openpype/plugins/publish/collect_sequence_frame_data.py rename to openpype/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py index f9ac869ec3e..db70d4fe0a9 100644 --- a/openpype/plugins/publish/collect_sequence_frame_data.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py @@ -1,22 +1,32 @@ import pyblish.api import clique +from openpype.pipeline import OptionalPyblishPluginMixin + + +class CollectSequenceFrameData( + pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin +): + """Collect Original Sequence Frame Data -class CollectSequenceFrameData(pyblish.api.InstancePlugin): - """Collect Sequence Frame Data If the representation includes files with frame numbers, then set `frameStart` and `frameEnd` for the instance to the start and end frame respectively """ - order = pyblish.api.CollectorOrder + 0.490 - label = "Collect Sequence Frame Data" + order = pyblish.api.CollectorOrder + 0.4905 + label = "Collect Original Sequence Frame Data" families = ["plate", "pointcache", "vdbcache", "online", "render"] hosts = ["traypublisher"] + optional = True def process(self, instance): + if not self.is_active(instance.data): + return + frame_data = self.get_frame_data_from_repre_sequence(instance) if not frame_data: diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 7f7b7d1452f..e13de114143 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -346,10 +346,10 @@ } }, "publish": { - "CollectFrameDataFromAssetEntity": { + "CollectSequenceFrameData": { "enabled": true, "optional": true, - "active": true + "active": false }, "ValidateFrameRange": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 184fc657bef..93e6325b5a7 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -350,8 +350,8 @@ "name": "template_validate_plugin", "template_data": [ { - "key": "CollectFrameDataFromAssetEntity", - "label": "Collect frame range from asset entity" + "key": "CollectSequenceFrameData", + "label": "Collect Original Sequence Frame Data" }, { "key": "ValidateFrameRange", From 8897cdaa92f21e5c9de22cccd9e73ecc65c0c845 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 27 Sep 2023 19:43:21 +0800 Subject: [PATCH 094/111] bug fix delete items from container to remove item --- openpype/hosts/max/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 3389447cb0e..b23d156d0df 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -91,7 +91,7 @@ ( current_selection = selectByName title:"Select Objects to remove from the Container" buttontext:"Remove" filter: nodes_to_rmv - if current_selection == undefined then return False + if current_selection == undefined or current_selection.count == 0 then return False temp_arr = #() i_node_arr = #() new_i_node_arr = #() From 1c8ab8ecfacc95543fa348a08c4e1b23495bc52c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Sep 2023 13:47:16 +0200 Subject: [PATCH 095/111] better label --- .../plugins/publish/collect_frame_data_from_asset_entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py b/openpype/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py index 2950076cd02..e8a2cae16c0 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py @@ -9,7 +9,7 @@ class CollectFrameDataFromAssetEntity(pyblish.api.InstancePlugin): """ order = pyblish.api.CollectorOrder + 0.491 - label = "Collect Frame Data From Asset" + label = "Collect Missing Frame Data From Asset" families = ["plate", "pointcache", "vdbcache", "online", "render"] From 723d187835a83d7f76c65017411b0a7263a8e18a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 27 Sep 2023 19:54:01 +0800 Subject: [PATCH 096/111] hound --- openpype/hosts/max/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index b23d156d0df..31f01b6bbf4 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -91,7 +91,7 @@ ( current_selection = selectByName title:"Select Objects to remove from the Container" buttontext:"Remove" filter: nodes_to_rmv - if current_selection == undefined or current_selection.count == 0 then return False + if current_selection == undefined or current_selection.count == 0 then return False # noqa temp_arr = #() i_node_arr = #() new_i_node_arr = #() From 42132e50e98208f581f97005dba839ba414265ee Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 27 Sep 2023 21:34:57 +0800 Subject: [PATCH 097/111] # noqa makes the maxscript not working --- openpype/hosts/max/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 31f01b6bbf4..b23d156d0df 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -91,7 +91,7 @@ ( current_selection = selectByName title:"Select Objects to remove from the Container" buttontext:"Remove" filter: nodes_to_rmv - if current_selection == undefined or current_selection.count == 0 then return False # noqa + if current_selection == undefined or current_selection.count == 0 then return False temp_arr = #() i_node_arr = #() new_i_node_arr = #() From 49e8a4b008a17ce97fb3ed4a77c53534f6e2509c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 27 Sep 2023 21:42:08 +0800 Subject: [PATCH 098/111] hound --- openpype/hosts/max/api/plugin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index b23d156d0df..881295b317a 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -91,7 +91,10 @@ ( current_selection = selectByName title:"Select Objects to remove from the Container" buttontext:"Remove" filter: nodes_to_rmv - if current_selection == undefined or current_selection.count == 0 then return False + if current_selection == undefined or current_selection.count == 0 then + ( + return False + ) temp_arr = #() i_node_arr = #() new_i_node_arr = #() From 9fdd895bb6e4acb16313225f6c85604002471547 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 27 Sep 2023 21:44:19 +0800 Subject: [PATCH 099/111] rename current_selection to current_sel --- openpype/hosts/max/api/plugin.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 881295b317a..fa6db073dba 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -65,12 +65,12 @@ on button_add pressed do ( - current_selection = selectByName title:"Select Objects to add to + current_sel = selectByName title:"Select Objects to add to the Container" buttontext:"Add" filter:nodes_to_add - if current_selection == undefined then return False + if current_sel == undefined then return False temp_arr = #() i_node_arr = #() - for c in current_selection do + for c in current_sel do ( handle_name = node_to_name c node_ref = NodeTransformMonitor node:c @@ -89,9 +89,9 @@ on button_del pressed do ( - current_selection = selectByName title:"Select Objects to remove + current_sel = selectByName title:"Select Objects to remove from the Container" buttontext:"Remove" filter: nodes_to_rmv - if current_selection == undefined or current_selection.count == 0 then + if current_sel == undefined or current_sel.count == 0 then ( return False ) @@ -100,7 +100,7 @@ new_i_node_arr = #() new_temp_arr = #() - for c in current_selection do + for c in current_sel do ( node_ref = NodeTransformMonitor node:c as string handle_name = node_to_name c From 6deb9338cbd56447ea548f20eb0a51158b124b9e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 27 Sep 2023 21:47:02 +0800 Subject: [PATCH 100/111] fix the typo of rstrip --- openpype/hosts/nuke/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 9e41dbe8bc3..351778d997a 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3452,7 +3452,7 @@ def get_head_filename_without_hashes(original_path, name): "%.*d", "{{:0>{}}}".format(padding), fhead - ).rstip(".") + ).rstrip(".") new_fhead = "{}.{}".format(fhead, name) filename = filename.replace(fhead, new_fhead) return filename From ebdcc49cd7895aa3e4aceadb0a0af116a0e41843 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 27 Sep 2023 22:52:53 +0800 Subject: [PATCH 101/111] implement more concise function for getting filenames with hashes --- openpype/hosts/nuke/api/lib.py | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 351778d997a..d34e7a1e0a6 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3432,30 +3432,20 @@ def get_head_filename_without_hashes(original_path, name): Args: original_path (str): the filename with frame hashes - e.g. "renderCompositingMain.####.exr" + e.g. "renderCompositingMain.####.exr" name (str): the name of the tags - e.g. "baking" + e.g. "baking" Returns: - filename: the renamed filename with the tag - e.g. "renderCompositingMain.baking.####.exr" + str: the renamed filename with the tag + e.g. "renderCompositingMain.baking.####.exr" """ filename = os.path.basename(original_path) - fhead = os.path.splitext(filename)[0].strip(".") - if "#" in fhead: - fhead = re.sub("#+", "", fhead).rstrip(".") - elif "%" in fhead: - # use regex to convert %04d to {:0>4} - padding = re.search("%(\\d)+d", fhead) - padding = padding.group(1) if padding else 1 - fhead = re.sub( - "%.*d", - "{{:0>{}}}".format(padding), - fhead - ).rstrip(".") - new_fhead = "{}.{}".format(fhead, name) - filename = filename.replace(fhead, new_fhead) - return filename + + def insert_name(matchobj): + return "{}.{}".format(name, matchobj.group(0)) + + return re.sub(r"(%\d*d)|#+", insert_name, filename) def get_filenames_without_hash(filename, frame_start, frame_end): From abef01cd0560ce60e108ed71c1de1d159b7efcb1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 27 Sep 2023 22:54:18 +0800 Subject: [PATCH 102/111] edit docstring --- openpype/hosts/nuke/api/lib.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index d34e7a1e0a6..cc2c5a6ec78 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3430,15 +3430,20 @@ def get_head_filename_without_hashes(original_path, name): To avoid the system being confused on finding the filename with frame hashes if the head of the filename has the hashed symbol + Examples: + >>> get_head_filename_without_hashes("render.####.exr", "baking") + render.baking.####.exr + >>> get_head_filename_without_hashes("render.%d.exr", "tag") + render.tag.%d.exr + >>> get_head_filename_without_hashes("exr.####.exr", "foo") + exr.foo.%04d.exr + Args: original_path (str): the filename with frame hashes - e.g. "renderCompositingMain.####.exr" name (str): the name of the tags - e.g. "baking" Returns: str: the renamed filename with the tag - e.g. "renderCompositingMain.baking.####.exr" """ filename = os.path.basename(original_path) From e493886f4de4316778d3129d40f441f8ded24b71 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 27 Sep 2023 23:17:12 +0800 Subject: [PATCH 103/111] improve docstring on lib.py and add comment on the condition of setting filename with extension and improved the deprecrated settings --- openpype/hosts/nuke/api/lib.py | 2 +- openpype/hosts/nuke/api/plugin.py | 4 ++++ .../publish/extract_review_baking_streams.py | 10 +++++----- openpype/settings/ayon_settings.py | 15 ++++++--------- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index cc2c5a6ec78..dafc4bf8385 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3463,7 +3463,7 @@ def get_filenames_without_hash(filename, frame_start, frame_end): frame_end (str): end of the frame Returns: - filenames(list): list of filename + list: filename per frame of the sequence """ filenames = [] for frame in range(int(frame_start), (int(frame_end) + 1)): diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 348a0b5d76b..e16aef67408 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -818,9 +818,13 @@ def __init__(self, self.file = self.fhead + self.name + ".{}".format(self.ext) if ".{}".format(self.ext) not in VIDEO_EXTENSIONS: + # filename would be with frame hashes if + # the file extension is not in video format filename = get_head_filename_without_hashes( self.path_in, self.name) self.file = filename + # make sure the filename are in + # correct image output format if ".{}".format(self.ext) not in self.file: original_ext = os.path.splitext(filename)[-1].strip(".") # noqa self.file = filename.replace(original_ext, ext) diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py b/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py index d9ae673c2cc..fe468bd263c 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py @@ -33,13 +33,13 @@ def apply_settings(cls, project_settings): nuke_publish = project_settings["nuke"]["publish"] deprecated_setting = nuke_publish["ExtractReviewDataMov"] current_setting = nuke_publish["ExtractReviewDataBakingStreams"] - if not deprecated_setting["enabled"]: - if current_setting["enabled"]: - cls.viewer_lut_raw = current_setting["viewer_lut_raw"] - cls.outputs = current_setting["outputs"] - else: + if deprecated_setting["enabled"]: + # Use deprecated settings if they are still enabled cls.viewer_lut_raw = deprecated_setting["viewer_lut_raw"] cls.outputs = deprecated_setting["outputs"] + elif current_setting["enabled"]: + cls.viewer_lut_raw = current_setting["viewer_lut_raw"] + cls.outputs = current_setting["outputs"] def process(self, instance): families = set(instance.data["families"]) diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index b43e0b7c5fd..dc6e9fab12a 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -754,11 +754,10 @@ def _convert_nuke_project_settings(ayon_settings, output): current_review_settings = ( ayon_publish["ExtractReviewDataBakingStreams"] ) - if not deprecrated_review_settings["enabled"]: - if current_review_settings["enabled"]: - outputs_settings = current_review_settings["outputs"] - else: + if deprecrated_review_settings["enabled"]: outputs_settings = deprecrated_review_settings["outputs"] + elif current_review_settings["enabled"]: + outputs_settings = current_review_settings["outputs"] for item in outputs_settings: item_filter = item["filter"] @@ -780,12 +779,10 @@ def _convert_nuke_project_settings(ayon_settings, output): name = item.pop("name") new_review_data_outputs[name] = item - if deprecrated_review_settings["outputs"] == ( - current_review_settings["outputs"] - ): - current_review_settings["outputs"] = new_review_data_outputs - else: + if deprecrated_review_settings["enabled"]: deprecrated_review_settings["outputs"] = new_review_data_outputs + elif current_review_settings["enabled"]: + current_review_settings["outputs"] = new_review_data_outputs collect_instance_data = ayon_publish["CollectInstanceData"] if "sync_workfile_version_on_product_types" in collect_instance_data: From 973e4804d5731dbb85e916177ecef7854d5d30d1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 27 Sep 2023 23:30:57 +0800 Subject: [PATCH 104/111] make sure not using .replace --- openpype/hosts/nuke/api/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index e16aef67408..81841d17be9 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -826,8 +826,8 @@ def __init__(self, # make sure the filename are in # correct image output format if ".{}".format(self.ext) not in self.file: - original_ext = os.path.splitext(filename)[-1].strip(".") # noqa - self.file = filename.replace(original_ext, ext) + filename_no_ext, _ = os.path.splitext(filename) + self.file = "{}.{}".format(filename_no_ext, self.ext) self.path = os.path.join( self.staging_dir, self.file).replace("\\", "/") From 70d3f20de4c1963c67bd40be2613fc67ee34e017 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Thu, 28 Sep 2023 09:30:43 +0000 Subject: [PATCH 105/111] [Automated] Release --- CHANGELOG.md | 264 ++++++++++++++++++++++++++++++++++++++++++++ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 266 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bcf66a210a..8f14340348e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,270 @@ # Changelog +## [3.17.1](https://github.com/ynput/OpenPype/tree/3.17.1) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.17.0...3.17.1) + +### **🆕 New features** + + +
+Unreal: Yeti support #5643 + +Implemented Yeti support for Unreal. + + +___ + +
+ + +
+Houdini: Add Static Mesh product-type (family) #5481 + +This PR adds support to publish Unreal Static Mesh in Houdini as FBXQuick recap +- [x] Add UE Static Mesh Creator +- [x] Dynamic subset name like in Maya +- [x] Collect Static Mesh Type +- [x] Update collect output node +- [x] Validate FBX output node +- [x] Validate mesh is static +- [x] Validate Unreal Static Mesh Name +- [x] Validate Subset Name +- [x] FBX Extractor +- [x] FBX Loader +- [x] Update OP Settings +- [x] Update AYON Settings + + +___ + +
+ + +
+Launcher tool: Refactor launcher tool (for AYON) #5612 + +Refactored launcher tool to new tool. Separated backend and frontend logic. Refactored logic is AYON-centric and is used only in AYON mode, so it does not affect OpenPype. + + +___ + +
+ +### **🚀 Enhancements** + + +
+Maya: Use custom staging dir function for Maya renders - OP-5265 #5186 + +Check for custom staging dir when setting the renders output folder in Maya. + + +___ + +
+ + +
+Colorspace: updating file path detection methods #5273 + +Support for OCIO v2 file rules integrated into the available color management API + + +___ + +
+ + +
+Chore: add default isort config #5572 + +Add default configuration for isort tool + + +___ + +
+ + +
+Deadline: set PATH environment in deadline jobs by GlobalJobPreLoad #5622 + +This PR makes `GlobalJobPreLoad` to set `PATH` environment in deadline jobs so that we don't have to use the full executable path for deadline to launch the dcc app. This trick should save us adding logic to pass houdini patch version and modifying Houdini deadline plugin. This trick should work with other DCCs + + +___ + +
+ + +
+nuke: extract review data mov read node with expression #5635 + +Some productions might have set default values for read nodes, those settings are not colliding anymore now. + + +___ + +
+ +### **🐛 Bug fixes** + + +
+Maya: Support new publisher for colorsets validation. #5630 + +Fix `validate_color_sets` for the new publisher.In current `develop` the repair option does not appear due to wrong error raising. + + +___ + +
+ + +
+Houdini: Camera Loader fix mismatch for Maya cameras #5584 + +This PR adds +- A workaround to match Maya render mask in Houdini +- `SetCameraResolution` inventory action +- set camera resolution when loading or updating camera + + +___ + +
+ + +
+Nuke: fix set colorspace on writes #5634 + +Colorspace is set correctly to any write node created from publisher. + + +___ + +
+ + +
+TVPaint: Fix review family extraction #5637 + +Extractor marks representation of review instance with review tag. + + +___ + +
+ + +
+AYON settings: Extract OIIO transcode settings #5639 + +Output definitions of Extract OIIO transcode have name to match OpenPype settings, and the settings are converted to dictionary in settings conversion. + + +___ + +
+ + +
+AYON: Fix task type short name conversion #5641 + +Convert AYON task type short name for OpenPype correctly. + + +___ + +
+ + +
+colorspace: missing `allowed_exts` fix #5646 + +Colorspace module is not failing due to missing `allowed_exts` attribute. + + +___ + +
+ + +
+Photoshop: remove trailing underscore in subset name #5647 + +If {layer} placeholder is at the end of subset name template and not used (for example in `auto_image` where separating it by layer doesn't make any sense) trailing '_' was kept. This updates cleaning logic and extracts it as it might be similar in regular `image` instance. + + +___ + +
+ + +
+traypublisher: missing `assetEntity` in context data #5648 + +Issue with missing `assetEnity` key in context data is not problem anymore. + + +___ + +
+ + +
+AYON: Workfiles tool save button works #5653 + +Fix save as button in workfiles tool.(It is mystery why this stopped to work??) + + +___ + +
+ + +
+Max: bug fix delete items from container #5658 + +Fix the bug shown when clicking "Delete Items from Container" and selecting nothing and press ok. + + +___ + +
+ +### **🔀 Refactored code** + + +
+Chore: Remove unused functions from Fusion integration #5617 + +Cleanup unused code from Fusion integration + + +___ + +
+ +### **Merged pull requests** + + +
+Increase timout for deadline test #5654 + +DL picks up jobs quite slow, so bump up delay. + + +___ + +
+ + + + ## [3.17.0](https://github.com/ynput/OpenPype/tree/3.17.0) diff --git a/openpype/version.py b/openpype/version.py index c8ae6dffd8f..f1e0cd0b801 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.17.1-nightly.3" +__version__ = "3.17.1" diff --git a/pyproject.toml b/pyproject.toml index d0b1ecf5894..2460185bdd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.17.0" # OpenPype +version = "3.17.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 030d5843fa2baba9e723067f34db0e1dbc46f297 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 28 Sep 2023 09:31:44 +0000 Subject: [PATCH 106/111] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index a2edd28f5b7..591d865ca56 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 OpenPype Tray options: + - 3.17.1 - 3.17.1-nightly.3 - 3.17.1-nightly.2 - 3.17.1-nightly.1 @@ -134,7 +135,6 @@ body: - 3.14.10-nightly.7 - 3.14.10-nightly.6 - 3.14.10-nightly.5 - - 3.14.10-nightly.4 validations: required: true - type: dropdown From 356f05ff91efd32dcecbb72081f94740ec8df908 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 28 Sep 2023 19:20:37 +0800 Subject: [PATCH 107/111] docstring tweaks --- openpype/hosts/nuke/api/lib.py | 2 +- .../nuke/plugins/publish/extract_review_baking_streams.py | 6 +++--- openpype/settings/ayon_settings.py | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index dafc4bf8385..07f394ec001 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3433,7 +3433,7 @@ def get_head_filename_without_hashes(original_path, name): Examples: >>> get_head_filename_without_hashes("render.####.exr", "baking") render.baking.####.exr - >>> get_head_filename_without_hashes("render.%d.exr", "tag") + >>> get_head_filename_without_hashes("render.%04d.exr", "tag") render.tag.%d.exr >>> get_head_filename_without_hashes("exr.####.exr", "foo") exr.foo.%04d.exr diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py b/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py index fe468bd263c..1ba107a3e70 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py @@ -9,7 +9,7 @@ class ExtractReviewDataBakingStreams(publish.Extractor): - """Extracts movie and thumbnail with baked in luts + """Extracts Sequences and thumbnail with baked in luts must be run after extract_render_local.py @@ -27,8 +27,8 @@ class ExtractReviewDataBakingStreams(publish.Extractor): @classmethod def apply_settings(cls, project_settings): - """just in case there are some old presets - in deprecated ExtractReviewDataMov Plugins + """Apply the settings from the deprecated + ExtractReviewDataMov plugin for backwards compatibility """ nuke_publish = project_settings["nuke"]["publish"] deprecated_setting = nuke_publish["ExtractReviewDataMov"] diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index dc6e9fab12a..f23046e6c48 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -749,7 +749,8 @@ def _convert_nuke_project_settings(ayon_settings, output): new_review_data_outputs = {} outputs_settings = None - # just in case that the users having old presets in outputs setting + # Check deprecated ExtractReviewDataMov + # settings for backwards compatibility deprecrated_review_settings = ayon_publish["ExtractReviewDataMov"] current_review_settings = ( ayon_publish["ExtractReviewDataBakingStreams"] From 6c1e066b3b7d58eba28d38900a886a0c89c556b5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 28 Sep 2023 20:07:28 +0800 Subject: [PATCH 108/111] Rename ExtractReviewDataBakingStreams to ExtractReviewIntermediate --- .../nuke/plugins/publish/extract_review_baking_streams.py | 6 +++--- openpype/settings/ayon_settings.py | 2 +- openpype/settings/defaults/project_settings/nuke.json | 2 +- .../projects_schema/schemas/schema_nuke_publish.json | 6 +++--- server_addon/nuke/server/settings/publish_plugins.py | 4 ++-- website/docs/project_settings/settings_project_global.md | 2 +- website/docs/pype2/admin_presets_plugins.md | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py b/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py index 1ba107a3e70..4407c039b4f 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py @@ -8,7 +8,7 @@ from openpype.hosts.nuke.api.lib import maintained_selection -class ExtractReviewDataBakingStreams(publish.Extractor): +class ExtractReviewIntermediate(publish.Extractor): """Extracts Sequences and thumbnail with baked in luts must be run after extract_render_local.py @@ -16,7 +16,7 @@ class ExtractReviewDataBakingStreams(publish.Extractor): """ order = pyblish.api.ExtractorOrder + 0.01 - label = "Extract Review Data Baking Streams" + label = "Extract Review Intermediate" families = ["review"] hosts = ["nuke"] @@ -32,7 +32,7 @@ def apply_settings(cls, project_settings): """ nuke_publish = project_settings["nuke"]["publish"] deprecated_setting = nuke_publish["ExtractReviewDataMov"] - current_setting = nuke_publish["ExtractReviewDataBakingStreams"] + current_setting = nuke_publish["ExtractReviewIntermediate"] if deprecated_setting["enabled"]: # Use deprecated settings if they are still enabled cls.viewer_lut_raw = deprecated_setting["viewer_lut_raw"] diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index f23046e6c48..fbf35aec0a3 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -753,7 +753,7 @@ def _convert_nuke_project_settings(ayon_settings, output): # settings for backwards compatibility deprecrated_review_settings = ayon_publish["ExtractReviewDataMov"] current_review_settings = ( - ayon_publish["ExtractReviewDataBakingStreams"] + ayon_publish["ExtractReviewIntermediate"] ) if deprecrated_review_settings["enabled"]: outputs_settings = deprecrated_review_settings["outputs"] diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index fac78dbcd5c..7346c9d7b88 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -501,7 +501,7 @@ } } }, - "ExtractReviewDataBakingStreams": { + "ExtractReviewIntermediate": { "enabled": true, "viewer_lut_raw": false, "outputs": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json index 0f366d55ba1..c14f47a3a7b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json @@ -373,14 +373,14 @@ }, { "type": "label", - "label": "^ Settings and for ExtractReviewDataMov is deprecated and will be soon removed.
Please use ExtractReviewDataBakingStreams instead." + "label": "^ Settings and for ExtractReviewDataMov is deprecated and will be soon removed.
Please use ExtractReviewIntermediate instead." }, { "type": "dict", "collapsible": true, "checkbox_key": "enabled", - "key": "ExtractReviewDataBakingStreams", - "label": "ExtractReviewDataBakingStreams", + "key": "ExtractReviewIntermediate", + "label": "ExtractReviewIntermediate", "is_group": true, "children": [ { diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py index 6459dd7225d..399aa7e38e8 100644 --- a/server_addon/nuke/server/settings/publish_plugins.py +++ b/server_addon/nuke/server/settings/publish_plugins.py @@ -282,7 +282,7 @@ class PublishPuginsModel(BaseSettingsModel): title="Extract Review Data Mov", default_factory=ExtractReviewDataMovModel ) - ExtractReviewDataBakingStreams: ExtractReviewBakingStreamsModel = Field( + ExtractReviewIntermediate: ExtractReviewBakingStreamsModel = Field( title="Extract Review Data Baking Streams", default_factory=ExtractReviewBakingStreamsModel ) @@ -481,7 +481,7 @@ class PublishPuginsModel(BaseSettingsModel): } ] }, - "ExtractReviewDataBakingStreams": { + "ExtractReviewIntermediate": { "enabled": True, "viewer_lut_raw": False, "outputs": [ diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 8ecfe0c5da0..3aa97721181 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -189,7 +189,7 @@ A profile may generate multiple outputs from a single input. Each output must de - Profile filtering defines which group of output definitions is used but output definitions may require more specific filters on their own. - They may filter by subset name (regex can be used) or publish families. Publish families are more complex as are based on knowing code base. - Filtering by custom tags -> this is used for targeting to output definitions from other extractors using settings (at this moment only Nuke bake extractor can target using custom tags). - - Nuke extractor settings path: `project_settings/nuke/publish/ExtractReviewDataBakingStreams/outputs/baking/add_custom_tags` + - Nuke extractor settings path: `project_settings/nuke/publish/ExtractReviewIntermediate/outputs/baking/add_custom_tags` - Filtering by input length. Input may be video, sequence or single image. It is possible that `.mp4` should be created only when input is video or sequence and to create review `.png` when input is single frame. In some cases the output should be created even if it's single frame or multi frame input. diff --git a/website/docs/pype2/admin_presets_plugins.md b/website/docs/pype2/admin_presets_plugins.md index a869ead8196..a039c5fbd87 100644 --- a/website/docs/pype2/admin_presets_plugins.md +++ b/website/docs/pype2/admin_presets_plugins.md @@ -534,7 +534,7 @@ Plugin responsible for generating thumbnails with colorspace controlled by Nuke. } ``` -### `ExtractReviewDataBakingStreams` +### `ExtractReviewIntermediate` `viewer_lut_raw` **true** will publish the baked mov file without any colorspace conversion. It will be baked with the workfile workspace. This can happen in case the Viewer input process uses baked screen space luts. #### baking with controlled colorspace From ef12a5229dec982aa525f9fee2c29493263e0faf Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 28 Sep 2023 20:27:55 +0800 Subject: [PATCH 109/111] plural form for extract_review_intermediate --- ...king_streams.py => extract_review_intermediates.py} | 4 ++-- openpype/settings/ayon_settings.py | 2 +- openpype/settings/defaults/project_settings/nuke.json | 2 +- .../projects_schema/schemas/schema_nuke_publish.json | 6 +++--- server_addon/nuke/server/settings/publish_plugins.py | 10 +++++----- .../docs/project_settings/settings_project_global.md | 2 +- website/docs/pype2/admin_presets_plugins.md | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) rename openpype/hosts/nuke/plugins/publish/{extract_review_baking_streams.py => extract_review_intermediates.py} (99%) diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py b/openpype/hosts/nuke/plugins/publish/extract_review_intermediates.py similarity index 99% rename from openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py rename to openpype/hosts/nuke/plugins/publish/extract_review_intermediates.py index 4407c039b4f..2d996b1381b 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_baking_streams.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_intermediates.py @@ -8,7 +8,7 @@ from openpype.hosts.nuke.api.lib import maintained_selection -class ExtractReviewIntermediate(publish.Extractor): +class ExtractReviewIntermediates(publish.Extractor): """Extracts Sequences and thumbnail with baked in luts must be run after extract_render_local.py @@ -32,7 +32,7 @@ def apply_settings(cls, project_settings): """ nuke_publish = project_settings["nuke"]["publish"] deprecated_setting = nuke_publish["ExtractReviewDataMov"] - current_setting = nuke_publish["ExtractReviewIntermediate"] + current_setting = nuke_publish["ExtractReviewIntermediates"] if deprecated_setting["enabled"]: # Use deprecated settings if they are still enabled cls.viewer_lut_raw = deprecated_setting["viewer_lut_raw"] diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index fbf35aec0a3..68693bb953c 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -753,7 +753,7 @@ def _convert_nuke_project_settings(ayon_settings, output): # settings for backwards compatibility deprecrated_review_settings = ayon_publish["ExtractReviewDataMov"] current_review_settings = ( - ayon_publish["ExtractReviewIntermediate"] + ayon_publish["ExtractReviewIntermediates"] ) if deprecrated_review_settings["enabled"]: outputs_settings = deprecrated_review_settings["outputs"] diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 7346c9d7b88..ad9f46c8abe 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -501,7 +501,7 @@ } } }, - "ExtractReviewIntermediate": { + "ExtractReviewIntermediates": { "enabled": true, "viewer_lut_raw": false, "outputs": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json index c14f47a3a7b..fa08e19c63d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json @@ -373,14 +373,14 @@ }, { "type": "label", - "label": "^ Settings and for ExtractReviewDataMov is deprecated and will be soon removed.
Please use ExtractReviewIntermediate instead." + "label": "^ Settings and for ExtractReviewDataMov is deprecated and will be soon removed.
Please use ExtractReviewIntermediates instead." }, { "type": "dict", "collapsible": true, "checkbox_key": "enabled", - "key": "ExtractReviewIntermediate", - "label": "ExtractReviewIntermediate", + "key": "ExtractReviewIntermediates", + "label": "ExtractReviewIntermediates", "is_group": true, "children": [ { diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py index 399aa7e38e8..efb814eff04 100644 --- a/server_addon/nuke/server/settings/publish_plugins.py +++ b/server_addon/nuke/server/settings/publish_plugins.py @@ -177,7 +177,7 @@ class ExtractReviewDataMovModel(BaseSettingsModel): ) -class ExtractReviewBakingStreamsModel(BaseSettingsModel): +class ExtractReviewIntermediatesModel(BaseSettingsModel): enabled: bool = Field(title="Enabled") viewer_lut_raw: bool = Field(title="Viewer lut raw") outputs: list[BakingStreamModel] = Field( @@ -282,9 +282,9 @@ class PublishPuginsModel(BaseSettingsModel): title="Extract Review Data Mov", default_factory=ExtractReviewDataMovModel ) - ExtractReviewIntermediate: ExtractReviewBakingStreamsModel = Field( - title="Extract Review Data Baking Streams", - default_factory=ExtractReviewBakingStreamsModel + ExtractReviewIntermediates: ExtractReviewIntermediatesModel = Field( + title="Extract Review Intermediates", + default_factory=ExtractReviewIntermediatesModel ) ExtractSlateFrame: ExtractSlateFrameModel = Field( title="Extract Slate Frame", @@ -481,7 +481,7 @@ class PublishPuginsModel(BaseSettingsModel): } ] }, - "ExtractReviewIntermediate": { + "ExtractReviewIntermediates": { "enabled": True, "viewer_lut_raw": False, "outputs": [ diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 3aa97721181..27aa60a4644 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -189,7 +189,7 @@ A profile may generate multiple outputs from a single input. Each output must de - Profile filtering defines which group of output definitions is used but output definitions may require more specific filters on their own. - They may filter by subset name (regex can be used) or publish families. Publish families are more complex as are based on knowing code base. - Filtering by custom tags -> this is used for targeting to output definitions from other extractors using settings (at this moment only Nuke bake extractor can target using custom tags). - - Nuke extractor settings path: `project_settings/nuke/publish/ExtractReviewIntermediate/outputs/baking/add_custom_tags` + - Nuke extractor settings path: `project_settings/nuke/publish/ExtractReviewIntermediates/outputs/baking/add_custom_tags` - Filtering by input length. Input may be video, sequence or single image. It is possible that `.mp4` should be created only when input is video or sequence and to create review `.png` when input is single frame. In some cases the output should be created even if it's single frame or multi frame input. diff --git a/website/docs/pype2/admin_presets_plugins.md b/website/docs/pype2/admin_presets_plugins.md index a039c5fbd87..b5e8a3b8a8d 100644 --- a/website/docs/pype2/admin_presets_plugins.md +++ b/website/docs/pype2/admin_presets_plugins.md @@ -534,7 +534,7 @@ Plugin responsible for generating thumbnails with colorspace controlled by Nuke. } ``` -### `ExtractReviewIntermediate` +### `ExtractReviewIntermediates` `viewer_lut_raw` **true** will publish the baked mov file without any colorspace conversion. It will be baked with the workfile workspace. This can happen in case the Viewer input process uses baked screen space luts. #### baking with controlled colorspace From f45552ff79fcc72b38825886584b4cecf9160a43 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 28 Sep 2023 20:29:09 +0800 Subject: [PATCH 110/111] label tweak --- .../hosts/nuke/plugins/publish/extract_review_intermediates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_intermediates.py b/openpype/hosts/nuke/plugins/publish/extract_review_intermediates.py index 2d996b1381b..78fb37e8d78 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_intermediates.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_intermediates.py @@ -16,7 +16,7 @@ class ExtractReviewIntermediates(publish.Extractor): """ order = pyblish.api.ExtractorOrder + 0.01 - label = "Extract Review Intermediate" + label = "Extract Review Intermediates" families = ["review"] hosts = ["nuke"] From f57c1eb8889fea3b29a85ef0e425c909236dcc7a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 28 Sep 2023 23:32:39 +0800 Subject: [PATCH 111/111] edit docsting and rename BakingStreamModel as IntermediateOutputModel --- .../nuke/plugins/publish/extract_review_intermediates.py | 3 ++- server_addon/nuke/server/settings/publish_plugins.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_intermediates.py b/openpype/hosts/nuke/plugins/publish/extract_review_intermediates.py index 78fb37e8d78..da060e3157b 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_intermediates.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_intermediates.py @@ -9,7 +9,8 @@ class ExtractReviewIntermediates(publish.Extractor): - """Extracts Sequences and thumbnail with baked in luts + """Extracting intermediate videos or sequences with + thumbnail for transcoding. must be run after extract_render_local.py diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py index efb814eff04..19206149b6d 100644 --- a/server_addon/nuke/server/settings/publish_plugins.py +++ b/server_addon/nuke/server/settings/publish_plugins.py @@ -149,7 +149,7 @@ class ReformatNodesConfigModel(BaseSettingsModel): ) -class BakingStreamModel(BaseSettingsModel): +class IntermediateOutputModel(BaseSettingsModel): name: str = Field(title="Output name") filter: BakingStreamFilterModel = Field( title="Filter", default_factory=BakingStreamFilterModel) @@ -171,7 +171,7 @@ class ExtractReviewDataMovModel(BaseSettingsModel): """ enabled: bool = Field(title="Enabled") viewer_lut_raw: bool = Field(title="Viewer lut raw") - outputs: list[BakingStreamModel] = Field( + outputs: list[IntermediateOutputModel] = Field( default_factory=list, title="Baking streams" ) @@ -180,7 +180,7 @@ class ExtractReviewDataMovModel(BaseSettingsModel): class ExtractReviewIntermediatesModel(BaseSettingsModel): enabled: bool = Field(title="Enabled") viewer_lut_raw: bool = Field(title="Viewer lut raw") - outputs: list[BakingStreamModel] = Field( + outputs: list[IntermediateOutputModel] = Field( default_factory=list, title="Baking streams" )