mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
OP-2414 - Hound
This commit is contained in:
parent
0611e60813
commit
f29b8e7158
8 changed files with 9 additions and 831 deletions
|
|
@ -8,7 +8,7 @@ This script implements client communication with Avalon server to bridge
|
|||
gap between Python and QtScript.
|
||||
|
||||
*/
|
||||
|
||||
/* jshint proto: true */
|
||||
var LD_OPENHARMONY_PATH = System.getenv('LIB_OPENHARMONY_PATH');
|
||||
LD_OPENHARMONY_PATH = LD_OPENHARMONY_PATH + '/openHarmony.js';
|
||||
LD_OPENHARMONY_PATH = LD_OPENHARMONY_PATH.replace(/\\/g, "/");
|
||||
|
|
@ -356,7 +356,7 @@ function start() {
|
|||
app.avalonMenu = null;
|
||||
|
||||
for (var i = 0 ; i < actions.length; i++) {
|
||||
label = System.getenv('AVALON_LABEL')
|
||||
label = System.getenv('AVALON_LABEL');
|
||||
if (actions[i].text == label) {
|
||||
app.avalonMenu = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -489,7 +489,7 @@ def send(request):
|
|||
|
||||
def select_nodes(nodes):
|
||||
""" Selects nodes in Node View """
|
||||
selected_nodes = self.send(
|
||||
_ = self.send(
|
||||
{
|
||||
"function": "AvalonHarmony.selectNodes",
|
||||
"args": nodes
|
||||
|
|
|
|||
|
|
@ -149,6 +149,7 @@ def application_launch():
|
|||
# send scripts to Harmony
|
||||
harmony.send({"script": pype_harmony_js})
|
||||
harmony.send({"script": script})
|
||||
inject_avalon_js()
|
||||
|
||||
|
||||
def export_template(backdrops, nodes, filepath):
|
||||
|
|
@ -218,16 +219,6 @@ def inject_avalon_js():
|
|||
harmony.send({"script": script})
|
||||
|
||||
|
||||
def install():
|
||||
"""Install Harmony-specific functionality of avalon-core.
|
||||
|
||||
This function is called automatically on calling `api.install(harmony)`.
|
||||
"""
|
||||
print("Installing Avalon Harmony...")
|
||||
pyblish.api.register_host("harmony")
|
||||
avalon.api.on("application.launched", inject_avalon_js)
|
||||
|
||||
|
||||
def ls():
|
||||
"""Yields containers from Harmony scene.
|
||||
|
||||
|
|
@ -325,7 +316,7 @@ def containerise(name,
|
|||
context,
|
||||
loader=None,
|
||||
suffix=None,
|
||||
nodes=[]):
|
||||
nodes=None):
|
||||
"""Imprint node with metadata.
|
||||
|
||||
Containerisation enables a tracking of version, author and origin
|
||||
|
|
@ -342,6 +333,9 @@ def containerise(name,
|
|||
Returns:
|
||||
container (str): Path of container assembly.
|
||||
"""
|
||||
if not nodes:
|
||||
nodes = []
|
||||
|
||||
data = {
|
||||
"schema": "openpype:container-2.0",
|
||||
"id": AVALON_CONTAINER_ID,
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
from .lib import (
|
||||
launch,
|
||||
on_file_changed,
|
||||
open_file,
|
||||
current_file,
|
||||
has_unsaved_changes,
|
||||
file_extensions,
|
||||
work_root,
|
||||
save_file,
|
||||
send,
|
||||
show,
|
||||
save_scene,
|
||||
setup_startup_scripts,
|
||||
check_libs
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Library API.
|
||||
"launch",
|
||||
"on_file_changed",
|
||||
"open_file",
|
||||
"current_file",
|
||||
"has_unsaved_changes",
|
||||
"file_extensions",
|
||||
"work_root",
|
||||
"save_file",
|
||||
"send",
|
||||
"show",
|
||||
"save_scene",
|
||||
"setup_startup_scripts",
|
||||
"check_libs"
|
||||
]
|
||||
|
|
@ -1,262 +0,0 @@
|
|||
function Client()
|
||||
{
|
||||
var self = this;
|
||||
self.socket = new QTcpSocket(this);
|
||||
self.received = "";
|
||||
|
||||
self.log_debug = function(data)
|
||||
{
|
||||
message = typeof(data.message) != "undefined" ? data.message : data;
|
||||
MessageLog.trace("(DEBUG): " + message.toString());
|
||||
};
|
||||
|
||||
|
||||
self.log_info = function(data)
|
||||
{
|
||||
message = typeof(data.message) != "undefined" ? data.message : data;
|
||||
MessageLog.trace("(INFO): " + message.toString());
|
||||
};
|
||||
|
||||
|
||||
self.log_warning = function(data)
|
||||
{
|
||||
message = typeof(data.message) != "undefined" ? data.message : data;
|
||||
MessageLog.trace("(WARNING): " + message.toString());
|
||||
};
|
||||
|
||||
|
||||
self.log_error = function(data)
|
||||
{
|
||||
message = typeof(data.message) != "undefined" ? data.message : data;
|
||||
MessageLog.trace("(ERROR): " + message.toString());
|
||||
};
|
||||
|
||||
self.process_request = function(request)
|
||||
{
|
||||
self.log_debug("Processing: " + JSON.stringify(request));
|
||||
var result = null;
|
||||
|
||||
if (request["function"] != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var func = eval(request["function"]);
|
||||
|
||||
if (request.args == null)
|
||||
{
|
||||
result = func();
|
||||
}else
|
||||
{
|
||||
result = func(request.args);
|
||||
}
|
||||
}
|
||||
|
||||
catch (error)
|
||||
{
|
||||
result = "Error processing request.\nRequest:\n" + JSON.stringify(request) + "\nError:\n" + error;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
self.on_ready_read = function()
|
||||
{
|
||||
self.log_debug("Receiving data...");
|
||||
data = self.socket.readAll();
|
||||
|
||||
if (data.size() != 0)
|
||||
{
|
||||
for ( var i = 0; i < data.size(); ++i)
|
||||
{
|
||||
self.received = self.received.concat(String.fromCharCode(data.at(i)));
|
||||
}
|
||||
}
|
||||
|
||||
self.log_debug("Received: " + self.received);
|
||||
|
||||
request = JSON.parse(self.received);
|
||||
self.log_debug("Request: " + JSON.stringify(request));
|
||||
|
||||
request.result = self.process_request(request);
|
||||
|
||||
if (!request.reply)
|
||||
{
|
||||
request.reply = true;
|
||||
self._send(JSON.stringify(request));
|
||||
}
|
||||
|
||||
self.received = "";
|
||||
};
|
||||
|
||||
self.on_connected = function()
|
||||
{
|
||||
self.log_debug("Connected to server.");
|
||||
self.socket.readyRead.connect(self.on_ready_read);
|
||||
};
|
||||
|
||||
self._send = function(message)
|
||||
{
|
||||
self.log_debug("Sending: " + message);
|
||||
|
||||
var data = new QByteArray();
|
||||
outstr = new QDataStream(data, QIODevice.WriteOnly);
|
||||
outstr.writeInt(0);
|
||||
data.append("UTF-8");
|
||||
outstr.device().seek(0);
|
||||
outstr.writeInt(data.size() - 4);
|
||||
var codec = QTextCodec.codecForUtfText(data);
|
||||
self.socket.write(codec.fromUnicode(message));
|
||||
};
|
||||
|
||||
self.send = function(request, wait)
|
||||
{
|
||||
self._send(JSON.stringify(request));
|
||||
|
||||
while (wait)
|
||||
{
|
||||
try
|
||||
{
|
||||
JSON.parse(self.received);
|
||||
break;
|
||||
}
|
||||
catch(err)
|
||||
{
|
||||
self.socket.waitForReadyRead(5000);
|
||||
}
|
||||
}
|
||||
|
||||
self.received = "";
|
||||
};
|
||||
|
||||
self.on_disconnected = function()
|
||||
{
|
||||
self.socket.close();
|
||||
};
|
||||
|
||||
self.disconnect = function()
|
||||
{
|
||||
self.socket.close();
|
||||
};
|
||||
|
||||
self.socket.connected.connect(self.on_connected);
|
||||
self.socket.disconnected.connect(self.on_disconnected);
|
||||
}
|
||||
|
||||
function start()
|
||||
{
|
||||
var self = this;
|
||||
var host = "127.0.0.1";
|
||||
var port = parseInt(System.getenv("AVALON_TOONBOOM_PORT"));
|
||||
|
||||
// Attach the client to the QApplication to preserve.
|
||||
var app = QCoreApplication.instance();
|
||||
|
||||
if (app.avalon_client == null)
|
||||
{
|
||||
app.avalon_client = new Client();
|
||||
app.avalon_client.socket.connectToHost(host, port);
|
||||
}
|
||||
|
||||
var menu_bar = QApplication.activeWindow().menuBar();
|
||||
var menu = menu_bar.addMenu("Avalon");
|
||||
|
||||
self.on_creator = function()
|
||||
{
|
||||
app.avalon_client.send(
|
||||
{
|
||||
"module": "avalon.toonboom",
|
||||
"method": "show",
|
||||
"args": ["creator"]
|
||||
},
|
||||
false
|
||||
);
|
||||
};
|
||||
var action = menu.addAction("Create...");
|
||||
action.triggered.connect(self.on_creator);
|
||||
|
||||
self.on_workfiles = function()
|
||||
{
|
||||
app.avalon_client.send(
|
||||
{
|
||||
"module": "avalon.toonboom",
|
||||
"method": "show",
|
||||
"args": ["workfiles"]
|
||||
},
|
||||
false
|
||||
);
|
||||
};
|
||||
action = menu.addAction("Workfiles");
|
||||
action.triggered.connect(self.on_workfiles);
|
||||
|
||||
self.on_load = function()
|
||||
{
|
||||
app.avalon_client.send(
|
||||
{
|
||||
"module": "avalon.toonboom",
|
||||
"method": "show",
|
||||
"args": ["loader"]
|
||||
},
|
||||
false
|
||||
);
|
||||
};
|
||||
action = menu.addAction("Load...");
|
||||
action.triggered.connect(self.on_load);
|
||||
|
||||
self.on_publish = function()
|
||||
{
|
||||
app.avalon_client.send(
|
||||
{
|
||||
"module": "avalon.toonboom",
|
||||
"method": "show",
|
||||
"args": ["publish"]
|
||||
},
|
||||
false
|
||||
);
|
||||
};
|
||||
action = menu.addAction("Publish...");
|
||||
action.triggered.connect(self.on_publish);
|
||||
|
||||
self.on_manage = function()
|
||||
{
|
||||
app.avalon_client.send(
|
||||
{
|
||||
"module": "avalon.toonboom",
|
||||
"method": "show",
|
||||
"args": ["sceneinventory"]
|
||||
},
|
||||
false
|
||||
);
|
||||
};
|
||||
action = menu.addAction("Manage...");
|
||||
action.triggered.connect(self.on_manage);
|
||||
|
||||
// Watch scene file for changes.
|
||||
app.on_file_changed = function(path)
|
||||
{
|
||||
var app = QCoreApplication.instance();
|
||||
if (app.avalon_on_file_changed){
|
||||
app.avalon_client.send(
|
||||
{
|
||||
"module": "avalon.toonboom",
|
||||
"method": "on_file_changed",
|
||||
"args": [path]
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
app.watcher.addPath(path);
|
||||
};
|
||||
|
||||
app.watcher = new QFileSystemWatcher();
|
||||
extension = ".xstage";
|
||||
var product_name = about.productName();
|
||||
if (product_name.toLowerCase().indexOf("storyboard") !== -1){
|
||||
extension = ".sboard";
|
||||
}
|
||||
scene_path = scene.currentProjectPath() + "/" + scene.currentVersionName() + extension;
|
||||
app.watcher.addPath(scene_path);
|
||||
app.watcher.fileChanged.connect(app.on_file_changed);
|
||||
app.avalon_on_file_changed = true;
|
||||
}
|
||||
|
|
@ -1,363 +0,0 @@
|
|||
import os
|
||||
import random
|
||||
import sys
|
||||
import queue
|
||||
import shutil
|
||||
import zipfile
|
||||
import signal
|
||||
import threading
|
||||
import subprocess
|
||||
import importlib
|
||||
import logging
|
||||
import filecmp
|
||||
from uuid import uuid4
|
||||
|
||||
from .server import Server
|
||||
from openpype.tools.utils import host_tools
|
||||
from Qt import QtWidgets
|
||||
|
||||
self = sys.modules[__name__]
|
||||
self.server = None
|
||||
self.pid = None
|
||||
self.application_path = None
|
||||
self.callback_queue = None
|
||||
self.workfile_path = None
|
||||
self.port = None
|
||||
self.extension = None
|
||||
self.application_name = None
|
||||
|
||||
# Setup logging.
|
||||
self.log = logging.getLogger(__name__)
|
||||
self.log.setLevel(logging.DEBUG)
|
||||
|
||||
signature = str(uuid4()).replace("-", "_")
|
||||
|
||||
|
||||
def execute_in_main_thread(func_to_call_from_main_thread):
|
||||
self.callback_queue.put(func_to_call_from_main_thread)
|
||||
|
||||
|
||||
def main_thread_listen():
|
||||
callback = self.callback_queue.get()
|
||||
callback()
|
||||
|
||||
|
||||
def setup_startup_scripts():
|
||||
"""Manages installation of avalon's TB_sceneOpened.js for Harmony launch.
|
||||
|
||||
If a studio already has defined "TOONBOOM_GLOBAL_SCRIPT_LOCATION", copies
|
||||
the TB_sceneOpened.js to that location if the file is different.
|
||||
Otherwise, will set the env var to point to the avalon/harmony folder.
|
||||
|
||||
Admins should be aware that this will overwrite TB_sceneOpened in the
|
||||
"TOONBOOM_GLOBAL_SCRIPT_LOCATION", and that if they want to have additional
|
||||
logic, they will need to one of the following:
|
||||
* Create a Harmony package to manage startup logic
|
||||
* Use TB_sceneOpenedUI.js instead to manage startup logic
|
||||
* Add their startup logic to avalon/harmony/TB_sceneOpened.js
|
||||
"""
|
||||
avalon_dcc_dir = os.path.dirname(os.path.dirname(__file__))
|
||||
startup_js = "TB_sceneOpened.js"
|
||||
|
||||
if os.getenv("TOONBOOM_GLOBAL_SCRIPT_LOCATION"):
|
||||
|
||||
avalon_harmony_startup = os.path.join(avalon_dcc_dir, startup_js)
|
||||
|
||||
env_harmony_startup = os.path.join(
|
||||
os.getenv("TOONBOOM_GLOBAL_SCRIPT_LOCATION"), startup_js)
|
||||
|
||||
if not filecmp.cmp(avalon_harmony_startup, env_harmony_startup):
|
||||
try:
|
||||
shutil.copy(avalon_harmony_startup, env_harmony_startup)
|
||||
except Exception as e:
|
||||
self.log.error(e)
|
||||
self.log.warning(
|
||||
"Failed to copy {0} to {1}! "
|
||||
"Defaulting to Avalon TOONBOOM_GLOBAL_SCRIPT_LOCATION."
|
||||
.format(avalon_harmony_startup, env_harmony_startup))
|
||||
|
||||
os.environ["TOONBOOM_GLOBAL_SCRIPT_LOCATION"] = avalon_dcc_dir
|
||||
else:
|
||||
os.environ["TOONBOOM_GLOBAL_SCRIPT_LOCATION"] = avalon_dcc_dir
|
||||
|
||||
|
||||
def check_libs():
|
||||
"""Check if `OpenHarmony`_ is available.
|
||||
|
||||
Avalon expects either path in `LIB_OPENHARMONY_PATH` or `openHarmony.js`
|
||||
present in `TOONBOOM_GLOBAL_SCRIPT_LOCATION`.
|
||||
|
||||
Throws:
|
||||
RuntimeError: If openHarmony is not found.
|
||||
|
||||
.. _OpenHarmony:
|
||||
https://github.com/cfourney/OpenHarmony
|
||||
|
||||
"""
|
||||
if not os.getenv("LIB_OPENHARMONY_PATH"):
|
||||
|
||||
if os.getenv("TOONBOOM_GLOBAL_SCRIPT_LOCATION"):
|
||||
if os.path.exists(
|
||||
os.path.join(
|
||||
os.getenv("TOONBOOM_GLOBAL_SCRIPT_LOCATION"),
|
||||
"openHarmony.js")):
|
||||
|
||||
os.environ["LIB_OPENHARMONY_PATH"] = \
|
||||
os.getenv("TOONBOOM_GLOBAL_SCRIPT_LOCATION")
|
||||
return
|
||||
|
||||
else:
|
||||
self.log.error(("Cannot find OpenHarmony library. "
|
||||
"Please set path to it in LIB_OPENHARMONY_PATH "
|
||||
"environment variable."))
|
||||
raise RuntimeError("Missing OpenHarmony library.")
|
||||
|
||||
|
||||
def launch(application_path, zip_file):
|
||||
"""Setup for Toon Boom application launch.
|
||||
|
||||
Launches Toon Boom application and the server, then starts listening on the
|
||||
main thread for callbacks from the server. This is to have Qt applications
|
||||
run in the main thread.
|
||||
|
||||
Args:
|
||||
application_path (str): Path to application executable.
|
||||
zip_file (str): Path to application scene file zipped.
|
||||
application_name (str): Application identifier.
|
||||
"""
|
||||
self.port = random.randrange(5000, 6000)
|
||||
os.environ["AVALON_TOONBOOM_PORT"] = str(self.port)
|
||||
self.application_path = application_path
|
||||
|
||||
self.application_name = "harmony"
|
||||
if "storyboard" in application_path.lower():
|
||||
self.application_name = "storyboardpro"
|
||||
|
||||
extension_mapping = {"harmony": "xstage", "storyboardpro": "sboard"}
|
||||
self.extension = extension_mapping[self.application_name]
|
||||
|
||||
# Launch Harmony.
|
||||
setup_startup_scripts()
|
||||
|
||||
if os.environ.get("AVALON_TOONBOOM_WORKFILES_ON_LAUNCH", False):
|
||||
host_tools.show_workfiles(save=False)
|
||||
|
||||
# No launch through Workfiles happened.
|
||||
if not self.workfile_path:
|
||||
launch_zip_file(zip_file)
|
||||
|
||||
self.callback_queue = queue.Queue()
|
||||
while True:
|
||||
main_thread_listen()
|
||||
|
||||
|
||||
def get_local_path(filepath):
|
||||
"""From the provided path get the equivalent local path."""
|
||||
basename = os.path.splitext(os.path.basename(filepath))[0]
|
||||
harmony_path = os.path.join(
|
||||
os.path.expanduser("~"), ".avalon", self.application_name
|
||||
)
|
||||
return os.path.join(harmony_path, basename)
|
||||
|
||||
|
||||
def launch_zip_file(filepath):
|
||||
"""Launch a Harmony application instance with the provided zip file."""
|
||||
self.log.debug("Localizing {}".format(filepath))
|
||||
|
||||
local_path = get_local_path(filepath)
|
||||
scene_path = os.path.join(
|
||||
local_path, os.path.basename(local_path) + "." + self.extension
|
||||
)
|
||||
extract_zip_file = False
|
||||
if os.path.exists(scene_path):
|
||||
# Check remote scene is newer than local.
|
||||
if os.path.getmtime(scene_path) < os.path.getmtime(filepath):
|
||||
shutil.rmtree(local_path)
|
||||
extract_zip_file = True
|
||||
else:
|
||||
extract_zip_file = True
|
||||
|
||||
if extract_zip_file:
|
||||
with zipfile.ZipFile(filepath, "r") as zip_ref:
|
||||
zip_ref.extractall(local_path)
|
||||
|
||||
# Close existing scene.
|
||||
if self.pid:
|
||||
os.kill(self.pid, signal.SIGTERM)
|
||||
|
||||
# Stop server.
|
||||
if self.server:
|
||||
self.server.stop()
|
||||
|
||||
# Launch Avalon server.
|
||||
self.server = Server(self.port)
|
||||
thread = threading.Thread(target=self.server.start)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
# Save workfile path for later.
|
||||
self.workfile_path = filepath
|
||||
|
||||
self.log.debug("Launching {}".format(scene_path))
|
||||
process = subprocess.Popen([self.application_path, scene_path])
|
||||
self.pid = process.pid
|
||||
|
||||
|
||||
def file_extensions():
|
||||
return [".zip"]
|
||||
|
||||
|
||||
def has_unsaved_changes():
|
||||
if self.server:
|
||||
return self.server.send({"function": "scene.isDirty"})["result"]
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def save_file(filepath):
|
||||
temp_path = self.get_local_path(filepath)
|
||||
|
||||
if os.path.exists(temp_path):
|
||||
shutil.rmtree(temp_path)
|
||||
|
||||
self.server.send(
|
||||
{"function": "scene.saveAs", "args": [temp_path]}
|
||||
)["result"]
|
||||
|
||||
zip_and_move(temp_path, filepath)
|
||||
|
||||
self.workfile_path = filepath
|
||||
|
||||
func = """function add_path(path)
|
||||
{
|
||||
var app = QCoreApplication.instance();
|
||||
app.watcher.addPath(path);
|
||||
}
|
||||
add_path
|
||||
"""
|
||||
|
||||
scene_path = os.path.join(
|
||||
temp_path, os.path.basename(temp_path) + "." + self.extension
|
||||
)
|
||||
self.server.send(
|
||||
{"function": func, "args": [scene_path]}
|
||||
)
|
||||
|
||||
|
||||
def open_file(filepath):
|
||||
launch_zip_file(filepath)
|
||||
|
||||
|
||||
def current_file():
|
||||
"""Returning None to make Workfiles app look at first file extension."""
|
||||
return None
|
||||
|
||||
|
||||
def work_root(session):
|
||||
return os.path.normpath(session["AVALON_WORKDIR"]).replace("\\", "/")
|
||||
|
||||
|
||||
def zip_and_move(source, destination):
|
||||
"""Zip a directory and move to `destination`
|
||||
|
||||
Args:
|
||||
- source (str): Directory to zip and move to destination.
|
||||
- destination (str): Destination file path to zip file.
|
||||
"""
|
||||
os.chdir(os.path.dirname(source))
|
||||
shutil.make_archive(os.path.basename(source), "zip", source)
|
||||
shutil.move(os.path.basename(source) + ".zip", destination)
|
||||
self.log.debug("Saved \"{}\" to \"{}\"".format(source, destination))
|
||||
|
||||
|
||||
def on_file_changed(path):
|
||||
"""Threaded zipping and move of the project directory.
|
||||
|
||||
This method is called when the scene file is changed.
|
||||
"""
|
||||
|
||||
self.log.debug("File changed: " + path)
|
||||
|
||||
if self.workfile_path is None:
|
||||
return
|
||||
|
||||
thread = threading.Thread(
|
||||
target=zip_and_move, args=(os.path.dirname(path), self.workfile_path)
|
||||
)
|
||||
thread.start()
|
||||
|
||||
|
||||
def send(request):
|
||||
"""Public method for sending requests to Toon Boom application."""
|
||||
return self.server.send(request)
|
||||
|
||||
|
||||
def show(module_name):
|
||||
"""Call show on "module_name".
|
||||
|
||||
This allows to make a QApplication ahead of time and always "exec_" to
|
||||
prevent crashing.
|
||||
|
||||
Args:
|
||||
module_name (str): Name of module to call "show" on.
|
||||
"""
|
||||
# Need to have an existing QApplication.
|
||||
app = QtWidgets.QApplication.instance()
|
||||
if not app:
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
# Get tool name from module name
|
||||
# TODO this is for backwards compatibility not sure if `avalon.js`
|
||||
# is automatically updated.
|
||||
# Previous javascript sent 'module_name' which contained whole tool import
|
||||
# string e.g. "avalon.tools.workfiles" now it should be only "workfiles"
|
||||
tool_name = module_name.split(".")[-1]
|
||||
if tool_name == "publish":
|
||||
host_tools.show_tool_by_name(tool_name)
|
||||
return
|
||||
|
||||
# Get and show tool.
|
||||
# TODO convert toonboom implementation to run in Qt application as main
|
||||
# thread
|
||||
tool_window = host_tools.get_tool_by_name(tool_name)
|
||||
tool_window.show()
|
||||
|
||||
# QApplication needs to always execute, except when publishing.
|
||||
app.exec_()
|
||||
|
||||
|
||||
def save_scene():
|
||||
"""Saves the Toon Boom scene safely.
|
||||
|
||||
The built-in (to Avalon) background zip and moving of the Harmony scene
|
||||
folder, interfers with server/client communication by sending two requests
|
||||
at the same time. This only happens when sending "scene.saveAll()". This
|
||||
method prevents this double request and safely saves the 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 %s_func()
|
||||
{
|
||||
var app = QCoreApplication.instance();
|
||||
app.avalon_on_file_changed = false;
|
||||
scene.saveAll();
|
||||
return (
|
||||
scene.currentProjectPath() + "/" + scene.currentVersionName()
|
||||
);
|
||||
}
|
||||
%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 %s_func()
|
||||
{
|
||||
var app = QCoreApplication.instance();
|
||||
app.avalon_on_file_changed = true;
|
||||
}
|
||||
%s_func
|
||||
""" % (signature, signature)
|
||||
self.send({"function": func})
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
import socket
|
||||
import logging
|
||||
import json
|
||||
import traceback
|
||||
import importlib
|
||||
import functools
|
||||
|
||||
from . import lib
|
||||
|
||||
|
||||
class Server(object):
|
||||
|
||||
def __init__(self, port):
|
||||
self.connection = None
|
||||
self.received = ""
|
||||
self.port = port
|
||||
|
||||
# Setup logging.
|
||||
self.log = logging.getLogger(__name__)
|
||||
self.log.setLevel(logging.DEBUG)
|
||||
|
||||
# Create a TCP/IP socket
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
# Bind the socket to the port
|
||||
server_address = ("localhost", port)
|
||||
self.log.debug("Starting up on {}".format(server_address))
|
||||
self.socket.bind(server_address)
|
||||
|
||||
# Listen for incoming connections
|
||||
self.socket.listen(1)
|
||||
|
||||
def process_request(self, request):
|
||||
"""
|
||||
Args:
|
||||
request (dict): {
|
||||
"module": (str), # Module of method.
|
||||
"method" (str), # Name of method in module.
|
||||
"args" (list), # Arguments to pass to method.
|
||||
"kwargs" (dict), # Keywork arguments to pass to method.
|
||||
"reply" (bool), # Optional wait for method completion.
|
||||
}
|
||||
"""
|
||||
self.log.debug("Processing request: {}".format(request))
|
||||
|
||||
try:
|
||||
module = importlib.import_module(request["module"])
|
||||
method = getattr(module, request["method"])
|
||||
|
||||
args = request.get("args", [])
|
||||
kwargs = request.get("kwargs", {})
|
||||
partial_method = functools.partial(method, *args, **kwargs)
|
||||
|
||||
lib.execute_in_main_thread(partial_method)
|
||||
except Exception:
|
||||
self.log.error(traceback.format_exc())
|
||||
|
||||
def receive(self):
|
||||
"""Receives data from `self.connection`.
|
||||
|
||||
When the data is a json serializable string, a reply is sent then
|
||||
processing of the request.
|
||||
"""
|
||||
|
||||
while True:
|
||||
# Receive the data in small chunks and retransmit it
|
||||
request = None
|
||||
while True:
|
||||
if self.connection is None:
|
||||
break
|
||||
|
||||
data = self.connection.recv(4096)
|
||||
if data:
|
||||
self.received += data.decode("utf-8")
|
||||
else:
|
||||
break
|
||||
|
||||
self.log.debug("Received: {}".format(self.received))
|
||||
|
||||
try:
|
||||
request = json.loads(self.received)
|
||||
break
|
||||
except json.decoder.JSONDecodeError:
|
||||
pass
|
||||
|
||||
if request is None:
|
||||
break
|
||||
|
||||
self.received = ""
|
||||
|
||||
self.log.debug("Request: {}".format(request))
|
||||
if "reply" not in request.keys():
|
||||
request["reply"] = True
|
||||
self._send(json.dumps(request))
|
||||
|
||||
self.process_request(request)
|
||||
|
||||
def start(self):
|
||||
"""Entry method for server.
|
||||
|
||||
Waits for a connection on `self.port` before going into listen mode.
|
||||
"""
|
||||
|
||||
# Wait for a connection
|
||||
self.log.debug("Waiting for a connection.")
|
||||
self.connection, client_address = self.socket.accept()
|
||||
|
||||
self.log.debug("Connection from: {}".format(client_address))
|
||||
|
||||
self.receive()
|
||||
|
||||
def stop(self):
|
||||
self.log.debug("Shutting down server.")
|
||||
if self.connection is None:
|
||||
self.log.debug("Connect to shutdown.")
|
||||
socket.socket(
|
||||
socket.AF_INET, socket.SOCK_STREAM
|
||||
).connect(("localhost", self.port))
|
||||
|
||||
self.connection.close()
|
||||
self.connection = None
|
||||
|
||||
self.socket.close()
|
||||
|
||||
def _send(self, message):
|
||||
"""Send a message to Harmony.
|
||||
|
||||
Args:
|
||||
message (str): Data to send to Harmony.
|
||||
"""
|
||||
|
||||
# Wait for a connection.
|
||||
while not self.connection:
|
||||
pass
|
||||
|
||||
self.log.debug("Sending: {}".format(message))
|
||||
self.connection.sendall(message.encode("utf-8"))
|
||||
|
||||
def send(self, request):
|
||||
"""Send a request in dictionary to Harmony.
|
||||
|
||||
Waits for a reply from Harmony.
|
||||
|
||||
Args:
|
||||
request (dict): Data to send to Harmony.
|
||||
"""
|
||||
self._send(json.dumps(request))
|
||||
|
||||
result = None
|
||||
while True:
|
||||
try:
|
||||
result = json.loads(self.received)
|
||||
break
|
||||
except json.decoder.JSONDecodeError:
|
||||
pass
|
||||
|
||||
self.received = ""
|
||||
|
||||
return result
|
||||
|
|
@ -179,7 +179,7 @@ class StdOutBroker:
|
|||
self.original_stderr_write(text)
|
||||
if self.send_to_tray:
|
||||
self.log_queue.append(text)
|
||||
|
||||
|
||||
def _process_queue(self):
|
||||
"""Sends lines and purges queue"""
|
||||
if not self.send_to_tray:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue