/**************************************************************************/ /* resource_importer_scene.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 "resource_importer_scene.h" #include "core/error/error_macros.h" #include "core/io/dir_access.h" #include "core/io/resource_saver.h" #include "core/object/script_language.h" #include "editor/editor_interface.h" #include "editor/editor_node.h" #include "editor/import/3d/scene_import_settings.h" #include "editor/settings/editor_settings.h" #include "scene/3d/importer_mesh_instance_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/navigation/navigation_region_3d.h" #include "scene/3d/occluder_instance_3d.h" #include "scene/3d/physics/area_3d.h" #include "scene/3d/physics/collision_shape_3d.h" #include "scene/3d/physics/static_body_3d.h" #include "scene/3d/physics/vehicle_body_3d.h" #include "scene/animation/animation_player.h" #include "scene/resources/3d/box_shape_3d.h" #include "scene/resources/3d/importer_mesh.h" #include "scene/resources/3d/separation_ray_shape_3d.h" #include "scene/resources/3d/sphere_shape_3d.h" #include "scene/resources/3d/world_boundary_shape_3d.h" #include "scene/resources/animation.h" #include "scene/resources/bone_map.h" #include "scene/resources/packed_scene.h" #include "scene/resources/resource_format_text.h" void EditorSceneFormatImporter::get_extensions(List *r_extensions) const { Vector arr; if (GDVIRTUAL_CALL(_get_extensions, arr)) { for (int i = 0; i < arr.size(); i++) { r_extensions->push_back(arr[i]); } return; } ERR_FAIL(); } Node *EditorSceneFormatImporter::import_scene(const String &p_path, uint32_t p_flags, const HashMap &p_options, List *r_missing_deps, Error *r_err) { Dictionary options_dict; for (const KeyValue &elem : p_options) { options_dict[elem.key] = elem.value; } Object *ret = nullptr; if (GDVIRTUAL_CALL(_import_scene, p_path, p_flags, options_dict, ret)) { return Object::cast_to(ret); } ERR_FAIL_V(nullptr); } void EditorSceneFormatImporter::add_import_option(const String &p_name, const Variant &p_default_value) { ERR_FAIL_NULL_MSG(current_option_list, "add_import_option() can only be called from get_import_options()."); add_import_option_advanced(p_default_value.get_type(), p_name, p_default_value); } void EditorSceneFormatImporter::add_import_option_advanced(Variant::Type p_type, const String &p_name, const Variant &p_default_value, PropertyHint p_hint, const String &p_hint_string, int p_usage_flags) { ERR_FAIL_NULL_MSG(current_option_list, "add_import_option_advanced() can only be called from get_import_options()."); current_option_list->push_back(ResourceImporter::ImportOption(PropertyInfo(p_type, p_name, p_hint, p_hint_string, p_usage_flags), p_default_value)); } void EditorSceneFormatImporter::get_import_options(const String &p_path, List *r_options) { current_option_list = r_options; GDVIRTUAL_CALL(_get_import_options, p_path); current_option_list = nullptr; } Variant EditorSceneFormatImporter::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap &p_options) { Variant ret; // For compatibility with the old API, pass the import type as a boolean. GDVIRTUAL_CALL(_get_option_visibility, p_path, p_scene_import_type == "AnimationLibrary", p_option, ret); return ret; } void EditorSceneFormatImporter::_bind_methods() { ClassDB::bind_method(D_METHOD("add_import_option", "name", "value"), &EditorSceneFormatImporter::add_import_option); ClassDB::bind_method(D_METHOD("add_import_option_advanced", "type", "name", "default_value", "hint", "hint_string", "usage_flags"), &EditorSceneFormatImporter::add_import_option_advanced, DEFVAL(PROPERTY_HINT_NONE), DEFVAL(""), DEFVAL(PROPERTY_USAGE_DEFAULT)); GDVIRTUAL_BIND(_get_extensions); GDVIRTUAL_BIND(_import_scene, "path", "flags", "options"); GDVIRTUAL_BIND(_get_import_options, "path"); GDVIRTUAL_BIND(_get_option_visibility, "path", "for_animation", "option"); BIND_CONSTANT(IMPORT_SCENE); BIND_CONSTANT(IMPORT_ANIMATION); BIND_CONSTANT(IMPORT_FAIL_ON_MISSING_DEPENDENCIES); BIND_CONSTANT(IMPORT_GENERATE_TANGENT_ARRAYS); BIND_CONSTANT(IMPORT_USE_NAMED_SKIN_BINDS); BIND_CONSTANT(IMPORT_DISCARD_MESHES_AND_MATERIALS); BIND_CONSTANT(IMPORT_FORCE_DISABLE_MESH_COMPRESSION); } ///////////////////////////////// void EditorScenePostImport::_bind_methods() { GDVIRTUAL_BIND(_post_import, "scene") ClassDB::bind_method(D_METHOD("get_source_file"), &EditorScenePostImport::get_source_file); } Node *EditorScenePostImport::post_import(Node *p_scene) { Object *ret; if (GDVIRTUAL_CALL(_post_import, p_scene, ret)) { return Object::cast_to(ret); } return p_scene; } String EditorScenePostImport::get_source_file() const { return source_file; } void EditorScenePostImport::init(const String &p_source_file) { source_file = p_source_file; } /////////////////////////////////////////////////////// Variant EditorScenePostImportPlugin::get_option_value(const StringName &p_name) const { ERR_FAIL_COND_V_MSG(current_options == nullptr && current_options_dict == nullptr, Variant(), "get_option_value called from a function where option values are not available."); ERR_FAIL_COND_V_MSG(current_options && !current_options->has(p_name), Variant(), "get_option_value called with unexisting option argument: " + String(p_name)); ERR_FAIL_COND_V_MSG(current_options_dict && !current_options_dict->has(p_name), Variant(), "get_option_value called with unexisting option argument: " + String(p_name)); if (current_options && current_options->has(p_name)) { return (*current_options)[p_name]; } if (current_options_dict && current_options_dict->has(p_name)) { return (*current_options_dict)[p_name]; } return Variant(); } void EditorScenePostImportPlugin::add_import_option(const String &p_name, const Variant &p_default_value) { ERR_FAIL_NULL_MSG(current_option_list, "add_import_option() can only be called from get_import_options()."); add_import_option_advanced(p_default_value.get_type(), p_name, p_default_value); } void EditorScenePostImportPlugin::add_import_option_advanced(Variant::Type p_type, const String &p_name, const Variant &p_default_value, PropertyHint p_hint, const String &p_hint_string, int p_usage_flags) { ERR_FAIL_NULL_MSG(current_option_list, "add_import_option_advanced() can only be called from get_import_options()."); current_option_list->push_back(ResourceImporter::ImportOption(PropertyInfo(p_type, p_name, p_hint, p_hint_string, p_usage_flags), p_default_value)); } void EditorScenePostImportPlugin::get_internal_import_options(InternalImportCategory p_category, List *r_options) { current_option_list = r_options; GDVIRTUAL_CALL(_get_internal_import_options, p_category); current_option_list = nullptr; } Variant EditorScenePostImportPlugin::get_internal_option_visibility(InternalImportCategory p_category, const String &p_scene_import_type, const String &p_option, const HashMap &p_options) const { current_options = &p_options; Variant ret; // For compatibility with the old API, pass the import type as a boolean. GDVIRTUAL_CALL(_get_internal_option_visibility, p_category, p_scene_import_type == "AnimationLibrary", p_option, ret); current_options = nullptr; return ret; } Variant EditorScenePostImportPlugin::get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const HashMap &p_options) const { current_options = &p_options; Variant ret; GDVIRTUAL_CALL(_get_internal_option_update_view_required, p_category, p_option, ret); current_options = nullptr; return ret; } void EditorScenePostImportPlugin::internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref p_resource, const Dictionary &p_options) { current_options_dict = &p_options; GDVIRTUAL_CALL(_internal_process, p_category, p_base_scene, p_node, p_resource); current_options_dict = nullptr; } void EditorScenePostImportPlugin::get_import_options(const String &p_path, List *r_options) { current_option_list = r_options; GDVIRTUAL_CALL(_get_import_options, p_path); current_option_list = nullptr; } Variant EditorScenePostImportPlugin::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap &p_options) const { current_options = &p_options; Variant ret; GDVIRTUAL_CALL(_get_option_visibility, p_path, p_scene_import_type == "AnimationLibrary", p_option, ret); current_options = nullptr; return ret; } void EditorScenePostImportPlugin::pre_process(Node *p_scene, const HashMap &p_options) { current_options = &p_options; GDVIRTUAL_CALL(_pre_process, p_scene); current_options = nullptr; } void EditorScenePostImportPlugin::post_process(Node *p_scene, const HashMap &p_options) { current_options = &p_options; GDVIRTUAL_CALL(_post_process, p_scene); current_options = nullptr; } void EditorScenePostImportPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("get_option_value", "name"), &EditorScenePostImportPlugin::get_option_value); ClassDB::bind_method(D_METHOD("add_import_option", "name", "value"), &EditorScenePostImportPlugin::add_import_option); ClassDB::bind_method(D_METHOD("add_import_option_advanced", "type", "name", "default_value", "hint", "hint_string", "usage_flags"), &EditorScenePostImportPlugin::add_import_option_advanced, DEFVAL(PROPERTY_HINT_NONE), DEFVAL(""), DEFVAL(PROPERTY_USAGE_DEFAULT)); GDVIRTUAL_BIND(_get_internal_import_options, "category"); GDVIRTUAL_BIND(_get_internal_option_visibility, "category", "for_animation", "option"); GDVIRTUAL_BIND(_get_internal_option_update_view_required, "category", "option"); GDVIRTUAL_BIND(_internal_process, "category", "base_node", "node", "resource"); GDVIRTUAL_BIND(_get_import_options, "path"); GDVIRTUAL_BIND(_get_option_visibility, "path", "for_animation", "option"); GDVIRTUAL_BIND(_pre_process, "scene"); GDVIRTUAL_BIND(_post_process, "scene"); BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_NODE); BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE); BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_MESH); BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_MATERIAL); BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_ANIMATION); BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE); BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE); BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_MAX); } ///////////////////////////////////////////////////////// const String ResourceImporterScene::material_extension[3] = { ".tres", ".res", ".material" }; String ResourceImporterScene::get_importer_name() const { // For compatibility with 4.2 and earlier we need to keep the "scene" and "animation_library" names. // However this is arbitrary so for new import types we can use any string. if (_scene_import_type == "PackedScene") { return "scene"; } else if (_scene_import_type == "AnimationLibrary") { return "animation_library"; } return _scene_import_type; } String ResourceImporterScene::get_visible_name() const { // This is displayed on the UI. Friendly names here are nice but not vital, so fall back to the type. if (_scene_import_type == "PackedScene") { return "Scene"; } return _scene_import_type.capitalize(); } void ResourceImporterScene::get_recognized_extensions(List *p_extensions) const { get_scene_importer_extensions(p_extensions); } String ResourceImporterScene::get_save_extension() const { if (_scene_import_type == "PackedScene") { return "scn"; } return "res"; } String ResourceImporterScene::get_resource_type() const { return _scene_import_type; } int ResourceImporterScene::get_format_version() const { return 1; } bool ResourceImporterScene::get_option_visibility(const String &p_path, const String &p_option, const HashMap &p_options) const { if (_scene_import_type == "PackedScene") { if (p_option.begins_with("animation/")) { if (p_option != "animation/import" && !bool(p_options["animation/import"])) { return false; } } } else if (_scene_import_type == "AnimationLibrary") { if (p_option == "animation/import") { // Option ignored, animation always imported. return false; } if (p_option == "nodes/root_type" || p_option == "nodes/root_name" || p_option.begins_with("meshes/") || p_option.begins_with("skins/")) { return false; // Nothing to do here for animations. } } if (p_option == "nodes/use_node_type_suffixes" && p_options.has("nodes/use_name_suffixes")) { return p_options["nodes/use_name_suffixes"]; } if (p_option == "meshes/lightmap_texel_size" && int(p_options["meshes/light_baking"]) != 2) { // Only display the lightmap texel size import option when using the Static Lightmaps light baking mode. return false; } for (int i = 0; i < post_importer_plugins.size(); i++) { Variant ret = post_importer_plugins.write[i]->get_option_visibility(p_path, _scene_import_type, p_option, p_options); if (ret.get_type() == Variant::BOOL) { if (!ret) { return false; } } } for (Ref importer : scene_importers) { Variant ret = importer->get_option_visibility(p_path, _scene_import_type, p_option, p_options); if (ret.get_type() == Variant::BOOL) { if (!ret) { return false; } } } return true; } int ResourceImporterScene::get_preset_count() const { return 0; } String ResourceImporterScene::get_preset_name(int p_idx) const { return String(); } void ResourceImporterScene::_pre_fix_global(Node *p_scene, const HashMap &p_options) const { if (p_options.has("animation/import_rest_as_RESET") && (bool)p_options["animation/import_rest_as_RESET"]) { TypedArray anim_players = p_scene->find_children("*", "AnimationPlayer"); if (anim_players.is_empty()) { AnimationPlayer *anim_player = memnew(AnimationPlayer); anim_player->set_name("AnimationPlayer"); p_scene->add_child(anim_player); anim_player->set_owner(p_scene); anim_players.append(anim_player); } Ref reset_anim; for (int i = 0; i < anim_players.size(); i++) { AnimationPlayer *player = cast_to(anim_players[i]); if (player->has_animation(SceneStringName(RESET))) { reset_anim = player->get_animation(SceneStringName(RESET)); break; } } if (reset_anim.is_null()) { AnimationPlayer *anim_player = cast_to(anim_players[0]); reset_anim.instantiate(); Ref anim_library; if (anim_player->has_animation_library(StringName())) { anim_library = anim_player->get_animation_library(StringName()); } else { anim_library.instantiate(); anim_player->add_animation_library(StringName(), anim_library); } anim_library->add_animation(SceneStringName(RESET), reset_anim); } TypedArray skeletons = p_scene->find_children("*", "Skeleton3D"); for (int i = 0; i < skeletons.size(); i++) { Skeleton3D *skeleton = cast_to(skeletons[i]); NodePath skeleton_path = p_scene->get_path_to(skeleton); HashSet existing_pos_tracks; HashSet existing_rot_tracks; for (int trk_i = 0; trk_i < reset_anim->get_track_count(); trk_i++) { NodePath np = reset_anim->track_get_path(trk_i); if (reset_anim->track_get_type(trk_i) == Animation::TYPE_POSITION_3D) { existing_pos_tracks.insert(np); } if (reset_anim->track_get_type(trk_i) == Animation::TYPE_ROTATION_3D) { existing_rot_tracks.insert(np); } } for (int bone_i = 0; bone_i < skeleton->get_bone_count(); bone_i++) { NodePath bone_path(skeleton_path.get_names(), Vector{ skeleton->get_bone_name(bone_i) }, false); if (!existing_pos_tracks.has(bone_path)) { int pos_t = reset_anim->add_track(Animation::TYPE_POSITION_3D); reset_anim->track_set_path(pos_t, bone_path); reset_anim->position_track_insert_key(pos_t, 0.0, skeleton->get_bone_rest(bone_i).origin); reset_anim->track_set_imported(pos_t, true); } if (!existing_rot_tracks.has(bone_path)) { int rot_t = reset_anim->add_track(Animation::TYPE_ROTATION_3D); reset_anim->track_set_path(rot_t, bone_path); reset_anim->rotation_track_insert_key(rot_t, 0.0, skeleton->get_bone_rest(bone_i).basis.get_rotation_quaternion()); reset_anim->track_set_imported(rot_t, true); } } } } } static bool _teststr(const String &p_what, const String &p_str) { String what = p_what; // Remove trailing spaces and numbers, some apps like blender add ".number" to duplicates // (dot is replaced with _ as invalid character) so also compensate for this. while (what.length() && (is_digit(what[what.length() - 1]) || what[what.length() - 1] <= 32 || what[what.length() - 1] == '_')) { what = what.substr(0, what.length() - 1); } if (what.containsn("$" + p_str)) { // Blender and other stuff. return true; } if (what.to_lower().ends_with("-" + p_str)) { //collada only supports "_" and "-" besides letters return true; } if (what.to_lower().ends_with("_" + p_str)) { //collada only supports "_" and "-" besides letters return true; } return false; } static String _fixstr(const String &p_what, const String &p_str) { String what = p_what; // Remove trailing spaces and numbers, some apps like blender add ".number" to duplicates // (dot is replaced with _ as invalid character) so also compensate for this. while (what.length() && (is_digit(what[what.length() - 1]) || what[what.length() - 1] <= 32 || what[what.length() - 1] == '_')) { what = what.substr(0, what.length() - 1); } String end = p_what.substr(what.length()); if (what.containsn("$" + p_str)) { // Blender and other stuff. return what.replace("$" + p_str, "") + end; } if (what.to_lower().ends_with("-" + p_str)) { //collada only supports "_" and "-" besides letters return what.substr(0, what.length() - (p_str.length() + 1)) + end; } if (what.to_lower().ends_with("_" + p_str)) { //collada only supports "_" and "-" besides letters return what.substr(0, what.length() - (p_str.length() + 1)) + end; } return what; } static void _pre_gen_shape_list(Ref &mesh, Vector> &r_shape_list, bool p_convex) { ERR_FAIL_COND_MSG(mesh.is_null(), "Cannot generate shape list with null mesh value."); if (!p_convex) { Ref shape = mesh->create_trimesh_shape(); r_shape_list.push_back(shape); } else { Vector> cd; cd.push_back(mesh->create_convex_shape(true, /*Passing false, otherwise VHACD will be used to simplify (Decompose) the Mesh.*/ false)); if (cd.size()) { for (int i = 0; i < cd.size(); i++) { r_shape_list.push_back(cd[i]); } } } } struct ScalableNodeCollection { HashSet node_3ds; HashSet> importer_meshes; HashSet> skins; HashSet> animations; }; void _rescale_importer_mesh(Vector3 p_scale, Ref p_mesh, bool is_shadow = false) { // MESH and SKIN data divide, to compensate for object position multiplying. const int surf_count = p_mesh->get_surface_count(); const int blendshape_count = p_mesh->get_blend_shape_count(); struct LocalSurfData { Mesh::PrimitiveType prim = {}; Array arr; Array bsarr; Dictionary lods; String name; Ref mat; uint64_t fmt_compress_flags = 0; }; Vector surf_data_by_mesh; Vector blendshape_names; for (int bsidx = 0; bsidx < blendshape_count; bsidx++) { blendshape_names.append(p_mesh->get_blend_shape_name(bsidx)); } for (int surf_idx = 0; surf_idx < surf_count; surf_idx++) { Mesh::PrimitiveType prim = p_mesh->get_surface_primitive_type(surf_idx); const uint64_t fmt_compress_flags = p_mesh->get_surface_format(surf_idx); Array arr = p_mesh->get_surface_arrays(surf_idx); String name = p_mesh->get_surface_name(surf_idx); Dictionary lods; Ref mat = p_mesh->get_surface_material(surf_idx); { Vector vertex_array = arr[ArrayMesh::ARRAY_VERTEX]; for (int vert_arr_i = 0; vert_arr_i < vertex_array.size(); vert_arr_i++) { vertex_array.write[vert_arr_i] = vertex_array[vert_arr_i] * p_scale; } arr[ArrayMesh::ARRAY_VERTEX] = vertex_array; } Array blendshapes; for (int bsidx = 0; bsidx < blendshape_count; bsidx++) { Array current_bsarr = p_mesh->get_surface_blend_shape_arrays(surf_idx, bsidx); Vector current_bs_vertex_array = current_bsarr[ArrayMesh::ARRAY_VERTEX]; int current_bs_vert_arr_len = current_bs_vertex_array.size(); for (int32_t bs_vert_arr_i = 0; bs_vert_arr_i < current_bs_vert_arr_len; bs_vert_arr_i++) { current_bs_vertex_array.write[bs_vert_arr_i] = current_bs_vertex_array[bs_vert_arr_i] * p_scale; } current_bsarr[ArrayMesh::ARRAY_VERTEX] = current_bs_vertex_array; blendshapes.push_back(current_bsarr); } LocalSurfData surf_data_dictionary = LocalSurfData(); surf_data_dictionary.prim = prim; surf_data_dictionary.arr = arr; surf_data_dictionary.bsarr = blendshapes; surf_data_dictionary.lods = lods; surf_data_dictionary.fmt_compress_flags = fmt_compress_flags; surf_data_dictionary.name = name; surf_data_dictionary.mat = mat; surf_data_by_mesh.push_back(surf_data_dictionary); } p_mesh->clear(); for (int bsidx = 0; bsidx < blendshape_count; bsidx++) { p_mesh->add_blend_shape(blendshape_names[bsidx]); } for (int surf_idx = 0; surf_idx < surf_count; surf_idx++) { const Mesh::PrimitiveType prim = surf_data_by_mesh[surf_idx].prim; const Array arr = surf_data_by_mesh[surf_idx].arr; const Array bsarr = surf_data_by_mesh[surf_idx].bsarr; const Dictionary lods = surf_data_by_mesh[surf_idx].lods; const uint64_t fmt_compress_flags = surf_data_by_mesh[surf_idx].fmt_compress_flags; const String name = surf_data_by_mesh[surf_idx].name; const Ref mat = surf_data_by_mesh[surf_idx].mat; p_mesh->add_surface(prim, arr, bsarr, lods, mat, name, fmt_compress_flags); } if (!is_shadow && p_mesh->get_shadow_mesh() != p_mesh && p_mesh->get_shadow_mesh().is_valid()) { _rescale_importer_mesh(p_scale, p_mesh->get_shadow_mesh(), true); } } void _rescale_skin(Vector3 p_scale, Ref p_skin) { // MESH and SKIN data divide, to compensate for object position multiplying. for (int i = 0; i < p_skin->get_bind_count(); i++) { Transform3D transform = p_skin->get_bind_pose(i); p_skin->set_bind_pose(i, Transform3D(transform.basis, p_scale * transform.origin)); } } void _rescale_animation(Vector3 p_scale, Ref p_animation) { for (int track_idx = 0; track_idx < p_animation->get_track_count(); track_idx++) { if (p_animation->track_get_type(track_idx) == Animation::TYPE_POSITION_3D) { for (int key_idx = 0; key_idx < p_animation->track_get_key_count(track_idx); key_idx++) { Vector3 value = p_animation->track_get_key_value(track_idx, key_idx); value = p_scale * value; p_animation->track_set_key_value(track_idx, key_idx, value); } } } } void _apply_scale_to_scalable_node_collection(ScalableNodeCollection &p_collection, Vector3 p_scale) { for (Node3D *node_3d : p_collection.node_3ds) { node_3d->set_position(p_scale * node_3d->get_position()); Skeleton3D *skeleton_3d = Object::cast_to(node_3d); if (skeleton_3d) { for (int i = 0; i < skeleton_3d->get_bone_count(); i++) { Transform3D rest = skeleton_3d->get_bone_rest(i); Vector3 position = skeleton_3d->get_bone_pose_position(i); skeleton_3d->set_bone_rest(i, Transform3D(rest.basis, p_scale * rest.origin)); skeleton_3d->set_bone_pose_position(i, p_scale * position); } } } for (Ref mesh : p_collection.importer_meshes) { _rescale_importer_mesh(p_scale, mesh, false); } for (Ref skin : p_collection.skins) { _rescale_skin(p_scale, skin); } for (Ref animation : p_collection.animations) { _rescale_animation(p_scale, animation); } } void _populate_scalable_nodes_collection(Node *p_node, ScalableNodeCollection &p_collection) { if (!p_node) { return; } Node3D *node_3d = Object::cast_to(p_node); if (node_3d) { p_collection.node_3ds.insert(node_3d); ImporterMeshInstance3D *mesh_instance_3d = Object::cast_to(p_node); if (mesh_instance_3d) { Ref mesh = mesh_instance_3d->get_mesh(); if (mesh.is_valid()) { p_collection.importer_meshes.insert(mesh); } Ref skin = mesh_instance_3d->get_skin(); if (skin.is_valid()) { p_collection.skins.insert(skin); } } } AnimationPlayer *animation_player = Object::cast_to(p_node); if (animation_player) { List animation_list; animation_player->get_animation_list(&animation_list); for (const StringName &E : animation_list) { Ref animation = animation_player->get_animation(E); p_collection.animations.insert(animation); } } for (int i = 0; i < p_node->get_child_count(); i++) { Node *child = p_node->get_child(i); _populate_scalable_nodes_collection(child, p_collection); } } void _apply_permanent_scale_to_descendants(Node *p_root_node, Vector3 p_scale) { ScalableNodeCollection scalable_node_collection; _populate_scalable_nodes_collection(p_root_node, scalable_node_collection); _apply_scale_to_scalable_node_collection(scalable_node_collection, p_scale); } Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap, Vector>> &r_collision_map, Pair *r_occluder_arrays, List> &r_node_renames, const HashMap &p_options) { bool use_name_suffixes = true; if (p_options.has("nodes/use_name_suffixes")) { use_name_suffixes = p_options["nodes/use_name_suffixes"]; } if (!use_name_suffixes) { return p_node; } // Children first. for (int i = 0; i < p_node->get_child_count(); i++) { Node *r = _pre_fix_node(p_node->get_child(i), p_root, r_collision_map, r_occluder_arrays, r_node_renames, p_options); if (!r) { i--; // Was erased. } } String name = p_node->get_name(); NodePath original_path = p_root->get_path_to(p_node); // Used to detect renames due to import hints. Ref original_meta = memnew(Resource); // Create temp resource to hold original meta original_meta->merge_meta_from(p_node); bool isroot = p_node == p_root; if (!isroot && _teststr(name, "noimp")) { p_node->set_owner(nullptr); memdelete(p_node); return nullptr; } if (Object::cast_to(p_node)) { ImporterMeshInstance3D *mi = Object::cast_to(p_node); Ref m = mi->get_mesh(); if (m.is_valid()) { for (int i = 0; i < m->get_surface_count(); i++) { Ref mat = m->get_surface_material(i); if (mat.is_null()) { continue; } if (_teststr(mat->get_name(), "alpha")) { mat->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA); mat->set_name(_fixstr(mat->get_name(), "alpha")); } if (_teststr(mat->get_name(), "vcol")) { mat->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); mat->set_flag(BaseMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); mat->set_name(_fixstr(mat->get_name(), "vcol")); } } } } if (Object::cast_to(p_node)) { AnimationPlayer *ap = Object::cast_to(p_node); // Node paths in animation tracks are relative to the following path (this is used to fix node paths below). Node *ap_root = ap->get_node(ap->get_root_node()); NodePath path_prefix = p_root->get_path_to(ap_root); bool nodes_were_renamed = r_node_renames.size() != 0; List anims; ap->get_animation_list(&anims); for (const StringName &E : anims) { Ref anim = ap->get_animation(E); ERR_CONTINUE(anim.is_null()); // Remove animation tracks referencing non-importable nodes. for (int i = 0; i < anim->get_track_count(); i++) { NodePath path = anim->track_get_path(i); for (int j = 0; j < path.get_name_count(); j++) { String node = path.get_name(j); if (_teststr(node, "noimp")) { anim->remove_track(i); i--; break; } } } // Fix node paths in animations, in case nodes were renamed earlier due to import hints. if (nodes_were_renamed) { for (int i = 0; i < anim->get_track_count(); i++) { NodePath path = anim->track_get_path(i); // Convert track path to absolute node path without subnames (some manual work because we are not in the scene tree). Vector absolute_path_names = path_prefix.get_names(); absolute_path_names.append_array(path.get_names()); NodePath absolute_path(absolute_path_names, false); absolute_path.simplify(); // Fix paths to renamed nodes. for (const Pair &F : r_node_renames) { if (F.first == absolute_path) { NodePath new_path(ap_root->get_path_to(F.second).get_names(), path.get_subnames(), false); print_verbose(vformat("Fix: Correcting node path in animation track: %s should be %s", path, new_path)); anim->track_set_path(i, new_path); break; // Only one match is possible. } } } } String animname = E; const int loop_string_count = 3; static const char *loop_strings[loop_string_count] = { "loop_mode", "loop", "cycle" }; for (int i = 0; i < loop_string_count; i++) { if (_teststr(animname, loop_strings[i])) { anim->set_loop_mode(Animation::LOOP_LINEAR); animname = _fixstr(animname, loop_strings[i]); Ref library = ap->get_animation_library(ap->find_animation_library(anim)); library->rename_animation(E, animname); } } } } bool use_node_type_suffixes = true; if (p_options.has("nodes/use_node_type_suffixes")) { use_node_type_suffixes = p_options["nodes/use_node_type_suffixes"]; } if (!use_node_type_suffixes) { return p_node; } if (_teststr(name, "colonly") || _teststr(name, "convcolonly")) { if (isroot) { return p_node; } String fixed_name; if (_teststr(name, "colonly")) { fixed_name = _fixstr(name, "colonly"); } else if (_teststr(name, "convcolonly")) { fixed_name = _fixstr(name, "convcolonly"); } if (fixed_name.is_empty()) { p_node->set_owner(nullptr); memdelete(p_node); ERR_FAIL_V_MSG(nullptr, vformat("Skipped node `%s` because its name is empty after removing the suffix.", name)); } ImporterMeshInstance3D *mi = Object::cast_to(p_node); if (mi) { Ref mesh = mi->get_mesh(); if (mesh.is_valid()) { Vector> shapes; if (r_collision_map.has(mesh)) { shapes = r_collision_map[mesh]; } else if (_teststr(name, "colonly")) { _pre_gen_shape_list(mesh, shapes, false); r_collision_map[mesh] = shapes; } else if (_teststr(name, "convcolonly")) { _pre_gen_shape_list(mesh, shapes, true); r_collision_map[mesh] = shapes; } if (shapes.size()) { StaticBody3D *col = memnew(StaticBody3D); col->set_transform(mi->get_transform()); col->set_name(fixed_name); _copy_meta(p_node, col); p_node->replace_by(col); p_node->set_owner(nullptr); memdelete(p_node); p_node = col; _add_shapes(col, shapes); } } } else if (p_node->has_meta("empty_draw_type")) { String empty_draw_type = String(p_node->get_meta("empty_draw_type")); StaticBody3D *sb = memnew(StaticBody3D); sb->set_name(fixed_name); Object::cast_to(sb)->set_transform(Object::cast_to(p_node)->get_transform()); _copy_meta(p_node, sb); p_node->replace_by(sb); p_node->set_owner(nullptr); memdelete(p_node); p_node = sb; CollisionShape3D *colshape = memnew(CollisionShape3D); if (empty_draw_type == "CUBE") { BoxShape3D *boxShape = memnew(BoxShape3D); boxShape->set_size(Vector3(2, 2, 2)); colshape->set_shape(boxShape); } else if (empty_draw_type == "SINGLE_ARROW") { SeparationRayShape3D *rayShape = memnew(SeparationRayShape3D); rayShape->set_length(1); colshape->set_shape(rayShape); Object::cast_to(sb)->rotate_x(Math::PI / 2); } else if (empty_draw_type == "IMAGE") { WorldBoundaryShape3D *world_boundary_shape = memnew(WorldBoundaryShape3D); colshape->set_shape(world_boundary_shape); } else { SphereShape3D *sphereShape = memnew(SphereShape3D); sphereShape->set_radius(1); colshape->set_shape(sphereShape); } sb->add_child(colshape, true); colshape->set_owner(sb->get_owner()); } } else if (_teststr(name, "rigid") && Object::cast_to(p_node)) { if (isroot) { return p_node; } ImporterMeshInstance3D *mi = Object::cast_to(p_node); Ref mesh = mi->get_mesh(); if (mesh.is_valid()) { Vector> shapes; if (r_collision_map.has(mesh)) { shapes = r_collision_map[mesh]; } else { _pre_gen_shape_list(mesh, shapes, true); } RigidBody3D *rigid_body = memnew(RigidBody3D); rigid_body->set_name(_fixstr(name, "rigid_body")); _copy_meta(p_node, rigid_body); p_node->replace_by(rigid_body); rigid_body->set_transform(mi->get_transform()); p_node = rigid_body; mi->set_transform(Transform3D()); rigid_body->add_child(mi, true); mi->set_owner(rigid_body->get_owner()); _add_shapes(rigid_body, shapes); } } else if ((_teststr(name, "col") || (_teststr(name, "convcol"))) && Object::cast_to(p_node)) { ImporterMeshInstance3D *mi = Object::cast_to(p_node); Ref mesh = mi->get_mesh(); if (mesh.is_valid()) { Vector> shapes; String fixed_name; if (r_collision_map.has(mesh)) { shapes = r_collision_map[mesh]; } else if (_teststr(name, "col")) { _pre_gen_shape_list(mesh, shapes, false); r_collision_map[mesh] = shapes; } else if (_teststr(name, "convcol")) { _pre_gen_shape_list(mesh, shapes, true); r_collision_map[mesh] = shapes; } if (_teststr(name, "col")) { fixed_name = _fixstr(name, "col"); } else if (_teststr(name, "convcol")) { fixed_name = _fixstr(name, "convcol"); } if (!fixed_name.is_empty()) { if (mi->get_parent() && !mi->get_parent()->has_node(fixed_name)) { mi->set_name(fixed_name); } } if (shapes.size()) { StaticBody3D *col = memnew(StaticBody3D); mi->add_child(col, true); col->set_owner(mi->get_owner()); _add_shapes(col, shapes); } } } else if (_teststr(name, "navmesh") && Object::cast_to(p_node)) { if (isroot) { return p_node; } ImporterMeshInstance3D *mi = Object::cast_to(p_node); Ref mesh = mi->get_mesh(); ERR_FAIL_COND_V(mesh.is_null(), nullptr); NavigationRegion3D *nmi = memnew(NavigationRegion3D); nmi->set_name(_fixstr(name, "navmesh")); Ref nmesh = mesh->create_navigation_mesh(); nmi->set_navigation_mesh(nmesh); Object::cast_to(nmi)->set_transform(mi->get_transform()); _copy_meta(p_node, nmi); p_node->replace_by(nmi); p_node->set_owner(nullptr); memdelete(p_node); p_node = nmi; } else if (_teststr(name, "occ") || _teststr(name, "occonly")) { if (isroot) { return p_node; } ImporterMeshInstance3D *mi = Object::cast_to(p_node); if (mi) { Ref mesh = mi->get_mesh(); if (mesh.is_valid()) { if (r_occluder_arrays) { OccluderInstance3D::bake_single_node(mi, 0.0f, r_occluder_arrays->first, r_occluder_arrays->second); } if (_teststr(name, "occ")) { String fixed_name = _fixstr(name, "occ"); if (!fixed_name.is_empty()) { if (mi->get_parent() && !mi->get_parent()->has_node(fixed_name)) { mi->set_name(fixed_name); } } } else { p_node->set_owner(nullptr); memdelete(p_node); p_node = nullptr; } } } } else if (_teststr(name, "vehicle")) { if (isroot) { return p_node; } Node *owner = p_node->get_owner(); Node3D *s = Object::cast_to(p_node); VehicleBody3D *bv = memnew(VehicleBody3D); String n = _fixstr(p_node->get_name(), "vehicle"); bv->set_name(n); _copy_meta(p_node, bv); p_node->replace_by(bv); p_node->set_name(n); bv->add_child(p_node); bv->set_owner(owner); p_node->set_owner(owner); bv->set_transform(s->get_transform()); s->set_transform(Transform3D()); p_node = bv; } else if (_teststr(name, "wheel")) { if (isroot) { return p_node; } Node *owner = p_node->get_owner(); Node3D *s = Object::cast_to(p_node); VehicleWheel3D *bv = memnew(VehicleWheel3D); String n = _fixstr(p_node->get_name(), "wheel"); bv->set_name(n); _copy_meta(p_node, bv); p_node->replace_by(bv); p_node->set_name(n); bv->add_child(p_node); bv->set_owner(owner); p_node->set_owner(owner); bv->set_transform(s->get_transform()); s->set_transform(Transform3D()); p_node = bv; } else if (Object::cast_to(p_node)) { //last attempt, maybe collision inside the mesh data ImporterMeshInstance3D *mi = Object::cast_to(p_node); Ref mesh = mi->get_mesh(); if (mesh.is_valid()) { Vector> shapes; if (r_collision_map.has(mesh)) { shapes = r_collision_map[mesh]; } else if (_teststr(mesh->get_name(), "col")) { _pre_gen_shape_list(mesh, shapes, false); r_collision_map[mesh] = shapes; mesh->set_name(_fixstr(mesh->get_name(), "col")); } else if (_teststr(mesh->get_name(), "convcol")) { _pre_gen_shape_list(mesh, shapes, true); r_collision_map[mesh] = shapes; mesh->set_name(_fixstr(mesh->get_name(), "convcol")); } else if (_teststr(mesh->get_name(), "occ")) { if (r_occluder_arrays) { OccluderInstance3D::bake_single_node(mi, 0.0f, r_occluder_arrays->first, r_occluder_arrays->second); } mesh->set_name(_fixstr(mesh->get_name(), "occ")); } if (shapes.size()) { StaticBody3D *col = memnew(StaticBody3D); p_node->add_child(col, true); col->set_owner(p_node->get_owner()); _add_shapes(col, shapes); } } } if (p_node) { NodePath new_path = p_root->get_path_to(p_node); if (new_path != original_path) { print_verbose(vformat("Fix: Renamed %s to %s", original_path, new_path)); r_node_renames.push_back({ original_path, p_node }); } // If we created new node instead, merge meta values from the original node. p_node->merge_meta_from(*original_meta); } return p_node; } Node *ResourceImporterScene::_pre_fix_animations(Node *p_node, Node *p_root, const Dictionary &p_node_data, const Dictionary &p_animation_data, float p_animation_fps) { // children first for (int i = 0; i < p_node->get_child_count(); i++) { Node *r = _pre_fix_animations(p_node->get_child(i), p_root, p_node_data, p_animation_data, p_animation_fps); if (!r) { i--; //was erased } } String import_id = p_node->get_meta("import_id", "PATH:" + String(p_root->get_path_to(p_node))); Dictionary node_settings; if (p_node_data.has(import_id)) { node_settings = p_node_data[import_id]; } { //make sure this is unique node_settings = node_settings.duplicate(true); //fill node settings for this node with default values List iopts; get_internal_import_options(INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, &iopts); for (const ImportOption &E : iopts) { if (!node_settings.has(E.option.name)) { node_settings[E.option.name] = E.default_value; } } } if (Object::cast_to(p_node)) { AnimationPlayer *ap = Object::cast_to(p_node); List anims; ap->get_animation_list(&anims); AnimationImportTracks import_tracks_mode[TRACK_CHANNEL_MAX] = { AnimationImportTracks(int(node_settings["import_tracks/position"])), AnimationImportTracks(int(node_settings["import_tracks/rotation"])), AnimationImportTracks(int(node_settings["import_tracks/scale"])) }; if (!anims.is_empty() && (import_tracks_mode[0] != ANIMATION_IMPORT_TRACKS_IF_PRESENT || import_tracks_mode[1] != ANIMATION_IMPORT_TRACKS_IF_PRESENT || import_tracks_mode[2] != ANIMATION_IMPORT_TRACKS_IF_PRESENT)) { _optimize_track_usage(ap, import_tracks_mode); } } return p_node; } Node *ResourceImporterScene::_post_fix_animations(Node *p_node, Node *p_root, const Dictionary &p_node_data, const Dictionary &p_animation_data, float p_animation_fps, bool p_remove_immutable_tracks) { // children first for (int i = 0; i < p_node->get_child_count(); i++) { Node *r = _post_fix_animations(p_node->get_child(i), p_root, p_node_data, p_animation_data, p_animation_fps, p_remove_immutable_tracks); if (!r) { i--; //was erased } } String import_id = p_node->get_meta("import_id", "PATH:" + String(p_root->get_path_to(p_node))); Dictionary node_settings; if (p_node_data.has(import_id)) { node_settings = p_node_data[import_id]; } { //make sure this is unique node_settings = node_settings.duplicate(true); //fill node settings for this node with default values List iopts; get_internal_import_options(INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, &iopts); for (const ImportOption &E : iopts) { if (!node_settings.has(E.option.name)) { node_settings[E.option.name] = E.default_value; } } } if (Object::cast_to(p_node)) { AnimationPlayer *ap = Object::cast_to(p_node); List anims; ap->get_animation_list(&anims); if (p_remove_immutable_tracks) { AnimationImportTracks import_tracks_mode[TRACK_CHANNEL_MAX] = { AnimationImportTracks(int(node_settings["import_tracks/position"])), AnimationImportTracks(int(node_settings["import_tracks/rotation"])), AnimationImportTracks(int(node_settings["import_tracks/scale"])) }; HashMap used_tracks[TRACK_CHANNEL_MAX]; for (const StringName &name : anims) { Ref anim = ap->get_animation(name); int track_count = anim->get_track_count(); LocalVector tracks_to_keep; for (int track_i = 0; track_i < track_count; track_i++) { tracks_to_keep.push_back(track_i); int track_channel_type = 0; switch (anim->track_get_type(track_i)) { case Animation::TYPE_POSITION_3D: track_channel_type = TRACK_CHANNEL_POSITION; break; case Animation::TYPE_ROTATION_3D: track_channel_type = TRACK_CHANNEL_ROTATION; break; case Animation::TYPE_SCALE_3D: track_channel_type = TRACK_CHANNEL_SCALE; break; default: continue; } AnimationImportTracks track_mode = import_tracks_mode[track_channel_type]; NodePath path = anim->track_get_path(track_i); Node *n = p_root->get_node(path); Node3D *n3d = Object::cast_to(n); Skeleton3D *skel = Object::cast_to(n); bool keep_track = false; Vector3 loc; Quaternion rot; Vector3 scale; if (skel && path.get_subname_count() > 0) { StringName bone = path.get_subname(0); int bone_idx = skel->find_bone(bone); if (bone_idx == -1) { continue; } // Note that this is using get_bone_pose to update the bone pose cache. Transform3D bone_rest = skel->get_bone_rest(bone_idx); loc = bone_rest.origin / skel->get_motion_scale(); rot = bone_rest.basis.get_rotation_quaternion(); scale = bone_rest.basis.get_scale(); } else if (n3d) { loc = n3d->get_position(); rot = n3d->get_transform().basis.get_rotation_quaternion(); scale = n3d->get_scale(); } else { continue; } if (track_mode == ANIMATION_IMPORT_TRACKS_IF_PRESENT_FOR_ALL) { if (used_tracks[track_channel_type].has(path)) { if (used_tracks[track_channel_type][path]) { continue; } } else { used_tracks[track_channel_type].insert(path, false); } } for (int key_i = 0; key_i < anim->track_get_key_count(track_i) && !keep_track; key_i++) { switch (track_channel_type) { case TRACK_CHANNEL_POSITION: { Vector3 key_pos; anim->position_track_get_key(track_i, key_i, &key_pos); if (!key_pos.is_equal_approx(loc)) { keep_track = true; if (track_mode == ANIMATION_IMPORT_TRACKS_IF_PRESENT_FOR_ALL) { used_tracks[track_channel_type][path] = true; } } } break; case TRACK_CHANNEL_ROTATION: { Quaternion key_rot; anim->rotation_track_get_key(track_i, key_i, &key_rot); if (!key_rot.is_equal_approx(rot)) { keep_track = true; if (track_mode == ANIMATION_IMPORT_TRACKS_IF_PRESENT_FOR_ALL) { used_tracks[track_channel_type][path] = true; } } } break; case TRACK_CHANNEL_SCALE: { Vector3 key_scl; anim->scale_track_get_key(track_i, key_i, &key_scl); if (!key_scl.is_equal_approx(scale)) { keep_track = true; if (track_mode == ANIMATION_IMPORT_TRACKS_IF_PRESENT_FOR_ALL) { used_tracks[track_channel_type][path] = true; } } } break; default: break; } } if (track_mode != ANIMATION_IMPORT_TRACKS_IF_PRESENT_FOR_ALL && !keep_track) { tracks_to_keep.remove_at(tracks_to_keep.size() - 1); } } for (int dst_track_i = 0; dst_track_i < (int)tracks_to_keep.size(); dst_track_i++) { int src_track_i = tracks_to_keep[dst_track_i]; if (src_track_i != dst_track_i) { anim->track_swap(src_track_i, dst_track_i); } } for (int track_i = track_count - 1; track_i >= (int)tracks_to_keep.size(); track_i--) { anim->remove_track(track_i); } } for (const StringName &name : anims) { Ref anim = ap->get_animation(name); int track_count = anim->get_track_count(); LocalVector tracks_to_keep; for (int track_i = 0; track_i < track_count; track_i++) { tracks_to_keep.push_back(track_i); int track_channel_type = 0; switch (anim->track_get_type(track_i)) { case Animation::TYPE_POSITION_3D: track_channel_type = TRACK_CHANNEL_POSITION; break; case Animation::TYPE_ROTATION_3D: track_channel_type = TRACK_CHANNEL_ROTATION; break; case Animation::TYPE_SCALE_3D: track_channel_type = TRACK_CHANNEL_SCALE; break; default: continue; } AnimationImportTracks track_mode = import_tracks_mode[track_channel_type]; if (track_mode == ANIMATION_IMPORT_TRACKS_IF_PRESENT_FOR_ALL) { NodePath path = anim->track_get_path(track_i); if (used_tracks[track_channel_type].has(path) && !used_tracks[track_channel_type][path]) { tracks_to_keep.remove_at(tracks_to_keep.size() - 1); } } } for (int dst_track_i = 0; dst_track_i < (int)tracks_to_keep.size(); dst_track_i++) { int src_track_i = tracks_to_keep[dst_track_i]; if (src_track_i != dst_track_i) { anim->track_swap(src_track_i, dst_track_i); } } for (int track_i = track_count - 1; track_i >= (int)tracks_to_keep.size(); track_i--) { anim->remove_track(track_i); } } } bool use_optimizer = node_settings["optimizer/enabled"]; float anim_optimizer_linerr = node_settings["optimizer/max_velocity_error"]; float anim_optimizer_angerr = node_settings["optimizer/max_angular_error"]; int anim_optimizer_preerr = node_settings["optimizer/max_precision_error"]; if (use_optimizer) { _optimize_animations(ap, anim_optimizer_linerr, anim_optimizer_angerr, anim_optimizer_preerr); } bool use_compression = node_settings["compression/enabled"]; int anim_compression_page_size = node_settings["compression/page_size"]; if (use_compression) { _compress_animations(ap, anim_compression_page_size); } for (const StringName &name : anims) { Ref anim = ap->get_animation(name); Array animation_slices; if (p_animation_data.has(name)) { Dictionary anim_settings = p_animation_data[name]; { int slice_count = anim_settings["slices/amount"]; for (int i = 0; i < slice_count; i++) { String slice_name = anim_settings["slice_" + itos(i + 1) + "/name"]; int from_frame = anim_settings["slice_" + itos(i + 1) + "/start_frame"]; int end_frame = anim_settings["slice_" + itos(i + 1) + "/end_frame"]; Animation::LoopMode loop_mode = static_cast((int)anim_settings["slice_" + itos(i + 1) + "/loop_mode"]); bool save_to_file = anim_settings["slice_" + itos(i + 1) + "/save_to_file/enabled"]; String save_to_path = anim_settings["slice_" + itos(i + 1) + "/save_to_file/path"]; bool save_to_file_keep_custom = anim_settings["slice_" + itos(i + 1) + "/save_to_file/keep_custom_tracks"]; animation_slices.push_back(slice_name); animation_slices.push_back(from_frame / p_animation_fps); animation_slices.push_back(end_frame / p_animation_fps); animation_slices.push_back(loop_mode); animation_slices.push_back(save_to_file); animation_slices.push_back(save_to_path); animation_slices.push_back(save_to_file_keep_custom); } if (animation_slices.size() > 0) { _create_slices(ap, anim, animation_slices, true); } } { //fill with default values List iopts; get_internal_import_options(INTERNAL_IMPORT_CATEGORY_ANIMATION, &iopts); for (const ImportOption &F : iopts) { if (!anim_settings.has(F.option.name)) { anim_settings[F.option.name] = F.default_value; } } } anim->set_loop_mode(static_cast((int)anim_settings["settings/loop_mode"])); bool save = anim_settings["save_to_file/enabled"]; String path = anim_settings["save_to_file/path"]; bool keep_custom = anim_settings["save_to_file/keep_custom_tracks"]; Ref saved_anim = _save_animation_to_file(anim, save, path, keep_custom); if (saved_anim != anim) { Ref al = ap->get_animation_library(ap->find_animation_library(anim)); al->add_animation(name, saved_anim); //replace } } } } return p_node; } Node *ResourceImporterScene::_replace_node_with_type_and_script(Node *p_node, String p_node_type, Ref