Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # -*- coding: utf-8 -*-
- #
- # Bypass Indie Nuke's Python 10-node limitation by using TCL to access the nodes.
- # Mimics common nuke.Node and nuke.Knob methods/properties for Tcl nodes
- #
- # Hey the Foundry - why not just fix the limitation instead of forcing
- # everyone to write workarounds like this?
- # A healhy indie ecosystem is good for everyone.
- # Increased productivity = more users = more sales = more happy customers.
- # This file is provided as-is, without warranty of any kind.
- # Use at your own risk. Test before using in production.
- # EXAMPLE USAGE:
- '''
- import importlib, tclnode_proxy as P
- group=P.selectedNode()
- nodes = P.nodesInGroup(group=group, class_filter="Write")
- nodes = nodes + P.nodesInGroup(group=group, class_filter="Read")
- fr_node = P.toNode("FrameRange1", group=group)
- start = int ( fr_node.knob('first_frame').value() )
- end = int ( fr_node.knob('last_frame').value() )
- print('Set frame range from ', start, 'to', end)
- group.knob('lifetimeStart').setValue( start )
- group.knob('lifetimeEnd').setValue( end )
- group.knob('useLifetime').setValue(1)
- group.knob('postage_stamp').setValue(0)
- fr_node.knob('first_frame').setValue('parent.lifetimeStart')
- fr_node.knob('last_frame').setValue('parent.lifetimeEnd')
- for n in nodes:
- print(n)
- n.knob('first').setValue('parent.lifetimeStart')
- n.knob('last').setValue('parent.lifetimeEnd')
- n.knob('postage_stamp').setValue(0)
- '''
- import nuke
- from contextlib import contextmanager
- __all__ = [
- "Node", "Knob",
- "toNode", "allNodes", "selectedNode", "selectedNodes",
- "nodesInGroup", "nodesRecursive",
- ]
- # ---------------- low-level Tcl utils ----------------
- def _tcl(cmd):
- return nuke.tcl(cmd)
- def _brace(s):
- if s is None:
- return "{}"
- s = str(s).replace("{", r"\{").replace("}", r"\}")
- return "{" + s + "}"
- def _split_words(s):
- return [w for w in s.split() if w]
- def _handle_dot_knob(handle, knob=None):
- """Make a single braced token like {node123.file} or {node123}."""
- if knob is None:
- return _brace(handle)
- return _brace("%s.%s" % (handle, knob))
- @contextmanager
- def _tcl_scope(group_path):
- """Temporarily push into a group for Tcl commands."""
- if group_path:
- _tcl("push " + _brace(group_path))
- try:
- yield
- finally:
- _tcl("pop")
- else:
- yield
- def _nodes_in_group(group):
- """Return list of internal Tcl handles in a group/root across builds."""
- variants = []
- if group:
- variants.append("nodes " + _brace(group))
- variants.append("push " + _brace(group) + "; nodes; pop")
- else:
- variants.append("nodes root")
- variants.append("nodes .")
- variants.append("nodes")
- for cmd in variants:
- try:
- out = _tcl(cmd)
- return _split_words(out)
- except Exception:
- continue
- return []
- def _knob_names_union(handle, group=None):
- """
- Try several Tcl 'knobs' variants and union the names.
- This makes enumeration robust across Nuke versions/builds.
- """
- variants = [
- "knobs " + _brace(handle), # default
- "knobs -a " + _brace(handle), # 'all' (some builds)
- "knobs -A " + _brace(handle), # 'All' (older/alt flag)
- "knobs -all " + _brace(handle), # explicit 'all'
- "knobs -internal " + _brace(handle), # include hidden/internal
- "knobs -u " + _brace(handle), # user knobs only
- ]
- names = set()
- with _tcl_scope(group):
- for cmd in variants:
- try:
- out = _tcl(cmd)
- if out:
- for w in out.split():
- names.add(w)
- except Exception:
- # Ignore unknown flags on this build
- continue
- return sorted(names)
- # ---------------- Knob wrapper (Tcl-only) ----------------
- class Knob(object):
- """Tcl-only knob wrapper mimicking common nuke.Knob methods."""
- __slots__ = ("_handle", "_name", "_group")
- def __init__(self, node_handle, knob_name, group=None):
- self._handle = node_handle
- self._name = knob_name
- self._group = group
- def __repr__(self):
- return "<Knob %s on %s>" % (self._name, self._handle)
- def name(self):
- return self._name
- # --- values ---
- def value(self):
- with _tcl_scope(self._group):
- return _tcl("value " + _handle_dot_knob(self._handle, self._name))
- def getValue(self):
- return self.value()
- def setValue(self, v):
- with _tcl_scope(self._group):
- if isinstance(v, bool):
- v = "1" if v else "0"
- _tcl("knob " + _handle_dot_knob(self._handle, self._name) + " " + _brace(v))
- # --- typed helpers (optional) ---
- def valueAsInt(self):
- try: return int(float(self.value()))
- except Exception: return 0
- def valueAsFloat(self):
- try: return float(self.value())
- except Exception: return 0.0
- def valueAsBool(self):
- return self.value().strip().lower() in ("1", "true")
- # ---------------- Node proxy (Tcl-only) ----------------
- class Node(object):
- """
- Tcl-only proxy that looks/feels like nuke.Node for common tasks.
- Construct via module helpers (toNode/allNodes) or from a handle.
- """
- __slots__ = ("_handle", "_group")
- def __init__(self, handle, group=None):
- self._handle = handle # internal Tcl id (e.g. node1abc...)
- self._group = group
- # --- representation ---
- def __repr__(self):
- try:
- return "<Node %s [%s] handle=%s>" % (self.name(), self.Class(), self._handle)
- except Exception:
- return "<Node handle=%s>" % (self._handle,)
- # --- identity / basics ---
- def Class(self):
- with _tcl_scope(self._group):
- return _tcl("class " + _brace(self._handle)).strip()
- def name(self):
- with _tcl_scope(self._group):
- return _tcl("value " + _handle_dot_knob(self._handle, "name")).strip()
- def setName(self, new_name):
- with _tcl_scope(self._group):
- _tcl("knob " + _handle_dot_knob(self._handle, "name") + " " + _brace(new_name))
- def fullName(self):
- """Best-effort path 'Group/Sub/DisplayName'."""
- nm = self.name()
- return (self._group + "/" + nm) if self._group else nm
- def exists(self):
- try:
- self.Class()
- return True
- except Exception:
- return False
- # --- knobs API ---
- def knobs(self):
- """Return dict of {knob_name: Knob} like nuke.Node.knobs()."""
- names = _knob_names_union(self._handle, group=self._group)
- return {k: Knob(self._handle, k, self._group) for k in names}
- def knob(self, name):
- """Return a Knob wrapper or None if missing."""
- if not name:
- return None
- with _tcl_scope(self._group):
- try:
- # Probe by reading (empty ok); if it errors, knob doesn't exist
- _ = _tcl("value " + _handle_dot_knob(self._handle, name))
- return Knob(self._handle, name, self._group)
- except Exception:
- return None
- def __getitem__(self, name):
- """node['file'] -> Knob wrapper (like real Nuke)."""
- k = self.knob(name)
- if k is None:
- raise KeyError("Knob not found: %s" % name)
- return k
- def __str__(self):
- return self.name()
- # --- convenience (common Node methods) ---
- def setXpos(self, x):
- self["xpos"].setValue(int(x))
- def setYpos(self, y):
- self["ypos"].setValue(int(y))
- def xpos(self): return self["xpos"].valueAsInt()
- def ypos(self): return self["ypos"].valueAsInt()
- def hideInputs(self, on=True):
- self["hide_input"].setValue(bool(on))
- def error(self):
- """Mirror Node.error() (returns string; may be empty)."""
- try:
- return self["error"].value()
- except Exception:
- return ""
- # --- execute/render (Write or executable nodes) ---
- def execute(self, first=None, last=None, incr=1, ignore_errors=False):
- """Execute this node over a frame range (falls back to script range)."""
- f, l = _resolve_range_for(self, first, last, self._group)
- return execute_nodes(self, first=f, last=l, incr=incr, group=self._group, ignore_errors=ignore_errors)
- # ---------------- discovery / module-level helpers ----------------
- def nodesInGroup(group=None, class_filter=None, include_disabled=True):
- """Return Node proxies (Tcl handles) in a group/root."""
- handles = _nodes_in_group(group)
- nodes = [Node(h, group) for h in handles]
- if class_filter:
- kept = []
- with _tcl_scope(group):
- for n in nodes:
- try:
- if _tcl("class " + _brace(n._handle)).strip() == class_filter:
- kept.append(n)
- except Exception:
- pass
- nodes = kept
- if not include_disabled:
- nodes = [n for n in nodes if not (n.knob("disable") and n.knob("disable").valueAsBool())]
- return nodes
- def nodesRecursive(start_group=None, class_filter=None, include_disabled=True):
- out = []
- def walk(gpath):
- current = nodesInGroup(group=gpath, class_filter=class_filter, include_disabled=include_disabled)
- for n in current:
- out.append(n)
- if n.Class() in ("Group", "LiveGroup", "Gizmo"):
- walk(n.fullName())
- walk(start_group)
- return out
- def allNodes(node_class=None, group=None):
- """Like nuke.allNodes(className=None, group=None)."""
- return nodesInGroup(group=group, class_filter=node_class)
- def toNode(display_name, group=None):
- """Like nuke.toNode(name): first match by DISPLAY name in group/root."""
- for n in nodesInGroup(group=group):
- if n.name() == display_name:
- return n
- return None
- def selectedNodes(group=None, class_filter=None):
- out = []
- for n in nodesInGroup(group=group, class_filter=class_filter):
- k = n.knob("selected")
- if k and k.valueAsBool():
- out.append(n)
- return out
- def selectedNode(group=None):
- arr = selectedNodes(group=group)
- return arr[0] if arr else None
- # ---------------- execute helpers (Tcl 'execute') ----------------
- def _int_or_none(s):
- try:
- return int(float(str(s).strip()))
- except Exception:
- return None
- def _script_frame_range():
- f = _int_or_none(_tcl("value root.first_frame"))
- l = _int_or_none(_tcl("value root.last_frame"))
- return (f or 0, l or 0)
- def _node_limit_range(node, group=None):
- # Works for Write: use_limit/first/last
- token = node._handle if isinstance(node, Node) else str(node)
- with _tcl_scope(group):
- try:
- use = _tcl("value " + _handle_dot_knob(token, "use_limit")).strip().lower()
- if use in ("1", "true"):
- f = _int_or_none(_tcl("value " + _handle_dot_knob(token, "first")))
- l = _int_or_none(_tcl("value " + _handle_dot_knob(token, "last")))
- if f is not None and l is not None:
- return (f, l)
- except Exception:
- pass
- return (None, None)
- def _resolve_range_for(node, first, last, group):
- f = _int_or_none(first); l = _int_or_none(last)
- if f is not None and l is not None:
- return (f, l)
- f2, l2 = _node_limit_range(node, group=group)
- if f2 is not None and l2 is not None:
- return (f2, l2)
- return _script_frame_range()
- def _range_list(first, last, incr=1):
- first = int(first); last = int(last); incr = int(incr)
- return _brace("%d %d" % (first, last)) if incr == 1 else _brace("%d %d %d" % (first, last, incr))
- def _as_token(obj):
- if isinstance(obj, Node):
- return _brace(obj._handle)
- return _brace(str(obj))
- def execute_nodes(nodes, first=None, last=None, incr=1, group=None, ignore_errors=False):
- if not isinstance(nodes, (list, tuple)):
- nodes = [nodes]
- # Resolve range once (common case); advanced users can pass explicit first/last.
- f, l = _resolve_range_for(nodes[0], first, last, group)
- toks = " ".join(_as_token(n) for n in nodes)
- rng = _range_list(f, l, incr=incr)
- cmd = "execute " + toks + " " + rng
- with _tcl_scope(group):
- try:
- _tcl(cmd)
- except Exception as e:
- if not ignore_errors:
- raise
- return str(e)
Advertisement
Add Comment
Please, Sign In to add comment