rename folder

This commit is contained in:
Milan Kolar 2021-04-01 18:54:46 +02:00
parent 483c930a68
commit 8432e94615
1511 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,53 @@
from avalon.nuke import lib as anlib
from openpype.hosts.nuke.api import plugin
import nuke
class CreateBackdrop(plugin.PypeCreator):
"""Add Publishable Backdrop"""
name = "nukenodes"
label = "Create Backdrop"
family = "nukenodes"
icon = "file-archive-o"
defaults = ["Main"]
def __init__(self, *args, **kwargs):
super(CreateBackdrop, self).__init__(*args, **kwargs)
self.nodes = nuke.selectedNodes()
self.node_color = "0xdfea5dff"
return
def process(self):
from nukescripts import autoBackdrop
nodes = list()
if (self.options or {}).get("useSelection"):
nodes = self.nodes
if len(nodes) >= 1:
anlib.select_nodes(nodes)
bckd_node = autoBackdrop()
bckd_node["name"].setValue("{}_BDN".format(self.name))
bckd_node["tile_color"].setValue(int(self.node_color, 16))
bckd_node["note_font_size"].setValue(24)
bckd_node["label"].setValue("[{}]".format(self.name))
# add avalon knobs
instance = anlib.set_avalon_knob_data(bckd_node, self.data)
return instance
else:
msg = str("Please select nodes you "
"wish to add to a container")
self.log.error(msg)
nuke.message(msg)
return
else:
bckd_node = autoBackdrop()
bckd_node["name"].setValue("{}_BDN".format(self.name))
bckd_node["tile_color"].setValue(int(self.node_color, 16))
bckd_node["note_font_size"].setValue(24)
bckd_node["label"].setValue("[{}]".format(self.name))
# add avalon knobs
instance = anlib.set_avalon_knob_data(bckd_node, self.data)
return instance

View file

@ -0,0 +1,53 @@
from avalon.nuke import lib as anlib
from openpype.hosts.nuke.api import plugin
import nuke
class CreateCamera(plugin.PypeCreator):
"""Add Publishable Backdrop"""
name = "camera"
label = "Create 3d Camera"
family = "camera"
icon = "camera"
defaults = ["Main"]
def __init__(self, *args, **kwargs):
super(CreateCamera, self).__init__(*args, **kwargs)
self.nodes = nuke.selectedNodes()
self.node_color = "0xff9100ff"
return
def process(self):
nodes = list()
if (self.options or {}).get("useSelection"):
nodes = self.nodes
if len(nodes) >= 1:
# loop selected nodes
for n in nodes:
data = self.data.copy()
if len(nodes) > 1:
# rename subset name only if more
# then one node are selected
subset = self.family + n["name"].value().capitalize()
data["subset"] = subset
# change node color
n["tile_color"].setValue(int(self.node_color, 16))
# add avalon knobs
anlib.set_avalon_knob_data(n, data)
return True
else:
msg = str("Please select nodes you "
"wish to add to a container")
self.log.error(msg)
nuke.message(msg)
return
else:
# if selected is off then create one node
camera_node = nuke.createNode("Camera2")
camera_node["tile_color"].setValue(int(self.node_color, 16))
# add avalon knobs
instance = anlib.set_avalon_knob_data(camera_node, self.data)
return instance

View file

@ -0,0 +1,83 @@
from avalon.nuke import lib as anlib
from openpype.hosts.nuke.api import plugin
import nuke
class CreateGizmo(plugin.PypeCreator):
"""Add Publishable "gizmo" group
The name is symbolically gizmo as presumably
it is something familiar to nuke users as group of nodes
distributed downstream in workflow
"""
name = "gizmo"
label = "Gizmo"
family = "gizmo"
icon = "file-archive-o"
defaults = ["ViewerInput", "Lut", "Effect"]
def __init__(self, *args, **kwargs):
super(CreateGizmo, self).__init__(*args, **kwargs)
self.nodes = nuke.selectedNodes()
self.node_color = "0x7533c1ff"
return
def process(self):
if (self.options or {}).get("useSelection"):
nodes = self.nodes
self.log.info(len(nodes))
if len(nodes) == 1:
anlib.select_nodes(nodes)
node = nodes[-1]
# check if Group node
if node.Class() in "Group":
node["name"].setValue("{}_GZM".format(self.name))
node["tile_color"].setValue(int(self.node_color, 16))
return anlib.set_avalon_knob_data(node, self.data)
else:
msg = ("Please select a group node "
"you wish to publish as the gizmo")
self.log.error(msg)
nuke.message(msg)
if len(nodes) >= 2:
anlib.select_nodes(nodes)
nuke.makeGroup()
gizmo_node = nuke.selectedNode()
gizmo_node["name"].setValue("{}_GZM".format(self.name))
gizmo_node["tile_color"].setValue(int(self.node_color, 16))
# add sticky node wit guide
with gizmo_node:
sticky = nuke.createNode("StickyNote")
sticky["label"].setValue(
"Add following:\n- set Input"
" nodes\n- set one Output1\n"
"- create User knobs on the group")
# add avalon knobs
return anlib.set_avalon_knob_data(gizmo_node, self.data)
else:
msg = ("Please select nodes you "
"wish to add to the gizmo")
self.log.error(msg)
nuke.message(msg)
return
else:
with anlib.maintained_selection():
gizmo_node = nuke.createNode("Group")
gizmo_node["name"].setValue("{}_GZM".format(self.name))
gizmo_node["tile_color"].setValue(int(self.node_color, 16))
# add sticky node wit guide
with gizmo_node:
sticky = nuke.createNode("StickyNote")
sticky["label"].setValue(
"Add following:\n- add Input"
" nodes\n- add one Output1\n"
"- create User knobs on the group")
# add avalon knobs
return anlib.set_avalon_knob_data(gizmo_node, self.data)

View file

@ -0,0 +1,56 @@
from collections import OrderedDict
import avalon.api
import avalon.nuke
from openpype import api as pype
from openpype.hosts.nuke.api import plugin
import nuke
class CrateRead(plugin.PypeCreator):
# change this to template preset
name = "ReadCopy"
label = "Create Read Copy"
hosts = ["nuke"]
family = "source"
families = family
icon = "film"
defaults = ["Effect", "Backplate", "Fire", "Smoke"]
def __init__(self, *args, **kwargs):
super(CrateRead, self).__init__(*args, **kwargs)
self.nodes = nuke.selectedNodes()
data = OrderedDict()
data['family'] = self.family
data['families'] = self.families
for k, v in self.data.items():
if k not in data.keys():
data.update({k: v})
self.data = data
def process(self):
self.name = self.data["subset"]
nodes = self.nodes
if not nodes or len(nodes) == 0:
msg = "Please select Read node"
self.log.error(msg)
nuke.message(msg)
else:
count_reads = 0
for node in nodes:
if node.Class() != 'Read':
continue
avalon_data = self.data
avalon_data['subset'] = "{}".format(self.name)
avalon.nuke.lib.set_avalon_knob_data(node, avalon_data)
node['tile_color'].setValue(16744935)
count_reads += 1
if count_reads < 1:
msg = "Please select Read node"
self.log.error(msg)
nuke.message(msg)
return

View file

@ -0,0 +1,136 @@
from collections import OrderedDict
from openpype.hosts.nuke.api import (
plugin,
lib)
import nuke
class CreateWritePrerender(plugin.PypeCreator):
# change this to template preset
name = "WritePrerender"
label = "Create Write Prerender"
hosts = ["nuke"]
n_class = "Write"
family = "prerender"
icon = "sign-out"
defaults = ["Key01", "Bg01", "Fg01", "Branch01", "Part01"]
def __init__(self, *args, **kwargs):
super(CreateWritePrerender, self).__init__(*args, **kwargs)
data = OrderedDict()
data["family"] = self.family
data["families"] = self.n_class
for k, v in self.data.items():
if k not in data.keys():
data.update({k: v})
self.data = data
self.nodes = nuke.selectedNodes()
self.log.debug("_ self.data: '{}'".format(self.data))
def process(self):
inputs = []
outputs = []
instance = nuke.toNode(self.data["subset"])
selected_node = None
# use selection
if (self.options or {}).get("useSelection"):
nodes = self.nodes
if not (len(nodes) < 2):
msg = ("Select only one node. The node "
"you want to connect to, "
"or tick off `Use selection`")
self.log.error(msg)
nuke.message(msg)
if len(nodes) == 0:
msg = (
"No nodes selected. Please select a single node to connect"
" to or tick off `Use selection`"
)
self.log.error(msg)
nuke.message(msg)
selected_node = nodes[0]
inputs = [selected_node]
outputs = selected_node.dependent()
if instance:
if (instance.name() in selected_node.name()):
selected_node = instance.dependencies()[0]
# if node already exist
if instance:
# collect input / outputs
inputs = instance.dependencies()
outputs = instance.dependent()
selected_node = inputs[0]
# remove old one
nuke.delete(instance)
# recreate new
write_data = {
"nodeclass": self.n_class,
"families": [self.family],
"avalon": self.data,
"creator": self.__class__.__name__
}
if self.presets.get('fpath_template'):
self.log.info("Adding template path from preset")
write_data.update(
{"fpath_template": self.presets["fpath_template"]}
)
else:
self.log.info("Adding template path from plugin")
write_data.update({
"fpath_template": ("{work}/prerenders/nuke/{subset}"
"/{subset}.{frame}.{ext}")})
self.log.info("write_data: {}".format(write_data))
write_node = lib.create_write_node(
self.data["subset"],
write_data,
input=selected_node,
prenodes=[],
review=False)
# relinking to collected connections
for i, input in enumerate(inputs):
write_node.setInput(i, input)
write_node.autoplace()
for output in outputs:
output.setInput(0, write_node)
# open group node
write_node.begin()
for n in nuke.allNodes():
# get write node
if n.Class() in "Write":
w_node = n
write_node.end()
# add inner write node Tab
write_node.addKnob(nuke.Tab_Knob("WriteLinkedKnobs"))
# linking knobs to group property panel
linking_knobs = ["channels", "___", "first", "last", "use_limit"]
for k in linking_knobs:
if "___" in k:
write_node.addKnob(nuke.Text_Knob(''))
else:
lnk = nuke.Link_Knob(k)
lnk.makeLink(w_node.name(), k)
lnk.setName(k.replace('_', ' ').capitalize())
lnk.clearFlag(nuke.STARTLINE)
write_node.addKnob(lnk)
return write_node

View file

@ -0,0 +1,112 @@
from collections import OrderedDict
from openpype.hosts.nuke.api import (
plugin,
lib)
import nuke
class CreateWriteRender(plugin.PypeCreator):
# change this to template preset
name = "WriteRender"
label = "Create Write Render"
hosts = ["nuke"]
n_class = "Write"
family = "render"
icon = "sign-out"
defaults = ["Main", "Mask"]
def __init__(self, *args, **kwargs):
super(CreateWriteRender, self).__init__(*args, **kwargs)
data = OrderedDict()
data["family"] = self.family
data["families"] = self.n_class
for k, v in self.data.items():
if k not in data.keys():
data.update({k: v})
self.data = data
self.nodes = nuke.selectedNodes()
self.log.debug("_ self.data: '{}'".format(self.data))
def process(self):
inputs = []
outputs = []
instance = nuke.toNode(self.data["subset"])
selected_node = None
# use selection
if (self.options or {}).get("useSelection"):
nodes = self.nodes
if not (len(nodes) < 2):
msg = ("Select only one node. "
"The node you want to connect to, "
"or tick off `Use selection`")
self.log.error(msg)
nuke.message(msg)
return
if len(nodes) == 0:
msg = (
"No nodes selected. Please select a single node to connect"
" to or tick off `Use selection`"
)
self.log.error(msg)
nuke.message(msg)
return
selected_node = nodes[0]
inputs = [selected_node]
outputs = selected_node.dependent()
if instance:
if (instance.name() in selected_node.name()):
selected_node = instance.dependencies()[0]
# if node already exist
if instance:
# collect input / outputs
inputs = instance.dependencies()
outputs = instance.dependent()
selected_node = inputs[0]
# remove old one
nuke.delete(instance)
# recreate new
write_data = {
"nodeclass": self.n_class,
"families": [self.family],
"avalon": self.data,
"creator": self.__class__.__name__
}
if self.presets.get('fpath_template'):
self.log.info("Adding template path from preset")
write_data.update(
{"fpath_template": self.presets["fpath_template"]}
)
else:
self.log.info("Adding template path from plugin")
write_data.update({
"fpath_template": ("{work}/renders/nuke/{subset}"
"/{subset}.{frame}.{ext}")})
write_node = lib.create_write_node(
self.data["subset"],
write_data,
input=selected_node)
# relinking to collected connections
for i, input in enumerate(inputs):
write_node.setInput(i, input)
write_node.autoplace()
for output in outputs:
output.setInput(0, write_node)
return write_node

View file

@ -0,0 +1,21 @@
from avalon import api
class SelectContainers(api.InventoryAction):
label = "Select Containers"
icon = "mouse-pointer"
color = "#d8d8d8"
def process(self, containers):
import avalon.nuke
nodes = [i["_node"] for i in containers]
with avalon.nuke.viewer_update_and_undo_stop():
# clear previous_selection
[n['selected'].setValue(False) for n in nodes]
# Select tool
for node in nodes:
node["selected"].setValue(True)

View file

@ -0,0 +1,68 @@
# 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,80 @@
"""A module containing generic loader actions that will display in the Loader.
"""
from avalon import api
from openpype.api import Logger
log = Logger().get_logger(__name__)
class SetFrameRangeLoader(api.Loader):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["animation",
"camera",
"write",
"yeticache",
"pointcache"]
representations = ["*"]
label = "Set frame range"
order = 11
icon = "clock-o"
color = "white"
def load(self, context, name, namespace, data):
from openpype.hosts.nuke.api import lib
version = context['version']
version_data = version.get("data", {})
start = version_data.get("frameStart", None)
end = version_data.get("frameEnd", None)
log.info("start: {}, end: {}".format(start, end))
if start is None or end is None:
log.info("Skipping setting frame range because start or "
"end frame data is missing..")
return
lib.update_frame_range(start, end)
class SetFrameRangeWithHandlesLoader(api.Loader):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["animation",
"camera",
"write",
"yeticache",
"pointcache"]
representations = ["*"]
label = "Set frame range (with handles)"
order = 12
icon = "clock-o"
color = "white"
def load(self, context, name, namespace, data):
from openpype.hosts.nuke.api import lib
version = context['version']
version_data = version.get("data", {})
start = version_data.get("frameStart", None)
end = version_data.get("frameEnd", None)
if start is None or end is None:
print("Skipping setting frame range because start or "
"end frame data is missing..")
return
# Include handles
handles = version_data.get("handles", 0)
start -= handles
end += handles
lib.update_frame_range(start, end)

View file

