Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement #474 #504

Open
wants to merge 39 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
919f1ca
Fix TypeError: 'Node' object does not support item assignment
davidlatwe Dec 27, 2019
56f55fc
Push root logging message level to INFO level
davidlatwe Dec 27, 2019
1ff01fa
Implement `lsattr`
davidlatwe Jan 6, 2020
9df1197
Implement node avalonId set/get and copies finder
davidlatwe Jan 6, 2020
75e82c8
Refactored `containerise` and `ls`
davidlatwe Jan 6, 2020
3c685d0
Remove container data validation
davidlatwe Jan 6, 2020
c84473b
Implement node sync
davidlatwe Jan 6, 2020
98d3562
Fix Qt4 compat
davidlatwe Jan 6, 2020
134b4cd
Collect containerized nodes while parsing container
davidlatwe Jan 6, 2020
bd88c1a
Provide a way to keep old style `containerise`
davidlatwe Jan 6, 2020
05b5086
Vendorize `knobby`
davidlatwe Jan 6, 2020
f217fa4
Replace with `vendor.knobby`
davidlatwe Jan 6, 2020
875f83f
Adopt `knobby`
davidlatwe Jan 6, 2020
817133c
Merge branch 'knobby' into imp#474
davidlatwe Jan 6, 2020
f11e595
Drop 'avalonDataGroup' knob
davidlatwe Jan 6, 2020
90f3602
Merge remote-tracking branch 'upstream/master' into imp#474
davidlatwe Jan 7, 2020
78472b9
Adopt #501
davidlatwe Jan 7, 2020
6a11ea7
Able to opt-out container node in root
davidlatwe Jan 7, 2020
e1a6468
Fix copies finder
davidlatwe Jan 7, 2020
e564034
Cosmetic, by Black
davidlatwe Jan 7, 2020
6125b2d
Update nuke.vendor.knobby
davidlatwe Jan 8, 2020
0befc94
Update nuke.vendor.knobby
davidlatwe Jan 9, 2020
12a5722
Update nuke.vendor.knobby
davidlatwe Jan 9, 2020
b07df50
Merge branch 'master' into imp#474
davidlatwe Jan 14, 2020
a19b066
Change env var name to `AVALON_NUKE_OLD_CONTAINER`
davidlatwe Jan 14, 2020
0aa0d65
Update nuke.vendor.knobby
davidlatwe Feb 4, 2020
b0c7f55
Remove duplicated function call
davidlatwe Feb 4, 2020
97f7754
Re-implement task label update
davidlatwe Feb 4, 2020
5c651d8
Merge remote-tracking branch 'upstream/master' into imp#474
davidlatwe Mar 27, 2020
7eb0536
Fix finding copies in nested group
davidlatwe Apr 22, 2020
b1fd067
Fix finding container members in nested group
davidlatwe Apr 22, 2020
7819599
Add `role` into dataChanged signal
davidlatwe Apr 22, 2020
be839a6
Expose find_copies and sync_copies
tokejepsen Jun 1, 2020
64d563c
find_copies enhancements
tokejepsen Jun 1, 2020
d8438ae
Merge pull request #2 from tokejepsen/patch-5
davidlatwe Jul 22, 2020
59c1ccc
Merge pull request #3 from tokejepsen/patch-6
davidlatwe Jul 22, 2020
7374745
Fix knob KeyError when syncing in multi-view session
davidlatwe Jul 22, 2020
3129b24
Avoid syncing node name when force enabled
davidlatwe Jul 22, 2020
a6cd92b
Fix for accidently committing test code..
davidlatwe Jul 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
407 changes: 174 additions & 233 deletions avalon/nuke/lib.py

Large diffs are not rendered by default.

141 changes: 117 additions & 24 deletions avalon/nuke/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from pyblish import api as pyblish

from . import lib, command
from ..lib import find_submodule
from .. import api
from ..vendor.Qt import QtWidgets
from ..pipeline import AVALON_CONTAINER_ID
Expand All @@ -18,8 +19,11 @@
self = sys.modules[__name__]
self._parent = None # Main Window cache

AVALON_CONTAINERS = "AVALON_CONTAINERS"
AVALON_CONFIG = os.environ["AVALON_CONFIG"]

USE_OLD_CONTAINER = os.getenv("AVALON_NUKE_OLD_CONTAINER")


