Merge branch 'develop' into enhancement/set-version-works-for-all-selection

This commit is contained in:
Jakub Trllo 2024-06-14 13:40:26 +02:00 committed by GitHub
commit 6461d1ba4f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 638 additions and 390 deletions

View file

@ -1,7 +1,8 @@
from .window import ContextDialog, main
from .window import ContextDialog, main, ask_for_context
__all__ = (
"ContextDialog",
"main",
"ask_for_context"
)

View file

@ -791,3 +791,12 @@ def main(
window.show()
app.exec_()
controller.store_output()
def ask_for_context(strict=True):
controller = ContextDialogController()
controller.set_strict(strict)
window = ContextDialog(controller=controller)
window.exec_()
return controller.get_selected_context()

View file

@ -182,7 +182,27 @@ class TrayManager:
}:
envs.pop(key, None)
# Remove any existing addon path from 'PYTHONPATH'
addons_dir = os.environ.get("AYON_ADDONS_DIR", "")
if addons_dir:
addons_dir = os.path.normpath(addons_dir)
addons_dir = addons_dir.lower()
pythonpath = envs.get("PYTHONPATH") or ""
new_python_paths = []
for path in pythonpath.split(os.pathsep):
if not path:
continue
path = os.path.normpath(path)
if path.lower().startswith(addons_dir):
continue
new_python_paths.append(path)
envs["PYTHONPATH"] = os.pathsep.join(new_python_paths)
# Start new process
run_detached_process(args, env=envs)
# Exit current tray process
self.exit()
def exit(self):

View file