@ -0,0 +1,250 @@
from avalon import api, style, io
import nuke
import nukescripts
from openpype.hosts.nuke.api import lib as pnlib
from avalon.nuke import lib as anlib
from avalon.nuke import containerise, update_container
reload(pnlib)
class LoadBackdropNodes(api.Loader):
"""Loading Published Backdrop nodes (workfile, nukenodes)"""
representations = ["nk"]
families = ["workfile", "nukenodes"]
label = "Iport Nuke Nodes"
order = 0
icon = "eye"
color = style.colors.light
node_color = "0x7533c1ff"
def load(self, context, name, namespace, data):
"""
Loading function to import .nk file into script and wrap
it on backdrop
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: containerised nuke node object
"""
# get main variables
version = context['version']
version_data = version.get("data", {})
vname = version.get("name", None)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
namespace = namespace or context['asset']['name']
colorspace = version_data.get("colorspace", None)
object_name = "{}_{}".format(name, namespace)
# prepare data for imprinting
# add additional metadata from the version to imprint to Avalon knob
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
for k in add_keys:
data_imprint.update({k: version_data[k]})
# getting file path
file = self.fname.replace("\\", "/")
# adding nodes to node graph
# just in case we are in group lets jump out of it
nuke.endGroup()
# Get mouse position
n = nuke.createNode("NoOp")
xcursor, ycursor = (n.xpos(), n.ypos())
anlib.reset_selection()
nuke.delete(n)
bdn_frame = 50
with anlib.maintained_selection():
# add group from nk
nuke.nodePaste(file)
# get all pasted nodes
new_nodes = list()
nodes = nuke.selectedNodes()
# get pointer position in DAG
xpointer, ypointer = pnlib.find_free_space_to_paste_nodes(nodes, direction="right", offset=200+bdn_frame)
# reset position to all nodes and replace inputs and output
for n in nodes:
anlib.reset_selection()
xpos = (n.xpos() - xcursor) + xpointer
ypos = (n.ypos() - ycursor) + ypointer
n.setXYpos(xpos, ypos)
# replace Input nodes for dots
if n.Class() in "Input":
dot = nuke.createNode("Dot")
new_name = n.name().replace("INP", "DOT")
dot.setName(new_name)
dot["label"].setValue(new_name)
dot.setXYpos(xpos, ypos)
new_nodes.append(dot)
# rewire
dep = n.dependent()
for d in dep:
index = next((i for i, dpcy in enumerate(
d.dependencies())
if n is dpcy), 0)
d.setInput(index, dot)
# remove Input node
anlib.reset_selection()
nuke.delete(n)
continue
# replace Input nodes for dots
elif n.Class() in "Output":
dot = nuke.createNode("Dot")
new_name = n.name() + "_DOT"
dot.setName(new_name)
dot["label"].setValue(new_name)
dot.setXYpos(xpos, ypos)
new_nodes.append(dot)
# rewire
dep = next((d for d in n.dependencies()), None)
if dep:
dot.setInput(0, dep)
# remove Input node
anlib.reset_selection()
nuke.delete(n)
continue
else:
new_nodes.append(n)
# reselect nodes with new Dot instead of Inputs and Output
anlib.reset_selection()
anlib.select_nodes(new_nodes)
# place on backdrop
bdn = nukescripts.autoBackdrop()
# add frame offset
xpos = bdn.xpos() - bdn_frame
ypos = bdn.ypos() - bdn_frame
bdwidth = bdn["bdwidth"].value() + (bdn_frame*2)
bdheight = bdn["bdheight"].value() + (bdn_frame*2)
bdn["xpos"].setValue(xpos)
bdn["ypos"].setValue(ypos)
bdn["bdwidth"].setValue(bdwidth)
bdn["bdheight"].setValue(bdheight)
bdn["name"].setValue(object_name)
bdn["label"].setValue("Version tracked frame: \n`{}`\n\nPLEASE DO NOT REMOVE OR MOVE \nANYTHING FROM THIS FRAME!".format(object_name))
bdn["note_font_size"].setValue(20)
return containerise(
node=bdn,
name=name,
namespace=namespace,
context=context,
loader=self.__class__.__name__,
data=data_imprint)
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:
"""
# get main variables
# Get version from io
version = io.find_one({
"type": "version",
"_id": representation["parent"]
})
# get corresponding node
GN = nuke.toNode(container['objectName'])
file = api.get_representation_path(representation).replace("\\", "/")
context = representation["context"]
name = container['name']
version_data = version.get("data", {})
vname = version.get("name", None)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
namespace = container['namespace']
colorspace = version_data.get("colorspace", None)
object_name = "{}_{}".format(name, namespace)
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
for k in add_keys:
data_imprint.update({k: version_data[k]})
# adding nodes to node graph
# just in case we are in group lets jump out of it
nuke.endGroup()
with anlib.maintained_selection():
xpos = GN.xpos()
ypos = GN.ypos()
avalon_data = anlib.get_avalon_knob_data(GN)
nuke.delete(GN)
# add group from nk
nuke.nodePaste(file)
GN = nuke.selectedNode()
anlib.set_avalon_knob_data(GN, avalon_data)
GN.setXYpos(xpos, ypos)
GN["name"].setValue(object_name)
# get all versions in list
versions = io.find({
"type": "version",
"parent": version["parent"]
}).distinct('name')
max_version = max(versions)
# change color of node
if version.get("name") not in [max_version]:
GN["tile_color"].setValue(int("0xd88467ff", 16))
else:
GN["tile_color"].setValue(int(self.node_color, 16))
self.log.info("udated to version: {}".format(version.get("name")))
return update_container(GN, data_imprint)
def switch(self, container, representation):
self.update(container, representation)
def remove(self, container):
from avalon.nuke import viewer_update_and_undo_stop
node = nuke.toNode(container['objectName'])
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -0,0 +1,187 @@
from avalon import api, io
from avalon.nuke import lib as anlib
from avalon.nuke import containerise, update_container
import nuke
class AlembicCameraLoader(api.Loader):
"""
This will load alembic camera into script.
"""
families = ["camera"]
representations = ["abc"]
label = "Load Alembic Camera"
icon = "camera"
color = "orange"
node_color = "0x3469ffff"
def load(self, context, name, namespace, data):
# get main variables
version = context['version']
version_data = version.get("data", {})
vname = version.get("name", None)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
fps = version_data.get("fps") or nuke.root()["fps"].getValue()
namespace = namespace or context['asset']['name']
object_name = "{}_{}".format(name, namespace)
# prepare data for imprinting
# add additional metadata from the version to imprint to Avalon knob
add_keys = ["source", "author", "fps"]
data_imprint = {"frameStart": first,
"frameEnd": last,
"version": vname,
"objectName": object_name}
for k in add_keys:
data_imprint.update({k: version_data[k]})
# getting file path
file = self.fname.replace("\\", "/")
with anlib.maintained_selection():
camera_node = nuke.createNode(
"Camera2",
"name {} file {} read_from_file True".format(
object_name, file),
inpanel=False
)
camera_node.forceValidate()
camera_node["frame_rate"].setValue(float(fps))
# workaround because nuke's bug is not adding
# animation keys properly
xpos = camera_node.xpos()
ypos = camera_node.ypos()
nuke.nodeCopy("%clipboard%")
nuke.delete(camera_node)
nuke.nodePaste("%clipboard%")
camera_node = nuke.toNode(object_name)
camera_node.setXYpos(xpos, ypos)
# color node by correct color by actual version
self.node_version_color(version, camera_node)
return containerise(
node=camera_node,
name=name,
namespace=namespace,
context=context,
loader=self.__class__.__name__,
data=data_imprint)
def update(self, container, representation):
"""
Called by Scene Inventory when look should be updated to current
version.
If any reference edits cannot be applied, eg. shader renamed and
material not present, reference is unloaded and cleaned.
All failed edits are highlighted to the user via message box.
Args:
container: object that has look to be updated
representation: (dict): relationship data to get proper
representation from DB and persisted
data in .json
Returns:
None
"""
# Get version from io
version = io.find_one({
"type": "version",
"_id": representation["parent"]
})
object_name = container['objectName']
# get corresponding node
camera_node = nuke.toNode(object_name)
# get main variables
version_data = version.get("data", {})
vname = version.get("name", None)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
fps = version_data.get("fps") or nuke.root()["fps"].getValue()
# prepare data for imprinting
# add additional metadata from the version to imprint to Avalon knob
add_keys = ["source", "author", "fps"]
data_imprint = {"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"objectName": object_name}
for k in add_keys:
data_imprint.update({k: version_data[k]})
# getting file path
file = api.get_representation_path(representation).replace("\\", "/")
with anlib.maintained_selection():
camera_node = nuke.toNode(object_name)
camera_node['selected'].setValue(True)
# collect input output dependencies
dependencies = camera_node.dependencies()
dependent = camera_node.dependent()
camera_node["frame_rate"].setValue(float(fps))
camera_node["file"].setValue(file)
# workaround because nuke's bug is
# not adding animation keys properly
xpos = camera_node.xpos()
ypos = camera_node.ypos()
nuke.nodeCopy("%clipboard%")
nuke.delete(camera_node)
nuke.nodePaste("%clipboard%")
camera_node = nuke.toNode(object_name)
camera_node.setXYpos(xpos, ypos)
# link to original input nodes
for i, input in enumerate(dependencies):
camera_node.setInput(i, input)
# link to original output nodes
for d in dependent:
index = next((i for i, dpcy in enumerate(
d.dependencies())
if camera_node is dpcy), 0)
d.setInput(index, camera_node)
# color node by correct color by actual version
self.node_version_color(version, camera_node)
self.log.info("udated to version: {}".format(version.get("name")))
return update_container(camera_node, data_imprint)
def node_version_color(self, version, node):
""" Coloring a node by correct color by actual version
"""
# get all versions in list
versions = io.find({
"type": "version",
"parent": version["parent"]
}).distinct('name')
max_version = max(versions)
# change color of node
if version.get("name") not in [max_version]:
node["tile_color"].setValue(int("0xd88467ff", 16))
else:
node["tile_color"].setValue(int(self.node_color, 16))
def switch(self, container, representation):
self.update(container, representation)
def remove(self, container):
from avalon.nuke import viewer_update_and_undo_stop
node = nuke.toNode(container['objectName'])
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -0,0 +1,163 @@
from avalon import api, style, io
import nuke
from avalon.nuke import lib as anlib
from avalon.nuke import containerise, update_container
class LoadGizmo(api.Loader):
"""Loading nuke Gizmo"""
representations = ["gizmo"]
families = ["gizmo"]
label = "Load Gizmo"
order = 0
icon = "dropbox"
color = style.colors.light
node_color = "0x75338eff"
def load(self, context, name, namespace, data):
"""
Loading function to get Gizmo into node graph
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: containerised nuke node object
"""
# get main variables
version = context['version']
version_data = version.get("data", {})
vname = version.get("name", None)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
namespace = namespace or context['asset']['name']
colorspace = version_data.get("colorspace", None)
object_name = "{}_{}".format(name, namespace)
# prepare data for imprinting
# add additional metadata from the version to imprint to Avalon knob
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
for k in add_keys:
data_imprint.update({k: version_data[k]})
# getting file path
file = self.fname.replace("\\", "/")
# adding nodes to node graph
# just in case we are in group lets jump out of it
nuke.endGroup()
with anlib.maintained_selection():
# add group from nk
nuke.nodePaste(file)
GN = nuke.selectedNode()
GN["name"].setValue(object_name)
return containerise(
node=GN,
name=name,
namespace=namespace,
context=context,
loader=self.__class__.__name__,
data=data_imprint)
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:
"""
# get main variables
# Get version from io
version = io.find_one({
"type": "version",
"_id": representation["parent"]
})
# get corresponding node
GN = nuke.toNode(container['objectName'])
file = api.get_representation_path(representation).replace("\\", "/")
name = container['name']
version_data = version.get("data", {})
vname = version.get("name", None)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
namespace = container['namespace']
colorspace = version_data.get("colorspace", None)
object_name = "{}_{}".format(name, namespace)
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
for k in add_keys:
data_imprint.update({k: version_data[k]})
# adding nodes to node graph
# just in case we are in group lets jump out of it
nuke.endGroup()
with anlib.maintained_selection():
xpos = GN.xpos()
ypos = GN.ypos()
avalon_data = anlib.get_avalon_knob_data(GN)
nuke.delete(GN)
# add group from nk
nuke.nodePaste(file)
GN = nuke.selectedNode()
anlib.set_avalon_knob_data(GN, avalon_data)
GN.setXYpos(xpos, ypos)
GN["name"].setValue(object_name)
# get all versions in list
versions = io.find({
"type": "version",
"parent": version["parent"]
}).distinct('name')
max_version = max(versions)
# change color of node
if version.get("name") not in [max_version]:
GN["tile_color"].setValue(int("0xd88467ff", 16))
else:
GN["tile_color"].setValue(int(self.node_color, 16))
self.log.info("udated to version: {}".format(version.get("name")))
return update_container(GN, data_imprint)
def switch(self, container, representation):
self.update(container, representation)
def remove(self, container):
from avalon.nuke import viewer_update_and_undo_stop
node = nuke.toNode(container['objectName'])
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -0,0 +1,240 @@
from avalon import api, style, io
import nuke
from openpype.hosts.nuke.api import lib as pnlib
from avalon.nuke import lib as anlib
from avalon.nuke import containerise, update_container
class LoadGizmoInputProcess(api.Loader):
"""Loading colorspace soft effect exported from nukestudio"""
representations = ["gizmo"]
families = ["gizmo"]
label = "Load Gizmo - Input Process"
order = 0
icon = "eye"
color = style.colors.alert
node_color = "0x7533c1ff"
def load(self, context, name, namespace, data):
"""
Loading function to get Gizmo as Input Process on viewer
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: containerised nuke node object
"""
# get main variables
version = context['version']
version_data = version.get("data", {})
vname = version.get("name", None)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
namespace = namespace or context['asset']['name']
colorspace = version_data.get("colorspace", None)
object_name = "{}_{}".format(name, namespace)
# prepare data for imprinting
# add additional metadata from the version to imprint to Avalon knob
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
for k in add_keys:
data_imprint.update({k: version_data[k]})
# getting file path
file = self.fname.replace("\\", "/")
# adding nodes to node graph
# just in case we are in group lets jump out of it
nuke.endGroup()
with anlib.maintained_selection():
# add group from nk
nuke.nodePaste(file)
GN = nuke.selectedNode()
GN["name"].setValue(object_name)
# try to place it under Viewer1
if not self.connect_active_viewer(GN):
nuke.delete(GN)
return
return containerise(
node=GN,
name=name,
namespace=namespace,
context=context,
loader=self.__class__.__name__,
data=data_imprint)
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:
"""
# get main variables
# Get version from io
version = io.find_one({
"type": "version",
"_id": representation["parent"]
})
# get corresponding node
GN = nuke.toNode(container['objectName'])
file = api.get_representation_path(representation).replace("\\", "/")
name = container['name']
version_data = version.get("data", {})
vname = version.get("name", None)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
namespace = container['namespace']
colorspace = version_data.get("colorspace", None)
object_name = "{}_{}".format(name, namespace)
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
for k in add_keys:
data_imprint.update({k: version_data[k]})
# adding nodes to node graph
# just in case we are in group lets jump out of it
nuke.endGroup()
with anlib.maintained_selection():
xpos = GN.xpos()
ypos = GN.ypos()
avalon_data = anlib.get_avalon_knob_data(GN)
nuke.delete(GN)
# add group from nk
nuke.nodePaste(file)
GN = nuke.selectedNode()
anlib.set_avalon_knob_data(GN, avalon_data)
GN.setXYpos(xpos, ypos)
GN["name"].setValue(object_name)
# get all versions in list
versions = io.find({
"type": "version",
"parent": version["parent"]
}).distinct('name')
max_version = max(versions)
# change color of node
if version.get("name") not in [max_version]:
GN["tile_color"].setValue(int("0xd88467ff", 16))
else:
GN["tile_color"].setValue(int(self.node_color, 16))
self.log.info("udated to version: {}".format(version.get("name")))
return update_container(GN, data_imprint)
def connect_active_viewer(self, group_node):
"""
Finds Active viewer and
place the node under it, also adds
name of group into Input Process of the viewer
Arguments:
group_node (nuke node): nuke group node object
"""
group_node_name = group_node["name"].value()
viewer = [n for n in nuke.allNodes() if "Viewer1" in n["name"].value()]
if len(viewer) > 0:
viewer = viewer[0]
else:
msg = str("Please create Viewer node before you "
"run this action again")
self.log.error(msg)
nuke.message(msg)
return None
# get coordinates of Viewer1
xpos = viewer["xpos"].value()
ypos = viewer["ypos"].value()
ypos += 150
viewer["ypos"].setValue(ypos)
# set coordinates to group node
group_node["xpos"].setValue(xpos)
group_node["ypos"].setValue(ypos + 50)
# add group node name to Viewer Input Process
viewer["input_process_node"].setValue(group_node_name)
# put backdrop under
pnlib.create_backdrop(label="Input Process", layer=2,
nodes=[viewer, group_node], color="0x7c7faaff")
return True
def get_item(self, data, trackIndex, subTrackIndex):
return {key: val for key, val in data.items()
if subTrackIndex == val["subTrackIndex"]
if trackIndex == val["trackIndex"]}
def byteify(self, input):
"""
Converts unicode strings to strings
It goes trought all dictionary
Arguments:
input (dict/str): input
Returns:
dict: with fixed values and keys
"""
if isinstance(input, dict):
return {self.byteify(key): self.byteify(value)
for key, value in input.iteritems()}
elif isinstance(input, list):
return [self.byteify(element) for element in input]
elif isinstance(input, unicode):
return input.encode('utf-8')
else:
return input
def switch(self, container, representation):
self.update(container, representation)
def remove(self, container):
from avalon.nuke import viewer_update_and_undo_stop
node = nuke.toNode(container['objectName'])
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -0,0 +1,233 @@
import re
import nuke
from avalon.vendor import qargparse
from avalon import api, io
from openpype.hosts.nuke.api.lib import (
get_imageio_input_colorspace
)
class LoadImage(api.Loader):
"""Load still image into Nuke"""
families = ["render", "source", "plate", "review", "image"]
representations = ["exr", "dpx", "jpg", "jpeg", "png", "psd"]
label = "Load Image"
order = -10
icon = "image"
color = "white"
node_name_template = "{class_name}_{ext}"
options = [
qargparse.Integer(
"frame_number",
label="Frame Number",
default=int(nuke.root()["first_frame"].getValue()),
min=1,
max=999999,
help="What frame is reading from?"
)
]
def load(self, context, name, namespace, options):
from avalon.nuke import (
containerise,
viewer_update_and_undo_stop
)
self.log.info("__ options: `{}`".format(options))
frame_number = options.get("frame_number", 1)
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))
last = first = int(frame_number)
# Fallback to asset name when namespace is None
if namespace is None:
namespace = context['asset']['name']
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"]
frame = repr_cont.get("frame")
if frame:
padding = len(frame)
file = file.replace(
frame,
format(frame_number, "0{}".format(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
with viewer_update_and_undo_stop():
r = nuke.createNode(
"Read",
"name {}".format(read_name))
r["file"].setValue(file)
# Set colorspace defined in version data
colorspace = context["version"]["data"].get("colorspace")
if colorspace:
r["colorspace"].setValue(str(colorspace))
preset_clrsp = get_imageio_input_colorspace(file)
if preset_clrsp is not None:
r["colorspace"].setValue(preset_clrsp)
r["origfirst"].setValue(first)
r["first"].setValue(first)
r["origlast"].setValue(last)
r["last"].setValue(last)
# add additional metadata from the version to imprint Avalon knob
add_keys = ["source", "colorspace", "author", "fps", "version"]
data_imprint = {
"frameStart": first,
"frameEnd": last
}
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})
r["tile_color"].setValue(int("0x4ecd25ff", 16))
return containerise(r,
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
)
node = nuke.toNode(container["objectName"])
frame_number = node["first"].value()
assert node.Class() == "Read", "Must be Read"
repr_cont = representation["context"]
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("\\", "/")
frame = repr_cont.get("frame")
if frame:
padding = len(frame)
file = file.replace(
frame,
format(frame_number, "0{}".format(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", {})
last = first = int(frame_number)
# Set the global in to the start frame of the sequence
node["file"].setValue(file)
node["origfirst"].setValue(first)
node["first"].setValue(first)
node["origlast"].setValue(last)
node["last"].setValue(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"),
"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]:
node["tile_color"].setValue(int("0xd84f20ff", 16))
else:
node["tile_color"].setValue(int("0x4ecd25ff", 16))
# Update the imprinted representation
update_container(
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
node = nuke.toNode(container['objectName'])
assert node.Class() == "Read", "Must be Read"
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -0,0 +1,336 @@
from avalon import api, style, io
import nuke
import json
from collections import OrderedDict
class LoadLuts(api.Loader):
"""Loading colorspace soft effect exported from nukestudio"""
representations = ["lutJson"]
families = ["lut"]
label = "Load Luts - nodes"
order = 0
icon = "cc"
color = style.colors.light
ignore_attr = ["useLifetime"]
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: containerised nuke node object
"""
# import dependencies
from avalon.nuke import containerise
# get main variables
version = context['version']
version_data = version.get("data", {})
vname = version.get("name", None)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
workfile_first_frame = int(nuke.root()["first_frame"].getValue())
namespace = namespace or context['asset']['name']
colorspace = version_data.get("colorspace", None)
object_name = "{}_{}".format(name, namespace)
# prepare data for imprinting
# add additional metadata from the version to imprint to Avalon knob
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
for k in add_keys:
data_imprint.update({k: version_data[k]})
# getting file path
file = self.fname.replace("\\", "/")
# getting data from json file with unicode conversion
with open(file, "r") as f:
json_f = {self.byteify(key): self.byteify(value)
for key, value in json.load(f).iteritems()}
# get correct order of nodes by positions on track and subtrack
nodes_order = self.reorder_nodes(json_f["effects"])
# adding nodes to node graph
# just in case we are in group lets jump out of it
nuke.endGroup()
GN = nuke.createNode("Group")
GN["name"].setValue(object_name)
# adding content to the group node
with GN:
pre_node = nuke.createNode("Input")
pre_node["name"].setValue("rgb")
for ef_name, ef_val in nodes_order.items():
node = nuke.createNode(ef_val["class"])
for k, v in ef_val["node"].items():
if k in self.ignore_attr:
continue
try:
node[k].value()
except NameError as e:
self.log.warning(e)
continue
if isinstance(v, list) and len(v) > 4:
node[k].setAnimated()
for i, value in enumerate(v):
if isinstance(value, list):
for ci, cv in enumerate(value):
node[k].setValueAt(
cv,
(workfile_first_frame + i),
ci)
else:
node[k].setValueAt(
value,
(workfile_first_frame + i))
else:
node[k].setValue(v)
node.setInput(0, pre_node)
pre_node = node
output = nuke.createNode("Output")
output.setInput(0, pre_node)
# try to find parent read node
self.connect_read_node(GN, namespace, json_f["assignTo"])
GN["tile_color"].setValue(int("0x3469ffff", 16))
self.log.info("Loaded lut setup: `{}`".format(GN["name"].value()))
return containerise(
node=GN,
name=name,
namespace=namespace,
context=context,
loader=self.__class__.__name__,
data=data_imprint)
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
)
# get main variables
# Get version from io
version = io.find_one({
"type": "version",
"_id": representation["parent"]
})
# get corresponding node
GN = nuke.toNode(container['objectName'])
file = api.get_representation_path(representation).replace("\\", "/")
name = container['name']
version_data = version.get("data", {})
vname = version.get("name", None)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
workfile_first_frame = int(nuke.root()["first_frame"].getValue())
namespace = container['namespace']
colorspace = version_data.get("colorspace", None)
object_name = "{}_{}".format(name, namespace)
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
for k in add_keys:
data_imprint.update({k: version_data[k]})
# Update the imprinted representation
update_container(
GN,
data_imprint
)
# getting data from json file with unicode conversion
with open(file, "r") as f:
json_f = {self.byteify(key): self.byteify(value)
for key, value in json.load(f).iteritems()}
# get correct order of nodes by positions on track and subtrack
nodes_order = self.reorder_nodes(json_f["effects"])
# adding nodes to node graph
# just in case we are in group lets jump out of it
nuke.endGroup()
# adding content to the group node
with GN:
# first remove all nodes
[nuke.delete(n) for n in nuke.allNodes()]
# create input node
pre_node = nuke.createNode("Input")
pre_node["name"].setValue("rgb")
for ef_name, ef_val in nodes_order.items():
node = nuke.createNode(ef_val["class"])
for k, v in ef_val["node"].items():
if k in self.ignore_attr:
continue
try:
node[k].value()
except NameError as e:
self.log.warning(e)
continue
if isinstance(v, list) and len(v) > 3:
node[k].setAnimated()
for i, value in enumerate(v):
if isinstance(value, list):
for ci, cv in enumerate(value):
node[k].setValueAt(
cv,
(workfile_first_frame + i),
ci)
else:
node[k].setValueAt(
value,
(workfile_first_frame + i))
else:
node[k].setValue(v)
node.setInput(0, pre_node)
pre_node = node
# create output node
output = nuke.createNode("Output")
output.setInput(0, pre_node)
# try to find parent read node
self.connect_read_node(GN, namespace, json_f["assignTo"])
# get all versions in list
versions = io.find({
"type": "version",
"parent": version["parent"]
}).distinct('name')
max_version = max(versions)
# change color of node
if version.get("name") not in [max_version]:
GN["tile_color"].setValue(int("0xd84f20ff", 16))
else:
GN["tile_color"].setValue(int("0x3469ffff", 16))
self.log.info("udated to version: {}".format(version.get("name")))
def connect_read_node(self, group_node, asset, subset):
"""
Finds read node and selects it
Arguments:
asset (str): asset name
Returns:
nuke node: node is selected
None: if nothing found
"""
search_name = "{0}_{1}".format(asset, subset)
node = [n for n in nuke.allNodes() if search_name in n["name"].value()]
if len(node) > 0:
rn = node[0]
else:
rn = None
# Parent read node has been found
# solving connections
if rn:
dep_nodes = rn.dependent()
if len(dep_nodes) > 0:
for dn in dep_nodes:
dn.setInput(0, group_node)
group_node.setInput(0, rn)
group_node.autoplace()
def reorder_nodes(self, data):
new_order = OrderedDict()
trackNums = [v["trackIndex"] for k, v in data.items()]
subTrackNums = [v["subTrackIndex"] for k, v in data.items()]
for trackIndex in range(
min(trackNums), max(trackNums) + 1):
for subTrackIndex in range(
min(subTrackNums), max(subTrackNums) + 1):
item = self.get_item(data, trackIndex, subTrackIndex)
if item is not {}:
new_order.update(item)
return new_order
def get_item(self, data, trackIndex, subTrackIndex):
return {key: val for key, val in data.items()
if subTrackIndex == val["subTrackIndex"]
if trackIndex == val["trackIndex"]}
def byteify(self, input):
"""
Converts unicode strings to strings
It goes trought all dictionary
Arguments:
input (dict/str): input
Returns:
dict: with fixed values and keys
"""
if isinstance(input, dict):
return {self.byteify(key): self.byteify(value)
for key, value in input.iteritems()}
elif isinstance(input, list):
return [self.byteify(element) for element in input]
elif isinstance(input, unicode):
return input.encode('utf-8')
else:
return input
def switch(self, container, representation):
self.update(container, representation)
def remove(self, container):
from avalon.nuke import viewer_update_and_undo_stop
node = nuke.toNode(container['objectName'])
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -0,0 +1,354 @@
from avalon import api, style, io
import nuke
import json
from collections import OrderedDict
from openpype.hosts.nuke.api import lib
class LoadLutsInputProcess(api.Loader):
"""Loading colorspace soft effect exported from nukestudio"""
representations = ["lutJson"]
families = ["lut"]
label = "Load Luts - Input Process"
order = 0
icon = "eye"
color = style.colors.alert
ignore_attr = ["useLifetime"]
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: containerised nuke node object
"""
# import dependencies
from avalon.nuke import containerise
# get main variables
version = context['version']
version_data = version.get("data", {})
vname = version.get("name", None)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
workfile_first_frame = int(nuke.root()["first_frame"].getValue())
namespace = namespace or context['asset']['name']
colorspace = version_data.get("colorspace", None)
object_name = "{}_{}".format(name, namespace)
# prepare data for imprinting
# add additional metadata from the version to imprint to Avalon knob
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
for k in add_keys:
data_imprint.update({k: version_data[k]})
# getting file path
file = self.fname.replace("\\", "/")
# getting data from json file with unicode conversion
with open(file, "r") as f:
json_f = {self.byteify(key): self.byteify(value)
for key, value in json.load(f).iteritems()}
# get correct order of nodes by positions on track and subtrack
nodes_order = self.reorder_nodes(json_f["effects"])
# adding nodes to node graph
# just in case we are in group lets jump out of it
nuke.endGroup()
GN = nuke.createNode("Group")
GN["name"].setValue(object_name)
# adding content to the group node
with GN:
pre_node = nuke.createNode("Input")
pre_node["name"].setValue("rgb")
for ef_name, ef_val in nodes_order.items():
node = nuke.createNode(ef_val["class"])
for k, v in ef_val["node"].items():
if k in self.ignore_attr:
continue
try:
node[k].value()
except NameError as e:
self.log.warning(e)
continue
if isinstance(v, list) and len(v) > 4:
node[k].setAnimated()
for i, value in enumerate(v):
if isinstance(value, list):
for ci, cv in enumerate(value):
node[k].setValueAt(
cv,
(workfile_first_frame + i),
ci)
else:
node[k].setValueAt(
value,
(workfile_first_frame + i))
else:
node[k].setValue(v)
node.setInput(0, pre_node)
pre_node = node
output = nuke.createNode("Output")
output.setInput(0, pre_node)
# try to place it under Viewer1
if not self.connect_active_viewer(GN):
nuke.delete(GN)
return
GN["tile_color"].setValue(int("0x3469ffff", 16))
self.log.info("Loaded lut setup: `{}`".format(GN["name"].value()))
return containerise(
node=GN,
name=name,
namespace=namespace,
context=context,
loader=self.__class__.__name__,
data=data_imprint)
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
)
# get main variables
# Get version from io
version = io.find_one({
"type": "version",
"_id": representation["parent"]
})
# get corresponding node
GN = nuke.toNode(container['objectName'])
file = api.get_representation_path(representation).replace("\\", "/")
name = container['name']
version_data = version.get("data", {})
vname = version.get("name", None)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
workfile_first_frame = int(nuke.root()["first_frame"].getValue())
namespace = container['namespace']
colorspace = version_data.get("colorspace", None)
object_name = "{}_{}".format(name, namespace)
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
for k in add_keys:
data_imprint.update({k: version_data[k]})
# Update the imprinted representation
update_container(
GN,
data_imprint
)
# getting data from json file with unicode conversion
with open(file, "r") as f:
json_f = {self.byteify(key): self.byteify(value)
for key, value in json.load(f).iteritems()}
# get correct order of nodes by positions on track and subtrack
nodes_order = self.reorder_nodes(json_f["effects"])
# adding nodes to node graph
# just in case we are in group lets jump out of it
nuke.endGroup()
# adding content to the group node
with GN:
# first remove all nodes
[nuke.delete(n) for n in nuke.allNodes()]
# create input node
pre_node = nuke.createNode("Input")
pre_node["name"].setValue("rgb")
for ef_name, ef_val in nodes_order.items():
node = nuke.createNode(ef_val["class"])
for k, v in ef_val["node"].items():
if k in self.ignore_attr:
continue
try:
node[k].value()
except NameError as e:
self.log.warning(e)
continue
if isinstance(v, list) and len(v) > 3:
node[k].setAnimated()
for i, value in enumerate(v):
if isinstance(value, list):
for ci, cv in enumerate(value):
node[k].setValueAt(
cv,
(workfile_first_frame + i),
ci)
else:
node[k].setValueAt(
value,
(workfile_first_frame + i))
else:
node[k].setValue(v)
node.setInput(0, pre_node)
pre_node = node
# create output node
output = nuke.createNode("Output")
output.setInput(0, pre_node)
# try to place it under Viewer1
if not self.connect_active_viewer(GN):
nuke.delete(GN)
return
# get all versions in list
versions = io.find({
"type": "version",
"parent": version["parent"]
}).distinct('name')
max_version = max(versions)
# change color of node
if version.get("name") not in [max_version]:
GN["tile_color"].setValue(int("0xd84f20ff", 16))
else:
GN["tile_color"].setValue(int("0x3469ffff", 16))
self.log.info("udated to version: {}".format(version.get("name")))
def connect_active_viewer(self, group_node):
"""
Finds Active viewer and
place the node under it, also adds
name of group into Input Process of the viewer
Arguments:
group_node (nuke node): nuke group node object
"""
group_node_name = group_node["name"].value()
viewer = [n for n in nuke.allNodes() if "Viewer1" in n["name"].value()]
if len(viewer) > 0:
viewer = viewer[0]
else:
msg = str("Please create Viewer node before you "
"run this action again")
self.log.error(msg)
nuke.message(msg)
return None
# get coordinates of Viewer1
xpos = viewer["xpos"].value()
ypos = viewer["ypos"].value()
ypos += 150
viewer["ypos"].setValue(ypos)
# set coordinates to group node
group_node["xpos"].setValue(xpos)
group_node["ypos"].setValue(ypos + 50)
# add group node name to Viewer Input Process
viewer["input_process_node"].setValue(group_node_name)
# put backdrop under
lib.create_backdrop(label="Input Process", layer=2, nodes=[viewer, group_node], color="0x7c7faaff")
return True
def reorder_nodes(self, data):
new_order = OrderedDict()
trackNums = [v["trackIndex"] for k, v in data.items()]
subTrackNums = [v["subTrackIndex"] for k, v in data.items()]
for trackIndex in range(
min(trackNums), max(trackNums) + 1):
for subTrackIndex in range(
min(subTrackNums), max(subTrackNums) + 1):
item = self.get_item(data, trackIndex, subTrackIndex)
if item is not {}:
new_order.update(item)
return new_order
def get_item(self, data, trackIndex, subTrackIndex):
return {key: val for key, val in data.items()
if subTrackIndex == val["subTrackIndex"]
if trackIndex == val["trackIndex"]}
def byteify(self, input):
"""
Converts unicode strings to strings
It goes trought all dictionary
Arguments:
input (dict/str): input
Returns:
dict: with fixed values and keys
"""
if isinstance(input, dict):
return {self.byteify(key): self.byteify(value)
for key, value in input.iteritems()}
elif isinstance(input, list):
return [self.byteify(element) for element in input]
elif isinstance(input, unicode):
return input.encode('utf-8')
else:
return input
def switch(self, container, representation):
self.update(container, representation)
def remove(self, container):
from avalon.nuke import viewer_update_and_undo_stop
node = nuke.toNode(container['objectName'])
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -0,0 +1,27 @@
from avalon import api
import nuke
class MatchmoveLoader(api.Loader):
"""
This will run matchmove script to create track in script.
"""
families = ["matchmove"]
representations = ["py"]
defaults = ["Camera", "Object"]
label = "Run matchmove script"
icon = "empire"
color = "orange"
def load(self, context, name, namespace, data):
if self.fname.lower().endswith(".py"):
exec(open(self.fname).read())
else:
msg = "Unsupported script type"
self.log.error(msg)
nuke.message(msg)
return True

View file

@ -0,0 +1,326 @@
import nuke
import contextlib
from avalon import api, io
from openpype.api import get_current_project_settings
from openpype.hosts.nuke.api.lib import (
get_imageio_input_colorspace
)
@contextlib.contextmanager
def preserve_trim(node):
"""Preserve the relative trim of the Loader tool.
This tries to preserve the loader's trim (trim in and trim out) after
the context by reapplying the "amount" it trims on the clip's length at
start and end.
"""
# working script frame range
script_start = nuke.root()["first_frame"].value()
start_at_frame = None
offset_frame = None
if node['frame_mode'].value() == "start at":
start_at_frame = node['frame'].value()
if node['frame_mode'].value() == "offset":
offset_frame = node['frame'].value()
try:
yield
finally:
if start_at_frame:
node['frame_mode'].setValue("start at")
node['frame'].setValue(str(script_start))
print("start frame of Read was set to"
"{}".format(script_start))
if offset_frame:
node['frame_mode'].setValue("offset")
node['frame'].setValue(str((script_start + offset_frame)))
print("start frame of Read was set to"
"{}".format(script_start))
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"
script_start = nuke.root()["first_frame"].value()
node_name_template = "{class_name}_{ext}"
def load(self, context, name, namespace, data):
from avalon.nuke import (
containerise,
viewer_update_and_undo_stop
)
version = context['version']
version_data = version.get("data", {})
repr_id = context["representation"]["_id"]
orig_first = version_data.get("frameStart")
orig_last = version_data.get("frameEnd")
diff = orig_first - 1
first = orig_first - diff
last = orig_last - diff
handle_start = version_data.get("handleStart", 0)
handle_end = version_data.get("handleEnd", 0)
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 += handle_start + 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)
# Create the Loader with the filename path set
with viewer_update_and_undo_stop():
read_node = nuke.createNode(
"Read",
"name {}".format(read_name)
)
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)
# start at script start
read_node['frame_mode'].setValue("start at")
read_node['frame'].setValue(str(self.script_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))
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
handles = version_data.get("handles", 0)
handle_start = version_data.get("handleStart", 0)
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
# fix handle start and end if none are available
if not handle_start and not handle_end:
handle_start = handles
handle_end = handles
# create handles offset (only to last, because of mov)
last += handle_start + handle_end
# Update the loader's path whilst preserving some values
with preserve_trim(read_node):
read_node["file"].setValue(file)
self.log.info("__ node['file']: {}".format(
read_node["file"].value()))
# 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)
# start at script start
read_node['frame_mode'].setValue("start at")
read_node['frame'].setValue(str(self.script_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(handle_start),
"handleEnd": str(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))
# 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
node = nuke.toNode(container['objectName'])
assert node.Class() == "Read", "Must be Read"
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -0,0 +1,162 @@
from avalon import api, style, io
from avalon.nuke import get_avalon_knob_data
import nuke
class LinkAsGroup(api.Loader):
"""Copy the published file to be pasted at the desired location"""
representations = ["nk"]
families = ["workfile", "nukenodes"]
label = "Load Precomp"
order = 0
icon = "file"
color = style.colors.alert
def load(self, context, name, namespace, data):
from avalon.nuke import containerise
# for k, v in context.items():
# log.info("key: `{}`, value: {}\n".format(k, v))
version = context['version']
version_data = version.get("data", {})
vname = version.get("name", None)
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']
file = self.fname.replace("\\", "/")
self.log.info("file: {}\n".format(self.fname))
precomp_name = context["representation"]["context"]["subset"]
self.log.info("versionData: {}\n".format(context["version"]["data"]))
# add additional metadata from the version to imprint to Avalon knob
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {
"startingFrame": first,
"frameStart": first,
"frameEnd": last,
"version": vname
}
for k in add_keys:
data_imprint.update({k: context["version"]['data'][k]})
data_imprint.update({"objectName": precomp_name})
# group context is set to precomp, so back up one level.
nuke.endGroup()
# P = nuke.nodes.LiveGroup("file {}".format(file))
P = nuke.createNode(
"Precomp",
"file {}".format(file))
# Set colorspace defined in version data
colorspace = context["version"]["data"].get("colorspace", None)
self.log.info("colorspace: {}\n".format(colorspace))
P["name"].setValue("{}_{}".format(name, namespace))
P["useOutput"].setValue(True)
with P:
# iterate trough all nodes in group node and find pype writes
writes = [n.name() for n in nuke.allNodes()
if n.Class() == "Group"
if get_avalon_knob_data(n)]
if writes:
# create panel for selecting output
panel_choices = " ".join(writes)
panel_label = "Select write node for output"
p = nuke.Panel("Select Write Node")
p.addEnumerationPulldown(
panel_label, panel_choices)
p.show()
P["output"].setValue(p.value(panel_label))
P["tile_color"].setValue(0xff0ff0ff)
return containerise(
node=P,
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
)
node = nuke.toNode(container['objectName'])
root = api.get_representation_path(representation).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)
updated_dict = {}
updated_dict.update({
"representation": str(representation["_id"]),
"frameEnd": version["data"].get("frameEnd"),
"version": version.get("name"),
"colorspace": version["data"].get("colorspace"),
"source": version["data"].get("source"),
"handles": version["data"].get("handles"),
"fps": version["data"].get("fps"),
"author": version["data"].get("author"),
"outputDir": version["data"].get("outputDir"),
})
# Update the imprinted representation
update_container(
node,
updated_dict
)
node["file"].setValue(root)
# change color of node
if version.get("name") not in [max_version]:
node["tile_color"].setValue(int("0xd84f20ff", 16))
else:
node["tile_color"].setValue(int("0xff0ff0ff", 16))
self.log.info("udated to version: {}".format(version.get("name")))
def remove(self, container):
from avalon.nuke import viewer_update_and_undo_stop
node = nuke.toNode(container['objectName'])
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -0,0 +1,346 @@
import nuke
import contextlib
from avalon import api, io
from openpype.hosts.nuke.api.lib import (
get_imageio_input_colorspace
)
@contextlib.contextmanager
def preserve_trim(node):
"""Preserve the relative trim of the Loader tool.
This tries to preserve the loader's trim (trim in and trim out) after
the context by reapplying the "amount" it trims on the clip's length at
start and end.
"""
# working script frame range
script_start = nuke.root()["first_frame"].value()
start_at_frame = None
offset_frame = None
if node['frame_mode'].value() == "start at":
start_at_frame = node['frame'].value()
if node['frame_mode'].value() == "offset":
offset_frame = node['frame'].value()
try:
yield
finally:
if start_at_frame:
node['frame_mode'].setValue("start at")
node['frame'].setValue(str(script_start))
print("start frame of Read was set to"
"{}".format(script_start))
if offset_frame:
node['frame_mode'].setValue("offset")
node['frame'].setValue(str((script_start + offset_frame)))
print("start frame of Read was set to"
"{}".format(script_start))
def loader_shift(node, frame, relative=False):
"""Shift global in time by i preserving duration
This moves the loader by i frames preserving global duration. When relative
is False it will shift the global in to the start frame.
Args:
loader (tool): The fusion loader tool.
frame (int): The amount of frames to move.
relative (bool): When True the shift is relative, else the shift will
change the global in to frame.
Returns:
int: The resulting relative frame change (how much it moved)
"""
# working script frame range
script_start = nuke.root()["first_frame"].value()
if relative:
node['frame_mode'].setValue("start at")
node['frame'].setValue(str(script_start))
else:
node['frame_mode'].setValue("start at")
node['frame'].setValue(str(frame))
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"
node_name_template = "{class_name}_{ext}"
def load(self, context, name, namespace, data):
from avalon.nuke import (
containerise,
viewer_update_and_undo_stop
)
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"]
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
with viewer_update_and_undo_stop():
# TODO: it might be universal read to img/geo/camera
r = nuke.createNode(
"Read",
"name {}".format(read_name))
r["file"].setValue(file)
# Set colorspace defined in version data
colorspace = context["version"]["data"].get("colorspace")
if colorspace:
r["colorspace"].setValue(str(colorspace))
preset_clrsp = get_imageio_input_colorspace(file)
if preset_clrsp is not None:
r["colorspace"].setValue(preset_clrsp)
loader_shift(r, first, relative=True)
r["origfirst"].setValue(int(first))
r["first"].setValue(int(first))
r["origlast"].setValue(int(last))
r["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})
r["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(r, speed, time_warp_nodes)
return containerise(r,
name=name,
namespace=namespace,
context=context,
loader=self.__class__.__name__,
data=data_imprint)
def make_retimes(self, node, 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.handle_start + self.first_frame
)
if time_warp_nodes != []:
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(
(self.first_frame + i) + value,
(self.first_frame + i))
else:
# if static value `int`
twn["lookup"].setValue(timewarp["lookup"])
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
)
node = nuke.toNode(container['objectName'])
assert node.Class() == "Read", "Must be Read"
repr_cont = representation["context"]
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(node['name'].value(), representation))
first = 0
first -= self.handle_start
last += self.handle_end
# Update the loader's path whilst preserving some values
with preserve_trim(node):
node["file"].setValue(file)
self.log.info("__ node['file']: {}".format(node["file"].value()))
# Set the global in to the start frame of the sequence
loader_shift(node, first, relative=True)
node["origfirst"].setValue(int(first))
node["first"].setValue(int(first))
node["origlast"].setValue(int(last))
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 node
if version.get("name") not in [max_version]:
node["tile_color"].setValue(int("0xd84f20ff", 16))
else:
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(node, speed, time_warp_nodes)
# Update the imprinted representation
update_container(
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
node = nuke.toNode(container['objectName'])
assert node.Class() == "Read", "Must be Read"
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -0,0 +1,87 @@
import pyblish.api
from openpype.hosts.nuke.api import lib as pnlib
import nuke
@pyblish.api.log
class CollectBackdrops(pyblish.api.InstancePlugin):
"""Collect Backdrop node instance and its content
"""
order = pyblish.api.CollectorOrder + 0.22
label = "Collect Backdrop"
hosts = ["nuke"]
families = ["nukenodes"]
def process(self, instance):
bckn = instance[0]
# define size of the backdrop
left = bckn.xpos()
top = bckn.ypos()
right = left + bckn['bdwidth'].value()
bottom = top + bckn['bdheight'].value()
# iterate all nodes
for node in nuke.allNodes():
# exclude viewer
if node.Class() == "Viewer":
continue
# find all related nodes
if (node.xpos() > left) \
and (node.xpos() + node.screenWidth() < right) \
and (node.ypos() > top) \
and (node.ypos() + node.screenHeight() < bottom):
# add contained nodes to instance's node list
instance.append(node)
# get all connections from outside of backdrop
nodes = instance[1:]
connections_in, connections_out = pnlib.get_dependent_nodes(nodes)
instance.data["nodeConnectionsIn"] = connections_in
instance.data["nodeConnectionsOut"] = connections_out
# make label nicer
instance.data["label"] = "{0} ({1} nodes)".format(
bckn.name(), len(instance) - 1)
instance.data["families"].append(instance.data["family"])
# Get frame range
handle_start = instance.context.data["handleStart"]
handle_end = instance.context.data["handleEnd"]
first_frame = int(nuke.root()["first_frame"].getValue())
last_frame = int(nuke.root()["last_frame"].getValue())
# get version
version = instance.context.data.get('version')
if not version:
raise RuntimeError("Script name has no version in the name.")
instance.data['version'] = version
# Add version data to instance
version_data = {
"handles": handle_start,
"handleStart": handle_start,
"handleEnd": handle_end,
"frameStart": first_frame + handle_start,
"frameEnd": last_frame - handle_end,
"version": int(version),
"families": [instance.data["family"]] + instance.data["families"],
"subset": instance.data["subset"],
"fps": instance.context.data["fps"]
}
instance.data.update({
"versionData": version_data,
"frameStart": first_frame,
"frameEnd": last_frame
})
self.log.info("Backdrop content collected: `{}`".format(instance[:]))
self.log.info("Backdrop instance collected: `{}`".format(instance))

View file

@ -0,0 +1,17 @@
import nuke
import pyblish.api
class CollectFramerate(pyblish.api.ContextPlugin):
"""Collect framerate."""
order = pyblish.api.CollectorOrder
label = "Collect Framerate"
hosts = [
"nuke",
"nukeassist"
]
def process(self, context):
context.data["fps"] = nuke.root()["fps"].getValue()

View file

@ -0,0 +1,50 @@
import pyblish.api
import nuke
@pyblish.api.log
class CollectGizmo(pyblish.api.InstancePlugin):
"""Collect Gizmo (group) node instance and its content
"""
order = pyblish.api.CollectorOrder + 0.22
label = "Collect Gizmo (Group)"
hosts = ["nuke"]
families = ["gizmo"]
def process(self, instance):
grpn = instance[0]
# add family to familiess
instance.data["families"].insert(0, instance.data["family"])
# make label nicer
instance.data["label"] = "{0} ({1} nodes)".format(
grpn.name(), len(instance) - 1)
# Get frame range
handle_start = instance.context.data["handleStart"]
handle_end = instance.context.data["handleEnd"]
first_frame = int(nuke.root()["first_frame"].getValue())
last_frame = int(nuke.root()["last_frame"].getValue())
# Add version data to instance
version_data = {
"handles": handle_start,
"handleStart": handle_start,
"handleEnd": handle_end,
"frameStart": first_frame + handle_start,
"frameEnd": last_frame - handle_end,
"colorspace": nuke.root().knob('workingSpaceLUT').value(),
"families": [instance.data["family"]] + instance.data["families"],
"subset": instance.data["subset"],
"fps": instance.context.data["fps"]
}
instance.data.update({
"versionData": version_data,
"frameStart": first_frame,
"frameEnd": last_frame
})
self.log.info("Gizmo content collected: `{}`".format(instance[:]))
self.log.info("Gizmo instance collected: `{}`".format(instance))

View file

@ -0,0 +1,132 @@
import os
import re
import nuke
import pyblish.api
from avalon import io, api
@pyblish.api.log
class CollectNukeReads(pyblish.api.InstancePlugin):
"""Collect all read nodes."""
order = pyblish.api.CollectorOrder + 0.04
label = "Collect Source Reads"
hosts = ["nuke", "nukeassist"]
families = ["source"]
def process(self, instance):
asset_data = io.find_one({"type": "asset",
"name": api.Session["AVALON_ASSET"]})
self.log.debug("asset_data: {}".format(asset_data["data"]))
self.log.debug("checking instance: {}".format(instance))
node = instance[0]
if node.Class() != "Read":
return
file_path = node["file"].value()
file_name = os.path.basename(file_path)
items = file_name.split(".")
if len(items) < 2:
raise ValueError
ext = items[-1]
# Get frame range
handle_start = instance.context.data["handleStart"]
handle_end = instance.context.data["handleEnd"]
first_frame = node['first'].value()
last_frame = node['last'].value()
# colorspace
colorspace = node["colorspace"].value()
if "default" in colorspace:
colorspace = colorspace.replace("default (", "").replace(")", "")
# # Easier way to sequence - Not tested
# isSequence = True
# if first_frame == last_frame:
# isSequence = False
isSequence = False
if len(items) > 1:
sequence = items[-2]
hash_regex = re.compile(r'([#*])')
seq_regex = re.compile(r'[%0-9*d]')
hash_match = re.match(hash_regex, sequence)
seq_match = re.match(seq_regex, sequence)
if hash_match or seq_match:
isSequence = True
# get source path
path = nuke.filename(node)
source_dir = os.path.dirname(path)
self.log.debug('source dir: {}'.format(source_dir))
if isSequence:
source_files = [f for f in os.listdir(source_dir)
if ext in f
if items[0] in f]
else:
source_files = file_name
# Include start and end render frame in label
name = node.name()
label = "{0} ({1}-{2})".format(
name,
int(first_frame),
int(last_frame)
)
self.log.debug("collected_frames: {}".format(label))
if "representations" not in instance.data:
instance.data["representations"] = []
representation = {
'name': ext,
'ext': ext,
'files': source_files,
"stagingDir": source_dir,
"frameStart": "%0{}d".format(
len(str(last_frame))) % first_frame
}
instance.data["representations"].append(representation)
transfer = False
if "publish" in node.knobs():
transfer = node["publish"]
instance.data['transfer'] = transfer
# Add version data to instance
version_data = {
"handles": handle_start,
"handleStart": handle_start,
"handleEnd": handle_end,
"frameStart": first_frame + handle_start,
"frameEnd": last_frame - handle_end,
"colorspace": colorspace,
"families": [instance.data["family"]],
"subset": instance.data["subset"],
"fps": instance.context.data["fps"]
}
instance.data.update({
"versionData": version_data,
"path": path,
"stagingDir": source_dir,
"ext": ext,
"label": label,
"frameStart": first_frame,
"frameEnd": last_frame,
"colorspace": colorspace,
"handles": int(asset_data["data"].get("handles", 0)),
"step": 1,
"fps": int(nuke.root()['fps'].value())
})
self.log.debug("instance.data: {}".format(instance.data))

View file

@ -0,0 +1,40 @@
import pyblish.api
import nuke
class CollectSlate(pyblish.api.InstancePlugin):
"""Check if SLATE node is in scene and connected to rendering tree"""
order = pyblish.api.CollectorOrder + 0.09
label = "Collect Slate Node"
hosts = ["nuke"]
families = ["render", "render.local", "render.farm"]
def process(self, instance):
node = instance[0]
slate = next((n for n in nuke.allNodes()
if "slate" in n.name().lower()
if not n["disable"].getValue()),
None)
if slate:
# check if slate node is connected to write node tree
slate_check = 0
slate_node = None
while slate_check == 0:
try:
node = node.dependencies()[0]
if slate.name() in node.name():
slate_node = node
slate_check = 1
except IndexError:
break
if slate_node:
instance.data["slateNode"] = slate_node
instance.data["families"].append("slate")
self.log.info(
"Slate node is in node graph: `{}`".format(slate.name()))
self.log.debug(
"__ instance: `{}`".format(instance))

View file

@ -0,0 +1,103 @@
import pyblish.api
from avalon.nuke import lib as anlib
from openpype.hosts.nuke.api import lib as pnlib
import nuke
import os
import openpype
reload(pnlib)
class ExtractBackdropNode(openpype.api.Extractor):
"""Extracting content of backdrop nodes
Will create nuke script only with containing nodes.
Also it will solve Input and Output nodes.
"""
order = pyblish.api.ExtractorOrder
label = "Extract Backdrop"
hosts = ["nuke"]
families = ["nukenodes"]
def process(self, instance):
tmp_nodes = list()
nodes = instance[1:]
# Define extract output file path
stagingdir = self.staging_dir(instance)
filename = "{0}.nk".format(instance.name)
path = os.path.join(stagingdir, filename)
# maintain selection
with anlib.maintained_selection():
# all connections outside of backdrop
connections_in = instance.data["nodeConnectionsIn"]
connections_out = instance.data["nodeConnectionsOut"]
self.log.debug("_ connections_in: `{}`".format(connections_in))
self.log.debug("_ connections_out: `{}`".format(connections_out))
# create input nodes and name them as passing node (*_INP)
for n, inputs in connections_in.items():
for i, input in inputs:
inpn = nuke.createNode("Input")
inpn["name"].setValue("{}_{}_INP".format(n.name(), i))
n.setInput(i, inpn)
inpn.setXYpos(input.xpos(), input.ypos())
nodes.append(inpn)
tmp_nodes.append(inpn)
anlib.reset_selection()
# connect output node
for n, output in connections_out.items():
opn = nuke.createNode("Output")
self.log.info(n.name())
self.log.info(output.name())
output.setInput(
next((i for i, d in enumerate(output.dependencies())
if d.name() in n.name()), 0), opn)
opn.setInput(0, n)
opn.autoplace()
nodes.append(opn)
tmp_nodes.append(opn)
anlib.reset_selection()
# select nodes to copy
anlib.reset_selection()
anlib.select_nodes(nodes)
# create tmp nk file
# save file to the path
nuke.nodeCopy(path)
# Clean up
for tn in tmp_nodes:
nuke.delete(tn)
# restore original connections
# reconnect input node
for n, inputs in connections_in.items():
for i, input in inputs:
n.setInput(i, input)
# reconnect output node
for n, output in connections_out.items():
output.setInput(
next((i for i, d in enumerate(output.dependencies())
if d.name() in n.name()), 0), n)
if "representations" not in instance.data:
instance.data["representations"] = []
# create representation
representation = {
'name': 'nk',
'ext': 'nk',
'files': filename,
"stagingDir": stagingdir
}
instance.data["representations"].append(representation)
self.log.info("Extracted instance '{}' to: {}".format(
instance.name, path))
self.log.info("Data {}".format(
instance.data))

View file

@ -0,0 +1,185 @@
import nuke
import os
import math
import pyblish.api
import openpype.api
from avalon.nuke import lib as anlib
from pprint import pformat
class ExtractCamera(openpype.api.Extractor):
""" 3D camera exctractor
"""
label = 'Exctract Camera'
order = pyblish.api.ExtractorOrder
families = ["camera"]
hosts = ["nuke"]
# presets
write_geo_knobs = [
("file_type", "abc"),
("storageFormat", "Ogawa"),
("writeGeometries", False),
("writePointClouds", False),
("writeAxes", False)
]
def process(self, instance):
handle_start = instance.context.data["handleStart"]
handle_end = instance.context.data["handleEnd"]
first_frame = int(nuke.root()["first_frame"].getValue())
last_frame = int(nuke.root()["last_frame"].getValue())
step = 1
output_range = str(nuke.FrameRange(first_frame, last_frame, step))
self.log.info("instance.data: `{}`".format(
pformat(instance.data)))
rm_nodes = list()
self.log.info("Crating additional nodes")
subset = instance.data["subset"]
staging_dir = self.staging_dir(instance)
# get extension form preset
extension = next((k[1] for k in self.write_geo_knobs
if k[0] == "file_type"), None)
if not extension:
raise RuntimeError(
"Bad config for extension in presets. "
"Talk to your supervisor or pipeline admin")
# create file name and path
filename = subset + ".{}".format(extension)
file_path = os.path.join(staging_dir, filename).replace("\\", "/")
with anlib.maintained_selection():
# bake camera with axeses onto word coordinate XYZ
rm_n = bakeCameraWithAxeses(
nuke.toNode(instance.data["name"]), output_range)
rm_nodes.append(rm_n)
# create scene node
rm_n = nuke.createNode("Scene")
rm_nodes.append(rm_n)
# create write geo node
wg_n = nuke.createNode("WriteGeo")
wg_n["file"].setValue(file_path)
# add path to write to
for k, v in self.write_geo_knobs:
wg_n[k].setValue(v)
rm_nodes.append(wg_n)
# write out camera
nuke.execute(
wg_n,
int(first_frame),
int(last_frame)
)
# erase additional nodes
for n in rm_nodes:
nuke.delete(n)
self.log.info(file_path)
# create representation data
if "representations" not in instance.data:
instance.data["representations"] = []
representation = {
'name': extension,
'ext': extension,
'files': filename,
"stagingDir": staging_dir,
"frameStart": first_frame,
"frameEnd": last_frame
}
instance.data["representations"].append(representation)
instance.data.update({
"path": file_path,
"outputDir": staging_dir,
"ext": extension,
"handleStart": handle_start,
"handleEnd": handle_end,
"frameStart": first_frame + handle_start,
"frameEnd": last_frame - handle_end,
"frameStartHandle": first_frame,
"frameEndHandle": last_frame,
})
self.log.info("Extracted instance '{0}' to: {1}".format(
instance.name, file_path))
def bakeCameraWithAxeses(camera_node, output_range):
""" Baking all perent hiearchy of axeses into camera
with transposition onto word XYZ coordinance
"""
bakeFocal = False
bakeHaperture = False
bakeVaperture = False
camera_matrix = camera_node['world_matrix']
new_cam_n = nuke.createNode("Camera2")
new_cam_n.setInput(0, None)
new_cam_n['rotate'].setAnimated()
new_cam_n['translate'].setAnimated()
old_focal = camera_node['focal']
if old_focal.isAnimated() and not (old_focal.animation(0).constant()):
new_cam_n['focal'].setAnimated()
bakeFocal = True
else:
new_cam_n['focal'].setValue(old_focal.value())
old_haperture = camera_node['haperture']
if old_haperture.isAnimated() and not (
old_haperture.animation(0).constant()):
new_cam_n['haperture'].setAnimated()
bakeHaperture = True
else:
new_cam_n['haperture'].setValue(old_haperture.value())
old_vaperture = camera_node['vaperture']
if old_vaperture.isAnimated() and not (
old_vaperture.animation(0).constant()):
new_cam_n['vaperture'].setAnimated()
bakeVaperture = True
else:
new_cam_n['vaperture'].setValue(old_vaperture.value())
new_cam_n['win_translate'].setValue(camera_node['win_translate'].value())
new_cam_n['win_scale'].setValue(camera_node['win_scale'].value())
for x in nuke.FrameRange(output_range):
math_matrix = nuke.math.Matrix4()
for y in range(camera_matrix.height()):
for z in range(camera_matrix.width()):
matrix_pointer = z + (y * camera_matrix.width())
math_matrix[matrix_pointer] = camera_matrix.getValueAt(
x, (y + (z * camera_matrix.width())))
rot_matrix = nuke.math.Matrix4(math_matrix)
rot_matrix.rotationOnly()
rot = rot_matrix.rotationsZXY()
new_cam_n['rotate'].setValueAt(math.degrees(rot[0]), x, 0)
new_cam_n['rotate'].setValueAt(math.degrees(rot[1]), x, 1)
new_cam_n['rotate'].setValueAt(math.degrees(rot[2]), x, 2)
new_cam_n['translate'].setValueAt(
camera_matrix.getValueAt(x, 3), x, 0)
new_cam_n['translate'].setValueAt(
camera_matrix.getValueAt(x, 7), x, 1)
new_cam_n['translate'].setValueAt(
camera_matrix.getValueAt(x, 11), x, 2)
if bakeFocal:
new_cam_n['focal'].setValueAt(old_focal.getValueAt(x), x)
if bakeHaperture:
new_cam_n['haperture'].setValueAt(old_haperture.getValueAt(x), x)
if bakeVaperture:
new_cam_n['vaperture'].setValueAt(old_vaperture.getValueAt(x), x)
return new_cam_n

View file

@ -0,0 +1,94 @@
import pyblish.api
from avalon.nuke import lib as anlib
from openpype.hosts.nuke.api import utils as pnutils
import nuke
import os
import openpype
class ExtractGizmo(openpype.api.Extractor):
"""Extracting Gizmo (Group) node
Will create nuke script only with the Gizmo node.
"""
order = pyblish.api.ExtractorOrder
label = "Extract Gizmo (Group)"
hosts = ["nuke"]
families = ["gizmo"]
def process(self, instance):
tmp_nodes = list()
orig_grpn = instance[0]
# Define extract output file path
stagingdir = self.staging_dir(instance)
filename = "{0}.nk".format(instance.name)
path = os.path.join(stagingdir, filename)
# maintain selection
with anlib.maintained_selection():
orig_grpn_name = orig_grpn.name()
tmp_grpn_name = orig_grpn_name + "_tmp"
# select original group node
anlib.select_nodes([orig_grpn])
# copy to clipboard
nuke.nodeCopy("%clipboard%")
# reset selection to none
anlib.reset_selection()
# paste clipboard
nuke.nodePaste("%clipboard%")
# assign pasted node
copy_grpn = nuke.selectedNode()
copy_grpn.setXYpos((orig_grpn.xpos() + 120), orig_grpn.ypos())
# convert gizmos to groups
pnutils.bake_gizmos_recursively(copy_grpn)
# remove avalonknobs
knobs = copy_grpn.knobs()
avalon_knobs = [k for k in knobs.keys()
for ak in ["avalon:", "ak:"]
if ak in k]
avalon_knobs.append("publish")
for ak in avalon_knobs:
copy_grpn.removeKnob(knobs[ak])
# add to temporary nodes
tmp_nodes.append(copy_grpn)
# swap names
orig_grpn.setName(tmp_grpn_name)
copy_grpn.setName(orig_grpn_name)
# create tmp nk file
# save file to the path
nuke.nodeCopy(path)
# Clean up
for tn in tmp_nodes:
nuke.delete(tn)
# rename back to original
orig_grpn.setName(orig_grpn_name)
if "representations" not in instance.data:
instance.data["representations"] = []
# create representation
representation = {
'name': 'gizmo',
'ext': 'nk',
'files': filename,
"stagingDir": stagingdir
}
instance.data["representations"].append(representation)
self.log.info("Extracted instance '{}' to: {}".format(
instance.name, path))
self.log.info("Data {}".format(
instance.data))

View file

@ -0,0 +1,38 @@
import nuke
import pyblish.api
from avalon.nuke import maintained_selection
class CreateOutputNode(pyblish.api.ContextPlugin):
"""Adding output node for each ouput write node
So when latly user will want to Load .nk as LifeGroup or Precomp
Nuke will not complain about missing Output node
"""
label = 'Output Node Create'
order = pyblish.api.ExtractorOrder + 0.4
families = ["workfile"]
hosts = ['nuke']
def process(self, context):
# capture selection state
with maintained_selection():
active_node = [node for inst in context[:]
for node in inst[:]
if "ak:family" in node.knobs()]
if active_node:
self.log.info(active_node)
active_node = active_node[0]
self.log.info(active_node)
active_node['selected'].setValue(True)
# select only instance render node
output_node = nuke.createNode("Output")
# deselect all and select the original selection
output_node['selected'].setValue(False)
# save script
nuke.scriptSave()
# add node to instance node list
context.data["outputNode"] = output_node

View file

@ -0,0 +1,26 @@
import os
import pyblish.api
class ExtractOutputDirectory(pyblish.api.InstancePlugin):
"""Extracts the output path for any collection or single output_path."""
order = pyblish.api.ExtractorOrder - 0.05
label = "Output Directory"
optional = True
# targets = ["process"]
def process(self, instance):
path = None
if "output_path" in instance.data.keys():
path = instance.data["path"]
if not path:
return
if not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path))

View file

@ -0,0 +1,103 @@
import pyblish.api
import nuke
import os
import openpype
import clique
class NukeRenderLocal(openpype.api.Extractor):
# TODO: rewrite docstring to nuke
"""Render the current Fusion composition locally.
Extract the result of savers by starting a comp render
This will run the local render of Fusion.
"""
order = pyblish.api.ExtractorOrder
label = "Render Local"
hosts = ["nuke"]
families = ["render.local", "prerender.local"]
def process(self, instance):
families = instance.data["families"]
node = None
for x in instance:
if x.Class() == "Write":
node = x
self.log.debug("instance collected: {}".format(instance.data))
first_frame = instance.data.get("frameStartHandle", None)
# exception for slate workflow
if "slate" in families:
first_frame -= 1
last_frame = instance.data.get("frameEndHandle", None)
node_subset_name = instance.data.get("name", None)
self.log.info("Starting render")
self.log.info("Start frame: {}".format(first_frame))
self.log.info("End frame: {}".format(last_frame))
# Ensure output directory exists.
directory = os.path.dirname(node["file"].value())
if not os.path.exists(directory):
os.makedirs(directory)
# Render frames
nuke.execute(
node_subset_name,
int(first_frame),
int(last_frame)
)
# exception for slate workflow
if "slate" in families:
first_frame += 1
path = node['file'].value()
out_dir = os.path.dirname(path)
ext = node["file_type"].value()
if "representations" not in instance.data:
instance.data["representations"] = []
collected_frames = os.listdir(out_dir)
repre = {
'name': ext,
'ext': ext,
'frameStart': "%0{}d".format(len(str(last_frame))) % first_frame,
'files': collected_frames,
"stagingDir": out_dir
}
instance.data["representations"].append(repre)
self.log.info("Extracted instance '{0}' to: {1}".format(
instance.name,
out_dir
))
# redefinition of families
if "render.local" in families:
instance.data['family'] = 'render'
families.remove('render.local')
families.insert(0, "render2d")
elif "prerender.local" in families:
instance.data['family'] = 'prerender'
families.remove('prerender.local')
families.insert(0, "prerender")
instance.data["families"] = families
collections, remainder = clique.assemble(collected_frames)
self.log.info('collections: {}'.format(str(collections)))
if collections:
collection = collections[0]
instance.data['collection'] = collection
self.log.info('Finished render')
self.log.debug("instance extracted: {}".format(instance.data))

View file

@ -0,0 +1,59 @@
import os
import pyblish.api
from avalon.nuke import lib as anlib
from openpype.hosts.nuke.api import lib as pnlib
import openpype
reload(pnlib)
class ExtractReviewDataLut(openpype.api.Extractor):
"""Extracts movie and thumbnail with baked in luts
must be run after extract_render_local.py
"""
order = pyblish.api.ExtractorOrder + 0.005
label = "Extract Review Data Lut"
families = ["review"]
hosts = ["nuke"]
def process(self, instance):
families = instance.data["families"]
self.log.info("Creating staging dir...")
if "representations" in instance.data:
staging_dir = instance.data[
"representations"][0]["stagingDir"].replace("\\", "/")
instance.data["stagingDir"] = staging_dir
instance.data["representations"][0]["tags"] = ["review"]
else:
instance.data["representations"] = []
# get output path
render_path = instance.data['path']
staging_dir = os.path.normpath(os.path.dirname(render_path))
instance.data["stagingDir"] = staging_dir
self.log.info(
"StagingDir `{0}`...".format(instance.data["stagingDir"]))
# generate data
with anlib.maintained_selection():
exporter = pnlib.ExporterReviewLut(
self, instance
)
data = exporter.generate_lut()
# assign to representations
instance.data["lutPath"] = os.path.join(
exporter.stagingDir, exporter.file).replace("\\", "/")
instance.data["representations"] += data["representations"]
if "render.farm" in families:
instance.data["families"].remove("review")
instance.data["families"].remove("ftrack")
self.log.debug(
"_ lutPath: {}".format(instance.data["lutPath"]))
self.log.debug(
"_ representations: {}".format(instance.data["representations"]))

View file

@ -0,0 +1,66 @@
import os
import pyblish.api
from avalon.nuke import lib as anlib
from openpype.hosts.nuke.api import lib as pnlib
import openpype
class ExtractReviewDataMov(openpype.api.Extractor):
"""Extracts movie and thumbnail with baked in luts
must be run after extract_render_local.py
"""
order = pyblish.api.ExtractorOrder + 0.01
label = "Extract Review Data Mov"
families = ["review"]
hosts = ["nuke"]
# presets
viewer_lut_raw = None
bake_colorspace_fallback = None
bake_colorspace_main = None
def process(self, instance):
families = instance.data["families"]
self.log.info("Creating staging dir...")
if "representations" not in instance.data:
instance.data["representations"] = list()
staging_dir = os.path.normpath(
os.path.dirname(instance.data['path']))
instance.data["stagingDir"] = staging_dir
self.log.info(
"StagingDir `{0}`...".format(instance.data["stagingDir"]))
# generate data
with anlib.maintained_selection():
exporter = pnlib.ExporterReviewMov(
self, instance)
if "render.farm" in families:
instance.data["families"].remove("review")
instance.data["families"].remove("ftrack")
data = exporter.generate_mov(farm=True)
self.log.debug(
"_ data: {}".format(data))
instance.data.update({
"bakeRenderPath": data.get("bakeRenderPath"),
"bakeScriptPath": data.get("bakeScriptPath"),
"bakeWriteNodeName": data.get("bakeWriteNodeName")
})
else:
data = exporter.generate_mov()
# assign to representations
instance.data["representations"] += data["representations"]
self.log.debug(
"_ representations: {}".format(instance.data["representations"]))

View file

@ -0,0 +1,15 @@
import nuke
import pyblish.api
class ExtractScriptSave(pyblish.api.Extractor):
"""
"""
label = 'Script Save'
order = pyblish.api.Extractor.order - 0.1
hosts = ['nuke']
def process(self, instance):
self.log.info('saving script')
nuke.scriptSave()

View file

@ -0,0 +1,169 @@
import os
import nuke
from avalon.nuke import lib as anlib
import pyblish.api
import openpype
class ExtractSlateFrame(openpype.api.Extractor):
"""Extracts movie and thumbnail with baked in luts
must be run after extract_render_local.py
"""
order = pyblish.api.ExtractorOrder - 0.001
label = "Extract Slate Frame"
families = ["slate"]
hosts = ["nuke"]
def process(self, instance):
if hasattr(self, "viewer_lut_raw"):
self.viewer_lut_raw = self.viewer_lut_raw
else:
self.viewer_lut_raw = False
with anlib.maintained_selection():
self.log.debug("instance: {}".format(instance))
self.log.debug("instance.data[families]: {}".format(
instance.data["families"]))
self.render_slate(instance)
def render_slate(self, instance):
node_subset_name = instance.data.get("name", None)
node = instance[0] # group node
self.log.info("Creating staging dir...")
if "representations" not in instance.data:
instance.data["representations"] = list()
staging_dir = os.path.normpath(
os.path.dirname(instance.data['path']))
instance.data["stagingDir"] = staging_dir
self.log.info(
"StagingDir `{0}`...".format(instance.data["stagingDir"]))
frame_length = int(
instance.data["frameEnd"] - instance.data["frameStart"] + 1
)
temporary_nodes = []
collection = instance.data.get("collection", None)
if collection:
# get path
fname = os.path.basename(collection.format(
"{head}{padding}{tail}"))
fhead = collection.format("{head}")
collected_frames_len = int(len(collection.indexes))
# get first and last frame
first_frame = min(collection.indexes) - 1
self.log.info('frame_length: {}'.format(frame_length))
self.log.info(
'len(collection.indexes): {}'.format(collected_frames_len)
)
if ("slate" in instance.data["families"]) \
and (frame_length != collected_frames_len):
first_frame += 1
last_frame = first_frame
else:
fname = os.path.basename(instance.data.get("path", None))
fhead = os.path.splitext(fname)[0] + "."
first_frame = instance.data.get("frameStartHandle", None) - 1
last_frame = first_frame
if "#" in fhead:
fhead = fhead.replace("#", "")[:-1]
previous_node = node
# get input process and connect it to baking
ipn = self.get_view_process_node()
if ipn is not None:
ipn.setInput(0, previous_node)
previous_node = ipn
temporary_nodes.append(ipn)
if not self.viewer_lut_raw:
dag_node = nuke.createNode("OCIODisplay")
dag_node.setInput(0, previous_node)
previous_node = dag_node
temporary_nodes.append(dag_node)
# create write node
write_node = nuke.createNode("Write")
file = fhead + "slate.png"
path = os.path.join(staging_dir, file).replace("\\", "/")
instance.data["slateFrame"] = path
write_node["file"].setValue(path)
write_node["file_type"].setValue("png")
write_node["raw"].setValue(1)
write_node.setInput(0, previous_node)
temporary_nodes.append(write_node)
# fill slate node with comments
self.add_comment_slate_node(instance)
# Render frames
nuke.execute(write_node.name(), int(first_frame), int(last_frame))
# also render slate as sequence frame
nuke.execute(node_subset_name, int(first_frame), int(last_frame))
self.log.debug(
"slate frame path: {}".format(instance.data["slateFrame"]))
# Clean up
for node in temporary_nodes:
nuke.delete(node)
def get_view_process_node(self):
# Select only the target node
if nuke.selectedNodes():
[n.setSelected(False) for n in nuke.selectedNodes()]
ipn_orig = None
for v in [n for n in nuke.allNodes()
if "Viewer" in n.Class()]:
ip = v['input_process'].getValue()
ipn = v['input_process_node'].getValue()
if "VIEWER_INPUT" not in ipn and ip:
ipn_orig = nuke.toNode(ipn)
ipn_orig.setSelected(True)
if ipn_orig:
nuke.nodeCopy('%clipboard%')
[n.setSelected(False) for n in nuke.selectedNodes()] # Deselect all
nuke.nodePaste('%clipboard%')
ipn = nuke.selectedNode()
return ipn
def add_comment_slate_node(self, instance):
node = instance.data.get("slateNode")
if not node:
return
comment = instance.context.data.get("comment")
intent_value = instance.context.data.get("intent")
if intent_value and isinstance(intent_value, dict):
intent_value = intent_value.get("value")
try:
node["f_submission_note"].setValue(comment)
node["f_submitting_for"].setValue(intent_value or "")
except NameError:
return
instance.data.pop("slateNode")

View file

@ -0,0 +1,177 @@
import os
import nuke
from avalon.nuke import lib as anlib
import pyblish.api
import openpype
class ExtractThumbnail(openpype.api.Extractor):
"""Extracts movie and thumbnail with baked in luts
must be run after extract_render_local.py
"""
order = pyblish.api.ExtractorOrder + 0.01
label = "Extract Thumbnail"
families = ["review"]
hosts = ["nuke"]
# presets
nodes = {}
def process(self, instance):
if "render.farm" in instance.data["families"]:
return
with anlib.maintained_selection():
self.log.debug("instance: {}".format(instance))
self.log.debug("instance.data[families]: {}".format(
instance.data["families"]))
self.render_thumbnail(instance)
def render_thumbnail(self, instance):
node = instance[0] # group node
self.log.info("Creating staging dir...")
if "representations" not in instance.data:
instance.data["representations"] = list()
staging_dir = os.path.normpath(
os.path.dirname(instance.data['path']))
instance.data["stagingDir"] = staging_dir
self.log.info(
"StagingDir `{0}`...".format(instance.data["stagingDir"]))
temporary_nodes = []
collection = instance.data.get("collection", None)
if collection:
# get path
fname = os.path.basename(collection.format(
"{head}{padding}{tail}"))
fhead = collection.format("{head}")
# get first and last frame
first_frame = min(collection.indexes)
last_frame = max(collection.indexes)
else:
fname = os.path.basename(instance.data.get("path", None))
fhead = os.path.splitext(fname)[0] + "."
first_frame = instance.data.get("frameStart", None)
last_frame = instance.data.get("frameEnd", None)
if "#" in fhead:
fhead = fhead.replace("#", "")[:-1]
path_render = os.path.join(staging_dir, fname).replace("\\", "/")
# check if file exist otherwise connect to write node
if os.path.isfile(path_render):
rnode = nuke.createNode("Read")
rnode["file"].setValue(path_render)
rnode["first"].setValue(first_frame)
rnode["origfirst"].setValue(first_frame)
rnode["last"].setValue(last_frame)
rnode["origlast"].setValue(last_frame)
temporary_nodes.append(rnode)
previous_node = rnode
else:
previous_node = node
# get input process and connect it to baking
ipn = self.get_view_process_node()
if ipn is not None:
ipn.setInput(0, previous_node)
previous_node = ipn
temporary_nodes.append(ipn)
reformat_node = nuke.createNode("Reformat")
ref_node = self.nodes.get("Reformat", None)
if ref_node:
for k, v in ref_node:
self.log.debug("k, v: {0}:{1}".format(k, v))
if isinstance(v, unicode):
v = str(v)
reformat_node[k].setValue(v)
reformat_node.setInput(0, previous_node)
previous_node = reformat_node
temporary_nodes.append(reformat_node)
dag_node = nuke.createNode("OCIODisplay")
dag_node.setInput(0, previous_node)
previous_node = dag_node
temporary_nodes.append(dag_node)
# create write node
write_node = nuke.createNode("Write")
file = fhead + "jpeg"
name = "thumbnail"
path = os.path.join(staging_dir, file).replace("\\", "/")
instance.data["thumbnail"] = path
write_node["file"].setValue(path)
write_node["file_type"].setValue("jpeg")
write_node["raw"].setValue(1)
write_node.setInput(0, previous_node)
temporary_nodes.append(write_node)
tags = ["thumbnail", "publish_on_farm"]
# retime for
first_frame = int(last_frame) / 2
last_frame = int(last_frame) / 2
repre = {
'name': name,
'ext': "jpeg",
"outputName": "thumb",
'files': file,
"stagingDir": staging_dir,
"frameStart": first_frame,
"frameEnd": last_frame,
"tags": tags
}
instance.data["representations"].append(repre)
# Render frames
nuke.execute(write_node.name(), int(first_frame), int(last_frame))
self.log.debug(
"representations: {}".format(instance.data["representations"]))
# Clean up
for node in temporary_nodes:
nuke.delete(node)
def get_view_process_node(self):
# Select only the target node
if nuke.selectedNodes():
[n.setSelected(False) for n in nuke.selectedNodes()]
ipn_orig = None
for v in [n for n in nuke.allNodes()
if "Viewer" == n.Class()]:
ip = v['input_process'].getValue()
ipn = v['input_process_node'].getValue()
if "VIEWER_INPUT" not in ipn and ip:
ipn_orig = nuke.toNode(ipn)
ipn_orig.setSelected(True)
if ipn_orig:
nuke.nodeCopy('%clipboard%')
# Deselect all
[n.setSelected(False) for n in nuke.selectedNodes()]
nuke.nodePaste('%clipboard%')
ipn = nuke.selectedNode()
return ipn

View file

@ -0,0 +1,23 @@
import nuke
import pyblish.api
class IncrementScriptVersion(pyblish.api.ContextPlugin):
"""Increment current script version."""
order = pyblish.api.IntegratorOrder + 0.9
label = "Increment Script Version"
optional = True
families = ["workfile", "render", "render.local", "render.farm"]
hosts = ['nuke']
def process(self, context):
assert all(result["success"] for result in context.data["results"]), (
"Publishing not succesfull so version is not increased.")
from openpype.lib import version_up
path = context.data["currentFile"]
nuke.scriptSaveAs(version_up(path))
self.log.info('Incrementing script version')

View file

@ -0,0 +1,153 @@
import nuke
import pyblish.api
from avalon import io, api
from avalon.nuke import lib as anlib
@pyblish.api.log
class PreCollectNukeInstances(pyblish.api.ContextPlugin):
"""Collect all nodes with Avalon knob."""
order = pyblish.api.CollectorOrder - 0.59
label = "Pre-collect Instances"
hosts = ["nuke", "nukeassist"]
# presets
sync_workfile_version = False
def process(self, context):
asset_data = io.find_one({
"type": "asset",
"name": api.Session["AVALON_ASSET"]
})
self.log.debug("asset_data: {}".format(asset_data["data"]))
instances = []
root = nuke.root()
self.log.debug("nuke.allNodes(): {}".format(nuke.allNodes()))
for node in nuke.allNodes():
if node.Class() in ["Viewer", "Dot"]:
continue
try:
if node["disable"].value():
continue
except Exception as E:
self.log.warning(E)
# get data from avalon knob
avalon_knob_data = anlib.get_avalon_knob_data(
node, ["avalon:", "ak:"])
self.log.debug("avalon_knob_data: {}".format(avalon_knob_data))
if not avalon_knob_data:
continue
if avalon_knob_data["id"] != "pyblish.avalon.instance":
continue
# establish families
family = avalon_knob_data["family"]
families_ak = avalon_knob_data.get("families", [])
families = list()
if families_ak:
families.append(families_ak.lower())
families.append(family)
# except disabled nodes but exclude backdrops in test
if ("nukenodes" not in family) and (node["disable"].value()):
continue
subset = avalon_knob_data.get(
"subset", None) or node["name"].value()
# Create instance
instance = context.create_instance(subset)
instance.append(node)
# get review knob value
review = False
if "review" in node.knobs():
review = node["review"].value()
families.append("review")
families.append("ftrack")
# Add all nodes in group instances.
if node.Class() == "Group":
# check if it is write node in family
if "write" in families:
target = node["render"].value()
if target == "Use existing frames":
# Local rendering
self.log.info("flagged for no render")
families.append("render")
elif target == "Local":
# Local rendering
self.log.info("flagged for local render")
families.append("{}.local".format("render"))
elif target == "On farm":
# Farm rendering
self.log.info("flagged for farm render")
instance.data["transfer"] = False
families.append("{}.farm".format("render"))
if "render" in families:
families.remove("render")
family = "write"
node.begin()
for i in nuke.allNodes():
instance.append(i)
node.end()
self.log.debug("__ families: `{}`".format(families))
# Get format
format = root['format'].value()
resolution_width = format.width()
resolution_height = format.height()
pixel_aspect = format.pixelAspect()
# get publish knob value
if "publish" not in node.knobs():
anlib.add_publish_knob(node)
# sync workfile version
if not next((f for f in families
if "prerender" in f),
None) and self.sync_workfile_version:
# get version to instance for integration
instance.data['version'] = instance.context.data['version']
instance.data.update({
"subset": subset,
"asset": avalon_knob_data["asset"],
"label": node.name(),
"name": node.name(),
"subset": subset,
"family": family,
"families": families,
"avalonKnob": avalon_knob_data,
"step": 1,
"publish": node.knob('publish').value(),
"fps": nuke.root()['fps'].value(),
"resolutionWidth": resolution_width,
"resolutionHeight": resolution_height,
"pixelAspect": pixel_aspect,
"review": review
})
self.log.info("collected instance: {}".format(instance.data))
instances.append(instance)
# create instances in context data if not are created yet
if not context.data.get("instances"):
context.data["instances"] = list()
context.data["instances"].extend(instances)
self.log.debug("context: {}".format(context))

View file

@ -0,0 +1,103 @@
import nuke
import pyblish.api
import os
import openpype.api as pype
from avalon.nuke import lib as anlib
reload(anlib)
class CollectWorkfile(pyblish.api.ContextPlugin):
"""Collect current script for publish."""
order = pyblish.api.CollectorOrder - 0.60
label = "Pre-collect Workfile"
hosts = ['nuke']
def process(self, context):
root = nuke.root()
current_file = os.path.normpath(nuke.root().name())
knob_data = anlib.get_avalon_knob_data(root)
anlib.add_publish_knob(root)
family = "workfile"
task = os.getenv("AVALON_TASK", None)
# creating instances per write node
staging_dir = os.path.dirname(current_file)
base_name = os.path.basename(current_file)
subset = family + task.capitalize()
# Get frame range
first_frame = int(root["first_frame"].getValue())
last_frame = int(root["last_frame"].getValue())
handle_start = int(knob_data.get("handleStart", 0))
handle_end = int(knob_data.get("handleEnd", 0))
# Get format
format = root['format'].value()
resolution_width = format.width()
resolution_height = format.height()
pixel_aspect = format.pixelAspect()
# Create instance
instance = context.create_instance(subset)
instance.add(root)
script_data = {
"asset": os.getenv("AVALON_ASSET", None),
"frameStart": first_frame + handle_start,
"frameEnd": last_frame - handle_end,
"resolutionWidth": resolution_width,
"resolutionHeight": resolution_height,
"pixelAspect": pixel_aspect,
# backward compatibility
"handles": handle_start,
"handleStart": handle_start,
"handleEnd": handle_end,
"step": 1,
"fps": root['fps'].value(),
"currentFile": current_file,
"version": int(pype.get_version_from_path(current_file)),
"host": pyblish.api.current_host(),
"hostVersion": nuke.NUKE_VERSION_STRING
}
context.data.update(script_data)
# creating instance data
instance.data.update({
"subset": subset,
"label": base_name,
"name": base_name,
"publish": root.knob('publish').value(),
"family": family,
"families": [family],
"representations": list()
})
# adding basic script data
instance.data.update(script_data)
# creating representation
representation = {
'name': 'nk',
'ext': 'nk',
'files': base_name,
"stagingDir": staging_dir,
}
instance.data["representations"].append(representation)
self.log.info('Publishing script version')
# create instances in context data if not are created yet
if not context.data.get("instances"):
context.data["instances"] = list()
context.data["instances"].append(instance)

View file

@ -0,0 +1,169 @@
import os
import nuke
import pyblish.api
import openpype.api as pype
from avalon import io, api
@pyblish.api.log
class CollectNukeWrites(pyblish.api.InstancePlugin):
"""Collect all write nodes."""
order = pyblish.api.CollectorOrder - 0.58
label = "Pre-collect Writes"
hosts = ["nuke", "nukeassist"]
families = ["write"]
# preset attributes
sync_workfile_version = True
def process(self, instance):
families = instance.data["families"]
node = None
for x in instance:
if x.Class() == "Write":
node = x
if node is None:
return
self.log.debug("checking instance: {}".format(instance))
# Determine defined file type
ext = node["file_type"].value()
# Determine output type
output_type = "img"
if ext == "mov":
output_type = "mov"
# Get frame range
handle_start = instance.context.data["handleStart"]
handle_end = instance.context.data["handleEnd"]
first_frame = int(nuke.root()["first_frame"].getValue())
last_frame = int(nuke.root()["last_frame"].getValue())
frame_length = int(last_frame - first_frame + 1)
if node["use_limit"].getValue():
first_frame = int(node["first"].getValue())
last_frame = int(node["last"].getValue())
# get path
path = nuke.filename(node)
output_dir = os.path.dirname(path)
self.log.debug('output dir: {}'.format(output_dir))
# create label
name = node.name()
# Include start and end render frame in label
label = "{0} ({1}-{2})".format(
name,
int(first_frame),
int(last_frame)
)
if [fm for fm in families
if fm in ["render", "prerender"]]:
if "representations" not in instance.data:
instance.data["representations"] = list()
representation = {
'name': ext,
'ext': ext,
"stagingDir": output_dir,
"tags": list()
}
try:
collected_frames = [f for f in os.listdir(output_dir)
if ext in f]
if collected_frames:
collected_frames_len = len(collected_frames)
frame_start_str = "%0{}d".format(
len(str(last_frame))) % first_frame
representation['frameStart'] = frame_start_str
# in case slate is expected and not yet rendered
self.log.debug("_ frame_length: {}".format(frame_length))
self.log.debug(
"_ collected_frames_len: {}".format(
collected_frames_len))
# this will only run if slate frame is not already
# rendered from previews publishes
if "slate" in instance.data["families"] \
and (frame_length == collected_frames_len) \
and ("prerender" not in instance.data["families"]):
frame_slate_str = "%0{}d".format(
len(str(last_frame))) % (first_frame - 1)
slate_frame = collected_frames[0].replace(
frame_start_str, frame_slate_str)
collected_frames.insert(0, slate_frame)
representation['files'] = collected_frames
instance.data["representations"].append(representation)
except Exception:
instance.data["representations"].append(representation)
self.log.debug("couldn't collect frames: {}".format(label))
# Add version data to instance
version_data = {
"colorspace": node["colorspace"].value(),
}
group_node = [x for x in instance if x.Class() == "Group"][0]
deadlineChunkSize = 1
if "deadlineChunkSize" in group_node.knobs():
deadlineChunkSize = group_node["deadlineChunkSize"].value()
deadlinePriority = 50
if "deadlinePriority" in group_node.knobs():
deadlinePriority = group_node["deadlinePriority"].value()
instance.data.update({
"versionData": version_data,
"path": path,
"outputDir": output_dir,
"ext": ext,
"label": label,
"handleStart": handle_start,
"handleEnd": handle_end,
"frameStart": first_frame + handle_start,
"frameEnd": last_frame - handle_end,
"frameStartHandle": first_frame,
"frameEndHandle": last_frame,
"outputType": output_type,
"families": families,
"colorspace": node["colorspace"].value(),
"deadlineChunkSize": deadlineChunkSize,
"deadlinePriority": deadlinePriority
})
if "prerender" in families:
instance.data.update({
"family": "prerender",
"families": []
})
# * Add audio to instance if exists.
# Find latest versions document
version_doc = pype.get_latest_version(
instance.data["asset"], "audioMain"
)
repre_doc = None
if version_doc:
# Try to find it's representation (Expected there is only one)
repre_doc = io.find_one(
{"type": "representation", "parent": version_doc["_id"]}
)
# Add audio to instance if representation was found
if repre_doc:
instance.data["audio"] = [{
"offset": 0,
"filename": api.get_representation_path(repre_doc)
}]
self.log.debug("families: {}".format(families))
self.log.debug("instance.data: {}".format(instance.data))

View file

@ -0,0 +1,22 @@
import nuke
import pyblish.api
class RemoveOutputNode(pyblish.api.ContextPlugin):
"""Removing output node for each ouput write node
"""
label = 'Output Node Remove'
order = pyblish.api.IntegratorOrder + 0.4
families = ["workfile"]
hosts = ['nuke']
def process(self, context):
try:
output_node = context.data["outputNode"]
name = output_node["name"].value()
self.log.info("Removing output node: '{}'".format(name))
nuke.delete(output_node)
except Exception:
return

View file

@ -0,0 +1,70 @@
import pyblish
from avalon.nuke import lib as anlib
import nuke
class SelectCenterInNodeGraph(pyblish.api.Action):
"""
Centering failed instance node in node grap
"""
label = "Center node in node graph"
icon = "wrench"
on = "failed"
def process(self, context, plugin):
# Get the errored instances
failed = []
for result in context.data["results"]:
if (result["error"] is not None and result["instance"] is not None
and result["instance"] not in failed):
failed.append(result["instance"])
# Apply pyblish.logic to get the instances for the plug-in
instances = pyblish.api.instances_by_plugin(failed, plugin)
all_xC = list()
all_yC = list()
# maintain selection
with anlib.maintained_selection():
# collect all failed nodes xpos and ypos
for instance in instances:
bdn = instance[0]
xC = bdn.xpos() + bdn.screenWidth() / 2
yC = bdn.ypos() + bdn.screenHeight() / 2
all_xC.append(xC)
all_yC.append(yC)
self.log.info("all_xC: `{}`".format(all_xC))
self.log.info("all_yC: `{}`".format(all_yC))
# zoom to nodes in node graph
nuke.zoom(2, [min(all_xC), min(all_yC)])
@pyblish.api.log
class ValidateBackdrop(pyblish.api.InstancePlugin):
"""Validate amount of nodes on backdrop node in case user
forgoten to add nodes above the publishing backdrop node"""
order = pyblish.api.ValidatorOrder
optional = True
families = ["nukenodes"]
label = "Validate Backdrop"
hosts = ["nuke"]
actions = [SelectCenterInNodeGraph]
def process(self, instance):
connections_out = instance.data["nodeConnectionsOut"]
msg_multiple_outputs = (
"Only one outcoming connection from "
"\"{}\" is allowed").format(instance.data["name"])
assert len(connections_out.keys()) <= 1, msg_multiple_outputs
msg_no_content = "No content on backdrop node: \"{}\"".format(
instance.data["name"])
assert len(instance) > 1, msg_no_content

View file

@ -0,0 +1,58 @@
import pyblish
from avalon.nuke import lib as anlib
import nuke
class OpenFailedGroupNode(pyblish.api.Action):
"""
Centering failed instance node in node grap
"""
label = "Open Gizmo in Node Graph"
icon = "wrench"
on = "failed"
def process(self, context, plugin):
# Get the errored instances
failed = []
for result in context.data["results"]:
if (result["error"] is not None and result["instance"] is not None
and result["instance"] not in failed):
failed.append(result["instance"])
# Apply pyblish.logic to get the instances for the plug-in
instances = pyblish.api.instances_by_plugin(failed, plugin)
# maintain selection
with anlib.maintained_selection():
# collect all failed nodes xpos and ypos
for instance in instances:
grpn = instance[0]
nuke.showDag(grpn)
@pyblish.api.log
class ValidateGizmo(pyblish.api.InstancePlugin):
"""Validate amount of output nodes in gizmo (group) node"""
order = pyblish.api.ValidatorOrder
optional = True
families = ["gizmo"]
label = "Validate Gizmo (Group)"
hosts = ["nuke"]
actions = [OpenFailedGroupNode]
def process(self, instance):
grpn = instance[0]
with grpn:
connections_out = nuke.allNodes('Output')
msg_multiple_outputs = "Only one outcoming connection from "
"\"{}\" is allowed".format(instance.data["name"])
assert len(connections_out) <= 1, msg_multiple_outputs
connections_in = nuke.allNodes('Input')
msg_missing_inputs = "At least one Input node has to be used in: "
"\"{}\"".format(instance.data["name"])
assert len(connections_in) >= 1, msg_missing_inputs

View file

@ -0,0 +1,106 @@
import nuke
import pyblish.api
import openpype.api
class ValidateKnobs(pyblish.api.ContextPlugin):
"""Ensure knobs are consistent.
Knobs to validate and their values comes from the
Controlled by plugin settings that require json in following structure:
"ValidateKnobs": {
"enabled": true,
"knobs": {
"family": {
"knob_name": knob_value
}
}
}
"""
order = pyblish.api.ValidatorOrder
label = "Validate Knobs"
hosts = ["nuke"]
actions = [openpype.api.RepairContextAction]
optional = True
def process(self, context):
invalid = self.get_invalid(context, compute=True)
if invalid:
raise RuntimeError(
"Found knobs with invalid values:\n{}".format(invalid)
)
@classmethod
def get_invalid(cls, context, compute=False):
invalid = context.data.get("invalid_knobs", [])
if compute:
invalid = cls.get_invalid_knobs(context)
return invalid
@classmethod
def get_invalid_knobs(cls, context):
invalid_knobs = []
for instance in context:
# Filter publisable instances.
if not instance.data["publish"]:
continue
# Filter families.
families = [instance.data["family"]]
families += instance.data.get("families", [])
families = list(set(families) & set(cls.knobs.keys()))
if not families:
continue
# Get all knobs to validate.
knobs = {}
for family in families:
for preset in cls.knobs[family]:
knobs.update({preset: cls.knobs[family][preset]})
# Get invalid knobs.
nodes = []
for node in nuke.allNodes():
nodes.append(node)
if node.Class() == "Group":
node.begin()
for i in nuke.allNodes():
nodes.append(i)
node.end()
for node in nodes:
for knob in node.knobs():
if knob not in knobs.keys():
continue
expected = knobs[knob]
if node[knob].value() != expected:
invalid_knobs.append(
{
"knob": node[knob],
"name": node[knob].name(),
"label": node[knob].label(),
"expected": expected,
"current": node[knob].value()
}
)
context.data["invalid_knobs"] = invalid_knobs
return invalid_knobs
@classmethod
def repair(cls, instance):
invalid = cls.get_invalid(instance)
for data in invalid:
if isinstance(data["expected"], unicode):
data["knob"].setValue(str(data["expected"]))
continue
data["knob"].setValue(data["expected"])

View file

@ -0,0 +1,78 @@
import nuke
import pyblish.api
class RepairWriteResolutionDifference(pyblish.api.Action):
label = "Repair"
icon = "wrench"
on = "failed"
def process(self, context, plugin):
# Get the errored instances
failed = []
for result in context.data["results"]:
if (result["error"] is not None and result["instance"] is not None
and result["instance"] not in failed):
failed.append(result["instance"])
# Apply pyblish.logic to get the instances for the plug-in
instances = pyblish.api.instances_by_plugin(failed, plugin)
for instance in instances:
reformat = instance[0].dependencies()[0]
if reformat.Class() != "Reformat":
reformat = nuke.nodes.Reformat(inputs=[instance[0].input(0)])
xpos = instance[0].xpos()
ypos = instance[0].ypos() - 26
dependent_ypos = instance[0].dependencies()[0].ypos()
if (instance[0].ypos() - dependent_ypos) <= 51:
xpos += 110
reformat.setXYpos(xpos, ypos)
instance[0].setInput(0, reformat)
reformat["resize"].setValue("none")
class ValidateOutputResolution(pyblish.api.InstancePlugin):
"""Validates Output Resolution.
It is making sure the resolution of write's input is the same as
Format definition of script in Root node.
"""
order = pyblish.api.ValidatorOrder
optional = True
families = ["render", "render.local", "render.farm"]
label = "Write Resolution"
hosts = ["nuke"]
actions = [RepairWriteResolutionDifference]
def process(self, instance):
# Skip bounding box check if a crop node exists.
if instance[0].dependencies()[0].Class() == "Crop":
return
msg = "Bounding box is outside the format."
assert self.check_resolution(instance), msg
def check_resolution(self, instance):
node = instance[0]
root_width = instance.data["resolutionWidth"]
root_height = instance.data["resolutionHeight"]
write_width = node.format().width()
write_height = node.format().height()
if (root_width != write_width) or (root_height != write_height):
return None
else:
return True

View file

@ -0,0 +1,83 @@
import os
import toml
import nuke
import pyblish.api
from avalon import api
from bson.objectid import ObjectId
class RepairReadLegacyAction(pyblish.api.Action):
label = "Repair"
icon = "wrench"
on = "failed"
def process(self, context, plugin):
# Get the errored instances
failed = []
for result in context.data["results"]:
if (result["error"] is not None and result["instance"] is not None
and result["instance"] not in failed):
failed.append(result["instance"])
# Apply pyblish.logic to get the instances for the plug-in
instances = pyblish.api.instances_by_plugin(failed, plugin)
for instance in instances:
data = toml.loads(instance[0]["avalon"].value())
data["name"] = instance[0].name()
data["xpos"] = instance[0].xpos()
data["ypos"] = instance[0].ypos()
data["extension"] = os.path.splitext(
instance[0]["file"].value()
)[1][1:]
data["connections"] = []
for d in instance[0].dependent():
for i in range(d.inputs()):
if d.input(i) == instance[0]:
data["connections"].append([i, d])
nuke.delete(instance[0])
loader_name = "LoadSequence"
if data["extension"] == "mov":
loader_name = "LoadMov"
loader_plugin = None
for Loader in api.discover(api.Loader):
if Loader.__name__ != loader_name:
continue
loader_plugin = Loader
api.load(
Loader=loader_plugin,
representation=ObjectId(data["representation"])
)
node = nuke.toNode(data["name"])
for connection in data["connections"]:
connection[1].setInput(connection[0], node)
node.setXYpos(data["xpos"], data["ypos"])
class ValidateReadLegacy(pyblish.api.InstancePlugin):
"""Validate legacy read instance[0]s."""
order = pyblish.api.ValidatorOrder
optional = True
families = ["read.legacy"]
label = "Read Legacy"
hosts = ["nuke"]
actions = [RepairReadLegacyAction]
def process(self, instance):
msg = "Clean up legacy read node \"{}\"".format(instance)
assert False, msg

View file

@ -0,0 +1,89 @@
import os
import pyblish.api
from openpype.api import ValidationException
import clique
@pyblish.api.log
class RepairCollectionAction(pyblish.api.Action):
label = "Repair"
on = "failed"
icon = "wrench"
def process(self, context, plugin):
self.log.info(context[0][0])
files_remove = [os.path.join(context[0].data["outputDir"], f)
for r in context[0].data.get("representations", [])
for f in r.get("files", [])
]
self.log.info("Files to be removed: {}".format(files_remove))
for f in files_remove:
os.remove(f)
self.log.debug("removing file: {}".format(f))
context[0][0]["render"].setValue(True)
self.log.info("Rendering toggled ON")
class ValidateRenderedFrames(pyblish.api.InstancePlugin):
""" Validates file output. """
order = pyblish.api.ValidatorOrder + 0.1
families = ["render", "prerender"]
label = "Validate rendered frame"
hosts = ["nuke", "nukestudio"]
actions = [RepairCollectionAction]
def process(self, instance):
for repre in instance.data.get('representations'):
if not repre.get('files'):
msg = ("no frames were collected, "
"you need to render them")
self.log.error(msg)
raise ValidationException(msg)
collections, remainder = clique.assemble(repre["files"])
self.log.info('collections: {}'.format(str(collections)))
self.log.info('remainder: {}'.format(str(remainder)))
collection = collections[0]
frame_length = int(
instance.data["frameEndHandle"] - instance.data["frameStartHandle"] + 1
)
if frame_length != 1:
if len(collections) != 1:
msg = "There are multiple collections in the folder"
self.log.error(msg)
raise ValidationException(msg)
if not collection.is_contiguous():
msg = "Some frames appear to be missing"
self.log.error(msg)
raise ValidationException(msg)
# if len(remainder) != 0:
# msg = "There are some extra files in folder"
# self.log.error(msg)
# raise ValidationException(msg)
collected_frames_len = int(len(collection.indexes))
self.log.info('frame_length: {}'.format(frame_length))
self.log.info(
'len(collection.indexes): {}'.format(collected_frames_len)
)
if ("slate" in instance.data["families"]) \
and (frame_length != collected_frames_len):
collected_frames_len -= 1
assert (collected_frames_len == frame_length), (
"{} missing frames. Use repair to render all frames"
).format(__name__)
instance.data['collection'] = collection
return

View file

@ -0,0 +1,123 @@
import pyblish.api
from avalon import io
from openpype import lib
@pyblish.api.log
class ValidateScript(pyblish.api.InstancePlugin):
""" Validates file output. """
order = pyblish.api.ValidatorOrder + 0.1
families = ["workfile"]
label = "Check script settings"
hosts = ["nuke"]
optional = True
def process(self, instance):
ctx_data = instance.context.data
asset_name = ctx_data["asset"]
asset = lib.get_asset(asset_name)
asset_data = asset["data"]
# These attributes will be checked
attributes = [
"fps",
"frameStart",
"frameEnd",
"resolutionWidth",
"resolutionHeight",
"handleStart",
"handleEnd"
]
# Value of these attributes can be found on parents
hierarchical_attributes = [
"fps",
"resolutionWidth",
"resolutionHeight",
"pixelAspect",
"handleStart",
"handleEnd"
]
missing_attributes = []
asset_attributes = {}
for attr in attributes:
if attr in asset_data:
asset_attributes[attr] = asset_data[attr]
elif attr in hierarchical_attributes:
# Try to find fps on parent
parent = asset['parent']
if asset_data['visualParent'] is not None:
parent = asset_data['visualParent']
value = self.check_parent_hierarchical(parent, attr)
if value is None:
missing_attributes.append(attr)
else:
asset_attributes[attr] = value
else:
missing_attributes.append(attr)
# Raise error if attributes weren't found on asset in database
if len(missing_attributes) > 0:
atr = ", ".join(missing_attributes)
msg = 'Missing attributes "{}" in asset "{}"'
message = msg.format(atr, asset_name)
raise ValueError(message)
# Get handles from database, Default is 0 (if not found)
handle_start = 0
handle_end = 0
if "handleStart" in asset_attributes:
handle_start = asset_attributes["handleStart"]
if "handleEnd" in asset_attributes:
handle_end = asset_attributes["handleEnd"]
asset_attributes["fps"] = float("{0:.4f}".format(
asset_attributes["fps"]))
# Get values from nukescript
script_attributes = {
"handleStart": ctx_data["handleStart"],
"handleEnd": ctx_data["handleEnd"],
"fps": float("{0:.4f}".format(ctx_data["fps"])),
"frameStart": ctx_data["frameStart"],
"frameEnd": ctx_data["frameEnd"],
"resolutionWidth": ctx_data["resolutionWidth"],
"resolutionHeight": ctx_data["resolutionHeight"],
"pixelAspect": ctx_data["pixelAspect"]
}
# Compare asset's values Nukescript X Database
not_matching = []
for attr in attributes:
self.log.debug("asset vs script attribute \"{}\": {}, {}".format(
attr, asset_attributes[attr], script_attributes[attr])
)
if asset_attributes[attr] != script_attributes[attr]:
not_matching.append(attr)
# Raise error if not matching
if len(not_matching) > 0:
msg = "Attributes '{}' are not set correctly"
# Alert user that handles are set if Frame start/end not match
if (
(("frameStart" in not_matching) or ("frameEnd" in not_matching)) and
((handle_start > 0) or (handle_end > 0))
):
msg += " (`handle_start` are set to {})".format(handle_start)
msg += " (`handle_end` are set to {})".format(handle_end)
message = msg.format(", ".join(not_matching))
raise ValueError(message)
def check_parent_hierarchical(self, entityId, attr):
if entityId is None:
return None
entity = io.find_one({"_id": entityId})
if attr in entity['data']:
self.log.info(attr)
return entity['data'][attr]
else:
return self.check_parent_hierarchical(entity['parent'], attr)

View file

@ -0,0 +1,106 @@
import nuke
import pyblish.api
class RepairNukeBoundingBoxAction(pyblish.api.Action):
label = "Repair"
icon = "wrench"
on = "failed"
def process(self, context, plugin):
# Get the errored instances
failed = []
for result in context.data["results"]:
if (result["error"] is not None and result["instance"] is not None
and result["instance"] not in failed):
failed.append(result["instance"])
# Apply pyblish.logic to get the instances for the plug-in
instances = pyblish.api.instances_by_plugin(failed, plugin)
for instance in instances:
crop = instance[0].dependencies()[0]
if crop.Class() != "Crop":
crop = nuke.nodes.Crop(inputs=[instance[0].input(0)])
xpos = instance[0].xpos()
ypos = instance[0].ypos() - 26
dependent_ypos = instance[0].dependencies()[0].ypos()
if (instance[0].ypos() - dependent_ypos) <= 51:
xpos += 110
crop.setXYpos(xpos, ypos)
instance[0].setInput(0, crop)
crop["box"].setValue(
(
0.0,
0.0,
instance[0].input(0).width(),
instance[0].input(0).height()
)
)
class ValidateNukeWriteBoundingBox(pyblish.api.InstancePlugin):
"""Validates write bounding box.
Ffmpeg does not support bounding boxes outside of the image
resolution a crop is needed. This needs to validate all frames, as each
rendered exr can break the ffmpeg transcode.
"""
order = pyblish.api.ValidatorOrder
optional = True
families = ["render", "render.local", "render.farm"]
label = "Write Bounding Box"
hosts = ["nuke"]
actions = [RepairNukeBoundingBoxAction]
def process(self, instance):
# Skip bounding box check if a crop node exists.
if instance[0].dependencies()[0].Class() == "Crop":
return
msg = "Bounding box is outside the format."
assert self.check_bounding_box(instance), msg
def check_bounding_box(self, instance):
node = instance[0]
first_frame = instance.data["frameStart"]
last_frame = instance.data["frameEnd"]
format_width = node.format().width()
format_height = node.format().height()
# The trick is that we need to execute() some node every time we go to
# a next frame, to update the context.
# So we create a CurveTool that we can execute() on every frame.
temporary_node = nuke.nodes.CurveTool()
bbox_check = True
for frame in range(first_frame, last_frame + 1):
# Workaround to update the tree
nuke.execute(temporary_node, frame, frame)
x = node.bbox().x()
y = node.bbox().y()
w = node.bbox().w()
h = node.bbox().h()
if x < 0 or (x + w) > format_width:
bbox_check = False
break
if y < 0 or (y + h) > format_height:
bbox_check = False
break
nuke.delete(temporary_node)
return bbox_check

View file

@ -0,0 +1,53 @@
import pyblish.api
import openpype.hosts.nuke.lib
class RepairNukeWriteDeadlineTab(pyblish.api.Action):
label = "Repair"
icon = "wrench"
on = "failed"
def process(self, context, plugin):
# Get the errored instances
failed = []
for result in context.data["results"]:
if (result["error"] is not None and result["instance"] is not None
and result["instance"] not in failed):
failed.append(result["instance"])
# Apply pyblish.logic to get the instances for the plug-in
instances = pyblish.api.instances_by_plugin(failed, plugin)
for instance in instances:
group_node = [x for x in instance if x.Class() == "Group"][0]
# Remove exising knobs.
knob_names = openpype.hosts.nuke.lib.get_deadline_knob_names()
for name, knob in group_node.knobs().iteritems():
if name in knob_names:
group_node.removeKnob(knob)
openpype.hosts.nuke.lib.add_deadline_tab(group_node)
class ValidateNukeWriteDeadlineTab(pyblish.api.InstancePlugin):
"""Ensure Deadline tab is present and current."""
order = pyblish.api.ValidatorOrder
label = "Deadline Tab"
hosts = ["nuke"]
optional = True
families = ["render"]
actions = [RepairNukeWriteDeadlineTab]
def process(self, instance):
group_node = [x for x in instance if x.Class() == "Group"][0]
knob_names = openpype.hosts.nuke.lib.get_deadline_knob_names()
missing_knobs = []
for name in knob_names:
if name not in group_node.knobs().keys():
missing_knobs.append(name)
assert not missing_knobs, "Missing knobs: {}".format(missing_knobs)

View file

@ -0,0 +1,107 @@
import toml
import os
import nuke
from avalon import api
import re
import pyblish.api
import openpype.api
from avalon.nuke import get_avalon_knob_data
class ValidateWriteLegacy(pyblish.api.InstancePlugin):
"""Validate legacy write nodes."""
order = pyblish.api.ValidatorOrder
optional = True
families = ["write"]
label = "Validate Write Legacy"
hosts = ["nuke"]
actions = [openpype.api.RepairAction]
def process(self, instance):
node = instance[0]
msg = "Clean up legacy write node \"{}\"".format(instance)
if node.Class() not in ["Group", "Write"]:
return
# test avalon knobs
family_knobs = ["ak:family", "avalon:family"]
family_test = [k for k in node.knobs().keys() if k in family_knobs]
self.log.debug("_ family_test: {}".format(family_test))
# test if render in family test knob
# and only one item should be available
assert len(family_test) == 1, msg + " > More avalon attributes"
assert "render" in node[family_test[0]].value(), msg + \
" > Not correct family"
# test if `file` knob in node, this way old
# non-group-node write could be detected
assert "file" not in node.knobs(), msg + \
" > file knob should not be present"
# check if write node is having old render targeting
assert "render_farm" not in node.knobs(), msg + \
" > old way of setting render target"
@classmethod
def repair(cls, instance):
node = instance[0]
if "Write" in node.Class():
data = toml.loads(node["avalon"].value())
else:
data = get_avalon_knob_data(node)
# collect reusable data
data["XYpos"] = (node.xpos(), node.ypos())
data["input"] = node.input(0)
data["publish"] = node["publish"].value()
data["render"] = node["render"].value()
data["render_farm"] = node["render_farm"].value()
data["review"] = node["review"].value()
data["use_limit"] = node["use_limit"].value()
data["first"] = node["first"].value()
data["last"] = node["last"].value()
family = data["family"]
cls.log.debug("_ orig node family: {}".format(family))
# define what family of write node should be recreated
if family == "render":
Create_name = "CreateWriteRender"
elif family == "prerender":
Create_name = "CreateWritePrerender"
# get appropriate plugin class
creator_plugin = None
for Creator in api.discover(api.Creator):
if Creator.__name__ != Create_name:
continue
creator_plugin = Creator
# delete the legaci write node
nuke.delete(node)
# create write node with creator
new_node_name = data["subset"]
creator_plugin(new_node_name, data["asset"]).process()
node = nuke.toNode(new_node_name)
node.setXYpos(*data["XYpos"])
node.setInput(0, data["input"])
node["publish"].setValue(data["publish"])
node["review"].setValue(data["review"])
node["use_limit"].setValue(data["use_limit"])
node["first"].setValue(data["first"])
node["last"].setValue(data["last"])
# recreate render targets
if data["render"]:
node["render"].setValue("Local")
if data["render_farm"]:
node["render"].setValue("On farm")

View file

@ -0,0 +1,69 @@
import os
import pyblish.api
import openpype.utils
import openpype.hosts.nuke.lib as nukelib
import avalon.nuke
@pyblish.api.log
class RepairNukeWriteNodeAction(pyblish.api.Action):
label = "Repair"
on = "failed"
icon = "wrench"
def process(self, context, plugin):
instances = openpype.utils.filter_instances(context, plugin)
for instance in instances:
node = instance[1]
correct_data = nukelib.get_write_node_template_attr(node)
for k, v in correct_data.items():
node[k].setValue(v)
self.log.info("Node attributes were fixed")
class ValidateNukeWriteNode(pyblish.api.InstancePlugin):
""" Validates file output. """
order = pyblish.api.ValidatorOrder
optional = True
families = ["render"]
label = "Write Node"
actions = [RepairNukeWriteNodeAction]
hosts = ["nuke"]
def process(self, instance):
node = instance[1]
correct_data = nukelib.get_write_node_template_attr(node)
check = []
for k, v in correct_data.items():
if k is 'file':
padding = len(v.split('#'))
ref_path = avalon.nuke.lib.get_node_path(v, padding)
n_path = avalon.nuke.lib.get_node_path(node[k].value(), padding)
isnt = False
for i, p in enumerate(ref_path):
if str(n_path[i]) not in str(p):
if not isnt:
isnt = True
else:
continue
if isnt:
check.append([k, v, node[k].value()])
else:
if str(node[k].value()) not in str(v):
check.append([k, v, node[k].value()])
self.log.info(check)
msg = "Node's attribute `{0}` is not correct!\n" \
"\nCorrect: `{1}` \n\nWrong: `{2}` \n\n"
if check:
print_msg = ""
for item in check:
print_msg += msg.format(item[0], item[1], item[2])
print_msg += "`RMB` click to the validator and `A` to fix!"
assert not check, print_msg