def reload_pipeline():
"""Attempt to reload pipeline at run-time.
Expand Down Expand Up @@ -53,44 +57,108 @@ def reload_pipeline():
_register_events()


def containerise(node,
name,
def containerise(name,
namespace,
nodes,
context,
loader=None,
data=None):
data=None,
no_backdrop=True,
suffix="CON"):
"""Bundle `node` into an assembly and imprint it with metadata

Containerisation enables a tracking of version, author and origin
for loaded assets.

Arguments:
node (nuke.Node): Nuke's node object to imprint as container
name (str): Name of resulting assembly
namespace (str): Namespace under which to host container
nodes (list): A list of `nuke.Node` object to containerise
context (dict): Asset information
loader (str, optional): Name of node used to produce this container.
data (dict, optional): Additional data to imprint.
no_backdrop (bool, optional): No container(backdrop) node presented.
suffix (str, optional): Suffix of container, defaults to `_CON`.

Returns:
node (nuke.Node): containerised nuke's node object

"""
from nukescripts import autoBackdrop

if isinstance(name, nuke.Node):
# For compatibling with old style args
# containerise(node, name, namespace, context, ...)
_ = nodes
nodes = [name]
name = namespace
namespace = _

data = OrderedDict(
[
("schema", "avalon-core:container-2.0"),
("id", AVALON_CONTAINER_ID),
("name", name),
("namespace", namespace),
("loader", str(loader)),
("representation", context["representation"]["_id"]),
("representation", str(context["representation"]["_id"])),
],

**data or dict()
)

lib.set_avalon_knob_data(node, data)
if USE_OLD_CONTAINER:
node = nodes[0]
lib.set_avalon_knob_data(node, data)
return node

return node
# New style

container_color = data.pop("color", int("0x7A7A7AFF", 16))
container_name = "%s_%s_%s" % (namespace, name, suffix)

lib.reset_selection()
lib.select_nodes(nodes)

container = autoBackdrop()
container.setName(container_name)
container["label"].setValue(container_name)
container["tile_color"].setValue(container_color)
container["selected"].setValue(True)
# (NOTE) Backdrop may not fully cover if there's only one node, so we
# expand backdrop a bit ot ensure that.
container["bdwidth"].setValue(container["bdwidth"].value() + 100)
container["bdheight"].setValue(container["bdheight"].value() + 100)
container["xpos"].setValue(container["xpos"].value() - 50)

lib.set_avalon_knob_data(container, data)

container_id = lib.set_id(container)
for node in nodes:
lib.set_id(node, container_id=container_id)

# Containerising

nuke.nodeCopy("_containerizing_")

main_container = nuke.toNode(AVALON_CONTAINERS)
if main_container is None:
main_container = nuke.createNode("Group")
main_container.setName(AVALON_CONTAINERS)
main_container["postage_stamp"].setValue(True)
main_container["note_font_size"].setValue(40)
main_container["tile_color"].setValue(int("0x283648FF", 16))
main_container["xpos"].setValue(-500)
main_container["ypos"].setValue(-500)

main_container.begin()
nuke.nodePaste("_containerizing_")
main_container.end()

if no_backdrop:
nuke.delete(container)

return container


def parse_container(node):
Expand All @@ -105,20 +173,17 @@ def parse_container(node):
dict: The container schema data for this container node.

"""
data = lib.read(node)

# (TODO) Remove key validation when `ls` has re-implemented.
#
# If not all required data return the empty container
required = ["schema", "id", "name",
"namespace", "loader", "representation"]
if not all(key in data for key in required):
return
data = lib.get_avalon_knob_data(node)

# Store the node's name
data["objectName"] = node["name"].value()
# Store reference to the node object
data["_node"] = node
# Get containerized nodes
if node.fullName() == "%s.%s" % (AVALON_CONTAINERS, node.name()):
data["_members"] = lib.lsattr("avalon:containerId",
value=data["avalonId"],
group=nuke.toNode(AVALON_CONTAINERS))

return data

Expand All @@ -143,6 +208,10 @@ def update_container(node, keys=None):
if not container:
raise TypeError("Not a valid container node.")

# Remove unprintable entries
container.pop("_node", None)
container.pop("_members", None)

container.update(keys)
node = lib.set_avalon_knob_data(node, container)

Expand Down Expand Up @@ -187,6 +256,24 @@ def process(self):
return instance


def _ls1():
"""Yields all nodes for listing Avalon containers"""
for node in nuke.allNodes(recurseGroups=False):
yield node


def _ls2():
"""Yields nodes that has 'avalon:id' knob from AVALON_CONTAINERS"""
for node in nuke.allNodes("BackdropNode",
group=nuke.toNode(AVALON_CONTAINERS)):
knob = node.fullName() + ".avalon:id"
if nuke.exists(knob) and nuke.knob(knob) == AVALON_CONTAINER_ID:
yield node


