initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
390
modules/gltf/editor/editor_import_blend_runner.cpp
Normal file
390
modules/gltf/editor/editor_import_blend_runner.cpp
Normal file
@@ -0,0 +1,390 @@
|
||||
/**************************************************************************/
|
||||
/* editor_import_blend_runner.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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"
|
||||
|
||||
#include "core/io/http_client.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/file_system/editor_file_system.h"
|
||||
#include "editor/settings/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
|
||||
export_err = 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 export_err:
|
||||
raise export_err
|
||||
# Important to return a value to prevent the error 'cannot marshal None unless allow_none is enabled'.
|
||||
return 'BLENDER_GODOT_EXPORT_SUCCESSFUL'
|
||||
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:
|
||||
export_err = None
|
||||
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 Exception as e:
|
||||
export_err = e
|
||||
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;
|
||||
for (const KeyValue<Variant, Variant> &kv : p_dict) {
|
||||
const String &key = kv.key;
|
||||
String value;
|
||||
const Variant &raw_value = kv.value;
|
||||
|
||||
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;
|
||||
for (const KeyValue<Variant, Variant> &kv : p_dict) {
|
||||
const String &key = kv.key;
|
||||
String value;
|
||||
const Variant &raw_value = kv.value;
|
||||
|
||||
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/blender_path");
|
||||
|
||||
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()) {
|
||||
Error err = do_import_rpc(p_options);
|
||||
if (err != OK) {
|
||||
// Retry without using RPC (slow, but better than the import failing completely).
|
||||
if (err == ERR_CONNECTION_ERROR) {
|
||||
// Disable RPC if the connection could not be established.
|
||||
print_error(vformat("Failed to connect to Blender via RPC, switching to direct imports of .blend files. Check your proxy and firewall settings, then RPC can be re-enabled by changing the editor setting `filesystem/import/blender/rpc_port` to %d.", rpc_port));
|
||||
EditorSettings::get_singleton()->set_manually("filesystem/import/blender/rpc_port", 0);
|
||||
rpc_port = 0;
|
||||
}
|
||||
if (err != ERR_QUERY_FAILED) {
|
||||
err = do_import_direct(p_options);
|
||||
}
|
||||
}
|
||||
return err;
|
||||
} else {
|
||||
return do_import_direct(p_options);
|
||||
}
|
||||
}
|
||||
|
||||
HTTPClient::Status EditorImportBlendRunner::connect_blender_rpc(const Ref<HTTPClient> &p_client, int p_timeout_usecs) {
|
||||
p_client->connect_to_host("127.0.0.1", rpc_port);
|
||||
HTTPClient::Status status = p_client->get_status();
|
||||
|
||||
int attempts = 1;
|
||||
int wait_usecs = 1000;
|
||||
|
||||
bool done = false;
|
||||
while (!done) {
|
||||
OS::get_singleton()->delay_usec(wait_usecs);
|
||||
status = p_client->get_status();
|
||||
switch (status) {
|
||||
case HTTPClient::STATUS_RESOLVING:
|
||||
case HTTPClient::STATUS_CONNECTING: {
|
||||
p_client->poll();
|
||||
break;
|
||||
}
|
||||
case HTTPClient::STATUS_CONNECTED: {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (attempts * wait_usecs < p_timeout_usecs) {
|
||||
p_client->connect_to_host("127.0.0.1", rpc_port);
|
||||
} else {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
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();
|
||||
HTTPClient::Status status = connect_blender_rpc(client, 1000000);
|
||||
if (status != HTTPClient::STATUS_CONNECTED) {
|
||||
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.
|
||||
bool done = false;
|
||||
PackedByteArray response;
|
||||
while (!done) {
|
||||
status = client->get_status();
|
||||
switch (status) {
|
||||
case HTTPClient::STATUS_REQUESTING: {
|
||||
client->poll();
|
||||
break;
|
||||
}
|
||||
case HTTPClient::STATUS_BODY: {
|
||||
client->poll();
|
||||
response.append_array(client->read_response_body_chunk());
|
||||
break;
|
||||
}
|
||||
case HTTPClient::STATUS_CONNECTED: {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ERR_FAIL_V_MSG(ERR_CONNECTION_ERROR, vformat("Unexpected status during RPC response: %d", status));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String response_text = "No response from Blender.";
|
||||
if (response.size() > 0) {
|
||||
response_text = String::utf8((const char *)response.ptr(), response.size());
|
||||
}
|
||||
|
||||
if (client->get_response_code() != HTTPClient::RESPONSE_OK) {
|
||||
ERR_FAIL_V_MSG(ERR_QUERY_FAILED, vformat("Error received from Blender - status code: %s, error: %s", client->get_response_code(), response_text));
|
||||
} else if (response_text.find("BLENDER_GODOT_EXPORT_SUCCESSFUL") < 0) {
|
||||
// Previous versions of Godot used a Python script where the RPC function did not return
|
||||
// a value, causing the error 'cannot marshal None unless allow_none is enabled'.
|
||||
// If an older version of Godot is running and has started Blender with this script,
|
||||
// we will receive the error, but there's a good chance that the import was successful.
|
||||
// We are discarding this error to maintain backward compatibility and prevent situations
|
||||
// where the user needs to close the older version of Godot or kill Blender.
|
||||
if (response_text.find("cannot marshal None unless allow_none is enabled") < 0) {
|
||||
String error_message;
|
||||
if (_extract_error_message_xml(response, error_message)) {
|
||||
ERR_FAIL_V_MSG(ERR_QUERY_FAILED, vformat("Blender exportation failed: %s", error_message));
|
||||
} else {
|
||||
ERR_FAIL_V_MSG(ERR_QUERY_FAILED, vformat("Blender exportation failed: %s", response_text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
bool EditorImportBlendRunner::_extract_error_message_xml(const Vector<uint8_t> &p_response_data, String &r_error_message) {
|
||||
// Based on RPC Xml spec from: https://xmlrpc.com/spec.md
|
||||
Ref<XMLParser> parser = memnew(XMLParser);
|
||||
Error err = parser->open_buffer(p_response_data);
|
||||
if (err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
r_error_message = String();
|
||||
while (parser->read() == OK) {
|
||||
if (parser->get_node_type() == XMLParser::NODE_TEXT) {
|
||||
if (parser->get_node_data().size()) {
|
||||
if (r_error_message.size()) {
|
||||
r_error_message += " ";
|
||||
}
|
||||
r_error_message += parser->get_node_data().trim_suffix("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return r_error_message.size();
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
65
modules/gltf/editor/editor_import_blend_runner.h
Normal file
65
modules/gltf/editor/editor_import_blend_runner.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/**************************************************************************/
|
||||
/* editor_import_blend_runner.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/http_client.h"
|
||||
#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);
|
||||
bool _extract_error_message_xml(const Vector<uint8_t> &p_response_data, String &r_error_message);
|
||||
|
||||
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);
|
||||
HTTPClient::Status connect_blender_rpc(const Ref<HTTPClient> &p_client, int p_timeout_usecs);
|
||||
|
||||
EditorImportBlendRunner();
|
||||
};
|
118
modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
Normal file
118
modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
/**************************************************************************/
|
||||
/* editor_scene_exporter_gltf_plugin.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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_scene_exporter_gltf_plugin.h"
|
||||
|
||||
#include "editor_scene_exporter_gltf_settings.h"
|
||||
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/file_system/editor_file_system.h"
|
||||
#include "editor/gui/editor_file_dialog.h"
|
||||
#include "editor/import/3d/scene_import_settings.h"
|
||||
#include "editor/inspector/editor_inspector.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
|
||||
String SceneExporterGLTFPlugin::get_plugin_name() const {
|
||||
return "ConvertGLTF2";
|
||||
}
|
||||
|
||||
bool SceneExporterGLTFPlugin::has_main_screen() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
SceneExporterGLTFPlugin::SceneExporterGLTFPlugin() {
|
||||
_gltf_document.instantiate();
|
||||
// Set up the file dialog.
|
||||
_file_dialog = memnew(EditorFileDialog);
|
||||
_file_dialog->connect("file_selected", callable_mp(this, &SceneExporterGLTFPlugin::_export_scene_as_gltf));
|
||||
_file_dialog->set_title(TTR("Export Library"));
|
||||
_file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
|
||||
_file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
|
||||
_file_dialog->clear_filters();
|
||||
_file_dialog->add_filter("*.glb");
|
||||
_file_dialog->add_filter("*.gltf");
|
||||
_file_dialog->set_title(TTR("Export Scene to glTF 2.0 File"));
|
||||
EditorNode::get_singleton()->get_gui_base()->add_child(_file_dialog);
|
||||
// Set up the export settings menu.
|
||||
_export_settings.instantiate();
|
||||
_export_settings->generate_property_list(_gltf_document);
|
||||
_settings_inspector = memnew(EditorInspector);
|
||||
_settings_inspector->set_custom_minimum_size(Size2(350, 300) * EDSCALE);
|
||||
_file_dialog->add_side_menu(_settings_inspector, TTR("Export Settings:"));
|
||||
// Add a button to the Scene -> Export menu to pop up the settings dialog.
|
||||
PopupMenu *menu = get_export_as_menu();
|
||||
int idx = menu->get_item_count();
|
||||
menu->add_item(TTRC("glTF 2.0 Scene..."));
|
||||
menu->set_item_metadata(idx, callable_mp(this, &SceneExporterGLTFPlugin::_popup_gltf_export_dialog));
|
||||
}
|
||||
|
||||
void SceneExporterGLTFPlugin::_popup_gltf_export_dialog() {
|
||||
Node *root = EditorNode::get_singleton()->get_tree()->get_edited_scene_root();
|
||||
if (!root) {
|
||||
EditorNode::get_singleton()->show_accept(TTR("This operation can't be done without a scene."), TTR("OK"));
|
||||
return;
|
||||
}
|
||||
// Set the file dialog's file name to the scene name.
|
||||
String filename = String(root->get_scene_file_path().get_file().get_basename());
|
||||
if (filename.is_empty()) {
|
||||
filename = root->get_name();
|
||||
}
|
||||
_file_dialog->set_current_file(filename + String(".gltf"));
|
||||
// Generate and refresh the export settings.
|
||||
_export_settings->generate_property_list(_gltf_document, root);
|
||||
_settings_inspector->edit(nullptr);
|
||||
_settings_inspector->edit(_export_settings.ptr());
|
||||
// Show the file dialog.
|
||||
_file_dialog->popup_centered_ratio();
|
||||
}
|
||||
|
||||
void SceneExporterGLTFPlugin::_export_scene_as_gltf(const String &p_file_path) {
|
||||
Node *root = EditorNode::get_singleton()->get_tree()->get_edited_scene_root();
|
||||
if (!root) {
|
||||
EditorNode::get_singleton()->show_accept(TTR("This operation can't be done without a scene."), TTR("OK"));
|
||||
return;
|
||||
}
|
||||
List<String> deps;
|
||||
Ref<GLTFState> state;
|
||||
state.instantiate();
|
||||
state->set_copyright(_export_settings->get_copyright());
|
||||
int32_t flags = 0;
|
||||
flags |= EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS;
|
||||
state->set_bake_fps(_export_settings->get_bake_fps());
|
||||
Error err = _gltf_document->append_from_scene(root, state, flags);
|
||||
if (err != OK) {
|
||||
ERR_PRINT(vformat("glTF2 save scene error %s.", itos(err)));
|
||||
}
|
||||
err = _gltf_document->write_to_filesystem(state, p_file_path);
|
||||
if (err != OK) {
|
||||
ERR_PRINT(vformat("glTF2 save scene error %s.", itos(err)));
|
||||
}
|
||||
EditorFileSystem::get_singleton()->scan_changes();
|
||||
}
|
55
modules/gltf/editor/editor_scene_exporter_gltf_plugin.h
Normal file
55
modules/gltf/editor/editor_scene_exporter_gltf_plugin.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/**************************************************************************/
|
||||
/* editor_scene_exporter_gltf_plugin.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../gltf_document.h"
|
||||
#include "editor_scene_exporter_gltf_settings.h"
|
||||
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
|
||||
class EditorFileDialog;
|
||||
class EditorInspector;
|
||||
|
||||
class SceneExporterGLTFPlugin : public EditorPlugin {
|
||||
GDCLASS(SceneExporterGLTFPlugin, EditorPlugin);
|
||||
|
||||
Ref<GLTFDocument> _gltf_document;
|
||||
Ref<EditorSceneExporterGLTFSettings> _export_settings;
|
||||
EditorInspector *_settings_inspector = nullptr;
|
||||
EditorFileDialog *_file_dialog = nullptr;
|
||||
void _popup_gltf_export_dialog();
|
||||
void _export_scene_as_gltf(const String &p_file_path);
|
||||
|
||||
public:
|
||||
virtual String get_plugin_name() const override;
|
||||
bool has_main_screen() const override;
|
||||
SceneExporterGLTFPlugin();
|
||||
};
|
257
modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp
Normal file
257
modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
/**************************************************************************/
|
||||
/* editor_scene_exporter_gltf_settings.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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_scene_exporter_gltf_settings.h"
|
||||
|
||||
const uint32_t PROP_EDITOR_SCRIPT_VAR = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_SCRIPT_VARIABLE;
|
||||
|
||||
bool EditorSceneExporterGLTFSettings::_set(const StringName &p_name, const Variant &p_value) {
|
||||
String name_str = String(p_name);
|
||||
if (name_str.contains_char('/')) {
|
||||
return _set_extension_setting(name_str, p_value);
|
||||
}
|
||||
if (p_name == StringName("image_format")) {
|
||||
_document->set_image_format(p_value);
|
||||
emit_signal(CoreStringName(property_list_changed));
|
||||
return true;
|
||||
}
|
||||
if (p_name == StringName("lossy_quality")) {
|
||||
_document->set_lossy_quality(p_value);
|
||||
return true;
|
||||
}
|
||||
if (p_name == StringName("fallback_image_format")) {
|
||||
_document->set_fallback_image_format(p_value);
|
||||
emit_signal(CoreStringName(property_list_changed));
|
||||
return true;
|
||||
}
|
||||
if (p_name == StringName("fallback_image_quality")) {
|
||||
_document->set_fallback_image_quality(p_value);
|
||||
return true;
|
||||
}
|
||||
if (p_name == StringName("root_node_mode")) {
|
||||
_document->set_root_node_mode((GLTFDocument::RootNodeMode)(int64_t)p_value);
|
||||
return true;
|
||||
}
|
||||
if (p_name == StringName("visibility_mode")) {
|
||||
_document->set_visibility_mode((GLTFDocument::VisibilityMode)(int64_t)p_value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EditorSceneExporterGLTFSettings::_get(const StringName &p_name, Variant &r_ret) const {
|
||||
String name_str = String(p_name);
|
||||
if (name_str.contains_char('/')) {
|
||||
return _get_extension_setting(name_str, r_ret);
|
||||
}
|
||||
if (p_name == StringName("image_format")) {
|
||||
r_ret = _document->get_image_format();
|
||||
return true;
|
||||
}
|
||||
if (p_name == StringName("lossy_quality")) {
|
||||
r_ret = _document->get_lossy_quality();
|
||||
return true;
|
||||
}
|
||||
if (p_name == StringName("fallback_image_format")) {
|
||||
r_ret = _document->get_fallback_image_format();
|
||||
return true;
|
||||
}
|
||||
if (p_name == StringName("fallback_image_quality")) {
|
||||
r_ret = _document->get_fallback_image_quality();
|
||||
return true;
|
||||
}
|
||||
if (p_name == StringName("root_node_mode")) {
|
||||
r_ret = _document->get_root_node_mode();
|
||||
return true;
|
||||
}
|
||||
if (p_name == StringName("visibility_mode")) {
|
||||
r_ret = _document->get_visibility_mode();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EditorSceneExporterGLTFSettings::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
for (PropertyInfo prop : _property_list) {
|
||||
if (prop.name == "lossy_quality") {
|
||||
const String image_format = get("image_format");
|
||||
const bool is_image_format_lossy = image_format == "JPEG" || image_format.containsn("Lossy");
|
||||
prop.usage = is_image_format_lossy ? PROPERTY_USAGE_DEFAULT : PROPERTY_USAGE_STORAGE;
|
||||
}
|
||||
if (prop.name == "fallback_image_format") {
|
||||
const String image_format = get("image_format");
|
||||
const bool is_image_format_extension = image_format != "None" && image_format != "PNG" && image_format != "JPEG";
|
||||
prop.usage = is_image_format_extension ? PROPERTY_USAGE_DEFAULT : PROPERTY_USAGE_STORAGE;
|
||||
}
|
||||
if (prop.name == "fallback_image_quality") {
|
||||
const String image_format = get("image_format");
|
||||
const bool is_image_format_extension = image_format != "None" && image_format != "PNG" && image_format != "JPEG";
|
||||
const String fallback_format = get("fallback_image_format");
|
||||
prop.usage = (is_image_format_extension && fallback_format != "None") ? PROPERTY_USAGE_DEFAULT : PROPERTY_USAGE_STORAGE;
|
||||
}
|
||||
p_list->push_back(prop);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSceneExporterGLTFSettings::_on_extension_property_list_changed() {
|
||||
generate_property_list(_document);
|
||||
emit_signal(CoreStringName(property_list_changed));
|
||||
}
|
||||
|
||||
bool EditorSceneExporterGLTFSettings::_set_extension_setting(const String &p_name_str, const Variant &p_value) {
|
||||
PackedStringArray split = String(p_name_str).split("/", true, 1);
|
||||
if (!_config_name_to_extension_map.has(split[0])) {
|
||||
return false;
|
||||
}
|
||||
Ref<GLTFDocumentExtension> extension = _config_name_to_extension_map[split[0]];
|
||||
bool valid;
|
||||
extension->set(split[1], p_value, &valid);
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool EditorSceneExporterGLTFSettings::_get_extension_setting(const String &p_name_str, Variant &r_ret) const {
|
||||
PackedStringArray split = String(p_name_str).split("/", true, 1);
|
||||
if (!_config_name_to_extension_map.has(split[0])) {
|
||||
return false;
|
||||
}
|
||||
Ref<GLTFDocumentExtension> extension = _config_name_to_extension_map[split[0]];
|
||||
bool valid;
|
||||
r_ret = extension->get(split[1], &valid);
|
||||
return valid;
|
||||
}
|
||||
|
||||
String get_friendly_config_prefix(Ref<GLTFDocumentExtension> p_extension) {
|
||||
String config_prefix = p_extension->get_name();
|
||||
if (!config_prefix.is_empty()) {
|
||||
return config_prefix;
|
||||
}
|
||||
const String class_name = p_extension->get_class_name();
|
||||
config_prefix = class_name.trim_prefix("GLTFDocumentExtension").trim_suffix("GLTFDocumentExtension").capitalize();
|
||||
if (!config_prefix.is_empty()) {
|
||||
return config_prefix;
|
||||
}
|
||||
PackedStringArray supported_extensions = p_extension->get_supported_extensions();
|
||||
if (supported_extensions.size() > 0) {
|
||||
return supported_extensions[0];
|
||||
}
|
||||
return "Unknown GLTFDocumentExtension";
|
||||
}
|
||||
|
||||
bool is_any_node_invisible(Node *p_node) {
|
||||
if (p_node->has_method("is_visible")) {
|
||||
bool visible = p_node->call("is_visible");
|
||||
if (!visible) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < p_node->get_child_count(); i++) {
|
||||
if (is_any_node_invisible(p_node->get_child(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Run this before popping up the export settings, because the extensions may have changed.
|
||||
void EditorSceneExporterGLTFSettings::generate_property_list(Ref<GLTFDocument> p_document, Node *p_root) {
|
||||
_property_list.clear();
|
||||
_document = p_document;
|
||||
String image_format_hint_string = "None,PNG,JPEG";
|
||||
// Add properties from all document extensions.
|
||||
for (Ref<GLTFDocumentExtension> &extension : GLTFDocument::get_all_gltf_document_extensions()) {
|
||||
const Callable on_prop_changed = callable_mp(this, &EditorSceneExporterGLTFSettings::_on_extension_property_list_changed);
|
||||
if (!extension->is_connected(CoreStringName(property_list_changed), on_prop_changed)) {
|
||||
extension->connect(CoreStringName(property_list_changed), on_prop_changed);
|
||||
}
|
||||
const String config_prefix = get_friendly_config_prefix(extension);
|
||||
_config_name_to_extension_map[config_prefix] = extension;
|
||||
// If the extension allows saving in different image formats, add to the enum.
|
||||
PackedStringArray saveable_image_formats = extension->get_saveable_image_formats();
|
||||
for (int i = 0; i < saveable_image_formats.size(); i++) {
|
||||
image_format_hint_string += "," + saveable_image_formats[i];
|
||||
}
|
||||
// Look through the extension's properties and find the relevant ones.
|
||||
List<PropertyInfo> ext_prop_list;
|
||||
extension->get_property_list(&ext_prop_list);
|
||||
for (const PropertyInfo &prop : ext_prop_list) {
|
||||
// We only want properties that will show up in the exporter
|
||||
// settings list. Exclude Resource's properties, as they are
|
||||
// not relevant to the exporter. Include any user-defined script
|
||||
// variables exposed to the editor (PROP_EDITOR_SCRIPT_VAR).
|
||||
if ((prop.usage & PROP_EDITOR_SCRIPT_VAR) == PROP_EDITOR_SCRIPT_VAR) {
|
||||
PropertyInfo ext_prop = prop;
|
||||
ext_prop.name = config_prefix + "/" + prop.name;
|
||||
_property_list.push_back(ext_prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add top-level properties (in addition to what _bind_methods registers).
|
||||
PropertyInfo image_format_prop = PropertyInfo(Variant::STRING, "image_format", PROPERTY_HINT_ENUM, image_format_hint_string);
|
||||
_property_list.push_back(image_format_prop);
|
||||
PropertyInfo lossy_quality_prop = PropertyInfo(Variant::FLOAT, "lossy_quality", PROPERTY_HINT_RANGE, "0,1,0.01");
|
||||
_property_list.push_back(lossy_quality_prop);
|
||||
PropertyInfo fallback_image_format_prop = PropertyInfo(Variant::STRING, "fallback_image_format", PROPERTY_HINT_ENUM, "None,PNG,JPEG");
|
||||
_property_list.push_back(fallback_image_format_prop);
|
||||
PropertyInfo fallback_image_quality_prop = PropertyInfo(Variant::FLOAT, "fallback_image_quality", PROPERTY_HINT_RANGE, "0,1,0.01");
|
||||
_property_list.push_back(fallback_image_quality_prop);
|
||||
PropertyInfo root_node_mode_prop = PropertyInfo(Variant::INT, "root_node_mode", PROPERTY_HINT_ENUM, "Single Root,Keep Root,Multi Root");
|
||||
_property_list.push_back(root_node_mode_prop);
|
||||
// If the scene contains any non-visible nodes, show the visibility mode setting.
|
||||
if (p_root != nullptr && is_any_node_invisible(p_root)) {
|
||||
PropertyInfo visibility_mode_prop = PropertyInfo(Variant::INT, "visibility_mode", PROPERTY_HINT_ENUM, "Include & Required,Include & Optional,Exclude");
|
||||
_property_list.push_back(visibility_mode_prop);
|
||||
}
|
||||
}
|
||||
|
||||
String EditorSceneExporterGLTFSettings::get_copyright() const {
|
||||
return _copyright;
|
||||
}
|
||||
|
||||
void EditorSceneExporterGLTFSettings::set_copyright(const String &p_copyright) {
|
||||
_copyright = p_copyright;
|
||||
}
|
||||
|
||||
void EditorSceneExporterGLTFSettings::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_copyright"), &EditorSceneExporterGLTFSettings::get_copyright);
|
||||
ClassDB::bind_method(D_METHOD("set_copyright", "copyright"), &EditorSceneExporterGLTFSettings::set_copyright);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "copyright", PROPERTY_HINT_PLACEHOLDER_TEXT, "Example: 2014 Godette"), "set_copyright", "get_copyright");
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_bake_fps"), &EditorSceneExporterGLTFSettings::get_bake_fps);
|
||||
ClassDB::bind_method(D_METHOD("set_bake_fps", "bake_fps"), &EditorSceneExporterGLTFSettings::set_bake_fps);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_fps"), "set_bake_fps", "get_bake_fps");
|
||||
}
|
||||
|
||||
double EditorSceneExporterGLTFSettings::get_bake_fps() const {
|
||||
return _bake_fps;
|
||||
}
|
||||
|
||||
void EditorSceneExporterGLTFSettings::set_bake_fps(const double p_bake_fps) {
|
||||
_bake_fps = p_bake_fps;
|
||||
}
|
62
modules/gltf/editor/editor_scene_exporter_gltf_settings.h
Normal file
62
modules/gltf/editor/editor_scene_exporter_gltf_settings.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/**************************************************************************/
|
||||
/* editor_scene_exporter_gltf_settings.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../gltf_document.h"
|
||||
|
||||
class EditorSceneExporterGLTFSettings : public RefCounted {
|
||||
GDCLASS(EditorSceneExporterGLTFSettings, RefCounted);
|
||||
List<PropertyInfo> _property_list;
|
||||
Ref<GLTFDocument> _document;
|
||||
HashMap<String, Ref<GLTFDocumentExtension>> _config_name_to_extension_map;
|
||||
|
||||
String _copyright;
|
||||
double _bake_fps = 30.0;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
void _on_extension_property_list_changed();
|
||||
|
||||
bool _set_extension_setting(const String &p_name_str, const Variant &p_value);
|
||||
bool _get_extension_setting(const String &p_name_str, Variant &r_ret) const;
|
||||
|
||||
public:
|
||||
void generate_property_list(Ref<GLTFDocument> p_document, Node *p_root = nullptr);
|
||||
|
||||
String get_copyright() const;
|
||||
void set_copyright(const String &p_copyright);
|
||||
|
||||
double get_bake_fps() const;
|
||||
void set_bake_fps(const double p_bake_fps);
|
||||
};
|
615
modules/gltf/editor/editor_scene_importer_blend.cpp
Normal file
615
modules/gltf/editor/editor_scene_importer_blend.cpp
Normal file
@@ -0,0 +1,615 @@
|
||||
/**************************************************************************/
|
||||
/* editor_scene_importer_blend.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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_scene_importer_blend.h"
|
||||
|
||||
#include "../gltf_defines.h"
|
||||
#include "../gltf_document.h"
|
||||
#include "editor_import_blend_runner.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/gui/editor_file_dialog.h"
|
||||
#include "editor/settings/editor_settings.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "main/main.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
|
||||
#ifdef WINDOWS_ENABLED
|
||||
#include <shlwapi.h>
|
||||
#endif
|
||||
|
||||
static bool _get_blender_version(const String &p_path, int &r_major, int &r_minor, String *r_err = nullptr) {
|
||||
if (!FileAccess::exists(p_path)) {
|
||||
if (r_err) {
|
||||
*r_err = TTR("Path does not point to a valid executable.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
List<String> args;
|
||||
args.push_back("--version");
|
||||
String pipe;
|
||||
Error err = OS::get_singleton()->execute(p_path, args, &pipe);
|
||||
if (err != OK) {
|
||||
if (r_err) {
|
||||
*r_err = TTR("Couldn't run Blender executable.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
int bl = pipe.find("Blender ");
|
||||
if (bl == -1) {
|
||||
if (r_err) {
|
||||
*r_err = vformat(TTR("Unexpected --version output from Blender executable at: %s."), p_path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
pipe = pipe.substr(bl);
|
||||
pipe = pipe.replace_first("Blender ", "");
|
||||
int pp = pipe.find_char('.');
|
||||
if (pp == -1) {
|
||||
if (r_err) {
|
||||
*r_err = vformat(TTR("Couldn't extract version information from Blender executable at: %s."), p_path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
String v = pipe.substr(0, pp);
|
||||
r_major = v.to_int();
|
||||
if (r_major < 3) {
|
||||
if (r_err) {
|
||||
*r_err = vformat(TTR("Found Blender version %d.x, which is too old for this importer (3.0+ is required)."), r_major);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int pp2 = pipe.find_char('.', pp + 1);
|
||||
r_minor = pp2 > pp ? pipe.substr(pp + 1, pp2 - pp - 1).to_int() : 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EditorSceneFormatImporterBlend::get_extensions(List<String> *r_extensions) const {
|
||||
r_extensions->push_back("blend");
|
||||
}
|
||||
|
||||
Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_t p_flags,
|
||||
const HashMap<StringName, Variant> &p_options,
|
||||
List<String> *r_missing_deps, Error *r_err) {
|
||||
String blender_path = EDITOR_GET("filesystem/import/blender/blender_path");
|
||||
|
||||
ERR_FAIL_COND_V_MSG(blender_path.is_empty(), nullptr, "Blender path is empty, check your Editor Settings.");
|
||||
ERR_FAIL_COND_V_MSG(!FileAccess::exists(blender_path), nullptr, vformat("Invalid Blender path: %s, check your Editor Settings.", blender_path));
|
||||
|
||||
if (blender_major_version == -1 || blender_minor_version == -1 || last_tested_blender_path != blender_path) {
|
||||
String error;
|
||||
if (!_get_blender_version(blender_path, blender_major_version, blender_minor_version, &error)) {
|
||||
ERR_FAIL_V_MSG(nullptr, error);
|
||||
}
|
||||
last_tested_blender_path = blender_path;
|
||||
}
|
||||
|
||||
// Get global paths for source and sink.
|
||||
// Escape paths to be valid Python strings to embed in the script.
|
||||
String source_global = ProjectSettings::get_singleton()->globalize_path(p_path);
|
||||
|
||||
#ifdef WINDOWS_ENABLED
|
||||
// On Windows, when using a network share path, the above will return a path starting with "//"
|
||||
// which once handed to Blender will be treated like a relative path. So we need to replace the
|
||||
// first two characters with "\\" to make it absolute again.
|
||||
if (source_global.is_network_share_path()) {
|
||||
source_global = "\\\\" + source_global.substr(2);
|
||||
}
|
||||
#endif
|
||||
|
||||
const String blend_basename = p_path.get_file().get_basename();
|
||||
const String sink = ProjectSettings::get_singleton()->get_imported_files_path().path_join(
|
||||
vformat("%s-%s.gltf", blend_basename, p_path.md5_text()));
|
||||
const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink);
|
||||
// If true, unpack the original images to the Godot file system and use them. Allows changing image import settings like VRAM compression.
|
||||
// If false, allow Blender to convert the original images, such as re-packing roughness and metallic into one roughness+metallic texture.
|
||||
// In most cases this is desired, but if the .blend file's images are not in the correct format, this must be disabled for correct behavior.
|
||||
const bool unpack_original_images = p_options.has(SNAME("blender/materials/unpack_enabled")) && p_options[SNAME("blender/materials/unpack_enabled")];
|
||||
|
||||
// Handle configuration options.
|
||||
|
||||
Dictionary request_options;
|
||||
Dictionary parameters_map;
|
||||
|
||||
parameters_map["filepath"] = sink_global;
|
||||
parameters_map["export_keep_originals"] = unpack_original_images;
|
||||
parameters_map["export_format"] = "GLTF_SEPARATE";
|
||||
parameters_map["export_yup"] = true;
|
||||
parameters_map["export_import_convert_lighting_mode"] = "COMPAT";
|
||||
|
||||
if (p_options.has(SNAME("blender/nodes/custom_properties")) && p_options[SNAME("blender/nodes/custom_properties")]) {
|
||||
parameters_map["export_extras"] = true;
|
||||
} else {
|
||||
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_map["export_skins"] = false;
|
||||
} else if (skins == BLEND_BONE_INFLUENCES_COMPATIBLE) {
|
||||
parameters_map["export_skins"] = true;
|
||||
parameters_map["export_all_influences"] = false;
|
||||
} else if (skins == BLEND_BONE_INFLUENCES_ALL) {
|
||||
parameters_map["export_skins"] = true;
|
||||
parameters_map["export_all_influences"] = true;
|
||||
}
|
||||
} else {
|
||||
parameters_map["export_skins"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/materials/export_materials"))) {
|
||||
int32_t exports = p_options["blender/materials/export_materials"];
|
||||
switch (exports) {
|
||||
case BLEND_MATERIAL_EXPORT_PLACEHOLDER: {
|
||||
parameters_map["export_materials"] = "PLACEHOLDER";
|
||||
} break;
|
||||
case BLEND_MATERIAL_EXPORT_EXPORT: {
|
||||
parameters_map["export_materials"] = "EXPORT";
|
||||
} break;
|
||||
case BLEND_MATERIAL_EXPORT_NAMED_PLACEHOLDER: {
|
||||
parameters_map["export_materials"] = "EXPORT";
|
||||
parameters_map["export_image_format"] = "NONE";
|
||||
} break;
|
||||
}
|
||||
} else {
|
||||
parameters_map["export_materials"] = "PLACEHOLDER";
|
||||
}
|
||||
if (p_options.has(SNAME("blender/nodes/cameras")) && p_options[SNAME("blender/nodes/cameras")]) {
|
||||
parameters_map["export_cameras"] = true;
|
||||
} else {
|
||||
parameters_map["export_cameras"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/nodes/punctual_lights")) && p_options[SNAME("blender/nodes/punctual_lights")]) {
|
||||
parameters_map["export_lights"] = true;
|
||||
} else {
|
||||
parameters_map["export_lights"] = false;
|
||||
}
|
||||
if (blender_major_version > 4 || (blender_major_version == 4 && blender_minor_version >= 2)) {
|
||||
if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) {
|
||||
parameters_map["export_vertex_color"] = "MATERIAL";
|
||||
} else {
|
||||
parameters_map["export_vertex_color"] = "NONE";
|
||||
}
|
||||
} else {
|
||||
if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) {
|
||||
parameters_map["export_colors"] = true;
|
||||
} else {
|
||||
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_map["use_visible"] = true;
|
||||
} else if (visible == BLEND_VISIBLE_RENDERABLE) {
|
||||
parameters_map["use_renderable"] = true;
|
||||
} else if (visible == BLEND_VISIBLE_ALL) {
|
||||
parameters_map["use_renderable"] = false;
|
||||
parameters_map["use_visible"] = false;
|
||||
}
|
||||
} else {
|
||||
parameters_map["use_renderable"] = false;
|
||||
parameters_map["use_visible"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/nodes/active_collection_only")) && p_options[SNAME("blender/nodes/active_collection_only")]) {
|
||||
parameters_map["use_active_collection"] = true;
|
||||
}
|
||||
|
||||
if (p_options.has(SNAME("blender/meshes/uvs")) && p_options[SNAME("blender/meshes/uvs")]) {
|
||||
parameters_map["export_texcoords"] = true;
|
||||
} else {
|
||||
parameters_map["export_texcoords"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/meshes/normals")) && p_options[SNAME("blender/meshes/normals")]) {
|
||||
parameters_map["export_normals"] = true;
|
||||
} else {
|
||||
parameters_map["export_normals"] = false;
|
||||
}
|
||||
|
||||
if (blender_major_version > 4 || (blender_major_version == 4 && blender_minor_version >= 1)) {
|
||||
if (p_options.has(SNAME("blender/meshes/export_geometry_nodes_instances")) && p_options[SNAME("blender/meshes/export_geometry_nodes_instances")]) {
|
||||
parameters_map["export_gn_mesh"] = true;
|
||||
if (blender_major_version == 4 && blender_minor_version == 1) {
|
||||
// There is a bug in Blender 4.1 where it can't export lights and geometry nodes at the same time, one must be disabled.
|
||||
parameters_map["export_lights"] = false;
|
||||
}
|
||||
} else {
|
||||
parameters_map["export_gn_mesh"] = false;
|
||||
}
|
||||
}
|
||||
if (p_options.has(SNAME("blender/meshes/tangents")) && p_options[SNAME("blender/meshes/tangents")]) {
|
||||
parameters_map["export_tangents"] = true;
|
||||
} else {
|
||||
parameters_map["export_tangents"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/animation/group_tracks")) && p_options[SNAME("blender/animation/group_tracks")]) {
|
||||
if (blender_major_version > 3 || (blender_major_version == 3 && blender_minor_version >= 6)) {
|
||||
parameters_map["export_animation_mode"] = "ACTIONS";
|
||||
} else {
|
||||
parameters_map["export_nla_strips"] = true;
|
||||
}
|
||||
} else {
|
||||
if (blender_major_version > 3 || (blender_major_version == 3 && blender_minor_version >= 6)) {
|
||||
parameters_map["export_animation_mode"] = "ACTIVE_ACTIONS";
|
||||
} else {
|
||||
parameters_map["export_nla_strips"] = false;
|
||||
}
|
||||
}
|
||||
if (p_options.has(SNAME("blender/animation/limit_playback")) && p_options[SNAME("blender/animation/limit_playback")]) {
|
||||
parameters_map["export_frame_range"] = true;
|
||||
} else {
|
||||
parameters_map["export_frame_range"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/animation/always_sample")) && p_options[SNAME("blender/animation/always_sample")]) {
|
||||
parameters_map["export_force_sampling"] = true;
|
||||
} else {
|
||||
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_map["export_def_bones"] = true;
|
||||
} else {
|
||||
parameters_map["export_def_bones"] = false;
|
||||
}
|
||||
if (p_options.has(SNAME("blender/nodes/modifiers")) && p_options[SNAME("blender/nodes/modifiers")]) {
|
||||
parameters_map["export_apply"] = true;
|
||||
} else {
|
||||
parameters_map["export_apply"] = false;
|
||||
}
|
||||
|
||||
request_options["unpack_all"] = unpack_original_images;
|
||||
request_options["path"] = source_global;
|
||||
request_options["gltf_options"] = parameters_map;
|
||||
|
||||
// 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;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Import the generated glTF.
|
||||
|
||||
// Use GLTFDocument instead of glTF importer to keep image references.
|
||||
Ref<GLTFDocument> gltf;
|
||||
gltf.instantiate();
|
||||
Ref<GLTFState> state;
|
||||
state.instantiate();
|
||||
|
||||
if (p_options.has("gltf/naming_version")) {
|
||||
int naming_version = p_options["gltf/naming_version"];
|
||||
gltf->set_naming_version(naming_version);
|
||||
}
|
||||
if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) {
|
||||
state->set_import_as_skeleton_bones(true);
|
||||
}
|
||||
state->set_scene_name(blend_basename);
|
||||
state->set_extract_path(p_path.get_base_dir());
|
||||
state->set_extract_prefix(blend_basename);
|
||||
err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, sink.get_base_dir());
|
||||
if (err != OK) {
|
||||
if (r_err) {
|
||||
*r_err = FAILED;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
ERR_FAIL_COND_V(!p_options.has("animation/fps"), nullptr);
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
bool trimming = p_options.has("animation/trimming") ? (bool)p_options["animation/trimming"] : false;
|
||||
return gltf->generate_scene(state, (float)p_options["animation/fps"], trimming, false);
|
||||
#else
|
||||
return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], false);
|
||||
#endif
|
||||
}
|
||||
|
||||
Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option,
|
||||
const HashMap<StringName, Variant> &p_options) {
|
||||
if (p_path.get_extension().to_lower() != "blend") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_option.begins_with("animation/")) {
|
||||
if (p_option != "animation/import" && !bool(p_options["animation/import"])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) {
|
||||
// Returns all the options when path is empty because that means it's for the Project Settings.
|
||||
if (!p_path.is_empty() && p_path.get_extension().to_lower() != "blend") {
|
||||
return;
|
||||
}
|
||||
#define ADD_OPTION_BOOL(PATH, VALUE) \
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, SNAME(PATH)), VALUE));
|
||||
#define ADD_OPTION_ENUM(PATH, ENUM_HINT, VALUE) \
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, SNAME(PATH), PROPERTY_HINT_ENUM, ENUM_HINT), VALUE));
|
||||
|
||||
ADD_OPTION_ENUM("blender/nodes/visible", "All,Visible Only,Renderable", BLEND_VISIBLE_ALL);
|
||||
ADD_OPTION_BOOL("blender/nodes/active_collection_only", false);
|
||||
ADD_OPTION_BOOL("blender/nodes/punctual_lights", true);
|
||||
ADD_OPTION_BOOL("blender/nodes/cameras", true);
|
||||
ADD_OPTION_BOOL("blender/nodes/custom_properties", true);
|
||||
ADD_OPTION_ENUM("blender/nodes/modifiers", "No Modifiers,All Modifiers", BLEND_MODIFIERS_ALL);
|
||||
ADD_OPTION_BOOL("blender/meshes/colors", false);
|
||||
ADD_OPTION_BOOL("blender/meshes/uvs", true);
|
||||
ADD_OPTION_BOOL("blender/meshes/normals", true);
|
||||
ADD_OPTION_BOOL("blender/meshes/export_geometry_nodes_instances", false);
|
||||
ADD_OPTION_BOOL("blender/meshes/tangents", true);
|
||||
ADD_OPTION_ENUM("blender/meshes/skins", "None,4 Influences (Compatible),All Influences", BLEND_BONE_INFLUENCES_ALL);
|
||||
ADD_OPTION_BOOL("blender/meshes/export_bones_deforming_mesh_only", false);
|
||||
ADD_OPTION_BOOL("blender/materials/unpack_enabled", true);
|
||||
ADD_OPTION_ENUM("blender/materials/export_materials", "Placeholder,Export,Named Placeholder", BLEND_MATERIAL_EXPORT_EXPORT);
|
||||
ADD_OPTION_BOOL("blender/animation/limit_playback", true);
|
||||
ADD_OPTION_BOOL("blender/animation/always_sample", true);
|
||||
ADD_OPTION_BOOL("blender/animation/group_tracks", true);
|
||||
|
||||
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/naming_version", PROPERTY_HINT_ENUM, "Godot 4.0 or 4.1,Godot 4.2 to 4.4,Godot 4.5 or later"), 2));
|
||||
}
|
||||
|
||||
void EditorSceneFormatImporterBlend::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {
|
||||
if (!p_import_params.has("gltf/naming_version")) {
|
||||
// If a .blend's existing import file is missing the glTF
|
||||
// naming compatibility version, we need to use version 1.
|
||||
// Version 1 is the behavior before this option was added.
|
||||
p_import_params["gltf/naming_version"] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////
|
||||
|
||||
static bool _test_blender_path(const String &p_path, String *r_err = nullptr) {
|
||||
int major, minor;
|
||||
return _get_blender_version(p_path, major, minor, r_err);
|
||||
}
|
||||
|
||||
bool EditorFileSystemImportFormatSupportQueryBlend::is_active() const {
|
||||
bool blend_enabled = GLOBAL_GET_CACHED(bool, "filesystem/import/blender/enabled");
|
||||
|
||||
if (blend_enabled && !_test_blender_path(EDITOR_GET("filesystem/import/blender/blender_path").operator String())) {
|
||||
// Intending to import Blender, but blend not configured.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
Vector<String> EditorFileSystemImportFormatSupportQueryBlend::get_file_extensions() const {
|
||||
Vector<String> ret;
|
||||
ret.push_back("blend");
|
||||
return ret;
|
||||
}
|
||||
|
||||
void EditorFileSystemImportFormatSupportQueryBlend::_validate_path(String p_path) {
|
||||
String error;
|
||||
bool success = false;
|
||||
if (p_path == "") {
|
||||
error = TTR("Path is empty.");
|
||||
} else {
|
||||
if (_test_blender_path(p_path, &error)) {
|
||||
success = true;
|
||||
if (auto_detected_path == p_path) {
|
||||
error = TTR("Path to Blender executable is valid (Autodetected).");
|
||||
} else {
|
||||
error = TTR("Path to Blender executable is valid.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
path_status->set_text(error);
|
||||
|
||||
if (success) {
|
||||
path_status->add_theme_color_override(SceneStringName(font_color), path_status->get_theme_color(SNAME("success_color"), EditorStringName(Editor)));
|
||||
configure_blender_dialog->get_ok_button()->set_disabled(false);
|
||||
} else {
|
||||
path_status->add_theme_color_override(SceneStringName(font_color), path_status->get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
|
||||
configure_blender_dialog->get_ok_button()->set_disabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorFileSystemImportFormatSupportQueryBlend::_autodetect_path() {
|
||||
// Autodetect
|
||||
auto_detected_path = "";
|
||||
|
||||
#if defined(MACOS_ENABLED)
|
||||
Vector<String> find_paths = {
|
||||
"/opt/homebrew/bin/blender",
|
||||
"/opt/local/bin/blender",
|
||||
"/usr/local/bin/blender",
|
||||
"/usr/local/opt/blender",
|
||||
"/Applications/Blender.app/Contents/MacOS/Blender",
|
||||
};
|
||||
{
|
||||
List<String> mdfind_args;
|
||||
mdfind_args.push_back("kMDItemCFBundleIdentifier=org.blenderfoundation.blender");
|
||||
|
||||
String output;
|
||||
Error err = OS::get_singleton()->execute("mdfind", mdfind_args, &output);
|
||||
if (err == OK) {
|
||||
for (const String &find_path : output.split("\n")) {
|
||||
find_paths.push_back(find_path.path_join("Contents/MacOS/Blender"));
|
||||
}
|
||||
}
|
||||
}
|
||||
#elif defined(WINDOWS_ENABLED)
|
||||
Vector<String> find_paths = {
|
||||
"C:\\Program Files\\Blender Foundation\\blender.exe",
|
||||
"C:\\Program Files (x86)\\Blender Foundation\\blender.exe",
|
||||
};
|
||||
{
|
||||
char blender_opener_path[MAX_PATH];
|
||||
DWORD path_len = MAX_PATH;
|
||||
HRESULT res = AssocQueryString(0, ASSOCSTR_EXECUTABLE, ".blend", "open", blender_opener_path, &path_len);
|
||||
if (res == S_OK) {
|
||||
find_paths.push_back(String(blender_opener_path).get_base_dir().path_join("blender.exe"));
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(UNIX_ENABLED)
|
||||
Vector<String> find_paths = {
|
||||
"/usr/bin/blender",
|
||||
"/usr/local/bin/blender",
|
||||
"/opt/blender/bin/blender",
|
||||
};
|
||||
#endif
|
||||
|
||||
for (const String &find_path : find_paths) {
|
||||
if (_test_blender_path(find_path)) {
|
||||
auto_detected_path = find_path;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void EditorFileSystemImportFormatSupportQueryBlend::_path_confirmed() {
|
||||
confirmed = true;
|
||||
}
|
||||
|
||||
void EditorFileSystemImportFormatSupportQueryBlend::_select_install(String p_path) {
|
||||
blender_path->set_text(p_path);
|
||||
_validate_path(p_path);
|
||||
}
|
||||
void EditorFileSystemImportFormatSupportQueryBlend::_browse_install() {
|
||||
if (blender_path->get_text() != String()) {
|
||||
browse_dialog->set_current_file(blender_path->get_text());
|
||||
}
|
||||
|
||||
browse_dialog->popup_centered_ratio();
|
||||
}
|
||||
|
||||
void EditorFileSystemImportFormatSupportQueryBlend::_update_icons() {
|
||||
blender_path_browse->set_button_icon(blender_path_browse->get_editor_theme_icon(SNAME("FolderBrowse")));
|
||||
}
|
||||
|
||||
bool EditorFileSystemImportFormatSupportQueryBlend::query() {
|
||||
if (!configure_blender_dialog) {
|
||||
configure_blender_dialog = memnew(ConfirmationDialog);
|
||||
configure_blender_dialog->set_title(TTR("Configure Blender Importer"));
|
||||
configure_blender_dialog->set_flag(Window::FLAG_BORDERLESS, true); // Avoid closing accidentally.
|
||||
configure_blender_dialog->set_close_on_escape(false);
|
||||
|
||||
String select_exec_label = TTR("Blender 3.0+ is required to import '.blend' files.\nPlease provide a valid path to a Blender executable.");
|
||||
#ifdef MACOS_ENABLED
|
||||
select_exec_label += "\n" + TTR("On macOS, this should be the `Contents/MacOS/blender` file within the Blender `.app` folder.");
|
||||
#endif
|
||||
VBoxContainer *vb = memnew(VBoxContainer);
|
||||
vb->add_child(memnew(Label(select_exec_label)));
|
||||
|
||||
HBoxContainer *hb = memnew(HBoxContainer);
|
||||
|
||||
blender_path = memnew(LineEdit);
|
||||
blender_path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
blender_path->set_accessibility_name(TTRC("Path"));
|
||||
hb->add_child(blender_path);
|
||||
|
||||
blender_path_browse = memnew(Button);
|
||||
blender_path_browse->set_text(TTR("Browse"));
|
||||
blender_path_browse->connect(SceneStringName(pressed), callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_browse_install));
|
||||
hb->add_child(blender_path_browse);
|
||||
|
||||
hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
hb->set_custom_minimum_size(Size2(400 * EDSCALE, 0));
|
||||
|
||||
vb->add_child(hb);
|
||||
|
||||
path_status = memnew(Label);
|
||||
path_status->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
|
||||
vb->add_child(path_status);
|
||||
|
||||
configure_blender_dialog->add_child(vb);
|
||||
|
||||
blender_path->connect(SceneStringName(text_changed), callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_validate_path));
|
||||
|
||||
EditorNode::get_singleton()->get_gui_base()->add_child(configure_blender_dialog);
|
||||
|
||||
configure_blender_dialog->set_ok_button_text(TTR("Confirm Path"));
|
||||
configure_blender_dialog->set_cancel_button_text(TTR("Disable '.blend' Import"));
|
||||
configure_blender_dialog->get_cancel_button()->set_tooltip_text(TTR("Disables Blender '.blend' files import for this project. Can be re-enabled in Project Settings."));
|
||||
configure_blender_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_path_confirmed));
|
||||
|
||||
browse_dialog = memnew(EditorFileDialog);
|
||||
browse_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
|
||||
browse_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
|
||||
browse_dialog->connect("file_selected", callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_select_install));
|
||||
|
||||
EditorNode::get_singleton()->get_gui_base()->add_child(browse_dialog);
|
||||
|
||||
// Update icons.
|
||||
// This is a hack because we can't rely on notifications here as we don't receive them.
|
||||
// Usually, we only have to wait for `NOTIFICATION_THEME_CHANGED` to update the icons.
|
||||
callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_update_icons).call_deferred();
|
||||
}
|
||||
|
||||
String path = EDITOR_GET("filesystem/import/blender/blender_path");
|
||||
|
||||
if (path.is_empty() && _autodetect_path()) {
|
||||
path = auto_detected_path;
|
||||
}
|
||||
|
||||
blender_path->set_text(path);
|
||||
|
||||
_validate_path(path);
|
||||
|
||||
configure_blender_dialog->popup_centered();
|
||||
confirmed = false;
|
||||
|
||||
while (true) {
|
||||
DisplayServer::get_singleton()->process_events();
|
||||
Main::iteration();
|
||||
if (!configure_blender_dialog->is_visible() || confirmed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (confirmed) {
|
||||
// Can only confirm a valid path.
|
||||
EditorSettings::get_singleton()->set("filesystem/import/blender/blender_path", blender_path->get_text());
|
||||
EditorSettings::get_singleton()->save();
|
||||
} else {
|
||||
// Disable Blender import
|
||||
ProjectSettings::get_singleton()->set("filesystem/import/blender/enabled", false);
|
||||
ProjectSettings::get_singleton()->save();
|
||||
|
||||
if (EditorNode::immediate_confirmation_dialog(TTR("Disabling '.blend' file import requires restarting the editor."), TTR("Save & Restart"), TTR("Restart"))) {
|
||||
EditorNode::get_singleton()->save_all_scenes();
|
||||
}
|
||||
EditorNode::get_singleton()->restart_editor();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
110
modules/gltf/editor/editor_scene_importer_blend.h
Normal file
110
modules/gltf/editor/editor_scene_importer_blend.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/**************************************************************************/
|
||||
/* editor_scene_importer_blend.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/file_system/editor_file_system.h"
|
||||
#include "editor/import/3d/resource_importer_scene.h"
|
||||
|
||||
class Animation;
|
||||
class Node;
|
||||
class ConfirmationDialog;
|
||||
|
||||
class EditorSceneFormatImporterBlend : public EditorSceneFormatImporter {
|
||||
GDCLASS(EditorSceneFormatImporterBlend, EditorSceneFormatImporter);
|
||||
|
||||
int blender_major_version = -1;
|
||||
int blender_minor_version = -1;
|
||||
String last_tested_blender_path;
|
||||
|
||||
public:
|
||||
enum {
|
||||
BLEND_VISIBLE_ALL,
|
||||
BLEND_VISIBLE_VISIBLE_ONLY,
|
||||
BLEND_VISIBLE_RENDERABLE
|
||||
};
|
||||
enum {
|
||||
BLEND_BONE_INFLUENCES_NONE,
|
||||
BLEND_BONE_INFLUENCES_COMPATIBLE,
|
||||
BLEND_BONE_INFLUENCES_ALL
|
||||
};
|
||||
enum {
|
||||
BLEND_MATERIAL_EXPORT_PLACEHOLDER,
|
||||
BLEND_MATERIAL_EXPORT_EXPORT,
|
||||
BLEND_MATERIAL_EXPORT_NAMED_PLACEHOLDER,
|
||||
};
|
||||
enum {
|
||||
BLEND_MODIFIERS_NONE,
|
||||
BLEND_MODIFIERS_ALL
|
||||
};
|
||||
|
||||
virtual void get_extensions(List<String> *r_extensions) const override;
|
||||
virtual Node *import_scene(const String &p_path, uint32_t p_flags,
|
||||
const HashMap<StringName, Variant> &p_options,
|
||||
List<String> *r_missing_deps, Error *r_err = nullptr) override;
|
||||
virtual void get_import_options(const String &p_path,
|
||||
List<ResourceImporter::ImportOption> *r_options) override;
|
||||
virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option,
|
||||
const HashMap<StringName, Variant> &p_options) override;
|
||||
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
|
||||
};
|
||||
|
||||
class LineEdit;
|
||||
class Button;
|
||||
class EditorFileDialog;
|
||||
class Label;
|
||||
|
||||
class EditorFileSystemImportFormatSupportQueryBlend : public EditorFileSystemImportFormatSupportQuery {
|
||||
GDCLASS(EditorFileSystemImportFormatSupportQueryBlend, EditorFileSystemImportFormatSupportQuery);
|
||||
|
||||
ConfirmationDialog *configure_blender_dialog = nullptr;
|
||||
LineEdit *blender_path = nullptr;
|
||||
Button *blender_path_browse = nullptr;
|
||||
EditorFileDialog *browse_dialog = nullptr;
|
||||
Label *path_status = nullptr;
|
||||
bool confirmed = false;
|
||||
|
||||
String auto_detected_path;
|
||||
void _validate_path(String p_path);
|
||||
|
||||
bool _autodetect_path();
|
||||
|
||||
void _path_confirmed();
|
||||
|
||||
void _select_install(String p_path);
|
||||
void _browse_install();
|
||||
|
||||
void _update_icons();
|
||||
|
||||
public:
|
||||
virtual bool is_active() const override;
|
||||
virtual Vector<String> get_file_extensions() const override;
|
||||
virtual bool query() override;
|
||||
};
|
103
modules/gltf/editor/editor_scene_importer_gltf.cpp
Normal file
103
modules/gltf/editor/editor_scene_importer_gltf.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
/**************************************************************************/
|
||||
/* editor_scene_importer_gltf.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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_scene_importer_gltf.h"
|
||||
|
||||
#include "../gltf_defines.h"
|
||||
#include "../gltf_document.h"
|
||||
|
||||
void EditorSceneFormatImporterGLTF::get_extensions(List<String> *r_extensions) const {
|
||||
r_extensions->push_back("gltf");
|
||||
r_extensions->push_back("glb");
|
||||
}
|
||||
|
||||
Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t p_flags,
|
||||
const HashMap<StringName, Variant> &p_options,
|
||||
List<String> *r_missing_deps, Error *r_err) {
|
||||
Ref<GLTFDocument> gltf;
|
||||
gltf.instantiate();
|
||||
Ref<GLTFState> state;
|
||||
state.instantiate();
|
||||
if (p_options.has("gltf/naming_version")) {
|
||||
int naming_version = p_options["gltf/naming_version"];
|
||||
gltf->set_naming_version(naming_version);
|
||||
}
|
||||
if (p_options.has("gltf/embedded_image_handling")) {
|
||||
int32_t enum_option = p_options["gltf/embedded_image_handling"];
|
||||
state->set_handle_binary_image(enum_option);
|
||||
}
|
||||
if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) {
|
||||
state->set_import_as_skeleton_bones(true);
|
||||
}
|
||||
if (p_options.has(SNAME("extract_path"))) {
|
||||
state->set_extract_path(p_options["extract_path"]);
|
||||
}
|
||||
state->set_bake_fps(p_options["animation/fps"]);
|
||||
Error err = gltf->append_from_file(p_path, state, p_flags);
|
||||
if (err != OK) {
|
||||
if (r_err) {
|
||||
*r_err = err;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
if (p_options.has("animation/import")) {
|
||||
state->set_create_animations(bool(p_options["animation/import"]));
|
||||
}
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
bool trimming = p_options.has("animation/trimming") ? (bool)p_options["animation/trimming"] : false;
|
||||
return gltf->generate_scene(state, state->get_bake_fps(), trimming, false);
|
||||
#else
|
||||
return gltf->generate_scene(state, state->get_bake_fps(), (bool)p_options["animation/trimming"], false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void EditorSceneFormatImporterGLTF::get_import_options(const String &p_path,
|
||||
List<ResourceImporter::ImportOption> *r_options) {
|
||||
String file_extension = p_path.get_extension().to_lower();
|
||||
// Returns all the options when path is empty because that means it's for the Project Settings.
|
||||
if (p_path.is_empty() || file_extension == "gltf" || file_extension == "glb") {
|
||||
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/naming_version", PROPERTY_HINT_ENUM, "Godot 4.0 or 4.1,Godot 4.2 to 4.4,Godot 4.5 or later"), 2));
|
||||
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFState::HANDLE_BINARY_EXTRACT_TEXTURES));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSceneFormatImporterGLTF::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {
|
||||
if (!p_import_params.has("gltf/naming_version")) {
|
||||
// If an existing import file is missing the glTF
|
||||
// compatibility version, we need to use version 0.
|
||||
p_import_params["gltf/naming_version"] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Variant EditorSceneFormatImporterGLTF::get_option_visibility(const String &p_path, const String &p_scene_import_type,
|
||||
const String &p_option, const HashMap<StringName, Variant> &p_options) {
|
||||
return true;
|
||||
}
|
51
modules/gltf/editor/editor_scene_importer_gltf.h
Normal file
51
modules/gltf/editor/editor_scene_importer_gltf.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/**************************************************************************/
|
||||
/* editor_scene_importer_gltf.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/import/3d/resource_importer_scene.h"
|
||||
|
||||
class Animation;
|
||||
class Node;
|
||||
|
||||
class EditorSceneFormatImporterGLTF : public EditorSceneFormatImporter {
|
||||
GDCLASS(EditorSceneFormatImporterGLTF, EditorSceneFormatImporter);
|
||||
|
||||
public:
|
||||
virtual void get_extensions(List<String> *r_extensions) const override;
|
||||
virtual Node *import_scene(const String &p_path, uint32_t p_flags,
|
||||
const HashMap<StringName, Variant> &p_options,
|
||||
List<String> *r_missing_deps, Error *r_err = nullptr) override;
|
||||
virtual void get_import_options(const String &p_path,
|
||||
List<ResourceImporter::ImportOption> *r_options) override;
|
||||
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
|
||||
virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type,
|
||||
const String &p_option, const HashMap<StringName, Variant> &p_options) override;
|
||||
};
|
Reference in New Issue
Block a user