mirror of
https://github.com/godotengine/godot.git
synced 2024-11-22 12:12:28 +00:00
Merge pull request #69319 from RedMser/blender-import-rpc
Batch import Blend files using XML RPC
This commit is contained in:
commit
d29036bcda
314
modules/gltf/editor/editor_import_blend_runner.cpp
Normal file
314
modules/gltf/editor/editor_import_blend_runner.cpp
Normal file
@ -0,0 +1,314 @@
|
||||
/*************************************************************************/
|
||||
/* editor_import_blend_runner.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "editor_import_blend_runner.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "core/io/http_client.h"
|
||||
#include "editor/editor_file_system.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_settings.h"
|
||||
|
||||
static constexpr char PYTHON_SCRIPT_RPC[] = R"(
|
||||
import bpy, sys, threading
|
||||
from xmlrpc.server import SimpleXMLRPCServer
|
||||
req = threading.Condition()
|
||||
res = threading.Condition()
|
||||
info = None
|
||||
def xmlrpc_server():
|
||||
server = SimpleXMLRPCServer(('127.0.0.1', %d))
|
||||
server.register_function(export_gltf)
|
||||
server.serve_forever()
|
||||
def export_gltf(opts):
|
||||
with req:
|
||||
global info
|
||||
info = ('export_gltf', opts)
|
||||
req.notify()
|
||||
with res:
|
||||
res.wait()
|
||||
if bpy.app.version < (3, 0, 0):
|
||||
print('Blender 3.0 or higher is required.', file=sys.stderr)
|
||||
threading.Thread(target=xmlrpc_server).start()
|
||||
while True:
|
||||
with req:
|
||||
while info is None:
|
||||
req.wait()
|
||||
method, opts = info
|
||||
if method == 'export_gltf':
|
||||
try:
|
||||
bpy.ops.wm.open_mainfile(filepath=opts['path'])
|
||||
if opts['unpack_all']:
|
||||
bpy.ops.file.unpack_all(method='USE_LOCAL')
|
||||
bpy.ops.export_scene.gltf(**opts['gltf_options'])
|
||||
except:
|
||||
pass
|
||||
info = None
|
||||
with res:
|
||||
res.notify()
|
||||
)";
|
||||
|
||||
static constexpr char PYTHON_SCRIPT_DIRECT[] = R"(
|
||||
import bpy, sys
|
||||
opts = %s
|
||||
if bpy.app.version < (3, 0, 0):
|
||||
print('Blender 3.0 or higher is required.', file=sys.stderr)
|
||||
bpy.ops.wm.open_mainfile(filepath=opts['path'])
|
||||
if opts['unpack_all']:
|
||||
bpy.ops.file.unpack_all(method='USE_LOCAL')
|
||||
bpy.ops.export_scene.gltf(**opts['gltf_options'])
|
||||
)";
|
||||
|
||||
String dict_to_python(const Dictionary &p_dict) {
|
||||
String entries;
|
||||
Array dict_keys = p_dict.keys();
|
||||
for (int i = 0; i < dict_keys.size(); i++) {
|
||||
const String key = dict_keys[i];
|
||||
String value;
|
||||
Variant raw_value = p_dict[key];
|
||||
|
||||
switch (raw_value.get_type()) {
|
||||
case Variant::Type::BOOL: {
|
||||
value = raw_value ? "True" : "False";
|
||||
break;
|
||||
}
|
||||
case Variant::Type::STRING:
|
||||
case Variant::Type::STRING_NAME: {
|
||||
value = raw_value;
|
||||
value = vformat("'%s'", value.c_escape());
|
||||
break;
|
||||
}
|
||||
case Variant::Type::DICTIONARY: {
|
||||
value = dict_to_python(raw_value);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ERR_FAIL_V_MSG("", vformat("Unhandled Variant type %s for python dictionary", Variant::get_type_name(raw_value.get_type())));
|
||||
}
|
||||
}
|
||||
|
||||
entries += vformat("'%s': %s,", key, value);
|
||||
}
|
||||
return vformat("{%s}", entries);
|
||||
}
|
||||
|
||||
String dict_to_xmlrpc(const Dictionary &p_dict) {
|
||||
String members;
|
||||
Array dict_keys = p_dict.keys();
|
||||
for (int i = 0; i < dict_keys.size(); i++) {
|
||||
const String key = dict_keys[i];
|
||||
String value;
|
||||
Variant raw_value = p_dict[key];
|
||||
|
||||
switch (raw_value.get_type()) {
|
||||
case Variant::Type::BOOL: {
|
||||
value = vformat("<boolean>%d</boolean>", raw_value ? 1 : 0);
|
||||
break;
|
||||
}
|
||||
case Variant::Type::STRING:
|
||||
case Variant::Type::STRING_NAME: {
|
||||
value = raw_value;
|
||||
value = vformat("<string>%s</string>", value.xml_escape());
|
||||
break;
|
||||
}
|
||||
case Variant::Type::DICTIONARY: {
|
||||
value = dict_to_xmlrpc(raw_value);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ERR_FAIL_V_MSG("", vformat("Unhandled Variant type %s for XMLRPC", Variant::get_type_name(raw_value.get_type())));
|
||||
}
|
||||
}
|
||||
|
||||
members += vformat("<member><name>%s</name><value>%s</value></member>", key, value);
|
||||
}
|
||||
return vformat("<struct>%s</struct>", members);
|
||||
}
|
||||
|
||||
Error EditorImportBlendRunner::start_blender(const String &p_python_script, bool p_blocking) {
|
||||
String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path");
|
||||
|
||||
#ifdef WINDOWS_ENABLED
|
||||
blender_path = blender_path.path_join("blender.exe");
|
||||
#else
|
||||
blender_path = blender_path.path_join("blender");
|
||||
#endif
|
||||
|
||||
List<String> args;
|
||||
args.push_back("--background");
|
||||
args.push_back("--python-expr");
|
||||
args.push_back(p_python_script);
|
||||
|
||||
Error err;
|
||||
if (p_blocking) {
|
||||
int exitcode = 0;
|
||||
err = OS::get_singleton()->execute(blender_path, args, nullptr, &exitcode);
|
||||
if (exitcode != 0) {
|
||||
return FAILED;
|
||||
}
|
||||
} else {
|
||||
err = OS::get_singleton()->create_process(blender_path, args, &blender_pid);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
Error EditorImportBlendRunner::do_import(const Dictionary &p_options) {
|
||||
if (is_using_rpc()) {
|
||||
return do_import_rpc(p_options);
|
||||
} else {
|
||||
return do_import_direct(p_options);
|
||||
}
|
||||
}
|
||||
|
||||
Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) {
|
||||
kill_timer->stop();
|
||||
|
||||
// Start Blender if not already running.
|
||||
if (!is_running()) {
|
||||
// Start an XML RPC server on the given port.
|
||||
String python = vformat(PYTHON_SCRIPT_RPC, rpc_port);
|
||||
Error err = start_blender(python, false);
|
||||
if (err != OK || blender_pid == 0) {
|
||||
return FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert options to XML body.
|
||||
String xml_options = dict_to_xmlrpc(p_options);
|
||||
String xml_body = vformat("<?xml version=\"1.0\"?><methodCall><methodName>export_gltf</methodName><params><param><value>%s</value></param></params></methodCall>", xml_options);
|
||||
|
||||
// Connect to RPC server.
|
||||
Ref<HTTPClient> client = HTTPClient::create();
|
||||
client->connect_to_host("127.0.0.1", rpc_port);
|
||||
|
||||
bool done = false;
|
||||
while (!done) {
|
||||
HTTPClient::Status status = client->get_status();
|
||||
switch (status) {
|
||||
case HTTPClient::STATUS_RESOLVING:
|
||||
case HTTPClient::STATUS_CONNECTING: {
|
||||
client->poll();
|
||||
break;
|
||||
}
|
||||
case HTTPClient::STATUS_CONNECTED: {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ERR_FAIL_V_MSG(ERR_CONNECTION_ERROR, vformat("Unexpected status during RPC connection: %d", status));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send XML request.
|
||||
PackedByteArray xml_buffer = xml_body.to_utf8_buffer();
|
||||
Error err = client->request(HTTPClient::METHOD_POST, "/", Vector<String>(), xml_buffer.ptr(), xml_buffer.size());
|
||||
if (err != OK) {
|
||||
ERR_FAIL_V_MSG(err, vformat("Unable to send RPC request: %d", err));
|
||||
}
|
||||
|
||||
// Wait for response.
|
||||
done = false;
|
||||
while (!done) {
|
||||
HTTPClient::Status status = client->get_status();
|
||||
switch (status) {
|
||||
case HTTPClient::STATUS_REQUESTING: {
|
||||
client->poll();
|
||||
break;
|
||||
}
|
||||
case HTTPClient::STATUS_BODY: {
|
||||
client->poll();
|
||||
// Parse response here if needed. For now we can just ignore it.
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ERR_FAIL_V_MSG(ERR_CONNECTION_ERROR, vformat("Unexpected status during RPC response: %d", status));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error EditorImportBlendRunner::do_import_direct(const Dictionary &p_options) {
|
||||
// Export glTF directly.
|
||||
String python = vformat(PYTHON_SCRIPT_DIRECT, dict_to_python(p_options));
|
||||
Error err = start_blender(python, true);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void EditorImportBlendRunner::_resources_reimported(const PackedStringArray &p_files) {
|
||||
if (is_running()) {
|
||||
// After a batch of imports is done, wait a few seconds before trying to kill blender,
|
||||
// in case of having multiple imports trigger in quick succession.
|
||||
kill_timer->start();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorImportBlendRunner::_kill_blender() {
|
||||
kill_timer->stop();
|
||||
if (is_running()) {
|
||||
OS::get_singleton()->kill(blender_pid);
|
||||
}
|
||||
blender_pid = 0;
|
||||
}
|
||||
|
||||
void EditorImportBlendRunner::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_PREDELETE: {
|
||||
_kill_blender();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorImportBlendRunner *EditorImportBlendRunner::singleton = nullptr;
|
||||
|
||||
EditorImportBlendRunner::EditorImportBlendRunner() {
|
||||
ERR_FAIL_COND_MSG(singleton != nullptr, "EditorImportBlendRunner already created.");
|
||||
singleton = this;
|
||||
|
||||
rpc_port = EDITOR_GET("filesystem/import/blender/rpc_port");
|
||||
|
||||
kill_timer = memnew(Timer);
|
||||
add_child(kill_timer);
|
||||
kill_timer->set_one_shot(true);
|
||||
kill_timer->set_wait_time(EDITOR_GET("filesystem/import/blender/rpc_server_uptime"));
|
||||
kill_timer->connect("timeout", callable_mp(this, &EditorImportBlendRunner::_kill_blender));
|
||||
|
||||
EditorFileSystem::get_singleton()->connect("resources_reimported", callable_mp(this, &EditorImportBlendRunner::_resources_reimported));
|
||||
}
|
||||
|
||||
#endif // TOOLS_ENABLED
|
69
modules/gltf/editor/editor_import_blend_runner.h
Normal file
69
modules/gltf/editor/editor_import_blend_runner.h
Normal file
@ -0,0 +1,69 @@
|
||||
/*************************************************************************/
|
||||
/* editor_import_blend_runner.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef EDITOR_IMPORT_BLEND_RUNNER_H
|
||||
#define EDITOR_IMPORT_BLEND_RUNNER_H
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "core/os/os.h"
|
||||
#include "scene/main/node.h"
|
||||
#include "scene/main/timer.h"
|
||||
|
||||
class EditorImportBlendRunner : public Node {
|
||||
GDCLASS(EditorImportBlendRunner, Node);
|
||||
|
||||
static EditorImportBlendRunner *singleton;
|
||||
|
||||
Timer *kill_timer;
|
||||
void _resources_reimported(const PackedStringArray &p_files);
|
||||
void _kill_blender();
|
||||
void _notification(int p_what);
|
||||
|
||||
protected:
|
||||
int rpc_port = 0;
|
||||
OS::ProcessID blender_pid = 0;
|
||||
Error start_blender(const String &p_python_script, bool p_blocking);
|
||||
Error do_import_direct(const Dictionary &p_options);
|
||||
Error do_import_rpc(const Dictionary &p_options);
|
||||
|
||||
public:
|
||||
static EditorImportBlendRunner *get_singleton() { return singleton; }
|
||||
|
||||
bool is_running() { return blender_pid != 0 && OS::get_singleton()->is_process_running(blender_pid); }
|
||||
bool is_using_rpc() { return rpc_port != 0; }
|
||||
Error do_import(const Dictionary &p_options);
|
||||
|
||||
EditorImportBlendRunner();
|
||||
};
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
#endif // EDITOR_IMPORT_BLEND_RUNNER_H
|
@ -34,6 +34,7 @@
|
||||
|
||||
#include "../gltf_defines.h"
|
||||
#include "../gltf_document.h"
|
||||
#include "editor_import_blend_runner.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "editor/editor_file_dialog.h"
|
||||
@ -68,149 +69,129 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
|
||||
|
||||
// Handle configuration options.
|
||||
|
||||
String parameters_arg;
|
||||
Dictionary request_options;
|
||||
Dictionary parameters_map;
|
||||
|
||||
parameters_map["filepath"] = sink_global;
|
||||
parameters_map["export_keep_originals"] = true;
|
||||
parameters_map["export_format"] = "GLTF_SEPARATE";
|
||||
parameters_map["export_yup"] = true;
|
||||
|
||||
if (p_options.has(SNAME("blender/nodes/custom_properties")) && p_options[SNAME("blender/nodes/custom_properties")]) {
|
||||
parameters_arg += "export_extras=True,";
|
||||
parameters_map["export_extras"] = true;
|
||||
} else {
|
||||
parameters_arg += "export_extras=False,";
|
||||
parameters_map["export_extras"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/meshes/skins"))) {
|
||||
int32_t skins = p_options["blender/meshes/skins"];
|
||||
if (skins == BLEND_BONE_INFLUENCES_NONE) {
|
||||
parameters_arg += "export_skins=False,";
|
||||
parameters_map["export_skins"] = false;
|
||||
} else if (skins == BLEND_BONE_INFLUENCES_COMPATIBLE) {
|
||||
parameters_arg += "export_all_influences=False,export_skins=True,";
|
||||
parameters_map["export_skins"] = true;
|
||||
parameters_map["export_all_influences"] = false;
|
||||
} else if (skins == BLEND_BONE_INFLUENCES_ALL) {
|
||||
parameters_arg += "export_all_influences=True,export_skins=True,";
|
||||
parameters_map["export_skins"] = true;
|
||||
parameters_map["export_all_influences"] = true;
|
||||
}
|
||||
} else {
|
||||
parameters_arg += "export_skins=False,";
|
||||
parameters_map["export_skins"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/materials/export_materials"))) {
|
||||
int32_t exports = p_options["blender/materials/export_materials"];
|
||||
if (exports == BLEND_MATERIAL_EXPORT_PLACEHOLDER) {
|
||||
parameters_arg += "export_materials='PLACEHOLDER',";
|
||||
parameters_map["export_materials"] = "PLACEHOLDER";
|
||||
} else if (exports == BLEND_MATERIAL_EXPORT_EXPORT) {
|
||||
parameters_arg += "export_materials='EXPORT',";
|
||||
parameters_map["export_materials"] = "EXPORT";
|
||||
}
|
||||
} else {
|
||||
parameters_arg += "export_materials='PLACEHOLDER',";
|
||||
parameters_map["export_materials"] = "PLACEHOLDER";
|
||||
}
|
||||
if (p_options.has(SNAME("blender/nodes/cameras")) && p_options[SNAME("blender/nodes/cameras")]) {
|
||||
parameters_arg += "export_cameras=True,";
|
||||
parameters_map["export_cameras"] = true;
|
||||
} else {
|
||||
parameters_arg += "export_cameras=False,";
|
||||
parameters_map["export_cameras"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/nodes/punctual_lights")) && p_options[SNAME("blender/nodes/punctual_lights")]) {
|
||||
parameters_arg += "export_lights=True,";
|
||||
parameters_map["export_lights"] = true;
|
||||
} else {
|
||||
parameters_arg += "export_lights=False,";
|
||||
parameters_map["export_lights"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) {
|
||||
parameters_arg += "export_colors=True,";
|
||||
parameters_map["export_colors"] = true;
|
||||
} else {
|
||||
parameters_arg += "export_colors=False,";
|
||||
parameters_map["export_colors"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/nodes/visible"))) {
|
||||
int32_t visible = p_options["blender/nodes/visible"];
|
||||
if (visible == BLEND_VISIBLE_VISIBLE_ONLY) {
|
||||
parameters_arg += "use_visible=True,";
|
||||
parameters_map["use_visible"] = true;
|
||||
} else if (visible == BLEND_VISIBLE_RENDERABLE) {
|
||||
parameters_arg += "use_renderable=True,";
|
||||
parameters_map["use_renderable"] = true;
|
||||
} else if (visible == BLEND_VISIBLE_ALL) {
|
||||
parameters_arg += "use_visible=False,use_renderable=False,";
|
||||
parameters_map["use_renderable"] = false;
|
||||
parameters_map["use_visible"] = false;
|
||||
}
|
||||
} else {
|
||||
parameters_arg += "use_visible=False,use_renderable=False,";
|
||||
parameters_map["use_renderable"] = false;
|
||||
parameters_map["use_visible"] = false;
|
||||
}
|
||||
|
||||
if (p_options.has(SNAME("blender/meshes/uvs")) && p_options[SNAME("blender/meshes/uvs")]) {
|
||||
parameters_arg += "export_texcoords=True,";
|
||||
parameters_map["export_texcoords"] = true;
|
||||
} else {
|
||||
parameters_arg += "export_texcoords=False,";
|
||||
parameters_map["export_texcoords"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/meshes/normals")) && p_options[SNAME("blender/meshes/normals")]) {
|
||||
parameters_arg += "export_normals=True,";
|
||||
parameters_map["export_normals"] = true;
|
||||
} else {
|
||||
parameters_arg += "export_normals=False,";
|
||||
parameters_map["export_normals"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/meshes/tangents")) && p_options[SNAME("blender/meshes/tangents")]) {
|
||||
parameters_arg += "export_tangents=True,";
|
||||
parameters_map["export_tangents"] = true;
|
||||
} else {
|
||||
parameters_arg += "export_tangents=False,";
|
||||
parameters_map["export_tangents"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/animation/group_tracks")) && p_options[SNAME("blender/animation/group_tracks")]) {
|
||||
parameters_arg += "export_nla_strips=True,";
|
||||
parameters_map["export_nla_strips"] = true;
|
||||
} else {
|
||||
parameters_arg += "export_nla_strips=False,";
|
||||
parameters_map["export_nla_strips"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/animation/limit_playback")) && p_options[SNAME("blender/animation/limit_playback")]) {
|
||||
parameters_arg += "export_frame_range=True,";
|
||||
parameters_map["export_frame_range"] = true;
|
||||
} else {
|
||||
parameters_arg += "export_frame_range=False,";
|
||||
parameters_map["export_frame_range"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/animation/always_sample")) && p_options[SNAME("blender/animation/always_sample")]) {
|
||||
parameters_arg += "export_force_sampling=True,";
|
||||
parameters_map["export_force_sampling"] = true;
|
||||
} else {
|
||||
parameters_arg += "export_force_sampling=False,";
|
||||
parameters_map["export_force_sampling"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/meshes/export_bones_deforming_mesh_only")) && p_options[SNAME("blender/meshes/export_bones_deforming_mesh_only")]) {
|
||||
parameters_arg += "export_def_bones=True,";
|
||||
parameters_map["export_def_bones"] = true;
|
||||
} else {
|
||||
parameters_arg += "export_def_bones=False,";
|
||||
parameters_map["export_def_bones"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/nodes/modifiers")) && p_options[SNAME("blender/nodes/modifiers")]) {
|
||||
parameters_arg += "export_apply=True";
|
||||
parameters_map["export_apply"] = true;
|
||||
} else {
|
||||
parameters_arg += "export_apply=False";
|
||||
parameters_map["export_apply"] = false;
|
||||
}
|
||||
|
||||
String unpack_all;
|
||||
if (p_options.has(SNAME("blender/materials/unpack_enabled")) && p_options[SNAME("blender/materials/unpack_enabled")]) {
|
||||
unpack_all = "bpy.ops.file.unpack_all(method='USE_LOCAL');";
|
||||
request_options["unpack_all"] = true;
|
||||
} else {
|
||||
request_options["unpack_all"] = false;
|
||||
}
|
||||
|
||||
// Prepare Blender export script.
|
||||
request_options["path"] = source_global;
|
||||
request_options["gltf_options"] = parameters_map;
|
||||
|
||||
String common_args = vformat("filepath='%s',", sink_global) +
|
||||
"export_format='GLTF_SEPARATE',"
|
||||
"export_yup=True," +
|
||||
parameters_arg;
|
||||
String export_script =
|
||||
String("import bpy, sys;") +
|
||||
"print('Blender 3.0 or higher is required.', file=sys.stderr) if bpy.app.version < (3, 0, 0) else None;" +
|
||||
vformat("bpy.ops.wm.open_mainfile(filepath='%s');", source_global) +
|
||||
unpack_all +
|
||||
vformat("bpy.ops.export_scene.gltf(export_keep_originals=True,%s);", common_args);
|
||||
print_verbose(export_script);
|
||||
|
||||
// Run script with configured Blender binary.
|
||||
|
||||
String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path");
|
||||
|
||||
#ifdef WINDOWS_ENABLED
|
||||
blender_path = blender_path.path_join("blender.exe");
|
||||
#else
|
||||
blender_path = blender_path.path_join("blender");
|
||||
#endif
|
||||
|
||||
List<String> args;
|
||||
args.push_back("--background");
|
||||
args.push_back("--python-expr");
|
||||
args.push_back(export_script);
|
||||
|
||||
String standard_out;
|
||||
int ret;
|
||||
OS::get_singleton()->execute(blender_path, args, &standard_out, &ret, true);
|
||||
print_verbose(blender_path);
|
||||
print_verbose(standard_out);
|
||||
|
||||
if (ret != 0) {
|
||||
// Run Blender and export glTF.
|
||||
Error err = EditorImportBlendRunner::get_singleton()->do_import(request_options);
|
||||
if (err != OK) {
|
||||
if (r_err) {
|
||||
*r_err = ERR_SCRIPT_FAILED;
|
||||
}
|
||||
ERR_PRINT(vformat("Blend export to glTF failed with error: %d.", ret));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -226,7 +207,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
|
||||
if (p_options.has(SNAME("blender/materials/unpack_enabled")) && p_options[SNAME("blender/materials/unpack_enabled")]) {
|
||||
base_dir = sink.get_base_dir();
|
||||
}
|
||||
Error err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, base_dir);
|
||||
err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, base_dir);
|
||||
if (err != OK) {
|
||||
if (r_err) {
|
||||
*r_err = FAILED;
|
||||
|
@ -36,6 +36,7 @@
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
#include "core/config/project_settings.h"
|
||||
#include "editor/editor_import_blend_runner.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_scene_exporter_gltf_plugin.h"
|
||||
#include "editor/editor_scene_importer_blend.h"
|
||||
@ -52,6 +53,14 @@ static void _editor_init() {
|
||||
|
||||
bool blend_enabled = GLOBAL_GET("filesystem/import/blender/enabled");
|
||||
// Defined here because EditorSettings doesn't exist in `register_gltf_types` yet.
|
||||
EDITOR_DEF_RST("filesystem/import/blender/rpc_port", 6011);
|
||||
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT,
|
||||
"filesystem/import/blender/rpc_port", PROPERTY_HINT_RANGE, "0,65535,1"));
|
||||
|
||||
EDITOR_DEF_RST("filesystem/import/blender/rpc_server_uptime", 5);
|
||||
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::FLOAT,
|
||||
"filesystem/import/blender/rpc_server_uptime", PROPERTY_HINT_RANGE, "0,300,1,or_greater,suffix:s"));
|
||||
|
||||
String blender3_path = EDITOR_DEF_RST("filesystem/import/blender/blender3_path", "");
|
||||
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING,
|
||||
"filesystem/import/blender/blender3_path", PROPERTY_HINT_GLOBAL_DIR));
|
||||
@ -71,6 +80,8 @@ static void _editor_init() {
|
||||
EditorFileSystem::get_singleton()->add_import_format_support_query(blend_import_query);
|
||||
}
|
||||
}
|
||||
memnew(EditorImportBlendRunner);
|
||||
EditorNode::get_singleton()->add_child(EditorImportBlendRunner::get_singleton());
|
||||
|
||||
// FBX to glTF importer.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user