mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
534 lines
20 KiB
Python
534 lines
20 KiB
Python
from openpype.hosts.nuke.api.lib_template_builder import (
|
|
delete_placeholder_attributes, get_placeholder_attributes,
|
|
hide_placeholder_attributes)
|
|
from openpype.lib.abstract_template_loader import (
|
|
AbstractPlaceholder,
|
|
AbstractTemplateLoader)
|
|
import nuke
|
|
from collections import defaultdict
|
|
from openpype.hosts.nuke.api.lib import (
|
|
find_free_space_to_paste_nodes, get_extremes, get_io, imprint,
|
|
refresh_node, refresh_nodes, reset_selection,
|
|
get_names_from_nodes, get_nodes_from_names, select_nodes)
|
|
PLACEHOLDER_SET = 'PLACEHOLDERS_SET'
|
|
|
|
|
|
class NukeTemplateLoader(AbstractTemplateLoader):
|
|
"""Concrete implementation of AbstractTemplateLoader for Nuke
|
|
|
|
"""
|
|
|
|
def import_template(self, path):
|
|
"""Import template into current scene.
|
|
Block if a template is already loaded.
|
|
|
|
Args:
|
|
path (str): A path to current template (usually given by
|
|
get_template_path implementation)
|
|
|
|
Returns:
|
|
bool: Wether the template was succesfully imported or not
|
|
"""
|
|
|
|
# TODO check if the template is already imported
|
|
|
|
nuke.nodePaste(path)
|
|
reset_selection()
|
|
|
|
return True
|
|
|
|
def preload(self, placeholder, loaders_by_name, last_representation):
|
|
placeholder.data["nodes_init"] = nuke.allNodes()
|
|
placeholder.data["_id"] = last_representation['_id']
|
|
|
|
def populate_template(self, ignored_ids=None):
|
|
place_holders = self.get_template_nodes()
|
|
while len(place_holders) > 0:
|
|
super().populate_template(ignored_ids)
|
|
place_holders = self.get_template_nodes()
|
|
|
|
@staticmethod
|
|
def get_template_nodes():
|
|
placeholders = []
|
|
allGroups = [nuke.thisGroup()]
|
|
while len(allGroups) > 0:
|
|
group = allGroups.pop(0)
|
|
for node in group.nodes():
|
|
if "builder_type" in node.knobs().keys() and (
|
|
'is_placeholder' in node.knobs().keys()
|
|
and node.knob('is_placeholder').value()):
|
|
if 'empty' in node.knobs().keys()\
|
|
and node.knob('empty').value():
|
|
continue
|
|
placeholders += [node]
|
|
if isinstance(node, nuke.Group):
|
|
allGroups.append(node)
|
|
|
|
return placeholders
|
|
|
|
def update_missing_containers(self):
|
|
nodes_byId = {}
|
|
nodes_byId = defaultdict(lambda: [], nodes_byId)
|
|
|
|
for n in nuke.allNodes():
|
|
if 'id_rep' in n.knobs().keys():
|
|
nodes_byId[n.knob('id_rep').getValue()] += [n.name()]
|
|
if 'empty' in n.knobs().keys():
|
|
n.removeKnob(n.knob('empty'))
|
|
imprint(n, {"empty": False})
|
|
for s in nodes_byId.values():
|
|
n = None
|
|
for name in s:
|
|
n = nuke.toNode(name)
|
|
if 'builder_type' in n.knobs().keys():
|
|
break
|
|
if n is not None and 'builder_type' in n.knobs().keys():
|
|
|
|
placeholder = nuke.nodes.NoOp()
|
|
placeholder.setName('PLACEHOLDER')
|
|
placeholder.knob('tile_color').setValue(4278190335)
|
|
attributes = get_placeholder_attributes(n, enumerate=True)
|
|
imprint(placeholder, attributes)
|
|
x = int(n.knob('x').getValue())
|
|
y = int(n.knob('y').getValue())
|
|
placeholder.setXYpos(x, y)
|
|
imprint(placeholder, {'nb_children': 1})
|
|
refresh_node(placeholder)
|
|
|
|
self.populate_template(self.get_loaded_containers_by_id())
|
|
|
|
def get_loaded_containers_by_id(self):
|
|
ids = []
|
|
for n in nuke.allNodes():
|
|
if 'id_rep' in n.knobs():
|
|
ids.append(n.knob('id_rep').getValue())
|
|
|
|
# Removes duplicates in the list
|
|
ids = list(set(ids))
|
|
return ids
|
|
|
|
def get_placeholders(self):
|
|
placeholders = super().get_placeholders()
|
|
return placeholders
|
|
|
|
def delete_placeholder(self, placeholder):
|
|
node = placeholder.data['node']
|
|
lastLoaded = placeholder.data['last_loaded']
|
|
if not placeholder.data['delete']:
|
|
if 'empty' in node.knobs().keys():
|
|
node.removeKnob(node.knob('empty'))
|
|
imprint(node, {"empty": True})
|
|
else:
|
|
if lastLoaded:
|
|
if 'last_loaded' in node.knobs().keys():
|
|
for s in node.knob('last_loaded').values():
|
|
n = nuke.toNode(s)
|
|
try:
|
|
delete_placeholder_attributes(n)
|
|
except Exception:
|
|
pass
|
|
|
|
lastLoaded_names = []
|
|
for loadedNode in lastLoaded:
|
|
lastLoaded_names.append(loadedNode.name())
|
|
imprint(node, {'last_loaded': lastLoaded_names})
|
|
|
|
for n in lastLoaded:
|
|
refresh_node(n)
|
|
refresh_node(node)
|
|
if 'builder_type' not in n.knobs().keys():
|
|
attributes = get_placeholder_attributes(node, True)
|
|
imprint(n, attributes)
|
|
imprint(n, {'is_placeholder': False})
|
|
hide_placeholder_attributes(n)
|
|
n.knob('is_placeholder').setVisible(False)
|
|
imprint(n, {'x': node.xpos(), 'y': node.ypos()})
|
|
n.knob('x').setVisible(False)
|
|
n.knob('y').setVisible(False)
|
|
nuke.delete(node)
|
|
|
|
|
|
class NukePlaceholder(AbstractPlaceholder):
|
|
"""Concrete implementation of AbstractPlaceholder for Nuke
|
|
|
|
"""
|
|
|
|
optional_attributes = {'asset', 'subset', 'hierarchy'}
|
|
|
|
def get_data(self, node):
|
|
user_data = dict()
|
|
dictKnobs = node.knobs()
|
|
for attr in self.attributes.union(self.optional_attributes):
|
|
if attr in dictKnobs.keys():
|
|
user_data[attr] = dictKnobs[attr].getValue()
|
|
user_data['node'] = node
|
|
|
|
if 'nb_children' in dictKnobs.keys():
|
|
user_data['nb_children'] = int(dictKnobs['nb_children'].getValue())
|
|
else:
|
|
user_data['nb_children'] = 0
|
|
if 'siblings' in dictKnobs.keys():
|
|
user_data['siblings'] = dictKnobs['siblings'].values()
|
|
else:
|
|
user_data['siblings'] = []
|
|
|
|
fullName = node.fullName()
|
|
user_data['group_name'] = fullName.rpartition('.')[0]
|
|
user_data['last_loaded'] = []
|
|
user_data['delete'] = False
|
|
self.data = user_data
|
|
|
|
def parent_in_hierarchy(self, containers):
|
|
return
|
|
|
|
def create_sib_copies(self):
|
|
""" creating copies of the palce_holder siblings (the ones who were
|
|
loaded with it) for the new nodes added
|
|
|
|
Returns :
|
|
copies (dict) : with copied nodes names and their copies
|
|
"""
|
|
|
|
copies = {}
|
|
siblings = get_nodes_from_names(self.data['siblings'])
|
|
for n in siblings:
|
|
reset_selection()
|
|
n.setSelected(True)
|
|
nuke.nodeCopy("%clipboard%")
|
|
reset_selection()
|
|
nuke.nodePaste("%clipboard%")
|
|
new_node = nuke.selectedNodes()[0]
|
|
x_init = int(new_node.knob('x_init').getValue())
|
|
y_init = int(new_node.knob('y_init').getValue())
|
|
new_node.setXYpos(x_init, y_init)
|
|
if isinstance(new_node, nuke.BackdropNode):
|
|
w_init = new_node.knob('w_init').getValue()
|
|
h_init = new_node.knob('h_init').getValue()
|
|
new_node.knob('bdwidth').setValue(w_init)
|
|
new_node.knob('bdheight').setValue(h_init)
|
|
refresh_node(n)
|
|
|
|
if 'id_rep' in n.knobs().keys():
|
|
n.removeKnob(n.knob('id_rep'))
|
|
copies[n.name()] = new_node
|
|
return copies
|
|
|
|
def fix_z_order(self):
|
|
"""
|
|
fix the problem of z_order when a backdrop is loaded
|
|
"""
|
|
orders_bd = []
|
|
nodes_loaded = self.data['last_loaded']
|
|
for n in nodes_loaded:
|
|
if isinstance(n, nuke.BackdropNode):
|
|
orders_bd.append(n.knob("z_order").getValue())
|
|
|
|
if orders_bd:
|
|
|
|
min_order = min(orders_bd)
|
|
siblings = self.data["siblings"]
|
|
|
|
orders_sib = []
|
|
for s in siblings:
|
|
n = nuke.toNode(s)
|
|
if isinstance(n, nuke.BackdropNode):
|
|
orders_sib.append(n.knob("z_order").getValue())
|
|
if orders_sib:
|
|
max_order = max(orders_sib)
|
|
for n in nodes_loaded:
|
|
if isinstance(n, nuke.BackdropNode):
|
|
z_order = n.knob("z_order").getValue()
|
|
n.knob("z_order").setValue(
|
|
z_order + max_order - min_order + 1)
|
|
|
|
def update_nodes(self, nodes, considered_nodes, offset_y=None):
|
|
""" Adjust backdrop nodes dimensions and positions considering some nodes
|
|
sizes
|
|
|
|
Arguments:
|
|
nodes (list): list of nodes to update
|
|
considered_nodes (list) : list of nodes to consider while updating
|
|
positions and dimensions
|
|
offset (int) : distance between copies
|
|
"""
|
|
node = self.data['node']
|
|
|
|
min_x, min_y, max_x, max_y = get_extremes(considered_nodes)
|
|
|
|
diff_x = diff_y = 0
|
|
contained_nodes = [] # for backdrops
|
|
|
|
if offset_y is None:
|
|
width_ph = node.screenWidth()
|
|
height_ph = node.screenHeight()
|
|
diff_y = max_y - min_y - height_ph
|
|
diff_x = max_x - min_x - width_ph
|
|
contained_nodes = [node]
|
|
min_x = node.xpos()
|
|
min_y = node.ypos()
|
|
else:
|
|
siblings = get_nodes_from_names(self.data['siblings'])
|
|
minX, _, maxX, _ = get_extremes(siblings)
|
|
diff_y = max_y - min_y + 20
|
|
diff_x = abs(max_x - min_x - maxX + minX)
|
|
contained_nodes = considered_nodes
|
|
|
|
if diff_y > 0 or diff_x > 0:
|
|
for n in nodes:
|
|
refresh_node(n)
|
|
if n != node and n not in considered_nodes:
|
|
|
|
if not isinstance(n, nuke.BackdropNode)\
|
|
or isinstance(n, nuke.BackdropNode)\
|
|
and not set(contained_nodes) <= set(n.getNodes()):
|
|
if offset_y is None and n.xpos() >= min_x:
|
|
n.setXpos(n.xpos() + diff_x)
|
|
|
|
if n.ypos() >= min_y:
|
|
n.setYpos(n.ypos() + diff_y)
|
|
|
|
else:
|
|
width = n.screenWidth()
|
|
height = n.screenHeight()
|
|
n.knob("bdwidth").setValue(width + diff_x)
|
|
n.knob("bdheight").setValue(height + diff_y)
|
|
|
|
refresh_node(n)
|
|
|
|
def imprint_inits(self):
|
|
"""
|
|
add initial positions and dimensions to the attributes
|
|
"""
|
|
for n in nuke.allNodes():
|
|
refresh_node(n)
|
|
imprint(n, {'x_init': n.xpos(), 'y_init': n.ypos()})
|
|
n.knob('x_init').setVisible(False)
|
|
n.knob('y_init').setVisible(False)
|
|
width = n.screenWidth()
|
|
height = n.screenHeight()
|
|
if 'bdwidth' in n.knobs().keys():
|
|
imprint(n, {'w_init': width, 'h_init': height})
|
|
n.knob('w_init').setVisible(False)
|
|
n.knob('h_init').setVisible(False)
|
|
refresh_node(n)
|
|
|
|
def imprint_siblings(self):
|
|
"""
|
|
- add siblings names to placeholder attributes (nodes loaded with it)
|
|
- add Id to the attributes of all the other nodes
|
|
"""
|
|
|
|
nodes_loaded = self.data['last_loaded']
|
|
d = {"id_rep": str(self.data['_id'])}
|
|
|
|
for n in nodes_loaded:
|
|
if "builder_type" in n.knobs().keys()\
|
|
and ('is_placeholder' not in n.knobs().keys()
|
|
or 'is_placeholder' in n.knobs().keys()
|
|
and n.knob('is_placeholder').value()):
|
|
|
|
siblings = list(set(nodes_loaded) - set([n]))
|
|
siblings_name = get_names_from_nodes(siblings)
|
|
siblings = {"siblings": siblings_name}
|
|
imprint(n, siblings)
|
|
|
|
elif 'builder_type' not in n.knobs().keys():
|
|
# save the id of representation for all imported nodes
|
|
imprint(n, d)
|
|
n.knob('id_rep').setVisible(False)
|
|
refresh_node(n)
|
|
|
|
def set_loaded_connections(self):
|
|
"""
|
|
set inputs and outputs of loaded nodes"""
|
|
|
|
node = self.data['node']
|
|
input, output = get_io(self.data['last_loaded'])
|
|
for n in node.dependent():
|
|
for i in range(n.inputs()):
|
|
if n.input(i) == node:
|
|
n.setInput(i, output)
|
|
|
|
for n in node.dependencies():
|
|
for i in range(node.inputs()):
|
|
if node.input(i) == n:
|
|
input.setInput(0, n)
|
|
|
|
def set_copies_connections(self, copies):
|
|
"""
|
|
set inputs and outputs of the copies
|
|
|
|
Arguments :
|
|
copies (dict) : with copied nodes names and their copies
|
|
"""
|
|
input, output = get_io(self.data['last_loaded'])
|
|
siblings = get_nodes_from_names(self.data['siblings'])
|
|
inp, out = get_io(siblings)
|
|
inp_copy, out_copy = (copies[inp.name()], copies[out.name()])
|
|
|
|
for node_init in siblings:
|
|
if node_init != out:
|
|
node_copy = copies[node_init.name()]
|
|
for n in node_init.dependent():
|
|
for i in range(n.inputs()):
|
|
if n.input(i) == node_init:
|
|
if n in siblings:
|
|
copies[n.name()].setInput(i, node_copy)
|
|
else:
|
|
input.setInput(0, node_copy)
|
|
|
|
for n in node_init.dependencies():
|
|
for i in range(node_init.inputs()):
|
|
if node_init.input(i) == n:
|
|
if node_init == inp:
|
|
inp_copy.setInput(i, n)
|
|
elif n in siblings:
|
|
node_copy.setInput(i, copies[n.name()])
|
|
else:
|
|
node_copy.setInput(i, output)
|
|
|
|
inp.setInput(0, out_copy)
|
|
|
|
def move_to_placeholder_group(self, nodes_loaded):
|
|
"""
|
|
opening the placeholder's group and copying loaded nodes in it.
|
|
|
|
Returns :
|
|
nodes_loaded (list): the new list of pasted nodes
|
|
"""
|
|
|
|
groups_name = self.data['group_name']
|
|
reset_selection()
|
|
select_nodes(nodes_loaded)
|
|
if groups_name:
|
|
nuke.nodeCopy("%clipboard%")
|
|
for n in nuke.selectedNodes():
|
|
nuke.delete(n)
|
|
group = nuke.toNode(groups_name)
|
|
group.begin()
|
|
nuke.nodePaste("%clipboard%")
|
|
nodes_loaded = nuke.selectedNodes()
|
|
return nodes_loaded
|
|
|
|
def clean(self):
|
|
print("cleaaaaar")
|
|
# deselect all selected nodes
|
|
node = self.data['node']
|
|
|
|
# getting the latest nodes added
|
|
nodes_init = self.data["nodes_init"]
|
|
nodes_loaded = list(set(nuke.allNodes()) - set(nodes_init))
|
|
print(nodes_loaded)
|
|
if nodes_loaded:
|
|
self.data['delete'] = True
|
|
else:
|
|
return
|
|
nodes_loaded = self.move_to_placeholder_group(nodes_loaded)
|
|
self.data['last_loaded'] = nodes_loaded
|
|
refresh_nodes(nodes_loaded)
|
|
|
|
# positioning of the loaded nodes
|
|
min_x, min_y, _, _ = get_extremes(nodes_loaded)
|
|
for n in nodes_loaded:
|
|
xpos = (n.xpos() - min_x) + node.xpos()
|
|
ypos = (n.ypos() - min_y) + node.ypos()
|
|
n.setXYpos(xpos, ypos)
|
|
refresh_nodes(nodes_loaded)
|
|
|
|
self.fix_z_order() # fix the problem of z_order for backdrops
|
|
self.imprint_siblings()
|
|
|
|
if self.data['nb_children'] == 0:
|
|
# save initial nodes postions and dimensions, update them
|
|
# and set inputs and outputs of loaded nodes
|
|
|
|
self.imprint_inits()
|
|
self.update_nodes(nuke.allNodes(), nodes_loaded)
|
|
self.set_loaded_connections()
|
|
|
|
elif self.data['siblings']:
|
|
# create copies of placeholder siblings for the new loaded nodes,
|
|
# set their inputs and outpus and update all nodes positions and
|
|
# dimensions and siblings names
|
|
|
|
siblings = get_nodes_from_names(self.data['siblings'])
|
|
refresh_nodes(siblings)
|
|
copies = self.create_sib_copies()
|
|
new_nodes = list(copies.values()) # copies nodes
|
|
self.update_nodes(new_nodes, nodes_loaded)
|
|
node.removeKnob(node.knob('siblings'))
|
|
new_nodes_name = get_names_from_nodes(new_nodes)
|
|
imprint(node, {'siblings': new_nodes_name})
|
|
self.set_copies_connections(copies)
|
|
|
|
self.update_nodes(nuke.allNodes(),
|
|
new_nodes + nodes_loaded, 20)
|
|
|
|
new_siblings = get_names_from_nodes(new_nodes)
|
|
self.data['siblings'] = new_siblings
|
|
|
|
else:
|
|
# if the placeholder doesn't have siblings, the loaded
|
|
# nodes will be placed in a free space
|
|
|
|
xpointer, ypointer = find_free_space_to_paste_nodes(
|
|
nodes_loaded, direction="bottom", offset=200
|
|
)
|
|
n = nuke.createNode("NoOp")
|
|
reset_selection()
|
|
nuke.delete(n)
|
|
for n in nodes_loaded:
|
|
xpos = (n.xpos() - min_x) + xpointer
|
|
ypos = (n.ypos() - min_y) + ypointer
|
|
n.setXYpos(xpos, ypos)
|
|
|
|
self.data['nb_children'] += 1
|
|
reset_selection()
|
|
# go back to root group
|
|
nuke.root().begin()
|
|
|
|
def convert_to_db_filters(self, current_asset, linked_asset):
|
|
if self.data['builder_type'] == "context_asset":
|
|
return [{
|
|
"type": "representation",
|
|
"context.asset": {
|
|
"$eq": current_asset, "$regex": self.data['asset']},
|
|
"context.subset": {"$regex": self.data['subset']},
|
|
"context.hierarchy": {"$regex": self.data['hierarchy']},
|
|
"context.representation": self.data['representation'],
|
|
"context.family": self.data['family'],
|
|
}]
|
|
|
|
elif self.data['builder_type'] == "linked_asset":
|
|
return [{
|
|
"type": "representation",
|
|
"context.asset": {
|
|
"$eq": asset_name, "$regex": self.data['asset']},
|
|
"context.subset": {"$regex": self.data['subset']},
|
|
"context.hierarchy": {"$regex": self.data['hierarchy']},
|
|
"context.representation": self.data['representation'],
|
|
"context.family": self.data['family'],
|
|
} for asset_name in linked_asset]
|
|
|
|
else:
|
|
return [{
|
|
"type": "representation",
|
|
"context.asset": {"$regex": self.data['asset']},
|
|
"context.subset": {"$regex": self.data['subset']},
|
|
"context.hierarchy": {"$regex": self.data['hierarchy']},
|
|
"context.representation": self.data['representation'],
|
|
"context.family": self.data['family'],
|
|
}]
|
|
|
|
def err_message(self):
|
|
return (
|
|
"Error while trying to load a representation.\n"
|
|
"Either the subset wasn't published or the template is malformed."
|
|
"\n\n"
|
|
"Builder was looking for:\n{attributes}".format(
|
|
attributes="\n".join([
|
|
"{}: {}".format(key.title(), value)
|
|
for key, value in self.data.items()]
|
|
)
|
|
)
|
|
)
|