Merge pull request #2140 from pypeclub/feature/PYPE-1120-nk-geo-workflow

This commit is contained in:
Jakub Ježek 2021-11-29 17:49:32 +01:00 committed by GitHub
commit 4b7384e03b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 429 additions and 1 deletions

View file

@ -70,7 +70,8 @@ def install():
family_states = [
"write",
"review",
"nukenodes"
"nukenodes",
"model",
"gizmo"
]

View file

@ -0,0 +1,85 @@
from avalon.nuke import lib as anlib
from openpype.hosts.nuke.api import plugin
import nuke
class CreateModel(plugin.PypeCreator):
"""Add Publishable Model Geometry"""
name = "model"
label = "Create 3d Model"
family = "model"
icon = "cube"
defaults = ["Main"]
def __init__(self, *args, **kwargs):
super(CreateModel, self).__init__(*args, **kwargs)
self.nodes = nuke.selectedNodes()
self.node_color = "0xff3200ff"
return
def process(self):
nodes = list()
if (self.options or {}).get("useSelection"):
nodes = self.nodes
for n in nodes:
n['selected'].setValue(0)
end_nodes = list()
# get the latest nodes in tree for selecion
for n in nodes:
x = n
end = 0
while end == 0:
try:
x = x.dependent()[0]
except:
end_node = x
end = 1
end_nodes.append(end_node)
# set end_nodes
end_nodes = list(set(end_nodes))
# check if nodes is 3d nodes
for n in end_nodes:
n['selected'].setValue(1)
sn = nuke.createNode("Scene")
if not sn.input(0):
end_nodes.remove(n)
nuke.delete(sn)
# loop over end nodes
for n in end_nodes:
n['selected'].setValue(1)
self.nodes = nuke.selectedNodes()
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
model_node = nuke.createNode("WriteGeo")
model_node["tile_color"].setValue(int(self.node_color, 16))
# add avalon knobs
instance = anlib.set_avalon_knob_data(model_node, self.data)
return instance

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 AlembicModelLoader(api.Loader):
"""
This will load alembic model into script.
"""
families = ["model"]
representations = ["abc"]
label = "Load Alembic Model"
icon = "cube"
color = "orange"
node_color = "0x4ecd91ff"
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():
model_node = nuke.createNode(
"ReadGeo2",
"name {} file {} ".format(
object_name, file),
inpanel=False
)
model_node.forceValidate()
model_node["frame_rate"].setValue(float(fps))
# workaround because nuke's bug is not adding
# animation keys properly
xpos = model_node.xpos()
ypos = model_node.ypos()
nuke.nodeCopy("%clipboard%")
nuke.delete(model_node)
nuke.nodePaste("%clipboard%")
model_node = nuke.toNode(object_name)
model_node.setXYpos(xpos, ypos)
# color node by correct color by actual version
self.node_version_color(version, model_node)
return containerise(
node=model_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
model_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():
model_node = nuke.toNode(object_name)
model_node['selected'].setValue(True)
# collect input output dependencies
dependencies = model_node.dependencies()
dependent = model_node.dependent()
model_node["frame_rate"].setValue(float(fps))
model_node["file"].setValue(file)
# workaround because nuke's bug is
# not adding animation keys properly
xpos = model_node.xpos()
ypos = model_node.ypos()
nuke.nodeCopy("%clipboard%")
nuke.delete(model_node)
nuke.nodePaste("%clipboard%")
model_node = nuke.toNode(object_name)
model_node.setXYpos(xpos, ypos)
# link to original input nodes
for i, input in enumerate(dependencies):
model_node.setInput(i, input)
# link to original output nodes
for d in dependent:
index = next((i for i, dpcy in enumerate(
d.dependencies())
if model_node is dpcy), 0)
d.setInput(index, model_node)
# color node by correct color by actual version
self.node_version_color(version, model_node)
self.log.info("udated to version: {}".format(version.get("name")))
return update_container(model_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,49 @@
import pyblish.api
import nuke
@pyblish.api.log
class CollectModel(pyblish.api.InstancePlugin):
"""Collect Model node instance and its content
"""
order = pyblish.api.CollectorOrder + 0.22
label = "Collect Model"
hosts = ["nuke"]
families = ["model"]
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"] = grpn.name()
# 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("Model content collected: `{}`".format(instance[:]))
self.log.info("Model instance collected: `{}`".format(instance))

View file

@ -0,0 +1,103 @@
import nuke
import os
import pyblish.api
import openpype.api
from avalon.nuke import lib as anlib
from pprint import pformat
class ExtractModel(openpype.api.Extractor):
""" 3D model exctractor
"""
label = 'Exctract Model'
order = pyblish.api.ExtractorOrder
families = ["model"]
hosts = ["nuke"]
# presets
write_geo_knobs = [
("file_type", "abc"),
("storageFormat", "Ogawa"),
("writeGeometries", True),
("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())
self.log.info("instance.data: `{}`".format(
pformat(instance.data)))
rm_nodes = list()
model_node = instance[0]
self.log.info("Crating additional nodes")
subset = instance.data["subset"]
staging_dir = self.staging_dir(instance)
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():
# select model node
anlib.select_nodes([model_node])
# 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 model
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))

View file

@ -24,6 +24,9 @@
{
"nukenodes": "nukenodes"
},
{
"model": "model"
},
{
"camera": "camera"
},