No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

cryptomatte_utilities_tests.py 65KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531
  1. #
  2. #
  3. # Copyright (c) 2014, 2015, 2016, 2017 Psyop Media Company, LLC
  4. # See license.txt
  5. #
  6. #
  7. import sys
  8. import unittest
  9. def get_all_unit_tests():
  10. """ Returns the list of unit tests (to run in any context)"""
  11. return [CSVParsing, CryptoHashing]
  12. def get_all_nuke_tests():
  13. """ Returns the list of Nuke integration tests"""
  14. return [CSVParsingNuke, CryptomatteNodePasting, CryptomatteNukeTests]
  15. #############################################
  16. # Env. variables
  17. #############################################
  18. """
  19. The supplied Cryptomatte sample images are required for testing. The path to the sample_images
  20. directory can be defined using the environment variable below. Otherwise, they are searched for
  21. relative to this file.
  22. """
  23. SAMPLES_IMAGES_DIR_ENVIRON = "CRYPTOMATTE_TESTING_SAMPLES"
  24. global CRYPTOMATTETEST_SKIP_CLEANUP_ON_FAILURE
  25. CRYPTOMATTETEST_SKIP_CLEANUP_ON_FAILURE = False
  26. def set_skip_cleanup_on_failure(enabled):
  27. assert type(enabled) is bool
  28. global CRYPTOMATTETEST_SKIP_CLEANUP_ON_FAILURE
  29. CRYPTOMATTETEST_SKIP_CLEANUP_ON_FAILURE = enabled
  30. def reset_skip_cleanup_on_failure():
  31. global CRYPTOMATTETEST_SKIP_CLEANUP_ON_FAILURE
  32. CRYPTOMATTETEST_SKIP_CLEANUP_ON_FAILURE = False
  33. #############################################
  34. # Unit tests
  35. #############################################
  36. class CSVParsing(unittest.TestCase):
  37. csv_str = ("""str, "str with space", "single 'quotes'", """
  38. '"with_a,_comma", "with comma, and \\"quotes\\"", <123.45>, '
  39. '" space_in_front", "space_at_end ", "has_escape\\\\chars", '
  40. '"cyrillic \xd1\x80\xd0\xb0\xd0\xb2\xd0\xbd\xd0\xb8\xd0\xbd\xd0\xb0"')
  41. name_list = [
  42. "str", "str with space", "single 'quotes'", "with_a,_comma", 'with comma, and "quotes"',
  43. "<123.45>", " space_in_front", "space_at_end ", "has_escape\\chars",
  44. "cyrillic \xd1\x80\xd0\xb0\xd0\xb2\xd0\xbd\xd0\xb8\xd0\xbd\xd0\xb0"
  45. ]
  46. def test_csv_round_trip(self):
  47. import cryptomatte_utilities as cu
  48. """Ensures the round trip is correct for CSV encoding and decoding. """
  49. def check_results(encoded, decoded):
  50. self.assertEqual(encoded, self.csv_str,
  51. "Round trip to str failed: %s != %s" % (self.csv_str, encoded))
  52. self.assertEqual(self.name_list, decoded,
  53. "Round trip to list failed: %s != %s" % (self.name_list, decoded))
  54. # start from csv
  55. decoded = cu._decode_csv(self.csv_str)
  56. encoded = cu._encode_csv(decoded)
  57. check_results(encoded, decoded)
  58. # start from list
  59. encoded = cu._encode_csv(self.name_list)
  60. decoded = cu._decode_csv(encoded)
  61. check_results(encoded, decoded)
  62. class CryptoHashing(unittest.TestCase):
  63. mm3hash_float_values = {
  64. "hello": 6.0705627102400005616e-17,
  65. "cube": -4.08461912519e+15,
  66. "sphere": 2.79018604383e+15,
  67. "plane": 3.66557617593e-11,
  68. # utf-8 bytes for "plane" in Bulgarian
  69. "\xd1\x80\xd0\xb0\xd0\xb2\xd0\xbd\xd0\xb8\xd0\xbd\xd0\xb0": -1.3192631212399999468e-25,
  70. # utf-8 bytes for "girl" in German
  71. "m\xc3\xa4dchen": 6.2361298211599995797e+25,
  72. }
  73. def test_mm3hash_float(self):
  74. import cryptomatte_utilities as cu
  75. for name, hashvalue in self.mm3hash_float_values.iteritems():
  76. msg = "%s hash does not line up: %s %s" % (name, hashvalue, cu.mm3hash_float(name))
  77. self.assertEqual(cu.mm3hash_float(name), cu.single_precision(hashvalue), msg)
  78. #############################################
  79. # Nuke tests
  80. #############################################
  81. global g_cancel_nuke_testing
  82. g_cancel_nuke_testing = False
  83. class CSVParsingNuke(unittest.TestCase):
  84. """
  85. Nuke removes escaped characters when you use getValue() on a string knob.
  86. Therefore, testing the round trip through the gizmo is slightly complicated.
  87. """
  88. def setUp(self):
  89. import nuke
  90. self.gizmo = nuke.nodes.Cryptomatte()
  91. def tearDown(self):
  92. import nuke
  93. nuke.delete(self.gizmo)
  94. def round_trip_through_gizmo(self, csv, msg):
  95. import cryptomatte_utilities as cu
  96. # start from csv
  97. self.gizmo.knob("matteList").setValue(csv.replace("\\", "\\\\"))
  98. for i in range(3):
  99. matte_set = cu.get_mattelist_as_set(self.gizmo)
  100. self.gizmo.knob("matteList").setValue("")
  101. cu.set_mattelist_from_set(self.gizmo, matte_set)
  102. result_csv = self.gizmo.knob("matteList").getValue()
  103. correct_set = set(cu._decode_csv(csv))
  104. result_set = set(cu._decode_csv(result_csv))
  105. self.assertEqual(correct_set, result_set, "%s: Came out as: %s" % (msg, result_csv))
  106. def test_csv_through_gizmo(self):
  107. self.round_trip_through_gizmo('"name containing a \\"quote\\" "', "Round trip failed")
  108. def test_big_csv_through_gizmo(self):
  109. self.round_trip_through_gizmo(CSVParsing.csv_str, "Round trip failed")
  110. class CryptomatteNodePasting(unittest.TestCase):
  111. """
  112. This one takes some explaining.
  113. In Nuke 8, 9, 10, it's been discovered that when copy and pasting certain nodes,
  114. or when opening certain scripts the inputchanged knob change callback breaks the script.
  115. What actually happens is the call to metadata() breaks it.
  116. The only reliable way to notice that it's unsafe we've found is calling node.screenHeight(),
  117. and if zero, stopping.
  118. see: https://github.com/Psyop/Cryptomatte/issues/18
  119. This test tests for it.
  120. """
  121. expected_error_prefix = "On paste, node lost connection to"
  122. def test_paste_with_channelmerge(self):
  123. """Tests this bug has been fixed."""
  124. import nuke
  125. pastable = """
  126. set cut_paste_input [stack 0]
  127. version 10.0 v4
  128. push $cut_paste_input
  129. TimeOffset {
  130. name {prefix}_TimeOffset7
  131. label "\[value time_offset]"
  132. selected true
  133. xpos 13246
  134. ypos 6816
  135. }
  136. set Ndd6dafc0 [stack 0]
  137. push $Ndd6dafc0
  138. add_layer {crypto_object crypto_object.red crypto_object.green crypto_object.blue crypto_object.alpha}
  139. add_layer {crypto_object00 crypto_object00.red crypto_object00.green crypto_object00.blue crypto_object00.alpha}
  140. add_layer {crypto_object01 crypto_object01.red crypto_object01.green crypto_object01.blue crypto_object01.alpha}
  141. add_layer {crypto_object02 crypto_object02.red crypto_object02.green crypto_object02.blue crypto_object02.alpha}
  142. Cryptomatte {
  143. name {prefix}_Cryptomatte6
  144. selected true
  145. xpos 13150
  146. ypos 6876
  147. matteList ""
  148. cryptoLayer crypto_object
  149. expression a
  150. keyedName "ID value not in manifest."
  151. previewChannel crypto_object
  152. in00 crypto_object00
  153. in01 crypto_object01
  154. in02 crypto_object02
  155. in03 none
  156. in04 none
  157. in05 none
  158. in06 none
  159. in07 none
  160. }
  161. ChannelMerge {
  162. inputs 2
  163. operation in
  164. name {prefix}_ChannelMerge7
  165. selected true
  166. xpos 13246
  167. ypos 6941
  168. }
  169. """
  170. prefix = "test_paste_with_channelmerge_"
  171. pasted_node_names = [(prefix + x)
  172. for x in ("_TimeOffset7", "_Cryptomatte6", "_ChannelMerge7")]
  173. self.assertFalse(
  174. any([nuke.toNode(x) for x in pasted_node_names]),
  175. "Nodes already exist at the start of testing, were not pasted. ")
  176. try:
  177. # deselect all
  178. for node in nuke.selectedNodes():
  179. node['selected'].setValue(False)
  180. nuke.tcl(pastable.replace("{prefix}", prefix))
  181. pasted_nodes = [nuke.toNode(x) for x in pasted_node_names]
  182. ch_merge_node = pasted_nodes[-1]
  183. self.assertTrue(ch_merge_node.input(0), "%s input 0." % self.expected_error_prefix)
  184. self.assertTrue(ch_merge_node.input(1), "%s input 1." % self.expected_error_prefix)
  185. except:
  186. raise
  187. finally:
  188. for node in pasted_nodes:
  189. if node:
  190. nuke.delete(node)
  191. def test_bug_still_exists(self):
  192. """Tests this bug still exists. We don't want to be running the fix if we don't have to. """
  193. import nuke
  194. if nuke.NUKE_VERSION_MAJOR == 7: # but is known not to exist in version 7.
  195. return
  196. def test_callback(node=None, knob=None):
  197. node.metadata()
  198. callback = lambda: test_callback(nuke.thisNode())
  199. nuke.addKnobChanged(callback, nodeClass='Cryptomatte')
  200. try:
  201. self.test_paste_with_channelmerge()
  202. exception = None
  203. except Exception, e:
  204. exception = e
  205. finally:
  206. nuke.removeKnobChanged(callback, nodeClass='Cryptomatte')
  207. if exception:
  208. self.assertTrue(
  209. type(exception) is AssertionError,
  210. "Bug check failure was not assertion error. %s " % exception)
  211. self.assertTrue(
  212. self.expected_error_prefix in str(exception),
  213. "Assertion did not contain expected prefix (wrong assertion failed, probably). %s" %
  214. exception)
  215. else:
  216. self.fail("Pasting bug does not reproduce in Nuke Version %s" % nuke.NUKE_VERSION_MAJOR)
  217. class CryptomatteNukeTests(unittest.TestCase):
  218. """
  219. Many tests are combined into one big class because setupClass takes significant time,
  220. and many tests in the same class is the lesser evil.
  221. """
  222. @classmethod
  223. def setUpClass(cls):
  224. import nuke
  225. import os
  226. default_path = os.path.normpath(os.path.join(__file__, "../", "../", "sample_images"))
  227. sample_images = os.environ.get(SAMPLES_IMAGES_DIR_ENVIRON, "") or default_path
  228. obj_path = os.path.join(sample_images, "bunny_CryptoObject.exr").replace("\\", "/")
  229. asset_path = os.path.join(sample_images, "bunny_CryptoAsset.exr").replace("\\", "/")
  230. material_path = os.path.join(sample_images, "bunny_CryptoMaterial.exr").replace("\\", "/")
  231. sidecar_path = os.path.join(sample_images, "sidecar_manifest",
  232. "bunny_CryptoObject.exr").replace("\\", "/")
  233. for file_path in [obj_path, asset_path, material_path, sidecar_path]:
  234. if not os.path.isfile(file_path):
  235. raise IOError(
  236. ("Could not find: %s. Sample image dir can be defined env variable, %s") %
  237. (file_path, SAMPLES_IMAGES_DIR_ENVIRON))
  238. cls.read_obj = nuke.nodes.Read(file=obj_path)
  239. cls.read_asset = nuke.nodes.Read(file=asset_path)
  240. cls.read_material = nuke.nodes.Read(file=material_path)
  241. cls.read_sidecar = nuke.nodes.Read(file=sidecar_path)
  242. cls.constant = nuke.nodes.Constant(color=0.5)
  243. cls.set_canceled(False)
  244. @classmethod
  245. def tearDownClass(cls):
  246. import nuke
  247. if cls.canceled():
  248. return
  249. nuke.delete(cls.read_obj)
  250. nuke.delete(cls.read_asset)
  251. nuke.delete(cls.read_material)
  252. nuke.delete(cls.read_sidecar)
  253. nuke.delete(cls.constant)
  254. @classmethod
  255. def set_canceled(cls, canceled):
  256. global g_cancel_nuke_testing
  257. g_cancel_nuke_testing = canceled
  258. @classmethod
  259. def canceled(cls):
  260. global g_cancel_nuke_testing
  261. return g_cancel_nuke_testing
  262. def skip_cleanup(self):
  263. global CRYPTOMATTETEST_SKIP_CLEANUP_ON_FAILURE
  264. test_ok = (sys.exc_info() == (None, None, None) or sys.exc_info()[0] is unittest.SkipTest)
  265. if CRYPTOMATTETEST_SKIP_CLEANUP_ON_FAILURE and not test_ok:
  266. self.set_canceled(True) # ensure that teardownClass does not run
  267. return True
  268. else:
  269. return False
  270. def setUp(self):
  271. import nuke
  272. import cryptomatte_utilities as cu
  273. if self.canceled():
  274. self.skipTest("Remaining tests canceled.")
  275. return
  276. # raise RuntimeError("Remaining tests canceled.")
  277. if not hasattr(self, "read_asset"):
  278. # this happens pre-python 2.7, in Nuke 7.
  279. # We can still create everything and run tests in Nuke 7,
  280. # They'll just scatter some nodes about.
  281. self.setUpClass()
  282. cu.reset_manifest_cache()
  283. self._remove_later = []
  284. self.gizmo = self.tempNode("Cryptomatte", inputs=[self.read_asset])
  285. self.merge = self.tempNode(
  286. "Merge", inputs=[self.read_obj, self.read_asset], also_merge="all")
  287. self.copyMetadata = self.tempNode("CopyMetaData", inputs=[self.merge, self.read_asset])
  288. self.read_obj_dot = self.tempNode("Dot", inputs=[self.read_obj])
  289. def tearDown(self):
  290. import nuke
  291. import cryptomatte_utilities as cu
  292. if not self.canceled():
  293. if self.constant.sample("red", 0, 0) != 0.5:
  294. self.set_canceled(True)
  295. self.fail(("After running '%s', can no longer sample. "
  296. "No remaining tests can run.") % (self.id()))
  297. if not self.canceled() and not self.skip_cleanup():
  298. for node in self._remove_later:
  299. nuke.delete(node)
  300. cu.reset_manifest_cache()
  301. def tempNode(self, nodeType, **kwargs):
  302. """
  303. Creates a temporary nuke node that will be removed in teardown,
  304. after the test_ method.
  305. """
  306. import nuke
  307. func = getattr(nuke.nodes, nodeType)
  308. node = func(**kwargs)
  309. self._remove_later.append(node)
  310. return node
  311. def delete_nodes_after_test(self, nodes):
  312. self._remove_later += nodes
  313. #############################################
  314. # Keying constants (for cryptoAsset example)
  315. #############################################
  316. heroflower_expr = (
  317. "((uCryptoAsset00.red == 2.07262543558e+26) ? uCryptoAsset00.green : 0.0) + "
  318. "((uCryptoAsset00.blue == 2.07262543558e+26) ? uCryptoAsset00.alpha : 0.0) + "
  319. "((uCryptoAsset01.red == 2.07262543558e+26) ? uCryptoAsset01.green : 0.0) + "
  320. "((uCryptoAsset01.blue == 2.07262543558e+26) ? uCryptoAsset01.alpha : 0.0) + "
  321. "((uCryptoAsset02.red == 2.07262543558e+26) ? uCryptoAsset02.green : 0.0) + "
  322. "((uCryptoAsset02.blue == 2.07262543558e+26) ? uCryptoAsset02.alpha : 0.0) + 0")
  323. black_pkr = ("add", (700.0, 700.0))
  324. floweredge_pkr = ("add", (884.0, 662.0))
  325. bunny_pkr = ("add", (769.0, 429.0))
  326. set_pkr = ("add", (490.0, 250.0)) # on this pixel, extracted alpha == 1.0
  327. bunnyflower_pkr = ("add", (842.0, 441.0))
  328. rm_black_pkr = ("remove", black_pkr[1])
  329. rm_floweredge_pkr = ("remove", floweredge_pkr[1])
  330. rm_bunny_pkr = ("remove", bunny_pkr[1])
  331. rm_set_pkr = ("remove", set_pkr[1])
  332. rm_bunnyflower_pkr = ("remove", bunnyflower_pkr[1])
  333. #############################################
  334. # Utils - Keying and sampling
  335. #############################################
  336. def key_on_image(self, *args):
  337. self.key_on_gizmo(self.gizmo, *args)
  338. def key_on_gizmo(self, gizmo, *args):
  339. def pickerCoords(coord):
  340. return [0.0, 0.0, 0.0, 0.0, coord[0], coord[1], 0.0, 0.0]
  341. for action, coordinates in args:
  342. if action == "add":
  343. gizmo.knob("pickerAdd").setValue(pickerCoords(coordinates))
  344. elif action == "remove":
  345. gizmo.knob("pickerRemove").setValue(pickerCoords(coordinates))
  346. def _sample_gizmo_assert(self, pkr, msg, inverse, **kwargs):
  347. for channel, value in kwargs.iteritems():
  348. sample = self.gizmo.sample(channel, pkr[1][0], pkr[1][1])
  349. msg_resolved = "%s: (%s) %s vs %s" % (msg, channel, sample, value)
  350. if inverse:
  351. self.assertNotEqual(sample, value, msg_resolved)
  352. else:
  353. self.assertEqual(sample, value, msg_resolved)
  354. def assertSampleEqual(self, pkr, msg, **kwargs):
  355. self._sample_gizmo_assert(pkr, msg, False, **kwargs)
  356. def assertSampleNotEqual(self, pkr, msg, **kwargs):
  357. self._sample_gizmo_assert(pkr, msg, True, **kwargs)
  358. def assertMatteList(self, value, msg):
  359. ml = self.gizmo.knob("matteList").getValue()
  360. self.assertEqual(ml, value, '%s. ("%s" vs "%s")' % (msg, value, ml))
  361. def hash_channel(self, node, pkr, channel, num_scanlines=8, precision=None):
  362. """
  363. Returns a hash from the values a channel, tested in a number of
  364. horizontal scanlines across the images. A picker tuple can be
  365. provided to ensure the values on that scanline are checked.
  366. If channel=depth is provided, and it finds only "depth.Z",
  367. it will assume that was intended. "
  368. """
  369. import hashlib
  370. if channel not in ["red", "green", "blue", "alpha"] and channel not in node.channels():
  371. raise RuntimeError("Incorrect channel specified. %s" % channel)
  372. anything = False
  373. m = hashlib.md5()
  374. width, height = node.width(), node.height()
  375. if pkr:
  376. y = pkr[1][1]
  377. for x in xrange(width):
  378. sample = node.sample(channel, x, y)
  379. if precision:
  380. sample = round(sample, precision)
  381. anything = anything or bool(sample)
  382. m.update(str(sample))
  383. for y_index in xrange(num_scanlines):
  384. y = (float(y_index) + 0.5) * height / (num_scanlines)
  385. for x in xrange(width):
  386. sample = node.sample(channel, x, y)
  387. anything = anything or bool(sample)
  388. if precision:
  389. sample = round(sample, precision)
  390. m.update(str(sample))
  391. return m.hexdigest() if anything else 0
  392. def hash_channel_layer(self, node, layer, channel, num_scanlines=16):
  393. """Hashes some scanlines of a layer other than rgba, by shuffling first. """
  394. shuf = self.tempNode("Shuffle", inputs=[node])
  395. shuf.knob("in").setValue(layer)
  396. return self.hash_channel(shuf, None, channel, num_scanlines=num_scanlines)
  397. #############################################
  398. # Manifest tests
  399. #############################################
  400. def _create_bogus_asset_manifest(self):
  401. bad_md = '{set exr/cryptomatte/d593dd7/manifest "\{broken\}"}'
  402. return self.tempNode(
  403. "ModifyMetaData", inputs=[self.read_asset],
  404. metadata=bad_md, name="ModifyMetaData_rename")
  405. def _create_missing_asset_manifest(self):
  406. bad_md = '{remove exr/cryptomatte/d593dd7/manifest ""}'
  407. return self.tempNode(
  408. "ModifyMetaData", inputs=[self.read_asset],
  409. metadata=bad_md, name="ModifyMetaData_remove")
  410. def test_manifests(self):
  411. # Embedded and sidecar
  412. import cryptomatte_utilities as cu
  413. for read in [self.read_obj, self.read_asset, self.read_material, self.read_sidecar]:
  414. cinfo = cu.CryptomatteInfo(read)
  415. mismatches, collisions = cinfo.test_manifest(quiet=True)
  416. self.assertTrue(cinfo.parse_manifest(),
  417. "%s manifest not loaded. " % read.knob("file").getValue())
  418. self.assertEqual(mismatches, [], "%s manifest mismatch" % read.knob("file").getValue())
  419. def test_load_manifests_lazy(self):
  420. import cryptomatte_utilities as cu
  421. gizmo = self.tempNode("Cryptomatte", inputs=[self.read_asset])
  422. cinfo = cu.CryptomatteInfo(gizmo)
  423. self.assertNotIn("manifest", cinfo.cryptomattes[cinfo.selection])
  424. def test_load_manifests_nonlazy(self):
  425. import cryptomatte_utilities as cu
  426. gizmo = self.tempNode("Cryptomatte", inputs=[self.read_asset])
  427. cinfo = cu.CryptomatteInfo(gizmo, True)
  428. self.assertIn("manifest", cinfo.cryptomattes[cinfo.selection])
  429. def test_layer_bogus_manifest(self):
  430. import cryptomatte_utilities as cu
  431. def test_manifest_and_keying(input_node):
  432. cu.reset_manifest_cache()
  433. cinfo = cu.CryptomatteInfo(input_node)
  434. self.gizmo.knob("matteList").setValue("")
  435. self.gizmo.setInput(0, input_node)
  436. self.assertFalse(cinfo.parse_manifest(), "Bogus manifest still loaded. ")
  437. cu.update_cryptomatte_gizmo(self.gizmo, True) # tests that this doesn't raise.
  438. self.assertEqual(
  439. self.gizmo.knob("cryptoLayer").value(), "uCryptoAsset", "Layer selection not set.")
  440. self.key_on_image(self.bunny_pkr)
  441. self.assertMatteList("<3.36000126251e-27>", "Could not key with bogus manifest.")
  442. bogus_asset = self._create_bogus_asset_manifest()
  443. test_manifest_and_keying(bogus_asset)
  444. missing_manifest = self._create_missing_asset_manifest()
  445. test_manifest_and_keying(missing_manifest)
  446. #############################################
  447. # Layer Selection
  448. #############################################
  449. def test_layer_selection(self, node=None):
  450. gizmo = node if node else self.gizmo
  451. # layer selection set up properly in the first place
  452. self.assertEqual(
  453. gizmo.knob("cryptoLayer").value(), "uCryptoAsset", "Layer selection not set.")
  454. # switching inputs switches layer selections
  455. gizmo.setInput(0, self.read_obj)
  456. self.assertEqual(
  457. gizmo.knob("cryptoLayer").value(), "uCryptoObject",
  458. "Input change did not switch layers")
  459. gizmo.setInput(0, self.read_asset)
  460. self.assertEqual(
  461. gizmo.knob("cryptoLayer").value(), "uCryptoAsset", "Input change did not switch layers")
  462. # switching inputs to a multi-cryptomatte stream does not switch layer selections
  463. gizmo.setInput(0, self.copyMetadata)
  464. self.assertEqual(
  465. gizmo.knob("cryptoLayer").value(), "uCryptoAsset",
  466. "Input change to multi should not have switched layers")
  467. gizmo.setInput(0, self.read_obj)
  468. gizmo.setInput(0, self.copyMetadata)
  469. self.assertEqual(
  470. gizmo.knob("cryptoLayer").value(), "uCryptoObject",
  471. "Input change to multi should not have switch layers")
  472. def test_layer_selection_after_keying(self):
  473. """ Tests all layer selection options are still available after keying.
  474. """
  475. layers = ["uCryptoAsset", "uCryptoObject"]
  476. choice_knob = self.gizmo.knob("cryptoLayerChoice")
  477. self.gizmo.setInput(0, self.copyMetadata) # set to multi
  478. for layer in layers:
  479. choice_knob.setValue(layer)
  480. self.assertEqual(self.gizmo.knob("cryptoLayer").value(), layer)
  481. self.assertEqual(set(choice_knob.values()), set(layers))
  482. self.key_on_gizmo(self.gizmo, self.triangle_pkr, self.set_pkr)
  483. self.assertEqual(set(choice_knob.values()), set(layers))
  484. new_gizmo = self.tempNode(
  485. "Cryptomatte", cryptoLayer="uCryptoAsset",
  486. inputs=[self.copyMetadata], stopAutoUpdate=True)
  487. self.assertEqual(set(new_gizmo.knob("cryptoLayerChoice").values()), set(layers))
  488. def test_layer_lock(self, node=None):
  489. gizmo = node if node else self.gizmo
  490. # locking layer selection stops the switching
  491. gizmo.setInput(0, self.read_asset)
  492. gizmo.knob("cryptoLayerLock").setValue(True)
  493. gizmo.setInput(0, self.read_obj_dot)
  494. self.assertEqual(
  495. gizmo.knob("cryptoLayer").value(), "uCryptoAsset",
  496. "cryptoLayerLock did not keep things from changing.")
  497. gizmo.knob("cryptoLayerLock").setValue(False)
  498. self.assertEqual(
  499. gizmo.knob("cryptoLayer").value(), "uCryptoObject",
  500. "Disabling cryptoLayerLock did not set gizmo back to uCryptoObject.")
  501. def _setup_test_layer_forced_update_func(self, gizmo):
  502. gizmo.setInput(0, self.read_obj_dot)
  503. self.read_obj_dot.setInput(0, self.read_asset)
  504. if (gizmo.knob("cryptoLayer").value() != "uCryptoObject"):
  505. raise RuntimeError("Upstream changes now trigger updates, test is invalid %s " %
  506. gizmo.knob("cryptoLayer").value())
  507. def test_layer_forced_update_btn(self, node=None):
  508. gizmo = node if node else self.gizmo
  509. self._setup_test_layer_forced_update_func(gizmo)
  510. gizmo.knob("forceUpdate").execute()
  511. self.assertEqual(
  512. gizmo.knob("cryptoLayer").value(), "uCryptoAsset",
  513. "Update button did not update gizmo. %s" % (gizmo.knob("cryptoLayer").value()))
  514. def test_layer_forced_update_func(self, node=None):
  515. import cryptomatte_utilities as cu
  516. gizmo = node if node else self.gizmo
  517. self._setup_test_layer_forced_update_func(gizmo)
  518. cu.update_cryptomatte_gizmo(gizmo, True)
  519. self.assertEqual(
  520. gizmo.knob("cryptoLayer").value(), "uCryptoAsset",
  521. "Update function should have updated from upstream changes. %s" %
  522. (gizmo.knob("cryptoLayer").value()))
  523. #############################################
  524. # Keying
  525. #############################################
  526. def test_keying_nothing(self):
  527. self.key_on_image(self.black_pkr)
  528. self.assertMatteList("", "Something selected on black. ")
  529. def _test_keying_partial_black(self, msg=""):
  530. # used as setup for other tests
  531. self.key_on_image(self.floweredge_pkr)
  532. self.assertMatteList("heroflower", msg or "Hero flower not selected on partial pixels. ")
  533. self.assertEqual(
  534. self.gizmo.knob("expression").getValue(), self.heroflower_expr, msg or
  535. "Hero flower expression was wrong. ")
  536. def test_keying_partial_black(self):
  537. self._test_keying_partial_black()
  538. def test_keying_manual(self):
  539. self.gizmo.knob("matteList").setValue("heroflower")
  540. self.assertEqual(
  541. self.gizmo.knob("expression").getValue(), self.heroflower_expr,
  542. "Expression did not update on setting matte list. ")
  543. def test_keying_with_removechannels(self):
  544. self.gizmo.knob("RemoveChannels").setValue(True)
  545. self.key_on_image(self.bunny_pkr)
  546. self.assertMatteList("bunny", "Could not key on image after Remove Channels was enabled.")
  547. def test_keying_blank_matteList(self):
  548. self._test_keying_partial_black()
  549. self.gizmo.knob("matteList").setValue("")
  550. self.assertEqual(
  551. self.gizmo.knob("expression").getValue(), "",
  552. "Expression did not update on blanking matte list. ")
  553. def test_keying_clear(self):
  554. import cryptomatte_utilities as cu
  555. self._test_keying_partial_black()
  556. cu.clear_cryptomatte_gizmo(self.gizmo)
  557. self.assertMatteList("", "Clear() failed. ")
  558. self.assertEqual(self.gizmo.knob("expression").getValue(), "", "Clear() failed. ")
  559. def test_clear_button(self):
  560. import cryptomatte_utilities as cu
  561. self._test_keying_partial_black()
  562. self.gizmo.knob("clear").execute()
  563. self.assertMatteList("", "Clear button failed. ")
  564. self.assertEqual(self.gizmo.knob("expression").getValue(), "", "Clear button failed. ")
  565. def test_keying_multiselect(self):
  566. import cryptomatte_utilities as cu
  567. # Multiselect over partial black
  568. self.key_on_image(self.floweredge_pkr, self.floweredge_pkr, self.floweredge_pkr)
  569. self.assertMatteList("heroflower",
  570. "Multiselect on edge didn't select only 'heroflower': (%s)" %
  571. self.gizmo.knob("matteList").getValue())
  572. # Multiselect over partial pixels
  573. cu.clear_cryptomatte_gizmo(self.gizmo)
  574. self.key_on_image(self.bunnyflower_pkr, self.black_pkr, self.bunnyflower_pkr)
  575. self.assertMatteList("bunny, heroflower", "Same pixel multiple selection was wrong: (%s)" %
  576. self.gizmo.knob("matteList").getValue())
  577. # Add set to selection
  578. self.key_on_image(self.set_pkr)
  579. self.assertMatteList(
  580. "bunny, heroflower, set",
  581. "Multi selection was wrong: (%s)" % self.gizmo.knob("matteList").getValue())
  582. # Remove bunny and flower
  583. self.key_on_image(self.rm_bunny_pkr, self.rm_black_pkr, self.rm_floweredge_pkr)
  584. self.assertMatteList(
  585. "set", "Bunny and flower not removed: (%s)" % self.gizmo.knob("matteList").getValue())
  586. def test_keying_single_selection(self):
  587. # Single selection
  588. self.gizmo.knob("matteList").setValue("bunny, heroflower, set")
  589. self.gizmo.knob("singleSelection").setValue(True)
  590. self.assertMatteList("bunny, heroflower, set",
  591. "Single selection knob changed without selection changed matte list.")
  592. self.key_on_image(self.set_pkr)
  593. self.assertMatteList(
  594. "set",
  595. "Single selection seems to have failed: (%s)" % self.gizmo.knob("matteList").getValue())
  596. for _ in range(5):
  597. self.key_on_image(self.black_pkr, self.bunnyflower_pkr)
  598. self.assertMatteList("bunny", "Single selection may be flickering on partials")
  599. def test_keying_stop_auto_update(self):
  600. self.gizmo.knob("stopAutoUpdate").setValue(True)
  601. self.gizmo.knob("expression").setValue(self.heroflower_expr)
  602. self.key_on_image(self.bunny_pkr)
  603. self.key_on_image(self.rm_set_pkr)
  604. self.key_on_image(self.rm_black_pkr)
  605. self.gizmo.knob("matteList").setValue("hello")
  606. self.assertEqual(
  607. self.gizmo.knob("expression").getValue(), self.heroflower_expr,
  608. "Stop auto update did not work. ")
  609. def test_keying_without_preview_channels(self):
  610. """
  611. Test that the gizmo can be set up and used properly without
  612. the preview channels being available.
  613. """
  614. self.gizmo.setInput(0, self.read_material) # switch layer selection
  615. remove = self.tempNode("Remove", inputs=[self.read_asset], channels="uCryptoAsset")
  616. self.gizmo.setInput(0, remove)
  617. self._test_keying_partial_black()
  618. self.gizmo.knob("forceUpdate").execute()
  619. def test_keying_without_prefix(self):
  620. """
  621. If the image is loaded without the exr/ prefix, things should keep working.
  622. """
  623. exception = None
  624. try:
  625. self.read_asset.knob("noprefix").setValue(True)
  626. self.gizmo.knob("forceUpdate").execute()
  627. self._test_keying_partial_black("Keying failed once read-node prefix was disabled.")
  628. except Exception, e:
  629. exception = e
  630. self.read_asset.knob("noprefix").setValue(False)
  631. if exception:
  632. raise exception
  633. #############################################
  634. # Output checking
  635. #############################################
  636. def test_output_preview(self):
  637. self.key_on_image(self.bunny_pkr)
  638. msg = "Selection did not light up properly. %s, %s"
  639. self.assertSampleEqual(
  640. self.bunny_pkr, "Preview image did not light up", red=1.0, green=1.0, alpha=1.0)
  641. self.assertSampleNotEqual(self.set_pkr, "Set pixels should be dark.", red=1.0, green=1.0)
  642. self.assertSampleEqual(self.set_pkr, "Set pixels should be unselected.", alpha=0.0)
  643. def test_output_preview_disabled(self):
  644. # stops lighting up after disabled, but alpha still correct
  645. self.key_on_image(self.bunny_pkr)
  646. self.gizmo.knob("previewEnabled").setValue(False)
  647. self.assertSampleEqual(
  648. self.bunny_pkr,
  649. "Preview image bunny pixels wrong when disabled",
  650. red=0.0,
  651. green=0.0,
  652. alpha=1.0)
  653. self.assertSampleEqual(
  654. self.set_pkr,
  655. "Preview image set pixels wrong when disabled",
  656. red=0.0,
  657. green=0.0,
  658. alpha=0.0)
  659. self.gizmo.knob("previewEnabled").setValue(True)
  660. def test_output_preview_multi(self):
  661. # add an item, make sure it lights up too
  662. self.key_on_image(self.bunny_pkr, self.set_pkr)
  663. self.assertSampleEqual(
  664. self.bunny_pkr, "Bunny pixels are wrong.", red=1.0, green=1.0, alpha=1.0)
  665. self.assertSampleEqual(self.set_pkr, "Set pixels are wrong.", red=1.0, green=1.0, alpha=1.0)
  666. #############################################
  667. # Matte list manipulations
  668. #############################################
  669. def _clear_manifest_cache(self):
  670. import cryptomatte_utilities as cu
  671. cu.g_cryptomatte_manf_from_names = {}
  672. cu.g_cryptomatte_manf_from_IDs = {}
  673. def test_matte_list_numeric(self):
  674. """
  675. Tests what happens with matte lists if you have a name matte list, but pick without a
  676. manifest, and vice version. It should be smart enough to not create anything redundant.
  677. """
  678. import nuke
  679. numeric_mlist_bunny = "<3.36000126251e-27>"
  680. numeric_mlist_set = "<7.36562399642e+18>"
  681. numeric_mlist_both = "<3.36000126251e-27>, <7.36562399642e+18>"
  682. name_mlist_bunny = "bunny"
  683. mod_md_node = self.tempNode(
  684. "ModifyMetaData",
  685. inputs=[self.read_asset],
  686. metadata='{set exr/cryptomatte/d593dd7/manifest "\{\}"}')
  687. self.gizmo.setInput(0, mod_md_node)
  688. self._clear_manifest_cache()
  689. self.key_on_image(self.bunny_pkr)
  690. self.assertMatteList(numeric_mlist_bunny, "Numeric mattelist incorrect.")
  691. # test adding redundant item numerically to a named selection
  692. self.gizmo.knob("matteList").setValue(name_mlist_bunny)
  693. self.key_on_image(self.bunny_pkr)
  694. self.assertMatteList("bunny", "Redundant matte list generated.")
  695. # test that removing named item by number works
  696. self.key_on_image(self.rm_bunny_pkr)
  697. self.assertMatteList("", "Removal of name by number failed.")
  698. # test that adding and removing numeric items works from a named list
  699. self.gizmo.knob("matteList").setValue("bunny, heroflower")
  700. self.key_on_image(self.set_pkr, self.rm_bunny_pkr)
  701. self.assertMatteList("<7.36562399642e+18>, heroflower", "Removal of name by number failed.")
  702. self.key_on_image(self.bunny_pkr)
  703. self.assertMatteList("<3.36000126251e-27>, <7.36562399642e+18>, heroflower",
  704. "Adding number to a name list failed.")
  705. def test_matte_list_name_modifications(self):
  706. self.gizmo.knob("matteList").setValue(
  707. "<3.36000126251e-27>, <7.36562399642e+18>, heroflower")
  708. self.key_on_image(self.rm_bunny_pkr, self.rm_set_pkr, self.rm_floweredge_pkr,
  709. self.rm_floweredge_pkr)
  710. self.assertMatteList("", "Could not remove numbers by name.")
  711. self.key_on_image(self.bunny_pkr, self.set_pkr)
  712. self.assertMatteList("bunny, set", "Could not re-add by picking.")
  713. #############################################
  714. # Decryptomatte
  715. #############################################
  716. def test_decrypto_basic(self):
  717. """ Tests both basic decryptomatte, as well as ensuring hash_channel() is
  718. returning different hashes for different values.
  719. """
  720. import cryptomatte_utilities as cu
  721. self.key_on_image(self.bunny_pkr)
  722. wrong_hash = self.hash_channel(self.gizmo, self.bunny_pkr, "green")
  723. correct_hash = self.hash_channel(self.gizmo, self.bunny_pkr, "alpha")
  724. new_nodes = cu._decryptomatte(self.gizmo)
  725. self.delete_nodes_after_test(new_nodes)
  726. decryptomatted_hash = self.hash_channel(new_nodes[-1], self.bunny_pkr, "alpha")
  727. self.assertNotEqual(decryptomatted_hash, wrong_hash,
  728. "Wrong hash is same as right hash, scanlines may be broken.")
  729. self.assertEqual(decryptomatted_hash, correct_hash,
  730. "Decryptomatte caused a different alpha from Cryptomatte.")
  731. def test_decrypto_in_group(self):
  732. """ Tests basic decryptomatte in a group.
  733. """
  734. # Put a cryptomatte with some values on it in a group
  735. import nuke
  736. self.key_on_image(self.bunny_pkr)
  737. gizmo_name = self.gizmo.fullName()
  738. self.gizmo['selected'].setValue(True)
  739. group = nuke.collapseToGroup(show=False)
  740. for node in nuke.selectedNodes():
  741. node['selected'].setValue(False)
  742. # Clean up of the group will remove the gizmo as well
  743. self.delete_nodes_after_test([group])
  744. self._remove_later = [x for x in self._remove_later if x != self.gizmo]
  745. self.gizmo = None
  746. # press the button
  747. grouped_gizmo = nuke.toNode("%s.%s" % (group.fullName(), gizmo_name))
  748. self.assertFalse(grouped_gizmo.knob("disable").value())
  749. grouped_gizmo.knob("decryptomatte").execute()
  750. self.assertTrue(grouped_gizmo.knob("disable").value())
  751. def test_decrypto_custom_channel(self):
  752. import cryptomatte_utilities as cu
  753. custom_layer = "uCryptoAsset" # guaranteed to already exist.
  754. custom_layer_subchannel = "%s.red" % custom_layer
  755. self.key_on_image(self.set_pkr)
  756. self.gizmo.knob("matteOutput").setValue(custom_layer)
  757. alpha_hash = self.hash_channel(self.gizmo, self.set_pkr, "alpha")
  758. correct_hash = self.hash_channel(self.gizmo, self.set_pkr, custom_layer_subchannel)
  759. new_nodes = cu._decryptomatte(self.gizmo)
  760. self.delete_nodes_after_test(new_nodes)
  761. decryptomatte_hash = self.hash_channel(new_nodes[-1], self.set_pkr, custom_layer_subchannel)
  762. self.assertNotEqual(correct_hash, alpha_hash, "Custom channel is the same as the alpha.")
  763. self.assertEqual(correct_hash, decryptomatte_hash,
  764. "Decryptomatte in a custom channel mismatch.")
  765. def test_decrypto_matteonly_unpremul(self):
  766. import cryptomatte_utilities as cu
  767. custom_layer = "uCryptoAsset" # guaranteed to already exist
  768. prec = 5
  769. # self.gizmo will have alpha for unpremult.
  770. self.key_on_image(self.set_pkr, self.bunny_pkr)
  771. new_gizmo = self.tempNode(
  772. "Cryptomatte",
  773. inputs=[self.gizmo],
  774. matteList="bunny",
  775. matteOnly=True,
  776. matteOutput=custom_layer)
  777. no_unpremult_hash = self.hash_channel(new_gizmo, self.set_pkr, "alpha", precision=prec)
  778. new_gizmo.knob("unpremultiply").setValue(True)
  779. channels = ["red", "green", "alpha", "%s.red" % custom_layer]
  780. gizmo_hashes = [self.hash_channel(new_gizmo, self.set_pkr, ch, precision=prec)
  781. for ch in channels]
  782. new_nodes = cu._decryptomatte(new_gizmo)
  783. self.delete_nodes_after_test(new_nodes)
  784. decrypto_hashes = [self.hash_channel(new_nodes[-1], self.set_pkr, ch, precision=prec)
  785. for ch in channels]
  786. self.assertNotEqual(no_unpremult_hash, gizmo_hashes[0],
  787. "Unpremult didn't seem to change anything.")
  788. for ch, g_hash, dc_hash in zip(channels, gizmo_hashes, decrypto_hashes):
  789. self.assertEqual(g_hash, dc_hash,
  790. "Difference between decryptomatte (%s) and regular (%s) in %s"
  791. % (new_nodes[-1].name(), new_gizmo.name(), ch))
  792. for i, channel in enumerate(channels[:-1]):
  793. msg = "Matte-only difference between %s and %s" % (channels[i], channels[i + 1])
  794. self.assertEqual(decrypto_hashes[i], decrypto_hashes[i + 1], msg)
  795. def test_decrypto_rmchannels_customlayer(self):
  796. self._test_decrypto_rmchannels("uCryptoAsset")
  797. def test_decrypto_rmchannels_alpha(self):
  798. self._test_decrypto_rmchannels("alpha")
  799. def _test_decrypto_rmchannels(self, output_layer="alpha"):
  800. import cryptomatte_utilities as cu
  801. orig_channels = set(x for x in self.gizmo.channels())
  802. self.gizmo.knob("RemoveChannels").setValue(True)
  803. self.gizmo.knob("matteOutput").setValue(output_layer)
  804. channels_removed = set(x for x in self.gizmo.channels())
  805. new_nodes = cu._decryptomatte(self.gizmo)
  806. self.delete_nodes_after_test(new_nodes)
  807. channels_removed_decrypto = set(x for x in new_nodes[-1].channels())
  808. channels_removed_decrypto |= set(['rgba.red', 'rgba.green', 'rgba.blue', 'rgba.alpha'])
  809. self.assertNotEqual(orig_channels, channels_removed,
  810. "Removing channels did nothing (%s)" % output_layer)
  811. self.assertEqual(channels_removed, channels_removed_decrypto,
  812. "Channels not removed properly after decrypto. (%s)" % output_layer)
  813. #############################################
  814. # Gizmo integrity
  815. #############################################
  816. def test_gizmo_version(self, node=None):
  817. import cryptomatte_utilities as cu
  818. def test_version(gizmo):
  819. gizmo_version = gizmo.knob("cryptomatteVersion").value()
  820. self.assertEqual(
  821. gizmo_version, cu.__version__,
  822. "%s version not same as Python version. "
  823. "(%s, %s)" % (gizmo.Class(), cu.__version__, gizmo_version))
  824. test_version(self.gizmo)
  825. test_version(self.tempNode("Encryptomatte"))
  826. def test_crypto_channel_knobs_type(self, node=None):
  827. import cryptomatte_utilities as cu
  828. for channel in cu.GIZMO_CHANNEL_KNOBS:
  829. self.assertTrue(
  830. self.gizmo.knob(channel).Class() in set(["Channel_Knob", "ChannelMask_Knob"]),
  831. "Input knob was not a channel knob, which causes failed renders "
  832. "due to expression errors on load. (%s)" % self.gizmo.knob(channel).Class())
  833. def test_encrypt_channel_knobs_type(self, node=None):
  834. import cryptomatte_utilities as cu
  835. encrypt = self.tempNode("Encryptomatte")
  836. for channel in cu.GIZMO_REMOVE_CHANNEL_KNOBS + cu.GIZMO_ADD_CHANNEL_KNOBS:
  837. self.assertTrue(
  838. encrypt.knob(channel).Class() in set(["Channel_Knob", "ChannelMask_Knob"]),
  839. "Input knob was not a channel knob, which causes failed renders "
  840. "due to expression errors on load. (%s)" % encrypt.knob(channel).Class())
  841. #############################################
  842. # Encryptomatte
  843. #############################################
  844. # todo(jfriedman): test stop_auto_update, auto filling in "matte name"
  845. triangle_coords = [
  846. [916.0, 512.0],
  847. [839.0, 416.0],
  848. [828.0, 676.0],
  849. ]
  850. triangle_pkr = ("add", (840.0, 615.0))
  851. def _setup_rotomask(self):
  852. """Which partially covers the flower."""
  853. import nuke.rotopaint as rp
  854. self.gizmo.setInput(0, self.read_asset)
  855. rotomask = self.tempNode("Roto", inputs=[self.read_asset])
  856. shape = rp.Shape(rotomask['curves'])
  857. shape.getAttributes().set("fx", 20)
  858. shape.getAttributes().set("fy", 20)
  859. for pos in self.triangle_coords:
  860. shape.append(pos)
  861. rotomask['curves'].rootLayer.append(shape)
  862. return rotomask
  863. def test_encrypt_layerselection(self):
  864. encryptomatte = self.tempNode("Encryptomatte", inputs=[self.gizmo])
  865. self.test_layer_selection(encryptomatte)
  866. def test_encrypt_layer_lock(self):
  867. encryptomatte = self.tempNode("Encryptomatte", inputs=[self.gizmo])
  868. self.test_layer_lock(encryptomatte)
  869. def test_encrypt_layer_forced_update_btn(self):
  870. encryptomatte = self.tempNode("Encryptomatte", inputs=[self.gizmo])
  871. self.test_layer_forced_update_btn(encryptomatte)
  872. def test_encrypt_matte_name_autofill2(self):
  873. encryptomatte = self.tempNode("Encryptomatte", inputs=[self.read_asset])
  874. self.assertEqual(
  875. encryptomatte.knob("matteName").getValue(), "",
  876. "Encryptomatte got a matte name not properly blank to start. ")
  877. encryptomatte.setInput(1, self.constant)
  878. self.assertEqual(
  879. encryptomatte.knob("matteName").getValue(),
  880. self.constant.name(), "Encryptomatte matte name was not set automatically.")
  881. encryptomatte.setInput(1, self.gizmo)
  882. self.assertEqual(
  883. encryptomatte.knob("matteName").getValue(),
  884. self.constant.name(), ("Encryptomatte matte should not have been "
  885. "set automatically if it was already connected. "))
  886. preconnected_encrypto = self.tempNode(
  887. "Encryptomatte", inputs=[self.read_asset, self.constant])
  888. self.assertEqual(
  889. encryptomatte.knob("matteName").getValue(),
  890. self.constant.name(), "Encryptomatte matte name was not set automatically on creation.")
  891. def test_encrypt_bogus_inputs(self):
  892. """ Tests that when setting up layers, entering the name before pressing "setup layers"
  893. doesn't spew python errors but fails gracefully.
  894. """
  895. import cryptomatte_utilities as cu
  896. encryptomatte = self.tempNode("Encryptomatte", matteName="triangle")
  897. try:
  898. encryptomatte.knob("cryptoLayer").setValue("customCrypto")
  899. cu.encryptomatte_knob_changed_event(encryptomatte, encryptomatte.knob("cryptoLayer"))
  900. encryptomatte.knob("setupLayers").setValue(True)
  901. cu.encryptomatte_knob_changed_event(encryptomatte, encryptomatte.knob("setupLayers"))
  902. except Exception, e:
  903. self.fail("Invalid crypto layer name raises error: %s" % e)
  904. def test_encrypt_setup_layers_numbers(self):
  905. """ Tests that when setting up layers, entering the name before pressing "setup layers"
  906. doesn't spew python errors but fails gracefully.
  907. """
  908. encryptomatte = self.tempNode("Encryptomatte", matteName="triangle")
  909. encryptomatte.knob("setupLayers").setValue(True)
  910. encryptomatte.knob("cryptoLayer").setValue("customCrypto")
  911. customLayers = [
  912. "customCrypto00", "customCrypto01", "customCrypto02", "customCrypto03",
  913. "customCrypto04", "customCrypto05", "customCrypto06", "customCrypto07",
  914. "customCrypto08", "customCrypto09"
  915. ]
  916. encryptomatte.knob("cryptoLayers").setValue(3)
  917. channels = set(encryptomatte.channels())
  918. for layer in customLayers[:3]:
  919. self.assertTrue("%s.red" % layer in channels, "%s not in channels" % layer)
  920. for layer in customLayers[3:]:
  921. self.assertFalse("%s.red" % layer in channels, "%s in channels" % layer)
  922. encryptomatte.knob("cryptoLayers").setValue(6)
  923. channels = encryptomatte.channels()
  924. for layer in customLayers[:6]:
  925. self.assertTrue("%s.red" % layer in channels, "%s not in channels" % layer)
  926. for layer in customLayers[6:]:
  927. self.assertFalse("%s.red" % layer in channels, "%s in channels" % layer)
  928. def test_encrypt_roundtrip_setup(self):
  929. import cryptomatte_utilities as cu
  930. keysurf_hash = self.hash_channel(self.gizmo, None, "blue", num_scanlines=16)
  931. roto_hash = self.hash_channel(self._setup_rotomask(), None, "alpha", num_scanlines=16)
  932. id0_hash = self.hash_channel_layer(self.read_asset, "uCryptoAsset00", "red")
  933. encryptomatte = self.tempNode(
  934. "Encryptomatte", inputs=[self.read_asset, self._setup_rotomask()], matteName="triangle")
  935. second_cryptomatte = self.tempNode(
  936. "Cryptomatte", matteList="triangle")
  937. second_cryptomatte.setInput(0, encryptomatte)
  938. self.assertEqual(encryptomatte.knob("cryptoLayer").getValue(), "uCryptoAsset", "Layer was not set")
  939. new_id0_hash = self.hash_channel_layer(encryptomatte, "uCryptoAsset00", "red")
  940. self.assertNotEqual(id0_hash, new_id0_hash, "ID channel did not change. ")
  941. decrypto_hash = self.hash_channel(second_cryptomatte, None, "alpha", num_scanlines=16)
  942. mod_keysurf_hash = self.hash_channel(second_cryptomatte, None, "alpha", num_scanlines=16)
  943. self.assertEqual(roto_hash, decrypto_hash, ("Alpha did not survive round trip through "
  944. "Encryptomatte and then Cryptomatte. "))
  945. self.assertNotEqual(keysurf_hash, mod_keysurf_hash, "preview image did not change. ")
  946. cinfo = cu.CryptomatteInfo(second_cryptomatte)
  947. names_to_IDs = cinfo.parse_manifest()
  948. self.assertTrue("set" in names_to_IDs, "Manifest doesn't contain original manifest")
  949. self.assertTrue("triangle" in names_to_IDs, "Manifest doesn't contain new members")
  950. def test_encrypt_roundtrip_keyable(self):
  951. encryptomatte = self.tempNode(
  952. "Encryptomatte", inputs=[self.read_asset, self._setup_rotomask()], matteName="triangle")
  953. self.gizmo.setInput(0, encryptomatte)
  954. self.key_on_image(self.set_pkr, self.triangle_pkr)
  955. mlist = self.gizmo.knob("matteList").getValue()
  956. self.assertEqual(mlist, "set, triangle",
  957. "Encrypto-modified manifest not properly keyable. {0}".format(mlist))
  958. self.assertTrue("set" in mlist, "Couldn't properly key 'set' (Pre-existing)")
  959. self.assertTrue("triangle" in mlist, "Couldn't properly key 'triangle' (New Member)")
  960. def test_encrypt_roundtrip_without_prefix(self):
  961. self.read_asset.knob("noprefix").setValue(True)
  962. exception = None
  963. try:
  964. self.test_encrypt_roundtrip_setup()
  965. except:
  966. raise
  967. finally:
  968. self.read_asset.knob("noprefix").setValue(False)
  969. def test_encrypt_bogus_manifest(self):
  970. import cryptomatte_utilities as cu
  971. mod_md_node = self._create_bogus_asset_manifest()
  972. encryptomatte = self.tempNode(
  973. "Encryptomatte", inputs=[mod_md_node, self._setup_rotomask()], matteName="triangle")
  974. self.gizmo.setInput(0, encryptomatte)
  975. self.key_on_image(self.triangle_pkr)
  976. self.gizmo.knob("forceUpdate").execute()
  977. self.assertMatteList("<1.54567320652e-21>", "Did not update after keying. ")
  978. def broken_test_parts():
  979. """ this should work, but doesn't for some reason. It produces the right result
  980. as seen in failfast, but breaks tests. The encryptomatte tests are generally too
  981. brittle and this is one example. """
  982. self.assertSampleEqual(
  983. self.triangle_pkr, "Encryptomatte result not keyable after bogus manifest", alpha=1.0)
  984. # broken_test_parts()
  985. def test_encrypt_merge_operations(self):
  986. import cryptomatte_utilities as cu
  987. encry_over = self.tempNode(
  988. "Encryptomatte",
  989. inputs=[self.read_asset, self._setup_rotomask()],
  990. matteName="triangle",
  991. mergeOperation="over")
  992. id0_hash = self.hash_channel_layer(encry_over, "uCryptoAsset00", "red")
  993. # use a new rotomask every time, less bug prone in Nuke.
  994. encry_under = self.tempNode(
  995. "Encryptomatte",
  996. inputs=[self.read_asset, self._setup_rotomask()],
  997. matteName="triangle",
  998. mergeOperation="under")
  999. id0_mod_hash = self.hash_channel_layer(encry_under, "uCryptoAsset00", "red")
  1000. self.assertNotEqual(id0_hash, id0_mod_hash, "Under mode did not change ID 0. ")
  1001. def test_encrypt_fresh_roundtrip(self):
  1002. constant2k = self.tempNode("Constant", format="square_2K")
  1003. empty_hash = self.hash_channel(constant2k, None, "alpha", num_scanlines=16)
  1004. roto1k = self._setup_rotomask()
  1005. roto1k.setInput(0, constant2k)
  1006. roto_hash = self.hash_channel(roto1k, None, "alpha", num_scanlines=16)
  1007. if empty_hash == roto_hash:
  1008. raise RuntimeError("Roto matte did not change alpha, test is invalid. (%s)" % roto_hash)
  1009. constant2k = self.tempNode("Constant", format="square_1K")
  1010. encryptomatte = self.tempNode(
  1011. "Encryptomatte", inputs=[constant2k, roto1k], matteName="triangle")
  1012. encryptomatte.knob("cryptoLayer").setValue("customCrypto")
  1013. encryptomatte.knob("setupLayers").setValue(True)
  1014. self.gizmo.setInput(0, encryptomatte)
  1015. self.key_on_image(self.triangle_pkr)
  1016. self.assertMatteList("triangle", "Encryptomatte did not produce a keyable triangle")
  1017. def broken_test_parts():
  1018. """ this should work, but doesn't for some reason. It produces the right result
  1019. as seen in failfast, but breaks tests. The encryptomatte tests are generally too
  1020. brittle and this is one example. """
  1021. self.gizmo.knob("forceUpdate").execute() # needed for some reason.
  1022. merge = self.tempNode("Merge", inputs=[constant2k, roto1k])
  1023. roto_hash_720 = self.hash_channel(merge, None, "alpha", num_scanlines=16)
  1024. decrypto_hash = self.hash_channel(self.gizmo, None, "alpha", num_scanlines=16)
  1025. self.assertEqual(
  1026. roto_hash_720, decrypto_hash,
  1027. ("Alpha did not survive round trip through "
  1028. "Encryptomatte (%s) and then Cryptomatte (%s). ") % (roto_hash_720, decrypto_hash))
  1029. # broken_test_parts()
  1030. def test_encrypt_manifest(self):
  1031. """Gets it into a weird state where it has a manifest but no cryptomatte."""
  1032. import cryptomatte_utilities as cu
  1033. encryptomatte = self.tempNode(
  1034. "Encryptomatte", inputs=[self.gizmo, self.constant], matteName="test")
  1035. cu.encryptomatte_knob_changed_event(encryptomatte, encryptomatte.knob("matteName"))
  1036. encryptomatte.knob("setupLayers").setValue(True)
  1037. encryptomatte.knob("cryptoLayer").setValue("customCrypto")
  1038. encryptomatte.knob("setupLayers").setValue(False)
  1039. cu.encryptomatte_knob_changed_event(encryptomatte, encryptomatte.knob("matteName"))
  1040. def test_encrypt_fresh_keyable(self):
  1041. """Tests fresh Encryptomatte setup where there is no input constant."""
  1042. import cryptomatte_utilities as cu
  1043. encryptomatte = self.tempNode(
  1044. "Encryptomatte", inputs=[None, self._setup_rotomask()], matteName="triangle",
  1045. setupLayers=True, cryptoLayer="custom_crypto")
  1046. self.gizmo.setInput(0, encryptomatte)
  1047. self.key_on_image(self.triangle_pkr)
  1048. self.assertMatteList("triangle", "Encryptomatte did not produce a keyable triangle")
  1049. def test_channel_identification(self):
  1050. """Makes sure multiple Cryptomattes can have sub-channels identified properly,
  1051. even if one's name starts with the others. """
  1052. import cryptomatte_utilities as cu
  1053. lyr_a_name, lyr_b_name = "cryLyr", "cryLyrSecond"
  1054. lyr_a_result = ['cryLyr00', 'cryLyr01', 'cryLyr02']
  1055. lyr_b_result = ['cryLyrSecond00', 'cryLyrSecond01', 'cryLyrSecond02']
  1056. encrypt_a = self.tempNode("Encryptomatte", setupLayers=True, cryptoLayer=lyr_a_name)
  1057. encrypt_b = self.tempNode("Encryptomatte", setupLayers=True, cryptoLayer=lyr_b_name)
  1058. encrypt_b.setInput(0, encrypt_a)
  1059. self.gizmo.setInput(0, encrypt_b)
  1060. cinfo = cu.CryptomatteInfo(self.gizmo)
  1061. identified_a = cinfo._identify_channels(lyr_a_name)
  1062. identified_b = cinfo._identify_channels(lyr_b_name)
  1063. self.assertEqual(lyr_a_result, identified_a)
  1064. self.assertEqual(lyr_b_result, identified_b)
  1065. cinfo.set_selection(lyr_a_name)
  1066. self.assertEqual(cinfo.get_channels(), identified_a)
  1067. cinfo.set_selection(lyr_b_name)
  1068. self.assertEqual(cinfo.get_channels(), identified_b)
  1069. #############################################
  1070. # Blender names ("with . in names)
  1071. #############################################
  1072. nuke_unfriendly_channel_names = [
  1073. ("custom.crypto", "custom_crypto"),
  1074. ("amp&rsand", "amp_rsand"),
  1075. ("per.od", "per_od"),
  1076. (".per.od", "_per_od"),
  1077. ("numlast_123", "numlast_123"),
  1078. ("123_numfirst", "_123_numfirst"),
  1079. ("123_numfirst.", "_123_numfirst_"),
  1080. ("num_123_middle", "num_123_middle"),
  1081. ]
  1082. def test_blendery_names_in_metadata(self):
  1083. """Tests that names with nuke-unfriendly layer names are mangled property."""
  1084. encryptomatte = self.tempNode(
  1085. "Encryptomatte", inputs=[None, self._setup_rotomask()], matteName="triangle",
  1086. setupLayers=True)
  1087. for bad_name, corrected_name in self.nuke_unfriendly_channel_names:
  1088. encryptomatte.knob("cryptoLayer").setValue(bad_name)
  1089. name_key = ""
  1090. metadata = encryptomatte.metadata()
  1091. for key in metadata:
  1092. if key.startswith("cryptomatte/") and key.endswith("/name"):
  1093. name_key = key
  1094. break
  1095. self.assertEqual(metadata[name_key], corrected_name)
  1096. modify_md = self.tempNode(
  1097. "ModifyMetaData", inputs=[encryptomatte],
  1098. metadata='{set %s "%s"}' % (name_key, bad_name))
  1099. self.gizmo.setInput(0, modify_md)
  1100. # For some reason, on first runs after opening a fresh nuke (12.0v3)
  1101. # this does not always update on its own.
  1102. encryptomatte.knob("forceUpdate").execute()
  1103. self.gizmo.knob("forceUpdate").execute()
  1104. self.assertEqual(
  1105. self.gizmo.knob("cryptoLayer").getValue(), corrected_name,
  1106. "Period should be removed from name and it should key.")
  1107. self.key_on_image(self.triangle_pkr)
  1108. self.assertMatteList("triangle", "Did not produce a keyable triangle")
  1109. self.test_bad_names_in_nuke_layers()
  1110. def test_blendery_names_encryptomatte(self):
  1111. """Tests that names with nuke-unfriendly layer names are mangled property by Encryptomatte."""
  1112. encryptomatte = self.tempNode(
  1113. "Encryptomatte", inputs=[None, self._setup_rotomask()], matteName="triangle",
  1114. setupLayers=True)
  1115. for bad_name, corrected_name in self.nuke_unfriendly_channel_names:
  1116. encryptomatte.knob("cryptoLayer").setValue(bad_name)
  1117. self.assertEqual(
  1118. corrected_name, encryptomatte.knob("cryptoLayer").getValue(),
  1119. "Period should be removed from Encryptomatte name.")
  1120. self.gizmo.setInput(0, encryptomatte)
  1121. # For some reason, on first runs after opening a fresh nuke (12.0v3)
  1122. # this does not always update on its own.
  1123. encryptomatte.knob("forceUpdate").execute()
  1124. self.gizmo.knob("forceUpdate").execute()
  1125. self.assertEqual(
  1126. self.gizmo.knob("cryptoLayer").getValue(), corrected_name,
  1127. "Period should be removed from name and it should key.")
  1128. self.key_on_image(self.triangle_pkr)
  1129. self.assertMatteList("triangle", "Did not produce a keyable triangle")
  1130. self.test_bad_names_in_nuke_layers()
  1131. def test_bad_names_in_nuke_layers(self):
  1132. import nuke
  1133. import cryptomatte_utilities as cu
  1134. for bad_name, corrected_name in self.nuke_unfriendly_channel_names:
  1135. if bad_name != cu._legal_nuke_layer_name(bad_name):
  1136. self.assertNotIn(
  1137. bad_name + "00", nuke.layers(),
  1138. "Bad layer (%s) got into nuke layers. Restarting Nuke is required to test this again." % bad_name)
  1139. def test_file_open_doesnt_update_nodes(self):
  1140. """
  1141. Note: This script behaves differently if the xpos and ypos are on the Cryptomatte node,
  1142. vs if they aren't. They need to be here for this to work.
  1143. There does not appear to be a difference in loading a file vs pasting
  1144. as of Nuke 12v3.
  1145. """
  1146. import nuke
  1147. prefix = "test_file_open_doesnt_update_nodes"
  1148. asset_file = self.read_asset.knob("file").getValue()
  1149. loadable = """
  1150. Read {
  1151. inputs 0
  1152. file_type exr
  1153. file %s
  1154. name %s_Read
  1155. selected true
  1156. xpos 498
  1157. ypos -17
  1158. }
  1159. add_layer {uCryptoAsset00 uCryptoAsset00.red uCryptoAsset00.green uCryptoAsset00.blue uCryptoAsset00.alpha}
  1160. add_layer {uCryptoAsset01 uCryptoAsset01.red uCryptoAsset01.green uCryptoAsset01.blue uCryptoAsset01.alpha}
  1161. add_layer {uCryptoAsset02 uCryptoAsset02.red uCryptoAsset02.green uCryptoAsset02.blue uCryptoAsset02.alpha}
  1162. Cryptomatte {
  1163. name %s_Cryptomatte
  1164. selected true
  1165. xpos 395
  1166. ypos 33
  1167. matteOutput alpha
  1168. matteOutput alpha
  1169. matteList "set, triangle"
  1170. cryptoLayer uCryptoMaterial
  1171. expression "((uCryptoMaterial00.red == 7.36562399642e+18 || uCryptoMaterial00.red == 1.54567320652e-21) ? uCryptoMaterial00.green : 0.0) + ((uCryptoMaterial00.blue == 7.36562399642e+18 || uCryptoMaterial00.blue == 1.54567320652e-21) ? uCryptoMaterial00.alpha : 0.0) + ((uCryptoMaterial01.red == 7.36562399642e+18 || uCryptoMaterial01.red == 1.54567320652e-21) ? uCryptoMaterial01.green : 0.0) + ((uCryptoMaterial01.blue == 7.36562399642e+18 || uCryptoMaterial01.blue == 1.54567320652e-21) ? uCryptoMaterial01.alpha : 0.0) + ((uCryptoMaterial02.red == 7.36562399642e+18 || uCryptoMaterial02.red == 1.54567320652e-21) ? uCryptoMaterial02.green : 0.0) + ((uCryptoMaterial02.blue == 7.36562399642e+18 || uCryptoMaterial02.blue == 1.54567320652e-21) ? uCryptoMaterial02.alpha : 0.0) + 0"
  1172. in00 uCryptoMaterial00
  1173. in01 uCryptoMaterial01
  1174. in02 uCryptoMaterial02
  1175. }
  1176. """ % (asset_file, prefix, prefix)
  1177. pasted_node_names = [(prefix + x)
  1178. for x in ("_Read", "_Cryptomatte")]
  1179. self.assertFalse(
  1180. any([nuke.toNode(x) for x in pasted_node_names]),
  1181. "Nodes already exist at the start of testing, were not pasted. ")
  1182. try:
  1183. for node in nuke.selectedNodes():
  1184. node['selected'].setValue(False)
  1185. nuke.scriptReadText(loadable)
  1186. loaded_nodes = [nuke.toNode(x) for x in pasted_node_names]
  1187. read, cryptomatte = loaded_nodes
  1188. msg = "Loaded file was changed from the state it was loaded. "
  1189. self.assertEqual(cryptomatte.knob("cryptoLayer").getValue(), "uCryptoMaterial")
  1190. self.assertEqual(cryptomatte.knob("in00").value(), "uCryptoMaterial00")
  1191. self.assertEqual(cryptomatte.knob("in01").value(), "uCryptoMaterial01")
  1192. self.assertEqual(cryptomatte.knob("in02").value(), "uCryptoMaterial02")
  1193. self.assertEqual(cryptomatte.knob("matteList").getValue(), "set, triangle")
  1194. self.assertIn("uCryptoMaterial00.red", cryptomatte.knob("expression").getValue())
  1195. except:
  1196. raise
  1197. finally:
  1198. if not self.skip_cleanup():
  1199. for node in loaded_nodes:
  1200. nuke.delete(node)
  1201. #############################################
  1202. # Ad hoc test running
  1203. #############################################
  1204. def run_unit_tests(test_filter="", failfast=False):
  1205. """ Utility function for manually running tests unit tests.
  1206. Returns unittest results if there are failures, otherwise None """
  1207. return run_tests(get_all_unit_tests(), test_filter=test_filter, failfast=failfast)
  1208. def run_nuke_tests(test_filter="", failfast=False):
  1209. """ Utility function for manually running tests inside Nuke
  1210. Returns unittest results if there are failures, otherwise None """
  1211. return run_tests(
  1212. get_all_unit_tests() + get_all_nuke_tests(), test_filter=test_filter, failfast=failfast)
  1213. def run_tests(test_cases, test_filter="", failfast=False):
  1214. """ Utility function for manually running tests.
  1215. Returns results if there are failures, otherwise None
  1216. Args:
  1217. test_filter will be matched fnmatch style (* wildcards) to either the name of the TestCase
  1218. class or test method.
  1219. failfast stop after a failure, and skip cleanup of the nodes that were created.
  1220. """
  1221. import nuke
  1222. import fnmatch
  1223. import platform
  1224. import cryptomatte_utilities as cu
  1225. def find_test_method(traceback):
  1226. """ Finds first "test*" function in traceback called. """
  1227. import re
  1228. match = re.search('", line \d+, in (test[a-z_0-9]+)', traceback)
  1229. return match.group(1) if match else ""
  1230. suite = unittest.TestSuite()
  1231. for case in test_cases:
  1232. suite.addTests(unittest.TestLoader().loadTestsFromTestCase(case))
  1233. if test_filter:
  1234. filtered_suite = unittest.TestSuite()
  1235. for test in suite:
  1236. if any(fnmatch.fnmatch(x, test_filter) for x in test.id().split(".")):
  1237. filtered_suite.addTest(test)
  1238. if not any(True for _ in filtered_suite):
  1239. raise RuntimeError("Filter %s selected no tests. " % test_filter)
  1240. suite = filtered_suite
  1241. set_skip_cleanup_on_failure(failfast)
  1242. pv = sys.version_info
  1243. if "%s.%s" % (pv[0], pv[1]) == "2.6":
  1244. runner = unittest.TextTestRunner(verbosity=2) # nuke 7 again..
  1245. else:
  1246. runner = unittest.TextTestRunner(verbosity=2, failfast=failfast)
  1247. result = runner.run(suite)
  1248. reset_skip_cleanup_on_failure()
  1249. print "---------"
  1250. print 'Cryptomatte %s, Nuke %s, %s' % (cu.__version__,
  1251. nuke.NUKE_VERSION_STRING,
  1252. platform.platform())
  1253. print "---------"
  1254. for test_instance, traceback in result.failures:
  1255. print "Failed: %s.%s" % (type(test_instance).__name__, find_test_method(traceback))
  1256. print
  1257. print traceback
  1258. print "---------"
  1259. for test_instance, traceback in result.errors:
  1260. print "Error: %s.%s" % (type(test_instance).__name__, find_test_method(traceback))
  1261. print
  1262. print traceback
  1263. print "---------"
  1264. if result.failures or result.errors:
  1265. print "TESTING FAILED: %s failed, %s errors. (%s test cases.)" % (len(result.failures),
  1266. len(result.errors),
  1267. suite.countTestCases())
  1268. return result
  1269. else:
  1270. print "Testing passed: %s failed, %s errors. (%s test cases.)" % (len(result.failures),
  1271. len(result.errors),
  1272. suite.countTestCases())
  1273. return None