Merge pull request #5541 from ynput/enhancement/OP-6154_Publishing-Luts

This commit is contained in:
Jakub Ježek 2023-10-19 14:12:25 +02:00 committed by GitHub
commit 6f22344fb5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 1202 additions and 21 deletions

View file

@ -0,0 +1,350 @@
import os
import json
import secrets
import nuke
import six
from openpype.client import (
get_version_by_id,
get_last_version_by_subset_id
)
from openpype.pipeline import (
load,
get_current_project_name,
get_representation_path,
)
from openpype.hosts.nuke.api import (
containerise,
viewer_update_and_undo_stop,
update_container,
)
class LoadOcioLookNodes(load.LoaderPlugin):
"""Loading Ocio look to the nuke.Node graph"""
families = ["ociolook"]
representations = ["*"]
extensions = {"json"}
label = "Load OcioLook [nodes]"
order = 0
icon = "cc"
color = "white"
ignore_attr = ["useLifetime"]
# plugin attributes
current_node_color = "0x4ecd91ff"
old_node_color = "0xd88467ff"
# json file variables
schema_version = 1
def load(self, context, name, namespace, data):
"""
Loading function to get the soft effects to particular read node
Arguments:
context (dict): context of version
name (str): name of the version
namespace (str): asset name
data (dict): compulsory attribute > not used
Returns:
nuke.Node: containerized nuke.Node object
"""
namespace = namespace or context['asset']['name']
suffix = secrets.token_hex(nbytes=4)
object_name = "{}_{}_{}".format(
name, namespace, suffix)
# getting file path
filepath = self.filepath_from_context(context)
json_f = self._load_json_data(filepath)
group_node = self._create_group_node(
object_name, filepath, json_f["data"])
self._node_version_color(context["version"], group_node)
self.log.info(
"Loaded lut setup: `{}`".format(group_node["name"].value()))
return containerise(
node=group_node,
name=name,
namespace=namespace,
context=context,
loader=self.__class__.__name__,
data={
"objectName": object_name,
}
)
def _create_group_node(
self,
object_name,
filepath,
data
):
"""Creates group node with all the nodes inside.
Creating mainly `OCIOFileTransform` nodes with `OCIOColorSpace` nodes
in between - in case those are needed.
Arguments:
object_name (str): name of the group node
filepath (str): path to json file
data (dict): data from json file
Returns:
nuke.Node: group node with all the nodes inside
"""
# get corresponding node
root_working_colorspace = nuke.root()["workingSpaceLUT"].value()
dir_path = os.path.dirname(filepath)
all_files = os.listdir(dir_path)
ocio_working_colorspace = _colorspace_name_by_type(
data["ocioLookWorkingSpace"])
# adding nodes to node graph
# just in case we are in group lets jump out of it
nuke.endGroup()
input_node = None
output_node = None
group_node = nuke.toNode(object_name)
if group_node:
# remove all nodes between Input and Output nodes
for node in group_node.nodes():
if node.Class() not in ["Input", "Output"]:
nuke.delete(node)
elif node.Class() == "Input":
input_node = node
elif node.Class() == "Output":
output_node = node
else:
group_node = nuke.createNode(
"Group",
"name {}_1".format(object_name),
inpanel=False
)
# adding content to the group node
with group_node:
pre_colorspace = root_working_colorspace
# reusing input node if it exists during update
if input_node:
pre_node = input_node
else:
pre_node = nuke.createNode("Input")
pre_node["name"].setValue("rgb")
# Compare script working colorspace with ocio working colorspace
# found in json file and convert to json's if needed
if pre_colorspace != ocio_working_colorspace:
pre_node = _add_ocio_colorspace_node(
pre_node,
pre_colorspace,
ocio_working_colorspace
)
pre_colorspace = ocio_working_colorspace
for ocio_item in data["ocioLookItems"]:
input_space = _colorspace_name_by_type(
ocio_item["input_colorspace"])
output_space = _colorspace_name_by_type(
ocio_item["output_colorspace"])
# making sure we are set to correct colorspace for otio item
if pre_colorspace != input_space:
pre_node = _add_ocio_colorspace_node(
pre_node,
pre_colorspace,
input_space
)
node = nuke.createNode("OCIOFileTransform")
# file path from lut representation
extension = ocio_item["ext"]
item_name = ocio_item["name"]
item_lut_file = next(
(
file for file in all_files
if file.endswith(extension)
),
None
)
if not item_lut_file:
raise ValueError(
"File with extension '{}' not "
"found in directory".format(extension)
)
item_lut_path = os.path.join(
dir_path, item_lut_file).replace("\\", "/")
node["file"].setValue(item_lut_path)
node["name"].setValue(item_name)
node["direction"].setValue(ocio_item["direction"])
node["interpolation"].setValue(ocio_item["interpolation"])
node["working_space"].setValue(input_space)
pre_node.autoplace()
node.setInput(0, pre_node)
node.autoplace()
# pass output space into pre_colorspace for next iteration
# or for output node comparison
pre_colorspace = output_space
pre_node = node
# making sure we are back in script working colorspace
if pre_colorspace != root_working_colorspace:
pre_node = _add_ocio_colorspace_node(
pre_node,
pre_colorspace,
root_working_colorspace
)
# reusing output node if it exists during update
if not output_node:
output = nuke.createNode("Output")
else:
output = output_node
output.setInput(0, pre_node)
return group_node
def update(self, container, representation):
project_name = get_current_project_name()
version_doc = get_version_by_id(project_name, representation["parent"])
object_name = container['objectName']
filepath = get_representation_path(representation)
json_f = self._load_json_data(filepath)
group_node = self._create_group_node(
object_name,
filepath,
json_f["data"]
)
self._node_version_color(version_doc, group_node)
self.log.info("Updated lut setup: `{}`".format(
group_node["name"].value()))
return update_container(
group_node, {"representation": str(representation["_id"])})
def _load_json_data(self, filepath):
# getting data from json file with unicode conversion
with open(filepath, "r") as _file:
json_f = {self._bytify(key): self._bytify(value)
for key, value in json.load(_file).items()}
# check if the version in json_f is the same as plugin version
if json_f["version"] != self.schema_version:
raise KeyError(
"Version of json file is not the same as plugin version")
return json_f
def _bytify(self, input):
"""
Converts unicode strings to strings
It goes through all dictionary
Arguments:
input (dict/str): input
Returns:
dict: with fixed values and keys
"""
if isinstance(input, dict):
return {self._bytify(key): self._bytify(value)
for key, value in input.items()}
elif isinstance(input, list):
return [self._bytify(element) for element in input]
elif isinstance(input, six.text_type):
return str(input)
else:
return input
def switch(self, container, representation):
self.update(container, representation)
def remove(self, container):
node = nuke.toNode(container['objectName'])
with viewer_update_and_undo_stop():
nuke.delete(node)
def _node_version_color(self, version, node):
""" Coloring a node by correct color by actual version"""
project_name = get_current_project_name()
last_version_doc = get_last_version_by_subset_id(
project_name, version["parent"], fields=["_id"]
)
# change color of node
if version["_id"] == last_version_doc["_id"]:
color_value = self.current_node_color
else:
color_value = self.old_node_color
node["tile_color"].setValue(int(color_value, 16))
def _colorspace_name_by_type(colorspace_data):
"""
Returns colorspace name by type
Arguments:
colorspace_data (dict): colorspace data
Returns:
str: colorspace name
"""
if colorspace_data["type"] == "colorspaces":
return colorspace_data["name"]
elif colorspace_data["type"] == "roles":
return colorspace_data["colorspace"]
else:
raise KeyError("Unknown colorspace type: {}".format(
colorspace_data["type"]))
def _add_ocio_colorspace_node(pre_node, input_space, output_space):
"""
Adds OCIOColorSpace node to the node graph
Arguments:
pre_node (nuke.Node): node to connect to
input_space (str): input colorspace
output_space (str): output colorspace
Returns:
nuke.Node: node with OCIOColorSpace node
"""
node = nuke.createNode("OCIOColorSpace")
node.setInput(0, pre_node)
node["in_colorspace"].setValue(input_space)
node["out_colorspace"].setValue(output_space)
pre_node.autoplace()
node.setInput(0, pre_node)
node.autoplace()
return node

