From 8e281f4efef4917fbe965f7e49979577e4e6e226 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 10 Jul 2023 13:17:30 +0200 Subject: [PATCH 01/58] simplification of subprocess calls --- openpype/pipeline/colorspace.py | 65 +++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 3f2d4891c1..2ca78f3520 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,7 +222,6 @@ 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))) @@ -236,6 +236,45 @@ def get_data_subprocess(config_path, data_type): 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 = { + "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 compatibility_check(): """Making sure PyOpenColorIO is importable""" try: @@ -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 02/58] 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 2ca78f3520..26e12871f8 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 16558642c6..1c86216347 100644 --- a/openpype/scripts/ocio_wrapper.py +++ b/openpype/scripts/ocio_wrapper.py @@ -27,7 +27,7 @@ import PyOpenColorIO as ocio @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 03/58] 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 26e12871f8..0b23c2b4e3 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 04/58] 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 0b23c2b4e3..72c140195e 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -19,6 +19,7 @@ log = Logger.get_logger(__name__) 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 1c86216347..4332ea5b01 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 c22acee2d4..a6fcc68055 100644 --- a/tests/unit/openpype/pipeline/test_colorspace.py +++ b/tests/unit/openpype/pipeline/test_colorspace.py @@ -185,5 +185,70 @@ class TestPipelineColorspace(TestPipeline): 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 05/58] 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 a6fcc68055..e63ca510f2 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 06/58] 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 72c140195e..13b235d5dd 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 4332ea5b01..1feedde627 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 07/58] 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 02a6b90f25..c90fb299f9 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -2,9 +2,12 @@ from copy import deepcopy 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 08/58] 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 ff15c70077..63266607ce 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 09/58] 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 c90fb299f9..9ea9d1f888 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 e63ca510f2..8ae98f7cf8 100644 --- a/tests/unit/openpype/pipeline/test_colorspace.py +++ b/tests/unit/openpype/pipeline/test_colorspace.py @@ -132,14 +132,14 @@ class TestPipelineColorspace(TestPipeline): 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 10/58] 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 9ea9d1f888..e675bdb2e1 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 11/58] 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 e675bdb2e1..30bd685b13 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -23,6 +23,7 @@ log = Logger.get_logger(__name__) 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 12/58] 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 30bd685b13..c1dc47245a 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 13/58] 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 c1dc47245a..eb3c4c3b94 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 14/58] 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 eb3c4c3b94..70b82d6be2 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 - from openpype.scripts.ocio_wrapper import _get_colorspace_data + CashedData.ocio_config_colorspaces[config_path] = \ + _get_colorspace_data(config_path) + + 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 15/58] 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 70b82d6be2..8378da5b98 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 16/58] hound --- openpype/pipeline/colorspace.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 8378da5b98..1251308eb3 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 17/58] 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 1251308eb3..8025022c59 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -20,7 +20,7 @@ from openpype.pipeline import Anatomy 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 18/58] 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 8025022c59..9c77723d12 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 19/58] optimisation of regex search suggestion from https://github.com/ynput/OpenPype/pull/5273#discussion_r1309372596 --- openpype/pipeline/colorspace.py | 49 +++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 9c77723d12..47227a0e3b 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." ) - colorspaces = colorspaces or get_ocio_config_colorspaces(config_path) - - # 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 + colorspaces = colorspaces or get_ocio_config_colorspaces(config_path) + underscored_colorspaces = { + key.replace(" ", "_"): key for key in colorspaces + if " " in key + } + + # 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 20/58] 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 47227a0e3b..118466bc92 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 21/58] 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 118466bc92..fa9f124130 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 22/58] 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 fa9f124130..20ca60b10e 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 23/58] 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 20ca60b10e..c7eb778fa2 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 24/58] 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 c7eb778fa2..9f1c297188 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 25/58] 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 9f1c297188..f1acd18a70 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 1feedde627..1515cb4e40 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 26/58] 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 f1acd18a70..e315633d41 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 1515cb4e40..56399f10a2 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 27/58] 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 8ae98f7cf8..338627098c 100644 --- a/tests/unit/openpype/pipeline/test_colorspace.py +++ b/tests/unit/openpype/pipeline/test_colorspace.py @@ -227,7 +227,7 @@ class TestPipelineColorspace(TestPipeline): 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 @@ class TestPipelineColorspace(TestPipeline): 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 28/58] 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 e315633d41..a4901b7dfd 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) + 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 + ) - # in case host color management is not enabled - if not config_data: - return None - - 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 29/58] 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 338627098c..435ea709ab 100644 --- a/tests/unit/openpype/pipeline/test_colorspace.py +++ b/tests/unit/openpype/pipeline/test_colorspace.py @@ -196,7 +196,7 @@ class TestPipelineColorspace(TestPipeline): 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 @@ class TestPipelineColorspace(TestPipeline): 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 @@ class TestPipelineColorspace(TestPipeline): # 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 @@ class TestPipelineColorspace(TestPipeline): 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 5b2e9da304697aea73af8cac3616d895e4bdd0d0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Sep 2023 14:52:19 +0200 Subject: [PATCH 30/58] (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 41e6a27cef..8626151beb 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2320,23 +2320,51 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies. # 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 31/58] 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 8626151beb..fb2b5d0f45 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2316,14 +2316,13 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies. ''' 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 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies. ): 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 32/58] 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 a0e1525cd0..1e318e17cf 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -869,6 +869,11 @@ class ExporterReviewMov(ExporterReview): 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 33/58] 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 ae16c13635..454c23a55e 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -24,7 +24,7 @@ log = Logger.get_logger(__name__) 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 435ea709ab..493be786a3 100644 --- a/tests/unit/openpype/pipeline/test_colorspace.py +++ b/tests/unit/openpype/pipeline/test_colorspace.py @@ -227,7 +227,7 @@ class TestPipelineColorspace(TestPipeline): 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 34/58] 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 454c23a55e..677656c02f 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -341,7 +341,7 @@ def parse_colorspace_from_filepath( 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 parse_colorspace_from_filepath( 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 35/58] 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 677656c02f..1bb4624537 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -353,29 +353,28 @@ def parse_colorspace_from_filepath( "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 36/58] 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 1bb4624537..4ece96cfff 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -365,12 +365,12 @@ def parse_colorspace_from_filepath( 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 37/58] 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 4ece96cfff..a1e86dbd64 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 38/58] 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 a1e86dbd64..0cc2f35a49 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -24,7 +24,7 @@ log = Logger.get_logger(__name__) 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 493be786a3..85faa8ff5d 100644 --- a/tests/unit/openpype/pipeline/test_colorspace.py +++ b/tests/unit/openpype/pipeline/test_colorspace.py @@ -227,7 +227,7 @@ class TestPipelineColorspace(TestPipeline): 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 39/58] 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 0cc2f35a49..446849b76e 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -25,7 +25,7 @@ log = Logger.get_logger(__name__) 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 40/58] 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 446849b76e..2dd618a1f2 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 41/58] 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 2dd618a1f2..44cff34c67 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 d498afbf489eefbe3a2733a17d6f8ebe988abf79 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 22 Sep 2023 15:24:17 +0100 Subject: [PATCH 42/58] 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 0000000000..8ff3dccea2 --- /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 4dcda29050..426e330f89 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 0000000000..2279d52111 --- /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 f96dd0ae18..6840509f79 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 7e48155b9e..24fbc0d8e7 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 43/58] 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 0000000000..59dbdbee31 --- /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 44/58] 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 2279d52111..816e125c33 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 60d75300114f9ebba17f3882a81627080ef92ac8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 23 Sep 2023 03:24:52 +0000 Subject: [PATCH 45/58] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 0762eb2f20..87d904fc84 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,8 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.17.1-nightly.2 + - 3.17.1-nightly.1 - 3.17.0 - 3.16.7 - 3.16.7-nightly.2 @@ -133,8 +135,6 @@ body: - 3.14.10-nightly.5 - 3.14.10-nightly.4 - 3.14.10-nightly.3 - - 3.14.10-nightly.2 - - 3.14.10-nightly.1 validations: required: true - type: dropdown From 4c854600cbbc3a7d4d1475893ab3f900d2c92605 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 25 Sep 2023 10:07:37 +0100 Subject: [PATCH 46/58] 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 8ff3dccea2..defa6ed6d9 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 426e330f89..7a1516997a 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 816e125c33..e72146a871 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 59dbdbee31..328f92d020 100644 --- a/openpype/hosts/unreal/plugins/load/load_yeticache.py +++ b/openpype/hosts/unreal/plugins/load/load_yeticache.py @@ -14,7 +14,7 @@ import unreal # noqa 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 6840509f79..57a612c5ae 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 24fbc0d8e7..ce24dad1b5 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 47/58] 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 1e318e17cf..4ce314f6fb 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -580,8 +580,9 @@ class ExporterReview(object): 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 @@ class ExporterReview(object): 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 48/58] 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 4ce314f6fb..6d5d7eddf1 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -588,6 +588,12 @@ class ExporterReview(object): # 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 49/58] 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 328f92d020..92f080d7c5 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 @@ class YetiLoader(plugin.Loader): 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 @@ class YetiLoader(plugin.Loader): 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 50/58] 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 defa6ed6d9..c9f9cd9ba8 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 92f080d7c5..bb6c2d78cc 100644 --- a/openpype/hosts/unreal/plugins/load/load_yeticache.py +++ b/openpype/hosts/unreal/plugins/load/load_yeticache.py @@ -90,18 +90,20 @@ class YetiLoader(plugin.Loader): 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 51/58] 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 bb6c2d78cc..22f5029bac 100644 --- a/openpype/hosts/unreal/plugins/load/load_yeticache.py +++ b/openpype/hosts/unreal/plugins/load/load_yeticache.py @@ -104,7 +104,6 @@ class YetiLoader(plugin.Loader): 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 e0ce8013f46a1cf1c46774f43697930c09a7fd6a Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 25 Sep 2023 11:32:44 +0100 Subject: [PATCH 52/58] 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 e72146a871..963285093e 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 @@ class ExtractYetiCache(publish.Extractor): 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 53/58] 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 44cff34c67..a77dc5763a 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 54/58] 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 a77dc5763a..a67457b1bf 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 b0ae4257f95f56a4d5510a48516945e5bfb4edbb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 25 Sep 2023 15:23:02 +0200 Subject: [PATCH 55/58] 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 a67457b1bf..2800050496 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 parse_colorspace_from_filepath( # 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 1bd07bd15b1bf238e186b23b7d112e9fe4637737 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 25 Sep 2023 18:01:24 +0200 Subject: [PATCH 56/58] 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 ae7a33b7b6..9f603a70d2 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 @@ class PSAutoCreator(AutoCreator): 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 e4229788bd..afde77fdb4 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.pipeline import CreatedInstance 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 @@ class AutoImageCreator(PSAutoCreator): 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 af20d456e0..4f2e90886a 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.pipeline import ( 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 @@ class ImageCreator(Creator): 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 @@ class ImageCreator(Creator): 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 0000000000..ad4feb42ae --- /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 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 57/58] 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 316d8b2a16..4d989ed22c 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 58/58] 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 c5bf526f52..9332177944 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."""