1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297 |
- #
- #
- # Copyright (c) 2014, 2015, 2016, 2017 Psyop Media Company, LLC
- # See license.txt
- #
- #
-
- __version__ = "1.2.8"
-
- GIZMO_CHANNEL_KNOBS = [
- "in00", "in01", "in02", "in03",
- "in04", "in05", "in06", "in07",
- "in08", "in09", "in10", "in11"
- ]
- GIZMO_REMOVE_CHANNEL_KNOBS = [
- "remove00", "remove01", "remove02", "remove03",
- "remove04", "remove05", "remove06", "remove07"
- ]
- GIZMO_ADD_CHANNEL_KNOBS = [
- "add00", "add01", "add02", "add03",
- "add04", "add05", "add06", "add07"
- ]
-
- CRYPTO_METADATA_LEGAL_PREFIX = ["exr/cryptomatte/", "cryptomatte/"]
- CRYPTO_METADATA_DEFAULT_PREFIX = CRYPTO_METADATA_LEGAL_PREFIX[1]
-
- import nuke
- import struct
- import re
-
- def setup_cryptomatte_ui():
- if nuke.GUI:
- toolbar = nuke.menu("Nodes")
- menu = toolbar.addMenu("Cryptomatte", "cryptomatte_logo.png",index=-1)
- menu.addCommand("Cryptomatte", "import cryptomatte_utilities as cu; cu.cryptomatte_create_gizmo();")
- menu.addCommand("Decryptomatte All", "import cryptomatte_utilities as cu; cu.decryptomatte_all();")
- menu.addCommand("Decryptomatte Selection", "import cryptomatte_utilities as cu; cu.decryptomatte_selected();")
- menu.addCommand("Encryptomatte", "import cryptomatte_utilities as cu; cu.encryptomatte_create_gizmo();")
-
- def setup_cryptomatte():
- nuke.addOnCreate(lambda: cryptomatte_on_create_event(
- nuke.thisNode(), nuke.thisKnob()), nodeClass='Cryptomatte')
- nuke.addKnobChanged(lambda: cryptomatte_knob_changed_event(
- nuke.thisNode(), nuke.thisKnob()), nodeClass='Cryptomatte')
- nuke.addKnobChanged(lambda: encryptomatte_knob_changed_event(
- nuke.thisNode(), nuke.thisKnob()), nodeClass='Encryptomatte')
- nuke.addOnCreate(lambda: encryptomatte_on_create_event(
- nuke.thisNode(), nuke.thisKnob()), nodeClass='Encryptomatte')
-
-
- #############################################
- # Testing
- #############################################
-
- class CryptomatteTesting(object):
- """ Utility functions for manually running tests.
- Returns results if there are failures, otherwise None
-
- Arguments of these functions:
- test_filter -- (string) will be matched fnmatch style (* wildcards) to either the name of the TestCase
- class or test method.
-
- failfast -- (bool) will stop after a failure, and skip cleanup of the nodes that were created.
- """
-
- def get_all_unit_tests(self):
- import cryptomatte_utilities_tests as cu_tests
- return cu_tests.get_all_unit_tests()
-
- def get_all_nuke_tests(self):
- import cryptomatte_utilities_tests as cu_tests
- return cu_tests.get_all_nuke_tests()
-
- def run_unit_tests(self, test_filter="", failfast=False):
- import cryptomatte_utilities_tests as cu_tests
- return cu_tests.run_unit_tests(test_filter, failfast)
-
- def run_nuke_tests(self, test_filter="", failfast=False):
- import cryptomatte_utilities_tests as cu_tests
- return cu_tests.run_nuke_tests(test_filter, failfast)
-
- tests = CryptomatteTesting()
-
- #############################################
- # Hash to float
- #############################################
-
- try:
- # try with a fast c-implementation, if available ...
- import mmh3 as mmh3
- except ImportError:
- # ... otherwise fallback to the pure python version
- import pymmh3 as mmh3
-
- def mm3hash_float(name):
- hash_32 = mmh3.hash(name)
- exp = hash_32 >> 23 & 255
- if (exp == 0) or (exp == 255):
- hash_32 ^= 1 << 23
-
- packed = struct.pack('<L', hash_32 & 0xffffffff)
- return struct.unpack('<f', packed)[0]
-
-
- def single_precision(float_in):
- import array
- return array.array("f", [float_in])[0]
-
-
- def id_to_rgb(id):
- # This takes the hashed id and converts it to a preview color
-
- import ctypes
- bits = ctypes.cast(ctypes.pointer(ctypes.c_float(id)), ctypes.POINTER(ctypes.c_uint32)).contents.value
-
- mask = 2 ** 32 - 1
- return [0.0, float((bits << 8) & mask) / float(mask), float((bits << 16) & mask) / float(mask)]
-
- def id_to_hex(id):
- return "{0:08x}".format(struct.unpack('<I', struct.pack('<f', id))[0])
-
- def layer_hash(layer_name):
- return id_to_hex(mm3hash_float(layer_name))[:-1]
-
- #############################################
- # Cryptomatte file processing
- #############################################
-
- def reset_manifest_cache():
- global g_cryptomatte_manf_from_names
- global g_cryptomatte_manf_from_IDs
-
- g_cryptomatte_manf_from_names = {}
- g_cryptomatte_manf_from_IDs = {}
-
- reset_manifest_cache()
-
-
- class CryptomatteInfo(object):
-
- def __init__(self, node_in, reload_metadata=False):
- """Take a nuke node, such as a read node or a Cryptomatte gizmo,
- and Reformat metadata into a dictionary, and collect channel
- information."""
- self.cryptomattes = {}
- self.nuke_node = node_in
- self.selection = None
- self.filename = None
-
- if not self.nuke_node:
- return
-
- exr_metadata_dict = {}
- if not reload_metadata:
- exr_metadata_dict = self._load_minimal_metadata()
-
- if not exr_metadata_dict:
- exr_metadata_dict = self.nuke_node.metadata(view=nuke.thisView()) or {}
-
- default_selection = None
-
- self.cachable_metadata = {}
- for key, value in exr_metadata_dict.iteritems():
- if key == "input/filename":
- self.filename = value
- self.cachable_metadata[key] = value
- for prefix in CRYPTO_METADATA_LEGAL_PREFIX:
- if not key.startswith(prefix):
- continue
- numbered_key = key[len(prefix):] # ex: "exr/cryptomatte/ae93ba3/name" --> "ae93ba3/name"
- metadata_id, partial_key = numbered_key.split("/") # ex: "ae93ba3/name" --> ae93ba3, "name"
- if metadata_id not in self.cryptomattes:
- self.cryptomattes[metadata_id] = {}
- if partial_key == "name":
- value = _legal_nuke_layer_name(value)
- self.cryptomattes[metadata_id][partial_key] = value
- self.cryptomattes[metadata_id]['md_prefix'] = prefix
- if partial_key != "manifest":
- self.cachable_metadata[key] = value
- break
-
- for metadata_id, value in self.cryptomattes.iteritems():
- if not "name" in value:
- value["name"] = ""
-
- if self.cryptomattes:
- default_selection = sorted(
- self.cryptomattes.keys(),
- key=lambda x: self.cryptomattes[x]["name"])[0]
-
- for metadata_id, value in self.cryptomattes.iteritems():
- name = value["name"]
- channels = self._identify_channels(name)
- self.cryptomattes[metadata_id]["channels"] = channels
-
- self.selection = default_selection
- if self.nuke_node.Class() in ["Cryptomatte", "Encryptomatte"]:
- selection_name = self.nuke_node.knob("cryptoLayer").getValue()
- if selection_name:
- valid_selection = self.set_selection(selection_name)
- if not valid_selection and not self.nuke_node.knob("cryptoLayerLock").getValue():
- self.selection = default_selection
-
- def is_valid(self):
- """Checks that the selection is valid."""
- if self.selection is None:
- return False
- if self.selection not in self.cryptomattes:
- return False
- if "channels" not in self.cryptomattes[self.selection]:
- return False
- if not self.cryptomattes[self.selection]["channels"]:
- return False
- return True
-
- def set_selection(self, selection):
- """ sets the selection (eg. cryptoObject) based on the name.
- Returns true if successful.
- """
- selection = _legal_nuke_layer_name(selection)
- for num in self.cryptomattes:
- if self.cryptomattes[num]["name"] == selection:
- self.selection = num
- return True
- self.selection = None
- return False
-
- def get_selection_metadata_key(self, key):
- return self.cryptomattes[self.selection]["md_prefix"] + self.selection + "/" + key
-
- def get_cryptomatte_names(self):
- """ gets the names of the cryptomattes contained the file, which
- are the possible selections or cryptomatte channels.
- """
- return [self.cryptomattes[x]["name"] for x in self.cryptomattes]
-
- def get_channels(self):
- return self.cryptomattes[self.selection]["channels"] if self.selection else None
-
- def get_selection_name(self):
- return self.cryptomattes[self.selection]["name"] if self.selection else None
-
- def get_metadata_cache(self):
- import json
- return json.dumps(self.cachable_metadata)
-
- def _load_minimal_metadata(self):
- """ Returns {} if no quicklky accessible metadata is found,
- otherwise returns only metadata accessible without loading everything,
- and no manifest (which may be large).
- """
- import json
- if not "metadataCache" in self.nuke_node.knobs():
- return {}
- metadata_cache = self.nuke_node.knob("metadataCache").getValue()
- if metadata_cache:
- return json.loads(metadata_cache)
- else:
- return {}
-
- def _identify_channels(self, name):
- """from a name like "cryptoObject",
- gets sorted channels, such as cryptoObject00, cryptoObject01, cryptoObject02
- """
-
- channel_list = []
- if self.nuke_node.Class() in ["Cryptomatte", "Encryptomatte"]:
- # nuke_node is a keyer gizmo or encryptomatte gizmo
- channel_list = self.nuke_node.node('Input1').channels()
- else:
- # nuke_node might a read node
- channel_list = self.nuke_node.channels()
-
- # regex for "cryptoObject" + digits + ending with .red or .r
- channel_regex = re.compile(r'({name}\d+)\.(?:red|r)$'.format(name=name))
- pure_channels = []
- for channel in channel_list:
- match = channel_regex.match(channel)
- if match:
- pure_channels.append(match.group(1))
-
- return sorted(pure_channels)[:len(GIZMO_CHANNEL_KNOBS)]
-
- def resolve_manifest_paths(self, exr_path, sidecar_path):
- import os
- if "\\" in sidecar_path:
- print "Cryptomatte: Invalid sidecar path (Back-slashes not allowed): ", sidecar_path
- return "" # to enforce the specification.
- joined = os.path.join(os.path.dirname(exr_path), sidecar_path)
- return os.path.normpath(joined)
-
- def lazy_load_manifest(self):
- import json
- if 'manifest' not in self.cryptomattes[self.selection]:
- manif_key = self.get_selection_metadata_key('manifest')
- manif_str = self.nuke_node.metadata(manif_key)
- if manif_str is None:
- return {}
- else:
- self.cryptomattes[self.selection]['manifest'] = manif_str
- try:
- return json.loads(self.cryptomattes[self.selection]['manifest'])
- except ValueError, e:
- print "Cryptomatte: Unable to parse manifest. (%s)." % e
- return {}
-
- def parse_manifest(self):
- """ Loads json manifest and unpacks hex strings into floats,
- and converts it to two dictionaries, which map IDs to names and vice versa.
- Also caches the last manifest in a global variable so that a session of selecting
- things does not constantly require reloading the manifest (' ~0.13 seconds for a
- 32,000 name manifest.')
- """
- import json
- import struct
- import os
-
- num = self.selection
- manifest = {}
-
- manif_file = self.cryptomattes[num].get("manif_file", "")
- if manif_file:
- manif_file = self.resolve_manifest_paths(self.filename, manif_file)
-
- if manif_file:
- if os.path.exists(manif_file):
- try:
- with open(manif_file) as json_data:
- manifest = json.load(json_data)
- except:
- print "Cryptomatte: Unable to parse manifest, ", manif_file
- else:
- print "Cryptomatte: Unable to find manifest file: ", manif_file
- else:
- manifest = self.lazy_load_manifest()
-
- from_names = {}
- from_ids = {}
-
- unpacker = struct.Struct('=f')
- packer = struct.Struct("=I")
- for name, value in manifest.iteritems():
- packed = packer.pack(int(value,16))
- packed = packed = '\0' * (4 - len(packed)) + packed
- id_float = unpacker.unpack( packed )[0]
- name_str = name.encode("utf8")
- from_names[name_str] = id_float
- from_ids[id_float] = name_str
-
- self.cryptomattes[num]["names_to_IDs"] = from_names
- self.cryptomattes[num]["ids_to_names"] = from_ids
-
- global g_cryptomatte_manf_from_names
- global g_cryptomatte_manf_from_IDs
- g_cryptomatte_manf_from_names = from_names
- g_cryptomatte_manf_from_IDs = from_ids
-
- return from_names
-
- def id_to_name(self, ID_value):
- """Checks the manifest for the ID value.
- Checks the last used manifest first, before decoding
- the existing one.
- """
- global g_cryptomatte_manf_from_IDs
- manf_cache = g_cryptomatte_manf_from_IDs
- if (type(manf_cache) is dict and ID_value in manf_cache):
- return g_cryptomatte_manf_from_IDs[ID_value]
- elif self.selection != None:
- self.parse_manifest()
- return self.cryptomattes[self.selection]["ids_to_names"].get(ID_value, None)
- else:
- return None
-
- def name_to_ID(self, name):
- return mm3hash_float(name)
-
- def test_manifest(self, quiet=False):
- """Testing function to check for implementation errors and hash collisions.
- Checks all names and values in the manifest in the manifest by rehashing them,
- to ensure that the entire process is sound. Also finds collisions. Returns a tuple
- of errors and collisions.
- """
- self.parse_manifest()
-
- ids = {}
- errors = []
- collisions = []
- manifest = self.cryptomattes[self.selection]["names_to_IDs"]
- for name, idvalue in manifest.iteritems():
- if mm3hash_float(name) != idvalue:
- errors.append("computed ID doesn't match manifest ID: (%s, %s)" % (idvalue, mm3hash_float(name)))
- else:
- if idvalue in ids:
- collisions.append("colliding: %s %s" % (ids[idvalue], name))
- ids[idvalue] = name
-
- if not quiet:
- print "Tested %s, %s names" % (self.nuke_node.name(), len(manifest))
- print " ", len(errors), "non-matching IDs between python and c++."
- print " ", len(collisions), "hash collisions in manifest."
-
- return errors, collisions
-
-
- def print_hash_info(name):
- hash_32 = mmh3.hash(name)
- print "Name:", name
- print "UTF-8 bytes:", " ".join( hex(ord(x))[2:] for x in name)
- print "Hash value (signed):", hash_32
- if hash_32 < 0:
- hash_32 = (-hash_32 - 1) ^ 0xFFFFFFFF
- print "Hash value (unsigned):", hash_32
- print "Float converted:", mm3hash_float(name)
-
-
- #############################################
- # Public - Create Crypto Gizmos
- #############################################
-
-
- def cryptomatte_create_gizmo():
- return nuke.createNode("Cryptomatte")
-
-
- def encryptomatte_create_gizmo():
- return nuke.createNode("Encryptomatte")
-
-
- #############################################
- # Public - cryptomatte Events
- #############################################
-
-
- def cryptomatte_on_create_event(node = None, knob = None):
- # make sure choices are available on load
- prev_stop_state = node.knob("stopAutoUpdate").getValue()
- node.knob("stopAutoUpdate").setValue(True)
-
- _set_crypto_layer_choice_options(node, CryptomatteInfo(node))
-
- node.knob("stopAutoUpdate").setValue(prev_stop_state)
-
- def cryptomatte_knob_changed_event(node = None, knob = None):
- if _limbo_state(node):
- return
-
- if knob.name() == "inputChange":
- if unsafe_to_do_inputChange(node):
- return # see comment in #unsafe_to_do_inputChange.
- cinfo = CryptomatteInfo(node, reload_metadata=True)
- _update_cryptomatte_gizmo(node, cinfo)
- elif knob.name() in ["cryptoLayer", "cryptoLayerLock"]:
- cinfo = CryptomatteInfo(node)
- _update_cryptomatte_gizmo(node, cinfo)
- elif knob.name() in ["cryptoLayerChoice"]:
- if not node.knob('cryptoLayerLock').value():
- knob_value = int(knob.getValue())
- choice_options = knob.values()
- if knob_value >= len(choice_options):
- return
- prev_crypto_layer = node.knob('cryptoLayer').value()
- new_crypto_layer = knob.values()[knob_value]
- if prev_crypto_layer != new_crypto_layer:
- node.knob('cryptoLayer').setValue(new_crypto_layer)
- cinfo = CryptomatteInfo(node)
- _update_cryptomatte_gizmo(node, cinfo)
-
- # Undo user action on menu
- knob.setValue(knob.values().index(node.knob('cryptoLayer').value()))
- elif knob.name() == "pickerAdd":
- if node.knob("singleSelection").getValue():
- node.knob("matteList").setValue("")
- ID_value = _get_knob_channel_value(node.knob("pickerAdd"), recursive_mode="add")
- if ID_value == 0.0:
- return
- cinfo = CryptomatteInfo(node)
- keyed_object = cinfo.id_to_name(ID_value) or "<%s>" % ID_value
- node.knob("pickerRemove").setValue([0] * 8)
- _matteList_modify(node, keyed_object, False)
- _update_cryptomatte_gizmo(node, cinfo)
-
- elif knob.name() == "pickerRemove":
- ID_value = _get_knob_channel_value(node.knob("pickerRemove"), recursive_mode="remove")
- if ID_value == 0.0:
- return
- cinfo = CryptomatteInfo(node)
- keyed_object = cinfo.id_to_name(ID_value) or "<%s>" % ID_value
- node.knob("pickerAdd").setValue([0] * 8)
- _matteList_modify(node, keyed_object, True)
- _update_cryptomatte_gizmo(node, cinfo)
-
- elif knob.name() == "matteList":
- cinfo = CryptomatteInfo(node)
- _update_cryptomatte_gizmo(node, cinfo)
- node.knob("pickerRemove").setValue([0] * 8)
- node.knob("pickerAdd").setValue([0] * 8)
-
- elif knob.name() in ["previewMode", "previewEnabled"]:
- cinfo = CryptomatteInfo(node)
- _update_cryptomatte_gizmo(node, cinfo)
-
- elif knob.name() == "forceUpdate":
- cinfo = CryptomatteInfo(node, reload_metadata=True)
- _update_cryptomatte_gizmo(node, cinfo, True)
-
-
- def encryptomatte_knob_changed_event(node=None, knob=None):
- if _limbo_state(node):
- return
-
- if knob.name() in ["matteName", "cryptoLayerLock"]:
- cinfo = CryptomatteInfo(node, reload_metadata=True)
- _update_encryptomatte_gizmo(node, cinfo)
- return
- if knob.name() in ["setupLayers", "cryptoLayer", "inputChange", "cryptoLayers"]:
- if knob.name() == "inputChange":
- if unsafe_to_do_inputChange(node):
- return # see comment in #unsafe_to_do_inputChange.
- _update_encyptomatte_setup_layers(node)
- cinfo = CryptomatteInfo(node, reload_metadata=True)
- _update_encryptomatte_gizmo(node, cinfo)
-
-
- def encryptomatte_on_create_event(node = None, knob = None):
- node.knob('cryptoLayers').setEnabled(node.knob('setupLayers').value())
-
-
- #############################################
- # Public - cryptomatte functions
- #############################################
-
-
- def update_cryptomatte_gizmo(node, force=False):
- """
- Not invoked by gizmo button.
-
- The gizmo button relies on knob changed callbacks, to avoid
- recursive evaluation of callbacks.
- """
- cinfo = CryptomatteInfo(node, reload_metadata=True)
- _update_cryptomatte_gizmo(node, cinfo, force=force)
-
-
- def clear_cryptomatte_gizmo(node):
- """Relies on knob changed callbacks to update gizmo after values change."""
- node.knob("matteList").setValue("")
- node.knob("expression").setValue("")
-
-
- def update_all_cryptomatte_gizmos():
- return _force_update_all()
-
-
- def update_encryptomatte_gizmo(node, force=False):
- cinfo = CryptomatteInfo(node, reload_metadata=True)
- _update_encryptomatte_gizmo(node, cinfo, force)
-
-
- def clear_encryptomatte_gizmo(node):
- node.knob("matteName").setValue("")
- _update_encryptomatte_gizmo(node, cinfo, True)
-
-
- #############################################
- # Utils - Update Gizmi
- # (gizmi is the plural of gizmo)
- #############################################
-
-
- def _cancel_update(gizmo, force):
- try:
- stopAutoUpdate = gizmo.knob("stopAutoUpdate").getValue()
- except:
- # This happens sometimes on creation. I don't really get it, but this seems to fix it.
- return True
- if (not force and stopAutoUpdate == 1.0):
- return True
- else:
- return False
-
-
- def _force_update_all():
- with nuke.root():
- node_count = 0
- for node in nuke.allNodes():
- if node.Class() == "Cryptomatte":
- node_count = node_count + 1
- cinfo = CryptomatteInfo(node, reload_metadata=True)
- _update_cryptomatte_gizmo(node, cinfo, force=True)
-
- nuke.message("Updated %s cryptomatte gizmos." % node_count)
-
-
- def unsafe_to_do_inputChange(node):
- """
- In Nuke 8, 9, 10, 11, 12 it's been discovered that when copy and pasting certain nodes,
- or when opening certain scripts the inputchanged knob change callback breaks the script.
-
- What actually happens is the call to metadata() breaks it.
-
- The only reliable way to notice that it's unsafe we've found is calling node.screenHeight(),
- and if zero, stopping.
-
- see: https://github.com/Psyop/Cryptomatte/issues/18
- """
- return nuke.NUKE_VERSION_MAJOR > 7 and node.screenHeight() == 0
-
-
- def _limbo_state(gizmo):
- """
- Checks if the node is in a limbo state. This happens when creating
- and connected a node at the same time. It manifests as the error,
- ValueError: A PythonObject is not attached to a node
- """
- try:
- gizmo.Class()
- except ValueError:
- return True
- return False
-
-
- #############################################
- # Utils - Update Gizmi
- #############################################
-
- def _set_channels(gizmo, channels, layer_name):
- gizmo.knob("cryptoLayer").setValue(layer_name)
- for i, knob_name in enumerate(GIZMO_CHANNEL_KNOBS):
- channel = channels[i] if i < len(channels) else "none"
- gizmo.knob(knob_name).setValue(channel)
-
- def _set_metadata_cache(gizmo, cinfo):
- gizmo.knob('metadataCache').setValue(cinfo.get_metadata_cache())
-
- def _set_crypto_layer_choice_options(gizmo, cinfo):
- layer_locked = gizmo.knob('cryptoLayerLock').value()
- values = sorted([v.get('name', '') for v in cinfo.cryptomattes.values()])
- gizmo.knob('cryptoLayerChoice').setEnabled(not layer_locked)
- gizmo.knob("cryptoLayerChoice").setValues(values)
- return values
-
- def _set_crypto_layer_choice(gizmo, cinfo):
- values = _set_crypto_layer_choice_options(gizmo, cinfo)
- current_selection = cinfo.get_selection_name()
-
- if current_selection:
- gizmo.knob("cryptoLayerChoice").setValue(values.index(current_selection))
-
- def _update_cryptomatte_gizmo(gizmo, cinfo, force=False):
- if _cancel_update(gizmo, force):
- return
- _set_metadata_cache(gizmo, cinfo)
- if not cinfo.is_valid():
- return
- cryptomatte_channels = cinfo.get_channels()
- if not cryptomatte_channels:
- return
- _set_channels(gizmo, cryptomatte_channels, cinfo.get_selection_name())
- _set_expression(gizmo, cryptomatte_channels)
- _set_preview_expression(gizmo, cryptomatte_channels)
- _set_crypto_layer_choice(gizmo, cinfo)
-
-
- def _legal_nuke_layer_name(name):
- """ Blender produces channels with certain characters in the name, which Nuke
- changes to "_". We have to make sure we handle Cryptomattes
- that are built this way. Doing this by only allowing alphanumeric
- output plus dash and underscores
- """
- prefix = ""
- if name and name[0] in "0123456789":
- prefix = "_"
- return prefix + "".join([x if x.lower() in 'abcdefghijklmnopqrstuvwxyz1234567890_-' else '_' for x in name])
-
-
- def _update_encryptomatte_gizmo(gizmo, cinfo, force=False):
- if _cancel_update(gizmo, force):
- return
-
- def reset_gizmo(gizmo):
- _set_channels(gizmo, [], "")
- gizmo.knob("alphaExpression").setValue("")
-
- matte_name = gizmo.knob('matteName').value()
- matte_input = gizmo.input(1)
- _set_metadata_cache(gizmo, cinfo)
-
- if matte_name == "" and not matte_input is None:
- matte_name = matte_input.name()
- gizmo.knob('matteName').setValue(matte_name)
-
- if matte_name == "":
- gizmo.knob('id').setValue(0.0)
- gizmo.knob('idHex').setValue('')
- gizmo.knob('previewColor').setValue([0.0, 0.0, 0.0])
-
- else:
- id_value = mm3hash_float(matte_name)
- gizmo.knob('id').setValue(id_value)
- gizmo.knob('idHex').setValue(id_to_hex(id_value))
- gizmo.knob('previewColor').setValue(id_to_rgb(id_value))
-
- if gizmo.knob('setupLayers').value():
- gizmo.knob('cryptoLayers').setEnabled(True)
- if cinfo.is_valid():
- cryptomatte_channels = cinfo.get_channels()
- if not cryptomatte_channels:
- cryptomatte_channels = []
- else:
- cryptomatte_channels = []
-
- crypto_layer = gizmo.knob('cryptoLayer').value()
- crypto_layer = _legal_nuke_layer_name(crypto_layer)
- if not crypto_layer:
- return reset_gizmo(gizmo)
- if crypto_layer in cryptomatte_channels:
- gizmo.knob('inputCryptoLayers').setValue(len(cryptomatte_channels) - 1)
- manifest_key = cinfo.get_selection_metadata_key("")
- gizmo.knob('manifestKey').setValue(manifest_key)
- gizmo.knob('newLayer').setValue(False)
- else:
- gizmo.knob('inputCryptoLayers').setValue(0)
- gizmo.knob('manifestKey').setValue(
- CRYPTO_METADATA_DEFAULT_PREFIX + layer_hash(crypto_layer) + '/')
- gizmo.knob('newLayer').setValue(True)
-
- cryptomatte_channels = [
- crypto_layer + "{0:02d}".format(i)
- for i in range(int(gizmo.knob('cryptoLayers').value()))
- ]
- _set_channels(gizmo, cryptomatte_channels, crypto_layer)
-
- else:
- gizmo.knob('cryptoLayers').setEnabled(False)
- if not cinfo.is_valid():
- return
-
- cryptomatte_channels = cinfo.get_channels()
- if not cryptomatte_channels:
- return
-
- gizmo.knob('newLayer').setValue(False)
- _set_channels(gizmo, cryptomatte_channels, cinfo.get_selection_name())
- gizmo.knob('inputCryptoLayers').setValue(len(cryptomatte_channels) - 1)
- gizmo.knob('cryptoLayers').setValue(len(cryptomatte_channels) - 1)
- manifest_key = cinfo.get_selection_metadata_key("")
- gizmo.knob('manifestKey').setValue(manifest_key)
-
- gizmo.knob("alphaExpression").setValue(_build_extraction_expression(cryptomatte_channels, [0.0]))
-
- def _update_encyptomatte_setup_layers(gizmo):
- setup_layers = gizmo.knob('setupLayers').value()
- num_layers = gizmo.knob('cryptoLayers').value()
- input_layers = gizmo.knob('inputCryptoLayers').value()
- crypto_layer = _legal_nuke_layer_name(gizmo.knob('cryptoLayer').value())
-
- if not setup_layers:
- gizmo.knob('manifestKey').setValue("")
- for ch_add, ch_remove in zip(GIZMO_ADD_CHANNEL_KNOBS, GIZMO_REMOVE_CHANNEL_KNOBS):
- gizmo.knob(ch_add).setValue("none")
- gizmo.knob(ch_remove).setValue("none")
- return
-
- all_layers = nuke.layers()
-
- num_ch = len(GIZMO_ADD_CHANNEL_KNOBS)
- for i, ch_add, ch_remove in zip(
- range(num_ch), GIZMO_ADD_CHANNEL_KNOBS, GIZMO_REMOVE_CHANNEL_KNOBS):
- this_layer = "{0}{1:02d}".format(crypto_layer, i)
- # Add
- if i < num_layers:
- if not this_layer in all_layers:
- channels = ["%s.%s" % (this_layer, c) for c in ['red', 'green', 'blue', 'alpha']]
- nuke.Layer(this_layer, channels)
-
- gizmo.knob(ch_add).setValue(this_layer)
- gizmo.knob(ch_remove).setValue("none")
- else:
- gizmo.knob(ch_add).setValue("none")
- if i <= input_layers:
- gizmo.knob(ch_remove).setValue(this_layer)
- else:
- gizmo.knob(ch_remove).setValue("none")
-
- def encryptomatte_add_manifest_id():
- node = nuke.thisNode()
- parent = nuke.thisParent()
- name = parent.knob('matteName').value()
- id_hex = parent.knob('idHex').value()
- manifest_key = parent.knob('manifestKey').value()
- metadata = node.metadata()
- manifest = metadata.get(manifest_key + 'manifest', "{}")
-
- # Add another item, with closing bracket
- last_item = '"%s":"%s"}' % (name, id_hex)
- last_bracket_pos = manifest.rfind('}')
- existing_items = manifest[:last_bracket_pos].rstrip()
- if not existing_items.endswith(',') and not existing_items.endswith('{'):
- existing_items += ','
- existing_items += last_item
- return existing_items
-
-
- #############################################
- # Utils - Troubleshooting
- #############################################
-
- def _troubleshoot_gizmo(gizmo):
- MSG_WRONGTYPE = 'Troubleshooting: Cannot troubleshoot non-Cryptomatte nodes'
- MSG_UNCONNECTED = 'Cryptomatte gizmo is not plugged into anything'
- MSG_NONE_FOUND = 'No Cryptomattes found in input. '
- MSG_LYR_UNSET = ('Gizmo needs updating, layer is not set. '
- 'Press "force update" or reconnect.')
- MSG_LYR_UNPOP = ('Gizmo needs updating, layer menu is not populated. '
- 'Press "force update" or reconnect.')
- MSG_ODD_STATE = ('Gizmo is in an odd state and needs updating. '
- 'Press "force update" or reconnect.')
- MSG_INVALID_SEL = ('Layer is set to "%s", which is unavailable. '
- 'Select another layer.')
- if gizmo.Class() != "Cryptomatte":
- return [MSG_WRONGTYPE]
-
- issues = []
- if not gizmo.input(0):
- issues.append(MSG_UNCONNECTED)
- else:
- cinfo = CryptomatteInfo(gizmo, reload_metadata=True)
- available = cinfo.get_cryptomatte_names()
- selection = gizmo.knob('cryptoLayer').value()
- menu_selection = gizmo.knob('cryptoLayerChoice').value()
- if not cinfo.cryptomattes:
- issues.append(MSG_NONE_FOUND)
- else:
- if not selection or not menu_selection:
- issues.append(MSG_LYR_UNSET)
- if menu_selection not in available:
- issues.append(MSG_LYR_UNPOP)
- elif selection not in gizmo.knob(GIZMO_CHANNEL_KNOBS[0]).value():
- issues.append(MSG_ODD_STATE)
- elif selection not in available:
- issues.append(MSG_INVALID_SEL % selection)
- return issues
-
- def _troubleshoot_setup():
- MSG_BAD_INSTALL = ("Installation is wrong, callbacks were not found. "
- "setup_cryptomatte() did not run, init.py may "
- "not be set up properly. ")
- PROXY_MODE = "Proxy mode is on, this can cause artifacts. "
- issues = []
- expected_knob_changeds = ["Cryptomatte", "Encryptomatte"]
- if any(x not in nuke.callbacks.knobChangeds for x in expected_knob_changeds):
- issues.append(MSG_BAD_INSTALL)
- if nuke.root().knob('proxy').value():
- issues.append(PROXY_MODE)
- return issues
-
- def troubleshoot_gizmo(node):
- issues = _troubleshoot_gizmo(node) + _troubleshoot_setup()
- if issues:
- nuke.message("Troubleshooting: Found the following issues: %s" %
- "\n - ".join([""] + issues))
- else:
- nuke.message("Troubleshooting: All good!")
-
- #############################################
- # Utils - Unload Manifest
- #############################################
-
-
- def unload_manifest(node):
- source_node = None
- if node.Class() == "Cryptomatte":
- source_node = node.input(0)
- if not source_node:
- nuke.message('Cryptomatte is not plugged into anything')
- return
- else:
- source_node = node
-
- cinfo = CryptomatteInfo(node, reload_metadata=True)
- if not cinfo.is_valid():
- nuke.message("Gizmo's cryptomatte selection is not valid or no cryptomattes are available. ")
- return
-
- names_to_IDs = cinfo.parse_manifest();
-
- if not names_to_IDs:
- nuke.message('No Cryptomatte manifest was found in the input. ')
- return
-
- new_keyers = []
- if nuke.ask('There are %s named mattes in the manifest, are you sure you want to create keyer nodes for all of them?' % len(names_to_IDs)):
- with nuke.root():
- dot = nuke.nodes.Dot()
- dot.setInput(0, source_node)
-
- progress = 0
- task = nuke.ProgressTask("Unloading Manifest")
- for name, metadata_ID in names_to_IDs.iteritems():
- if task.isCancelled():
- break
- task.setMessage("Creating Cryptomatte Keyer for %s" % name)
- task.setProgress( int(float(progress) / float(len(names_to_IDs)) * 100))
- keyer = nuke.nodes.Cryptomatte(name="ck_%s" % name, matteList=name, matteOnly=True)
- keyer.setInput(0, dot)
- _update_cryptomatte_gizmo(keyer, cinfo)
- new_keyers.append(keyer)
- progress = progress + 1
-
- return new_keyers
-
-
- #############################################
- # Utils - Build Expressions
- #############################################
-
-
- def _is_number(s):
- try:
- float(s)
- return True
- except ValueError:
- return False
-
-
- def _set_expression(gizmo, cryptomatte_channels):
- matte_list_str = gizmo.knob("matteList").getValue()
- ID_list = []
-
- matte_list = get_mattelist_as_set(gizmo)
-
- for item in matte_list:
- if item.startswith("<") and item.endswith(">"):
- numstr = item[1:-1]
- if _is_number(numstr):
- ID_list.append(single_precision(float(numstr)))
- else:
- ID_list.append(mm3hash_float(item))
-
- expression = _build_extraction_expression(cryptomatte_channels, ID_list)
- gizmo.knob("expression").setValue(expression)
-
-
- def _build_condition(condition, IDs):
- conditions = []
- for ID in IDs:
- conditions.append( condition.replace("ID", str(ID)) )
- return " || ".join(conditions)
-
-
- def _build_extraction_expression(channel_list, IDs):
- if not IDs:
- return ""
-
- iterated_expression = "({red_condition} ? sub_channel.green : 0.0) + ({blue_condition} ? sub_channel.alpha : 0.0) + more_work_needed"
-
- subcondition_red = "sub_channel.red == ID"
- subcondition_blue = "sub_channel.blue == ID"
-
- expression = ""
- for channel in channel_list:
- condition_r = _build_condition(subcondition_red, IDs)
- condition_b = _build_condition(subcondition_blue, IDs)
-
- channel_expression = iterated_expression.replace("red_condition", condition_r).replace("blue_condition", condition_b)
- channel_expression = channel_expression.replace("sub_channel", channel)
-
- if not expression:
- expression = channel_expression
- else:
- expression = expression.replace("more_work_needed", channel_expression)
- expression = expression.replace("more_work_needed", "0")
-
- expression = expression.replace("{", "(")
- expression = expression.replace("}", ")")
-
- return expression
-
- def _set_preview_expression(gizmo, cryptomatte_channels):
- enabled = gizmo.knob('previewEnabled').getValue()
- preview_mode = gizmo.knob('previewMode').value() if enabled else 'None'
-
- channel_pairs = []
- for c in cryptomatte_channels:
- channel_pairs.append(('%s.red' % c, '%s.green' % c))
- channel_pairs.append(('%s.blue' % c, '%s.alpha' % c))
-
- expressions = []
- if preview_mode == 'Edges':
- expressions = [
- "",
- "",
- "",
- "2.0 * %s" % channel_pairs[1][1],
- ]
- elif preview_mode == 'Colors':
- """
- Generates an expression like this:
- red = (
- (mantissa(abs(c00.red)) * 1 % 0.25) * c00.green +
- (mantissa(abs(c00.blue)) * 1 % 0.25) * c00.alpha +
- (mantissa(abs(c01.red)) * 1 % 0.25) * c01.green +
- (mantissa(abs(c01.blue)) * 1 % 0.25) * c01.alpha
- )
- green = (
- (mantissa(abs(c00.red)) * 4 % 0.25) * c00.green +
- (mantissa(abs(c00.blue)) * 4 % 0.25) * c00.alpha +
- (mantissa(abs(c01.red)) * 4 % 0.25) * c01.green +
- (mantissa(abs(c01.blue)) * 4 % 0.25) * c01.alpha
- )
- blue = (
- (mantissa(abs(c00.red)) * 16 % 0.25) * c00.green +
- (mantissa(abs(c00.blue)) * 16 % 0.25) * c00.alpha +
- (mantissa(abs(c01.red)) * 16 % 0.25) * c01.green +
- (mantissa(abs(c01.blue)) * 16 % 0.25) * c01.alpha
- )
- """
-
- puzzle_layers = 4
- factors = [1, 16, 64]
- puzzle_bits = "(mantissa(abs({id_chan})) * {factor} % 0.25) * {cov_chan}"
-
- for factor in factors:
- puzzle = " + ".join(
- puzzle_bits.format(factor=factor, id_chan=id_chan, cov_chan=cov_chan)
- for (id_chan, cov_chan) in channel_pairs[:puzzle_layers])
- expr = "(%s)" % puzzle
- expressions.append(expr)
- expressions.append("")
- else: # mode is none
- expressions = ["", "", "", ""]
- for i in range(4):
- gizmo.knob('previewExpression' + str(i)).setValue(expressions[i])
-
-
-
- #############################################
- # Utils - Manifest Processing Helpers
- #############################################
-
-
- def _id_from_matte_name(name):
- if name.startswith('<') and name.endswith('>') and _is_number(name[1:-1]):
- return single_precision(float(name[1:-1]))
- else:
- return mm3hash_float(name)
-
- def _get_knob_channel_value(knob, recursive_mode=None):
- bbox = knob.getValue()[4:]
- node = knob.node()
- upstream_node = node.input(0)
- if not upstream_node:
- return 0.0
-
- if recursive_mode is None:
- id_list = []
- else:
- matte_list = get_mattelist_as_set(node)
- id_list = map(_id_from_matte_name, matte_list)
-
- saw_bg = False
- add_mode = recursive_mode == "add"
- rm_mode = recursive_mode == "remove"
-
- for layer_knob in GIZMO_CHANNEL_KNOBS:
- layer = node.knob(layer_knob).value()
-
- if layer == "none":
- return 0.0
-
- for id_suffix, cov_suffix in [('.red', '.green'), ('.blue', '.alpha')]:
- id_chan = layer + id_suffix
- cov_chan = layer + cov_suffix
- selected_id = upstream_node.sample(id_chan, bbox[0], bbox[1])
- selected_coverage = upstream_node.sample(cov_chan, bbox[0], bbox[1])
-
- if add_mode or rm_mode:
- if selected_id == 0.0 or selected_coverage == 0.0:
- # Seen bg twice? Select bg.
- if saw_bg:
- break
- saw_bg = True
- continue
-
- in_list = selected_id in id_list
- if (add_mode and not in_list) or (rm_mode and in_list):
- return selected_id
- else:
- # Non-recursive. Just return the first id found.
- return selected_id
-
-
- #############################################
- # Utils - Comma seperated list processing
- #############################################
-
-
- def _encode_csv(iterable_items):
- """
- Encodes CSVs with special characters escaped, and surrounded in quotes
- if it contains any of these or spaces, with a space after each comma.
- """
- cleaned_items = []
- need_escape_chars = '"\\'
- need_space_chars = ' ,'
- for item in iterable_items:
- need_escape = any(x in item for x in need_escape_chars)
- need_quotes = need_escape or any(x in item for x in need_space_chars)
-
- cleaned = None
- if need_escape:
- cleaned = ""
- for char in item:
- if char in need_escape_chars:
- cleaned +=( '\\%s' % char )
- else:
- cleaned += char
- else:
- cleaned = item
- if need_quotes:
- cleaned_items.append('"%s"'%cleaned)
- else:
- cleaned_items.append(cleaned)
- result = ", ".join(cleaned_items)
- return result
-
-
- def _decode_csv(input_str):
- """ Decodes CSVs into a list of strings. """
- import csv
- reader = csv.reader([input_str], quotechar='"', delimiter=',', escapechar="\\",
- doublequote=False, quoting=csv.QUOTE_ALL, skipinitialspace=True);
- result = []
- for row in reader:
- result += row
- return result
-
-
- def get_mattelist_as_set(gizmo):
- matte_list = gizmo.knob("matteList").getValue()
- raw_list = _decode_csv(matte_list)
- result = set()
- for item in raw_list:
- item = item.encode("utf-8") if type(item) is unicode else str(item)
- result.add(item)
- return result
-
-
- def set_mattelist_from_set(gizmo, matte_items):
- " Creates a CSV matte list. "
- matte_names_list = list(matte_items)
- matte_names_list.sort(key=lambda x: x.lower())
- matte_list_str = _encode_csv(matte_names_list)
- matte_list_str = matte_list_str.replace("\\", "\\\\")
- gizmo.knob("matteList").setValue(matte_list_str)
-
- def _matteList_modify(gizmo, name, remove):
- def _matteList_set_add(name, matte_names):
- matte_names.add(name)
-
- def _matteList_set_remove(name, matte_names):
- if name in matte_names:
- matte_names.remove(name) # the simple case
- elif name.startswith('<') and name.endswith('>') and _is_number(name[1:-1]):
- # maybe it was selected by name before, but is being removed by number
- # (manifest was working, now it doesn't)
- num = single_precision(float(name[1:-1]))
- for existing_name in matte_names:
- if mm3hash_float(existing_name) == num:
- matte_names.remove(existing_name)
- break
- else:
- # maybe it was selected by number before, but is being removed by name
- # (manifest was broken, now it works)
- num_str = "<%s>" % mm3hash_float(name)
- if num_str in matte_names:
- matte_names.remove(num_str) # the simple case
-
-
- if not name or gizmo.knob("stopAutoUpdate").getValue() == 1.0:
- return
-
- matte_names = get_mattelist_as_set(gizmo)
- if remove:
- _matteList_set_remove(name, matte_names)
- else:
- _matteList_set_add(name, matte_names)
- set_mattelist_from_set(gizmo, matte_names)
-
-
- #############################################
- # Public - Decryption
- #############################################
-
-
- def decryptomatte_all(ask=True):
- decryptomatte_nodes(nuke.allNodes(), ask)
-
-
- def decryptomatte_selected(ask=False):
- decryptomatte_nodes(nuke.selectedNodes(), ask)
-
-
- def decryptomatte_button(node):
- if "." in nuke.thisNode().fullName():
- parent_name = ".".join(nuke.thisNode().fullName().split(".")[:-1])
- with nuke.toNode(parent_name):
- decryptomatte_nodes([node], False)
- return
- with nuke.root():
- decryptomatte_nodes([node], False)
-
-
- def decryptomatte_nodes(nodes, ask):
- """ Replaces Cryptomatte gizmos with equivelant nodes. """
- gizmos = [n for n in nodes if n.Class() == "Cryptomatte"]
- if not gizmos:
- return
-
- if not ask or nuke.ask(('Replace %s Cryptomatte gizmos with expression nodes? '
- 'Replaced Gizmos will be disabled and selected.') % len(gizmos)):
-
- for gizmo in gizmos:
- _decryptomatte(gizmo)
-
- for node in nuke.selectedNodes():
- node.knob("selected").setValue(False)
-
- for gizmo in gizmos:
- gizmo.knob("selected").setValue(True)
-
-
- #############################################
- # Decryptomatte helpers
- #############################################
-
-
- def _decryptomatte(gizmo):
- """ Returns list of new nodes, in order of connections. """
- orig_name = gizmo.name()
- disabled = gizmo.knob("disable").getValue()
- matte_list = gizmo.knob("matteList").value()
- matte_only = gizmo.knob("matteOnly").value()
- expression = gizmo.knob("expression").value()
- matte_output = gizmo.knob("matteOutput").value()
- unpremultiply = gizmo.knob("unpremultiply").value()
- remove_channels = gizmo.knob("RemoveChannels").value()
-
- # compile list immediate outputs to connect to
- connect_to = []
- for node in gizmo.dependent():
- for i in xrange(node.inputs()):
- input_node = node.input(i)
- if input_node and input_node.fullName() == gizmo.fullName():
- connect_to.append((i, input_node))
-
- # Modifiy expression to perform premult.
- if unpremultiply and expression:
- expression = "(%s) / (alpha ? alpha : 1)" % expression
-
- # Setup expression node.
- expr_node = nuke.nodes.Expression(
- inputs=[gizmo], channel0=matte_output, expr0=expression,
- name="%sExtract" % orig_name, disable=disabled)
- expr_node.addKnob(nuke.nuke.String_Knob(
- 'origMatteList', 'Original Matte List', matte_list))
- for knob_name in GIZMO_CHANNEL_KNOBS:
- expr_node.addKnob(nuke.nuke.Channel_Knob(knob_name, "none") )
- expr_node.knob(knob_name).setValue(gizmo.knob(knob_name).value())
- expr_node.knob(knob_name).setVisible(False)
- new_nodes = [expr_node]
-
- # Add remove channels node, if needed.
- if remove_channels:
- channels2 = matte_output if matte_output != "alpha" else ""
- remove = nuke.nodes.Remove(
- inputs=[expr_node], operation="keep", channels="rgba",
- channels2=channels2, name="%sRemove" % orig_name,
- disable=disabled)
- new_nodes.append(remove)
-
- # If "matte only" is used, add shuffle node.
- if matte_only:
- shuffle = nuke.nodes.Shuffle(
- name="%sMatteOnly" % orig_name, inputs=[new_nodes[-1]],
- disable=disabled)
- shuffle.knob("in").setValue(matte_output)
- new_nodes.append(shuffle)
-
- # Disable original
- gizmo.knob("disable").setValue(True)
-
- # Reconnect outputs
- for inputID, node in connect_to:
- node.setInput(inputID, new_nodes[-1])
- return new_nodes
|