View file

@ -0,0 +1,173 @@
# -*- coding: utf-8 -*-
"""Creator of colorspace look files.
This creator is used to publish colorspace look files thanks to
production type `ociolook`. All files are published as representation.
"""
from pathlib import Path
from openpype.client import get_asset_by_name
from openpype.lib.attribute_definitions import (
FileDef, EnumDef, TextDef, UISeparatorDef
)
from openpype.pipeline import (
CreatedInstance,
CreatorError
)
from openpype.pipeline import colorspace
from openpype.hosts.traypublisher.api.plugin import TrayPublishCreator
class CreateColorspaceLook(TrayPublishCreator):
"""Creates colorspace look files."""
identifier = "io.openpype.creators.traypublisher.colorspace_look"
label = "Colorspace Look"
family = "ociolook"
description = "Publishes color space look file."
extensions = [".cc", ".cube", ".3dl", ".spi1d", ".spi3d", ".csp", ".lut"]
enabled = False
colorspace_items = [
(None, "Not set")
]
colorspace_attr_show = False
config_items = None
config_data = None
def get_detail_description(self):
return """# Colorspace Look
This creator publishes color space look file (LUT).
"""
def get_icon(self):
return "mdi.format-color-fill"
def create(self, subset_name, instance_data, pre_create_data):
repr_file = pre_create_data.get("luts_file")
if not repr_file:
raise CreatorError("No files specified")
files = repr_file.get("filenames")
if not files:
# this should never happen
raise CreatorError("Missing files from representation")
asset_doc = get_asset_by_name(
self.project_name, instance_data["asset"])
subset_name = self.get_subset_name(
variant=instance_data["variant"],
task_name=instance_data["task"] or "Not set",
project_name=self.project_name,
asset_doc=asset_doc,
)
instance_data["creator_attributes"] = {
"abs_lut_path": (
Path(repr_file["directory"]) / files[0]).as_posix()
}
# Create new instance
new_instance = CreatedInstance(self.family, subset_name,
instance_data, self)
new_instance.transient_data["config_items"] = self.config_items
new_instance.transient_data["config_data"] = self.config_data
self._store_new_instance(new_instance)
def collect_instances(self):
super().collect_instances()
for instance in self.create_context.instances:
if instance.creator_identifier == self.identifier:
instance.transient_data["config_items"] = self.config_items
instance.transient_data["config_data"] = self.config_data
def get_instance_attr_defs(self):
return [
EnumDef(
"working_colorspace",
self.colorspace_items,
default="Not set",
label="Working Colorspace",
),
UISeparatorDef(
label="Advanced1"
),
TextDef(
"abs_lut_path",
label="LUT Path",
),
EnumDef(
"input_colorspace",
self.colorspace_items,
default="Not set",
label="Input Colorspace",
),
EnumDef(
"direction",
[
(None, "Not set"),
("forward", "Forward"),
("inverse", "Inverse")
],
default="Not set",
label="Direction"
),
EnumDef(
"interpolation",
[
(None, "Not set"),
("linear", "Linear"),
("tetrahedral", "Tetrahedral"),
("best", "Best"),
("nearest", "Nearest")
],
default="Not set",
label="Interpolation"
),
EnumDef(
"output_colorspace",
self.colorspace_items,
default="Not set",
label="Output Colorspace",
),
]
def get_pre_create_attr_defs(self):
return [
FileDef(
"luts_file",
folders=False,
extensions=self.extensions,
allow_sequences=False,
single_item=True,
label="Look Files",
)
]
def apply_settings(self, project_settings, system_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,
project_settings=project_settings
)
if not config_data:
self.enabled = False
return
filepath = config_data["path"]
config_items = colorspace.get_ocio_config_colorspaces(filepath)
labeled_colorspaces = colorspace.get_colorspaces_enumerator_items(
config_items,
include_aliases=True,
include_roles=True
)
self.config_items = config_items
self.config_data = config_data
self.colorspace_items.extend(labeled_colorspaces)
self.enabled = True

