From d7cacb348e62357fd6cea133c1f8ef5375ad0909 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 30 Sep 2020 18:52:15 +0200 Subject: [PATCH 1/2] fix clashing namespace js function calls --- avalon/harmony/TB_sceneOpened.js | 6 ++--- avalon/harmony/lib.py | 39 +++++++++++++++++--------------- avalon/harmony/pipeline.py | 24 ++++++++++++-------- avalon/toonboom/lib.py | 15 +++++++----- 4 files changed, 47 insertions(+), 37 deletions(-) diff --git a/avalon/harmony/TB_sceneOpened.js b/avalon/harmony/TB_sceneOpened.js index f4fba451c..43f36e60e 100644 --- a/avalon/harmony/TB_sceneOpened.js +++ b/avalon/harmony/TB_sceneOpened.js @@ -45,14 +45,14 @@ function Client() { try { - var func = eval.call( null, request["function"]); + var _func = eval.call( null, request["function"]); if (request.args == null) { - result = func(); + result = _func(); }else { - result = func(request.args); + result = _func(request.args); } } diff --git a/avalon/harmony/lib.py b/avalon/harmony/lib.py index c6b2857d1..f43f7bba5 100644 --- a/avalon/harmony/lib.py +++ b/avalon/harmony/lib.py @@ -12,6 +12,7 @@ import json import signal import time +from uuid import uuid4 from .server import Server from ..vendor.Qt import QtWidgets @@ -30,6 +31,8 @@ self.log = logging.getLogger(__name__) self.log.setLevel(logging.DEBUG) +signature = str(uuid4()) + class _ZipFile(zipfile.ZipFile): """Extended check for windows invalid characters.""" @@ -237,7 +240,7 @@ def show(module_name): def get_scene_data(): - func = """function func(args) + func = """function %s_func(args) { var metadata = scene.metadata("avalon"); if (metadata){ @@ -246,8 +249,8 @@ def get_scene_data(): return {}; } } - func - """ + %s_func + """ % (signature, signature) try: return self.send({"function": func})["result"] except json.decoder.JSONDecodeError: @@ -260,7 +263,7 @@ def get_scene_data(): def set_scene_data(data): # Write scene data. - func = """function func(args) + func = """function %s_func(args) { scene.setMetadata({ "name" : "avalon", @@ -270,8 +273,8 @@ def set_scene_data(data): "value" : JSON.stringify(args[0]) }); } - func - """ + %s_func + """ % (signature, signature) self.send({"function": func, "args": [data]}) @@ -378,19 +381,19 @@ def maintained_nodes_state(nodes): ) # Disable all nodes. - func = """function func(nodes) + func = """function %s_func(nodes) { for (var i = 0 ; i < nodes.length; i++) { node.setEnable(nodes[i], false); } } - func - """ + %s_func + """ % (signature, signature) self.send({"function": func, "args": [nodes]}) # Restore state after yield. - func = """function func(args) + func = """function %s_func(args) { var nodes = args[0]; var states = args[1]; @@ -399,8 +402,8 @@ def maintained_nodes_state(nodes): node.setEnable(nodes[i], states[i]); } } - func - """ + %s_func + """ % (signature, signature) try: yield @@ -418,7 +421,7 @@ def save_scene(): """ # Need to turn off the backgound watcher else the communication with # the server gets spammed with two requests at the same time. - func = """function func() + func = """function %s_func() { var app = QCoreApplication.instance(); app.avalon_on_file_changed = false; @@ -428,21 +431,21 @@ def save_scene(): scene.currentVersionName() + ".xstage" ); } - func - """ + %s_func + """ % (signature, signature) scene_path = self.send({"function": func})["result"] # Manually update the remote file. self.on_file_changed(scene_path, threaded=False) # Re-enable the background watcher. - func = """function func() + func = """function %s_func() { var app = QCoreApplication.instance(); app.avalon_on_file_changed = true; } - func - """ + %s_func + """ % (signature, signature) self.send({"function": func}) diff --git a/avalon/harmony/pipeline.py b/avalon/harmony/pipeline.py index 843f0e151..ac4fee00b 100644 --- a/avalon/harmony/pipeline.py +++ b/avalon/harmony/pipeline.py @@ -1,10 +1,14 @@ from .. import api, pipeline from . import lib from ..vendor import Qt +from uuid import uuid4 import pyblish.api +signature = str(uuid4()) + + def install(): """Install Harmony-specific functionality of avalon-core. @@ -49,18 +53,18 @@ class Creator(api.Creator): node_type = "COMPOSITE" def setup_node(self, node): - func = """function func(args) + func = """function %s_func(args) { node.setTextAttr(args[0], "COMPOSITE_MODE", 1, "Pass Through"); } - func - """ + %s_func + """ % (signature, signature) lib.send( {"function": func, "args": [node]} ) def process(self): - func = """function func(args) + func = """function %s_func(args) { var nodes = node.getNodes([args[0]]); var node_names = []; @@ -70,8 +74,8 @@ def process(self): } return node_names } - func - """ + %s_func + """ % (signature, signature) existing_node_names = lib.send( {"function": func, "args": [self.node_type]} @@ -87,7 +91,7 @@ def process(self): message_box.exec_() return False - func = """function func(args) + func = """function %s_func(args) { var result_node = node.add("Top", args[0], args[1], 0, 0, 0); @@ -103,8 +107,8 @@ def process(self): } return result_node } - func - """ + %s_func + """ % (signature, signature) with lib.maintained_selection() as selection: node = None @@ -161,7 +165,7 @@ def containerise(name, "loader": str(loader), "representation": str(context["representation"]["_id"]), "nodes": nodes - } + } lib.imprint(node, data) diff --git a/avalon/toonboom/lib.py b/avalon/toonboom/lib.py index 72adea62c..b1b9103b4 100644 --- a/avalon/toonboom/lib.py +++ b/avalon/toonboom/lib.py @@ -10,6 +10,7 @@ import importlib import logging import filecmp +from uuid import uuid4 from .server import Server from ..tools import workfiles @@ -29,6 +30,8 @@ self.log = logging.getLogger(__name__) self.log.setLevel(logging.DEBUG) +signature = str(uuid4()) + def execute_in_main_thread(func_to_call_from_main_thread): self.callback_queue.put(func_to_call_from_main_thread) @@ -294,7 +297,7 @@ def save_scene(): """ # Need to turn off the backgound watcher else the communication with # the server gets spammed with two requests at the same time. - func = """function func() + func = """function %s_func() { var app = QCoreApplication.instance(); app.avalon_on_file_changed = false; @@ -303,19 +306,19 @@ def save_scene(): scene.currentProjectPath() + "/" + scene.currentVersionName() ); } - func - """ + %s_func + """ % (signature, signature) scene_path = self.send({"function": func})["result"] + "." + self.extension # Manually update the remote file. self.on_file_changed(scene_path) # Re-enable the background watcher. - func = """function func() + func = """function %s_func() { var app = QCoreApplication.instance(); app.avalon_on_file_changed = true; } - func - """ + %s_func + """ % (signature, signature) self.send({"function": func}) From 00fb30bd32b00e7053ddd180f3a431dc6c986e69 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 1 Oct 2020 11:27:42 +0200 Subject: [PATCH 2/2] updated docs --- avalon/harmony/README.md | 273 +++++++++++++++++++++------------------ 1 file changed, 146 insertions(+), 127 deletions(-) diff --git a/avalon/harmony/README.md b/avalon/harmony/README.md index 19e887032..9aa986f70 100644 --- a/avalon/harmony/README.md +++ b/avalon/harmony/README.md @@ -51,12 +51,15 @@ You can show the Workfiles app when Harmony launches by setting environment vari To send from Python to Harmony you can use the exposed method: ```python from avalon import harmony -func = """function hello(person) +from uuid import uuid4 + + +func = """function %s_hello(person) { return ("Hello " + person + "!"); } -hello -""" +%s_hello +""" % (uuid4(), uuid4()) print(harmony.send({"function": func, "args": ["Python"]})["result"]) ``` NOTE: Its important to declare the function at the end of the function string. You can have multiple functions within your function string, but the function declared at the end is what gets executed. @@ -64,17 +67,28 @@ NOTE: Its important to declare the function at the end of the function string. Y To send a function with multiple arguments its best to declare the arguments within the function: ```python from avalon import harmony -func = """function hello(args) +from uuid import uuid4 + + +func = """function %s_hello(args) { var greeting = args[0]; var person = args[1]; return (greeting + " " + person + "!"); } -hello -""" +%s_hello +""" % (uuid4(), uuid4()) print(harmony.send({"function": func, "args": ["Hello", "Python"]})["result"]) ``` +### Caution + +When naming your functions be aware that they are executed in global scope. They can potentially clash with Harmony own function and object names. +For example `func` is already existing Harmony object. When you call your function `func` it will overwrite in global scope the one from Harmony, causing +erratic behavior of Harmony. Avalon is prefixing those function names with [UUID4](https://docs.python.org/3/library/uuid.html) making chance of such clash minimal. +See above examples how that works. This will result in function named `38dfcef0-a6d7-4064-8069-51fe99ab276e_hello()`. +You can find list of Harmony object and function in Harmony documentation. + ### Scene Save Instead of sending a request to Harmony with `scene.saveAll` please use: ```python @@ -96,6 +110,7 @@ These plugins were made with the [polly config](https://github.com/mindbender-st #### Creator Plugin ```python from avalon import harmony +from uuid import uuid4 class CreateComposite(harmony.Creator): @@ -126,12 +141,12 @@ class CreateRender(harmony.Creator): super(CreateRender, self).__init__(*args, **kwargs) def setup_node(self, node): - func = """function func(args) + func = """function %s_func(args) { node.setTextAttr(args[0], "DRAWING_TYPE", 1, "PNG4"); } - func - """ + %s_func + """ % (uuid4(), uuid4()) harmony.send( {"function": func, "args": [node]} ) @@ -212,7 +227,7 @@ class ExtractImage(pyblish.api.InstancePlugin): # Store display source node for later. display_node = "Top/Display" - func = """function func(display_node) + func = """function %s_func(display_node) { var source_node = null; if (node.isLinked(display_node, 0)) @@ -222,8 +237,8 @@ class ExtractImage(pyblish.api.InstancePlugin): } return source_node } - func - """ + %s_func + """ % (uuid4(), uuid4()) display_source_node = harmony.send( {"function": func, "args": [display_node]} )["result"] @@ -243,7 +258,7 @@ class ExtractImage(pyblish.api.InstancePlugin): var path = "{path}/{filename}" + frame + ".png"; celImage.imageFileAs(path, "", "PNG4"); }} - function func(composite_node) + function %s_func(composite_node) {{ node.link(composite_node, 0, "{display_node}", 0); render.frameReady.connect(frameReady); @@ -251,9 +266,9 @@ class ExtractImage(pyblish.api.InstancePlugin): render.renderSceneAll(); render.frameReady.disconnect(frameReady); }} - func - """ - restore_func = """function func(args) + %s_func + """ % (uuid4(), uuid4()) + restore_func = """function %s_func(args) { var display_node = args[0]; var display_source_node = args[1]; @@ -263,8 +278,8 @@ class ExtractImage(pyblish.api.InstancePlugin): } node.link(display_source_node, 0, display_node, 0); } - func - """ + %s_func + """ % (uuid4(), uuid4()) with harmony.maintained_selection(): self.log.info("Extracting %s" % str(list(instance))) @@ -326,113 +341,117 @@ copy_files = """function copyFile(srcFilename, dstFilename) } """ -import_files = """var PNGTransparencyMode = 0; //Premultiplied wih Black -var TGATransparencyMode = 0; //Premultiplied wih Black -var SGITransparencyMode = 0; //Premultiplied wih Black -var LayeredPSDTransparencyMode = 1; //Straight -var FlatPSDTransparencyMode = 2; //Premultiplied wih White - -function getUniqueColumnName( column_prefix ) +import_files = """function %s_import_files() { - var suffix = 0; - // finds if unique name for a column - var column_name = column_prefix; - while(suffix < 2000) - { - if(!column.type(column_name)) - break; - - suffix = suffix + 1; - column_name = column_prefix + "_" + suffix; - } - return column_name; + var PNGTransparencyMode = 0; // Premultiplied wih Black + var TGATransparencyMode = 0; // Premultiplied wih Black + var SGITransparencyMode = 0; // Premultiplied wih Black + var LayeredPSDTransparencyMode = 1; // Straight + var FlatPSDTransparencyMode = 2; // Premultiplied wih White + + function getUniqueColumnName( column_prefix ) + { + var suffix = 0; + // finds if unique name for a column + var column_name = column_prefix; + while(suffix < 2000) + { + if(!column.type(column_name)) + break; + + suffix = suffix + 1; + column_name = column_prefix + "_" + suffix; + } + return column_name; + } + + function import_files(args) + { + var root = args[0]; + var files = args[1]; + var name = args[2]; + var start_frame = args[3]; + + var vectorFormat = null; + var extension = null; + var filename = files[0]; + + var pos = filename.lastIndexOf("."); + if( pos < 0 ) + return null; + + extension = filename.substr(pos+1).toLowerCase(); + + if(extension == "jpeg") + extension = "jpg"; + if(extension == "tvg") + { + vectorFormat = "TVG" + extension ="SCAN"; // element.add() will use this. + } + + var elemId = element.add( + name, + "BW", + scene.numberOfUnitsZ(), + extension.toUpperCase(), + vectorFormat + ); + if (elemId == -1) + { + // hum, unknown file type most likely -- let's skip it. + return null; // no read to add. + } + + var uniqueColumnName = getUniqueColumnName(name); + column.add(uniqueColumnName , "DRAWING"); + column.setElementIdOfDrawing(uniqueColumnName, elemId); + + var read = node.add(root, name, "READ", 0, 0, 0); + var transparencyAttr = node.getAttr( + read, frame.current(), "READ_TRANSPARENCY" + ); + var opacityAttr = node.getAttr(read, frame.current(), "OPACITY"); + transparencyAttr.setValue(true); + opacityAttr.setValue(true); + + var alignmentAttr = node.getAttr(read, frame.current(), "ALIGNMENT_RULE"); + alignmentAttr.setValue("ASIS"); + + var transparencyModeAttr = node.getAttr( + read, frame.current(), "applyMatteToColor" + ); + if (extension == "png") + transparencyModeAttr.setValue(PNGTransparencyMode); + if (extension == "tga") + transparencyModeAttr.setValue(TGATransparencyMode); + if (extension == "sgi") + transparencyModeAttr.setValue(SGITransparencyMode); + if (extension == "psd") + transparencyModeAttr.setValue(FlatPSDTransparencyMode); + + node.linkAttr(read, "DRAWING.ELEMENT", uniqueColumnName); + + // Create a drawing for each file. + for( var i =0; i <= files.length - 1; ++i) + { + timing = start_frame + i + // Create a drawing drawing, 'true' indicate that the file exists. + Drawing.create(elemId, timing, true); + // Get the actual path, in tmp folder. + var drawingFilePath = Drawing.filename(elemId, timing.toString()); + copyFile( files[i], drawingFilePath ); + + column.setEntry(uniqueColumnName, 1, timing, timing.toString()); + } + return read; + } + import_files(); } +%s_import_files +""" % (uuid4(), uuid4()) -function import_files(args) -{ - var root = args[0]; - var files = args[1]; - var name = args[2]; - var start_frame = args[3]; - - var vectorFormat = null; - var extension = null; - var filename = files[0]; - - var pos = filename.lastIndexOf("."); - if( pos < 0 ) - return null; - - extension = filename.substr(pos+1).toLowerCase(); - - if(extension == "jpeg") - extension = "jpg"; - if(extension == "tvg") - { - vectorFormat = "TVG" - extension ="SCAN"; // element.add() will use this. - } - - var elemId = element.add( - name, - "BW", - scene.numberOfUnitsZ(), - extension.toUpperCase(), - vectorFormat - ); - if (elemId == -1) - { - // hum, unknown file type most likely -- let's skip it. - return null; // no read to add. - } - - var uniqueColumnName = getUniqueColumnName(name); - column.add(uniqueColumnName , "DRAWING"); - column.setElementIdOfDrawing(uniqueColumnName, elemId); - - var read = node.add(root, name, "READ", 0, 0, 0); - var transparencyAttr = node.getAttr( - read, frame.current(), "READ_TRANSPARENCY" - ); - var opacityAttr = node.getAttr(read, frame.current(), "OPACITY"); - transparencyAttr.setValue(true); - opacityAttr.setValue(true); - - var alignmentAttr = node.getAttr(read, frame.current(), "ALIGNMENT_RULE"); - alignmentAttr.setValue("ASIS"); - - var transparencyModeAttr = node.getAttr( - read, frame.current(), "applyMatteToColor" - ); - if (extension == "png") - transparencyModeAttr.setValue(PNGTransparencyMode); - if (extension == "tga") - transparencyModeAttr.setValue(TGATransparencyMode); - if (extension == "sgi") - transparencyModeAttr.setValue(SGITransparencyMode); - if (extension == "psd") - transparencyModeAttr.setValue(FlatPSDTransparencyMode); - - node.linkAttr(read, "DRAWING.ELEMENT", uniqueColumnName); - - // Create a drawing for each file. - for( var i =0; i <= files.length - 1; ++i) - { - timing = start_frame + i - // Create a drawing drawing, 'true' indicate that the file exists. - Drawing.create(elemId, timing, true); - // Get the actual path, in tmp folder. - var drawingFilePath = Drawing.filename(elemId, timing.toString()); - copyFile( files[i], drawingFilePath ); - - column.setEntry(uniqueColumnName, 1, timing, timing.toString()); - } - return read; -} -import_files -""" - -replace_files = """function replace_files(args) +replace_files = """function %s_replace_files(args) { var files = args[0]; var _node = args[1]; @@ -462,8 +481,8 @@ replace_files = """function replace_files(args) column.setEntry(_column, 1, timing, timing.toString()); } } -replace_files -""" +%s_replace_files +""" % (uuid4(), uuid4()) class ImageSequenceLoader(api.Loader): @@ -524,12 +543,12 @@ class ImageSequenceLoader(api.Loader): def remove(self, container): node = container.pop("node") - func = """function deleteNode(_node) + func = """function %s_deleteNode(_node) { node.deleteNode(_node, true, true); } - deleteNode - """ + %_deleteNode + """ % (uuid4(), uuid4()) harmony.send( {"function": func, "args": [node]} )