Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Nuke: Build workfile by template #3544

Closed
wants to merge 15 commits into from
87 changes: 87 additions & 0 deletions openpype/hosts/nuke/api/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -2604,3 +2604,90 @@ def ls_img_sequence(path):
}

return False


def get_io(nodes):

""" get the input and the output of a group of nodes
"""
if not nodes:
raise Exception("there is no nodes in the list")
if len(nodes) > 1:
input = None
output = None
for n in nodes:
if "Input" in n.name():
input = n
break

for n in nodes:
if "Output" in n.name():
output = n
break
if input is None:
raise Exception("No Input found")
if output is None:
raise Exception("No Output found")
else:
input = output = nodes[0]

return input, output


def get_extremes(nodes):

""" get the 4 numbers that represent the box of a group of nodes """
if not nodes:
raise Exception("there is no nodes in the list")

nodes_xpos = [n.xpos() for n in nodes] + \
[n.xpos() + n.screenWidth() for n in nodes]

nodes_ypos = [n.ypos() for n in nodes] + \
[n.ypos() + n.screenHeight() for n in nodes]

min_x, min_y = (min(nodes_xpos), min(nodes_ypos))
max_x, max_y = (max(nodes_xpos), max(nodes_ypos))
return min_x, min_y, max_x, max_y


def refresh_node(node):

""" correct a bug caused by the multi-threading of nuke
refresh the node to make sure that it takes the desired attributes """

x = node.xpos()
y = node.ypos()
nuke.autoplaceSnap(node)
node.setXYpos(x, y)


def refresh_nodes(nodes):
for n in nodes:
refresh_node(n)


def get_names_from_nodes(nodes):
"""
get list of nodes names

Arguments :
nodes(list) : list of nodes (nuke nodes) to convert into names (str)"""

names = []
for node in nodes:
names.append(node.name())
return names


def get_nodes_from_names(names):
"""
get list of nuke nodes from their names

Arguments :
names(list) : list of names (str) to convert into nodes"""

nodes = []
for name in names:
nodes.append(nuke.toNode(name))
return nodes
206 changes: 206 additions & 0 deletions openpype/hosts/nuke/api/lib_template_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
from collections import OrderedDict

from openpype.vendor.python.common import qargparse
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
from openpype.vendor.python.common import qargparse
import qargparse

from openpype.tools.utils.widgets import OptionDialog
from openpype.hosts.nuke.api.lib import imprint
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
from openpype.hosts.nuke.api.lib import imprint
from .lib import imprint

import nuke


# To change as enum
build_types = ["context_asset", "linked_asset", "all_assets"]


def get_placeholder_attributes(node, enumerate=False):
list_atts = ['builder_type', 'family', 'representation', 'loader',
'loader_args', 'order', 'asset', 'subset',
'hierarchy', 'siblings', 'last_loaded']
attributes = {}
for attr in node.knobs().keys():
if attr in list_atts:
if enumerate:
try:
attributes[attr] = node.knob(attr).values()
except AttributeError:
attributes[attr] = node.knob(attr).getValue()
else:
attributes[attr] = node.knob(attr).getValue()

return attributes


def delete_placeholder_attributes(node):
'''
function to delete all extra placeholder attributes
'''
extra_attributes = get_placeholder_attributes(node)
for attribute in extra_attributes.keys():
try:
node.removeKnob(node.knob(attribute))
except ValueError:
continue


def hide_placeholder_attributes(node):
'''
function to hide all extra placeholder attributes
'''
extra_attributes = get_placeholder_attributes(node)
for attribute in extra_attributes.keys():
try:
node.knob(attribute).setVisible(False)
except ValueError:
continue


def create_placeholder():

args = placeholder_window()

if not args:
return # operation canceled, no locator created

placeholder = nuke.nodes.NoOp()
placeholder.setName('PLACEHOLDER')
placeholder.knob('tile_color').setValue(4278190335)

# custom arg parse to force empty data query
# and still imprint them on placeholder
# and getting items when arg is of type Enumerator
options = OrderedDict()
for arg in args:
if not type(arg) == qargparse.Separator:
options[str(arg)] = arg._data.get("items") or arg.read()
imprint(placeholder, options)
imprint(placeholder, {'is_placeholder': True})
placeholder.knob('is_placeholder').setVisible(False)


def update_placeholder():
placeholder = nuke.selectedNodes()
if not placeholder:
raise ValueError("No node selected")
if len(placeholder) > 1:
raise ValueError("Too many selected nodes")
placeholder = placeholder[0]

