Merge pull request #575 from pypeclub/feature/567-Nuke_Publish_Camera

Nuke: Publishing, loading and updating alembic cameras
This commit is contained in:
Milan Kolar 2020-10-05 18:18:27 +02:00 committed by GitHub
commit 0c2ffde93a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 425 additions and 5 deletions

View file

@ -1,3 +0,0 @@
# create vanilla camera if no camera is selected
# if camera is selected then it will convert it into containerized object
# it is major versioned in publish

View file

@ -0,0 +1,53 @@
import avalon.nuke
from avalon.nuke import lib as anlib
import nuke
class CreateCamera(avalon.nuke.Creator):
"""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.imprint(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.imprint(camera_node, self.data)
return instance

View file

@ -240,7 +240,6 @@ class LoadBackdropNodes(api.Loader):
return update_container(GN, data_imprint)
def switch(self, container, representation):
self.update(container, representation)

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

@ -60,7 +60,6 @@ class CollectNukeInstances(pyblish.api.ContextPlugin):
families.append(family)
# except disabled nodes but exclude backdrops in test
if ("nukenodes" not in family) and (node["disable"].value()):
continue

View file

@ -0,0 +1,185 @@
import nuke
import os
import math
import pyblish.api
import pype.api
from avalon.nuke import lib as anlib
from pprint import pformat
class ExtractCamera(pype.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