View file

@ -0,0 +1,86 @@
import os
from pprint import pformat
import pyblish.api
from openpype.pipeline import publish
from openpype.pipeline import colorspace
class CollectColorspaceLook(pyblish.api.InstancePlugin,
publish.OpenPypePyblishPluginMixin):
"""Collect OCIO colorspace look from LUT file
"""
label = "Collect Colorspace Look"
order = pyblish.api.CollectorOrder
hosts = ["traypublisher"]
families = ["ociolook"]
def process(self, instance):
creator_attrs = instance.data["creator_attributes"]
lut_repre_name = "LUTfile"
file_url = creator_attrs["abs_lut_path"]
file_name = os.path.basename(file_url)
base_name, ext = os.path.splitext(file_name)
# set output name with base_name which was cleared
# of all symbols and all parts were capitalized
output_name = (base_name.replace("_", " ")
.replace(".", " ")
.replace("-", " ")
.title()
.replace(" ", ""))
# get config items
config_items = instance.data["transientData"]["config_items"]
config_data = instance.data["transientData"]["config_data"]
# get colorspace items
converted_color_data = {}
for colorspace_key in [
"working_colorspace",
"input_colorspace",
"output_colorspace"
]:
if creator_attrs[colorspace_key]:
color_data = colorspace.convert_colorspace_enumerator_item(
creator_attrs[colorspace_key], config_items)
converted_color_data[colorspace_key] = color_data
else:
converted_color_data[colorspace_key] = None
# add colorspace to config data
if converted_color_data["working_colorspace"]:
config_data["colorspace"] = (
converted_color_data["working_colorspace"]["name"]
)
# create lut representation data
lut_repre = {
"name": lut_repre_name,
"output": output_name,
"ext": ext.lstrip("."),
"files": file_name,
"stagingDir": os.path.dirname(file_url),
"tags": []
}
instance.data.update({
"representations": [lut_repre],
"source": file_url,
"ocioLookWorkingSpace": converted_color_data["working_colorspace"],
"ocioLookItems": [
{
"name": lut_repre_name,
"ext": ext.lstrip("."),
"input_colorspace": converted_color_data[
"input_colorspace"],
"output_colorspace": converted_color_data[
"output_colorspace"],
"direction": creator_attrs["direction"],
"interpolation": creator_attrs["interpolation"],
"config_data": config_data
}
],
})
self.log.debug(pformat(instance.data))