args = placeholder_window(get_placeholder_attributes(placeholder))
if not args:
return # operation canceled
# delete placeholder attributes
delete_placeholder_attributes(placeholder)

options = OrderedDict()
for arg in args:
if not type(arg) == qargparse.Separator:
options[str(arg)] = arg._data.get("items") or arg.read()
imprint(placeholder, options)


def imprint_enum(placeholder, args):
"""
Imprint method doesn't act properly with enums.
Replacing the functionnality with this for now
"""
enum_values = {str(arg): arg.read()
for arg in args if arg._data.get("items")}
string_to_value_enum_table = {
build: i for i, build
in enumerate(build_types)}
attrs = {}
for key, value in enum_values.items():
attrs[key] = string_to_value_enum_table[value]


def placeholder_window(options=None):
from openpype.hosts.nuke.api.pipeline import get_main_window
options = options or dict()
dialog = OptionDialog(parent=get_main_window())
dialog.setWindowTitle("Create Placeholder")

args = [
qargparse.Separator("Main attributes"),
qargparse.Enum(
"builder_type",
label="Asset Builder Type",
default=options.get("builder_type", 0),
items=build_types,
help="""Asset Builder Type
Builder type describe what template loader will look for.

context_asset : Template loader will look for subsets of
current context asset (Asset bob will find asset)

linked_asset : Template loader will look for assets linked
to current context asset.
Linked asset are looked in avalon database under field "inputLinks"
"""
),
qargparse.String(
"family",
default=options.get("family", ""),
label="OpenPype Family",
placeholder="ex: model, look ..."),
qargparse.String(
"representation",
default=options.get("representation", ""),
label="OpenPype Representation",
placeholder="ex: ma, abc ..."),
qargparse.String(
"loader",
default=options.get("loader", ""),
label="Loader",
placeholder="ex: ReferenceLoader, LightLoader ...",
help="""Loader

Defines what openpype loader will be used to load assets.
Useable loader depends on current host's loader list.
Field is case sensitive.
"""),
qargparse.String(
"loader_args",
default=options.get("loader_args", ""),
label="Loader Arguments",
placeholder='ex: {"camera":"persp", "lights":True}',
help="""Loader

Defines a dictionnary of arguments used to load assets.
Useable arguments depend on current placeholder Loader.
Field should be a valid python dict. Anything else will be ignored.
"""),
qargparse.Integer(
"order",
default=options.get("order", 0),
min=0,
max=999,
label="Order",
placeholder="ex: 0, 100 ... (smallest order loaded first)",
help="""Order

Order defines asset loading priority (0 to 999)
Priority rule is : "lowest is first to load"."""),
qargparse.Separator(
"Optional attributes "),
qargparse.String(
"asset",
default=options.get("asset", ""),
label="Asset filter",
placeholder="regex filtering by asset name",
help="Filtering assets by matching field regex to asset's name"),
qargparse.String(
"subset",
default=options.get("subset", ""),
label="Subset filter",
placeholder="regex filtering by subset name",
help="Filtering assets by matching field regex to subset's name"),
qargparse.String(
"hierarchy",
default=options.get("hierarchy", ""),
label="Hierarchy filter",
placeholder="regex filtering by asset's hierarchy",
help="Filtering assets by matching field asset's hierarchy")
]
dialog.create(args)
if not dialog.exec_():
return None

return args
27 changes: 26 additions & 1 deletion openpype/hosts/nuke/api/pipeline.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import os
import importlib
from collections import OrderedDict
from openpype.lib.build_template import (
build_workfile_template,
update_workfile_template
)
from openpype.hosts.nuke.api.lib_template_builder import (
create_placeholder, update_placeholder
)

import nuke

Expand All @@ -10,7 +17,7 @@
from openpype.api import (
Logger,
BuildWorkfile,
get_current_project_settings
get_current_project_settings,
)
from openpype.lib import register_event_callback
from openpype.pipeline import (
Expand Down Expand Up @@ -210,6 +217,24 @@ def _install_menu():
lambda: BuildWorkfile().process()
)

menu_template = menu.addMenu("Template Builder") # creating template menu
menu_template.addCommand(
"Build Workfile from template",
lambda: build_workfile_template()
)
menu_template.addCommand(
"Update Workfile",
lambda: update_workfile_template()
)
menu_template.addSeparator()
menu_template.addCommand(
"Create Place Holder",
lambda: create_placeholder()
)
menu_template.addCommand(
"Update Place Holder",
lambda: update_placeholder()
)
menu.addSeparator()
menu.addCommand(
"Experimental tools...",
Expand Down
Loading