""" Copyright (C) 2023 Adobe. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ # file: api.py # brief: Central API that connects the Remote engine operations with blender # author Adobe - 3D & Immersive # copyright 2023 Adobe Inc. All rights reserved. import os import time import json from .toolkit.manager import SRE_ToolkitManager from .presets.manager import SUBSTANCE_PresetManager from .network.manager import SUBSTANCE_ServerManager from .sbsar.manager import SUBSTANCE_SbsarManager from .shader.manager import SUBSTANCE_ShaderManager from .render.manager import SUBSTANCE_RenderManager from .utils import SUBSTANCE_Utils from .common import ( TOOLKIT_EXPECTED_VERSION, TOOLKIT_INSTALL_TIME, TOOLKIT_LAG_TIME, SERVER_HOST, Code_Response, Code_RequestType, Code_RequestEndpoint, Code_RequestOp, Code_InputWidget, Code_InputIdentifier, Code_SbsarOp ) class SUBSTANCE_Api(): currently_running = False toolkit_manager = SRE_ToolkitManager() server_manager = SUBSTANCE_ServerManager() sbsar_manager = SUBSTANCE_SbsarManager() shader_manager = SUBSTANCE_ShaderManager() presets_manager = SUBSTANCE_PresetManager() render_manager = SUBSTANCE_RenderManager() listeners = { "head": [], "get": [], "post": [], "patch": [] } temp_tx_files = [] temp_sbs_files = [] last_selection = None # MAIN @classmethod def version(cls): return (2, 1, 1) @classmethod def is_running(cls): return cls.toolkit_manager.is_running() @classmethod def initialize(cls, delay=TOOLKIT_LAG_TIME): # Time _time_start = time.time() SUBSTANCE_Utils.log_data("INFO", "Plugin initialization...") # Initialize Substance Remote Engine _result = cls.toolkit_start(delay) if _result[0] != Code_Response.success and _result[0] != Code_Response.toolkit_already_started: SUBSTANCE_Utils.log_data("ERROR", "[{}] Toolkit failed to initialize...".format(_result[0]), display=True) return _result elif _result[0] == Code_Response.toolkit_already_started: SUBSTANCE_Utils.log_data("INFO", "[{}] Toolkit is already in use by another client...".format(_result[0])) else: SUBSTANCE_Utils.log_data("INFO", "Toolkit Initialized...") _result = cls.server_manager.server_start() if _result[0] != Code_Response.success and _result[0] != Code_Response.server_already_running: SUBSTANCE_Utils.log_data("ERROR", "[{}] Server failed to initialize...".format(_result[0]), display=True) return (_result[0], None) elif _result[0] == Code_Response.server_already_running: SUBSTANCE_Utils.log_data("INFO", "[{}] Server already running...".format(_result[0])) else: SUBSTANCE_Utils.log_data("INFO", "Server Initialized...") _result = cls.server_manager.server_port() _data = { "app": { "pid": os.getpid(), "host": SERVER_HOST, "port": _result[1], "key": "BLD", "name": "Blender" } } _result = cls.msg_system(Code_RequestOp.add, _data) # Time _time_end = time.time() _load_time = round(_time_end - _time_start, 3) SUBSTANCE_Utils.log_data("INFO", "SRE Initialized sucessfully in [{}] seconds".format(_load_time), display=True) cls.currently_running = cls.toolkit_manager.is_running() return _result @classmethod def shutdown(cls): _result = cls.toolkit_stop() if _result[0] == Code_Response.toolkit_in_use: SUBSTANCE_Utils.log_data("INFO", "Toolkit is still in use by another [{}] client(s)...".format(_result[1])) return _result elif _result[0] == Code_Response.toolkit_not_running or _result[0] == Code_Response.toolkit_not_installed: SUBSTANCE_Utils.log_data("INFO", "[{}] Toolkit not active...".format(_result[0])) elif _result[0] == Code_Response.success: SUBSTANCE_Utils.log_data("INFO", "Toolkit terminated...") else: SUBSTANCE_Utils.log_data("ERROR", "[{}] Toolkit failed to shutdown...".format(_result[0])) return _result return (Code_Response.success, None) # LISTENERS @classmethod def listeners_add(cls, type, listener): cls.listeners[type].append(listener) return Code_Response.success @classmethod def listeners_remove(cls, type, listener): try: cls.listeners[type].remove(listener) return Code_Response.success except Exception: return Code_Response.api_listener_remove_error @classmethod def listeners_call(cls, type, data): for _listener in cls.listeners[type]: _listener.execute(data) # TOOLKIT @classmethod def toolkit_path_get(cls): return cls.toolkit_manager.get_path() @classmethod def toolkit_version_get(cls): if cls.toolkit_manager.is_installed(): _result = cls.toolkit_manager.version_get() return _result else: return (Code_Response.toolkit_not_installed, None) @classmethod def toolkit_install(cls, filepath): _result = cls.toolkit_uninstall() if _result[0] != Code_Response.success: return _result _result = cls.toolkit_manager.install(filepath) if _result[0] != Code_Response.success: return _result _result = cls.toolkit_version_get() if _result[1] is not None and _result[1] in TOOLKIT_EXPECTED_VERSION: _result = cls.initialize(TOOLKIT_INSTALL_TIME) return _result elif _result[1] is not None and _result[1] not in TOOLKIT_EXPECTED_VERSION: SUBSTANCE_Utils.log_data( "ERROR", "[{}] Toolkit update needed. Please download and install a compatible version".format(_result[1]), display=True) return (Code_Response.toolkit_version_error, None) @classmethod def toolkit_uninstall(cls): if cls.toolkit_manager.is_running(): _result = cls.shutdown() if _result[0] != Code_Response.success: _result = cls.msg_system(Code_RequestOp.kill, {}) if _result[0] != Code_Response.success: _result = cls.toolkit_manager.stop() if _result[0] != Code_Response.success: return _result if cls.toolkit_manager.is_installed(): time.sleep(TOOLKIT_INSTALL_TIME) return cls.toolkit_manager.remove() return (Code_Response.success, None) @classmethod def toolkit_start(cls, delay): if cls.toolkit_manager.is_installed(): return cls.toolkit_manager.start(delay) else: return (Code_Response.toolkit_not_installed, None) @classmethod def toolkit_stop(cls): if not cls.toolkit_manager.is_installed(): return (Code_Response.toolkit_not_installed, None) if not cls.toolkit_manager.is_running(): return (Code_Response.toolkit_not_running, None) _result = cls.msg_system(Code_RequestOp.remove, { "app": { "pid": os.getpid() } }) if _result[0] != Code_Response.success: return _result if _result[1]["result"] != Code_Response.success.value: return _result if _result[1]["data"]["active_apps"] > 0: return (Code_Response.toolkit_in_use, _result[1]["data"]["active_apps"]) else: return (Code_Response.success, 0) # SBSAR @classmethod def sbsar_load(cls, data, operation=Code_SbsarOp.load): _app_pid = os.getpid() if operation == Code_SbsarOp.load: _data = { "app": { "pid": _app_pid }, "sbsar": { "op": operation.value, "file": data["filepath"], "name": data["name"], "$outputsize": data["$outputsize"], "normal_format": data["normal_format"] }, } if "uuid" in data: _data["sbsar"]["uuid"] = data["uuid"] elif operation == Code_SbsarOp.reload: _data = { "app": { "pid": _app_pid }, "sbsar": { "op": operation.value, "uuid": data["uuid"], "file": data["filepath"], "name": data["name"], "graph_idx": data["graph_idx"], "preset": data["preset"] } } elif operation == Code_SbsarOp.duplicate: _data = { "app": { "pid": _app_pid }, "sbsar": { "op": operation.value, "uuid": data["uuid"], "file": data["filepath"], "name": data["name"] } } _result = cls.msg_sbsar(Code_RequestOp.add, _data) if _result[0] != Code_Response.success: return (_result[0], None) return (Code_Response.success, _result[1]) @classmethod def sbsar_remove(cls, uuid): _data = { "app": { "pid": os.getpid() }, "sbsar": { "uuid": uuid } } _result = cls.msg_sbsar(Code_RequestOp.remove, _data) if _result[0] != Code_Response.success: return (_result[0], None) return (Code_Response.success, _result[1]) @classmethod def sbsar_register(cls, sbsar): _result = cls.sbsar_manager.register(sbsar) return _result @classmethod def sbsar_unregister(cls, uuid): _result = cls.sbsar_manager.unregister(uuid) return _result @classmethod def sbsar_input_update(cls, context, sbsar, graph, input, value, callback): _sync = False if (input.guiWidget == Code_InputWidget.combobox.value or input.guiWidget == Code_InputWidget.togglebutton.value or input.guiWidget == Code_InputWidget.image.value or input.identifier == Code_InputIdentifier.outputsize.value): _sync = True if _sync: return callback(context, sbsar, graph, input, value) else: cls.render_manager.input_queue_add( context, sbsar, graph, input, value, callback ) return (Code_Response.success, None) @classmethod def sbsar_input_set(cls, sbsar, graph, input, value): _data = { "app": { "pid": os.getpid() }, "sbsar": { "uuid": sbsar.uuid, "graph_idx": int(graph.index) }, "input": { "id": int(input.id), "identifier": input.identifier, "type": input.type, "value": value } } _result = cls.msg_sbsar(Code_RequestOp.update, _data) if _result[0] != Code_Response.success: return (_result[0], None) return (Code_Response.success, _result[1]) # Presets @classmethod def sbsar_preset_add(cls, sbsar, graph, name): _data = { "app": { "pid": os.getpid() }, "sbsar": { "uuid": sbsar.uuid, "graph_idx": int(graph.index) }, "preset": { "name": name } } _result = cls.msg_preset(Code_RequestOp.add, _data) if _result[0] != Code_Response.success: return (_result[0], None) return (Code_Response.success, _result[1]) @classmethod def sbsar_preset_load(cls, sbsar, graph, value): _data = { "app": { "pid": os.getpid() }, "sbsar": { "uuid": sbsar.uuid, "graph_idx": int(graph.index) }, "preset": { "value": value } } _result = cls.msg_preset(Code_RequestOp.load, _data) if _result[0] != Code_Response.success: return (_result[0], None) return (Code_Response.success, _result[1]) @classmethod def sbsar_preset_write(cls, filepath, preset): _result = cls.presets_manager.preset_write_file(filepath, preset) return _result @classmethod def sbsar_preset_read(cls, filepath): _result = cls.presets_manager.preset_read_file(filepath) return _result # Rendering @classmethod def sbsar_is_rendering(cls, sbsar_id): if sbsar_id in cls.render_manager.render_current: return 2 else: for _key in cls.render_manager.render_queue: if sbsar_id in _key: return 1 return 0 @classmethod def sbsar_render(cls, render_id, data, request_type=Code_RequestType.r_sync): _result = cls.server_manager.server_port() if _result[0] != Code_Response.success: SUBSTANCE_Utils.log_data("ERROR", "[{}] Render Server not initialized...".format(_result[0])) return _data = { "app": { "pid": os.getpid(), "host": SERVER_HOST, "port": _result[1] }, "sbsar": { "uuid": data["uuid"], "graph_idx": data["index"] }, "render": { "path": data["out_path"], "outputs": data["outputs"] } } cls.render_manager.graph_render( render_id, _data, request_type, cls.msg_sbsar ) @classmethod def sbsar_render_callback(cls, data): cls.render_manager.render_finish(data) # SHADER PRESETS @classmethod def shader_initialize(cls, addon_prefs): addon_prefs.shaders.clear() _result = cls.shader_manager.initialize(addon_prefs) return _result @classmethod def shader_load(cls, filename): _result = cls.shader_manager.load(filename) return _result @classmethod def shader_register(cls, sbsar): _result = cls.shader_manager.register(sbsar) return _result @classmethod def shader_presets_remove(cls, shader_presets): _result = cls.shader_manager.remove_presets(shader_presets) return _result @classmethod def shader_presets_save(cls, shader_preset): _result = cls.shader_manager.save_presets(shader_preset) return _result # MSG SYSTEM @classmethod def msg_system(cls, op, data, request_type=Code_RequestType.r_sync): if cls.toolkit_manager.is_running(): _result = cls.server_manager.server_send_message( request_type, Code_RequestEndpoint.system.value, op.value, data ) if _result[0] != Code_Response.success: return _result return (Code_Response.success, json.loads(_result[1])) else: return (Code_Response.toolkit_not_running, None) # MSG SBSAR @classmethod def msg_sbsar(cls, op, data, request_type=Code_RequestType.r_sync): if cls.toolkit_manager.is_running(): _result = cls.server_manager.server_send_message( request_type, Code_RequestEndpoint.sbsar.value, op.value, data ) if _result[0] != Code_Response.success: return _result return (Code_Response.success, json.loads(_result[1])) else: return (Code_Response.toolkit_not_running, None) # MSG PRESET @classmethod def msg_preset(cls, op, data, request_type=Code_RequestType.r_sync): if cls.toolkit_manager.is_running(): _result = cls.server_manager.server_send_message( request_type, Code_RequestEndpoint.preset.value, op.value, data ) if _result[0] != Code_Response.success: return _result return (Code_Response.success, json.loads(_result[1])) else: return (Code_Response.toolkit_not_running, None)