_ls = _ls1 if USE_OLD_CONTAINER else _ls2


def ls():
"""List available containers.

Expand All @@ -197,16 +284,22 @@ def ls():
See the `container.json` schema for details on how it should look,
and the Maya equivalent, which is in `avalon.maya.pipeline`
"""
all_nodes = nuke.allNodes(recurseGroups=False)
config_host = find_submodule(api.registered_config(), "nuke")
has_metadata_collector = hasattr(config_host, "collect_container_metadata")

container_nodes = _ls()

for container in container_nodes:
data = parse_container(container)
if data is None:
continue

# TODO: add readgeo, readcamera, readimage
nodes = [n for n in all_nodes]
# Collect custom data if attribute is present
if has_metadata_collector:
metadata = config_host.collect_container_metadata(container)
data.update(metadata)

for n in nodes:
log.debug("name: `{}`".format(n.name()))
container = parse_container(n)
if container:
yield container
yield data


def install(config):
Expand Down
Empty file added avalon/nuke/vendor/__init__.py
Empty file.
15 changes: 15 additions & 0 deletions avalon/nuke/vendor/knobby/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
__version__ = "0.1.4"

from . import parser

try:
from . import util
except ImportError:
# No nuke module
util = None


__all__ = [
"parser",
"util",
]
142 changes: 142 additions & 0 deletions avalon/nuke/vendor/knobby/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import re
from collections import deque


def parse(script):
"""Parse Nuke node's TCL script string into nested list structure

Args:
script (str): Node knobs TCL script string

Returns:
Tablet: A list containing knob scripts or tab knobs that has parsed
into list

"""
queue = deque(script.split("\n"))
tab = Tablet()
tab.consume(queue)
return tab


TYPE_NODE = 0
TYPE_KNOBS = 1
TYPE_GROUP = -2
TYPE_KNOBS_CLOSE = -1
TYPE_GROUP_CLOSE = -3

TAB_PATTERN = re.compile(
'addUserKnob {20 '
'(?P<name>\\S+)'
'(| l (?P<label>".*"|\\S+))'
'(| n (?P<type>1|-[1-3]))'
'}'
)


class Tablet(list):
"""
"""

def __init__(self, name=None, label=None, type=None, parent=None):
self.name = name
self.label = label
self.type = type
self.parent = parent
self[:] = list()

self.tab_closed = False
self.not_in_group = type is not None and type != TYPE_GROUP

def __eq__(self, other):
return "@" + self.name == other

def find_tab(self, name):
"""Return child tab if exists in list"""
return next((item for item in self if item == "@" + name), None)

def consume(self, queue):
"""
"""
def under_root():
return getattr(self.parent, "parent", None) is not None

def ignore_tab_value(name):
if queue and queue[0] == "%s 1" % name:
queue.popleft()

while queue:
line = queue.popleft()
if not line:
continue

matched = TAB_PATTERN.search(line)
if matched:
tab_profile = matched.groupdict()
name = tab_profile["name"]
label = tab_profile["label"]
type = int(tab_profile["type"] or 0)
else:
self.append(line)
continue

ignore_tab_value(name)

if type in (TYPE_KNOBS_CLOSE, TYPE_GROUP_CLOSE):
self.parent.tab_closed = True
return

elif type == TYPE_NODE:
if self.not_in_group:
queue.appendleft(line)
return

tab = Tablet(name, label, type=type, parent=self)
self.append(tab)

tab.consume(queue)

if self.tab_closed and under_root():
return

def merge(self, other):
"""
"""
for item in other:
if isinstance(item, Tablet):
tab = self.find_tab(item.name)
if tab is not None:
tab.merge(item)
continue

self.append(item)

def to_script(self, join=True):
"""
"""
script = list()
for item in self:
if isinstance(item, Tablet):
sub_script = item.to_script(join=False)

line = "addUserKnob {20 " + item.name
if item.label is not None:
line += " l " + item.label

if item.type == TYPE_NODE:
sub_script.insert(0, line + "}")

elif item.type == TYPE_KNOBS:
sub_script.insert(0, line + " n 1}")
sub_script.append(line + " n -1}")

elif item.type == TYPE_GROUP:
sub_script.insert(0, line + " n -2}")
sub_script.append(line + " n -3}")

script += sub_script
continue

script.append(item)

return "\n".join(script) if join else script
Loading