528 lines
17 KiB
Python
528 lines
17 KiB
Python
"""
|
|
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 <http://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
# 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)
|