summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml13
-rw-r--r--modules/gdscript/gdscript.cpp96
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp180
-rw-r--r--modules/gdscript/gdscript_analyzer.h11
-rw-r--r--modules/gdscript/gdscript_compiler.cpp7
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp6
-rw-r--r--modules/gdscript/gdscript_parser.cpp41
-rw-r--r--modules/gdscript/gdscript_parser.h3
-rw-r--r--modules/gdscript/gdscript_utility_functions.cpp23
-rw-r--r--modules/gdscript/gdscript_warning.cpp12
-rw-r--r--modules/gdscript/gdscript_warning.h2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.gd20
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.out10
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd2
-rw-r--r--modules/gltf/doc_classes/GLTFDocumentExtension.xml16
-rw-r--r--modules/gltf/doc_classes/GLTFState.xml6
-rw-r--r--modules/gltf/editor/editor_import_blend_runner.cpp314
-rw-r--r--modules/gltf/editor/editor_import_blend_runner.h69
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp131
-rw-r--r--modules/gltf/editor/editor_scene_importer_gltf.cpp6
-rw-r--r--modules/gltf/extensions/gltf_document_extension.cpp32
-rw-r--r--modules/gltf/extensions/gltf_document_extension.h16
-rw-r--r--modules/gltf/gltf_document.cpp98
-rw-r--r--modules/gltf/gltf_state.cpp3
-rw-r--r--modules/gltf/gltf_state.h1
-rw-r--r--modules/gltf/register_types.cpp11
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rid.cs76
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs18
-rw-r--r--modules/navigation/godot_navigation_server.cpp34
-rw-r--r--modules/navigation/godot_navigation_server.h4
-rw-r--r--modules/navigation/nav_agent.cpp (renamed from modules/navigation/rvo_agent.cpp)14
-rw-r--r--modules/navigation/nav_agent.h (renamed from modules/navigation/rvo_agent.h)10
-rw-r--r--modules/navigation/nav_map.cpp18
-rw-r--r--modules/navigation/nav_map.h20
-rw-r--r--modules/webrtc/webrtc_multiplayer_peer.cpp1
-rw-r--r--modules/websocket/websocket_multiplayer_peer.cpp1
39 files changed, 1050 insertions, 285 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 7f9d4ae253..32acad76aa 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -227,18 +227,6 @@
[/codeblock]
</description>
</method>
- <method name="str" qualifiers="vararg">
- <return type="String" />
- <description>
- Converts one or more arguments to a [String] in the best way possible.
- [codeblock]
- var a = [10, 20, 30]
- var b = str(a);
- len(a) # Returns 3
- len(b) # Returns 12
- [/codeblock]
- </description>
- </method>
<method name="type_exists">
<return type="bool" />
<param index="0" name="type" type="StringName" />
@@ -563,6 +551,7 @@
[/codeblock]
[b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported.
[b]Note:[/b] As annotations describe their subject, the [code]@icon[/code] annotation must be placed before the class definition and inheritance.
+ [b]Note:[/b] Unlike other annotations, the argument of the [code]@icon[/code] annotation must be a string literal (constant expressions are not supported).
</description>
</annotation>
<annotation name="@onready">
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index a8667e9cec..8324cb0fe0 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -2437,21 +2437,25 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
GDScriptParser parser;
err = parser.parse(source, p_path, false);
- if (err) {
- return String();
- }
-
- GDScriptAnalyzer analyzer(&parser);
- err = analyzer.resolve_inheritance();
- if (err) {
- return String();
- }
const GDScriptParser::ClassNode *c = parser.get_tree();
-
- if (r_base_type) {
- *r_base_type = c->get_datatype().native_type;
- }
+ if (!c) {
+ return String(); // No class parsed.
+ }
+
+ /* **WARNING**
+ *
+ * This function is written with the goal to be *extremely* error tolerant, as such
+ * it should meet the following requirements:
+ *
+ * - It must not rely on the analyzer (in fact, the analyzer must not be used here),
+ * because at the time global classes are parsed, the dependencies may not be present
+ * yet, hence the function will fail (which is unintended).
+ * - It must not fail even if the parsing fails, because even if the file is broken,
+ * it should attempt its best to retrieve the inheritance metadata.
+ *
+ * Before changing this function, please ask the current maintainer of EditorFileSystem.
+ */
if (r_icon_path) {
if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) {
@@ -2460,7 +2464,73 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
*r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path();
}
}
+ if (r_base_type) {
+ const GDScriptParser::ClassNode *subclass = c;
+ String path = p_path;
+ GDScriptParser subparser;
+ while (subclass) {
+ if (subclass->extends_used) {
+ if (!subclass->extends_path.is_empty()) {
+ if (subclass->extends.size() == 0) {
+ get_global_class_name(subclass->extends_path, r_base_type);
+ subclass = nullptr;
+ break;
+ } else {
+ Vector<StringName> extend_classes = subclass->extends;
+
+ Ref<FileAccess> subfile = FileAccess::open(subclass->extends_path, FileAccess::READ);
+ if (subfile.is_null()) {
+ break;
+ }
+ String subsource = subfile->get_as_utf8_string();
+
+ if (subsource.is_empty()) {
+ break;
+ }
+ String subpath = subclass->extends_path;
+ if (subpath.is_relative_path()) {
+ subpath = path.get_base_dir().path_join(subpath).simplify_path();
+ }
+ if (OK != subparser.parse(subsource, subpath, false)) {
+ break;
+ }
+ path = subpath;
+ subclass = subparser.get_tree();
+
+ while (extend_classes.size() > 0) {
+ bool found = false;
+ for (int i = 0; i < subclass->members.size(); i++) {
+ if (subclass->members[i].type != GDScriptParser::ClassNode::Member::CLASS) {
+ continue;
+ }
+
+ const GDScriptParser::ClassNode *inner_class = subclass->members[i].m_class;
+ if (inner_class->identifier->name == extend_classes[0]) {
+ extend_classes.remove_at(0);
+ found = true;
+ subclass = inner_class;
+ break;
+ }
+ }
+ if (!found) {
+ subclass = nullptr;
+ break;
+ }
+ }
+ }
+ } else if (subclass->extends.size() == 1) {
+ *r_base_type = subclass->extends[0];
+ subclass = nullptr;
+ } else {
+ break;
+ }
+ } else {
+ *r_base_type = "RefCounted";
+ subclass = nullptr;
+ }
+ }
+ }
return c->identifier != nullptr ? String(c->identifier->name) : String();
}
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index f788236af3..cdb4f29854 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -1972,17 +1972,40 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
}
if (p_return->return_value != nullptr) {
- reduce_expression(p_return->return_value);
- if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type()) {
- update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type());
- }
- if (has_expected_type && expected_type.is_hard_type() && expected_type.kind == GDScriptParser::DataType::BUILTIN && expected_type.builtin_type == Variant::NIL) {
- push_error("A void function cannot return a value.", p_return);
- }
- if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) {
- update_const_expression_builtin_type(p_return->return_value, expected_type, "return");
+ bool is_void_function = has_expected_type && expected_type.is_hard_type() && expected_type.kind == GDScriptParser::DataType::BUILTIN && expected_type.builtin_type == Variant::NIL;
+ bool is_call = p_return->return_value->type == GDScriptParser::Node::CALL;
+ if (is_void_function && is_call) {
+ // Pretend the call is a root expression to allow those that are "void".
+ reduce_call(static_cast<GDScriptParser::CallNode *>(p_return->return_value), false, true);
+ } else {
+ reduce_expression(p_return->return_value);
+ }
+ if (is_void_function) {
+ p_return->void_return = true;
+ const GDScriptParser::DataType &return_type = p_return->return_value->datatype;
+ if (is_call && !return_type.is_hard_type()) {
+ String function_name = parser->current_function->identifier ? parser->current_function->identifier->name.operator String() : String("<anonymous function>");
+ String called_function_name = static_cast<GDScriptParser::CallNode *>(p_return->return_value)->function_name.operator String();
+#ifdef DEBUG_ENABLED
+ parser->push_warning(p_return, GDScriptWarning::UNSAFE_VOID_RETURN, function_name, called_function_name);
+#endif
+ mark_node_unsafe(p_return);
+ } else if (!is_call) {
+ push_error("A void function cannot return a value.", p_return);
+ }
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = Variant::NIL;
+ result.is_constant = true;
+ } else {
+ if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type()) {
+ update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type());
+ }
+ if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) {
+ update_const_expression_builtin_type(p_return->return_value, expected_type, "return");
+ }
+ result = p_return->return_value->get_datatype();
}
- result = p_return->return_value->get_datatype();
} else {
// Return type is null by default.
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
@@ -2464,6 +2487,62 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
p_binary_op->set_datatype(result);
}
+#ifdef TOOLS_ENABLED
+#ifndef DISABLE_DEPRECATED
+const char *GDScriptAnalyzer::get_rename_from_map(const char *map[][2], String key) {
+ for (int index = 0; map[index][0]; index++) {
+ if (map[index][0] == key) {
+ return map[index][1];
+ }
+ }
+ return nullptr;
+}
+
+// Checks if an identifier/function name has been renamed in Godot 4, uses ProjectConverter3To4 for rename map.
+// Returns the new name if found, nullptr otherwise.
+const char *GDScriptAnalyzer::check_for_renamed_identifier(String identifier, GDScriptParser::Node::Type type) {
+ switch (type) {
+ case GDScriptParser::Node::IDENTIFIER: {
+ // Check properties
+ const char *result = get_rename_from_map(ProjectConverter3To4::gdscript_properties_renames, identifier);
+ if (result) {
+ return result;
+ }
+ // Check enum values
+ result = get_rename_from_map(ProjectConverter3To4::enum_renames, identifier);
+ if (result) {
+ return result;
+ }
+ // Check color constants
+ result = get_rename_from_map(ProjectConverter3To4::color_renames, identifier);
+ if (result) {
+ return result;
+ }
+ // Check type names
+ result = get_rename_from_map(ProjectConverter3To4::class_renames, identifier);
+ if (result) {
+ return result;
+ }
+ return get_rename_from_map(ProjectConverter3To4::builtin_types_renames, identifier);
+ }
+ case GDScriptParser::Node::CALL: {
+ const char *result = get_rename_from_map(ProjectConverter3To4::gdscript_function_renames, identifier);
+ if (result) {
+ return result;
+ }
+ // Built-in Types are mistaken for function calls when the built-in type is not found.
+ // Check built-in types if function rename not found
+ return get_rename_from_map(ProjectConverter3To4::builtin_types_renames, identifier);
+ }
+ // Signal references don't get parsed through the GDScriptAnalyzer. No support for signal rename hints.
+ default:
+ // No rename found, return null
+ return nullptr;
+ }
+}
+#endif // DISABLE_DEPRECATED
+#endif // TOOLS_ENABLED
+
void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) {
bool all_is_constant = true;
HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing.
@@ -2881,7 +2960,22 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
}
if (!found && (is_self || (base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::BUILTIN))) {
String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string();
+#ifdef TOOLS_ENABLED
+#ifndef DISABLE_DEPRECATED
+ String rename_hint = String();
+ if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
+ const char *renamed_function_name = check_for_renamed_identifier(p_call->function_name, p_call->type);
+ if (renamed_function_name) {
+ rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", String(renamed_function_name) + "()");
+ }
+ }
+ push_error(vformat(R"*(Function "%s()" not found in base %s.%s)*", p_call->function_name, base_name, rename_hint), p_call->is_super ? p_call : p_call->callee);
+#else // !DISABLE_DEPRECATED
+ push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee);
+#endif // DISABLE_DEPRECATED
+#else
push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee);
+#endif
} else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) {
push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call);
}
@@ -3071,7 +3165,22 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
p_identifier->reduced_value = result;
p_identifier->set_datatype(type_from_variant(result, p_identifier));
} else if (base.is_hard_type()) {
- push_error(vformat(R"(Cannot find constant "%s" on type "%s".)", name, base.to_string()), p_identifier);
+#ifdef TOOLS_ENABLED
+#ifndef DISABLE_DEPRECATED
+ String rename_hint = String();
+ if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
+ const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
+ if (renamed_identifier_name) {
+ rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
+ }
+ }
+ push_error(vformat(R"(Cannot find constant "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
+#else // !DISABLE_DEPRECATED
+ push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", name, base.to_string()), p_identifier);
+#endif // DISABLE_DEPRECATED
+#else
+ push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", name, base.to_string()), p_identifier);
+#endif
}
} else {
switch (base.builtin_type) {
@@ -3100,7 +3209,22 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
}
}
if (base.is_hard_type()) {
+#ifdef TOOLS_ENABLED
+#ifndef DISABLE_DEPRECATED
+ String rename_hint = String();
+ if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
+ const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
+ if (renamed_identifier_name) {
+ rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
+ }
+ }
+ push_error(vformat(R"(Cannot find property "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
+#else // !DISABLE_DEPRECATED
push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
+#endif // DISABLE_DEPRECATED
+#else
+ push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
+#endif
}
}
}
@@ -3439,7 +3563,22 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
if (GDScriptUtilityFunctions::function_exists(name)) {
push_error(vformat(R"(Built-in function "%s" cannot be used as an identifier.)", name), p_identifier);
} else {
+#ifdef TOOLS_ENABLED
+#ifndef DISABLE_DEPRECATED
+ String rename_hint = String();
+ if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
+ const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
+ if (renamed_identifier_name) {
+ rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
+ }
+ }
+ push_error(vformat(R"(Identifier "%s" not declared in the current scope.%s)", name, rename_hint), p_identifier);
+#else // !DISABLE_DEPRECATED
+ push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier);
+#endif // DISABLE_DEPRECATED
+#else
push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier);
+#endif
}
GDScriptParser::DataType dummy;
dummy.kind = GDScriptParser::DataType::VARIANT;
@@ -3898,6 +4037,10 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op)
Variant GDScriptAnalyzer::make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced) {
Variant value;
+ if (p_expression == nullptr) {
+ return value;
+ }
+
if (p_expression->is_constant) {
is_reduced = true;
value = p_expression->reduced_value;
@@ -3962,6 +4105,10 @@ Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::Dictiona
}
Variant GDScriptAnalyzer::make_subscript_reduced_value(GDScriptParser::SubscriptNode *p_subscript, bool &is_reduced) {
+ if (p_subscript->base == nullptr || p_subscript->index == nullptr) {
+ return Variant();
+ }
+
bool is_base_value_reduced = false;
Variant base_value = make_expression_reduced_value(p_subscript->base, is_base_value_reduced);
if (!is_base_value_reduced) {
@@ -4708,11 +4855,6 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) {
}
Error GDScriptAnalyzer::resolve_inheritance() {
- // Apply annotations.
- for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) {
- resolve_annotation(E);
- E->apply(parser, parser->head);
- }
return resolve_class_inheritance(parser->head, true);
}
@@ -4746,6 +4888,12 @@ Error GDScriptAnalyzer::analyze() {
return err;
}
+ // Apply annotations.
+ for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) {
+ resolve_annotation(E);
+ E->apply(parser, parser->head);
+ }
+
resolve_interface();
resolve_body();
if (!parser->errors.is_empty()) {
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 613399fb40..b51564fb0a 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -37,6 +37,10 @@
#include "gdscript_cache.h"
#include "gdscript_parser.h"
+#ifdef TOOLS_ENABLED
+#include "editor/project_converter_3_to_4.h"
+#endif
+
class GDScriptAnalyzer {
GDScriptParser *parser = nullptr;
HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
@@ -133,6 +137,13 @@ class GDScriptAnalyzer {
bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context);
#endif
+#ifdef TOOLS_ENABLED
+#ifndef DISABLE_DEPRECATED
+ const char *get_rename_from_map(const char *map[][2], String key);
+ const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node::Type type);
+#endif // DISABLE_DEPRECATED
+#endif // TOOLS_ENABLED
+
public:
Error resolve_inheritance();
Error resolve_interface();
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 373d8a3efd..46cd4b0d55 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -1859,7 +1859,12 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
}
}
- gen->write_return(return_value);
+ if (return_n->void_return) {
+ // Always return "null", even if the expression is a call to a void function.
+ gen->write_return(codegen.add_constant(Variant()));
+ } else {
+ gen->write_return(return_value);
+ }
if (return_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary();
}
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index b5c8a6f478..d4f4358ac1 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -317,7 +317,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += " = ";
text += DADDR(2);
- incr += 3;
+ incr += 6;
} break;
case OPCODE_ASSIGN_TYPED_NATIVE: {
text += "assign typed native (";
@@ -434,7 +434,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
int instr_var_args = _code_ptr[++ip];
int argc = _code_ptr[ip + 1 + instr_var_args];
- Ref<Script> script_type = get_constant(_code_ptr[ip + argc + 2]);
+ Ref<Script> script_type = get_constant(_code_ptr[ip + argc + 2] & GDScriptFunction::ADDR_MASK);
Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + argc + 4];
StringName native_type = get_global_name(_code_ptr[ip + argc + 5]);
@@ -463,7 +463,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += "]";
- incr += 3 + argc;
+ incr += 6 + argc;
} break;
case OPCODE_CONSTRUCT_DICTIONARY: {
int instr_var_args = _code_ptr[++ip];
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index bb08b017c9..ed2dce471f 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -491,7 +491,12 @@ void GDScriptParser::parse_program() {
AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
if (annotation != nullptr) {
if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
- head->annotations.push_back(annotation);
+ // `@icon` needs to be applied in the parser. See GH-72444.
+ if (annotation->name == SNAME("@icon")) {
+ annotation->apply(this, head);
+ } else {
+ head->annotations.push_back(annotation);
+ }
} else {
annotation_stack.push_back(annotation);
// This annotation must appear after script-level annotations
@@ -809,7 +814,7 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
push_error(R"(Expected newline after a standalone annotation.)");
}
- if (annotation->name == "@export_category" || annotation->name == "@export_group" || annotation->name == "@export_subgroup") {
+ if (annotation->name == SNAME("@export_category") || annotation->name == SNAME("@export_group") || annotation->name == SNAME("@export_subgroup")) {
current_class->add_member_group(annotation);
} else {
// For potential non-group standalone annotations.
@@ -1436,7 +1441,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional.
if (valid) {
- valid = validate_annotation_argument_count(annotation);
+ valid = validate_annotation_arguments(annotation);
}
return valid ? annotation : nullptr;
@@ -3551,7 +3556,7 @@ bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const {
return (info->target_kind & p_target_kinds) > 0;
}
-bool GDScriptParser::validate_annotation_argument_count(AnnotationNode *p_annotation) {
+bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) {
ERR_FAIL_COND_V_MSG(!valid_annotations.has(p_annotation->name), false, vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name));
const MethodInfo &info = valid_annotations[p_annotation->name].info;
@@ -3566,6 +3571,27 @@ bool GDScriptParser::validate_annotation_argument_count(AnnotationNode *p_annota
return false;
}
+ // `@icon`'s argument needs to be resolved in the parser. See GH-72444.
+ if (p_annotation->name == SNAME("@icon")) {
+ ExpressionNode *argument = p_annotation->arguments[0];
+
+ if (argument->type != Node::LITERAL) {
+ push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument);
+ return false;
+ }
+
+ Variant value = static_cast<LiteralNode *>(argument)->value;
+
+ if (value.get_type() != Variant::STRING) {
+ push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument);
+ return false;
+ }
+
+ p_annotation->resolved_arguments.push_back(value);
+ }
+
+ // For other annotations, see `GDScriptAnalyzer::resolve_annotation()`.
+
return true;
}
@@ -3576,6 +3602,7 @@ bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p
bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_node) {
ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)");
+ ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);
ClassNode *p_class = static_cast<ClassNode *>(p_node);
p_class->icon_path = p_annotation->resolved_arguments[0];
return true;
@@ -3804,6 +3831,10 @@ template <PropertyUsageFlags t_usage>
bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_node) {
AnnotationNode *annotation = const_cast<AnnotationNode *>(p_annotation);
+ if (annotation->resolved_arguments.is_empty()) {
+ return false;
+ }
+
annotation->export_info.name = annotation->resolved_arguments[0];
switch (t_usage) {
@@ -3861,7 +3892,7 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
Dictionary rpc_config;
rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY;
- if (p_annotation->resolved_arguments.size()) {
+ if (!p_annotation->resolved_arguments.is_empty()) {
int last = p_annotation->resolved_arguments.size() - 1;
if (p_annotation->resolved_arguments[last].get_type() == Variant::INT) {
rpc_config["channel"] = p_annotation->resolved_arguments[last].operator int();
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index bb4549c15c..07dac25ec5 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -970,6 +970,7 @@ public:
struct ReturnNode : public Node {
ExpressionNode *return_value = nullptr;
+ bool void_return = false;
ReturnNode() {
type = RETURN;
@@ -1401,7 +1402,7 @@ private:
// Annotations
AnnotationNode *parse_annotation(uint32_t p_valid_targets);
bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false);
- bool validate_annotation_argument_count(AnnotationNode *p_annotation);
+ bool validate_annotation_arguments(AnnotationNode *p_annotation);
void clear_unused_annotations();
bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target);
bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target);
diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp
index 10d83dcfe5..758b61bb31 100644
--- a/modules/gdscript/gdscript_utility_functions.cpp
+++ b/modules/gdscript/gdscript_utility_functions.cpp
@@ -112,28 +112,6 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = String(result);
}
- static inline void str(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- if (p_arg_count < 1) {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.argument = 1;
- r_error.expected = 1;
- *r_ret = Variant();
- return;
- }
-
- String str;
- for (int i = 0; i < p_arg_count; i++) {
- String os = p_args[i]->operator String();
-
- if (i == 0) {
- str = os;
- } else {
- str += os;
- }
- }
- *r_ret = str;
- }
-
static inline void range(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
switch (p_arg_count) {
case 0: {
@@ -651,7 +629,6 @@ void GDScriptUtilityFunctions::register_functions() {
REGISTER_VARIANT_FUNC(convert, true, VARARG("what"), ARG("type", Variant::INT));
REGISTER_FUNC(type_exists, true, Variant::BOOL, ARG("type", Variant::STRING_NAME));
REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT));
- REGISTER_VARARG_FUNC(str, true, Variant::STRING);
REGISTER_VARARG_FUNC(range, false, Variant::ARRAY);
REGISTER_CLASS_FUNC(load, false, "Resource", ARG("path", Variant::STRING));
REGISTER_FUNC(inst_to_dict, false, Variant::DICTIONARY, ARG("instance", Variant::OBJECT));
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index 024fed8517..9436146bed 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -125,6 +125,10 @@ String GDScriptWarning::get_message() const {
CHECK_SYMBOLS(4);
return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided";
} break;
+ case UNSAFE_VOID_RETURN: {
+ CHECK_SYMBOLS(2);
+ return "The method '" + symbols[0] + "()' returns 'void' but it's trying to return a call to '" + symbols[1] + "()' that can't be ensured to also be 'void'.";
+ } break;
case DEPRECATED_KEYWORD: {
CHECK_SYMBOLS(2);
return "The '" + symbols[0] + "' keyword is deprecated and will be removed in a future release, please replace its uses by '" + symbols[1] + "'.";
@@ -163,6 +167,9 @@ String GDScriptWarning::get_message() const {
CHECK_SYMBOLS(1);
return vformat(R"(The identifier "%s" has misleading characters and might be confused with something else.)", symbols[0]);
}
+ case RENAMED_IN_GD4_HINT: {
+ break; // Renamed identifier hint is taken care of by the GDScriptAnalyzer. No message needed here.
+ }
case WARNING_MAX:
break; // Can't happen, but silences warning
}
@@ -184,6 +191,9 @@ int GDScriptWarning::get_default_value(Code p_code) {
PropertyInfo GDScriptWarning::get_property_info(Code p_code) {
// Making this a separate function in case a warning needs different PropertyInfo in the future.
+ if (p_code == Code::RENAMED_IN_GD4_HINT) {
+ return PropertyInfo(Variant::BOOL, get_settings_path_from_code(p_code));
+ }
return PropertyInfo(Variant::INT, get_settings_path_from_code(p_code), PROPERTY_HINT_ENUM, "Ignore,Warn,Error");
}
@@ -218,6 +228,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"UNSAFE_METHOD_ACCESS",
"UNSAFE_CAST",
"UNSAFE_CALL_ARGUMENT",
+ "UNSAFE_VOID_RETURN",
"DEPRECATED_KEYWORD",
"STANDALONE_TERNARY",
"ASSERT_ALWAYS_TRUE",
@@ -229,6 +240,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"INT_AS_ENUM_WITHOUT_MATCH",
"STATIC_CALLED_ON_INSTANCE",
"CONFUSABLE_IDENTIFIER",
+ "RENAMED_IN_GODOT_4_HINT"
};
static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index 7492972c1a..fa2907cdae 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -69,6 +69,7 @@ public:
UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes).
UNSAFE_CAST, // Cast used in an unknown type.
UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the require argument.
+ UNSAFE_VOID_RETURN, // Function returns void but returned a call to a function that can't be type checked.
DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced.
STANDALONE_TERNARY, // Return value of ternary expression is discarded.
ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true.
@@ -80,6 +81,7 @@ public:
INT_AS_ENUM_WITHOUT_MATCH, // An integer value was used as an enum value without matching enum member.
STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself.
CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e").
+ RENAMED_IN_GD4_HINT, // A variable or function that could not be found has been renamed in Godot 4
WARNING_MAX,
};
diff --git a/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.gd b/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.gd
new file mode 100644
index 0000000000..df89137f40
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.gd
@@ -0,0 +1,20 @@
+func test():
+ return_call()
+ return_nothing()
+ return_side_effect()
+ var r = return_side_effect.call() # Untyped call to check return value.
+ prints(r, typeof(r) == TYPE_NIL)
+ print("end")
+
+func side_effect(v):
+ print("effect")
+ return v
+
+func return_call() -> void:
+ return print("hello")
+
+func return_nothing() -> void:
+ return
+
+func return_side_effect() -> void:
+ return side_effect("x")
diff --git a/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.out b/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.out
new file mode 100644
index 0000000000..7c0416371f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.out
@@ -0,0 +1,10 @@
+GDTEST_OK
+>> WARNING
+>> Line: 20
+>> UNSAFE_VOID_RETURN
+>> The method 'return_side_effect()' returns 'void' but it's trying to return a call to 'side_effect()' that can't be ensured to also be 'void'.
+hello
+effect
+effect
+<null> true
+end
diff --git a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd
index 0085b3f367..2470fe978e 100644
--- a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd
+++ b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd
@@ -1,5 +1,5 @@
# Error here. Annotations should be used before `class_name`, not after.
-class_name HelloWorld
+class_name WrongAnnotationPlace
@icon("res://path/to/optional/icon.svg")
func test():
diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
index 6004de32f1..6e8340f618 100644
--- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml
+++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
@@ -22,7 +22,7 @@
</description>
</method>
<method name="_export_node" qualifiers="virtual">
- <return type="int" />
+ <return type="int" enum="Error" />
<param index="0" name="state" type="GLTFState" />
<param index="1" name="gltf_node" type="GLTFNode" />
<param index="2" name="json" type="Dictionary" />
@@ -33,7 +33,7 @@
</description>
</method>
<method name="_export_post" qualifiers="virtual">
- <return type="int" />
+ <return type="int" enum="Error" />
<param index="0" name="state" type="GLTFState" />
<description>
Part of the export process. This method is run last, after all other parts of the export process.
@@ -41,7 +41,7 @@
</description>
</method>
<method name="_export_preflight" qualifiers="virtual">
- <return type="int" />
+ <return type="int" enum="Error" />
<param index="0" name="state" type="GLTFState" />
<param index="1" name="root" type="Node" />
<description>
@@ -67,7 +67,7 @@
</description>
</method>
<method name="_import_node" qualifiers="virtual">
- <return type="int" />
+ <return type="int" enum="Error" />
<param index="0" name="state" type="GLTFState" />
<param index="1" name="gltf_node" type="GLTFNode" />
<param index="2" name="json" type="Dictionary" />
@@ -78,7 +78,7 @@
</description>
</method>
<method name="_import_post" qualifiers="virtual">
- <return type="int" />
+ <return type="int" enum="Error" />
<param index="0" name="state" type="GLTFState" />
<param index="1" name="root" type="Node" />
<description>
@@ -87,7 +87,7 @@
</description>
</method>
<method name="_import_post_parse" qualifiers="virtual">
- <return type="int" />
+ <return type="int" enum="Error" />
<param index="0" name="state" type="GLTFState" />
<description>
Part of the import process. This method is run after [method _generate_scene_node] and before [method _import_node].
@@ -95,7 +95,7 @@
</description>
</method>
<method name="_import_preflight" qualifiers="virtual">
- <return type="int" />
+ <return type="int" enum="Error" />
<param index="0" name="state" type="GLTFState" />
<param index="1" name="extensions" type="PackedStringArray" />
<description>
@@ -104,7 +104,7 @@
</description>
</method>
<method name="_parse_node_extensions" qualifiers="virtual">
- <return type="int" />
+ <return type="int" enum="Error" />
<param index="0" name="state" type="GLTFState" />
<param index="1" name="gltf_node" type="GLTFNode" />
<param index="2" name="extensions" type="Dictionary" />
diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml
index b8943795a0..b322c07cec 100644
--- a/modules/gltf/doc_classes/GLTFState.xml
+++ b/modules/gltf/doc_classes/GLTFState.xml
@@ -264,10 +264,16 @@
</members>
<constants>
<constant name="HANDLE_BINARY_DISCARD_TEXTURES" value="0">
+ Discards all embedded textures and uses untextured materials.
</constant>
<constant name="HANDLE_BINARY_EXTRACT_TEXTURES" value="1">
+ Extracts embedded textures to be reimported and compressed. Editor only. Acts as uncompressed at runtime.
</constant>
<constant name="HANDLE_BINARY_EMBED_AS_BASISU" value="2">
+ Embeds textures VRAM compressed with Basis Universal into the generated scene.
+ </constant>
+ <constant name="HANDLE_BINARY_EMBED_AS_UNCOMPRESSED" value="3">
+ Embeds textures compressed losslessly into the generated scene, matching old behavior.
</constant>
</constants>
</class>
diff --git a/modules/gltf/editor/editor_import_blend_runner.cpp b/modules/gltf/editor/editor_import_blend_runner.cpp
new file mode 100644
index 0000000000..c203a91834
--- /dev/null
+++ b/modules/gltf/editor/editor_import_blend_runner.cpp
@@ -0,0 +1,314 @@
+/**************************************************************************/
+/* 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"
+
+#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
diff --git a/modules/gltf/editor/editor_import_blend_runner.h b/modules/gltf/editor/editor_import_blend_runner.h
new file mode 100644
index 0000000000..b2b82394e1
--- /dev/null
+++ b/modules/gltf/editor/editor_import_blend_runner.h
@@ -0,0 +1,69 @@
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+#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
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index 5415c5818f..520f33261a 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -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.
-
- 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.
+ request_options["path"] = source_global;
+ request_options["gltf_options"] = parameters_map;
- 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;
diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp
index 67bbf8dd15..012a144d52 100644
--- a/modules/gltf/editor/editor_scene_importer_gltf.cpp
+++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp
@@ -51,8 +51,8 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t
doc.instantiate();
Ref<GLTFState> state;
state.instantiate();
- if (p_options.has("meshes/handle_gltf_embedded_images")) {
- int32_t enum_option = p_options["meshes/handle_gltf_embedded_images"];
+ 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);
}
Error err = doc->append_from_file(p_path, state, p_flags);
@@ -87,7 +87,7 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t
void EditorSceneFormatImporterGLTF::get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) {
- r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "meshes/handle_gltf_embedded_images", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed As Basis Universal", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFState::HANDLE_BINARY_EXTRACT_TEXTURES));
+ 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));
}
#endif // TOOLS_ENABLED
diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp
index 496a8f6cb8..bedb42eb32 100644
--- a/modules/gltf/extensions/gltf_document_extension.cpp
+++ b/modules/gltf/extensions/gltf_document_extension.cpp
@@ -49,9 +49,9 @@ void GLTFDocumentExtension::_bind_methods() {
// Import process.
Error GLTFDocumentExtension::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- int err = OK;
+ Error err = OK;
GDVIRTUAL_CALL(_import_preflight, p_state, p_extensions, err);
- return Error(err);
+ return err;
}
Vector<String> GLTFDocumentExtension::get_supported_extensions() {
@@ -63,9 +63,9 @@ Vector<String> GLTFDocumentExtension::get_supported_extensions() {
Error GLTFDocumentExtension::parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER);
- int err = OK;
+ Error err = OK;
GDVIRTUAL_CALL(_parse_node_extensions, p_state, p_gltf_node, p_extensions, err);
- return Error(err);
+ return err;
}
Node3D *GLTFDocumentExtension::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) {
@@ -79,34 +79,34 @@ Node3D *GLTFDocumentExtension::generate_scene_node(Ref<GLTFState> p_state, Ref<G
Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> p_state) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- int err = OK;
+ Error err = OK;
GDVIRTUAL_CALL(_import_post_parse, p_state, err);
- return Error(err);
+ return err;
}
Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER);
- int err = OK;
+ Error err = OK;
GDVIRTUAL_CALL(_import_node, p_state, p_gltf_node, r_dict, p_node, err);
- return Error(err);
+ return err;
}
Error GLTFDocumentExtension::import_post(Ref<GLTFState> p_state, Node *p_root) {
ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- int err = OK;
+ Error err = OK;
GDVIRTUAL_CALL(_import_post, p_state, p_root, err);
- return Error(err);
+ return err;
}
// Export process.
Error GLTFDocumentExtension::export_preflight(Ref<GLTFState> p_state, Node *p_root) {
ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER);
- int err = OK;
+ Error err = OK;
GDVIRTUAL_CALL(_export_preflight, p_state, p_root, err);
- return Error(err);
+ return err;
}
void GLTFDocumentExtension::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) {
@@ -120,14 +120,14 @@ Error GLTFDocumentExtension::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER);
- int err = OK;
+ Error err = OK;
GDVIRTUAL_CALL(_export_node, p_state, p_gltf_node, r_dict, p_node, err);
- return Error(err);
+ return err;
}
Error GLTFDocumentExtension::export_post(Ref<GLTFState> p_state) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- int err = OK;
+ Error err = OK;
GDVIRTUAL_CALL(_export_post, p_state, err);
- return Error(err);
+ return err;
}
diff --git a/modules/gltf/extensions/gltf_document_extension.h b/modules/gltf/extensions/gltf_document_extension.h
index 97f5045a1c..3531f81f6f 100644
--- a/modules/gltf/extensions/gltf_document_extension.h
+++ b/modules/gltf/extensions/gltf_document_extension.h
@@ -55,18 +55,18 @@ public:
virtual Error export_post(Ref<GLTFState> p_state);
// Import process.
- GDVIRTUAL2R(int, _import_preflight, Ref<GLTFState>, Vector<String>);
+ GDVIRTUAL2R(Error, _import_preflight, Ref<GLTFState>, Vector<String>);
GDVIRTUAL0R(Vector<String>, _get_supported_extensions);
- GDVIRTUAL3R(int, _parse_node_extensions, Ref<GLTFState>, Ref<GLTFNode>, Dictionary);
+ GDVIRTUAL3R(Error, _parse_node_extensions, Ref<GLTFState>, Ref<GLTFNode>, Dictionary);
GDVIRTUAL3R(Node3D *, _generate_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *);
- GDVIRTUAL1R(int, _import_post_parse, Ref<GLTFState>);
- GDVIRTUAL4R(int, _import_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *);
- GDVIRTUAL2R(int, _import_post, Ref<GLTFState>, Node *);
+ GDVIRTUAL1R(Error, _import_post_parse, Ref<GLTFState>);
+ GDVIRTUAL4R(Error, _import_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *);
+ GDVIRTUAL2R(Error, _import_post, Ref<GLTFState>, Node *);
// Export process.
- GDVIRTUAL2R(int, _export_preflight, Ref<GLTFState>, Node *);
+ GDVIRTUAL2R(Error, _export_preflight, Ref<GLTFState>, Node *);
GDVIRTUAL3(_convert_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *);
- GDVIRTUAL4R(int, _export_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *);
- GDVIRTUAL1R(int, _export_post, Ref<GLTFState>);
+ GDVIRTUAL4R(Error, _export_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *);
+ GDVIRTUAL1R(Error, _export_post, Ref<GLTFState>);
};
#endif // GLTF_DOCUMENT_EXTENSION_H
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 0b519bd6a3..1a09b5bdcc 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -32,6 +32,7 @@
#include "extensions/gltf_spec_gloss.h"
+#include "core/config/project_settings.h"
#include "core/crypto/crypto_core.h"
#include "core/io/config_file.h"
#include "core/io/dir_access.h"
@@ -3065,6 +3066,7 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p
// Ref: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#images
const Array &images = p_state->json["images"];
+ HashSet<String> used_names;
for (int i = 0; i < images.size(); i++) {
const Dictionary &d = images[i];
@@ -3092,11 +3094,21 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p
int data_size = 0;
String image_name;
+ if (d.has("name")) {
+ image_name = d["name"];
+ image_name = image_name.get_file().get_basename().validate_filename();
+ }
+ if (image_name.is_empty()) {
+ image_name = itos(i);
+ }
+ while (used_names.has(image_name)) {
+ image_name += "_" + itos(i);
+ }
+ used_names.insert(image_name);
if (d.has("uri")) {
// Handles the first two bullet points from the spec (embedded data, or external file).
String uri = d["uri"];
- image_name = uri;
if (uri.begins_with("data:")) { // Embedded data using base64.
// Validate data MIME types and throw a warning if it's one we don't know/support.
@@ -3158,7 +3170,6 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p
vformat("glTF: Image index '%d' specifies 'bufferView' but no 'mimeType', which is invalid.", i));
const GLTFBufferViewIndex bvi = d["bufferView"];
- image_name = itos(bvi);
ERR_FAIL_INDEX_V(bvi, p_state->buffer_views.size(), ERR_PARAMETER_RANGE_ERROR);
@@ -3206,13 +3217,12 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p
p_state->source_images.push_back(Ref<Image>());
continue;
}
+ img->set_name(image_name);
if (GLTFState::GLTFHandleBinary(p_state->handle_binary_image) == GLTFState::GLTFHandleBinary::HANDLE_BINARY_DISCARD_TEXTURES) {
p_state->images.push_back(Ref<Texture2D>());
p_state->source_images.push_back(Ref<Image>());
- continue;
- } else if (GLTFState::GLTFHandleBinary(p_state->handle_binary_image) == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EXTRACT_TEXTURES) {
- String extracted_image_name = image_name.get_file().get_basename().validate_filename();
- img->set_name(extracted_image_name);
+#ifdef TOOLS_ENABLED
+ } else if (Engine::get_singleton()->is_editor_hint() && GLTFState::GLTFHandleBinary(p_state->handle_binary_image) == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EXTRACT_TEXTURES) {
if (p_state->base_path.is_empty()) {
p_state->images.push_back(Ref<Texture2D>());
p_state->source_images.push_back(Ref<Image>());
@@ -3221,26 +3231,56 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p
p_state->images.push_back(Ref<Texture2D>());
p_state->source_images.push_back(Ref<Image>());
} else {
+ Error err = OK;
+ bool must_import = false;
String file_path = p_state->get_base_path() + "/" + p_state->filename.get_basename() + "_" + img->get_name() + ".png";
- Ref<ConfigFile> config;
- config.instantiate();
- if (FileAccess::exists(file_path + ".import")) {
- config->load(file_path + ".import");
+ if (!FileAccess::exists(file_path + ".import")) {
+ Ref<ConfigFile> config;
+ config.instantiate();
+ config->set_value("remap", "importer", "texture");
+ config->set_value("remap", "type", "Texture2D");
+ // Currently, it will likely use project defaults of Detect 3D, so textures will be reimported again.
+ if (!config->has_section_key("params", "mipmaps/generate")) {
+ config->set_value("params", "mipmaps/generate", true);
+ }
+
+ if (ProjectSettings::get_singleton()->has_setting("importer_defaults/texture")) {
+ //use defaults if exist
+ Dictionary importer_defaults = GLOBAL_GET("importer_defaults/texture");
+ List<Variant> importer_def_keys;
+ importer_defaults.get_key_list(&importer_def_keys);
+ for (const Variant &key : importer_def_keys) {
+ if (!config->has_section_key("params", (String)key)) {
+ config->set_value("params", (String)key, importer_defaults[key]);
+ }
+ }
+ }
+ err = config->save(file_path + ".import");
+ ERR_FAIL_COND_V(err != OK, err);
+ must_import = true;
}
- config->set_value("remap", "importer", "texture");
- config->set_value("remap", "type", "Texture2D");
- if (!config->has_section_key("params", "compress/mode")) {
- config->set_value("remap", "compress/mode", 2); //user may want another compression, so leave it bes
+ Vector<uint8_t> png_buffer = img->save_png_to_buffer();
+ if (ResourceLoader::exists(file_path)) {
+ Ref<FileAccess> file = FileAccess::open(file_path, FileAccess::READ, &err);
+ if (err == OK && file.is_valid()) {
+ Vector<uint8_t> orig_png_buffer = file->get_buffer(file->get_length());
+ if (png_buffer != orig_png_buffer) {
+ must_import = true;
+ }
+ }
+ } else {
+ must_import = true;
}
- if (!config->has_section_key("params", "mipmaps/generate")) {
- config->set_value("params", "mipmaps/generate", true);
+ if (must_import) {
+ Ref<FileAccess> file = FileAccess::open(file_path, FileAccess::WRITE, &err);
+ ERR_FAIL_COND_V(err != OK, err);
+ ERR_FAIL_COND_V(file.is_null(), FAILED);
+ file->store_buffer(png_buffer);
+ file->flush();
+ file.unref();
+ // ResourceLoader::import will crash if not is_editor_hint(), so this case is protected above and will fall through to uncompressed.
+ ResourceLoader::import(file_path);
}
- Error err = OK;
- err = config->save(file_path + ".import");
- ERR_FAIL_COND_V(err != OK, err);
- img->save_png(file_path);
- ERR_FAIL_COND_V(err != OK, err);
- ResourceLoader::import(file_path);
Ref<Texture2D> saved_image = ResourceLoader::load(file_path, "Texture2D");
if (saved_image.is_valid()) {
p_state->images.push_back(saved_image);
@@ -3252,7 +3292,7 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p
p_state->source_images.push_back(Ref<Image>());
}
}
- continue;
+#endif
} else if (GLTFState::GLTFHandleBinary(p_state->handle_binary_image) == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EMBED_AS_BASISU) {
Ref<PortableCompressedTexture2D> tex;
tex.instantiate();
@@ -3262,11 +3302,15 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p
tex->create_from_image(img, PortableCompressedTexture2D::COMPRESSION_MODE_BASIS_UNIVERSAL);
p_state->images.push_back(tex);
p_state->source_images.push_back(img);
- continue;
+ } else {
+ // This handles two cases: if editor hint and HANDLE_BINARY_EXTRACT_TEXTURES; or if HANDLE_BINARY_EMBED_AS_UNCOMPRESSED
+ Ref<ImageTexture> tex;
+ tex.instantiate();
+ tex->set_name(img->get_name());
+ tex->set_image(img);
+ p_state->images.push_back(tex);
+ p_state->source_images.push_back(img);
}
-
- p_state->images.push_back(Ref<Texture2D>());
- p_state->source_images.push_back(Ref<Image>());
}
print_verbose("glTF: Total images: " + itos(p_state->images.size()));
diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp
index b67484fc8e..b7b7113a97 100644
--- a/modules/gltf/gltf_state.cpp
+++ b/modules/gltf/gltf_state.cpp
@@ -120,11 +120,12 @@ void GLTFState::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "skeleton_to_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skeleton_to_node", "get_skeleton_to_node"); // RBMap<GLTFSkeletonIndex,
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "create_animations"), "set_create_animations", "get_create_animations"); // bool
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animations", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_animations", "get_animations"); // Vector<Ref<GLTFAnimation>>
- ADD_PROPERTY(PropertyInfo(Variant::INT, "handle_binary_image", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed As Basis Universal", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_handle_binary_image", "get_handle_binary_image"); // enum
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "handle_binary_image", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed As Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_handle_binary_image", "get_handle_binary_image"); // enum
BIND_CONSTANT(HANDLE_BINARY_DISCARD_TEXTURES);
BIND_CONSTANT(HANDLE_BINARY_EXTRACT_TEXTURES);
BIND_CONSTANT(HANDLE_BINARY_EMBED_AS_BASISU);
+ BIND_CONSTANT(HANDLE_BINARY_EMBED_AS_UNCOMPRESSED);
}
void GLTFState::add_used_extension(const String &p_extension_name, bool p_required) {
diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h
index 52d7949d03..b6979ca48e 100644
--- a/modules/gltf/gltf_state.h
+++ b/modules/gltf/gltf_state.h
@@ -108,6 +108,7 @@ public:
HANDLE_BINARY_DISCARD_TEXTURES = 0,
HANDLE_BINARY_EXTRACT_TEXTURES,
HANDLE_BINARY_EMBED_AS_BASISU,
+ HANDLE_BINARY_EMBED_AS_UNCOMPRESSED, // if this value changes from 3, ResourceImporterScene::pre_import must be changed as well.
};
int32_t get_handle_binary_image() {
return handle_binary_image;
diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp
index f80e12bbae..78589090db 100644
--- a/modules/gltf/register_types.cpp
+++ b/modules/gltf/register_types.cpp
@@ -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.
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs
index 813bdf1e9f..47a4516948 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs
@@ -48,7 +48,7 @@ namespace GodotPlugins.Game
}
catch (Exception e)
{
- Console.Error.WriteLine(e);
+ global::System.Console.Error.WriteLine(e);
return false.ToGodotBool();
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index 019504ad66..70b48b0e3a 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -185,7 +185,9 @@ namespace GodotTools.Export
foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories))
{
- AddSharedObject(file, tags: null, projectDataDirName);
+ AddSharedObject(file, tags: null,
+ Path.Join(projectDataDirName,
+ Path.GetRelativePath(publishOutputTempDir, Path.GetDirectoryName(file))));
}
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs
index 8463403096..4610761bdb 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs
@@ -7,7 +7,7 @@ namespace Godot
/// <summary>
/// Instantiates the scene's node hierarchy, erroring on failure.
/// Triggers child scene instantiation(s). Triggers a
- /// <see cref="Node.NotificationInstanced"/> notification on the root node.
+ /// <see cref="Node.NotificationSceneInstantiated"/> notification on the root node.
/// </summary>
/// <seealso cref="InstantiateOrNull{T}(GenEditState)"/>
/// <exception cref="InvalidCastException">
@@ -23,7 +23,7 @@ namespace Godot
/// <summary>
/// Instantiates the scene's node hierarchy, returning <see langword="null"/> on failure.
/// Triggers child scene instantiation(s). Triggers a
- /// <see cref="Node.NotificationInstanced"/> notification on the root node.
+ /// <see cref="Node.NotificationSceneInstantiated"/> notification on the root node.
/// </summary>
/// <seealso cref="Instantiate{T}(GenEditState)"/>
/// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rid.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rid.cs
index 150eb98fc7..350626389b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rid.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rid.cs
@@ -6,13 +6,17 @@ using Godot.NativeInterop;
namespace Godot
{
/// <summary>
- /// The Rid type is used to access the unique integer ID of a resource.
- /// They are opaque, which means they do not grant access to the associated
- /// resource by themselves. They are used by and with the low-level Server
- /// classes such as <see cref="RenderingServer"/>.
+ /// The RID type is used to access a low-level resource by its unique ID.
+ /// RIDs are opaque, which means they do not grant access to the resource
+ /// by themselves. They are used by the low-level server classes, such as
+ /// <see cref="DisplayServer"/>, <see cref="RenderingServer"/>,
+ /// <see cref="TextServer"/>, etc.
+ ///
+ /// A low-level resource may correspond to a high-level <see cref="Resource"/>,
+ /// such as <see cref="Texture"/> or <see cref="Mesh"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
- public readonly struct Rid
+ public readonly struct Rid : IEquatable<Rid>
{
private readonly ulong _id; // Default is 0
@@ -28,15 +32,73 @@ namespace Godot
=> _id = from is Resource res ? res.GetRid()._id : default;
/// <summary>
- /// Returns the ID of the referenced resource.
+ /// Returns the ID of the referenced low-level resource.
/// </summary>
/// <returns>The ID of the referenced resource.</returns>
public ulong Id => _id;
/// <summary>
+ /// Returns <see langword="true"/> if the <see cref="Rid"/> is not <c>0</c>.
+ /// </summary>
+ /// <returns>Whether or not the ID is valid.</returns>
+ public bool IsValid => _id != 0;
+
+ /// <summary>
+ /// Returns <see langword="true"/> if both <see cref="Rid"/>s are equal,
+ /// which means they both refer to the same low-level resource.
+ /// </summary>
+ /// <param name="left">The left RID.</param>
+ /// <param name="right">The right RID.</param>
+ /// <returns>Whether or not the RIDs are equal.</returns>
+ public static bool operator ==(Rid left, Rid right)
+ {
+ return left.Equals(right);
+ }
+
+ /// <summary>
+ /// Returns <see langword="true"/> if the <see cref="Rid"/>s are not equal.
+ /// </summary>
+ /// <param name="left">The left RID.</param>
+ /// <param name="right">The right RID.</param>
+ /// <returns>Whether or not the RIDs are equal.</returns>
+ public static bool operator !=(Rid left, Rid right)
+ {
+ return !left.Equals(right);
+ }
+
+ /// <summary>
+ /// Returns <see langword="true"/> if this RID and <paramref name="obj"/> are equal.
+ /// </summary>
+ /// <param name="obj">The other object to compare.</param>
+ /// <returns>Whether or not the color and the other object are equal.</returns>
+ public override readonly bool Equals(object obj)
+ {
+ return obj is Rid other && Equals(other);
+ }
+
+ /// <summary>
+ /// Returns <see langword="true"/> if the RIDs are equal.
+ /// </summary>
+ /// <param name="other">The other RID.</param>
+ /// <returns>Whether or not the RIDs are equal.</returns>
+ public readonly bool Equals(Rid other)
+ {
+ return _id == other.Id;
+ }
+
+ /// <summary>
+ /// Serves as the hash function for <see cref="Rid"/>.
+ /// </summary>
+ /// <returns>A hash code for this RID.</returns>
+ public override readonly int GetHashCode()
+ {
+ return HashCode.Combine(_id);
+ }
+
+ /// <summary>
/// Converts this <see cref="Rid"/> to a string.
/// </summary>
/// <returns>A string representation of this Rid.</returns>
- public override string ToString() => $"Rid({Id})";
+ public override string ToString() => $"RID({Id})";
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
index 5283dc7ec6..d7392dbda8 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
@@ -247,19 +247,19 @@ namespace Godot
/// <returns>The orthonormalized transform.</returns>
public readonly Transform2D Orthonormalized()
{
- Transform2D on = this;
+ Transform2D ortho = this;
- Vector2 onX = on.X;
- Vector2 onY = on.Y;
+ Vector2 orthoX = ortho.X;
+ Vector2 orthoY = ortho.Y;
- onX.Normalize();
- onY = onY - (onX * onX.Dot(onY));
- onY.Normalize();
+ orthoX.Normalize();
+ orthoY = orthoY - orthoX * orthoX.Dot(orthoY);
+ orthoY.Normalize();
- on.X = onX;
- on.Y = onY;
+ ortho.X = orthoX;
+ ortho.Y = orthoY;
- return on;
+ return ortho;
}
/// <summary>
diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp
index d546c5d3ba..c3cb1c5f13 100644
--- a/modules/navigation/godot_navigation_server.cpp
+++ b/modules/navigation/godot_navigation_server.cpp
@@ -262,7 +262,7 @@ TypedArray<RID> GodotNavigationServer::map_get_agents(RID p_map) const {
const NavMap *map = map_owner.get_or_null(p_map);
ERR_FAIL_COND_V(map == nullptr, agents_rids);
- const LocalVector<RvoAgent *> agents = map->get_agents();
+ const LocalVector<NavAgent *> agents = map->get_agents();
agents_rids.resize(agents.size());
for (uint32_t i = 0; i < agents.size(); i++) {
@@ -282,7 +282,7 @@ RID GodotNavigationServer::region_get_map(RID p_region) const {
}
RID GodotNavigationServer::agent_get_map(RID p_agent) const {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND_V(agent == nullptr, RID());
if (agent->get_map()) {
@@ -579,13 +579,13 @@ RID GodotNavigationServer::agent_create() {
MutexLock lock(operations_mutex);
RID rid = agent_owner.make_rid();
- RvoAgent *agent = agent_owner.get_or_null(rid);
+ NavAgent *agent = agent_owner.get_or_null(rid);
agent->set_self(rid);
return rid;
}
COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
if (agent->get_map()) {
@@ -612,77 +612,77 @@ COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) {
}
COMMAND_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_distance) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->neighborDist_ = p_distance;
}
COMMAND_2(agent_set_max_neighbors, RID, p_agent, int, p_count) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->maxNeighbors_ = p_count;
}
COMMAND_2(agent_set_time_horizon, RID, p_agent, real_t, p_time) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->timeHorizon_ = p_time;
}
COMMAND_2(agent_set_radius, RID, p_agent, real_t, p_radius) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->radius_ = p_radius;
}
COMMAND_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->maxSpeed_ = p_max_speed;
}
COMMAND_2(agent_set_velocity, RID, p_agent, Vector3, p_velocity) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->velocity_ = RVO::Vector3(p_velocity.x, p_velocity.y, p_velocity.z);
}
COMMAND_2(agent_set_target_velocity, RID, p_agent, Vector3, p_velocity) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->prefVelocity_ = RVO::Vector3(p_velocity.x, p_velocity.y, p_velocity.z);
}
COMMAND_2(agent_set_position, RID, p_agent, Vector3, p_position) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->position_ = RVO::Vector3(p_position.x, p_position.y, p_position.z);
}
COMMAND_2(agent_set_ignore_y, RID, p_agent, bool, p_ignore) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->ignore_y_ = p_ignore;
}
bool GodotNavigationServer::agent_is_map_changed(RID p_agent) const {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND_V(agent == nullptr, false);
return agent->is_map_changed();
}
COMMAND_2(agent_set_callback, RID, p_agent, Callable, p_callback) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->set_callback(p_callback);
@@ -713,7 +713,7 @@ COMMAND_1(free, RID, p_object) {
}
// Remove any assigned agent
- for (RvoAgent *agent : map->get_agents()) {
+ for (NavAgent *agent : map->get_agents()) {
map->remove_agent(agent);
agent->set_map(nullptr);
}
@@ -746,7 +746,7 @@ COMMAND_1(free, RID, p_object) {
link_owner.free(p_object);
} else if (agent_owner.owns(p_object)) {
- RvoAgent *agent = agent_owner.get_or_null(p_object);
+ NavAgent *agent = agent_owner.get_or_null(p_object);
// Removes this agent from the map if assigned
if (agent->get_map() != nullptr) {
diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h
index eea5713c40..0b113b77d4 100644
--- a/modules/navigation/godot_navigation_server.h
+++ b/modules/navigation/godot_navigation_server.h
@@ -36,10 +36,10 @@
#include "core/templates/rid_owner.h"
#include "servers/navigation_server_3d.h"
+#include "nav_agent.h"
#include "nav_link.h"
#include "nav_map.h"
#include "nav_region.h"
-#include "rvo_agent.h"
/// The commands are functions executed during the `sync` phase.
@@ -71,7 +71,7 @@ class GodotNavigationServer : public NavigationServer3D {
mutable RID_Owner<NavLink> link_owner;
mutable RID_Owner<NavMap> map_owner;
mutable RID_Owner<NavRegion> region_owner;
- mutable RID_Owner<RvoAgent> agent_owner;
+ mutable RID_Owner<NavAgent> agent_owner;
bool active = true;
LocalVector<NavMap *> active_maps;
diff --git a/modules/navigation/rvo_agent.cpp b/modules/navigation/nav_agent.cpp
index 40f1e925be..293544c0a5 100644
--- a/modules/navigation/rvo_agent.cpp
+++ b/modules/navigation/nav_agent.cpp
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* rvo_agent.cpp */
+/* nav_agent.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,15 +28,15 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#include "rvo_agent.h"
+#include "nav_agent.h"
#include "nav_map.h"
-void RvoAgent::set_map(NavMap *p_map) {
+void NavAgent::set_map(NavMap *p_map) {
map = p_map;
}
-bool RvoAgent::is_map_changed() {
+bool NavAgent::is_map_changed() {
if (map) {
bool is_changed = map->get_map_update_id() != map_update_id;
map_update_id = map->get_map_update_id();
@@ -46,15 +46,15 @@ bool RvoAgent::is_map_changed() {
}
}
-void RvoAgent::set_callback(Callable p_callback) {
+void NavAgent::set_callback(Callable p_callback) {
callback = p_callback;
}
-bool RvoAgent::has_callback() const {
+bool NavAgent::has_callback() const {
return callback.is_valid();
}
-void RvoAgent::dispatch_callback() {
+void NavAgent::dispatch_callback() {
if (!callback.is_valid()) {
return;
}
diff --git a/modules/navigation/rvo_agent.h b/modules/navigation/nav_agent.h
index 5f377b6079..f154ce14d9 100644
--- a/modules/navigation/rvo_agent.h
+++ b/modules/navigation/nav_agent.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* rvo_agent.h */
+/* nav_agent.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,8 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef RVO_AGENT_H
-#define RVO_AGENT_H
+#ifndef NAV_AGENT_H
+#define NAV_AGENT_H
#include "core/object/class_db.h"
#include "nav_rid.h"
@@ -38,7 +38,7 @@
class NavMap;
-class RvoAgent : public NavRid {
+class NavAgent : public NavRid {
NavMap *map = nullptr;
RVO::Agent agent;
Callable callback = Callable();
@@ -62,4 +62,4 @@ public:
void dispatch_callback();
};
-#endif // RVO_AGENT_H
+#endif // NAV_AGENT_H
diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp
index d763b1d3bc..b1674c8fc5 100644
--- a/modules/navigation/nav_map.cpp
+++ b/modules/navigation/nav_map.cpp
@@ -31,9 +31,9 @@
#include "nav_map.h"
#include "core/object/worker_thread_pool.h"
+#include "nav_agent.h"
#include "nav_link.h"
#include "nav_region.h"
-#include "rvo_agent.h"
#include <algorithm>
#define THREE_POINTS_CROSS_PRODUCT(m_a, m_b, m_c) (((m_c) - (m_a)).cross((m_b) - (m_a)))
@@ -568,18 +568,18 @@ void NavMap::remove_link(NavLink *p_link) {
}
}
-bool NavMap::has_agent(RvoAgent *agent) const {
+bool NavMap::has_agent(NavAgent *agent) const {
return (agents.find(agent) != -1);
}
-void NavMap::add_agent(RvoAgent *agent) {
+void NavMap::add_agent(NavAgent *agent) {
if (!has_agent(agent)) {
agents.push_back(agent);
agents_dirty = true;
}
}
-void NavMap::remove_agent(RvoAgent *agent) {
+void NavMap::remove_agent(NavAgent *agent) {
remove_agent_as_controlled(agent);
int64_t agent_index = agents.find(agent);
if (agent_index != -1) {
@@ -588,7 +588,7 @@ void NavMap::remove_agent(RvoAgent *agent) {
}
}
-void NavMap::set_agent_as_controlled(RvoAgent *agent) {
+void NavMap::set_agent_as_controlled(NavAgent *agent) {
const bool exist = (controlled_agents.find(agent) != -1);
if (!exist) {
ERR_FAIL_COND(!has_agent(agent));
@@ -596,7 +596,7 @@ void NavMap::set_agent_as_controlled(RvoAgent *agent) {
}
}
-void NavMap::remove_agent_as_controlled(RvoAgent *agent) {
+void NavMap::remove_agent_as_controlled(NavAgent *agent) {
int64_t active_avoidance_agent_index = controlled_agents.find(agent);
if (active_avoidance_agent_index != -1) {
controlled_agents.remove_at_unordered(active_avoidance_agent_index);
@@ -895,7 +895,7 @@ void NavMap::sync() {
// cannot use LocalVector here as RVO library expects std::vector to build KdTree
std::vector<RVO::Agent *> raw_agents;
raw_agents.reserve(agents.size());
- for (RvoAgent *agent : agents) {
+ for (NavAgent *agent : agents) {
raw_agents.push_back(agent->get_agent());
}
rvo.buildAgentTree(raw_agents);
@@ -916,7 +916,7 @@ void NavMap::sync() {
pm_edge_free_count = _new_pm_edge_free_count;
}
-void NavMap::compute_single_step(uint32_t index, RvoAgent **agent) {
+void NavMap::compute_single_step(uint32_t index, NavAgent **agent) {
(*(agent + index))->get_agent()->computeNeighbors(&rvo);
(*(agent + index))->get_agent()->computeNewVelocity(deltatime);
}
@@ -930,7 +930,7 @@ void NavMap::step(real_t p_deltatime) {
}
void NavMap::dispatch_callbacks() {
- for (RvoAgent *agent : controlled_agents) {
+ for (NavAgent *agent : controlled_agents) {
agent->dispatch_callback();
}
}
diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h
index fce7aff3ba..ab6a48dd70 100644
--- a/modules/navigation/nav_map.h
+++ b/modules/navigation/nav_map.h
@@ -42,7 +42,7 @@
class NavLink;
class NavRegion;
-class RvoAgent;
+class NavAgent;
class NavMap : public NavRid {
/// Map Up
@@ -78,10 +78,10 @@ class NavMap : public NavRid {
bool agents_dirty = false;
/// All the Agents (even the controlled one)
- LocalVector<RvoAgent *> agents;
+ LocalVector<NavAgent *> agents;
/// Controlled agents
- LocalVector<RvoAgent *> controlled_agents;
+ LocalVector<NavAgent *> controlled_agents;
/// Physics delta time
real_t deltatime = 0.0;
@@ -144,15 +144,15 @@ public:
return links;
}
- bool has_agent(RvoAgent *agent) const;
- void add_agent(RvoAgent *agent);
- void remove_agent(RvoAgent *agent);
- const LocalVector<RvoAgent *> &get_agents() const {
+ bool has_agent(NavAgent *agent) const;
+ void add_agent(NavAgent *agent);
+ void remove_agent(NavAgent *agent);
+ const LocalVector<NavAgent *> &get_agents() const {
return agents;
}
- void set_agent_as_controlled(RvoAgent *agent);
- void remove_agent_as_controlled(RvoAgent *agent);
+ void set_agent_as_controlled(NavAgent *agent);
+ void remove_agent_as_controlled(NavAgent *agent);
uint32_t get_map_update_id() const {
return map_update_id;
@@ -173,7 +173,7 @@ public:
int get_pm_edge_free_count() const { return pm_edge_free_count; }
private:
- void compute_single_step(uint32_t index, RvoAgent **agent);
+ void compute_single_step(uint32_t index, NavAgent **agent);
void clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners) const;
};
diff --git a/modules/webrtc/webrtc_multiplayer_peer.cpp b/modules/webrtc/webrtc_multiplayer_peer.cpp
index 36d0b41889..9224760c5b 100644
--- a/modules/webrtc/webrtc_multiplayer_peer.cpp
+++ b/modules/webrtc/webrtc_multiplayer_peer.cpp
@@ -128,7 +128,6 @@ void WebRTCMultiplayerPeer::poll() {
// Server connected.
connection_status = CONNECTION_CONNECTED;
emit_signal(SNAME("peer_connected"), TARGET_PEER_SERVER);
- emit_signal(SNAME("connection_succeeded"));
} else {
emit_signal(SNAME("peer_connected"), E);
}
diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp
index 389d8c56ad..c12fc5e834 100644
--- a/modules/websocket/websocket_multiplayer_peer.cpp
+++ b/modules/websocket/websocket_multiplayer_peer.cpp
@@ -237,7 +237,6 @@ void WebSocketMultiplayerPeer::_poll_client() {
}
connection_status = CONNECTION_CONNECTED;
emit_signal("peer_connected", 1);
- emit_signal("connection_succeeded");
} else {
return; // Still waiting for an ID.
}