Merge branch 'develop' of https://github.com/ynput/ayon-core into enhancement/AY-5073_Baking-animation-to-joins-during-the-publishing

This commit is contained in:
Kayla Man 2024-05-16 21:00:50 +08:00
commit f050aaed5f
23 changed files with 733 additions and 350 deletions

View file

@ -1,7 +1,7 @@
from ayon_applications import PreLaunchHook
from ayon_core.pipeline.colorspace import get_imageio_config
from ayon_core.pipeline.template_data import get_template_data_with_names
from ayon_core.pipeline.colorspace import get_imageio_config_preset
from ayon_core.pipeline.template_data import get_template_data
class OCIOEnvHook(PreLaunchHook):
@ -26,32 +26,38 @@ class OCIOEnvHook(PreLaunchHook):
def execute(self):
"""Hook entry method."""
template_data = get_template_data_with_names(
project_name=self.data["project_name"],
folder_path=self.data["folder_path"],
task_name=self.data["task_name"],
folder_entity = self.data["folder_entity"]
template_data = get_template_data(
self.data["project_entity"],
folder_entity=folder_entity,
task_entity=self.data["task_entity"],
host_name=self.host_name,
settings=self.data["project_settings"]
settings=self.data["project_settings"],
)
config_data = get_imageio_config(
project_name=self.data["project_name"],
host_name=self.host_name,
project_settings=self.data["project_settings"],
anatomy_data=template_data,
config_data = get_imageio_config_preset(
self.data["project_name"],
self.data["folder_path"],
self.data["task_name"],
self.host_name,
anatomy=self.data["anatomy"],
project_settings=self.data["project_settings"],
template_data=template_data,
env=self.launch_context.env,
folder_id=folder_entity["id"],
)
if config_data:
ocio_path = config_data["path"]
if self.host_name in ["nuke", "hiero"]:
ocio_path = ocio_path.replace("\\", "/")
self.log.info(
f"Setting OCIO environment to config path: {ocio_path}")
self.launch_context.env["OCIO"] = ocio_path
else:
if not config_data:
self.log.debug("OCIO not set or enabled")
return
ocio_path = config_data["path"]
if self.host_name in ["nuke", "hiero"]:
ocio_path = ocio_path.replace("\\", "/")
self.log.info(
f"Setting OCIO environment to config path: {ocio_path}")
self.launch_context.env["OCIO"] = ocio_path

View file

@ -1110,10 +1110,7 @@ def apply_colorspace_project():
'''
# backward compatibility layer
# TODO: remove this after some time
config_data = get_imageio_config(
project_name=get_current_project_name(),
host_name="hiero"
)
config_data = get_current_context_imageio_config_preset()
if config_data:
presets.update({

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- OPMenu Stencil.
It's used to extend the OPMenu.
-->
<menuDocument>
<menu>
<!-- Operator type and asset options. -->
<subMenu id="opmenu.vhda_options_create">
<insertBefore>opmenu.unsynchronize</insertBefore>
<scriptItem id="opmenu.vhda_create_ayon">
<insertAfter>opmenu.vhda_create</insertAfter>
<label>Create New (AYON)...</label>
<context>
</context>
<scriptCode>
<![CDATA[
from ayon_core.hosts.houdini.api.creator_node_shelves import create_interactive
node = kwargs["node"]
if node not in hou.selectedNodes():
node.setSelected(True)
create_interactive("io.openpype.creators.houdini.hda", **kwargs)
]]>
</scriptCode>
</scriptItem>
</subMenu>
</menu>
</menuDocument>

View file

@ -369,12 +369,8 @@ def reset_colorspace():
"""
if int(get_max_version()) < 2024:
return
project_name = get_current_project_name()
colorspace_mgr = rt.ColorPipelineMgr
project_settings = get_project_settings(project_name)
max_config_data = colorspace.get_imageio_config(
project_name, "max", project_settings)
max_config_data = colorspace.get_current_context_imageio_config_preset()
if max_config_data:
ocio_config_path = max_config_data["path"]
colorspace_mgr = rt.ColorPipelineMgr
@ -389,10 +385,7 @@ def check_colorspace():
"because Max main window can't be found.")
if int(get_max_version()) >= 2024:
color_mgr = rt.ColorPipelineMgr
project_name = get_current_project_name()
project_settings = get_project_settings(project_name)
max_config_data = colorspace.get_imageio_config(
project_name, "max", project_settings)
max_config_data = colorspace.get_current_context_imageio_config_preset()
if max_config_data and color_mgr.Mode != rt.Name("OCIO_Custom"):
if not is_headless():
from ayon_core.tools.utils import SimplePopup

View file

@ -52,11 +52,7 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
self._has_been_setup = True
def context_setting():
return lib.set_context_setting()
rt.callbacks.addScript(rt.Name('systemPostNew'),
context_setting)
rt.callbacks.addScript(rt.Name('systemPostNew'), on_new)
rt.callbacks.addScript(rt.Name('filePostOpen'),
lib.check_colorspace)
@ -163,6 +159,14 @@ def ls() -> list:
yield lib.read(container)
def on_new():
lib.set_context_setting()
if rt.checkForSave():
rt.resetMaxFile(rt.Name("noPrompt"))
rt.clearUndoBuffer()
rt.redrawViews()
def containerise(name: str, nodes: list, context,
namespace=None, loader=None, suffix="_CON"):
data = {

View file

@ -8,7 +8,7 @@ from ayon_core.pipeline import (
from ayon_core.pipeline.load.utils import get_representation_path_from_context
from ayon_core.pipeline.colorspace import (
get_imageio_file_rules_colorspace_from_filepath,
get_imageio_config,
get_current_context_imageio_config_preset,
get_imageio_file_rules
)
from ayon_core.settings import get_project_settings
@ -270,8 +270,7 @@ class FileNodeLoader(load.LoaderPlugin):
host_name = get_current_host_name()
project_settings = get_project_settings(project_name)
config_data = get_imageio_config(
project_name, host_name,
config_data = get_current_context_imageio_config_preset(
project_settings=project_settings
)

View file

@ -43,7 +43,9 @@ from ayon_core.pipeline import (
from ayon_core.pipeline.context_tools import (
get_current_context_custom_workfile_template
)
from ayon_core.pipeline.colorspace import get_imageio_config
from ayon_core.pipeline.colorspace import (
get_current_context_imageio_config_preset
)
from ayon_core.pipeline.workfile import BuildWorkfile
from . import gizmo_menu
from .constants import ASSIST
@ -1552,10 +1554,7 @@ class WorkfileSettings(object):
imageio_host (dict): host colorspace configurations
'''
config_data = get_imageio_config(
project_name=get_current_project_name(),
host_name="nuke"
)
config_data = get_current_context_imageio_config_preset()
workfile_settings = imageio_host["workfile"]
color_management = workfile_settings["color_management"]

View file

@ -62,7 +62,7 @@ class LoadBackdropNodes(load.LoaderPlugin):
}
# add attributes from the version to imprint to metadata knob
for k in ["source", "author", "fps"]:
for k in ["source", "fps"]:
data_imprint[k] = version_attributes[k]
# getting file path
@ -206,7 +206,7 @@ class LoadBackdropNodes(load.LoaderPlugin):
"colorspaceInput": colorspace,
}
for k in ["source", "author", "fps"]:
for k in ["source", "fps"]:
data_imprint[k] = version_attributes[k]
# adding nodes to node graph

View file

@ -48,7 +48,7 @@ class AlembicCameraLoader(load.LoaderPlugin):
"frameEnd": last,
"version": version_entity["version"],
}
for k in ["source", "author", "fps"]:
for k in ["source", "fps"]:
data_imprint[k] = version_attributes[k]
# getting file path
@ -123,7 +123,7 @@ class AlembicCameraLoader(load.LoaderPlugin):
}
# add attributes from the version to imprint to metadata knob
for k in ["source", "author", "fps"]:
for k in ["source", "fps"]:
data_imprint[k] = version_attributes[k]
# getting file path

View file

@ -9,7 +9,8 @@ from ayon_core.pipeline import (
get_representation_path,
)
from ayon_core.pipeline.colorspace import (
get_imageio_file_rules_colorspace_from_filepath
get_imageio_file_rules_colorspace_from_filepath,
get_current_context_imageio_config_preset,
)
from ayon_core.hosts.nuke.api.lib import (
get_imageio_input_colorspace,
@ -197,7 +198,6 @@ class LoadClip(plugin.NukeLoader):
"frameStart",
"frameEnd",
"source",
"author",
"fps",
"handleStart",
"handleEnd",
@ -347,8 +347,7 @@ class LoadClip(plugin.NukeLoader):
"source": version_attributes.get("source"),
"handleStart": str(self.handle_start),
"handleEnd": str(self.handle_end),
"fps": str(version_attributes.get("fps")),
"author": version_attributes.get("author")
"fps": str(version_attributes.get("fps"))
}
last_version_entity = ayon_api.get_last_version_by_product_id(
@ -547,9 +546,10 @@ class LoadClip(plugin.NukeLoader):
f"Colorspace from representation colorspaceData: {colorspace}"
)
config_data = get_current_context_imageio_config_preset()
# check if any filerules are not applicable
new_parsed_colorspace = get_imageio_file_rules_colorspace_from_filepath( # noqa
filepath, "nuke", project_name
filepath, "nuke", project_name, config_data=config_data
)
self.log.debug(f"Colorspace new filerules: {new_parsed_colorspace}")

View file

@ -69,7 +69,6 @@ class LoadEffects(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
"author",
"fps"
]:
data_imprint[k] = version_attributes[k]
@ -189,7 +188,6 @@ class LoadEffects(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
"author",
"fps",
]:
data_imprint[k] = version_attributes[k]

View file

@ -69,7 +69,6 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
"author",
"fps"
]:
data_imprint[k] = version_attributes[k]
@ -192,7 +191,6 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
"author",
"fps"
]:
data_imprint[k] = version_attributes[k]

View file

@ -71,7 +71,6 @@ class LoadGizmo(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
"author",
"fps"
]:
data_imprint[k] = version_attributes[k]
@ -139,7 +138,6 @@ class LoadGizmo(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
"author",
"fps"
]:
data_imprint[k] = version_attributes[k]

View file

@ -73,7 +73,6 @@ class LoadGizmoInputProcess(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
"author",
"fps"
]:
data_imprint[k] = version_attributes[k]
@ -145,7 +144,6 @@ class LoadGizmoInputProcess(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
"author",
"fps"
]:
data_imprint[k] = version_attributes[k]

View file

@ -133,7 +133,7 @@ class LoadImage(load.LoaderPlugin):
"version": version_entity["version"],
"colorspace": colorspace,
}
for k in ["source", "author", "fps"]:
for k in ["source", "fps"]:
data_imprint[k] = version_attributes.get(k, str(None))
r["tile_color"].setValue(int("0x4ecd25ff", 16))
@ -207,7 +207,6 @@ class LoadImage(load.LoaderPlugin):
"colorspace": version_attributes.get("colorSpace"),
"source": version_attributes.get("source"),
"fps": str(version_attributes.get("fps")),
"author": version_attributes.get("author")
}
# change color of node

View file