@ -2,7 +2,10 @@
"""Creator plugin for creating publishable Houdini Digital Assets."""
import ayon_api
from ayon_core.pipeline import CreatorError
from ayon_core.pipeline import (
CreatorError,
get_current_project_name
)
from ayon_houdini.api import plugin
import hou
@ -56,8 +59,18 @@ class CreateHDA(plugin.HoudiniCreator):
raise CreatorError(
"cannot create hda from node {}".format(to_hda))
# Pick a unique type name for HDA product per folder path per project.
type_name = (
"{project_name}{folder_path}_{node_name}".format(
project_name=get_current_project_name(),
folder_path=folder_path.replace("/","_"),
node_name=node_name
)
)
hda_node = to_hda.createDigitalAsset(
name=node_name,
name=type_name,
description=node_name,
hda_file_name="$HIP/{}.hda".format(node_name)
)
hda_node.layoutChildren()

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import os
from ayon_core.pipeline import get_representation_path
from ayon_core.pipeline.load import LoadError
from ayon_houdini.api import (
pipeline,
plugin
@ -28,14 +29,18 @@ class HdaLoader(plugin.HoudiniLoader):
# Get the root node
obj = hou.node("/obj")
# Create a unique name
counter = 1
namespace = namespace or context["folder"]["name"]
formatted = "{}_{}".format(namespace, name) if namespace else name
node_name = "{0}_{1:03d}".format(formatted, counter)
node_name = "{}_{}".format(namespace, name) if namespace else name
hou.hda.installFile(file_path)
hda_node = obj.createNode(name, node_name)
# Get the type name from the HDA definition.
hda_defs = hou.hda.definitionsInFile(file_path)
if not hda_defs:
raise LoadError(f"No HDA definitions found in file: {file_path}")
type_name = hda_defs[0].nodeTypeName()
hda_node = obj.createNode(type_name, node_name)
self[:] = [hda_node]

View file

@ -1,5 +1,4 @@
import os
from pprint import pformat
import re
import json
import six
@ -37,6 +36,7 @@ from ayon_core.pipeline import (
get_current_host_name,
get_current_project_name,
get_current_folder_path,
get_current_task_name,
AYON_INSTANCE_ID,
AVALON_INSTANCE_ID,
)
@ -154,15 +154,9 @@ def set_node_data(node, knobname, data):
"""
# if exists then update data
if knobname in node.knobs():
log.debug("Updating knobname `{}` on node `{}`".format(
knobname, node.name()
))
update_node_data(node, knobname, data)
return
log.debug("Creating knobname `{}` on node `{}`".format(
knobname, node.name()
))
# else create new
knob_value = JSON_PREFIX + json.dumps(data)
knob = nuke.String_Knob(knobname)
@ -513,11 +507,9 @@ def get_avalon_knob_data(node, prefix="avalon:", create=True):
# check if the node is avalon tracked
try:
# check if data available on the node
test = node[DATA_GROUP_KEY].value()
log.debug("Only testing if data available: `{}`".format(test))
except NameError as e:
_ = node[DATA_GROUP_KEY].value()
except NameError:
# if it doesn't then create it
log.debug("Creating avalon knob: `{}`".format(e))
if create:
node = set_avalon_knob_data(node)
return get_avalon_knob_data(node)
@ -678,8 +670,6 @@ def get_imageio_node_setting(node_class, plugin_name, product_name):
imageio_node = node
break
log.debug("__ imageio_node: {}".format(imageio_node))
if not imageio_node:
return
@ -690,8 +680,6 @@ def get_imageio_node_setting(node_class, plugin_name, product_name):
product_name,
imageio_node["knobs"]
)
log.info("ImageIO node: {}".format(imageio_node))
return imageio_node
@ -706,8 +694,6 @@ def get_imageio_node_override_setting(
# find matching override node
override_imageio_node = None
for onode in override_nodes:
log.debug("__ onode: {}".format(onode))
log.debug("__ productName: {}".format(product_name))
if node_class not in onode["nuke_node_class"]:
continue
@ -727,7 +713,6 @@ def get_imageio_node_override_setting(
override_imageio_node = onode
break
log.debug("__ override_imageio_node: {}".format(override_imageio_node))
# add overrides to imageio_node
if override_imageio_node:
# get all knob names in imageio_node
@ -740,7 +725,6 @@ def get_imageio_node_override_setting(
for knob in knobs_settings:
# add missing knobs into imageio_node
if oknob_name not in knob_names:
log.debug("_ adding knob: `{}`".format(oknob))
knobs_settings.append(oknob)
knob_names.append(oknob_name)
continue
@ -750,9 +734,6 @@ def get_imageio_node_override_setting(
knob_type = knob["type"]
# override matching knob name
log.debug(
"_ overriding knob: `{}` > `{}`".format(knob, oknob)
)
if not oknob_value:
# remove original knob if no value found in oknob
knobs_settings.remove(knob)
@ -923,7 +904,6 @@ def writes_version_sync():
new_version = "v" + str("{" + ":0>{}".format(padding) + "}").format(
int(rootVersion)
)
log.debug("new_version: {}".format(new_version))
except Exception:
return
@ -936,13 +916,11 @@ def writes_version_sync():
try:
if avalon_knob_data["families"] not in ["render"]:
log.debug(avalon_knob_data["families"])
continue
node_file = each["file"].value()
node_version = "v" + get_version_from_path(node_file)
log.debug("node_version: {}".format(node_version))
node_new_file = node_file.replace(node_version, new_version)
each["file"].setValue(node_new_file)
@ -1332,7 +1310,6 @@ def set_node_knobs_from_settings(node, knob_settings, **kwargs):
kwargs (dict)[optional]: keys for formattable knob settings
"""
for knob in knob_settings:
log.debug("__ knob: {}".format(pformat(knob)))
knob_name = knob["name"]
if knob_name not in node.knobs():
continue
@ -1486,13 +1463,17 @@ class WorkfileSettings(object):
Context._project_entity = project_entity
self._project_name = project_name
self._folder_path = get_current_folder_path()
self._task_name = get_current_task_name()
self._folder_entity = ayon_api.get_folder_by_path(
project_name, self._folder_path
)
self._root_node = root_node or nuke.root()
self._nodes = self.get_nodes(nodes=nodes)
self.data = kwargs
context_data = get_template_data_with_names(
project_name, self._folder_path, self._task_name, "nuke"
)
self.formatting_data = context_data
def get_nodes(self, nodes=None, nodes_filter=None):
@ -1509,36 +1490,23 @@ class WorkfileSettings(object):
for filter in nodes_filter:
return [n for n in self._nodes if filter in n.Class()]
def set_viewers_colorspace(self, viewer_dict):
def set_viewers_colorspace(self, imageio_nuke):
''' Adds correct colorspace to viewer
Arguments:
viewer_dict (dict): adjustments from presets
imageio_nuke (dict): nuke colorspace configurations
'''
if not isinstance(viewer_dict, dict):
msg = "set_viewers_colorspace(): argument should be dictionary"
log.error(msg)
nuke.message(msg)
return
filter_knobs = [
"viewerProcess",
"wipe_position",
"monitorOutOutputTransform"
]
display, viewer = get_viewer_config_from_string(
viewer_dict["viewerProcess"]
viewer_process = self._display_and_view_formatted(
imageio_nuke["viewer"]
)
viewer_process = create_viewer_profile_string(
viewer, display, path_like=False
)
display, viewer = get_viewer_config_from_string(
viewer_dict["output_transform"]
)
output_transform = create_viewer_profile_string(
viewer, display, path_like=False
output_transform = self._display_and_view_formatted(
imageio_nuke["monitor"]
)
erased_viewers = []
for v in nuke.allNodes(filter="Viewer"):
@ -1547,8 +1515,10 @@ class WorkfileSettings(object):
if viewer_process not in v["viewerProcess"].value():
copy_inputs = v.dependencies()
copy_knobs = {k: v[k].value() for k in v.knobs()
if k not in filter_knobs}
copy_knobs = {
k: v[k].value() for k in v.knobs()
if k not in filter_knobs
}
# delete viewer with wrong settings
erased_viewers.append(v["name"].value())
@ -1574,6 +1544,21 @@ class WorkfileSettings(object):
"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 string
Args:
view_profile (dict): view and display 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)
def set_root_colorspace(self, imageio_host):
''' Adds correct colorspace to root
@ -1590,12 +1575,12 @@ class WorkfileSettings(object):
if not config_data:
# no ocio config found and no custom path used
if self._root_node["colorManagement"].value() \
not in color_management:
not in color_management:
self._root_node["colorManagement"].setValue(color_management)
# second set ocio version
if self._root_node["OCIO_config"].value() \
not in native_ocio_config:
not in native_ocio_config:
self._root_node["OCIO_config"].setValue(native_ocio_config)
else:
@ -1623,21 +1608,25 @@ class WorkfileSettings(object):
if correct_settings:
self._set_ocio_config_path_to_workfile(config_data)
workfile_settings_output = {}
# get monitor lut from settings respecting Nuke version differences
monitor_lut_data = self._get_monitor_settings(
workfile_settings["monitor_out_lut"],
workfile_settings["monitor_lut"]
)
monitor_lut_data.update({
"workingSpaceLUT": workfile_settings["working_space"],
"int8Lut": workfile_settings["int_8_lut"],
"int16Lut": workfile_settings["int_16_lut"],
"logLut": workfile_settings["log_lut"],
"floatLut": workfile_settings["float_lut"]
})
workfile_settings_output.update(monitor_lut_data)
workfile_settings_output.update(
{
"workingSpaceLUT": workfile_settings["working_space"],
"int8Lut": workfile_settings["int_8_lut"],
"int16Lut": workfile_settings["int_16_lut"],
"logLut": workfile_settings["log_lut"],
"floatLut": workfile_settings["float_lut"],
}
)
# then set the rest
for knob, value_ in monitor_lut_data.items():
for knob, value_ in workfile_settings_output.items():
# skip unfilled ocio config path
# it will be dict in value
if isinstance(value_, dict):
@ -1646,7 +1635,6 @@ class WorkfileSettings(object):
if not value_:
continue
self._root_node[knob].setValue(str(value_))
log.debug("nuke.root()['{}'] changed to: {}".format(knob, value_))
def _get_monitor_settings(self, viewer_lut, monitor_lut):
""" Get monitor settings from viewer and monitor lut
@ -1889,8 +1877,6 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies.
elif node_data:
nuke_imageio_writes = get_write_node_template_attr(node)
log.debug("nuke_imageio_writes: `{}`".format(nuke_imageio_writes))
if not nuke_imageio_writes:
return
@ -1938,7 +1924,6 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies.
"to": future
}
log.debug(changes)
if changes:
msg = "Read nodes are not set to correct colorspace:\n\n"
for nname, knobs in changes.items():
@ -1972,7 +1957,7 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies.
log.info("Setting colorspace to viewers...")
try:
self.set_viewers_colorspace(nuke_colorspace["viewer"])
self.set_viewers_colorspace(nuke_colorspace)
except AttributeError as _error:
msg = "Set Colorspace to viewer error: {}".format(_error)
nuke.message(msg)
@ -2653,8 +2638,6 @@ class NukeDirmap(HostDirmap):
def dirmap_routine(self, source_path, destination_path):
source_path = source_path.lower().replace(os.sep, '/')
destination_path = destination_path.lower().replace(os.sep, '/')
log.debug("Map: {} with: {}->{}".format(self.file_name,
source_path, destination_path))
if platform.system().lower() == "windows":
self.file_name = self.file_name.lower().replace(
source_path, destination_path)

View file

@ -37,8 +37,6 @@ from .lib import (
INSTANCE_DATA_KNOB,
get_main_window,
WorkfileSettings,
# TODO: remove this once workfile builder will be removed
process_workfile_builder,
start_workfile_template_builder,
launch_workfiles_app,
check_inventory_versions,
@ -67,6 +65,7 @@ from .workio import (
current_file
)
from .constants import ASSIST
from . import push_to_project
log = Logger.get_logger(__name__)
@ -159,9 +158,6 @@ def add_nuke_callbacks():
# template builder callbacks
nuke.addOnCreate(start_workfile_template_builder, nodeClass="Root")
# TODO: remove this callback once workfile builder will be removed
nuke.addOnCreate(process_workfile_builder, nodeClass="Root")
# fix ffmpeg settings on script
nuke.addOnScriptLoad(on_script_load)
@ -332,6 +328,11 @@ def _install_menu():
lambda: update_placeholder()
)
menu.addCommand(
"Push to Project",
lambda: push_to_project.main()
)
menu.addSeparator()
menu.addCommand(
"Experimental tools...",

View file

@ -12,6 +12,7 @@ from ayon_core.lib import (
BoolDef,
EnumDef
)
from ayon_core.lib import StringTemplate
from ayon_core.pipeline import (
LoaderPlugin,
CreatorError,
@ -38,7 +39,6 @@ from .lib import (
set_node_data,
get_node_data,
get_view_process_node,
get_viewer_config_from_string,
get_filenames_without_hash,
link_knobs
)
@ -638,12 +638,15 @@ class ExporterReview(object):
from . import lib as opnlib
nuke_imageio = opnlib.get_nuke_imageio_settings()
# TODO: this is only securing backward compatibility lets remove
# this once all projects's anatomy are updated to newer config
if "baking" in nuke_imageio.keys():
return nuke_imageio["baking"]["viewerProcess"]
if nuke_imageio["baking_target"]["enabled"]:
return nuke_imageio["baking_target"]
else:
return nuke_imageio["viewer"]["viewerProcess"]
# viewer is having display and view keys only and it is
# display_view type
return {
"type": "display_view",
"display_view": nuke_imageio["viewer"],
}
class ExporterReviewLut(ExporterReview):
@ -790,6 +793,7 @@ class ExporterReviewMov(ExporterReview):
self.viewer_lut_raw = klass.viewer_lut_raw
self.write_colorspace = instance.data["colorspace"]
self.color_channels = instance.data["color_channels"]
self.formatting_data = instance.data["anatomyData"]
self.name = name or "baked"
self.ext = ext or "mov"
@ -837,7 +841,7 @@ class ExporterReviewMov(ExporterReview):
with maintained_selection():
self.log.info("Saving nodes as file... ")
# create nk path
path = os.path.splitext(self.path)[0] + ".nk"
path = f"{os.path.splitext(self.path)[0]}.nk"
# save file to the path
if not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path))
@ -861,21 +865,20 @@ class ExporterReviewMov(ExporterReview):
bake_viewer_process = kwargs["bake_viewer_process"]
bake_viewer_input_process_node = kwargs[
"bake_viewer_input_process"]
viewer_process_override = kwargs[
"viewer_process_override"]
baking_view_profile = (
viewer_process_override or self.get_imageio_baking_profile())
baking_colorspace = self.get_imageio_baking_profile()
colorspace_override = kwargs["colorspace_override"]
if colorspace_override["enabled"]:
baking_colorspace = colorspace_override
fps = self.instance.context.data["fps"]
self.log.debug(">> baking_view_profile `{}`".format(
baking_view_profile))
self.log.debug(f">> baking_view_profile `{baking_colorspace}`")
add_custom_tags = kwargs.get("add_custom_tags", [])
self.log.info(
"__ add_custom_tags: `{0}`".format(add_custom_tags))
self.log.info(f"__ add_custom_tags: `{add_custom_tags}`")
product_name = self.instance.data["productName"]
self._temp_nodes[product_name] = []
@ -932,32 +935,64 @@ class ExporterReviewMov(ExporterReview):
if not self.viewer_lut_raw:
# OCIODisplay
dag_node = nuke.createNode("OCIODisplay")
if baking_colorspace["type"] == "display_view":
display_view = baking_colorspace["display_view"]
# assign display
display, viewer = get_viewer_config_from_string(
str(baking_view_profile)
)
if display:
dag_node["display"].setValue(display)
message = "OCIODisplay... '{}'"
node = nuke.createNode("OCIODisplay")
# assign viewer
dag_node["view"].setValue(viewer)
# assign display and view
display = display_view["display"]
view = display_view["view"]
if config_data:
# convert display and view to colorspace
colorspace = get_display_view_colorspace_name(
config_path=config_data["path"],
display=display,
view=viewer
# 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:
# convert display and view to colorspace
colorspace = get_display_view_colorspace_name(
config_path=config_data["path"],
display=display, view=view
)
# OCIOColorSpace
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)
node = nuke.createNode("OCIOColorSpace")
message = "OCIOColorSpace... '{}'"
# no need to set input colorspace since it is driven by
# working colorspace
node["out_colorspace"].setValue(baking_colorspace)
colorspace = baking_colorspace
else:
raise ValueError(
"Invalid baking color space type: "
f"{baking_colorspace['type']}"
)
self._connect_to_above_nodes(
dag_node, product_name, "OCIODisplay... `{}`"
node, product_name, message
)
# Write node
write_node = nuke.createNode("Write")
self.log.debug("Path: {}".format(self.path))
self.log.debug(f"Path: {self.path}")
write_node["file"].setValue(str(self.path))
write_node["file_type"].setValue(str(self.ext))
write_node["channels"].setValue(str(self.color_channels))
@ -981,12 +1016,11 @@ class ExporterReviewMov(ExporterReview):
self.log.info("`mov64_write_timecode` knob was not found")
write_node["raw"].setValue(1)
# connect
write_node.setInput(0, self.previous_node)
self._temp_nodes[product_name].append(write_node)
self.log.debug("Write... `{}`".format(
self._temp_nodes[product_name])
)
self.log.debug(f"Write... `{self._temp_nodes[product_name]}`")
# ---------- end nodes creation
# ---------- render or save to nk
@ -1014,7 +1048,7 @@ class ExporterReviewMov(ExporterReview):
colorspace=colorspace,
)
self.log.debug("Representation... `{}`".format(self.data))
self.log.debug(f"Representation... `{self.data}`")
self.clean_nodes(product_name)
nuke.scriptSave()

View file

@ -0,0 +1,118 @@
from collections import defaultdict
import shutil
import os
from ayon_api import get_project, get_folder_by_id, get_task_by_id
from ayon_core.settings import get_project_settings
from ayon_core.pipeline import Anatomy, registered_host
from ayon_core.pipeline.template_data import get_template_data
from ayon_core.pipeline.workfile import get_workdir_with_workdir_data
from ayon_core.tools import context_dialog
from .utils import bake_gizmos_recursively
from .lib import MENU_LABEL
import nuke
def bake_container(container):
"""Bake containers to read nodes."""
node = container["node"]
# Fetch knobs to remove in order.
knobs_to_remove = []
remove = False
for count in range(0, node.numKnobs()):
knob = node.knob(count)
# All knobs from "AYON" tab knob onwards.
if knob.name() == MENU_LABEL:
remove = True
if remove:
knobs_to_remove.append(knob)
# Dont remove knobs from "containerId" onwards.
if knob.name() == "containerId":
remove = False
# Knobs needs to be remove in reverse order, because child knobs needs to
# be remove first.
for knob in reversed(knobs_to_remove):
node.removeKnob(knob)
node["tile_color"].setValue(0)
def main():
context = context_dialog.ask_for_context()
if context is None:
return
# Get workfile path to save to.
project_name = context["project_name"]
project = get_project(project_name)
folder = get_folder_by_id(project_name, context["folder_id"])
task = get_task_by_id(project_name, context["task_id"])
host = registered_host()
project_settings = get_project_settings(project_name)
anatomy = Anatomy(project_name)
workdir_data = get_template_data(
project, folder, task, host.name, project_settings
)
workdir = get_workdir_with_workdir_data(
workdir_data,
project_name,
anatomy,
project_settings=project_settings
)
# Save current workfile.
current_file = host.current_file()
host.save_file(current_file)
for container in host.ls():
bake_container(container)
# Bake gizmos.
bake_gizmos_recursively()
# Copy all read node files to "resources" folder next to workfile and
# change file path.
first_frame = int(nuke.root()["first_frame"].value())
last_frame = int(nuke.root()["last_frame"].value())
files_by_node_name = defaultdict(set)
nodes_by_name = {}
for count in range(first_frame, last_frame + 1):
nuke.frame(count)
for node in nuke.allNodes(filter="Read"):
files_by_node_name[node.name()].add(
nuke.filename(node, nuke.REPLACE)
)
nodes_by_name[node.name()] = node
resources_dir = os.path.join(workdir, "resources")
for name, files in files_by_node_name.items():
dir = os.path.join(resources_dir, name)
if not os.path.exists(dir):
os.makedirs(dir)
for f in files:
shutil.copy(f, os.path.join(dir, os.path.basename(f)))
node = nodes_by_name[name]
path = node["file"].value().replace(os.path.dirname(f), dir)
node["file"].setValue(path.replace("\\", "/"))
# Save current workfile to new context.
pushed_workfile = os.path.join(
workdir, os.path.basename(current_file))
host.save_file(pushed_workfile)
# Open current context workfile.
host.open_file(current_file)
nuke.message(f"Pushed to project: \n{pushed_workfile}")

View file

@ -28,29 +28,6 @@ class ExtractReviewIntermediates(publish.Extractor):
viewer_lut_raw = None
outputs = {}
@classmethod
def apply_settings(cls, project_settings):
"""Apply the settings from the deprecated
ExtractReviewDataMov plugin for backwards compatibility
"""
nuke_publish = project_settings["nuke"]["publish"]
deprecated_setting = nuke_publish["ExtractReviewDataMov"]
current_setting = nuke_publish.get("ExtractReviewIntermediates")
if not deprecated_setting["enabled"] and (
not current_setting["enabled"]
):
cls.enabled = False
if deprecated_setting["enabled"]:
# Use deprecated settings if they are still enabled
cls.viewer_lut_raw = deprecated_setting["viewer_lut_raw"]
cls.outputs = deprecated_setting["outputs"]
elif current_setting is None:
pass
elif current_setting["enabled"]:
cls.viewer_lut_raw = current_setting["viewer_lut_raw"]
cls.outputs = current_setting["outputs"]
def process(self, instance):
# TODO 'families' should not be included for filtering of outputs
families = set(instance.data["families"])

View file

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring AYON addon 'nuke' version."""
__version__ = "0.2.2"
__version__ = "0.2.3"

View file

@ -1,6 +1,6 @@
name = "nuke"
title = "Nuke"
version = "0.2.2"
version = "0.2.3"
client_dir = "ayon_nuke"

View file

@ -1,8 +1,12 @@
from typing import Type
from typing import Type, Any
from ayon_server.addons import BaseServerAddon
from .settings import NukeSettings, DEFAULT_VALUES
from .settings import (
NukeSettings,
DEFAULT_VALUES,
convert_settings_overrides
)
class NukeAddon(BaseServerAddon):
@ -11,3 +15,13 @@ class NukeAddon(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]:
convert_settings_overrides(source_version, overrides)
# Use super conversion
return await super().convert_settings_overrides(
source_version, overrides)

View file

@ -2,9 +2,12 @@ from .main import (
NukeSettings,
DEFAULT_VALUES,
)
from .conversion import convert_settings_overrides
__all__ = (
"NukeSettings",
"DEFAULT_VALUES",
"convert_settings_overrides",
)

View file

@ -133,3 +133,63 @@ class KnobModel(BaseSettingsModel):
"",
title="Expression"
)
colorspace_types_enum = [
{"value": "colorspace", "label": "Use Colorspace"},
{"value": "display_view", "label": "Use Display & View"},
]
class DisplayAndViewProfileModel(BaseSettingsModel):
_layout = "expanded"
display: str = SettingsField(
"",
title="Display",
description="What display to use",
)
view: str = SettingsField(
"",
title="View",
description=(
"What view to use. Anatomy context tokens can "
"be used to dynamically set the value."
),
)
class ColorspaceConfigurationModel(BaseSettingsModel):
_isGroup: bool = True
enabled: bool = SettingsField(
False,
title="Enabled",
description=(
"Enable baking target (colorspace or display/view)."
),
)
type: str = SettingsField(
"colorspace",
title="Target baking type",
description="Switch between different knob types",
enum_resolver=lambda: colorspace_types_enum,
conditionalEnum=True,
)
colorspace: str = SettingsField(
"",
title="Colorspace",
description=(
"What colorspace name to use. Anatomy context tokens can "
"be used to dynamically set the value."
),
)
display_view: DisplayAndViewProfileModel = SettingsField(
title="Display & View",
description="What display & view to use",
default_factory=DisplayAndViewProfileModel,
)

View file

@ -0,0 +1,143 @@
import re
from typing import Any
def _get_viewer_config_from_string(input_string):
"""Convert string to display and viewer string
Args:
input_string (str): string with viewer
Raises:
IndexError: if more then one slash in input string
IndexError: if missing closing bracket
Returns:
tuple[str]: display, viewer
"""
display = None
viewer = input_string
# check if () or / or \ in name
if "/" in viewer:
split = viewer.split("/")
# rise if more then one column
if len(split) > 2:
raise IndexError(
"Viewer Input string is not correct. "
f"More then two `/` slashes! {input_string}"
)
viewer = split[1]
display = split[0]
elif "(" in viewer:
pattern = r"([\w\d\s\.\-]+).*[(](.*)[)]"
result_ = re.findall(pattern, viewer)
try:
result_ = result_.pop()
display = str(result_[1]).rstrip()
viewer = str(result_[0]).rstrip()
except IndexError as e:
raise IndexError(
"Viewer Input string is not correct. "
f"Missing bracket! {input_string}"
) from e
return (display, viewer)
def _convert_imageio_baking_0_2_3(overrides):
if "baking" not in overrides:
return
baking_view_process = overrides["baking"].get("viewerProcess")
if baking_view_process is None:
return
display, view = _get_viewer_config_from_string(baking_view_process)
overrides["baking_target"] = {
"enabled": True,
"type": "display_view",
"display_view": {
"display": display,
"view": view,
},
}
def _convert_viewers_0_2_3(overrides):
if "viewer" not in overrides:
return
viewer = overrides["viewer"]
if "viewerProcess" in viewer:
viewer_process = viewer["viewerProcess"]
display, view = _get_viewer_config_from_string(viewer_process)
viewer.update({
"display": display,
"view": view,
})
if "output_transform" in viewer:
output_transform = viewer["output_transform"]
display, view = _get_viewer_config_from_string(output_transform)
overrides["monitor"] = {
"display": display,
"view": view,
}
def _convert_imageio_configs_0_2_3(overrides):
"""Image IO settings had changed.
0.2.2. is the latest version using the old way.
"""
if "imageio" not in overrides:
return
imageio_overrides = overrides["imageio"]
_convert_imageio_baking_0_2_3(imageio_overrides)
_convert_viewers_0_2_3(imageio_overrides)
def _convert_extract_intermediate_files_0_2_3(publish_overrides):
"""Extract intermediate files settings had changed.
0.2.2. is the latest version using the old way.
"""
# override can be either `display/view` or `view (display)`
if "ExtractReviewIntermediates" in publish_overrides:
extract_review_intermediates = publish_overrides[
"ExtractReviewIntermediates"]
for output in extract_review_intermediates.get("outputs", []):
if viewer_process_override := output.get("viewer_process_override"):
display, view = _get_viewer_config_from_string(
viewer_process_override)
output["colorspace_override"] = {
"enabled": True,
"type": "display_view",
"display_view": {
"display": display,
"view": view,
},
}
def _convert_publish_plugins(overrides):
if "publish" not in overrides:
return
_convert_extract_intermediate_files_0_2_3(overrides["publish"])
def convert_settings_overrides(
source_version: str,
overrides: dict[str, Any],
) -> dict[str, Any]:
_convert_imageio_configs_0_2_3(overrides)
_convert_publish_plugins(overrides)
return overrides

View file

@ -6,7 +6,10 @@ from ayon_server.settings import (
ensure_unique_names,
)
from .common import KnobModel
from .common import (
KnobModel,
ColorspaceConfigurationModel,
)
class NodesModel(BaseSettingsModel):
@ -52,6 +55,8 @@ class OverrideNodesModel(NodesModel):
class NodesSetting(BaseSettingsModel):
_isGroup: bool = True
required_nodes: list[RequiredNodesModel] = SettingsField(
title="Plugin required",
default_factory=list
@ -83,6 +88,8 @@ def ocio_configs_switcher_enum():
class WorkfileColorspaceSettings(BaseSettingsModel):
"""Nuke workfile colorspace preset. """
_isGroup: bool = True
color_management: Literal["Nuke", "OCIO"] = SettingsField(
title="Color Management Workflow"
)
@ -125,6 +132,8 @@ class ReadColorspaceRulesItems(BaseSettingsModel):
class RegexInputsModel(BaseSettingsModel):
_isGroup: bool = True
inputs: list[ReadColorspaceRulesItems] = SettingsField(
default_factory=list,
title="Inputs"
@ -132,15 +141,44 @@ class RegexInputsModel(BaseSettingsModel):
class ViewProcessModel(BaseSettingsModel):
viewerProcess: str = SettingsField(
title="Viewer Process Name"
_isGroup: bool = True
display: str = SettingsField(
"",
title="Display",
description="What display to use",
)
output_transform: str = SettingsField(
title="Output Transform"
view: str = SettingsField(
"",
title="View",
description=(
"What view to use. Anatomy context tokens can "
"be used to dynamically set the value."
),
)
class MonitorProcessModel(BaseSettingsModel):
_isGroup: bool = True
display: str = SettingsField(
"",
title="Display",
description="What display to use",
)
view: str = SettingsField(
"",
title="View",
description=(
"What view to use. Anatomy context tokens can "
"be used to dynamically set the value."
),
)
class ImageIOConfigModel(BaseSettingsModel):
_isGroup: bool = True
override_global_config: bool = SettingsField(
False,
title="Override global OCIO config"
@ -159,6 +197,8 @@ class ImageIOFileRuleModel(BaseSettingsModel):
class ImageIOFileRulesModel(BaseSettingsModel):
_isGroup: bool = True
activate_host_rules: bool = SettingsField(False)
rules: list[ImageIOFileRuleModel] = SettingsField(
default_factory=list,
@ -173,14 +213,7 @@ class ImageIOFileRulesModel(BaseSettingsModel):
class ImageIOSettings(BaseSettingsModel):
"""Nuke color management project settings. """
_isGroup: bool = True
"""# TODO: enhance settings with host api:
to restructure settings for simplification.
now: nuke/imageio/viewer/viewerProcess
future: nuke/imageio/viewer
"""
activate_host_color_management: bool = SettingsField(
True, title="Enable Color Management")
ocio_config: ImageIOConfigModel = SettingsField(
@ -197,18 +230,13 @@ class ImageIOSettings(BaseSettingsModel):
description="""Viewer profile is used during
Creation of new viewer node at knob viewerProcess"""
)
"""# TODO: enhance settings with host api:
to restructure settings for simplification.
now: nuke/imageio/baking/viewerProcess
future: nuke/imageio/baking
"""
baking: ViewProcessModel = SettingsField(
default_factory=ViewProcessModel,
title="Baking",
description="""Baking profile is used during
publishing baked colorspace data at knob viewerProcess"""
monitor: MonitorProcessModel = SettingsField(
default_factory=MonitorProcessModel,
title="Monitor OUT"
)
baking_target: ColorspaceConfigurationModel = SettingsField(
default_factory=ColorspaceConfigurationModel,
title="Baking Target Colorspace"
)
workfile: WorkfileColorspaceSettings = SettingsField(
@ -231,13 +259,12 @@ class ImageIOSettings(BaseSettingsModel):
DEFAULT_IMAGEIO_SETTINGS = {
"viewer": {
"viewerProcess": "ACES/sRGB",
"output_transform": "ACES/sRGB"
},
"baking": {
"viewerProcess": "ACES/Rec.709",
"output_transform": "ACES/Rec.709"
"viewer": {"display": "ACES", "view": "sRGB"},
"monitor": {"display": "ACES", "view": "Rec.709"},
"baking_target": {
"enabled": True,
"type": "colorspace",
"colorspace": "Output - Rec.709",
},
"workfile": {
"color_management": "OCIO",
@ -248,170 +275,67 @@ DEFAULT_IMAGEIO_SETTINGS = {
"int_8_lut": "role_matte_paint",
"int_16_lut": "role_texture_paint",
"log_lut": "role_compositing_log",
"float_lut": "role_scene_linear"
"float_lut": "role_scene_linear",
},
"nodes": {
"required_nodes": [
{
"plugins": [
"CreateWriteRender"
],
"plugins": ["CreateWriteRender"],
"nuke_node_class": "Write",
"knobs": [
{
"type": "text",
"name": "file_type",
"text": "exr"
},
{
"type": "text",
"name": "datatype",
"text": "16 bit half"
},
{
"type": "text",
"name": "compression",
"text": "Zip (1 scanline)"
},
{
"type": "boolean",
"name": "autocrop",
"boolean": True
},
{"type": "text", "name": "file_type", "text": "exr"},
{"type": "text", "name": "datatype", "text": "16 bit half"},
{"type": "text", "name": "compression", "text": "Zip (1 scanline)"},
{"type": "boolean", "name": "autocrop", "boolean": True},
{
"type": "color_gui",
"name": "tile_color",
"color_gui": [
186,
35,
35
]
"color_gui": [186, 35, 35],
},
{
"type": "text",
"name": "channels",
"text": "rgb"
},
{
"type": "text",
"name": "colorspace",
"text": "scene_linear"
},
{
"type": "boolean",
"name": "create_directories",
"boolean": True
}
]
{"type": "text", "name": "channels", "text": "rgb"},
{"type": "text", "name": "colorspace", "text": "scene_linear"},
{"type": "boolean", "name": "create_directories", "boolean": True},
],
},
{
"plugins": [
"CreateWritePrerender"
],
"plugins": ["CreateWritePrerender"],
"nuke_node_class": "Write",
"knobs": [
{
"type": "text",
"name": "file_type",
"text": "exr"
},
{
"type": "text",
"name": "datatype",
"text": "16 bit half"
},
{
"type": "text",
"name": "compression",
"text": "Zip (1 scanline)"
},
{
"type": "boolean",
"name": "autocrop",
"boolean": True
},
{"type": "text", "name": "file_type", "text": "exr"},
{"type": "text", "name": "datatype", "text": "16 bit half"},
{"type": "text", "name": "compression", "text": "Zip (1 scanline)"},
{"type": "boolean", "name": "autocrop", "boolean": True},
{
"type": "color_gui",
"name": "tile_color",
"color_gui": [
171,
171,
10
]
"color_gui": [171, 171, 10],
},
{
"type": "text",
"name": "channels",
"text": "rgb"
},
{
"type": "text",
"name": "colorspace",
"text": "scene_linear"
},
{
"type": "boolean",
"name": "create_directories",
"boolean": True
}
]
{"type": "text", "name": "channels", "text": "rgb"},
{"type": "text", "name": "colorspace", "text": "scene_linear"},
{"type": "boolean", "name": "create_directories", "boolean": True},
],
},
{
"plugins": [
"CreateWriteImage"
],
"plugins": ["CreateWriteImage"],
"nuke_node_class": "Write",
"knobs": [
{
"type": "text",
"name": "file_type",
"text": "tiff"
},
{
"type": "text",
"name": "datatype",
"text": "16 bit"
},
{
"type": "text",
"name": "compression",
"text": "Deflate"
},
{"type": "text", "name": "file_type", "text": "tiff"},
{"type": "text", "name": "datatype", "text": "16 bit"},
{"type": "text", "name": "compression", "text": "Deflate"},
{
"type": "color_gui",
"name": "tile_color",
"color_gui": [
56,
162,
7
]
"color_gui": [56, 162, 7],
},
{
"type": "text",
"name": "channels",
"text": "rgb"
},
{
"type": "text",
"name": "colorspace",
"text": "texture_paint"
},
{
"type": "boolean",
"name": "create_directories",
"boolean": True
}
]
}
{"type": "text", "name": "channels", "text": "rgb"},
{"type": "text", "name": "colorspace", "text": "texture_paint"},
{"type": "boolean", "name": "create_directories", "boolean": True},
],
},
],
"override_nodes": []
"override_nodes": [],
},
"regex_inputs": {
"inputs": [
{
"regex": "(beauty).*(?=.exr)",
"colorspace": "linear"
}
]
}
"inputs": [{"regex": "(beauty).*(?=.exr)", "colorspace": "linear"}]
},
}

View file

@ -5,7 +5,11 @@ from ayon_server.settings import (
ensure_unique_names,
task_types_enum
)
from .common import KnobModel, validate_json_dict
from .common import (
KnobModel,
ColorspaceConfigurationModel,
validate_json_dict,
)
def nuke_render_publish_types_enum():
@ -130,19 +134,22 @@ class IntermediateOutputModel(BaseSettingsModel):
title="Filter", default_factory=BakingStreamFilterModel)
read_raw: bool = SettingsField(
False,
title="Read raw switch"
)
viewer_process_override: str = SettingsField(
"",
title="Viewer process override"
title="Input read node RAW switch"
)
bake_viewer_process: bool = SettingsField(
True,
title="Bake viewer process"
title="Bake viewer process",
section="Baking target",
)
colorspace_override: ColorspaceConfigurationModel = SettingsField(
title="Target baking colorspace override",
description="Override Baking target with colorspace or display/view",
default_factory=ColorspaceConfigurationModel
)
bake_viewer_input_process: bool = SettingsField(
True,
title="Bake viewer input process node (LUT)"
title="Bake viewer input process node (LUT)",
section="Baking additional",
)
reformat_nodes_config: ReformatNodesConfigModel = SettingsField(
default_factory=ReformatNodesConfigModel,
@ -155,18 +162,6 @@ class IntermediateOutputModel(BaseSettingsModel):
title="Custom tags", default_factory=list)
class ExtractReviewDataMovModel(BaseSettingsModel):
"""[deprecated] use Extract Review Data Baking
Streams instead.
"""
enabled: bool = SettingsField(title="Enabled")
viewer_lut_raw: bool = SettingsField(title="Viewer lut raw")
outputs: list[IntermediateOutputModel] = SettingsField(
default_factory=list,
title="Baking streams"
)
class ExtractReviewIntermediatesModel(BaseSettingsModel):
enabled: bool = SettingsField(title="Enabled")
viewer_lut_raw: bool = SettingsField(title="Viewer lut raw")
@ -259,10 +254,6 @@ class PublishPluginsModel(BaseSettingsModel):
title="Extract Review Data Lut",
default_factory=ExtractReviewDataLutModel
)
ExtractReviewDataMov: ExtractReviewDataMovModel = SettingsField(
title="Extract Review Data Mov",
default_factory=ExtractReviewDataMovModel
)
ExtractReviewIntermediates: ExtractReviewIntermediatesModel = (
SettingsField(
title="Extract Review Intermediates",
@ -332,62 +323,6 @@ DEFAULT_PUBLISH_PLUGIN_SETTINGS = {
"ExtractReviewDataLut": {
"enabled": False
},
"ExtractReviewDataMov": {
"enabled": False,
"viewer_lut_raw": False,
"outputs": [
{
"name": "baking",
"publish": False,
"filter": {
"task_types": [],
"product_types": [],
"product_names": []
},
"read_raw": False,
"viewer_process_override": "",
"bake_viewer_process": True,
"bake_viewer_input_process": True,
"reformat_nodes_config": {
"enabled": False,
"reposition_nodes": [
{
"node_class": "Reformat",
"knobs": [
{
"type": "text",
"name": "type",
"text": "to format"
},
{
"type": "text",
"name": "format",
"text": "HD_1080"
},
{
"type": "text",
"name": "filter",
"text": "Lanczos6"
},
{
"type": "boolean",
"name": "black_outside",
"boolean": True
},
{
"type": "boolean",
"name": "pbb",
"boolean": False
}
]
}
]
},
"extension": "mov",
"add_custom_tags": []
}
]
},
"ExtractReviewIntermediates": {
"enabled": True,
"viewer_lut_raw": False,
@ -401,7 +336,15 @@ DEFAULT_PUBLISH_PLUGIN_SETTINGS = {
"product_names": []
},
"read_raw": False,
"viewer_process_override": "",
"colorspace_override": {
"enabled": False,
"type": "colorspace",
"colorspace": "",
"display_view": {
"display": "",
"view": ""
}
},
"bake_viewer_process": True,
"bake_viewer_input_process": True,
"reformat_nodes_config": {