Merge branch 'develop' into feature/PYPE-1120-nk-geo-workflow

This commit is contained in:
karimmozlia 2021-10-22 14:10:11 +02:00
commit 48a11f5873
86 changed files with 1799 additions and 1357 deletions

View file

@ -7,7 +7,6 @@ from collections import OrderedDict
from avalon import api, io, lib
from openpype.tools import workfiles
import avalon.nuke
from avalon.nuke import lib as anlib
from avalon.nuke import (
@ -24,7 +23,7 @@ from openpype.api import (
get_current_project_settings,
ApplicationManager
)
from openpype.tools.utils import host_tools
import nuke
from .utils import set_context_favorites
@ -1032,27 +1031,6 @@ class WorkfileSettings(object):
log.error(msg)
nuke.message(msg)
bbox = self._asset_entity.get('data', {}).get('crop')
if bbox:
try:
x, y, r, t = bbox.split(".")
data.update(
{
"x": int(x),
"y": int(y),
"r": int(r),
"t": int(t),
}
)
except Exception as e:
bbox = None
msg = ("{}:{} \nFormat:Crop need to be set with dots, "
"example: 0.0.1920.1080, "
"/nSetting to default").format(__name__, e)
log.error(msg)
nuke.message(msg)
existing_format = None
for format in nuke.formats():
if data["name"] == format.name():
@ -1064,12 +1042,6 @@ class WorkfileSettings(object):
existing_format.setWidth(data["width"])
existing_format.setHeight(data["height"])
existing_format.setPixelAspect(data["pixel_aspect"])
if bbox:
existing_format.setX(data["x"])
existing_format.setY(data["y"])
existing_format.setR(data["r"])
existing_format.setT(data["t"])
else:
format_string = self.make_format_string(**data)
log.info("Creating new format: {}".format(format_string))
@ -1689,7 +1661,7 @@ def launch_workfiles_app():
if not opnl.workfiles_launched:
opnl.workfiles_launched = True
workfiles.show(os.environ["AVALON_WORKDIR"])
host_tools.show_workfiles()
def process_workfile_builder():

View file

@ -4,7 +4,7 @@ from avalon.api import Session
from .lib import WorkfileSettings
from openpype.api import Logger, BuildWorkfile, get_current_project_settings
from openpype.tools import workfiles
from openpype.tools.utils import host_tools
log = Logger().get_logger(__name__)
@ -25,7 +25,7 @@ def install():
menu.removeItem(rm_item[1].name())
menu.addCommand(
name,
workfiles.show,
host_tools.show_workfiles,
index=2
)
menu.addSeparator(index=3)

View file

@ -1,4 +1,10 @@
import random
import string
import avalon.nuke
from avalon.nuke import lib as anlib
from avalon import api
from openpype.api import (
get_current_project_settings,
PypeCreatorMixin
@ -23,3 +29,68 @@ class PypeCreator(PypeCreatorMixin, avalon.nuke.pipeline.Creator):
self.log.error(msg + '\n\nPlease use other subset name!')
raise NameError("`{0}: {1}".format(__name__, msg))
return
def get_review_presets_config():
settings = get_current_project_settings()
review_profiles = (
settings["global"]
["publish"]
["ExtractReview"]
["profiles"]
)
outputs = {}
for profile in review_profiles:
outputs.update(profile.get("outputs", {}))
return [str(name) for name, _prop in outputs.items()]
class NukeLoader(api.Loader):
container_id_knob = "containerId"
container_id = ''.join(random.choice(
string.ascii_uppercase + string.digits) for _ in range(10))
def get_container_id(self, node):
id_knob = node.knobs().get(self.container_id_knob)
return id_knob.value() if id_knob else None
def get_members(self, source):
"""Return nodes that has same 'containerId' as `source`"""
source_id = self.get_container_id(source)
return [node for node in nuke.allNodes(recurseGroups=True)
if self.get_container_id(node) == source_id
and node is not source] if source_id else []
def set_as_member(self, node):
source_id = self.get_container_id(node)
if source_id:
node[self.container_id_knob].setValue(self.container_id)
else:
HIDEN_FLAG = 0x00040000
_knob = anlib.Knobby(
"String_Knob",
self.container_id,
flags=[nuke.READ_ONLY, HIDEN_FLAG])
knob = _knob.create(self.container_id_knob)
node.addKnob(knob)
def clear_members(self, parent_node):
members = self.get_members(parent_node)
dependent_nodes = None
for node in members:
_depndc = [n for n in node.dependent() if n not in members]
if not _depndc:
continue
dependent_nodes = _depndc
break
for member in members:
self.log.info("removing node: `{}".format(member.name()))
nuke.delete(member)
return dependent_nodes

View file

@ -99,7 +99,7 @@ class CreateWriteRender(plugin.PypeCreator):
"fpath_template": ("{work}/renders/nuke/{subset}"
"/{subset}.{frame}.{ext}")})
# add crop node to cut off all outside of format bounding box
# add reformat node to cut off all outside of format bounding box
# get width and height
try:
width, height = (selected_node.width(), selected_node.height())
@ -109,15 +109,11 @@ class CreateWriteRender(plugin.PypeCreator):
_prenodes = [
{
"name": "Crop01",
"class": "Crop",
"name": "Reformat01",
"class": "Reformat",
"knobs": [
("box", [
0.0,
0.0,
width,
height
])
("resize", 0),
("black_outside", 1),
],
"dependent": None
}

View file

@ -0,0 +1,37 @@
from avalon import api, style
from avalon.nuke import lib as anlib
from openpype.api import (
Logger)
class RepairOldLoaders(api.InventoryAction):
label = "Repair Old Loaders"
icon = "gears"
color = style.colors.alert
log = Logger().get_logger(__name__)
def process(self, containers):
import nuke
new_loader = "LoadClip"
for cdata in containers:
orig_loader = cdata["loader"]
orig_name = cdata["objectName"]
if orig_loader not in ["LoadSequence", "LoadMov"]:
self.log.warning(
"This repair action is only working on "
"`LoadSequence` and `LoadMov` Loaders")
continue
new_name = orig_name.replace(orig_loader, new_loader)
node = nuke.toNode(cdata["objectName"])
cdata.update({
"loader": new_loader,
"objectName": new_name
})
node["name"].setValue(new_name)
# get data from avalon knob
anlib.set_avalon_knob_data(node, cdata)

View file

@ -8,10 +8,10 @@ class SelectContainers(api.InventoryAction):
color = "#d8d8d8"
def process(self, containers):
import nuke
import avalon.nuke
nodes = [i["_node"] for i in containers]
nodes = [nuke.toNode(i["objectName"]) for i in containers]
with avalon.nuke.viewer_update_and_undo_stop():
# clear previous_selection

View file

@ -1,68 +0,0 @@
# from avalon import api, style
# from avalon.vendor.Qt import QtGui, QtWidgets
#
# import avalon.fusion
#
#
# class FusionSetToolColor(api.InventoryAction):
# """Update the color of the selected tools"""
#
# label = "Set Tool Color"
# icon = "plus"
# color = "#d8d8d8"
# _fallback_color = QtGui.QColor(1.0, 1.0, 1.0)
#
# def process(self, containers):
# """Color all selected tools the selected colors"""
#
# result = []
# comp = avalon.fusion.get_current_comp()
#
# # Get tool color
# first = containers[0]
# tool = first["_node"]
# color = tool.TileColor
#
# if color is not None:
# qcolor = QtGui.QColor().fromRgbF(color["R"], color["G"], color["B"])
# else:
# qcolor = self._fallback_color
#
# # Launch pick color
# picked_color = self.get_color_picker(qcolor)
# if not picked_color:
# return
#
# with avalon.fusion.comp_lock_and_undo_chunk(comp):
# for container in containers:
# # Convert color to RGB 0-1 floats
# rgb_f = picked_color.getRgbF()
# rgb_f_table = {"R": rgb_f[0], "G": rgb_f[1], "B": rgb_f[2]}
#
# # Update tool
# tool = container["_node"]
# tool.TileColor = rgb_f_table
#
# result.append(container)
#
# return result
#
# def get_color_picker(self, color):
# """Launch color picker and return chosen color
#
# Args:
# color(QtGui.QColor): Start color to display
#
# Returns:
# QtGui.QColor
#
# """
#
# color_dialog = QtWidgets.QColorDialog(color)
# color_dialog.setStyleSheet(style.load_stylesheet())
#
# accepted = color_dialog.exec_()
# if not accepted:
# return
#
# return color_dialog.selectedColor()

View file

@ -0,0 +1,371 @@
import nuke
from avalon.vendor import qargparse
from avalon import api, io
from openpype.hosts.nuke.api.lib import (
get_imageio_input_colorspace
)
from avalon.nuke import (
containerise,
update_container,
viewer_update_and_undo_stop,
maintained_selection
)
from openpype.hosts.nuke.api import plugin
class LoadClip(plugin.NukeLoader):
"""Load clip into Nuke
Either it is image sequence or video file.
"""
families = [
"source",
"plate",
"render",
"prerender",
"review"
]
representations = [
"exr",
"dpx",
"mov",
"review",
"mp4"
]
label = "Load Clip"
order = -20
icon = "file-video-o"
color = "white"
script_start = int(nuke.root()["first_frame"].value())
# option gui
defaults = {
"start_at_workfile": True
}
options = [
qargparse.Boolean(
"start_at_workfile",
help="Load at workfile start frame",
default=True
)
]
node_name_template = "{class_name}_{ext}"
@classmethod
def get_representations(cls):
return (
cls.representations
+ cls._representations
+ plugin.get_review_presets_config()
)
def load(self, context, name, namespace, options):
is_sequence = len(context["representation"]["files"]) > 1
file = self.fname.replace("\\", "/")
start_at_workfile = options.get(
"start_at_workfile", self.defaults["start_at_workfile"])
version = context['version']
version_data = version.get("data", {})
repr_id = context["representation"]["_id"]
colorspace = version_data.get("colorspace")
iio_colorspace = get_imageio_input_colorspace(file)
repr_cont = context["representation"]["context"]
self.log.info("version_data: {}\n".format(version_data))
self.log.debug(
"Representation id `{}` ".format(repr_id))
self.handle_start = version_data.get("handleStart", 0)
self.handle_end = version_data.get("handleEnd", 0)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
first -= self.handle_start
last += self.handle_end
if not is_sequence:
duration = last - first + 1
first = 1
last = first + duration
elif "#" not in file:
frame = repr_cont.get("frame")
assert frame, "Representation is not sequence"
padding = len(frame)
file = file.replace(frame, "#" * padding)
# Fallback to asset name when namespace is None
if namespace is None:
namespace = context['asset']['name']
if not file:
self.log.warning(
"Representation id `{}` is failing to load".format(repr_id))
return
name_data = {
"asset": repr_cont["asset"],
"subset": repr_cont["subset"],
"representation": context["representation"]["name"],
"ext": repr_cont["representation"],
"id": context["representation"]["_id"],
"class_name": self.__class__.__name__
}
read_name = self.node_name_template.format(**name_data)
# Create the Loader with the filename path set
read_node = nuke.createNode(
"Read",
"name {}".format(read_name))
# to avoid multiple undo steps for rest of process
# we will switch off undo-ing
with viewer_update_and_undo_stop():
read_node["file"].setValue(file)
# Set colorspace defined in version data
if colorspace:
read_node["colorspace"].setValue(str(colorspace))
elif iio_colorspace is not None:
read_node["colorspace"].setValue(iio_colorspace)
self.set_range_to_node(read_node, first, last, start_at_workfile)
# add additional metadata from the version to imprint Avalon knob
add_keys = ["frameStart", "frameEnd",
"source", "colorspace", "author", "fps", "version",
"handleStart", "handleEnd"]
data_imprint = {}
for k in add_keys:
if k == 'version':
data_imprint.update({k: context["version"]['name']})
else:
data_imprint.update(
{k: context["version"]['data'].get(k, str(None))})
data_imprint.update({"objectName": read_name})
read_node["tile_color"].setValue(int("0x4ecd25ff", 16))
container = containerise(
read_node,
name=name,
namespace=namespace,
context=context,
loader=self.__class__.__name__,
data=data_imprint)
if version_data.get("retime", None):
self.make_retimes(read_node, version_data)
self.set_as_member(read_node)
return container
def switch(self, container, representation):
self.update(container, representation)
def update(self, container, representation):
"""Update the Loader's path
Nuke automatically tries to reset some variables when changing
the loader's path to a new file. These automatic changes are to its
inputs:
"""
is_sequence = len(representation["files"]) > 1
read_node = nuke.toNode(container['objectName'])
file = api.get_representation_path(representation).replace("\\", "/")
start_at_workfile = bool("start at" in read_node['frame_mode'].value())
version = io.find_one({
"type": "version",
"_id": representation["parent"]
})
version_data = version.get("data", {})
repr_id = representation["_id"]
colorspace = version_data.get("colorspace")
iio_colorspace = get_imageio_input_colorspace(file)
repr_cont = representation["context"]
self.handle_start = version_data.get("handleStart", 0)
self.handle_end = version_data.get("handleEnd", 0)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
first -= self.handle_start
last += self.handle_end
if not is_sequence:
duration = last - first + 1
first = 1
last = first + duration
elif "#" not in file:
frame = repr_cont.get("frame")
assert frame, "Representation is not sequence"
padding = len(frame)
file = file.replace(frame, "#" * padding)
if not file:
self.log.warning(
"Representation id `{}` is failing to load".format(repr_id))
return
read_node["file"].setValue(file)
# to avoid multiple undo steps for rest of process
# we will switch off undo-ing
with viewer_update_and_undo_stop():
# Set colorspace defined in version data
if colorspace:
read_node["colorspace"].setValue(str(colorspace))
elif iio_colorspace is not None:
read_node["colorspace"].setValue(iio_colorspace)
self.set_range_to_node(read_node, first, last, start_at_workfile)
updated_dict = {
"representation": str(representation["_id"]),
"frameStart": str(first),
"frameEnd": str(last),
"version": str(version.get("name")),
"colorspace": colorspace,
"source": version_data.get("source"),
"handleStart": str(self.handle_start),
"handleEnd": str(self.handle_end),
"fps": str(version_data.get("fps")),
"author": version_data.get("author"),
"outputDir": version_data.get("outputDir"),
}
# change color of read_node
# get all versions in list
versions = io.find({
"type": "version",
"parent": version["parent"]
}).distinct('name')
max_version = max(versions)
if version.get("name") not in [max_version]:
read_node["tile_color"].setValue(int("0xd84f20ff", 16))
else:
read_node["tile_color"].setValue(int("0x4ecd25ff", 16))
# Update the imprinted representation
update_container(
read_node,
updated_dict
)
self.log.info("udated to version: {}".format(version.get("name")))
if version_data.get("retime", None):
self.make_retimes(read_node, version_data)
else:
self.clear_members(read_node)
self.set_as_member(read_node)
def set_range_to_node(self, read_node, first, last, start_at_workfile):
read_node['origfirst'].setValue(int(first))
read_node['first'].setValue(int(first))
read_node['origlast'].setValue(int(last))
read_node['last'].setValue(int(last))
# set start frame depending on workfile or version
self.loader_shift(read_node, start_at_workfile)
def remove(self, container):
from avalon.nuke import viewer_update_and_undo_stop
read_node = nuke.toNode(container['objectName'])
assert read_node.Class() == "Read", "Must be Read"
with viewer_update_and_undo_stop():
members = self.get_members(read_node)
nuke.delete(read_node)
for member in members:
nuke.delete(member)
def make_retimes(self, parent_node, version_data):
''' Create all retime and timewarping nodes with coppied animation '''
speed = version_data.get('speed', 1)
time_warp_nodes = version_data.get('timewarps', [])
last_node = None
source_id = self.get_container_id(parent_node)
self.log.info("__ source_id: {}".format(source_id))
self.log.info("__ members: {}".format(self.get_members(parent_node)))
dependent_nodes = self.clear_members(parent_node)
with maintained_selection():
parent_node['selected'].setValue(True)
if speed != 1:
rtn = nuke.createNode(
"Retime",
"speed {}".format(speed))
rtn["before"].setValue("continue")
rtn["after"].setValue("continue")
rtn["input.first_lock"].setValue(True)
rtn["input.first"].setValue(
self.script_start
)
self.set_as_member(rtn)
last_node = rtn
if time_warp_nodes != []:
start_anim = self.script_start + (self.handle_start / speed)
for timewarp in time_warp_nodes:
twn = nuke.createNode(
timewarp["Class"],
"name {}".format(timewarp["name"])
)
if isinstance(timewarp["lookup"], list):
# if array for animation
twn["lookup"].setAnimated()
for i, value in enumerate(timewarp["lookup"]):
twn["lookup"].setValueAt(
(start_anim + i) + value,
(start_anim + i))
else:
# if static value `int`
twn["lookup"].setValue(timewarp["lookup"])
self.set_as_member(twn)
last_node = twn
if dependent_nodes:
# connect to original inputs
for i, n in enumerate(dependent_nodes):
last_node.setInput(i, n)
def loader_shift(self, read_node, workfile_start=False):
""" Set start frame of read node to a workfile start
Args:
read_node (nuke.Node): The nuke's read node
workfile_start (bool): set workfile start frame if true
"""
if workfile_start:
read_node['frame_mode'].setValue("start at")
read_node['frame'].setValue(str(self.script_start))

View file

@ -12,7 +12,15 @@ from openpype.hosts.nuke.api.lib import (
class LoadImage(api.Loader):
"""Load still image into Nuke"""
families = ["render", "source", "plate", "review", "image"]
families = [
"render2d",
"source",
"plate",
"render",
"prerender",
"review",
"image"
]
representations = ["exr", "dpx", "jpg", "jpeg", "png", "psd", "tiff"]
label = "Load Image"
@ -33,6 +41,10 @@ class LoadImage(api.Loader):
)
]
@classmethod
def get_representations(cls):
return cls.representations + cls._representations
def load(self, context, name, namespace, options):
from avalon.nuke import (
containerise,

View file

@ -1,347 +0,0 @@
import nuke
from avalon.vendor import qargparse
from avalon import api, io
from openpype.api import get_current_project_settings
from openpype.hosts.nuke.api.lib import (
get_imageio_input_colorspace
)
def add_review_presets_config():
returning = {
"families": list(),
"representations": list()
}
settings = get_current_project_settings()
review_profiles = (
settings["global"]
["publish"]
["ExtractReview"]
["profiles"]
)
outputs = {}
for profile in review_profiles:
outputs.update(profile.get("outputs", {}))
for output, properities in outputs.items():
returning["representations"].append(output)
returning["families"] += properities.get("families", [])
return returning
class LoadMov(api.Loader):
"""Load mov file into Nuke"""
families = ["render", "source", "plate", "review"]
representations = ["mov", "review", "mp4"]
label = "Load mov"
order = -10
icon = "code-fork"
color = "orange"
first_frame = nuke.root()["first_frame"].value()
# options gui
defaults = {
"start_at_workfile": True
}
options = [
qargparse.Boolean(
"start_at_workfile",
help="Load at workfile start frame",
default=True
)
]
node_name_template = "{class_name}_{ext}"
def load(self, context, name, namespace, options):
from avalon.nuke import (
containerise,
viewer_update_and_undo_stop
)
start_at_workfile = options.get(
"start_at_workfile", self.defaults["start_at_workfile"])
version = context['version']
version_data = version.get("data", {})
repr_id = context["representation"]["_id"]
self.handle_start = version_data.get("handleStart", 0)
self.handle_end = version_data.get("handleEnd", 0)
orig_first = version_data.get("frameStart")
orig_last = version_data.get("frameEnd")
diff = orig_first - 1
first = orig_first - diff
last = orig_last - diff
colorspace = version_data.get("colorspace")
repr_cont = context["representation"]["context"]
self.log.debug(
"Representation id `{}` ".format(repr_id))
context["representation"]["_id"]
# create handles offset (only to last, because of mov)
last += self.handle_start + self.handle_end
# Fallback to asset name when namespace is None
if namespace is None:
namespace = context['asset']['name']
file = self.fname
if not file:
self.log.warning(
"Representation id `{}` is failing to load".format(repr_id))
return
file = file.replace("\\", "/")
name_data = {
"asset": repr_cont["asset"],
"subset": repr_cont["subset"],
"representation": context["representation"]["name"],
"ext": repr_cont["representation"],
"id": context["representation"]["_id"],
"class_name": self.__class__.__name__
}
read_name = self.node_name_template.format(**name_data)
read_node = nuke.createNode(
"Read",
"name {}".format(read_name)
)
# to avoid multiple undo steps for rest of process
# we will switch off undo-ing
with viewer_update_and_undo_stop():
read_node["file"].setValue(file)
read_node["origfirst"].setValue(first)
read_node["first"].setValue(first)
read_node["origlast"].setValue(last)
read_node["last"].setValue(last)
read_node['frame_mode'].setValue("start at")
if start_at_workfile:
# start at workfile start
read_node['frame'].setValue(str(self.first_frame))
else:
# start at version frame start
read_node['frame'].setValue(
str(orig_first - self.handle_start))
if colorspace:
read_node["colorspace"].setValue(str(colorspace))
preset_clrsp = get_imageio_input_colorspace(file)
if preset_clrsp is not None:
read_node["colorspace"].setValue(preset_clrsp)
# add additional metadata from the version to imprint Avalon knob
add_keys = [
"frameStart", "frameEnd", "handles", "source", "author",
"fps", "version", "handleStart", "handleEnd"
]
data_imprint = {}
for key in add_keys:
if key == 'version':
data_imprint.update({
key: context["version"]['name']
})
else:
data_imprint.update({
key: context["version"]['data'].get(key, str(None))
})
data_imprint.update({"objectName": read_name})
read_node["tile_color"].setValue(int("0x4ecd25ff", 16))
if version_data.get("retime", None):
speed = version_data.get("speed", 1)
time_warp_nodes = version_data.get("timewarps", [])
self.make_retimes(speed, time_warp_nodes)
return containerise(
read_node,
name=name,
namespace=namespace,
context=context,
loader=self.__class__.__name__,
data=data_imprint
)
def switch(self, container, representation):
self.update(container, representation)
def update(self, container, representation):
"""Update the Loader's path
Nuke automatically tries to reset some variables when changing
the loader's path to a new file. These automatic changes are to its
inputs:
"""
from avalon.nuke import (
update_container
)
read_node = nuke.toNode(container['objectName'])
assert read_node.Class() == "Read", "Must be Read"
file = self.fname
if not file:
repr_id = representation["_id"]
self.log.warning(
"Representation id `{}` is failing to load".format(repr_id))
return
file = file.replace("\\", "/")
# Get start frame from version data
version = io.find_one({
"type": "version",
"_id": representation["parent"]
})
# get all versions in list
versions = io.find({
"type": "version",
"parent": version["parent"]
}).distinct('name')
max_version = max(versions)
version_data = version.get("data", {})
orig_first = version_data.get("frameStart")
orig_last = version_data.get("frameEnd")
diff = orig_first - 1
# set first to 1
first = orig_first - diff
last = orig_last - diff
self.handle_start = version_data.get("handleStart", 0)
self.handle_end = version_data.get("handleEnd", 0)
colorspace = version_data.get("colorspace")
if first is None:
self.log.warning((
"Missing start frame for updated version"
"assuming starts at frame 0 for: "
"{} ({})").format(
read_node['name'].value(), representation))
first = 0
# create handles offset (only to last, because of mov)
last += self.handle_start + self.handle_end
read_node["file"].setValue(file)
# Set the global in to the start frame of the sequence
read_node["origfirst"].setValue(first)
read_node["first"].setValue(first)
read_node["origlast"].setValue(last)
read_node["last"].setValue(last)
read_node['frame_mode'].setValue("start at")
if int(float(self.first_frame)) == int(
float(read_node['frame'].value())):
# start at workfile start
read_node['frame'].setValue(str(self.first_frame))
else:
# start at version frame start
read_node['frame'].setValue(str(orig_first - self.handle_start))
if colorspace:
read_node["colorspace"].setValue(str(colorspace))
preset_clrsp = get_imageio_input_colorspace(file)
if preset_clrsp is not None:
read_node["colorspace"].setValue(preset_clrsp)
updated_dict = {}
updated_dict.update({
"representation": str(representation["_id"]),
"frameStart": str(first),
"frameEnd": str(last),
"version": str(version.get("name")),
"colorspace": version_data.get("colorspace"),
"source": version_data.get("source"),
"handleStart": str(self.handle_start),
"handleEnd": str(self.handle_end),
"fps": str(version_data.get("fps")),
"author": version_data.get("author"),
"outputDir": version_data.get("outputDir")
})
# change color of node
if version.get("name") not in [max_version]:
read_node["tile_color"].setValue(int("0xd84f20ff", 16))
else:
read_node["tile_color"].setValue(int("0x4ecd25ff", 16))
if version_data.get("retime", None):
speed = version_data.get("speed", 1)
time_warp_nodes = version_data.get("timewarps", [])
self.make_retimes(speed, time_warp_nodes)
# Update the imprinted representation
update_container(
read_node, updated_dict
)
self.log.info("udated to version: {}".format(version.get("name")))
def remove(self, container):
from avalon.nuke import viewer_update_and_undo_stop
read_node = nuke.toNode(container['objectName'])
assert read_node.Class() == "Read", "Must be Read"
with viewer_update_and_undo_stop():
nuke.delete(read_node)
def make_retimes(self, speed, time_warp_nodes):
''' Create all retime and timewarping nodes with coppied animation '''
if speed != 1:
rtn = nuke.createNode(
"Retime",
"speed {}".format(speed))
rtn["before"].setValue("continue")
rtn["after"].setValue("continue")
rtn["input.first_lock"].setValue(True)
rtn["input.first"].setValue(
self.first_frame
)
if time_warp_nodes != []:
start_anim = self.first_frame + (self.handle_start / speed)
for timewarp in time_warp_nodes:
twn = nuke.createNode(timewarp["Class"],
"name {}".format(timewarp["name"]))
if isinstance(timewarp["lookup"], list):
# if array for animation
twn["lookup"].setAnimated()
for i, value in enumerate(timewarp["lookup"]):
twn["lookup"].setValueAt(
(start_anim + i) + value,
(start_anim + i))
else:
# if static value `int`
twn["lookup"].setValue(timewarp["lookup"])

View file

@ -1,320 +0,0 @@
import nuke
from avalon.vendor import qargparse
from avalon import api, io
from openpype.hosts.nuke.api.lib import (
get_imageio_input_colorspace
)
class LoadSequence(api.Loader):
"""Load image sequence into Nuke"""
families = ["render", "source", "plate", "review"]
representations = ["exr", "dpx"]
label = "Load Image Sequence"
order = -20
icon = "file-video-o"
color = "white"
script_start = nuke.root()["first_frame"].value()
# option gui
defaults = {
"start_at_workfile": True
}
options = [
qargparse.Boolean(
"start_at_workfile",
help="Load at workfile start frame",
default=True
)
]
node_name_template = "{class_name}_{ext}"
def load(self, context, name, namespace, options):
from avalon.nuke import (
containerise,
viewer_update_and_undo_stop
)
start_at_workfile = options.get(
"start_at_workfile", self.defaults["start_at_workfile"])
version = context['version']
version_data = version.get("data", {})
repr_id = context["representation"]["_id"]
self.log.info("version_data: {}\n".format(version_data))
self.log.debug(
"Representation id `{}` ".format(repr_id))
self.first_frame = int(nuke.root()["first_frame"].getValue())
self.handle_start = version_data.get("handleStart", 0)
self.handle_end = version_data.get("handleEnd", 0)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
# Fallback to asset name when namespace is None
if namespace is None:
namespace = context['asset']['name']
first -= self.handle_start
last += self.handle_end
file = self.fname
if not file:
repr_id = context["representation"]["_id"]
self.log.warning(
"Representation id `{}` is failing to load".format(repr_id))
return
file = file.replace("\\", "/")
repr_cont = context["representation"]["context"]
assert repr_cont.get("frame"), "Representation is not sequence"
if "#" not in file:
frame = repr_cont.get("frame")
if frame:
padding = len(frame)
file = file.replace(frame, "#" * padding)
name_data = {
"asset": repr_cont["asset"],
"subset": repr_cont["subset"],
"representation": context["representation"]["name"],
"ext": repr_cont["representation"],
"id": context["representation"]["_id"],
"class_name": self.__class__.__name__
}
read_name = self.node_name_template.format(**name_data)
# Create the Loader with the filename path set
read_node = nuke.createNode(
"Read",
"name {}".format(read_name))
# to avoid multiple undo steps for rest of process
# we will switch off undo-ing
with viewer_update_and_undo_stop():
read_node["file"].setValue(file)
# Set colorspace defined in version data
colorspace = context["version"]["data"].get("colorspace")
if colorspace:
read_node["colorspace"].setValue(str(colorspace))
preset_clrsp = get_imageio_input_colorspace(file)
if preset_clrsp is not None:
read_node["colorspace"].setValue(preset_clrsp)
# set start frame depending on workfile or version
self.loader_shift(read_node, start_at_workfile)
read_node["origfirst"].setValue(int(first))
read_node["first"].setValue(int(first))
read_node["origlast"].setValue(int(last))
read_node["last"].setValue(int(last))
# add additional metadata from the version to imprint Avalon knob
add_keys = ["frameStart", "frameEnd",
"source", "colorspace", "author", "fps", "version",
"handleStart", "handleEnd"]
data_imprint = {}
for k in add_keys:
if k == 'version':
data_imprint.update({k: context["version"]['name']})
else:
data_imprint.update(
{k: context["version"]['data'].get(k, str(None))})
data_imprint.update({"objectName": read_name})
read_node["tile_color"].setValue(int("0x4ecd25ff", 16))
if version_data.get("retime", None):
speed = version_data.get("speed", 1)
time_warp_nodes = version_data.get("timewarps", [])
self.make_retimes(speed, time_warp_nodes)
return containerise(read_node,
name=name,
namespace=namespace,
context=context,
loader=self.__class__.__name__,
data=data_imprint)
def switch(self, container, representation):
self.update(container, representation)
def update(self, container, representation):
"""Update the Loader's path
Nuke automatically tries to reset some variables when changing
the loader's path to a new file. These automatic changes are to its
inputs:
"""
from avalon.nuke import (
update_container
)
read_node = nuke.toNode(container['objectName'])
assert read_node.Class() == "Read", "Must be Read"
repr_cont = representation["context"]
assert repr_cont.get("frame"), "Representation is not sequence"
file = api.get_representation_path(representation)
if not file:
repr_id = representation["_id"]
self.log.warning(
"Representation id `{}` is failing to load".format(repr_id))
return
file = file.replace("\\", "/")
if "#" not in file:
frame = repr_cont.get("frame")
if frame:
padding = len(frame)
file = file.replace(frame, "#" * padding)
# Get start frame from version data
version = io.find_one({
"type": "version",
"_id": representation["parent"]
})
# get all versions in list
versions = io.find({
"type": "version",
"parent": version["parent"]
}).distinct('name')
max_version = max(versions)
version_data = version.get("data", {})
self.first_frame = int(nuke.root()["first_frame"].getValue())
self.handle_start = version_data.get("handleStart", 0)
self.handle_end = version_data.get("handleEnd", 0)
first = version_data.get("frameStart")
last = version_data.get("frameEnd")
if first is None:
self.log.warning(
"Missing start frame for updated version"
"assuming starts at frame 0 for: "
"{} ({})".format(read_node['name'].value(), representation))
first = 0
first -= self.handle_start
last += self.handle_end
read_node["file"].setValue(file)
# set start frame depending on workfile or version
self.loader_shift(
read_node,
bool("start at" in read_node['frame_mode'].value()))
read_node["origfirst"].setValue(int(first))
read_node["first"].setValue(int(first))
read_node["origlast"].setValue(int(last))
read_node["last"].setValue(int(last))
updated_dict = {}
updated_dict.update({
"representation": str(representation["_id"]),
"frameStart": str(first),
"frameEnd": str(last),
"version": str(version.get("name")),
"colorspace": version_data.get("colorspace"),
"source": version_data.get("source"),
"handleStart": str(self.handle_start),
"handleEnd": str(self.handle_end),
"fps": str(version_data.get("fps")),
"author": version_data.get("author"),
"outputDir": version_data.get("outputDir"),
})
# change color of read_node
if version.get("name") not in [max_version]:
read_node["tile_color"].setValue(int("0xd84f20ff", 16))
else:
read_node["tile_color"].setValue(int("0x4ecd25ff", 16))
if version_data.get("retime", None):
speed = version_data.get("speed", 1)
time_warp_nodes = version_data.get("timewarps", [])
self.make_retimes(speed, time_warp_nodes)
# Update the imprinted representation
update_container(
read_node,
updated_dict
)
self.log.info("udated to version: {}".format(version.get("name")))
def remove(self, container):
from avalon.nuke import viewer_update_and_undo_stop
read_node = nuke.toNode(container['objectName'])
assert read_node.Class() == "Read", "Must be Read"
with viewer_update_and_undo_stop():
nuke.delete(read_node)
def make_retimes(self, speed, time_warp_nodes):
''' Create all retime and timewarping nodes with coppied animation '''
if speed != 1:
rtn = nuke.createNode(
"Retime",
"speed {}".format(speed))
rtn["before"].setValue("continue")
rtn["after"].setValue("continue")
rtn["input.first_lock"].setValue(True)
rtn["input.first"].setValue(
self.first_frame
)
if time_warp_nodes != []:
start_anim = self.first_frame + (self.handle_start / speed)
for timewarp in time_warp_nodes:
twn = nuke.createNode(timewarp["Class"],
"name {}".format(timewarp["name"]))
if isinstance(timewarp["lookup"], list):
# if array for animation
twn["lookup"].setAnimated()
for i, value in enumerate(timewarp["lookup"]):
twn["lookup"].setValueAt(
(start_anim + i) + value,
(start_anim + i))
else:
# if static value `int`
twn["lookup"].setValue(timewarp["lookup"])
def loader_shift(self, read_node, workfile_start=False):
""" Set start frame of read node to a workfile start
Args:
read_node (nuke.Node): The nuke's read node
workfile_start (bool): set workfile start frame if true
"""
if workfile_start:
read_node['frame_mode'].setValue("start at")
read_node['frame'].setValue(str(self.script_start))

View file

@ -56,8 +56,8 @@ class ValidateOutputResolution(pyblish.api.InstancePlugin):
def process(self, instance):
# Skip bounding box check if a crop node exists.
if instance[0].dependencies()[0].Class() == "Crop":
# Skip bounding box check if a reformat node exists.
if instance[0].dependencies()[0].Class() == "Reformat":
return
msg = "Bounding box is outside the format."