From 2155c33cfa6fe13eb2671bd3441cde393acdb23d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 1 Aug 2024 17:05:23 +0200 Subject: [PATCH 01/13] Add COLOR_VALUE_SEPARATOR constant and update display and view profile formatting. - Added COLOR_VALUE_SEPARATOR constant to separate color values. - Updated the display and view profile formatting method to handle multiple colorspaces separated by the defined separator. --- client/ayon_nuke/api/constants.py | 2 + client/ayon_nuke/api/lib.py | 63 +++++++++++++++++++++++++++---- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/client/ayon_nuke/api/constants.py b/client/ayon_nuke/api/constants.py index 1101997..c681cc0 100644 --- a/client/ayon_nuke/api/constants.py +++ b/client/ayon_nuke/api/constants.py @@ -2,3 +2,5 @@ ASSIST = bool(os.getenv("NUKEASSIST")) + +COLOR_VALUE_SEPARATOR = ";" diff --git a/client/ayon_nuke/api/lib.py b/client/ayon_nuke/api/lib.py index 9055212..aa3e084 100644 --- a/client/ayon_nuke/api/lib.py +++ b/client/ayon_nuke/api/lib.py @@ -48,7 +48,7 @@ ) from ayon_core.pipeline.workfile import BuildWorkfile from . import gizmo_menu -from .constants import ASSIST +from .constants import ASSIST, COLOR_VALUE_SEPARATOR from .workio import save_file from .utils import get_node_outputs @@ -1452,6 +1452,8 @@ class WorkfileSettings(object): """ + _display_and_view_colorspaces = None + def __init__(self, root_node=None, nodes=None, **kwargs): project_entity = kwargs.get("project") if project_entity is None: @@ -1545,7 +1547,12 @@ def set_viewers_colorspace(self, imageio_nuke): "It had wrong color profile".format(erased_viewers)) def _display_and_view_formatted(self, view_profile): - """ Format display and view profile string + """ Format display and view profile. + + Gets all possible display and view colorspaces and tries to set + viewerProcess to the one that is found in settings. It is iterating + over all possible combinations of display and view colorspaces. Those + could be separated by color value separator defined in constants. Args: view_profile (dict): view and display profile @@ -1553,11 +1560,53 @@ def _display_and_view_formatted(self, view_profile): Returns: str: formatted display and view profile string """ - display_view = create_viewer_profile_string( - view_profile["view"], view_profile["display"], path_like=False - ) - # format any template tokens used in the string - return StringTemplate(display_view).format_strict(self.formatting_data) + # default values + views = [view_profile["view"]] + displays = [view_profile["display"]] + + # separate all values by path separator + if COLOR_VALUE_SEPARATOR in view_profile["view"]: + views = view_profile["view"].split( + COLOR_VALUE_SEPARATOR) + if COLOR_VALUE_SEPARATOR in view_profile["display"]: + displays = view_profile["display"].split( + COLOR_VALUE_SEPARATOR) + + # generate all possible combination of display/view + display_views = [] + for view in views: + for display in displays: + display_views.append( + create_viewer_profile_string( + view.strip(), display.strip(), path_like=False + ) + ) + + for dv_item in display_views: + log.debug("Trying to set viewerProcess: `{}`".format(dv_item)) + # format any template tokens used in the string + dv_item_resolved = StringTemplate(dv_item).format_strict( + self.formatting_data + ) + log.info("Resolved viewerProcess: `{}`".format(dv_item_resolved)) + + if dv_item_resolved in self.get_display_and_view_colorspaces(): + return dv_item_resolved + + def get_display_and_view_colorspaces(self): + """ Get all possible display and view colorspaces + + This is stored in class variable to avoid multiple calls. + + Returns: + list: all possible display and view colorspaces + """ + if not self._display_and_view_colorspaces: + colorspace_knob = self._root_node["monitorLut"] + colorspaces = nuke.getColorspaceList(colorspace_knob) + self._display_and_view_colorspaces = colorspaces + + return self._display_and_view_colorspaces def set_root_colorspace(self, imageio_host): ''' Adds correct colorspace to root From 36bec51fa79733a0146e17b59858cc04d26bba84 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 1 Aug 2024 17:05:45 +0200 Subject: [PATCH 02/13] Update display and view settings Update display and view settings descriptions to include dynamic value setting and fallback options using semicolon separators. Added detailed descriptions for display and view settings, explaining dynamic value setting with anatomy context tokens and fallback options using semicolons. Examples provided. --- server/settings/common.py | 14 +++++++++++--- server/settings/imageio.py | 26 ++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/server/settings/common.py b/server/settings/common.py index 2ddbc3c..16a7c0f 100644 --- a/server/settings/common.py +++ b/server/settings/common.py @@ -147,15 +147,23 @@ class DisplayAndViewProfileModel(BaseSettingsModel): display: str = SettingsField( "", title="Display", - description="What display to use", + description=( + "What display to use. Anatomy context tokens can " + "be used to dynamically set the value. And also fallback can " + "be defined via ';' (semicolon) separator. \n" + "Example: \n'{project[code]} ; ACES'.\n" + "Note that we are stripping the spaces around the separator." + ), ) - view: str = SettingsField( "", title="View", description=( "What view to use. Anatomy context tokens can " - "be used to dynamically set the value." + "be used to dynamically set the value. And also fallback can " + "be defined via ';' separator. \nExample: \n" + "'{project[code]}_{parent}_{folder[name]} ; sRGB'.\n" + "Note that we are stripping the spaces around the separator." ), ) diff --git a/server/settings/imageio.py b/server/settings/imageio.py index 22798b2..89d469e 100644 --- a/server/settings/imageio.py +++ b/server/settings/imageio.py @@ -146,14 +146,23 @@ class ViewProcessModel(BaseSettingsModel): display: str = SettingsField( "", title="Display", - description="What display to use", + description=( + "What display to use. Anatomy context tokens can " + "be used to dynamically set the value. And also fallback can " + "be defined via ';' (semicolon) separator. \n" + "Example: \n'{project[code]} ; ACES'.\n" + "Note that we are stripping the spaces around the separator." + ), ) view: str = SettingsField( "", title="View", description=( "What view to use. Anatomy context tokens can " - "be used to dynamically set the value." + "be used to dynamically set the value. And also fallback can " + "be defined via ';' separator. \nExample: \n" + "'{project[code]}_{parent}_{folder[name]} ; sRGB'.\n" + "Note that we are stripping the spaces around the separator." ), ) @@ -164,14 +173,23 @@ class MonitorProcessModel(BaseSettingsModel): display: str = SettingsField( "", title="Display", - description="What display to use", + description=( + "What display to use. Anatomy context tokens can " + "be used to dynamically set the value. And also fallback can " + "be defined via ';' (semicolon) separator. \n" + "Example: \n'{project[code]} ; ACES'.\n" + "Note that we are stripping the spaces around the separator." + ), ) view: str = SettingsField( "", title="View", description=( "What view to use. Anatomy context tokens can " - "be used to dynamically set the value." + "be used to dynamically set the value. And also fallback can " + "be defined via ';' separator. \nExample: \n" + "'{project[code]}_{parent}_{folder[name]} ; sRGB'.\n" + "Note that we are stripping the spaces around the separator." ), ) From 8d6070b75f7f88c54749cecfa76af7e21d6da875 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Aug 2024 12:38:54 +0200 Subject: [PATCH 03/13] Add Nuke Colorspace related methods and caching mechanisms. Include functions to get colorspaces, check colorspace existence on a node, create viewer profile strings, format display and view profiles into strings or dictionaries, and format colorspace profile names. - Added methods for retrieving display and view colorspaces - Implemented function to obtain available colorspace profile names - Included functionality to check if a colorspace exists on a node - Created functions for generating viewer profile strings - Developed methods for formatting display and view profiles into strings or dictionaries - Added function to format colorspace profile names --- client/ayon_nuke/api/colorspace.py | 363 +++++++++++++++++++++++++++++ 1 file changed, 363 insertions(+) create mode 100644 client/ayon_nuke/api/colorspace.py diff --git a/client/ayon_nuke/api/colorspace.py b/client/ayon_nuke/api/colorspace.py new file mode 100644 index 0000000..17ec273 --- /dev/null +++ b/client/ayon_nuke/api/colorspace.py @@ -0,0 +1,363 @@ +""" +Nuke Colorspace related methods +""" + +from logging import root +import re + +from ayon_core.lib import ( + Logger, + StringTemplate, +) + +from .constants import COLOR_VALUE_SEPARATOR + +import nuke + +log = Logger.get_logger(__name__) + + +DISPLAY_AND_VIEW_COLORSPACES_CACHE = {} +COLORSPACES_CACHE = {} + + +def get_display_and_view_colorspaces(root_node: nuke.Node) -> list: + """Get all possible display and view colorspaces + + This is stored in class variable to avoid multiple calls. + + Args: + root_node (nuke.Node): root node + + Returns: + list: all possible display and view colorspaces + """ + global DISPLAY_AND_VIEW_COLORSPACES_CACHE + + script_name = nuke.root().name() + if not DISPLAY_AND_VIEW_COLORSPACES_CACHE.get(script_name): + colorspace_knob = root_node["monitorLut"] + colorspaces = nuke.getColorspaceList(colorspace_knob) + DISPLAY_AND_VIEW_COLORSPACES_CACHE[script_name] = colorspaces + + return DISPLAY_AND_VIEW_COLORSPACES_CACHE[script_name] + + +def get_colorspace_list( + colorspace_knob: nuke.Knob, + node: nuke.Node = None, +) -> list: + """Get available colorspace profile names + + Args: + colorspace_knob (nuke.Knob): nuke knob object + node (Optional[nuke.Node]): nuke node for caching differentiation + + Returns: + list: list of strings names of profiles + """ + results = [] + + # making sure any node is provided + node = node or nuke.root() + # unique script based identifier + script_name = nuke.root().name() + node_name = node.fullName() + identifier_key = f"{script_name}_{node_name}" + + if not COLORSPACES_CACHE.get(identifier_key): + # This pattern is to match with roles which uses an indentation and + # parentheses with original colorspace. The value returned from the + # colorspace is the string before the indentation, so we'll need to + # convert the values to match with value returned from the knob, + # ei. knob.value(). + pattern = r".*\t.* \(.*\)" + for colorspace in nuke.getColorspaceList(colorspace_knob): + match = re.search(pattern, colorspace) + if match: + results.append(colorspace.split("\t", 1)[0]) + else: + results.append(colorspace) + + COLORSPACES_CACHE[identifier_key] = results + + return COLORSPACES_CACHE[identifier_key] + + +def colorspace_exists_on_node(node: nuke.Node, colorspace_name: str) -> bool: + """ Check if colorspace exists on node + + Look through all options in the colorspace knob, and see if we have an + exact match to one of the items. + + Args: + node (nuke.Node): nuke node object + colorspace_name (str): color profile name + + Returns: + bool: True if exists + """ + node_knob_keys = node.knobs().keys() + + if "colorspace" in node_knob_keys: + colorspace_knob = node["colorspace"] + elif "floatLut" in node_knob_keys: + colorspace_knob = node["floatLut"] + else: + log.warning(f"Node '{node.name()}' does not have colorspace knob") + return False + + return colorspace_name in get_colorspace_list(colorspace_knob, node) + + +def create_viewer_profile_string(viewer, display=None, path_like=False) -> str: + """Convert viewer and display to string + + Args: + viewer (str): viewer name + display (Optional[str]): display name + path_like (Optional[bool]): if True, return path like string + + Returns: + str: viewer config string + """ + if not display: + return viewer + + if path_like: + return "{}/{}".format(display, viewer) + return "{} ({})".format(viewer, display) + + +def get_formatted_display_and_view( + view_profile: dict, + formatting_data: dict, + root_node: nuke.Node = None, +) -> str: + """Format display and view profile into string. + + This method is formatting a display and view profile. It is iterating + over all possible combinations of display and view colorspaces. Those + could be separated by COLOR_VALUE_SEPARATOR defined in constants. + + If Anatomy template tokens are used but formatting data is not provided, + it will try any other available variants in next position of the separator. + + This method also validate that the formatted display and view profile is + available in currently run nuke session ocio config. + + Example: + >>> from ayon_nuke.api.colorspace import get_formatted_display_and_view + >>> view_profile = { + ... "view": "{context};sRGB", + ... "display": "{project_code};ACES" + ... } + >>> formatting_data = { + ... "context": "01sh010", + ... "project_code": "proj01" + ...} + >>> display_and_view = get_formatted_display_and_view( + ... view_profile, formatting_data) + >>> print(display_and_view) + "01sh010 (proj01)" + + + Args: + view_profile (dict): view and display profile + formatting_data (dict): formatting data + root_node (Optional[nuke.Node]): root node + + Returns: + str: formatted display and view profile string + ex: "sRGB (ACES)" + """ + if not root_node: + root_node = nuke.root() + + views = [view_profile["view"]] + displays = [] + + # display could be optional in case nuke_default ocio config is used + if view_profile["display"]: + displays.append(view_profile["display"]) + + # separate all values by path separator + if COLOR_VALUE_SEPARATOR in view_profile["view"]: + views = view_profile["view"].split(COLOR_VALUE_SEPARATOR) + if COLOR_VALUE_SEPARATOR in view_profile["display"]: + displays = view_profile["display"].split(COLOR_VALUE_SEPARATOR) + + # generate all possible combination of display/view + display_views = [] + for view in views: + + if displays: + for display in displays: + display_views.append( + create_viewer_profile_string( + view.strip(), display.strip(), path_like=False + ) + ) + else: + display_views.append(view.strip()) + + for dv_item in display_views: + # format any template tokens used in the string + dv_item_resolved = StringTemplate(dv_item).format_strict( + formatting_data) + log.debug("Resolved display and view: `{}`".format(dv_item_resolved)) + + # making sure formatted colorspace exists in running session + if dv_item_resolved in get_display_and_view_colorspaces(root_node): + return dv_item_resolved + + +def get_formatted_display_and_view_as_dict( + view_profile: dict, + formatting_data: dict, + root_node: nuke.Node = None, +) -> dict: + """Format display and view profile into dict. + + This method is formatting a display and view profile. It is iterating + over all possible combinations of display and view colorspaces. Those + could be separated by COLOR_VALUE_SEPARATOR defined in constants. + + If Anatomy template tokens are used but formatting data is not provided, + it will try any other available variants in next position of the separator. + + This method also validate that the formatted display and view profile is + available in currently run nuke session ocio config. + + Example: + >>> from ayon_nuke.api.colorspace import get_formatted_display_and_view_as_dict # noqa + >>> view_profile = { + ... "view": "{context};sRGB", + ... "display": "{project_code};ACES" + ... } + >>> formatting_data = { + ... "context": "01sh010", + ... "project_code": "proj01" + ...} + >>> display_and_view = get_formatted_display_and_view_as_dict( + ... view_profile, formatting_data) + >>> print(display_and_view) + {"view": "01sh010", "display": "proj01"} + + + Args: + view_profile (dict): view and display profile + formatting_data (dict): formatting data + root_node (Optional[nuke.Node]): root node + + Returns: + dict: formatted display and view profile in dict + ex: {"view": "sRGB", "display": "ACES"} + """ + if not root_node: + root_node = nuke.root() + + views = [view_profile["view"]] + displays = [] + + # display could be optional in case nuke_default ocio config is used + if view_profile["display"]: + displays.append(view_profile["display"]) + + # separate all values by path separator + if COLOR_VALUE_SEPARATOR in view_profile["view"]: + views = view_profile["view"].split(COLOR_VALUE_SEPARATOR) + if COLOR_VALUE_SEPARATOR in view_profile["display"]: + displays = view_profile["display"].split(COLOR_VALUE_SEPARATOR) + + # generate all possible combination of display/view + display_views = [] + for view in views: + + if displays: + for display in displays: + display_views.append( + {"view": view.strip(), "display": display.strip()}) + else: + display_views.append({"view": view.strip(), "display": None}) + + for dv_item in display_views: + # format any template tokens used in the string + profile = { + "view": StringTemplate(dv_item["view"]).format_strict( + formatting_data), + "display": StringTemplate(dv_item["display"]).format_strict( + formatting_data) if dv_item["display"] else None, + } + log.debug("Resolved profile: `{}`".format(profile)) + + # making sure formatted colorspace exists in running session + # also need to test case where display is None + test_string = profile["view"] + if profile["display"]: + test_string = create_viewer_profile_string( + profile["view"], profile["display"], path_like=False + ) + + if test_string in get_display_and_view_colorspaces(root_node): + return profile + + +def get_formatted_colorspace( + colorspace_name: str, + formatting_data: dict, + root_node: nuke.Node = None, +) -> str: + """Format colorspace profile name into string. + + This method is formatting colorspace profile name. It is iterating + over all possible combinations of input string which could be separated + by COLOR_VALUE_SEPARATOR defined in constants. + + If Anatomy template tokens are used but formatting data is not provided, + it will try any other available variants in next position of the separator. + + This method also validate that the formatted colorspace profile name is + available in currently run nuke session ocio config. + + Example: + >>> from ayon_nuke.api.colorspace import get_formatted_colorspace + >>> colorspace_name = "{project_code}_{context};ACES - ACEScg" + >>> formatting_data = { + ... "context": "01sh010", + ... "project_code": "proj01" + ...} + >>> new_colorspace_name = get_formatted_colorspace( + ... colorspace_name, formatting_data) + >>> print(new_colorspace_name) + "proj01_01sh010" + + + Args: + colorspace_name (str): colorspace profile name + formatting_data (dict): formatting data + root_node (Optional[nuke.Node]): root node + + Returns: + str: formatted colorspace profile string + ex: "ACES - ACEScg" + """ + if not root_node: + root_node = nuke.root() + + colorspaces = [colorspace_name] + + # separate all values by path separator + if COLOR_VALUE_SEPARATOR in colorspace_name: + colorspaces = colorspace_name.split(COLOR_VALUE_SEPARATOR) + + # iterate via all found colorspaces + for citem in colorspaces: + # format any template tokens used in the string + citem_resolved = StringTemplate(citem.strip()).format_strict( + formatting_data) + log.debug("Resolved colorspace: `{}`".format(citem_resolved)) + + # making sure formatted colorspace exists in running session + if colorspace_exists_on_node(root_node, citem_resolved): + return citem_resolved From 8952e2bb90e1fbf20d6a4ceeb8d357b7a40fcee5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Aug 2024 12:39:20 +0200 Subject: [PATCH 04/13] Refactor: Move colorspace-related functions to separate module Detailed description: - Moved colorspace functions from utils to a new module called colorspace - Updated imports and references in lib.py - Added TODOs for further refactoring of related functions --- client/ayon_nuke/api/__init__.py | 2 +- client/ayon_nuke/api/lib.py | 80 +++++--------------------------- client/ayon_nuke/api/utils.py | 50 -------------------- 3 files changed, 13 insertions(+), 119 deletions(-) diff --git a/client/ayon_nuke/api/__init__.py b/client/ayon_nuke/api/__init__.py index caefba7..3ed0e32 100644 --- a/client/ayon_nuke/api/__init__.py +++ b/client/ayon_nuke/api/__init__.py @@ -45,7 +45,7 @@ create_write_node, link_knobs ) -from .utils import ( +from .colorspace import ( colorspace_exists_on_node, get_colorspace_list ) diff --git a/client/ayon_nuke/api/lib.py b/client/ayon_nuke/api/lib.py index aa3e084..8131e80 100644 --- a/client/ayon_nuke/api/lib.py +++ b/client/ayon_nuke/api/lib.py @@ -53,6 +53,8 @@ from .workio import save_file from .utils import get_node_outputs +from .colorspace import get_formatted_display_and_view + log = Logger.get_logger(__name__) MENU_LABEL = os.getenv("AYON_MENU_LABEL") or "AYON" @@ -744,6 +746,7 @@ def get_imageio_node_override_setting( return knobs_settings +# TODO: move into ./colorspace.py def get_imageio_input_colorspace(filename): ''' Get input file colorspace based on regex in settings. ''' @@ -1452,8 +1455,6 @@ class WorkfileSettings(object): """ - _display_and_view_colorspaces = None - def __init__(self, root_node=None, nodes=None, **kwargs): project_entity = kwargs.get("project") if project_entity is None: @@ -1492,6 +1493,7 @@ def get_nodes(self, nodes=None, nodes_filter=None): for filter in nodes_filter: return [n for n in self._nodes if filter in n.Class()] + # TODO: move into ./colorspace.py def set_viewers_colorspace(self, imageio_nuke): ''' Adds correct colorspace to viewer @@ -1504,11 +1506,11 @@ def set_viewers_colorspace(self, imageio_nuke): "wipe_position", "monitorOutOutputTransform" ] - viewer_process = self._display_and_view_formatted( - imageio_nuke["viewer"] + viewer_process = get_formatted_display_and_view( + imageio_nuke["viewer"], self.formatting_data, self._root_node ) - output_transform = self._display_and_view_formatted( - imageio_nuke["monitor"] + output_transform = get_formatted_display_and_view( + imageio_nuke["monitor"], self.formatting_data, self._root_node ) erased_viewers = [] for v in nuke.allNodes(filter="Viewer"): @@ -1546,68 +1548,7 @@ def set_viewers_colorspace(self, imageio_nuke): "Attention! Viewer nodes {} were erased." "It had wrong color profile".format(erased_viewers)) - def _display_and_view_formatted(self, view_profile): - """ Format display and view profile. - - Gets all possible display and view colorspaces and tries to set - viewerProcess to the one that is found in settings. It is iterating - over all possible combinations of display and view colorspaces. Those - could be separated by color value separator defined in constants. - - Args: - view_profile (dict): view and display profile - - Returns: - str: formatted display and view profile string - """ - # default values - views = [view_profile["view"]] - displays = [view_profile["display"]] - - # separate all values by path separator - if COLOR_VALUE_SEPARATOR in view_profile["view"]: - views = view_profile["view"].split( - COLOR_VALUE_SEPARATOR) - if COLOR_VALUE_SEPARATOR in view_profile["display"]: - displays = view_profile["display"].split( - COLOR_VALUE_SEPARATOR) - - # generate all possible combination of display/view - display_views = [] - for view in views: - for display in displays: - display_views.append( - create_viewer_profile_string( - view.strip(), display.strip(), path_like=False - ) - ) - - for dv_item in display_views: - log.debug("Trying to set viewerProcess: `{}`".format(dv_item)) - # format any template tokens used in the string - dv_item_resolved = StringTemplate(dv_item).format_strict( - self.formatting_data - ) - log.info("Resolved viewerProcess: `{}`".format(dv_item_resolved)) - - if dv_item_resolved in self.get_display_and_view_colorspaces(): - return dv_item_resolved - - def get_display_and_view_colorspaces(self): - """ Get all possible display and view colorspaces - - This is stored in class variable to avoid multiple calls. - - Returns: - list: all possible display and view colorspaces - """ - if not self._display_and_view_colorspaces: - colorspace_knob = self._root_node["monitorLut"] - colorspaces = nuke.getColorspaceList(colorspace_knob) - self._display_and_view_colorspaces = colorspaces - - return self._display_and_view_colorspaces - + # TODO: move into ./colorspace.py def set_root_colorspace(self, imageio_host): ''' Adds correct colorspace to root @@ -1867,6 +1808,7 @@ def _replace_ocio_path_with_env_var(self, config_data): return new_path + # TODO: move into ./colorspace.py def set_writes_colorspace(self): ''' Adds correct colorspace to write node dict @@ -1944,6 +1886,7 @@ def set_writes_colorspace(self): set_node_knobs_from_settings( write_node, nuke_imageio_writes["knobs"]) + # TODO: move into ./colorspace.py def set_reads_colorspace(self, read_clrs_inputs): """ Setting colorspace to Read nodes @@ -1991,6 +1934,7 @@ def set_reads_colorspace(self, read_clrs_inputs): nname, knobs["to"])) + # TODO: move into ./colorspace.py def set_colorspace(self): ''' Setting colorspace following presets ''' diff --git a/client/ayon_nuke/api/utils.py b/client/ayon_nuke/api/utils.py index 646bb0e..dc21537 100644 --- a/client/ayon_nuke/api/utils.py +++ b/client/ayon_nuke/api/utils.py @@ -1,5 +1,4 @@ import os -import re import nuke @@ -93,55 +92,6 @@ def bake_gizmos_recursively(in_group=None): bake_gizmos_recursively(node) -def colorspace_exists_on_node(node, colorspace_name): - """ Check if colorspace exists on node - - Look through all options in the colorspace knob, and see if we have an - exact match to one of the items. - - Args: - node (nuke.Node): nuke node object - colorspace_name (str): color profile name - - Returns: - bool: True if exists - """ - try: - colorspace_knob = node['colorspace'] - except ValueError: - # knob is not available on input node - return False - - return colorspace_name in get_colorspace_list(colorspace_knob) - - -def get_colorspace_list(colorspace_knob): - """Get available colorspace profile names - - Args: - colorspace_knob (nuke.Knob): nuke knob object - - Returns: - list: list of strings names of profiles - """ - results = [] - - # This pattern is to match with roles which uses an indentation and - # parentheses with original colorspace. The value returned from the - # colorspace is the string before the indentation, so we'll need to - # convert the values to match with value returned from the knob, - # ei. knob.value(). - pattern = r".*\t.* \(.*\)" - for colorspace in nuke.getColorspaceList(colorspace_knob): - match = re.search(pattern, colorspace) - if match: - results.append(colorspace.split("\t", 1)[0]) - else: - results.append(colorspace) - - return results - - def is_headless(): """ Returns: From 88777e93c46683ec686e87f1321aaa4bf18d9758 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Aug 2024 12:40:00 +0200 Subject: [PATCH 05/13] Update plugin imports and formatting functions Add new colorspace methods, and enhance display and view handling in ExporterReviewMov class. - Updated plugin imports to include new functions - Added methods for formatting anatomy and colorspace - Enhanced display and view handling logic --- client/ayon_nuke/api/plugin.py | 43 +++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/client/ayon_nuke/api/plugin.py b/client/ayon_nuke/api/plugin.py index 860b5c2..0597642 100644 --- a/client/ayon_nuke/api/plugin.py +++ b/client/ayon_nuke/api/plugin.py @@ -41,13 +41,17 @@ get_node_data, get_view_process_node, get_filenames_without_hash, - link_knobs + link_knobs, + format_anatomy, ) from .pipeline import ( list_instances, remove_instance ) -from ayon_nuke.api.lib import format_anatomy +from .colorspace import ( + get_formatted_display_and_view_as_dict, + get_formatted_colorspace +) def _collect_and_cache_nodes(creator): @@ -964,25 +968,27 @@ def generate_mov(self, farm=False, delete=True, **kwargs): if baking_colorspace["type"] == "display_view": display_view = baking_colorspace["display_view"] - message = "OCIODisplay... '{}'" - node = nuke.createNode("OCIODisplay") + display_view_f = get_formatted_display_and_view_as_dict( + display_view, self.formatting_data + ) + + if not display_view_f: + raise ValueError( + "Invalid display and view profile: " + f"'{display_view}'" + ) # assign display and view - display = display_view["display"] - view = display_view["view"] + display = display_view_f["display"] + view = display_view_f["view"] + + message = "OCIODisplay... '{}'" + node = nuke.createNode("OCIODisplay") # display could not be set in nuke_default config if display: - # format display string with anatomy data - display = StringTemplate(display).format_strict( - self.formatting_data - ) node["display"].setValue(display) - # format view string with anatomy data - view = StringTemplate(view).format_strict( - self.formatting_data) - # assign viewer node["view"].setValue(view) if config_data: @@ -996,8 +1002,13 @@ def generate_mov(self, farm=False, delete=True, **kwargs): elif baking_colorspace["type"] == "colorspace": baking_colorspace = baking_colorspace["colorspace"] # format colorspace string with anatomy data - baking_colorspace = StringTemplate( - baking_colorspace).format_strict(self.formatting_data) + baking_colorspace = get_formatted_colorspace( + baking_colorspace, self.formatting_data + ) + if not baking_colorspace: + raise ValueError( + f"Invalid baking color space: '{baking_colorspace}'" + ) node = nuke.createNode("OCIOColorSpace") message = "OCIOColorSpace... '{}'" # no need to set input colorspace since it is driven by From 3c73fc22f72cc0c175845401f09b3a40f01b00cc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Aug 2024 12:45:03 +0200 Subject: [PATCH 06/13] Refactor splitting logic for view and display values Simplify the code by handling view and display value splitting consistently. --- client/ayon_nuke/api/colorspace.py | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/client/ayon_nuke/api/colorspace.py b/client/ayon_nuke/api/colorspace.py index 17ec273..9e052ff 100644 --- a/client/ayon_nuke/api/colorspace.py +++ b/client/ayon_nuke/api/colorspace.py @@ -174,18 +174,14 @@ def get_formatted_display_and_view( if not root_node: root_node = nuke.root() - views = [view_profile["view"]] - displays = [] + views = view_profile["view"].split(COLOR_VALUE_SEPARATOR) + displays = view_profile["display"].split(COLOR_VALUE_SEPARATOR) # display could be optional in case nuke_default ocio config is used if view_profile["display"]: - displays.append(view_profile["display"]) - - # separate all values by path separator - if COLOR_VALUE_SEPARATOR in view_profile["view"]: - views = view_profile["view"].split(COLOR_VALUE_SEPARATOR) - if COLOR_VALUE_SEPARATOR in view_profile["display"]: displays = view_profile["display"].split(COLOR_VALUE_SEPARATOR) + else: + displays = [] # generate all possible combination of display/view display_views = [] @@ -257,18 +253,14 @@ def get_formatted_display_and_view_as_dict( if not root_node: root_node = nuke.root() - views = [view_profile["view"]] - displays = [] + views = view_profile["view"].split(COLOR_VALUE_SEPARATOR) + displays = view_profile["display"].split(COLOR_VALUE_SEPARATOR) # display could be optional in case nuke_default ocio config is used if view_profile["display"]: - displays.append(view_profile["display"]) - - # separate all values by path separator - if COLOR_VALUE_SEPARATOR in view_profile["view"]: - views = view_profile["view"].split(COLOR_VALUE_SEPARATOR) - if COLOR_VALUE_SEPARATOR in view_profile["display"]: displays = view_profile["display"].split(COLOR_VALUE_SEPARATOR) + else: + displays = [] # generate all possible combination of display/view display_views = [] @@ -345,11 +337,7 @@ def get_formatted_colorspace( if not root_node: root_node = nuke.root() - colorspaces = [colorspace_name] - - # separate all values by path separator - if COLOR_VALUE_SEPARATOR in colorspace_name: - colorspaces = colorspace_name.split(COLOR_VALUE_SEPARATOR) + colorspaces = colorspace_name.split(COLOR_VALUE_SEPARATOR) # iterate via all found colorspaces for citem in colorspaces: From e7c92d45493726e0420421fb07b6dad74909a45a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Aug 2024 14:01:59 +0200 Subject: [PATCH 07/13] Removing typing in functions --- client/ayon_nuke/api/colorspace.py | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/client/ayon_nuke/api/colorspace.py b/client/ayon_nuke/api/colorspace.py index 9e052ff..f1c8199 100644 --- a/client/ayon_nuke/api/colorspace.py +++ b/client/ayon_nuke/api/colorspace.py @@ -21,7 +21,7 @@ COLORSPACES_CACHE = {} -def get_display_and_view_colorspaces(root_node: nuke.Node) -> list: +def get_display_and_view_colorspaces(root_node): """Get all possible display and view colorspaces This is stored in class variable to avoid multiple calls. @@ -43,10 +43,7 @@ def get_display_and_view_colorspaces(root_node: nuke.Node) -> list: return DISPLAY_AND_VIEW_COLORSPACES_CACHE[script_name] -def get_colorspace_list( - colorspace_knob: nuke.Knob, - node: nuke.Node = None, -) -> list: +def get_colorspace_list(colorspace_knob, node=None): """Get available colorspace profile names Args: @@ -84,7 +81,7 @@ def get_colorspace_list( return COLORSPACES_CACHE[identifier_key] -def colorspace_exists_on_node(node: nuke.Node, colorspace_name: str) -> bool: +def colorspace_exists_on_node(node, colorspace_name): """ Check if colorspace exists on node Look through all options in the colorspace knob, and see if we have an @@ -110,7 +107,7 @@ def colorspace_exists_on_node(node: nuke.Node, colorspace_name: str) -> bool: return colorspace_name in get_colorspace_list(colorspace_knob, node) -def create_viewer_profile_string(viewer, display=None, path_like=False) -> str: +def create_viewer_profile_string(viewer, display=None, path_like=False): """Convert viewer and display to string Args: @@ -130,10 +127,7 @@ def create_viewer_profile_string(viewer, display=None, path_like=False) -> str: def get_formatted_display_and_view( - view_profile: dict, - formatting_data: dict, - root_node: nuke.Node = None, -) -> str: + view_profile, formatting_data, root_node=None): """Format display and view profile into string. This method is formatting a display and view profile. It is iterating @@ -209,10 +203,7 @@ def get_formatted_display_and_view( def get_formatted_display_and_view_as_dict( - view_profile: dict, - formatting_data: dict, - root_node: nuke.Node = None, -) -> dict: + view_profile, formatting_data, root_node=None): """Format display and view profile into dict. This method is formatting a display and view profile. It is iterating @@ -296,10 +287,7 @@ def get_formatted_display_and_view_as_dict( def get_formatted_colorspace( - colorspace_name: str, - formatting_data: dict, - root_node: nuke.Node = None, -) -> str: + colorspace_name, formatting_data, root_node=None): """Format colorspace profile name into string. This method is formatting colorspace profile name. It is iterating From 18bba07fbff460191012f3410455359580151752 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Aug 2024 17:24:17 +0200 Subject: [PATCH 08/13] Remove unused import statement from colorspace module The commit removes an unused import statement from the colorspace module. --- client/ayon_nuke/api/colorspace.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_nuke/api/colorspace.py b/client/ayon_nuke/api/colorspace.py index f1c8199..cdd684e 100644 --- a/client/ayon_nuke/api/colorspace.py +++ b/client/ayon_nuke/api/colorspace.py @@ -2,7 +2,6 @@ Nuke Colorspace related methods """ -from logging import root import re from ayon_core.lib import ( From ba6475b0c9cbf1ad5f05dc19f0496e58dd89dd74 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 15 Aug 2024 11:02:41 +0200 Subject: [PATCH 09/13] Refactor caches to use prefixed variables - Renamed global cache variables for clarity and consistency. - Updated cache access and assignment throughout the code. --- client/ayon_nuke/api/colorspace.py | 56 ++++++++++++++---------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/client/ayon_nuke/api/colorspace.py b/client/ayon_nuke/api/colorspace.py index cdd684e..079ad78 100644 --- a/client/ayon_nuke/api/colorspace.py +++ b/client/ayon_nuke/api/colorspace.py @@ -16,8 +16,8 @@ log = Logger.get_logger(__name__) -DISPLAY_AND_VIEW_COLORSPACES_CACHE = {} -COLORSPACES_CACHE = {} +_DISPLAY_AND_VIEW_COLORSPACES_CACHE = {} +_COLORSPACES_CACHE = {} def get_display_and_view_colorspaces(root_node): @@ -31,15 +31,13 @@ def get_display_and_view_colorspaces(root_node): Returns: list: all possible display and view colorspaces """ - global DISPLAY_AND_VIEW_COLORSPACES_CACHE - script_name = nuke.root().name() - if not DISPLAY_AND_VIEW_COLORSPACES_CACHE.get(script_name): + if _DISPLAY_AND_VIEW_COLORSPACES_CACHE.get(script_name) is None: colorspace_knob = root_node["monitorLut"] colorspaces = nuke.getColorspaceList(colorspace_knob) - DISPLAY_AND_VIEW_COLORSPACES_CACHE[script_name] = colorspaces + _DISPLAY_AND_VIEW_COLORSPACES_CACHE[script_name] = colorspaces - return DISPLAY_AND_VIEW_COLORSPACES_CACHE[script_name] + return _DISPLAY_AND_VIEW_COLORSPACES_CACHE[script_name] def get_colorspace_list(colorspace_knob, node=None): @@ -61,7 +59,7 @@ def get_colorspace_list(colorspace_knob, node=None): node_name = node.fullName() identifier_key = f"{script_name}_{node_name}" - if not COLORSPACES_CACHE.get(identifier_key): + if _COLORSPACES_CACHE.get(identifier_key) is None: # This pattern is to match with roles which uses an indentation and # parentheses with original colorspace. The value returned from the # colorspace is the string before the indentation, so we'll need to @@ -75,9 +73,9 @@ def get_colorspace_list(colorspace_knob, node=None): else: results.append(colorspace) - COLORSPACES_CACHE[identifier_key] = results + _COLORSPACES_CACHE[identifier_key] = results - return COLORSPACES_CACHE[identifier_key] + return _COLORSPACES_CACHE[identifier_key] def colorspace_exists_on_node(node, colorspace_name): @@ -168,27 +166,26 @@ def get_formatted_display_and_view( root_node = nuke.root() views = view_profile["view"].split(COLOR_VALUE_SEPARATOR) - displays = view_profile["display"].split(COLOR_VALUE_SEPARATOR) # display could be optional in case nuke_default ocio config is used + displays = [] if view_profile["display"]: displays = view_profile["display"].split(COLOR_VALUE_SEPARATOR) - else: - displays = [] # generate all possible combination of display/view display_views = [] for view in views: + # display could be optional in case nuke_default ocio config is used + if not displays: + display_views.append(view.strip()) + continue - if displays: - for display in displays: - display_views.append( - create_viewer_profile_string( - view.strip(), display.strip(), path_like=False - ) + for display in displays: + display_views.append( + create_viewer_profile_string( + view.strip(), display.strip(), path_like=False ) - else: - display_views.append(view.strip()) + ) for dv_item in display_views: # format any template tokens used in the string @@ -244,24 +241,23 @@ def get_formatted_display_and_view_as_dict( root_node = nuke.root() views = view_profile["view"].split(COLOR_VALUE_SEPARATOR) - displays = view_profile["display"].split(COLOR_VALUE_SEPARATOR) # display could be optional in case nuke_default ocio config is used + displays = [] if view_profile["display"]: displays = view_profile["display"].split(COLOR_VALUE_SEPARATOR) - else: - displays = [] # generate all possible combination of display/view display_views = [] for view in views: - - if displays: - for display in displays: - display_views.append( - {"view": view.strip(), "display": display.strip()}) - else: + # display could be optional in case nuke_default ocio config is used + if not displays: display_views.append({"view": view.strip(), "display": None}) + continue + + for display in displays: + display_views.append( + {"view": view.strip(), "display": display.strip()}) for dv_item in display_views: # format any template tokens used in the string From ae2efd425ccb01540d36c9519ec5d1e4d530c5fd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 11 Sep 2024 11:49:05 +0200 Subject: [PATCH 10/13] Remove unused function 'format_anatomy' from plugin file The commit removes the unused function 'format_anatomy' from the plugin file. --- client/ayon_nuke/api/plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_nuke/api/plugin.py b/client/ayon_nuke/api/plugin.py index 431fded..e6291e1 100644 --- a/client/ayon_nuke/api/plugin.py +++ b/client/ayon_nuke/api/plugin.py @@ -43,7 +43,6 @@ get_filenames_without_hash, get_work_default_directory, link_knobs, - format_anatomy, ) from .pipeline import ( list_instances, From 1bd1d368be4e1db9b9d7e1c72362112aeac05116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 11 Sep 2024 11:53:15 +0200 Subject: [PATCH 11/13] Update client/ayon_nuke/api/colorspace.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_nuke/api/colorspace.py | 34 +++++++++++++++++------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/client/ayon_nuke/api/colorspace.py b/client/ayon_nuke/api/colorspace.py index 079ad78..b37b7a8 100644 --- a/client/ayon_nuke/api/colorspace.py +++ b/client/ayon_nuke/api/colorspace.py @@ -259,26 +259,30 @@ def get_formatted_display_and_view_as_dict( display_views.append( {"view": view.strip(), "display": display.strip()}) + root_display_and_view = get_display_and_view_colorspaces(root_node) for dv_item in display_views: # format any template tokens used in the string - profile = { - "view": StringTemplate(dv_item["view"]).format_strict( - formatting_data), - "display": StringTemplate(dv_item["display"]).format_strict( - formatting_data) if dv_item["display"] else None, - } - log.debug("Resolved profile: `{}`".format(profile)) - - # making sure formatted colorspace exists in running session - # also need to test case where display is None - test_string = profile["view"] - if profile["display"]: + view = StringTemplate.format_strict_template( + dv_item["view"], formatting_data + ) + test_string = view + display = dv_item["display"] + if display: + display = StringTemplate.format_strict_template( + display, formatting_data + ) test_string = create_viewer_profile_string( - profile["view"], profile["display"], path_like=False + view, display, path_like=False ) - if test_string in get_display_and_view_colorspaces(root_node): - return profile + log.debug(f"Resolved View: '{view}' Display: '{display}'") + + # Make sure formatted colorspace exists in running session + if test_string in root_display_and_view: + return { + "view": view, + "display": display, + } def get_formatted_colorspace( From cda388ec8d8a6afd851acc99d2e4854daf358576 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 11 Sep 2024 11:57:58 +0200 Subject: [PATCH 12/13] Add handling for config without displays and specify colorspace in OCIO. - Added logic to handle configuration without displays by setting a default value. - Updated the comment to clarify that the colorspace should exist in the running OCIO configuration session. --- client/ayon_nuke/api/colorspace.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_nuke/api/colorspace.py b/client/ayon_nuke/api/colorspace.py index b37b7a8..1b98b1d 100644 --- a/client/ayon_nuke/api/colorspace.py +++ b/client/ayon_nuke/api/colorspace.py @@ -265,6 +265,7 @@ def get_formatted_display_and_view_as_dict( view = StringTemplate.format_strict_template( dv_item["view"], formatting_data ) + # for config without displays - nuke_default test_string = view display = dv_item["display"] if display: @@ -277,7 +278,7 @@ def get_formatted_display_and_view_as_dict( log.debug(f"Resolved View: '{view}' Display: '{display}'") - # Make sure formatted colorspace exists in running session + # Make sure formatted colorspace exists in running ocio config session if test_string in root_display_and_view: return { "view": view, From 2c525a8d48359ad400bcdba026d72ccffc7ddc93 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 30 Sep 2024 15:54:28 +0200 Subject: [PATCH 13/13] ruff suggestion --- client/ayon_nuke/api/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_nuke/api/lib.py b/client/ayon_nuke/api/lib.py index 8228b3d..5af72bd 100644 --- a/client/ayon_nuke/api/lib.py +++ b/client/ayon_nuke/api/lib.py @@ -53,7 +53,6 @@ from .constants import ( ASSIST, LOADER_CATEGORY_COLORS, - COLOR_VALUE_SEPARATOR, ) from .workio import save_file