View file

@ -1,6 +1,8 @@
import pyblish.api
from openpype.pipeline import registered_host
from openpype.pipeline import publish
from openpype.pipeline import (
publish,
registered_host
)
from openpype.lib import EnumDef
from openpype.pipeline import colorspace
@ -13,11 +15,14 @@ class CollectColorspace(pyblish.api.InstancePlugin,
label = "Choose representation colorspace"
order = pyblish.api.CollectorOrder + 0.49
hosts = ["traypublisher"]
families = ["render", "plate", "reference", "image", "online"]
enabled = False
colorspace_items = [
(None, "Don't override")
]
colorspace_attr_show = False
config_items = None
def process(self, instance):
values = self.get_attr_values_from_data(instance.data)
@ -48,10 +53,14 @@ class CollectColorspace(pyblish.api.InstancePlugin,
if config_data:
filepath = config_data["path"]
config_items = colorspace.get_ocio_config_colorspaces(filepath)
cls.colorspace_items.extend((
(name, name) for name in config_items.keys()
))
cls.colorspace_attr_show = True
labeled_colorspaces = colorspace.get_colorspaces_enumerator_items(
config_items,
include_aliases=True,
include_roles=True
)
cls.config_items = config_items
cls.colorspace_items.extend(labeled_colorspaces)
cls.enabled = True
@classmethod
def get_attribute_defs(cls):
@ -60,7 +69,6 @@ class CollectColorspace(pyblish.api.InstancePlugin,
"colorspace",
cls.colorspace_items,
default="Don't override",
label="Override Colorspace",
hidden=not cls.colorspace_attr_show
label="Override Colorspace"
)
]

View file

@ -0,0 +1,45 @@
import os
import json
import pyblish.api
from openpype.pipeline import publish
class ExtractColorspaceLook(publish.Extractor,
publish.OpenPypePyblishPluginMixin):
"""Extract OCIO colorspace look from LUT file
"""
label = "Extract Colorspace Look"
order = pyblish.api.ExtractorOrder
hosts = ["traypublisher"]
families = ["ociolook"]
def process(self, instance):
ociolook_items = instance.data["ocioLookItems"]
ociolook_working_color = instance.data["ocioLookWorkingSpace"]
staging_dir = self.staging_dir(instance)
# create ociolook file attributes
ociolook_file_name = "ocioLookFile.json"
ociolook_file_content = {
"version": 1,
"data": {
"ocioLookItems": ociolook_items,
"ocioLookWorkingSpace": ociolook_working_color
}
}
# write ociolook content into json file saved in staging dir
file_url = os.path.join(staging_dir, ociolook_file_name)
with open(file_url, "w") as f_:
json.dump(ociolook_file_content, f_, indent=4)
# create lut representation data
ociolook_repre = {
"name": "ocioLookFile",
"ext": "json",
"files": ociolook_file_name,
"stagingDir": staging_dir,
"tags": []
}
instance.data["representations"].append(ociolook_repre)

View file

@ -18,6 +18,7 @@ class ValidateColorspace(pyblish.api.InstancePlugin,
label = "Validate representation colorspace"
order = pyblish.api.ValidatorOrder
hosts = ["traypublisher"]
families = ["render", "plate", "reference", "image", "online"]
def process(self, instance):

View file

@ -0,0 +1,89 @@
import pyblish.api
from openpype.pipeline import (
publish,
PublishValidationError
)
class ValidateColorspaceLook(pyblish.api.InstancePlugin,
publish.OpenPypePyblishPluginMixin):
"""Validate colorspace look attributes"""
label = "Validate colorspace look attributes"
order = pyblish.api.ValidatorOrder
hosts = ["traypublisher"]
families = ["ociolook"]
def process(self, instance):
create_context = instance.context.data["create_context"]
created_instance = create_context.get_instance_by_id(
instance.data["instance_id"])
creator_defs = created_instance.creator_attribute_defs
ociolook_working_color = instance.data.get("ocioLookWorkingSpace")
ociolook_items = instance.data.get("ocioLookItems", [])
creator_defs_by_key = {_def.key: _def.label for _def in creator_defs}
not_set_keys = {}
if not ociolook_working_color:
not_set_keys["working_colorspace"] = creator_defs_by_key[
"working_colorspace"]
for ociolook_item in ociolook_items:
item_not_set_keys = self.validate_colorspace_set_attrs(
ociolook_item, creator_defs_by_key)
if item_not_set_keys:
not_set_keys[ociolook_item["name"]] = item_not_set_keys
if not_set_keys:
message = (
"Colorspace look attributes are not set: \n"
)
for key, value in not_set_keys.items():
if isinstance(value, list):
values_string = "\n\t- ".join(value)
message += f"\n\t{key}:\n\t- {values_string}"
else:
message += f"\n\t{value}"
raise PublishValidationError(
title="Colorspace Look attributes",
message=message,
description=message
)
def validate_colorspace_set_attrs(
self,
ociolook_item,
creator_defs_by_key
):
"""Validate colorspace look attributes"""
self.log.debug(f"Validate colorspace look attributes: {ociolook_item}")
check_keys = [
"input_colorspace",
"output_colorspace",
"direction",
"interpolation"
]
not_set_keys = []
for key in check_keys:
if ociolook_item[key]:
# key is set and it is correct
continue
def_label = creator_defs_by_key.get(key)
if not def_label:
# raise since key is not recognized by creator defs
raise KeyError(
f"Colorspace look attribute '{key}' is not "
f"recognized by creator attributes: {creator_defs_by_key}"
)
not_set_keys.append(def_label)
return not_set_keys

View file

@ -1,4 +1,3 @@
from copy import deepcopy
import re
import os
import json
@ -7,6 +6,7 @@ import functools
import platform
import tempfile
import warnings
from copy import deepcopy
from openpype import PACKAGE_DIR
from openpype.settings import get_project_settings
@ -356,7 +356,10 @@ def parse_colorspace_from_filepath(
"Must provide `config_path` if `colorspaces` is not provided."
)
colorspaces = colorspaces or get_ocio_config_colorspaces(config_path)
colorspaces = (
colorspaces
or get_ocio_config_colorspaces(config_path)["colorspaces"]
)
underscored_colorspaces = {
key.replace(" ", "_"): key for key in colorspaces
if " " in key
@ -393,7 +396,7 @@ def validate_imageio_colorspace_in_config(config_path, colorspace_name):
Returns:
bool: True if exists
"""
colorspaces = get_ocio_config_colorspaces(config_path)
colorspaces = get_ocio_config_colorspaces(config_path)["colorspaces"]
if colorspace_name not in colorspaces:
raise KeyError(
"Missing colorspace '{}' in config file '{}'".format(
@ -530,6 +533,157 @@ def get_ocio_config_colorspaces(config_path):
return CachedData.ocio_config_colorspaces[config_path]
def convert_colorspace_enumerator_item(
colorspace_enum_item,
config_items
):
"""Convert colorspace enumerator item to dictionary
Args:
colorspace_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
# split string with `::` separator and set first as key and second as value
item_type, item_name = colorspace_enum_item.split("::")
item_data = None
if item_type == "aliases":
# loop through all colorspaces and find matching alias
for name, _data in config_items.get("colorspaces", {}).items():
if item_name in _data.get("aliases", []):
item_data = deepcopy(_data)
item_data.update({
"name": name,
"type": "colorspace"
})
break
else:
# find matching colorspace item found in labeled_colorspaces
item_data = config_items.get(item_type, {}).get(item_name)
if item_data:
item_data = deepcopy(item_data)
item_data.update({
"name": item_name,
"type": item_type
})
# raise exception if item is not found
if not item_data:
message_config_keys = ", ".join(
"'{}':{}".format(
key,
set(config_items.get(key, {}).keys())
) for key in config_items.keys()
)
raise KeyError(
"Missing colorspace item '{}' in config data: [{}]".format(
colorspace_enum_item, message_config_keys
)
)
return item_data
def get_colorspaces_enumerator_items(
config_items,
include_aliases=False,
include_looks=False,
include_roles=False,
include_display_views=False
):
"""Get all colorspace data with labels
Wrapper function for aggregating all names and its families.
Families can be used for building menu and submenus in gui.
Args:
config_items (dict[str,dict]): colorspace data coming from
`get_ocio_config_colorspaces` function
include_aliases (bool): include aliases in result
include_looks (bool): include looks in result
include_roles (bool): include roles in result
Returns:
list[tuple[str,str]]: colorspace and family in couple
"""
labeled_colorspaces = []
aliases = set()
colorspaces = set()
looks = set()
roles = set()
display_views = set()
for items_type, colorspace_items in config_items.items():
if items_type == "colorspaces":
for color_name, color_data in colorspace_items.items():
if color_data.get("aliases"):
aliases.update([
(
"aliases::{}".format(alias_name),
"[alias] {} ({})".format(alias_name, color_name)
)
for alias_name in color_data["aliases"]
])
colorspaces.add((
"{}::{}".format(items_type, color_name),
"[colorspace] {}".format(color_name)
))
elif items_type == "looks":
looks.update([
(
"{}::{}".format(items_type, name),
"[look] {} ({})".format(name, role_data["process_space"])
)
for name, role_data in colorspace_items.items()
])
elif items_type == "displays_views":
display_views.update([
(
"{}::{}".format(items_type, name),
"[view (display)] {}".format(name)
)
for name, _ in colorspace_items.items()
])
elif items_type == "roles":
roles.update([
(
"{}::{}".format(items_type, name),
"[role] {} ({})".format(name, role_data["colorspace"])
)
for name, role_data in colorspace_items.items()
])
if roles and include_roles:
roles = sorted(roles, key=lambda x: x[0])
labeled_colorspaces.extend(roles)
# add colorspaces as second so it is not first in menu
colorspaces = sorted(colorspaces, key=lambda x: x[0])
labeled_colorspaces.extend(colorspaces)
if aliases and include_aliases:
aliases = sorted(aliases, key=lambda x: x[0])
labeled_colorspaces.extend(aliases)
if looks and include_looks:
looks = sorted(looks, key=lambda x: x[0])
labeled_colorspaces.extend(looks)
if display_views and include_display_views:
display_views = sorted(display_views, key=lambda x: x[0])
labeled_colorspaces.extend(display_views)
return labeled_colorspaces
# TODO: remove this in future - backward compatibility
@deprecated("_get_wrapped_with_subprocess")
def get_colorspace_data_subprocess(config_path):

View file

@ -107,6 +107,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
"rig",
"plate",
"look",
"ociolook",
"audio",
"yetiRig",
"yeticache",

View file

@ -106,11 +106,47 @@ def _get_colorspace_data(config_path):
config = ocio.Config().CreateFromFile(str(config_path))
return {
c_.getName(): c_.getFamily()
for c_ in config.getColorSpaces()
colorspace_data = {
"roles": {},
"colorspaces": {
color.getName(): {
"family": color.getFamily(),
"categories": list(color.getCategories()),
"aliases": list(color.getAliases()),
"equalitygroup": color.getEqualityGroup(),
}
for color in config.getColorSpaces()
},
"displays_views": {
f"{view} ({display})": {
"display": display,
"view": view
}
for display in config.getDisplays()
for view in config.getViews(display)
},
"looks": {}
}
# add looks
looks = config.getLooks()
if looks:
colorspace_data["looks"] = {
look.getName(): {"process_space": look.getProcessSpace()}
for look in looks
}
# add roles
roles = config.getRoles()
if roles:
colorspace_data["roles"] = {
role: {"colorspace": colorspace}
for (role, colorspace) in roles
}
return colorspace_data
@config.command(
name="get_views",

View file

@ -37,7 +37,7 @@ class TestPipelinePublishPlugins(TestPipeline):
# files are the same as those used in `test_pipeline_colorspace`
TEST_FILES = [
(
"1Lf-mFxev7xiwZCWfImlRcw7Fj8XgNQMh",
"1csqimz8bbNcNgxtEXklLz6GRv91D3KgA",
"test_pipeline_colorspace.zip",
""
)
@ -123,8 +123,7 @@ class TestPipelinePublishPlugins(TestPipeline):
def test_get_colorspace_settings(self, context, config_path_asset):
expected_config_template = (
"{root[work]}/{project[name]}"
"/{hierarchy}/{asset}/config/aces.ocio"
"{root[work]}/{project[name]}/config/aces.ocio"
)
expected_file_rules = {
"comp_review": {
@ -177,16 +176,16 @@ class TestPipelinePublishPlugins(TestPipeline):
# load plugin function for testing
plugin = publish_plugins.ColormanagedPyblishPluginMixin()
plugin.log = log
context.data["imageioSettings"] = (config_data_nuke, file_rules_nuke)
plugin.set_representation_colorspace(
representation_nuke, context,
colorspace_settings=(config_data_nuke, file_rules_nuke)
representation_nuke, context
)
# load plugin function for testing
plugin = publish_plugins.ColormanagedPyblishPluginMixin()
plugin.log = log
context.data["imageioSettings"] = (config_data_hiero, file_rules_hiero)
plugin.set_representation_colorspace(
representation_hiero, context,
colorspace_settings=(config_data_hiero, file_rules_hiero)
representation_hiero, context
)
colorspace_data_nuke = representation_nuke.get("colorspaceData")

View file

@ -0,0 +1,118 @@
import unittest
from openpype.pipeline.colorspace import convert_colorspace_enumerator_item
class TestConvertColorspaceEnumeratorItem(unittest.TestCase):
def setUp(self):
self.config_items = {
"colorspaces": {
"sRGB": {
"aliases": ["sRGB_1"],
"family": "colorspace",
"categories": ["colors"],
"equalitygroup": "equalitygroup",
},
"Rec.709": {
"aliases": ["rec709_1", "rec709_2"],
},
},
"looks": {
"sRGB_to_Rec.709": {
"process_space": "sRGB",
},
},
"displays_views": {
"sRGB (ACES)": {
"view": "sRGB",
"display": "ACES",
},
"Rec.709 (ACES)": {
"view": "Rec.709",
"display": "ACES",
},
},
"roles": {
"compositing_linear": {
"colorspace": "linear",
},
},
}
def test_valid_item(self):
colorspace_item_data = convert_colorspace_enumerator_item(
"colorspaces::sRGB", self.config_items)
self.assertEqual(
colorspace_item_data,
{
"name": "sRGB",
"type": "colorspaces",
"aliases": ["sRGB_1"],
"family": "colorspace",
"categories": ["colors"],
"equalitygroup": "equalitygroup"
}
)
alias_item_data = convert_colorspace_enumerator_item(
"aliases::rec709_1", self.config_items)
self.assertEqual(
alias_item_data,
{
"aliases": ["rec709_1", "rec709_2"],
"name": "Rec.709",
"type": "colorspace"
}
)
display_view_item_data = convert_colorspace_enumerator_item(
"displays_views::sRGB (ACES)", self.config_items)
self.assertEqual(
display_view_item_data,
{
"type": "displays_views",
"name": "sRGB (ACES)",
"view": "sRGB",
"display": "ACES"
}
)
role_item_data = convert_colorspace_enumerator_item(
"roles::compositing_linear", self.config_items)
self.assertEqual(
role_item_data,
{
"name": "compositing_linear",
"type": "roles",
"colorspace": "linear"
}
)
look_item_data = convert_colorspace_enumerator_item(
"looks::sRGB_to_Rec.709", self.config_items)
self.assertEqual(
look_item_data,
{
"type": "looks",
"name": "sRGB_to_Rec.709",
"process_space": "sRGB"
}
)
def test_invalid_item(self):
config_items = {
"RGB": {
"sRGB": {"red": 255, "green": 255, "blue": 255},
"AdobeRGB": {"red": 255, "green": 255, "blue": 255},
}
}
with self.assertRaises(KeyError):
convert_colorspace_enumerator_item("RGB::invalid", config_items)
def test_missing_config_data(self):
config_items = {}
with self.assertRaises(KeyError):
convert_colorspace_enumerator_item("RGB::sRGB", config_items)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,121 @@
import unittest
from openpype.pipeline.colorspace import get_colorspaces_enumerator_items
class TestGetColorspacesEnumeratorItems(unittest.TestCase):
def setUp(self):
self.config_items = {
"colorspaces": {
"sRGB": {
"aliases": ["sRGB_1"],
},
"Rec.709": {
"aliases": ["rec709_1", "rec709_2"],
},
},
"looks": {
"sRGB_to_Rec.709": {
"process_space": "sRGB",
},
},
"displays_views": {
"sRGB (ACES)": {
"view": "sRGB",
"display": "ACES",
},
"Rec.709 (ACES)": {
"view": "Rec.709",
"display": "ACES",
},
},
"roles": {
"compositing_linear": {
"colorspace": "linear",
},
},
}
def test_colorspaces(self):
result = get_colorspaces_enumerator_items(self.config_items)
expected = [
("colorspaces::Rec.709", "[colorspace] Rec.709"),
("colorspaces::sRGB", "[colorspace] sRGB"),
]
self.assertEqual(result, expected)
def test_aliases(self):
result = get_colorspaces_enumerator_items(
self.config_items, include_aliases=True)
expected = [
("colorspaces::Rec.709", "[colorspace] Rec.709"),
("colorspaces::sRGB", "[colorspace] sRGB"),
("aliases::rec709_1", "[alias] rec709_1 (Rec.709)"),
("aliases::rec709_2", "[alias] rec709_2 (Rec.709)"),
("aliases::sRGB_1", "[alias] sRGB_1 (sRGB)"),
]
self.assertEqual(result, expected)
def test_looks(self):
result = get_colorspaces_enumerator_items(
self.config_items, include_looks=True)
expected = [
("colorspaces::Rec.709", "[colorspace] Rec.709"),
("colorspaces::sRGB", "[colorspace] sRGB"),
("looks::sRGB_to_Rec.709", "[look] sRGB_to_Rec.709 (sRGB)"),
]
self.assertEqual(result, expected)
def test_display_views(self):
result = get_colorspaces_enumerator_items(
self.config_items, include_display_views=True)
expected = [
("colorspaces::Rec.709", "[colorspace] Rec.709"),
("colorspaces::sRGB", "[colorspace] sRGB"),
("displays_views::Rec.709 (ACES)", "[view (display)] Rec.709 (ACES)"), # noqa: E501
("displays_views::sRGB (ACES)", "[view (display)] sRGB (ACES)"),
]
self.assertEqual(result, expected)
def test_roles(self):
result = get_colorspaces_enumerator_items(
self.config_items, include_roles=True)
expected = [
("roles::compositing_linear", "[role] compositing_linear (linear)"), # noqa: E501
("colorspaces::Rec.709", "[colorspace] Rec.709"),
("colorspaces::sRGB", "[colorspace] sRGB"),
]
self.assertEqual(result, expected)
def test_all(self):
message_config_keys = ", ".join(
"'{}':{}".format(
key,
set(self.config_items.get(key, {}).keys())
) for key in self.config_items.keys()
)
print("Testing with config: [{}]".format(message_config_keys))
result = get_colorspaces_enumerator_items(
self.config_items,
include_aliases=True,
include_looks=True,
include_roles=True,
include_display_views=True,
)
expected = [
("roles::compositing_linear", "[role] compositing_linear (linear)"), # noqa: E501
("colorspaces::Rec.709", "[colorspace] Rec.709"),
("colorspaces::sRGB", "[colorspace] sRGB"),
("aliases::rec709_1", "[alias] rec709_1 (Rec.709)"),
("aliases::rec709_2", "[alias] rec709_2 (Rec.709)"),
("aliases::sRGB_1", "[alias] sRGB_1 (sRGB)"),
("looks::sRGB_to_Rec.709", "[look] sRGB_to_Rec.709 (sRGB)"),
("displays_views::Rec.709 (ACES)", "[view (display)] Rec.709 (ACES)"), # noqa: E501
("displays_views::sRGB (ACES)", "[view (display)] sRGB (ACES)"),
]
self.assertEqual(result, expected)
if __name__ == "__main__":
unittest.main()