@ -47,7 +47,7 @@ class AlembicModelLoader(load.LoaderPlugin):
"version": version_entity["version"]
}
# add attributes from the version to imprint to metadata knob
for k in ["source", "author", "fps"]:
for k in ["source", "fps"]:
data_imprint[k] = version_attributes[k]
# getting file path
@ -130,7 +130,7 @@ class AlembicModelLoader(load.LoaderPlugin):
}
# add additional metadata from the version to imprint to Avalon knob
for k in ["source", "author", "fps"]:
for k in ["source", "fps"]:
data_imprint[k] = version_attributes[k]
# getting file path

View file

@ -55,7 +55,6 @@ class LinkAsGroup(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
"author",
"fps"
]:
data_imprint[k] = version_attributes[k]
@ -131,7 +130,6 @@ class LinkAsGroup(load.LoaderPlugin):
"colorspace": version_attributes.get("colorSpace"),
"source": version_attributes.get("source"),
"fps": version_attributes.get("fps"),
"author": version_attributes.get("author")
}
# Update the imprinted representation

View file

@ -156,14 +156,9 @@ This creator publishes color space look file (LUT).
]
def apply_settings(self, project_settings):
host = self.create_context.host
host_name = host.name
project_name = host.get_current_project_name()
config_data = colorspace.get_imageio_config(
project_name, host_name,
config_data = colorspace.get_current_context_imageio_config_preset(
project_settings=project_settings
)
if not config_data:
self.enabled = False
return

View file

@ -1,10 +1,7 @@
import pyblish.api
from ayon_core.pipeline import (
publish,
registered_host
)
from ayon_core.lib import EnumDef
from ayon_core.pipeline import colorspace
from ayon_core.pipeline import publish
from ayon_core.pipeline.publish import KnownPublishError
@ -19,9 +16,10 @@ class CollectColorspace(pyblish.api.InstancePlugin,
families = ["render", "plate", "reference", "image", "online"]
enabled = False
colorspace_items = [
default_colorspace_items = [
(None, "Don't override")
]
colorspace_items = list(default_colorspace_items)
colorspace_attr_show = False
config_items = None
@ -69,14 +67,13 @@ class CollectColorspace(pyblish.api.InstancePlugin,
@classmethod
def apply_settings(cls, project_settings):
host = registered_host()
host_name = host.name
project_name = host.get_current_project_name()
config_data = colorspace.get_imageio_config(
project_name, host_name,
config_data = colorspace.get_current_context_imageio_config_preset(
project_settings=project_settings
)
enabled = False
colorspace_items = list(cls.default_colorspace_items)
config_items = None
if config_data:
filepath = config_data["path"]
config_items = colorspace.get_ocio_config_colorspaces(filepath)
@ -85,9 +82,11 @@ class CollectColorspace(pyblish.api.InstancePlugin,
include_aliases=True,
include_roles=True
)
cls.config_items = config_items
cls.colorspace_items.extend(labeled_colorspaces)
cls.enabled = True
colorspace_items.extend(labeled_colorspaces)
cls.config_items = config_items
cls.colorspace_items = colorspace_items
cls.enabled = enabled
@classmethod
def get_attribute_defs(cls):

View file

@ -7,7 +7,7 @@ from ayon_core.lib import Logger, run_subprocess, AYONSettingsRegistry
from ayon_core.lib.vendor_bin_utils import find_tool_in_custom_paths
from .rr_job import SubmitFile
from .rr_job import RRjob, SubmitterParameter # noqa F401
from .rr_job import RRJob, SubmitterParameter # noqa F401
class Api:

View file

@ -8,14 +8,19 @@ import tempfile
import warnings
from copy import deepcopy
import ayon_api
from ayon_core import AYON_CORE_ROOT
from ayon_core.settings import get_project_settings
from ayon_core.lib import (
filter_profiles,
StringTemplate,
run_ayon_launcher_process,
Logger
Logger,
)
from ayon_core.pipeline import Anatomy
from ayon_core.pipeline.template_data import get_template_data
from ayon_core.pipeline.load import get_representation_path_with_anatomy
from ayon_core.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS
@ -81,28 +86,25 @@ def deprecated(new_destination):
def _make_temp_json_file():
"""Wrapping function for json temp file
"""
temporary_json_file = None
try:
# Store dumped json to temporary file
temporary_json_file = tempfile.NamedTemporaryFile(
with tempfile.NamedTemporaryFile(
mode="w", suffix=".json", delete=False
)
temporary_json_file.close()
temporary_json_filepath = temporary_json_file.name.replace(
"\\", "/"
)
) as tmpfile:
temporary_json_filepath = tmpfile.name.replace("\\", "/")
yield temporary_json_filepath
except IOError as _error:
except IOError as exc:
raise IOError(
"Unable to create temp json file: {}".format(
_error
)
"Unable to create temp json file: {}".format(exc)
)
finally:
# Remove the temporary json
os.remove(temporary_json_filepath)
if temporary_json_file is not None:
os.remove(temporary_json_filepath)
def get_ocio_config_script_path():
@ -110,53 +112,58 @@ def get_ocio_config_script_path():
Returns:
str: path string
"""
return os.path.normpath(
os.path.join(
AYON_CORE_ROOT,
"scripts",
"ocio_wrapper.py"
)
return os.path.join(
os.path.normpath(AYON_CORE_ROOT),
"scripts",
"ocio_wrapper.py"
)
def get_colorspace_name_from_filepath(
filepath, host_name, project_name,
config_data=None, file_rules=None,
filepath,
host_name,
project_name,
config_data,
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.
filepath (str): Path string, file rule pattern is tested on it.
host_name (str): Host name.
project_name (str): Project name.
config_data (dict): Config path and template in dict.
file_rules (Optional[dict]): File rule data from settings.
project_settings (Optional[dict]): Project settings.
validate (Optional[bool]): should resulting colorspace be validated
with config file? Defaults to True.
with config file? Defaults to True.
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
)
Union[str, None]: name of colorspace
"""
if not config_data:
# in case global or host color management is not enabled
return None
if file_rules is None:
if project_settings is None:
project_settings = get_project_settings(project_name)
file_rules = get_imageio_file_rules(
project_name, host_name, project_settings
)
# 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,
filepath,
host_name,
project_name,
config_data=config_data,
file_rules=file_rules,
project_settings=project_settings
)
@ -182,7 +189,8 @@ def get_colorspace_name_from_filepath(
# validate matching colorspace with config
if validate:
validate_imageio_colorspace_in_config(
config_data["path"], colorspace_name)
config_data["path"], colorspace_name
)
return colorspace_name
@ -198,31 +206,12 @@ 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,
filepath,
host_name,
project_name,
config_data,
file_rules=None,
project_settings=None
):
"""Get colorspace name from filepath
@ -230,28 +219,28 @@ def get_imageio_file_rules_colorspace_from_filepath(
ImageIO Settings file rules are tested for matching rule.
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.
filepath (str): Path string, file rule pattern is tested on it.
host_name (str): Host name.
project_name (str): Project name.
config_data (dict): Config path and template in dict.
file_rules (Optional[dict]): File rule data from settings.
project_settings (Optional[dict]): Project settings.
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
)
Union[str, None]: Name of colorspace.
"""
if not config_data:
# in case global or host color management is not enabled
return None
if file_rules is None:
if project_settings is None:
project_settings = get_project_settings(project_name)
file_rules = get_imageio_file_rules(
project_name, host_name, project_settings
)
# match file rule from path
colorspace_name = None
for file_rule in file_rules:
@ -344,10 +333,10 @@ 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) for colorspace in
re.escape(colorspace)
# 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)
for colorspace in sorted(colorspaces, key=len, reverse=True)
)
return re.compile(pattern)
@ -519,16 +508,15 @@ def get_ocio_config_colorspaces(config_path):
if not compatibility_check():
# python environment is not compatible with PyOpenColorIO
# needs to be run in subprocess
CachedData.ocio_config_colorspaces[config_path] = \
_get_wrapped_with_subprocess(
"config", "get_colorspace", in_path=config_path
config_colorspaces = _get_wrapped_with_subprocess(
"config", "get_colorspace", in_path=config_path
)
else:
# TODO: refactor this so it is not imported but part of this file
from ayon_core.scripts.ocio_wrapper import _get_colorspace_data
CachedData.ocio_config_colorspaces[config_path] = \
_get_colorspace_data(config_path)
config_colorspaces = _get_colorspace_data(config_path)
CachedData.ocio_config_colorspaces[config_path] = config_colorspaces
return CachedData.ocio_config_colorspaces[config_path]
@ -540,11 +528,12 @@ def convert_colorspace_enumerator_item(
"""Convert colorspace enumerator item to dictionary
Args:
colorspace_item (str): colorspace and family in couple
config_items (dict[str,dict]): colorspace data
colorspace_enum_item (str): Colorspace and family in couple.
config_items (dict[str,dict]): Colorspace data.
Returns:
dict: colorspace data
"""
if "::" not in colorspace_enum_item:
return None
@ -745,6 +734,7 @@ def get_views_data_subprocess(config_path):
)
@deprecated("get_imageio_config_preset")
def get_imageio_config(
project_name,
host_name,
@ -758,6 +748,9 @@ def get_imageio_config(
Config path is formatted in `path` key
and original settings input is saved into `template` key.
Deprecated:
Deprecated since '0.3.1' . Use `get_imageio_config_preset` instead.
Args:
project_name (str): project name
host_name (str): host name
@ -768,157 +761,355 @@ def get_imageio_config(
Returns:
dict: config path data or empty dict
"""
project_settings = project_settings or get_project_settings(project_name)
anatomy = anatomy or Anatomy(project_name)
"""
if not anatomy_data:
from ayon_core.pipeline.context_tools import (
get_current_context_template_data)
from .context_tools import get_current_context_template_data
anatomy_data = get_current_context_template_data()
formatting_data = deepcopy(anatomy_data)
task_name = anatomy_data.get("task", {}).get("name")
folder_path = anatomy_data.get("folder", {}).get("path")
return get_imageio_config_preset(
project_name,
folder_path,
task_name,
host_name,
anatomy=anatomy,
project_settings=project_settings,
template_data=anatomy_data,
env=env,
)
# Add project roots to anatomy data
formatting_data["root"] = anatomy.roots
formatting_data["platform"] = platform.system().lower()
def _get_global_config_data(
project_name,
host_name,
anatomy,
template_data,
imageio_global,
folder_id,
log,
):
"""Get global config data.
Global config from core settings is using profiles that are based on
host name, task name and task type. The filtered profile can define 3
types of config sources:
1. AYON ocio addon configs.
2. Custom path to ocio config.
3. Path to 'ocioconfig' representation on product. Name of product can be
defined in settings. Product name can be regex but exact match is
always preferred.
None is returned when no profile is found, when path
Args:
project_name (str): Project name.
host_name (str): Host name.
anatomy (Anatomy): Project anatomy object.
template_data (dict[str, Any]): Template data.
imageio_global (dict[str, Any]): Core imagio settings.
folder_id (Union[dict[str, Any], None]): Folder id.
log (logging.Logger): Logger object.
Returns:
Union[dict[str, str], None]: Config data with path and template
or None.
"""
task_name = task_type = None
task_data = template_data.get("task")
if task_data:
task_name = task_data["name"]
task_type = task_data["type"]
filter_values = {
"task_names": task_name,
"task_types": task_type,
"host_names": host_name,
}
profile = filter_profiles(
imageio_global["ocio_config_profiles"], filter_values
)
if profile is None:
log.info(f"No config profile matched filters {str(filter_values)}")
return None
profile_type = profile["type"]
if profile_type in ("builtin_path", "custom_path"):
template = profile[profile_type]
result = StringTemplate.format_strict_template(
template, template_data
)
normalized_path = str(result.normalized())
if not os.path.exists(normalized_path):
log.warning(f"Path was not found '{normalized_path}'.")
return None
return {
"path": normalized_path,
"template": template
}
# TODO decide if this is the right name for representation
repre_name = "ocioconfig"
folder_info = template_data.get("folder")
if not folder_info:
log.warning("Folder info is missing.")
return None
folder_path = folder_info["path"]
product_name = profile["product_name"]
if folder_id is None:
folder_entity = ayon_api.get_folder_by_path(
project_name, folder_path, fields={"id"}
)
if not folder_entity:
log.warning(f"Folder entity '{folder_path}' was not found..")
return None
folder_id = folder_entity["id"]
product_entities_by_name = {
product_entity["name"]: product_entity
for product_entity in ayon_api.get_products(
project_name,
folder_ids={folder_id},
product_name_regex=product_name,
fields={"id", "name"}
)
}
if not product_entities_by_name:
log.debug(
f"No product entities were found for folder '{folder_path}' with"
f" product name filter '{product_name}'."
)
return None
# Try to use exact match first, otherwise use first available product
product_entity = product_entities_by_name.get(product_name)
if product_entity is None:
product_entity = next(iter(product_entities_by_name.values()))
product_name = product_entity["name"]
# Find last product version
version_entity = ayon_api.get_last_version_by_product_id(
project_name,
product_id=product_entity["id"],
fields={"id"}
)
if not version_entity:
log.info(
f"Product '{product_name}' does not have available any versions."
)
return None
# Find 'ocioconfig' representation entity
repre_entity = ayon_api.get_representation_by_name(
project_name,
representation_name=repre_name,
version_id=version_entity["id"],
)
if not repre_entity:
log.debug(
f"Representation '{repre_name}'"
f" not found on product '{product_name}'."
)
return None
path = get_representation_path_with_anatomy(repre_entity, anatomy)
template = repre_entity["attrib"]["template"]
return {
"path": path,
"template": template,
}
def get_imageio_config_preset(
project_name,
folder_path,
task_name,
host_name,
anatomy=None,
project_settings=None,
template_data=None,
env=None,
folder_id=None,
):
"""Returns config data from settings
Output contains 'path' key and 'template' key holds its template.
Template data can be prepared with 'get_template_data'.
Args:
project_name (str): Project name.
folder_path (str): Folder path.
task_name (str): Task name.
host_name (str): Host name.
anatomy (Optional[Anatomy]): Project anatomy object.
project_settings (Optional[dict]): Project settings.
template_data (Optional[dict]): Template data used for
template formatting.
env (Optional[dict]): Environment variables. Environments are used
for template formatting too. Values from 'os.environ' are used
when not provided.
folder_id (Optional[str]): Folder id. Is used only when config path
is received from published representation. Is autofilled when
not provided.
Returns:
dict: config path data or empty dict
"""
if not project_settings:
project_settings = get_project_settings(project_name)
# Get colorspace settings
imageio_global, imageio_host = _get_imageio_settings(
project_settings, host_name)
project_settings, host_name
)
# Global color management must be enabled to be able to use host settings
if not imageio_global["activate_global_color_management"]:
log.info("Colorspace management is disabled globally.")
return {}
# Host 'ocio_config' is optional
host_ocio_config = imageio_host.get("ocio_config") or {}
# Global color management must be enabled to be able to use host settings
activate_color_management = imageio_global.get(
"activate_global_color_management")
# TODO: remove this in future - backward compatibility
# For already saved overrides from previous version look for 'enabled'
# on host settings.
if activate_color_management is None:
activate_color_management = host_ocio_config.get("enabled", False)
if not activate_color_management:
# if global settings are disabled return empty dict because
# it is expected that no colorspace management is needed
log.info("Colorspace management is disabled globally.")
return {}
# TODO remove
# - backward compatibility when host settings had only 'enabled' flag
# the flag was split into 'activate_global_color_management'
# and 'override_global_config'
host_ocio_config_enabled = host_ocio_config.get("enabled", False)
# Check if host settings group is having 'activate_host_color_management'
# - if it does not have activation key then default it to True so it uses
# global settings
# This is for backward compatibility.
# TODO: in future rewrite this to be more explicit
activate_host_color_management = imageio_host.get(
"activate_host_color_management")
# TODO: remove this in future - backward compatibility
"activate_host_color_management"
)
if activate_host_color_management is None:
activate_host_color_management = host_ocio_config.get("enabled", False)
activate_host_color_management = host_ocio_config_enabled
if not activate_host_color_management:
# if host settings are disabled return False because
# it is expected that no colorspace management is needed
log.info(
"Colorspace management for host '{}' is disabled.".format(
host_name)
f"Colorspace management for host '{host_name}' is disabled."
)
return {}
# get config path from either global or host settings
# depending on override flag
project_entity = None
if anatomy is None:
project_entity = ayon_api.get_project(project_name)
anatomy = Anatomy(project_name, project_entity)
if env is None:
env = dict(os.environ.items())
if template_data:
template_data = deepcopy(template_data)
else:
if not project_entity:
project_entity = ayon_api.get_project(project_name)
folder_entity = task_entity = folder_id = None
if folder_path:
folder_entity = ayon_api.get_folder_by_path(
project_name, folder_path
)
folder_id = folder_entity["id"]
if folder_id and task_name:
task_entity = ayon_api.get_task_by_name(
project_name, folder_id, task_name
)
template_data = get_template_data(
project_entity,
folder_entity,
task_entity,
host_name,
project_settings,
)
# Add project roots to anatomy data
template_data["root"] = anatomy.roots
template_data["platform"] = platform.system().lower()
# Add environment variables to template data
template_data.update(env)
# Get config path from core or host settings
# - based on override flag in host settings
# TODO: in future rewrite this to be more explicit
override_global_config = host_ocio_config.get("override_global_config")
if override_global_config is None:
# for already saved overrides from previous version
# TODO: remove this in future - backward compatibility
override_global_config = host_ocio_config.get("enabled")
override_global_config = host_ocio_config_enabled
if override_global_config:
config_data = _get_config_data(
host_ocio_config["filepath"], formatting_data, env
if not override_global_config:
config_data = _get_global_config_data(
project_name,
host_name,
anatomy,
template_data,
imageio_global,
folder_id,
log,
)
else:
# get config path from global
config_global = imageio_global["ocio_config"]
config_data = _get_config_data(
config_global["filepath"], formatting_data, env
config_data = _get_host_config_data(
host_ocio_config["filepath"], template_data
)
if not config_data:
raise FileExistsError(
"No OCIO config found in settings. It is "
"either missing or there is typo in path inputs"
"No OCIO config found in settings. It is"
" either missing or there is typo in path inputs"
)
return config_data
def _get_config_data(path_list, anatomy_data, env=None):
def _get_host_config_data(templates, template_data):
"""Return first existing path in path list.
If template is used in path inputs,
then it is formatted by anatomy data
and environment variables
Use template data to fill possible formatting in paths.
Args:
path_list (list[str]): list of abs paths
anatomy_data (dict): formatting data
env (Optional[dict]): Environment variables.
templates (list[str]): List of templates to config paths.
template_data (dict): Template data used to format templates.
Returns:
dict: config data
Union[dict, None]: Config data or 'None' if templates are empty
or any path exists.
"""
formatting_data = deepcopy(anatomy_data)
environment_vars = env or dict(**os.environ)
# format the path for potential env vars
formatting_data.update(environment_vars)
# first try host config paths
for path_ in path_list:
formatted_path = _format_path(path_, formatting_data)
if not os.path.exists(formatted_path):
for template in templates:
formatted_path = StringTemplate.format_template(
template, template_data
)
if not formatted_path.solved:
continue
return {
"path": os.path.normpath(formatted_path),
"template": path_
}
def _format_path(template_path, formatting_data):
"""Single template path formatting.
Args:
template_path (str): template string
formatting_data (dict): data to be used for
template formatting
Returns:
str: absolute formatted path
"""
# format path for anatomy keys
formatted_path = StringTemplate(template_path).format(
formatting_data)
return os.path.abspath(formatted_path)
path = os.path.abspath(formatted_path)
if os.path.exists(path):
return {
"path": os.path.normpath(path),
"template": template
}
def get_imageio_file_rules(project_name, host_name, project_settings=None):
"""Get ImageIO File rules from project settings
Args:
project_name (str): project name
host_name (str): host name
project_settings (dict, optional): project settings.
Defaults to None.
project_name (str): Project name.
host_name (str): Host name.
project_settings (Optional[dict]): Project settings.
Returns:
list[dict[str, Any]]: file rules data
"""
project_settings = project_settings or get_project_settings(project_name)
@ -960,7 +1151,7 @@ def get_remapped_colorspace_to_native(
"""Return native colorspace name.
Args:
ocio_colorspace_name (str | None): ocio colorspace name
ocio_colorspace_name (str | None): OCIO colorspace name.
host_name (str): Host name.
imageio_host_settings (dict[str, Any]): ImageIO host settings.
@ -968,16 +1159,15 @@ def get_remapped_colorspace_to_native(
Union[str, None]: native colorspace name defined in remapping or None
"""
CachedData.remapping.setdefault(host_name, {})
if CachedData.remapping[host_name].get("to_native") is None:
host_mapping = CachedData.remapping.setdefault(host_name, {})
if "to_native" not in host_mapping:
remapping_rules = imageio_host_settings["remapping"]["rules"]
CachedData.remapping[host_name]["to_native"] = {
host_mapping["to_native"] = {
rule["ocio_name"]: rule["host_native_name"]
for rule in remapping_rules
}
return CachedData.remapping[host_name]["to_native"].get(
ocio_colorspace_name)
return host_mapping["to_native"].get(ocio_colorspace_name)
def get_remapped_colorspace_from_native(
@ -992,30 +1182,29 @@ def get_remapped_colorspace_from_native(
Returns:
Union[str, None]: Ocio colorspace name defined in remapping or None.
"""
CachedData.remapping.setdefault(host_name, {})
if CachedData.remapping[host_name].get("from_native") is None:
"""
host_mapping = CachedData.remapping.setdefault(host_name, {})
if "from_native" not in host_mapping:
remapping_rules = imageio_host_settings["remapping"]["rules"]
CachedData.remapping[host_name]["from_native"] = {
host_mapping["from_native"] = {
rule["host_native_name"]: rule["ocio_name"]
for rule in remapping_rules
}
return CachedData.remapping[host_name]["from_native"].get(
host_native_colorspace_name)
return host_mapping["from_native"].get(host_native_colorspace_name)
def _get_imageio_settings(project_settings, host_name):
"""Get ImageIO settings for global and host
Args:
project_settings (dict): project settings.
Defaults to None.
host_name (str): host name
project_settings (dict[str, Any]): Project settings.
host_name (str): Host name.
Returns:
tuple[dict, dict]: image io settings for global and host
tuple[dict, dict]: Image io settings for global and host.
"""
# get image io from global and host_name
imageio_global = project_settings["core"]["imageio"]
@ -1033,27 +1222,41 @@ def get_colorspace_settings_from_publish_context(context_data):
Returns:
tuple | bool: config, file rules or None
"""
if "imageioSettings" in context_data and context_data["imageioSettings"]:
return context_data["imageioSettings"]
project_name = context_data["projectName"]
folder_path = context_data["folderPath"]
task_name = context_data["task"]
host_name = context_data["hostName"]
anatomy_data = context_data["anatomyData"]
project_settings_ = context_data["project_settings"]
anatomy = context_data["anatomy"]
template_data = context_data["anatomyData"]
project_settings = context_data["project_settings"]
folder_id = None
folder_entity = context_data.get("folderEntity")
if folder_entity:
folder_id = folder_entity["id"]
config_data = get_imageio_config(
project_name, host_name,
project_settings=project_settings_,
anatomy_data=anatomy_data
config_data = get_imageio_config_preset(
project_name,
folder_path,
task_name,
host_name,
anatomy=anatomy,
project_settings=project_settings,
template_data=template_data,
folder_id=folder_id,
)
# caching invalid state, so it's not recalculated all the time
file_rules = None
if config_data:
file_rules = get_imageio_file_rules(
project_name, host_name,
project_settings=project_settings_
project_name,
host_name,
project_settings=project_settings
)
# caching settings for future instance processing
@ -1063,18 +1266,13 @@ def get_colorspace_settings_from_publish_context(context_data):
def set_colorspace_data_to_representation(
representation, context_data,
representation,
context_data,
colorspace=None,
log=None
):
"""Sets colorspace data to representation.
Args:
representation (dict): publishing representation
context_data (publish.Context.data): publishing context data
colorspace (str, optional): colorspace name. Defaults to None.
log (logging.Logger, optional): logger instance. Defaults to None.
Example:
```
{
@ -1089,6 +1287,12 @@ def set_colorspace_data_to_representation(
}
```
Args:
representation (dict): publishing representation
context_data (publish.Context.data): publishing context data
colorspace (Optional[str]): Colorspace name.
log (Optional[logging.Logger]): logger instance.
"""
log = log or Logger.get_logger(__name__)
@ -1122,12 +1326,15 @@ def set_colorspace_data_to_representation(
filename = filename[0]
# get matching colorspace from rules
colorspace = colorspace or get_imageio_colorspace_from_filepath(
filename, host_name, project_name,
config_data=config_data,
file_rules=file_rules,
project_settings=project_settings
)
if colorspace is None:
colorspace = get_imageio_file_rules_colorspace_from_filepath(
filename,
host_name,
project_name,
config_data=config_data,
file_rules=file_rules,
project_settings=project_settings
)
# infuse data to representation
if colorspace:
@ -1149,21 +1356,22 @@ def get_display_view_colorspace_name(config_path, display, view):
view (str): view name e.g. "sRGB"
Returns:
view color space name (str) e.g. "Output - sRGB"
"""
str: View color space name. e.g. "Output - sRGB"
"""
if not compatibility_check():
# python environment is not compatible with PyOpenColorIO
# needs to be run in subprocess
return get_display_view_colorspace_subprocess(config_path,
display, view)
return _get_display_view_colorspace_subprocess(
config_path, display, view
)
from ayon_core.scripts.ocio_wrapper import _get_display_view_colorspace_name # noqa
return _get_display_view_colorspace_name(config_path, display, view)
def get_display_view_colorspace_subprocess(config_path, display, view):
def _get_display_view_colorspace_subprocess(config_path, display, view):
"""Returns the colorspace attribute of the (display, view) pair
via subprocess.
@ -1174,8 +1382,8 @@ def get_display_view_colorspace_subprocess(config_path, display, view):
Returns:
view color space name (str) e.g. "Output - sRGB"
"""
"""
with _make_temp_json_file() as tmp_json_path:
# Prepare subprocess arguments
args = [
@ -1193,3 +1401,39 @@ def get_display_view_colorspace_subprocess(config_path, display, view):
# return default view colorspace name
with open(tmp_json_path, "r") as f:
return json.load(f)
# --- Current context functions ---
def get_current_context_imageio_config_preset(
anatomy=None,
project_settings=None,
template_data=None,
env=None,
):
"""Get ImageIO config preset for current context.
Args:
anatomy (Optional[Anatomy]): Current project anatomy.
project_settings (Optional[dict[str, Any]]): Current project settings.
template_data (Optional[dict[str, Any]]): Prepared template data
for current context.
env (Optional[dict[str, str]]): Custom environment variable values.
Returns:
dict: ImageIO config preset.
"""
from .context_tools import get_current_context, get_current_host_name
context = get_current_context()
host_name = get_current_host_name()
return get_imageio_config_preset(
context["project_name"],
context["folder_path"],
context["task_name"],
host_name,
anatomy=anatomy,
project_settings=project_settings,
template_data=template_data,
env=env,
)

View file

@ -1,3 +1,5 @@
from typing import Any
from ayon_server.addons import BaseServerAddon
from .settings import CoreSettings, DEFAULT_VALUES
@ -9,3 +11,53 @@ class CoreAddon(BaseServerAddon):
async def get_default_settings(self):
settings_model_cls = self.get_settings_model()
return settings_model_cls(**DEFAULT_VALUES)
async def convert_settings_overrides(
self,
source_version: str,
overrides: dict[str, Any],
) -> dict[str, Any]:
self._convert_imagio_configs_0_3_1(overrides)
# Use super conversion
return await super().convert_settings_overrides(
source_version, overrides
)
def _convert_imagio_configs_0_3_1(self, overrides):
"""Imageio config settings did change to profiles since 0.3.1. ."""
imageio_overrides = overrides.get("imageio") or {}
if (
"ocio_config" not in imageio_overrides
or "filepath" not in imageio_overrides["ocio_config"]
):
return
ocio_config = imageio_overrides.pop("ocio_config")
filepath = ocio_config["filepath"]
if not filepath:
return
first_filepath = filepath[0]
ocio_config_profiles = imageio_overrides.setdefault(
"ocio_config_profiles", []
)
base_value = {
"type": "builtin_path",
"product_name": "",
"host_names": [],
"task_names": [],
"task_types": [],
"custom_path": "",
"builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio"
}
if first_filepath in (
"{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
"{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio",
):
base_value["type"] = "builtin_path"
base_value["builtin_path"] = first_filepath
else:
base_value["type"] = "custom_path"
base_value["custom_path"] = first_filepath
ocio_config_profiles.append(base_value)

View file

@ -54,9 +54,67 @@ class CoreImageIOFileRulesModel(BaseSettingsModel):
return value
class CoreImageIOConfigModel(BaseSettingsModel):
filepath: list[str] = SettingsField(
default_factory=list, title="Config path"
def _ocio_config_profile_types():
return [
{"value": "builtin_path", "label": "AYON built-in OCIO config"},
{"value": "custom_path", "label": "Path to OCIO config"},
{"value": "product_name", "label": "Published product"},
]
def _ocio_built_in_paths():
return [
{
"value": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
"label": "ACES 1.2",
"description": "Aces 1.2 OCIO config file."
},
{
"value": "{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio",
"label": "Nuke default",
},
]
class CoreImageIOConfigProfilesModel(BaseSettingsModel):
_layout = "expanded"
host_names: list[str] = SettingsField(
default_factory=list,
title="Host names"
)
task_types: list[str] = SettingsField(
default_factory=list,
title="Task types",
enum_resolver=task_types_enum
)
task_names: list[str] = SettingsField(
default_factory=list,
title="Task names"
)
type: str = SettingsField(
title="Profile type",
enum_resolver=_ocio_config_profile_types,
conditionalEnum=True,
default="builtin_path",
section="---",
)
builtin_path: str = SettingsField(
"ACES 1.2",
title="Built-in OCIO config",
enum_resolver=_ocio_built_in_paths,
)
custom_path: str = SettingsField(
"",
title="OCIO config path",
description="Path to OCIO config. Anatomy formatting is supported.",
)
product_name: str = SettingsField(
"",
title="Product name",
description=(
"Published product name to get OCIO config from. "
"Partial match is supported."
),
)
@ -65,9 +123,8 @@ class CoreImageIOBaseModel(BaseSettingsModel):
False,
title="Enable Color Management"
)
ocio_config: CoreImageIOConfigModel = SettingsField(
default_factory=CoreImageIOConfigModel,
title="OCIO config"
ocio_config_profiles: list[CoreImageIOConfigProfilesModel] = SettingsField(
default_factory=list, title="OCIO config profiles"
)
file_rules: CoreImageIOFileRulesModel = SettingsField(
default_factory=CoreImageIOFileRulesModel,
@ -186,12 +243,17 @@ class CoreSettings(BaseSettingsModel):
DEFAULT_VALUES = {
"imageio": {
"activate_global_color_management": False,
"ocio_config": {
"filepath": [
"{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
"{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio"
]
},
"ocio_config_profiles": [
{
"host_names": [],
"task_types": [],
"task_names": [],
"type": "builtin_path",
"builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
"custom_path": "",
"product_name": "",
}
],
"file_rules": {
"activate_global_file_rules": False,
"rules": [
@ -199,42 +261,57 @@ DEFAULT_VALUES = {
"name": "example",
"pattern": ".*(beauty).*",
"colorspace": "ACES - ACEScg",
"ext": "exr"
"ext": "exr",
}
]
}
],
},
},
"studio_name": "",
"studio_code": "",
"environments": "{\n\"STUDIO_SW\": {\n \"darwin\": \"/mnt/REPO_SW\",\n \"linux\": \"/mnt/REPO_SW\",\n \"windows\": \"P:/REPO_SW\"\n }\n}",
"environments": json.dumps(
{
"STUDIO_SW": {
"darwin": "/mnt/REPO_SW",
"linux": "/mnt/REPO_SW",
"windows": "P:/REPO_SW"
}
},
indent=4
),
"tools": DEFAULT_TOOLS_VALUES,
"version_start_category": {
"profiles": []
},
"publish": DEFAULT_PUBLISH_VALUES,
"project_folder_structure": json.dumps({
"__project_root__": {
"prod": {},
"resources": {
"footage": {
"plates": {},
"offline": {}
"project_folder_structure": json.dumps(
{
"__project_root__": {
"prod": {},
"resources": {
"footage": {
"plates": {},
"offline": {}
},
"audio": {},
"art_dept": {}
},
"audio": {},
"art_dept": {}
},
"editorial": {},
"assets": {
"characters": {},
"locations": {}
},
"shots": {}
}
}, indent=4),
"editorial": {},
"assets": {
"characters": {},
"locations": {}
},
"shots": {}
}
},
indent=4
),
"project_plugins": {
"windows": [],
"darwin": [],
"linux": []
},
"project_environments": "{}"
"project_environments": json.dumps(
{},
indent=4
)
}