Files
blender-portable-repo/scripts/addons/Substance3DInBlender/api.py
T
2026-03-17 14:30:01 -06:00

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)