diff options
Diffstat (limited to 'editor')
42 files changed, 1551 insertions, 1093 deletions
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 14ca35a664..2ee7f0ffa5 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -165,20 +165,38 @@ public: } switch (animation->track_get_type(track)) { - case Animation::TYPE_TRANSFORM3D: { - Dictionary d_old = animation->track_get_key_value(track, key); - Dictionary d_new = d_old.duplicate(); - d_new[p_name] = p_value; - setting = true; - undo_redo->create_action(TTR("Anim Change Transform")); - undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, d_new); - undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old); - undo_redo->add_do_method(this, "_update_obj", animation); - undo_redo->add_undo_method(this, "_update_obj", animation); - undo_redo->commit_action(); + case Animation::TYPE_POSITION_3D: + case Animation::TYPE_ROTATION_3D: + case Animation::TYPE_SCALE_3D: { + if (name == "position" || name == "rotation" || name == "scale") { + Variant old = animation->track_get_key_value(track, key); + setting = true; + String chan; + switch (animation->track_get_type(track)) { + case Animation::TYPE_POSITION_3D: + chan = "Position3D"; + break; + case Animation::TYPE_ROTATION_3D: + chan = "Rotation3D"; + break; + case Animation::TYPE_SCALE_3D: + chan = "Scale3D"; + break; + default: { + } + } + + undo_redo->create_action(vformat(TTR("Anim Change %s"), chan)); + undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, p_value); + undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, old); + undo_redo->add_do_method(this, "_update_obj", animation); + undo_redo->add_undo_method(this, "_update_obj", animation); + undo_redo->commit_action(); + + setting = false; + return true; + } - setting = false; - return true; } break; case Animation::TYPE_VALUE: { if (name == "value") { @@ -412,12 +430,13 @@ public: } switch (animation->track_get_type(track)) { - case Animation::TYPE_TRANSFORM3D: { - Dictionary d = animation->track_get_key_value(track, key); - ERR_FAIL_COND_V(!d.has(name), false); - r_ret = d[p_name]; - return true; - + case Animation::TYPE_POSITION_3D: + case Animation::TYPE_ROTATION_3D: + case Animation::TYPE_SCALE_3D: { + if (name == "position" || name == "rotation" || name == "scale") { + r_ret = animation->track_get_key_value(track, key); + return true; + } } break; case Animation::TYPE_VALUE: { if (name == "value") { @@ -523,11 +542,14 @@ public: } switch (animation->track_get_type(track)) { - case Animation::TYPE_TRANSFORM3D: { - p_list->push_back(PropertyInfo(Variant::VECTOR3, "location")); - p_list->push_back(PropertyInfo(Variant::QUATERNION, "rotation")); + case Animation::TYPE_POSITION_3D: { + p_list->push_back(PropertyInfo(Variant::VECTOR3, "position")); + } break; + case Animation::TYPE_ROTATION_3D: { + p_list->push_back(PropertyInfo(Variant::VECTOR3, "rotation")); + } break; + case Animation::TYPE_SCALE_3D: { p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale")); - } break; case Animation::TYPE_VALUE: { Variant v = animation->track_get_key_value(track, key); @@ -779,17 +801,31 @@ public: } switch (animation->track_get_type(track)) { - case Animation::TYPE_TRANSFORM3D: { - Dictionary d_old = animation->track_get_key_value(track, key); - Dictionary d_new = d_old.duplicate(); - d_new[p_name] = p_value; - + case Animation::TYPE_POSITION_3D: + case Animation::TYPE_ROTATION_3D: + case Animation::TYPE_SCALE_3D: { + Variant old = animation->track_get_key_value(track, key); if (!setting) { + String chan; + switch (animation->track_get_type(track)) { + case Animation::TYPE_POSITION_3D: + chan = "Position3D"; + break; + case Animation::TYPE_ROTATION_3D: + chan = "Rotation3D"; + break; + case Animation::TYPE_SCALE_3D: + chan = "Scale3D"; + break; + default: { + } + } + setting = true; - undo_redo->create_action(TTR("Anim Multi Change Transform")); + undo_redo->create_action(vformat(TTR("Anim Multi Change %s"), chan)); } - undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, d_new); - undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old); + undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, p_value); + undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, old); update_obj = true; } break; case Animation::TYPE_VALUE: { @@ -1009,11 +1045,13 @@ public: } switch (animation->track_get_type(track)) { - case Animation::TYPE_TRANSFORM3D: { - Dictionary d = animation->track_get_key_value(track, key); - ERR_FAIL_COND_V(!d.has(name), false); - r_ret = d[p_name]; - return true; + case Animation::TYPE_POSITION_3D: + case Animation::TYPE_ROTATION_3D: + case Animation::TYPE_SCALE_3D: { + if (name == "position" || name == "rotation" || name == "scale") { + r_ret = animation->track_get_key_value(track, key); + return true; + } } break; case Animation::TYPE_VALUE: { @@ -1159,9 +1197,13 @@ public: if (same_track_type) { switch (animation->track_get_type(first_track)) { - case Animation::TYPE_TRANSFORM3D: { - p_list->push_back(PropertyInfo(Variant::VECTOR3, "location")); - p_list->push_back(PropertyInfo(Variant::QUATERNION, "rotation")); + case Animation::TYPE_POSITION_3D: { + p_list->push_back(PropertyInfo(Variant::VECTOR3, "position")); + } break; + case Animation::TYPE_ROTATION_3D: { + p_list->push_back(PropertyInfo(Variant::QUATERNION, "scale")); + } break; + case Animation::TYPE_SCALE_3D: { p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale")); } break; case Animation::TYPE_VALUE: { @@ -1359,7 +1401,9 @@ void AnimationTimelineEdit::_notification(int p_what) { add_track->get_popup()->clear(); add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")), TTR("Property Track")); - add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXform"), SNAME("EditorIcons")), TTR("3D Transform Track")); + add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXPosition"), SNAME("EditorIcons")), TTR("3D Position Track")); + add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXRotation"), SNAME("EditorIcons")), TTR("3D Rotation Track")); + add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXScale"), SNAME("EditorIcons")), TTR("3D Scale Track")); add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyCall"), SNAME("EditorIcons")), TTR("Call Method Track")); add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyBezier"), SNAME("EditorIcons")), TTR("Bezier Curve Track")); add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyAudio"), SNAME("EditorIcons")), TTR("Audio Playback Track")); @@ -1828,9 +1872,11 @@ void AnimationTrackEdit::_notification(int p_what) { Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); Color color = get_theme_color(SNAME("font_color"), SNAME("Label")); - Ref<Texture2D> type_icons[6] = { + Ref<Texture2D> type_icons[8] = { get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyXform"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyTrackPosition"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyTrackRotation"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyTrackScale"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyCall"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyBezier"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyAudio"), SNAME("EditorIcons")), @@ -2021,7 +2067,7 @@ void AnimationTrackEdit::_notification(int p_what) { interp_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2; interp_mode_rect.size = icon->get_size(); - if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM3D) { + if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) { draw_texture(icon, interp_mode_rect.position); } // Make it easier to click. @@ -2031,7 +2077,7 @@ void AnimationTrackEdit::_notification(int p_what) { ofs += icon->get_width() + hsep; interp_mode_rect.size.x += hsep; - if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM3D) { + if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) { draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); interp_mode_rect.size.x += down_icon->get_width(); } else { @@ -2054,7 +2100,7 @@ void AnimationTrackEdit::_notification(int p_what) { loop_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2; loop_mode_rect.size = icon->get_size(); - if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM3D) { + if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) { draw_texture(icon, loop_mode_rect.position); } @@ -2064,7 +2110,7 @@ void AnimationTrackEdit::_notification(int p_what) { ofs += icon->get_width() + hsep; loop_mode_rect.size.x += hsep; - if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM3D) { + if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) { draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); loop_mode_rect.size.x += down_icon->get_width(); } else { @@ -2284,9 +2330,11 @@ void AnimationTrackEdit::set_animation_and_track(const Ref<Animation> &p_animati track = p_track; update(); - Ref<Texture2D> type_icons[6] = { + Ref<Texture2D> type_icons[8] = { get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")), - get_theme_icon(SNAME("KeyXform"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyXPosition"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyXRotation"), SNAME("EditorIcons")), + get_theme_icon(SNAME("KeyXScale"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyCall"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyBezier"), SNAME("EditorIcons")), get_theme_icon(SNAME("KeyAudio"), SNAME("EditorIcons")), @@ -2456,17 +2504,17 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const { if (key_idx != -1) { String text = TTR("Time (s): ") + rtos(animation->track_get_key_time(track, key_idx)) + "\n"; switch (animation->track_get_type(track)) { - case Animation::TYPE_TRANSFORM3D: { - Dictionary d = animation->track_get_key_value(track, key_idx); - if (d.has("location")) { - text += "Pos: " + String(d["location"]) + "\n"; - } - if (d.has("rotation")) { - text += "Rot: " + String(d["rotation"]) + "\n"; - } - if (d.has("scale")) { - text += "Scale: " + String(d["scale"]) + "\n"; - } + case Animation::TYPE_POSITION_3D: { + Vector3 t = animation->track_get_key_value(track, key_idx); + text += "Position: " + String(t) + "\n"; + } break; + case Animation::TYPE_ROTATION_3D: { + Quaternion t = animation->track_get_key_value(track, key_idx); + text += "Rotation: " + String(t) + "\n"; + } break; + case Animation::TYPE_SCALE_3D: { + Vector3 t = animation->track_get_key_value(track, key_idx); + text += "Scale: " + String(t) + "\n"; } break; case Animation::TYPE_VALUE: { const Variant &v = animation->track_get_key_value(track, key_idx); @@ -3323,7 +3371,11 @@ static bool track_type_is_resettable(Animation::TrackType p_type) { [[fallthrough]]; case Animation::TYPE_BEZIER: [[fallthrough]]; - case Animation::TYPE_TRANSFORM3D: + case Animation::TYPE_POSITION_3D: + [[fallthrough]]; + case Animation::TYPE_ROTATION_3D: + [[fallthrough]]; + case Animation::TYPE_SCALE_3D: return true; default: return false; @@ -3483,33 +3535,50 @@ void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_ NodePath np = path; - int track_idx = -1; + int position_idx = -1; + int rotation_idx = -1; + int scale_idx = -1; for (int i = 0; i < animation->get_track_count(); i++) { - if (animation->track_get_type(i) != Animation::TYPE_TRANSFORM3D) { - continue; - } if (animation->track_get_path(i) != np) { continue; } - track_idx = i; - break; + if (animation->track_get_type(i) == Animation::TYPE_POSITION_3D) { + position_idx = i; + } + if (animation->track_get_type(i) == Animation::TYPE_ROTATION_3D) { + rotation_idx = i; + } + if (animation->track_get_type(i) == Animation::TYPE_SCALE_3D) { + scale_idx = i; + } } InsertData id; - Dictionary val; - id.path = np; - id.track_idx = track_idx; - id.value = p_xform; - id.type = Animation::TYPE_TRANSFORM3D; // TRANSLATORS: This describes the target of new animation track, will be inserted into another string. id.query = vformat(TTR("node '%s'"), p_node->get_name()); id.advance = false; - // Dialog insert. - _query_insert(id); + { + id.track_idx = position_idx; + id.value = p_xform.origin; + id.type = Animation::TYPE_POSITION_3D; + _query_insert(id); + } + { + id.track_idx = rotation_idx; + id.value = p_xform.basis.get_rotation_quaternion(); + id.type = Animation::TYPE_ROTATION_3D; + _query_insert(id); + } + { + id.track_idx = scale_idx; + id.value = p_xform.basis.get_scale(); + id.type = Animation::TYPE_SCALE_3D; + _query_insert(id); + } } bool AnimationTrackEditor::has_transform_track(Node3D *p_node, const String &p_sub) { @@ -3530,7 +3599,7 @@ bool AnimationTrackEditor::has_transform_track(Node3D *p_node, const String &p_s } int track_id = animation->find_track(path); if (track_id >= 0) { - if (animation->track_get_type(track_id) == Animation::TYPE_TRANSFORM3D) { + if (animation->track_get_type(track_id) == Animation::TYPE_POSITION_3D || animation->track_get_type(track_id) == Animation::TYPE_ROTATION_3D || animation->track_get_type(track_id) == Animation::TYPE_SCALE_3D) { return true; } } @@ -3977,18 +4046,13 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD Variant value; switch (p_id.type) { + case Animation::TYPE_POSITION_3D: + case Animation::TYPE_ROTATION_3D: + case Animation::TYPE_SCALE_3D: case Animation::TYPE_VALUE: { value = p_id.value; } break; - case Animation::TYPE_TRANSFORM3D: { - Transform3D tr = p_id.value; - Dictionary d; - d["location"] = tr.origin; - d["scale"] = tr.basis.get_scale(); - d["rotation"] = Quaternion(tr.basis); - value = d; - } break; case Animation::TYPE_BEZIER: { Array array; array.resize(5); @@ -4404,8 +4468,8 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) { ERR_FAIL_COND(!node); NodePath path_to = root->get_path_to(node); - if (adding_track_type == Animation::TYPE_TRANSFORM3D && !node->is_class("Node3D")) { - EditorNode::get_singleton()->show_warning(TTR("Transform3D tracks only apply to 3D-based nodes.")); + if ((adding_track_type == Animation::TYPE_POSITION_3D || adding_track_type == Animation::TYPE_ROTATION_3D || adding_track_type == Animation::TYPE_SCALE_3D) && !node->is_class("Node3D")) { + EditorNode::get_singleton()->show_warning(TTR("Position/Rotation/Scale 3D tracks only apply to 3D-based nodes.")); return; } @@ -4415,7 +4479,9 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) { prop_selector->set_type_filter(Vector<Variant::Type>()); prop_selector->select_property_from_instance(node); } break; - case Animation::TYPE_TRANSFORM3D: + case Animation::TYPE_POSITION_3D: + case Animation::TYPE_ROTATION_3D: + case Animation::TYPE_SCALE_3D: case Animation::TYPE_METHOD: { undo_redo->create_action(TTR("Add Track")); undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type); @@ -4584,7 +4650,27 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { } switch (animation->track_get_type(p_track)) { - case Animation::TYPE_TRANSFORM3D: { + case Animation::TYPE_POSITION_3D: { + if (!root->has_node(animation->track_get_path(p_track))) { + EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a key.")); + return; + } + Node3D *base = Object::cast_to<Node3D>(root->get_node(animation->track_get_path(p_track))); + + if (!base) { + EditorNode::get_singleton()->show_warning(TTR("Track is not of type Node3D, can't insert key")); + return; + } + + Vector3 pos = base->get_position(); + + undo_redo->create_action(TTR("Add Position Key")); + undo_redo->add_do_method(animation.ptr(), "position_track_insert_key", p_track, p_ofs, pos); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs); + undo_redo->commit_action(); + + } break; + case Animation::TYPE_ROTATION_3D: { if (!root->has_node(animation->track_get_path(p_track))) { EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a key.")); return; @@ -4596,14 +4682,30 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { return; } - Transform3D xf = base->get_transform(); + Quaternion rot = base->get_transform().basis.operator Quaternion(); + + undo_redo->create_action(TTR("Add Rotation Key")); + undo_redo->add_do_method(animation.ptr(), "rotation_track_insert_key", p_track, p_ofs, rot); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs); + undo_redo->commit_action(); + + } break; + case Animation::TYPE_SCALE_3D: { + if (!root->has_node(animation->track_get_path(p_track))) { + EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a key.")); + return; + } + Node3D *base = Object::cast_to<Node3D>(root->get_node(animation->track_get_path(p_track))); + + if (!base) { + EditorNode::get_singleton()->show_warning(TTR("Track is not of type Node3D, can't insert key")); + return; + } - Vector3 loc = xf.get_origin(); - Vector3 scale = xf.basis.get_scale_local(); - Quaternion rot = xf.basis; + Vector3 scale = base->get_scale(); - undo_redo->create_action(TTR("Add Transform Track Key")); - undo_redo->add_do_method(animation.ptr(), "transform_track_insert_key", p_track, p_ofs, loc, rot, scale); + undo_redo->create_action(TTR("Add Scale Key")); + undo_redo->add_do_method(animation.ptr(), "scale_track_insert_key", p_track, p_ofs, scale); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs); undo_redo->commit_action(); @@ -5277,8 +5379,14 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { } switch (animation->track_get_type(i)) { - case Animation::TYPE_TRANSFORM3D: - text += " (Transform)"; + case Animation::TYPE_POSITION_3D: + text += " (Position)"; + break; + case Animation::TYPE_ROTATION_3D: + text += " (Rotation)"; + break; + case Animation::TYPE_SCALE_3D: + text += " (Scale)"; break; case Animation::TYPE_METHOD: text += " (Methods)"; diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 576edef0c8..2555901557 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -35,7 +35,7 @@ #include "editor/editor_spin_slider.h" #include "editor/property_editor.h" #include "editor/property_selector.h" -#include "scene/animation/animation_cache.h" + #include "scene/gui/control.h" #include "scene/gui/file_dialog.h" #include "scene/gui/menu_button.h" diff --git a/editor/debugger/editor_visual_profiler.cpp b/editor/debugger/editor_visual_profiler.cpp index f17ad0d36c..f25f18b7e4 100644 --- a/editor/debugger/editor_visual_profiler.cpp +++ b/editor/debugger/editor_visual_profiler.cpp @@ -370,8 +370,8 @@ void EditorVisualProfiler::_update_frame(bool p_focus_selected) { float total_gpu = E->get_metadata(2); total_cpu += cpu_time; total_gpu += gpu_time; - E->set_metadata(1, cpu_time); - E->set_metadata(2, gpu_time); + E->set_metadata(1, total_cpu); + E->set_metadata(2, total_gpu); } category->set_icon(0, track_icon); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 6fef65c92d..767856f939 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -2163,7 +2163,7 @@ Error EditorFileSystem::_resource_import(const String &p_path) { } bool EditorFileSystem::_should_skip_directory(const String &p_path) { - if (p_path == ProjectSettings::get_singleton()->get_project_data_path()) { + if (p_path.begins_with(ProjectSettings::get_singleton()->get_project_data_path())) { return true; } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 5fd0a41788..2f92f60d5e 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1776,19 +1776,25 @@ void EditorNode::restart_editor() { } void EditorNode::_save_all_scenes() { + bool all_saved = true; for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { Node *scene = editor_data.get_edited_scene_root(i); - if (scene && scene->get_scene_file_path() != "" && DirAccess::exists(scene->get_scene_file_path().get_base_dir())) { - if (i != editor_data.get_edited_scene()) { - _save_scene(scene->get_scene_file_path(), i); + if (scene) { + if (scene->get_scene_file_path() != "" && DirAccess::exists(scene->get_scene_file_path().get_base_dir())) { + if (i != editor_data.get_edited_scene()) { + _save_scene(scene->get_scene_file_path(), i); + } else { + _save_scene_with_preview(scene->get_scene_file_path()); + } } else { - _save_scene_with_preview(scene->get_scene_file_path()); + all_saved = false; } - } else { - show_warning(TTR("Could not save one or more scenes!"), TTR("Save All Scenes")); } } + if (!all_saved) { + show_warning(TTR("Could not save one or more scenes!"), TTR("Save All Scenes")); + } _save_default_environment(); } @@ -3842,7 +3848,8 @@ void EditorNode::register_editor_types() { GDREGISTER_VIRTUAL_CLASS(EditorInterface); GDREGISTER_CLASS(EditorExportPlugin); GDREGISTER_CLASS(EditorResourceConversionPlugin); - GDREGISTER_CLASS(EditorSceneImporter); + GDREGISTER_CLASS(EditorSceneFormatImporter); + GDREGISTER_CLASS(EditorScenePostImportPlugin); GDREGISTER_CLASS(EditorInspector); GDREGISTER_CLASS(EditorInspectorPlugin); GDREGISTER_CLASS(EditorProperty); @@ -5929,7 +5936,7 @@ EditorNode::EditorNode() { ResourceFormatImporter::get_singleton()->add_importer(import_scene); { - Ref<EditorSceneImporterCollada> import_collada; + Ref<EditorSceneFormatImporterCollada> import_collada; import_collada.instantiate(); import_scene->add_importer(import_collada); @@ -5937,7 +5944,7 @@ EditorNode::EditorNode() { import_obj2.instantiate(); import_scene->add_importer(import_obj2); - Ref<EditorSceneImporterESCN> import_escn; + Ref<EditorSceneFormatImporterESCN> import_escn; import_escn.instantiate(); import_scene->add_importer(import_escn); } diff --git a/editor/editor_paths.cpp b/editor/editor_paths.cpp index 71f13c0c2f..5b48cc2638 100644 --- a/editor/editor_paths.cpp +++ b/editor/editor_paths.cpp @@ -200,6 +200,20 @@ EditorPaths::EditorPaths() { paths_valid = false; } } + + // Check that the project data directory '.gdignore' file exists + String project_data_gdignore_file_path = project_data_dir.plus_file(".gdignore"); + if (!FileAccess::exists(project_data_gdignore_file_path)) { + // Add an empty .gdignore file to avoid scan. + FileAccessRef f = FileAccess::open(project_data_gdignore_file_path, FileAccess::WRITE); + if (f) { + f->store_line(""); + f->close(); + } else { + ERR_PRINT("Failed to create file " + project_data_gdignore_file_path); + } + } + Engine::get_singleton()->set_shader_cache_path(project_data_dir); // Editor metadata dir. diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp index aee8322a97..61c01993ae 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -762,16 +762,23 @@ void EditorPlugin::remove_inspector_plugin(const Ref<EditorInspectorPlugin> &p_p EditorInspector::remove_inspector_plugin(p_plugin); } -void EditorPlugin::add_scene_import_plugin(const Ref<EditorSceneImporter> &p_importer) { +void EditorPlugin::add_scene_format_importer_plugin(const Ref<EditorSceneFormatImporter> &p_importer) { ERR_FAIL_COND(!p_importer.is_valid()); ResourceImporterScene::get_singleton()->add_importer(p_importer); } -void EditorPlugin::remove_scene_import_plugin(const Ref<EditorSceneImporter> &p_importer) { +void EditorPlugin::remove_scene_format_importer_plugin(const Ref<EditorSceneFormatImporter> &p_importer) { ERR_FAIL_COND(!p_importer.is_valid()); ResourceImporterScene::get_singleton()->remove_importer(p_importer); } +void EditorPlugin::add_scene_post_import_plugin(const Ref<EditorScenePostImportPlugin> &p_plugin) { + ResourceImporterScene::get_singleton()->add_post_importer_plugin(p_plugin); +} +void EditorPlugin::remove_scene_post_import_plugin(const Ref<EditorScenePostImportPlugin> &p_plugin) { + ResourceImporterScene::get_singleton()->remove_post_importer_plugin(p_plugin); +} + int find(const PackedStringArray &a, const String &v) { const String *r = a.ptr(); for (int j = 0; j < a.size(); ++j) { @@ -879,8 +886,10 @@ void EditorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_translation_parser_plugin", "parser"), &EditorPlugin::remove_translation_parser_plugin); ClassDB::bind_method(D_METHOD("add_import_plugin", "importer"), &EditorPlugin::add_import_plugin); ClassDB::bind_method(D_METHOD("remove_import_plugin", "importer"), &EditorPlugin::remove_import_plugin); - ClassDB::bind_method(D_METHOD("add_scene_import_plugin", "scene_importer"), &EditorPlugin::add_scene_import_plugin); - ClassDB::bind_method(D_METHOD("remove_scene_import_plugin", "scene_importer"), &EditorPlugin::remove_scene_import_plugin); + ClassDB::bind_method(D_METHOD("add_scene_format_importer_plugin", "scene_format_importer"), &EditorPlugin::add_scene_format_importer_plugin); + ClassDB::bind_method(D_METHOD("remove_scene_format_importer_plugin", "scene_format_importer"), &EditorPlugin::remove_scene_format_importer_plugin); + ClassDB::bind_method(D_METHOD("add_scene_post_import_plugin", "scene_import_plugin"), &EditorPlugin::add_scene_post_import_plugin); + ClassDB::bind_method(D_METHOD("remove_scene_post_import_plugin", "scene_import_plugin"), &EditorPlugin::remove_scene_post_import_plugin); ClassDB::bind_method(D_METHOD("add_export_plugin", "plugin"), &EditorPlugin::add_export_plugin); ClassDB::bind_method(D_METHOD("remove_export_plugin", "plugin"), &EditorPlugin::remove_export_plugin); ClassDB::bind_method(D_METHOD("add_spatial_gizmo_plugin", "plugin"), &EditorPlugin::add_spatial_gizmo_plugin); diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h index 57830df327..278059f8c4 100644 --- a/editor/editor_plugin.h +++ b/editor/editor_plugin.h @@ -288,8 +288,11 @@ public: void add_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin); void remove_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin); - void add_scene_import_plugin(const Ref<EditorSceneImporter> &p_importer); - void remove_scene_import_plugin(const Ref<EditorSceneImporter> &p_importer); + void add_scene_format_importer_plugin(const Ref<EditorSceneFormatImporter> &p_importer); + void remove_scene_format_importer_plugin(const Ref<EditorSceneFormatImporter> &p_importer); + + void add_scene_post_import_plugin(const Ref<EditorScenePostImportPlugin> &p_importer); + void remove_scene_post_import_plugin(const Ref<EditorScenePostImportPlugin> &p_importer); void add_autoload_singleton(const String &p_name, const String &p_path); void remove_autoload_singleton(const String &p_name); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 43d458c58e..223cc6650c 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -140,32 +140,37 @@ bool EditorSettings::_get(const StringName &p_name, Variant &r_ret) const { if (p_name == "shortcuts") { Array save_array; + const OrderedHashMap<String, List<Ref<InputEvent>>> &builtin_list = InputMap::get_singleton()->get_builtins(); for (const KeyValue<String, Ref<Shortcut>> &shortcut_definition : shortcuts) { Ref<Shortcut> sc = shortcut_definition.value; - if (builtin_action_overrides.has(shortcut_definition.key)) { + if (builtin_list.has(shortcut_definition.key)) { // This shortcut was auto-generated from built in actions: don't save. + // If the builtin is overriden, it will be saved in the "builtin_action_overrides" section below. continue; } - if (optimize_save) { - if (!sc->has_meta("original")) { - continue; //this came from settings but is not any longer used - } + Array shortcut_events = sc->get_events(); + + Dictionary dict; + dict["name"] = shortcut_definition.key; + dict["shortcuts"] = shortcut_events; + + if (!sc->has_meta("original")) { + // Getting the meta when it doesn't exist will return an empty array. If the 'shortcut_events' have been cleared, + // we still want save the shortcut in this case so that shortcuts that the user has customised are not reset, + // even if the 'original' has not been populated yet. This can happen when calling save() from the Project Manager. + save_array.push_back(dict); + continue; } Array original_events = sc->get_meta("original"); - Array shortcut_events = sc->get_events(); bool is_same = Shortcut::is_event_array_equal(original_events, shortcut_events); if (is_same) { continue; // Not changed from default; don't save. } - Dictionary dict; - dict["name"] = shortcut_definition.key; - dict["shortcuts"] = shortcut_events; - save_array.push_back(dict); } r_ret = save_array; @@ -1511,7 +1516,7 @@ void ED_SHORTCUT_OVERRIDE_ARRAY(const String &p_path, const String &p_feature, c // Directly override the existing shortcut. sc->set_events(events); - sc->set_meta("original", events); + sc->set_meta("original", events.duplicate(true)); } Ref<Shortcut> ED_SHORTCUT(const String &p_path, const String &p_name, Key p_keycode) { @@ -1545,21 +1550,21 @@ Ref<Shortcut> ED_SHORTCUT_ARRAY(const String &p_path, const String &p_name, cons sc.instantiate(); sc->set_name(p_name); sc->set_events(events); - sc->set_meta("original", events); + sc->set_meta("original", events.duplicate(true)); return sc; } Ref<Shortcut> sc = EditorSettings::get_singleton()->get_shortcut(p_path); if (sc.is_valid()) { sc->set_name(p_name); //keep name (the ones that come from disk have no name) - sc->set_meta("original", events); //to compare against changes + sc->set_meta("original", events.duplicate(true)); //to compare against changes return sc; } sc.instantiate(); sc->set_name(p_name); sc->set_events(events); - sc->set_meta("original", events); //to compare against changes + sc->set_meta("original", events.duplicate(true)); //to compare against changes EditorSettings::get_singleton()->add_shortcut(p_path, sc); return sc; diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp index 283496c0f1..b61f6e12eb 100644 --- a/editor/find_in_files.cpp +++ b/editor/find_in_files.cpp @@ -235,7 +235,7 @@ void FindInFiles::_scan_dir(String path, PackedStringArray &out_folders) { // Ignore special dirs (such as .git and project data directory) String project_data_dir_name = ProjectSettings::get_singleton()->get_project_data_dir_name(); - if (file.begins_with(".") || file == project_data_dir_name) { + if (file.begins_with(".") || file.begins_with(project_data_dir_name)) { continue; } if (dir->current_is_hidden()) { diff --git a/editor/icons/KeyTrackPosition.svg b/editor/icons/KeyTrackPosition.svg new file mode 100644 index 0000000000..05c6e88841 --- /dev/null +++ b/editor/icons/KeyTrackPosition.svg @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="10" + viewBox="0 0 10 10" + width="10" + version="1.1" + id="svg12" + sodipodi:docname="KeyTrackPosition.svg" + inkscape:version="1.1 (1:1.1+202106031931+af4d65493e)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs16" /> + <sodipodi:namedview + id="namedview14" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="false" + inkscape:zoom="42.2" + inkscape:cx="12.78436" + inkscape:cy="6.1729858" + inkscape:window-width="1848" + inkscape:window-height="1016" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="svg12" /> + <ellipse + style="fill:none;fill-opacity:0.401212;stroke:none;stroke-width:4.7811;stroke-linejoin:round" + id="path921" + cx="-0.88986981" + cy="6.0959954" + rx="1.2495773" + ry="1.0867468" /> + <path + d="M 4.949661,0.60426977 A 0.6444116,0.6444116 0 0 0 4.504153,0.79178767 L 3.215459,2.0804819 4.12663,2.9916532 4.95977,2.1585124 5.792911,2.9916532 6.704083,2.0804819 5.415388,0.79178767 A 0.6444116,0.6444116 0 0 0 4.949744,0.60426977 Z M 1.926771,3.3691634 0.638077,4.6578577 a 0.6444116,0.6444116 0 0 0 0,0.9111713 L 1.926771,6.8577233 2.837942,5.946552 2.004801,5.1134111 2.837942,4.2802702 1.926771,3.3690989 Z m 6.065948,0 -0.911171,0.9111713 0.83314,0.8331409 -0.83314,0.8331408 0.911171,0.9111714 1.288694,-1.2886944 a 0.6444116,0.6444116 0 0 0 0,-0.9111713 L 7.992719,3.3692278 Z M 4.959777,3.8247361 A 1.2886943,1.2886943 0 0 0 3.671083,5.1134305 1.2886943,1.2886943 0 0 0 4.959777,6.4021248 1.2886943,1.2886943 0 0 0 6.248471,5.1134305 1.2886943,1.2886943 0 0 0 4.959777,3.8247361 Z m -0.83314,3.4105296 -0.911172,0.9111715 1.288694,1.288694 a 0.6444116,0.6444116 0 0 0 0.911171,0 L 6.704025,8.1464372 5.792853,7.2352657 4.959712,8.0684062 4.126572,7.2352657 Z" + fill="#e0e0e0" + fill-opacity="0.99608" + id="path1400" + style="fill:#ea7940;fill-opacity:1;stroke-width:0.644347" /> +</svg> diff --git a/editor/icons/KeyTrackRotation.svg b/editor/icons/KeyTrackRotation.svg new file mode 100644 index 0000000000..d05e381eb2 --- /dev/null +++ b/editor/icons/KeyTrackRotation.svg @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="10" + viewBox="0 0 10 10" + width="10" + version="1.1" + id="svg12" + sodipodi:docname="KeyTrackRotation.svg" + inkscape:version="1.1 (1:1.1+202106031931+af4d65493e)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs16" /> + <sodipodi:namedview + id="namedview14" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="false" + inkscape:zoom="42.2" + inkscape:cx="1.9075829" + inkscape:cy="5.8175355" + inkscape:window-width="1848" + inkscape:window-height="1016" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="svg12" /> + <ellipse + style="fill:none;fill-opacity:0.401212;stroke:none;stroke-width:4.7811;stroke-linejoin:round" + id="path921" + cx="-0.88986981" + cy="6.0959954" + rx="1.2495773" + ry="1.0867468" /> + <path + d="m 5.0711986,0.88214062 a 4.1086779,4.1086779 0 0 0 -0.178839,0.00115 4.1086779,4.1086779 0 0 0 -0.409265,0.033245 A 4.1086779,4.1086779 0 0 0 0.99001362,4.1883346 4.1086779,4.1086779 0 0 0 2.1467236,7.9244144 h -0.648877 v 1.1739077 h 2.347816 a 0.58701268,0.58701268 0 0 0 0.569756,-0.729119 l -0.586953,-2.3478164 -1.139514,0.2854534 0.165082,0.6580347 a 2.9347699,2.9347699 0 0 1 -0.769204,-1.9752178 2.9347699,2.9347699 0 0 1 2.93477,-2.93477 2.9347699,2.9347699 0 0 1 2.93477,2.93477 2.9347699,2.9347699 0 0 1 -0.860944,2.0738257 l 0.831127,0.8311266 A 4.1086779,4.1086779 0 0 0 8.7040866,3.1726236 4.1086779,4.1086779 0 0 0 5.0711336,0.88215292 Z m -0.05159,2.93359608 a 1.173908,1.173908 0 0 0 -1.173907,1.173908 1.173908,1.173908 0 0 0 1.173907,1.173908 1.173908,1.173908 0 0 0 1.173909,-1.173908 1.173908,1.173908 0 0 0 -1.173909,-1.173908 z" + fill="#e0e0e0" + fill-opacity="0.99608" + id="path1165" + style="fill:#ff2b88;fill-opacity:1;stroke-width:0.586954" /> +</svg> diff --git a/editor/icons/KeyTrackScale.svg b/editor/icons/KeyTrackScale.svg new file mode 100644 index 0000000000..9269ccbca2 --- /dev/null +++ b/editor/icons/KeyTrackScale.svg @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="10" + viewBox="0 0 10 10" + width="10" + version="1.1" + id="svg12" + sodipodi:docname="KeyTrackScale.svg" + inkscape:version="1.1 (1:1.1+202106031931+af4d65493e)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs16" /> + <sodipodi:namedview + id="namedview14" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="false" + inkscape:zoom="42.2" + inkscape:cx="1.9075829" + inkscape:cy="5.8175355" + inkscape:window-width="1848" + inkscape:window-height="1016" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="svg12" /> + <ellipse + style="fill:none;fill-opacity:0.401212;stroke:none;stroke-width:4.7811;stroke-linejoin:round" + id="path921" + cx="-0.88986981" + cy="6.0959954" + rx="1.2495773" + ry="1.0867468" /> + <path + d="M 5.5742494,0.94786723 A 0.58774585,0.58774585 0 0 0 4.9865036,1.535613 0.58774585,0.58774585 0 0 0 5.5742494,2.1233589 h 1.519852 L 6.334146,2.8833142 7.1652774,3.7144456 7.9252328,2.9544903 V 4.4743422 A 0.58774585,0.58774585 0 0 0 8.5129787,5.0620881 0.58774585,0.58774585 0 0 0 9.1007245,4.4743422 V 1.535613 A 0.58780462,0.58780462 0 0 0 8.5129787,0.94786723 Z M 4.9865036,3.8865964 A 1.1754917,1.1754917 0 0 0 3.8110119,5.0620881 1.1754917,1.1754917 0 0 0 4.9865036,6.2375798 1.1754917,1.1754917 0 0 0 6.1619953,5.0620881 1.1754917,1.1754917 0 0 0 4.9865036,3.8865964 Z M 1.4600285,5.0620881 A 0.58774585,0.58774585 0 0 0 0.8722826,5.6498339 V 8.5885638 A 0.58780462,0.58780462 0 0 0 1.4600285,9.1763092 H 4.3987577 A 0.58774585,0.58774585 0 0 0 4.9865036,8.5885638 0.58774585,0.58774585 0 0 0 4.3987577,8.0008173 H 2.8789057 L 3.6388611,7.2408619 2.8077297,6.4097305 2.0477743,7.1696859 V 5.6498339 A 0.58774585,0.58774585 0 0 0 1.4600285,5.0620881 Z" + fill="#e0e0e0" + fill-opacity="0.99608" + id="path7" + style="fill:#eac840;fill-opacity:1;stroke-width:0.587745" /> +</svg> diff --git a/editor/icons/KeyXPosition.svg b/editor/icons/KeyXPosition.svg new file mode 100644 index 0000000000..5816a241c9 --- /dev/null +++ b/editor/icons/KeyXPosition.svg @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="10" + viewBox="0 0 10 10" + width="10" + version="1.1" + id="svg1678" + sodipodi:docname="KeyXPosition.svg" + inkscape:version="1.1 (1:1.1+202106031931+af4d65493e)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1682" /> + <sodipodi:namedview + id="namedview1680" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="false" + inkscape:zoom="84.4" + inkscape:cx="4.9940758" + inkscape:cy="5.0059242" + inkscape:window-width="1848" + inkscape:window-height="1016" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="svg1678" /> + <rect + fill="#ea7940" + height="6.1027" + ry=".76286" + transform="matrix(.70710678 -.70710678 .70710678 .70710678 0 -1042.4)" + width="6.1027" + x="-740.13947" + y="741.10779" + id="rect1676" /> +</svg> diff --git a/editor/icons/KeyXRotation.svg b/editor/icons/KeyXRotation.svg new file mode 100644 index 0000000000..6cd5a67ac0 --- /dev/null +++ b/editor/icons/KeyXRotation.svg @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="10" + viewBox="0 0 10 10" + width="10" + version="1.1" + id="svg1678" + sodipodi:docname="KeyXRotation.svg" + inkscape:version="1.1 (1:1.1+202106031931+af4d65493e)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1682" /> + <sodipodi:namedview + id="namedview1680" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="false" + inkscape:zoom="84.4" + inkscape:cx="0.32582938" + inkscape:cy="5.0059242" + inkscape:window-width="1848" + inkscape:window-height="1016" + inkscape:window-x="72" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="svg1678" /> + <rect + fill="#ea7940" + height="6.1027002" + ry="0.76286" + transform="rotate(-45,-1258.2881,-521.2)" + width="6.1030002" + x="-740.13947" + y="741.10779" + id="rect1676" + style="fill:#ee3c94;fill-opacity:1" /> +</svg> diff --git a/editor/icons/KeyXScale.svg b/editor/icons/KeyXScale.svg new file mode 100644 index 0000000000..588fa5d2f3 --- /dev/null +++ b/editor/icons/KeyXScale.svg @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="10" + viewBox="0 0 10 10" + width="10" + version="1.1" + id="svg1678" + sodipodi:docname="KeyXScale.svg" + inkscape:version="1.1 (1:1.1+202106031931+af4d65493e)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1682" /> + <sodipodi:namedview + id="namedview1680" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="false" + inkscape:zoom="84.4" + inkscape:cx="2.6658768" + inkscape:cy="5.0059242" + inkscape:window-width="1473" + inkscape:window-height="752" + inkscape:window-x="122" + inkscape:window-y="114" + inkscape:window-maximized="0" + inkscape:current-layer="svg1678" /> + <rect + fill="#ea7940" + height="6.1027002" + ry="0.76286" + transform="rotate(-45,-1258.2881,-521.2)" + width="6.1030002" + x="-740.13947" + y="741.10779" + id="rect1676" + style="fill:#dbbf4f;fill-opacity:1" /> +</svg> diff --git a/editor/import/editor_import_collada.cpp b/editor/import/editor_import_collada.cpp index 4b01595028..4e343ce3ce 100644 --- a/editor/import/editor_import_collada.cpp +++ b/editor/import/editor_import_collada.cpp @@ -91,8 +91,8 @@ struct ColladaImport { Error _create_mesh_surfaces(bool p_optimize, Ref<ImporterMesh> &p_mesh, const Map<String, Collada::NodeGeometry::Material> &p_material_map, const Collada::MeshData &meshdata, const Transform3D &p_local_xform, const Vector<int> &bone_remap, const Collada::SkinControllerData *p_skin_controller, const Collada::MorphControllerData *p_morph_data, Vector<Ref<ImporterMesh>> p_morph_meshes = Vector<Ref<ImporterMesh>>(), bool p_use_compression = false, bool p_use_mesh_material = false); Error load(const String &p_path, int p_flags, bool p_force_make_tangents = false, bool p_use_compression = false); void _fix_param_animation_tracks(); - void create_animation(int p_clip, bool p_make_tracks_in_all_bones, bool p_import_value_tracks); - void create_animations(bool p_make_tracks_in_all_bones, bool p_import_value_tracks); + void create_animation(int p_clip, bool p_import_value_tracks); + void create_animations(bool p_import_value_tracks); Set<String> tracks_in_clips; Vector<String> missing_textures; @@ -120,6 +120,15 @@ Error ColladaImport::_populate_skeleton(Skeleton3D *p_skeleton, Collada::Node *p skeleton_bone_map[p_skeleton][joint->sid] = r_bone; + { + Transform3D xform = joint->compute_transform(collada); + collada.fix_transform(xform) * joint->post_transform; + + p_skeleton->set_bone_pose_position(r_bone, xform.origin); + p_skeleton->set_bone_pose_rotation(r_bone, xform.basis.get_rotation_quaternion()); + p_skeleton->set_bone_pose_scale(r_bone, xform.basis.get_scale()); + } + if (collada.state.bone_rest_map.has(joint->sid)) { p_skeleton->set_bone_rest(r_bone, collada.fix_transform(collada.state.bone_rest_map[joint->sid])); //should map this bone to something for animation? @@ -1384,7 +1393,7 @@ void ColladaImport::_fix_param_animation_tracks() { } } -void ColladaImport::create_animations(bool p_make_tracks_in_all_bones, bool p_import_value_tracks) { +void ColladaImport::create_animations(bool p_import_value_tracks) { _fix_param_animation_tracks(); for (int i = 0; i < collada.state.animation_clips.size(); i++) { for (int j = 0; j < collada.state.animation_clips[i].tracks.size(); j++) { @@ -1417,13 +1426,13 @@ void ColladaImport::create_animations(bool p_make_tracks_in_all_bones, bool p_im } } - create_animation(-1, p_make_tracks_in_all_bones, p_import_value_tracks); + create_animation(-1, p_import_value_tracks); for (int i = 0; i < collada.state.animation_clips.size(); i++) { - create_animation(i, p_make_tracks_in_all_bones, p_import_value_tracks); + create_animation(i, p_import_value_tracks); } } -void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones, bool p_import_value_tracks) { +void ColladaImport::create_animation(int p_clip, bool p_import_value_tracks) { Ref<Animation> animation = Ref<Animation>(memnew(Animation)); if (p_clip == -1) { @@ -1522,10 +1531,55 @@ void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones continue; } - animation->add_track(Animation::TYPE_TRANSFORM3D); - int track = animation->get_track_count() - 1; - animation->track_set_path(track, path); - animation->track_set_imported(track, true); //helps merging later + bool has_position = false; + bool has_rotation = false; + bool has_scale = false; + + for (int i = 0; cn->xform_list.size(); i++) { + switch (cn->xform_list[i].op) { + case Collada::Node::XForm::OP_ROTATE: { + has_rotation = true; + } break; + case Collada::Node::XForm::OP_SCALE: { + has_scale = true; + } break; + case Collada::Node::XForm::OP_TRANSLATE: { + has_position = true; + } break; + case Collada::Node::XForm::OP_MATRIX: { + has_position = true; + has_rotation = true; + has_scale = true; + } break; + case Collada::Node::XForm::OP_VISIBILITY: { + } break; + } + } + + int base_track = animation->get_track_count(); + int position_idx = -1; + if (has_position) { + position_idx = animation->get_track_count(); + animation->add_track(Animation::TYPE_POSITION_3D); + animation->track_set_path(position_idx, path); + animation->track_set_imported(position_idx, true); //helps merging later + } + + int rotation_idx = -1; + if (has_rotation) { + rotation_idx = animation->get_track_count(); + animation->add_track(Animation::TYPE_ROTATION_3D); + animation->track_set_path(rotation_idx, path); + animation->track_set_imported(rotation_idx, true); //helps merging later + } + + int scale_idx = -1; + if (has_scale) { + scale_idx = animation->get_track_count(); + animation->add_track(Animation::TYPE_SCALE_3D); + animation->track_set_path(scale_idx, path); + animation->track_set_imported(scale_idx, true); //helps merging later + } Vector<real_t> snapshots = base_snapshots; @@ -1594,22 +1648,21 @@ void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones Transform3D xform = cn->compute_transform(collada); xform = collada.fix_transform(xform) * cn->post_transform; - if (nm.bone >= 0) { - //make bone transform relative to rest (in case of skeleton) - Skeleton3D *sk = Object::cast_to<Skeleton3D>(nm.node); - if (sk) { - xform = sk->get_bone_rest(nm.bone).affine_inverse() * xform; - } else { - ERR_PRINT("Collada: Invalid skeleton"); - } - } - Vector3 s = xform.basis.get_scale(); bool singular_matrix = Math::is_zero_approx(s.x) || Math::is_zero_approx(s.y) || Math::is_zero_approx(s.z); - Quaternion q = singular_matrix ? Quaternion() : xform.basis.get_rotation_quaternion(); + Quaternion q = singular_matrix ? Quaternion() : + xform.basis.get_rotation_quaternion(); Vector3 l = xform.origin; - animation->transform_track_insert_key(track, snapshots[i], l, q, s); + if (position_idx >= 0) { + animation->position_track_insert_key(position_idx, snapshots[i], l); + } + if (rotation_idx >= 0) { + animation->rotation_track_insert_key(rotation_idx, snapshots[i], q); + } + if (scale_idx >= 0) { + animation->scale_track_insert_key(scale_idx, snapshots[i], s); + } } if (nm.bone >= 0) { @@ -1621,48 +1674,15 @@ void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones if (found_anim) { tracks_found = true; } else { - animation->remove_track(track); - } - } - - if (p_make_tracks_in_all_bones) { - //some bones may lack animation, but since we don't store pose as a property, we must add keyframes! - for (const KeyValue<String, bool> &E : bones_with_animation) { - if (E.value) { - continue; + if (position_idx >= 0) { + animation->remove_track(base_track); } - - NodeMap &nm = node_map[E.key]; - String path = scene->get_path_to(nm.node); - ERR_CONTINUE(nm.bone < 0); - Skeleton3D *sk = static_cast<Skeleton3D *>(nm.node); - String name = sk->get_bone_name(nm.bone); - path = path + ":" + name; - - Collada::Node *cn = collada.state.scene_map[E.key]; - if (cn->ignore_anim) { - WARN_PRINT("Collada: Ignoring animation on node: " + path); - continue; + if (rotation_idx >= 0) { + animation->remove_track(base_track); + } + if (scale_idx >= 0) { + animation->remove_track(base_track); } - - animation->add_track(Animation::TYPE_TRANSFORM3D); - int track = animation->get_track_count() - 1; - animation->track_set_path(track, path); - animation->track_set_imported(track, true); //helps merging later - - Transform3D xform = cn->compute_transform(collada); - xform = collada.fix_transform(xform) * cn->post_transform; - - xform = sk->get_bone_rest(nm.bone).affine_inverse() * xform; - - Vector3 s = xform.basis.get_scale(); - bool singular_matrix = Math::is_zero_approx(s.x) || Math::is_zero_approx(s.y) || Math::is_zero_approx(s.z); - Quaternion q = singular_matrix ? Quaternion() : xform.basis.get_rotation_quaternion(); - Vector3 l = xform.origin; - - animation->transform_track_insert_key(track, 0, l, q, s); - - tracks_found = true; } } @@ -1728,15 +1748,15 @@ void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones /*************************************** SCENE ***********************************/ /*********************************************************************************/ -uint32_t EditorSceneImporterCollada::get_import_flags() const { +uint32_t EditorSceneFormatImporterCollada::get_import_flags() const { return IMPORT_SCENE | IMPORT_ANIMATION; } -void EditorSceneImporterCollada::get_extensions(List<String> *r_extensions) const { +void EditorSceneFormatImporterCollada::get_extensions(List<String> *r_extensions) const { r_extensions->push_back("dae"); } -Node *EditorSceneImporterCollada::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { +Node *EditorSceneFormatImporterCollada::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { if (r_err) { *r_err = OK; } @@ -1749,7 +1769,7 @@ Node *EditorSceneImporterCollada::import_scene(const String &p_path, uint32_t p_ state.use_mesh_builtin_materials = true; state.bake_fps = p_bake_fps; - Error err = state.load(p_path, flags, p_flags & EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS, false); + Error err = state.load(p_path, flags, p_flags & EditorSceneFormatImporter::IMPORT_GENERATE_TANGENT_ARRAYS, false); if (r_err) { *r_err = err; @@ -1773,7 +1793,7 @@ Node *EditorSceneImporterCollada::import_scene(const String &p_path, uint32_t p_ } if (p_flags & IMPORT_ANIMATION) { - state.create_animations(true, true); + state.create_animations(true); AnimationPlayer *ap = memnew(AnimationPlayer); for (int i = 0; i < state.animations.size(); i++) { String name; @@ -1792,15 +1812,15 @@ Node *EditorSceneImporterCollada::import_scene(const String &p_path, uint32_t p_ return state.scene; } -Ref<Animation> EditorSceneImporterCollada::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { +Ref<Animation> EditorSceneFormatImporterCollada::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { ColladaImport state; state.use_mesh_builtin_materials = false; - Error err = state.load(p_path, Collada::IMPORT_FLAG_ANIMATION, p_flags & EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS); + Error err = state.load(p_path, Collada::IMPORT_FLAG_ANIMATION, p_flags & EditorSceneFormatImporter::IMPORT_GENERATE_TANGENT_ARRAYS); ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load animation from file '" + p_path + "'."); - state.create_animations(true, true); + state.create_animations(true); if (state.scene) { memdelete(state.scene); } @@ -1813,5 +1833,5 @@ Ref<Animation> EditorSceneImporterCollada::import_animation(const String &p_path return anim; } -EditorSceneImporterCollada::EditorSceneImporterCollada() { +EditorSceneFormatImporterCollada::EditorSceneFormatImporterCollada() { } diff --git a/editor/import/editor_import_collada.h b/editor/import/editor_import_collada.h index bf45322765..055a6fe178 100644 --- a/editor/import/editor_import_collada.h +++ b/editor/import/editor_import_collada.h @@ -33,8 +33,8 @@ #include "editor/import/resource_importer_scene.h" -class EditorSceneImporterCollada : public EditorSceneImporter { - GDCLASS(EditorSceneImporterCollada, EditorSceneImporter); +class EditorSceneFormatImporterCollada : public EditorSceneFormatImporter { + GDCLASS(EditorSceneFormatImporterCollada, EditorSceneFormatImporter); public: virtual uint32_t get_import_flags() const override; @@ -42,7 +42,7 @@ public: virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps = nullptr, Error *r_err = nullptr) override; virtual Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) override; - EditorSceneImporterCollada(); + EditorSceneFormatImporterCollada(); }; #endif diff --git a/editor/import/editor_importer_bake_reset.cpp b/editor/import/editor_importer_bake_reset.cpp deleted file mode 100644 index 451a07351c..0000000000 --- a/editor/import/editor_importer_bake_reset.cpp +++ /dev/null @@ -1,234 +0,0 @@ -/*************************************************************************/ -/* editor_importer_bake_reset.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "editor/import/editor_importer_bake_reset.h" - -#include "core/error/error_list.h" -#include "core/error/error_macros.h" -#include "core/math/transform_3d.h" -#include "resource_importer_scene.h" -#include "scene/3d/importer_mesh_instance_3d.h" -#include "scene/3d/mesh_instance_3d.h" -#include "scene/3d/node_3d.h" -#include "scene/3d/skeleton_3d.h" -#include "scene/animation/animation_player.h" - -// Given that an engineering team has made a reference character, one wants ten animators to create animations. -// Currently, a tech artist needs to combine the ten files into one exported gltf2 to import into Godot Engine. -// We bake the RESET animation and then set it to identity, -// so that rigs with corresponding RESET animation can have their animations transferred with ease. -// -// The original algorithm for the code was used to change skeleton bone rolls to be parent to child. -// -// Reference https://github.com/godotengine/godot-proposals/issues/2961 -void BakeReset::_bake_animation_pose(Node *scene, const String &p_bake_anim) { - Map<StringName, BakeResetRestBone> r_rest_bones; - Vector<Node3D *> r_meshes; - List<Node *> queue; - queue.push_back(scene); - while (!queue.is_empty()) { - List<Node *>::Element *E = queue.front(); - Node *node = E->get(); - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(node); - // Step 1: import scene with animations into the rest bones data structure. - _fetch_reset_animation(ap, r_rest_bones, p_bake_anim); - - int child_count = node->get_child_count(); - for (int i = 0; i < child_count; i++) { - queue.push_back(node->get_child(i)); - } - queue.pop_front(); - } - - queue.push_back(scene); - while (!queue.is_empty()) { - List<Node *>::Element *E = queue.front(); - Node *node = E->get(); - ImporterMeshInstance3D *editor_mesh_3d = scene->cast_to<ImporterMeshInstance3D>(node); - MeshInstance3D *mesh_3d = scene->cast_to<MeshInstance3D>(node); - if (scene->cast_to<Skeleton3D>(node)) { - Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(node); - - // Step 2: Bake the RESET animation from the RestBone to the skeleton. - _fix_skeleton(skeleton, r_rest_bones); - } - if (editor_mesh_3d) { - NodePath path = editor_mesh_3d->get_skeleton_path(); - if (!path.is_empty() && editor_mesh_3d->get_node_or_null(path) && Object::cast_to<Skeleton3D>(editor_mesh_3d->get_node_or_null(path))) { - r_meshes.push_back(editor_mesh_3d); - } - } else if (mesh_3d) { - NodePath path = mesh_3d->get_skeleton_path(); - if (!path.is_empty() && mesh_3d->get_node_or_null(path) && Object::cast_to<Skeleton3D>(mesh_3d->get_node_or_null(path))) { - r_meshes.push_back(mesh_3d); - } - } - int child_count = node->get_child_count(); - for (int i = 0; i < child_count; i++) { - queue.push_back(node->get_child(i)); - } - queue.pop_front(); - } - - queue.push_back(scene); - while (!queue.is_empty()) { - List<Node *>::Element *E = queue.front(); - Node *node = E->get(); - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(node); - if (ap) { - // Step 3: Key all RESET animation frames to identity. - _align_animations(ap, r_rest_bones); - } - - int child_count = node->get_child_count(); - for (int i = 0; i < child_count; i++) { - queue.push_back(node->get_child(i)); - } - queue.pop_front(); - } -} - -void BakeReset::_align_animations(AnimationPlayer *p_ap, const Map<StringName, BakeResetRestBone> &r_rest_bones) { - ERR_FAIL_NULL(p_ap); - List<StringName> anim_names; - p_ap->get_animation_list(&anim_names); - for (List<StringName>::Element *anim_i = anim_names.front(); anim_i; anim_i = anim_i->next()) { - Ref<Animation> a = p_ap->get_animation(anim_i->get()); - ERR_CONTINUE(a.is_null()); - for (const KeyValue<StringName, BakeResetRestBone> &rest_bone_i : r_rest_bones) { - int track = a->find_track(NodePath(rest_bone_i.key)); - if (track == -1) { - continue; - } - int new_track = a->add_track(Animation::TYPE_TRANSFORM3D); - NodePath new_path = NodePath(rest_bone_i.key); - const BakeResetRestBone rest_bone = rest_bone_i.value; - a->track_set_path(new_track, new_path); - for (int key_i = 0; key_i < a->track_get_key_count(track); key_i++) { - Vector3 loc; - Quaternion rot; - Vector3 scale; - Error err = a->transform_track_get_key(track, key_i, &loc, &rot, &scale); - ERR_CONTINUE(err); - real_t time = a->track_get_key_time(track, key_i); - rot.normalize(); - loc = loc - rest_bone.loc; - rot = rest_bone.rest_delta.get_rotation_quaternion().inverse() * rot; - rot.normalize(); - scale = Vector3(1, 1, 1) - (rest_bone.rest_delta.get_scale() - scale); - // Apply the reverse of the rest changes to make the key be close to identity transform. - a->transform_track_insert_key(new_track, time, loc, rot, scale); - } - a->remove_track(track); - } - } -} - -void BakeReset::_fetch_reset_animation(AnimationPlayer *p_ap, Map<StringName, BakeResetRestBone> &r_rest_bones, const String &p_bake_anim) { - if (!p_ap) { - return; - } - List<StringName> anim_names; - p_ap->get_animation_list(&anim_names); - Node *root = p_ap->get_owner(); - ERR_FAIL_NULL(root); - if (!p_ap->has_animation(p_bake_anim)) { - return; - } - Ref<Animation> a = p_ap->get_animation(p_bake_anim); - if (a.is_null()) { - return; - } - for (int32_t track = 0; track < a->get_track_count(); track++) { - NodePath path = a->track_get_path(track); - String string_path = path; - Skeleton3D *skeleton = root->cast_to<Skeleton3D>(root->get_node(string_path.get_slice(":", 0))); - if (!skeleton) { - continue; - } - String bone_name = string_path.get_slice(":", 1); - for (int key_i = 0; key_i < a->track_get_key_count(track); key_i++) { - Vector3 loc; - Quaternion rot; - Vector3 scale; - Error err = a->transform_track_get_key(track, key_i, &loc, &rot, &scale); - if (err != OK) { - ERR_PRINT_ONCE("Reset animation baker can't get key."); - continue; - } - rot.normalize(); - Basis rot_basis = Basis(rot, scale); - BakeResetRestBone rest_bone; - rest_bone.rest_delta = rot_basis; - rest_bone.loc = loc; - // Store the animation into the RestBone. - r_rest_bones[StringName(String(skeleton->get_owner()->get_path_to(skeleton)) + ":" + bone_name)] = rest_bone; - break; - } - } -} - -void BakeReset::_fix_skeleton(Skeleton3D *p_skeleton, Map<StringName, BakeReset::BakeResetRestBone> &r_rest_bones) { - int bone_count = p_skeleton->get_bone_count(); - - // First iterate through all the bones and update the RestBone. - for (int j = 0; j < bone_count; j++) { - StringName final_path = String(p_skeleton->get_owner()->get_path_to(p_skeleton)) + String(":") + p_skeleton->get_bone_name(j); - BakeResetRestBone &rest_bone = r_rest_bones[final_path]; - rest_bone.rest_local = p_skeleton->get_bone_rest(j); - } - for (int i = 0; i < bone_count; i++) { - int parent_bone = p_skeleton->get_bone_parent(i); - String path = p_skeleton->get_owner()->get_path_to(p_skeleton); - StringName final_path = String(path) + String(":") + p_skeleton->get_bone_name(parent_bone); - if (parent_bone >= 0) { - r_rest_bones[path].children.push_back(i); - } - } - - // When we apply transform to a bone, we also have to move all of its children in the opposite direction. - for (int i = 0; i < bone_count; i++) { - StringName final_path = String(p_skeleton->get_owner()->get_path_to(p_skeleton)) + String(":") + p_skeleton->get_bone_name(i); - r_rest_bones[final_path].rest_local = r_rest_bones[final_path].rest_local * Transform3D(r_rest_bones[final_path].rest_delta, r_rest_bones[final_path].loc); - // Iterate through the children and move in the opposite direction. - for (int j = 0; j < r_rest_bones[final_path].children.size(); j++) { - int child_index = r_rest_bones[final_path].children[j]; - StringName children_path = String(p_skeleton->get_name()) + String(":") + p_skeleton->get_bone_name(child_index); - r_rest_bones[children_path].rest_local = Transform3D(r_rest_bones[final_path].rest_delta, r_rest_bones[final_path].loc).affine_inverse() * r_rest_bones[children_path].rest_local; - } - } - - for (int i = 0; i < bone_count; i++) { - StringName final_path = String(p_skeleton->get_owner()->get_path_to(p_skeleton)) + String(":") + p_skeleton->get_bone_name(i); - ERR_CONTINUE(!r_rest_bones.has(final_path)); - Transform3D rest_transform = r_rest_bones[final_path].rest_local; - p_skeleton->set_bone_rest(i, rest_transform); - } -} diff --git a/editor/import/editor_importer_bake_reset.h b/editor/import/editor_importer_bake_reset.h deleted file mode 100644 index e36ae86181..0000000000 --- a/editor/import/editor_importer_bake_reset.h +++ /dev/null @@ -1,54 +0,0 @@ -/*************************************************************************/ -/* editor_importer_bake_reset.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef RESOURCE_IMPORTER_BAKE_RESET_H -#define RESOURCE_IMPORTER_BAKE_RESET_H - -#include "scene/main/node.h" - -class Skeleton3D; -class AnimationPlayer; -class BakeReset { - struct BakeResetRestBone { - Transform3D rest_local; - Basis rest_delta; - Vector3 loc; - Vector<int> children; - }; - -public: - void _bake_animation_pose(Node *scene, const String &p_bake_anim); - -private: - void _fix_skeleton(Skeleton3D *p_skeleton, Map<StringName, BakeReset::BakeResetRestBone> &r_rest_bones); - void _align_animations(AnimationPlayer *p_ap, const Map<StringName, BakeResetRestBone> &r_rest_bones); - void _fetch_reset_animation(AnimationPlayer *p_ap, Map<StringName, BakeResetRestBone> &r_rest_bones, const String &p_bake_anim); -}; -#endif diff --git a/editor/import/resource_importer_obj.h b/editor/import/resource_importer_obj.h index 1bb5ef33ce..d9f2f79903 100644 --- a/editor/import/resource_importer_obj.h +++ b/editor/import/resource_importer_obj.h @@ -33,8 +33,8 @@ #include "resource_importer_scene.h" -class EditorOBJImporter : public EditorSceneImporter { - GDCLASS(EditorOBJImporter, EditorSceneImporter); +class EditorOBJImporter : public EditorSceneFormatImporter { + GDCLASS(EditorOBJImporter, EditorSceneFormatImporter); public: virtual uint32_t get_import_flags() const override; diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index 1e93113488..c393d7f5fc 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -32,7 +32,7 @@ #include "core/io/resource_saver.h" #include "editor/editor_node.h" -#include "editor/import/editor_importer_bake_reset.h" + #include "editor/import/scene_import_settings.h" #include "scene/3d/area_3d.h" #include "scene/3d/collision_shape_3d.h" @@ -52,7 +52,7 @@ #include "scene/resources/surface_tool.h" #include "scene/resources/world_boundary_shape_3d.h" -uint32_t EditorSceneImporter::get_import_flags() const { +uint32_t EditorSceneFormatImporter::get_import_flags() const { int ret; if (GDVIRTUAL_CALL(_get_import_flags, ret)) { return ret; @@ -61,7 +61,7 @@ uint32_t EditorSceneImporter::get_import_flags() const { ERR_FAIL_V(0); } -void EditorSceneImporter::get_extensions(List<String> *r_extensions) const { +void EditorSceneFormatImporter::get_extensions(List<String> *r_extensions) const { Vector<String> arr; if (GDVIRTUAL_CALL(_get_extensions, arr)) { for (int i = 0; i < arr.size(); i++) { @@ -73,7 +73,7 @@ void EditorSceneImporter::get_extensions(List<String> *r_extensions) const { ERR_FAIL(); } -Node *EditorSceneImporter::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { +Node *EditorSceneFormatImporter::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { Object *ret; if (GDVIRTUAL_CALL(_import_scene, p_path, p_flags, p_bake_fps, ret)) { return Object::cast_to<Node>(ret); @@ -82,7 +82,7 @@ Node *EditorSceneImporter::import_scene(const String &p_path, uint32_t p_flags, ERR_FAIL_V(nullptr); } -Ref<Animation> EditorSceneImporter::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { +Ref<Animation> EditorSceneFormatImporter::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { Ref<Animation> ret; if (GDVIRTUAL_CALL(_import_animation, p_path, p_flags, p_bake_fps, ret)) { return ret; @@ -94,17 +94,17 @@ Ref<Animation> EditorSceneImporter::import_animation(const String &p_path, uint3 //for documenters, these functions are useful when an importer calls an external conversion helper (like, fbx2gltf), //and you want to load the resulting file -Node *EditorSceneImporter::import_scene_from_other_importer(const String &p_path, uint32_t p_flags, int p_bake_fps) { +Node *EditorSceneFormatImporter::import_scene_from_other_importer(const String &p_path, uint32_t p_flags, int p_bake_fps) { return ResourceImporterScene::get_singleton()->import_scene_from_other_importer(this, p_path, p_flags, p_bake_fps); } -Ref<Animation> EditorSceneImporter::import_animation_from_other_importer(const String &p_path, uint32_t p_flags, int p_bake_fps) { +Ref<Animation> EditorSceneFormatImporter::import_animation_from_other_importer(const String &p_path, uint32_t p_flags, int p_bake_fps) { return ResourceImporterScene::get_singleton()->import_animation_from_other_importer(this, p_path, p_flags, p_bake_fps); } -void EditorSceneImporter::_bind_methods() { - ClassDB::bind_method(D_METHOD("import_scene_from_other_importer", "path", "flags", "bake_fps"), &EditorSceneImporter::import_scene_from_other_importer); - ClassDB::bind_method(D_METHOD("import_animation_from_other_importer", "path", "flags", "bake_fps"), &EditorSceneImporter::import_animation_from_other_importer); +void EditorSceneFormatImporter::_bind_methods() { + ClassDB::bind_method(D_METHOD("import_scene_from_other_importer", "path", "flags", "bake_fps"), &EditorSceneFormatImporter::import_scene_from_other_importer); + ClassDB::bind_method(D_METHOD("import_animation_from_other_importer", "path", "flags", "bake_fps"), &EditorSceneFormatImporter::import_animation_from_other_importer); GDVIRTUAL_BIND(_get_import_flags); GDVIRTUAL_BIND(_get_extensions); @@ -144,6 +144,105 @@ void EditorScenePostImport::init(const String &p_source_file) { EditorScenePostImport::EditorScenePostImport() { } +/////////////////////////////////////////////////////// + +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)[p_name]; + } + if (current_options_dict) { + (*current_options_dict)[p_name]; + } + return Variant(); +} +void EditorScenePostImportPlugin::add_import_option(const String &p_name, Variant p_default_value) { + ERR_FAIL_COND_MSG(current_option_list == nullptr, "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, Variant p_default_value, PropertyHint p_hint, const String &p_hint_string, int p_usage_flags) { + ERR_FAIL_COND_MSG(current_option_list == nullptr, "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<ResourceImporter::ImportOption> *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_option, const Map<StringName, Variant> &p_options) const { + current_options = &p_options; + Variant ret; + GDVIRTUAL_CALL(_get_internal_option_visibility, p_category, p_option, ret); + current_options = nullptr; + return ret; +} +Variant EditorScenePostImportPlugin::get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &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, RES 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(List<ResourceImporter::ImportOption> *r_options) { + current_option_list = r_options; + GDVIRTUAL_CALL(_get_import_options); + current_option_list = nullptr; +} +Variant EditorScenePostImportPlugin::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { + current_options = &p_options; + Variant ret; + GDVIRTUAL_CALL(_get_option_visibility, p_option, ret); + current_options = nullptr; + return ret; +} + +void EditorScenePostImportPlugin::pre_process(Node *p_scene, const Map<StringName, Variant> &p_options) { + current_options = &p_options; + GDVIRTUAL_CALL(_pre_process, p_scene); + current_options = nullptr; +} +void EditorScenePostImportPlugin::post_process(Node *p_scene, const Map<StringName, Variant> &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", "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); + GDVIRTUAL_BIND(_get_option_visibility, "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_MAX); +} + +///////////////////////////////////////////////////////// + String ResourceImporterScene::get_importer_name() const { return "scene"; } @@ -153,7 +252,7 @@ String ResourceImporterScene::get_visible_name() const { } void ResourceImporterScene::get_recognized_extensions(List<String> *p_extensions) const { - for (Set<Ref<EditorSceneImporter>>::Element *E = importers.front(); E; E = E->next()) { + for (Set<Ref<EditorSceneFormatImporter>>::Element *E = importers.front(); E; E = E->next()) { E->get()->get_extensions(p_extensions); } } @@ -181,6 +280,13 @@ bool ResourceImporterScene::get_option_visibility(const String &p_option, const return false; } + for (int i = 0; i < post_importer_plugins.size(); i++) { + Variant ret = post_importer_plugins.write[i]->get_option_visibility(p_option, p_options); + if (ret.get_type() == Variant::BOOL) { + return ret; + } + } + return true; } @@ -547,6 +653,26 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref< return nullptr; } + { + ObjectID node_id = p_node->get_instance_id(); + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_NODE, p_root, p_node, RES(), node_settings); + if (ObjectDB::get_instance(node_id) == nullptr) { //may have been erased, so do not continue + break; + } + } + } + + if (Object::cast_to<ImporterMeshInstance3D>(p_node)) { + ObjectID node_id = p_node->get_instance_id(); + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE, p_root, p_node, RES(), node_settings); + if (ObjectDB::get_instance(node_id) == nullptr) { //may have been erased, so do not continue + break; + } + } + } + if (Object::cast_to<ImporterMeshInstance3D>(p_node)) { ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(p_node); @@ -566,6 +692,11 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref< if (mat_id != String() && p_material_data.has(mat_id)) { Dictionary matdata = p_material_data[mat_id]; + + for (int j = 0; j < post_importer_plugins.size(); j++) { + post_importer_plugins.write[j]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MATERIAL, p_root, p_node, mat, matdata); + } + if (matdata.has("use_external/enabled") && bool(matdata["use_external/enabled"]) && matdata.has("use_external/path")) { String path = matdata["use_external/path"]; Ref<Material> external_mat = ResourceLoader::load(path); @@ -715,6 +846,10 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref< } } + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, p_root, p_node, RES(), node_settings); + } + bool use_optimizer = node_settings["optimizer/enabled"]; float anim_optimizer_linerr = node_settings["optimizer/max_linear_error"]; float anim_optimizer_angerr = node_settings["optimizer/max_angular_error"]; @@ -779,6 +914,41 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref< } } } + + 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.size() > 1 && (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); + } + } + + if (post_importer_plugins.size()) { + List<StringName> anims; + ap->get_animation_list(&anims); + for (const StringName &name : anims) { + if (p_animation_data.has(name)) { + Ref<Animation> anim = ap->get_animation(name); + Dictionary anim_settings = p_animation_data[name]; + { + //fill with default values + List<ImportOption> 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; + } + } + } + + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION, p_root, p_node, anim, node_settings); + } + } + } } } @@ -851,42 +1021,57 @@ void ResourceImporterScene::_create_clips(AnimationPlayer *anim, const Array &p_ new_anim->track_set_path(dtrack, default_anim->track_get_path(j)); if (kt > (from + 0.01) && k > 0) { - if (default_anim->track_get_type(j) == Animation::TYPE_TRANSFORM3D) { - Quaternion q; + if (default_anim->track_get_type(j) == Animation::TYPE_POSITION_3D) { Vector3 p; + default_anim->position_track_interpolate(j, from, &p); + new_anim->position_track_insert_key(dtrack, 0, p); + } else if (default_anim->track_get_type(j) == Animation::TYPE_ROTATION_3D) { + Quaternion r; + default_anim->rotation_track_interpolate(j, from, &r); + new_anim->rotation_track_insert_key(dtrack, 0, r); + } else if (default_anim->track_get_type(j) == Animation::TYPE_SCALE_3D) { Vector3 s; - default_anim->transform_track_interpolate(j, from, &p, &q, &s); - new_anim->transform_track_insert_key(dtrack, 0, p, q, s); - } - if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { + default_anim->scale_track_interpolate(j, from, &s); + new_anim->scale_track_insert_key(dtrack, 0, s); + } else if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { Variant var = default_anim->value_track_interpolate(j, from); new_anim->track_insert_key(dtrack, 0, var); } } } - if (default_anim->track_get_type(j) == Animation::TYPE_TRANSFORM3D) { - Quaternion q; + if (default_anim->track_get_type(j) == Animation::TYPE_POSITION_3D) { Vector3 p; + default_anim->position_track_get_key(j, k, &p); + new_anim->position_track_insert_key(dtrack, kt - from, p); + } else if (default_anim->track_get_type(j) == Animation::TYPE_ROTATION_3D) { + Quaternion r; + default_anim->rotation_track_get_key(j, k, &r); + new_anim->rotation_track_insert_key(dtrack, kt - from, r); + } else if (default_anim->track_get_type(j) == Animation::TYPE_SCALE_3D) { Vector3 s; - default_anim->transform_track_get_key(j, k, &p, &q, &s); - new_anim->transform_track_insert_key(dtrack, kt - from, p, q, s); - } - if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { + default_anim->scale_track_get_key(j, k, &s); + new_anim->scale_track_insert_key(dtrack, kt - from, s); + } else if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { Variant var = default_anim->track_get_key_value(j, k); new_anim->track_insert_key(dtrack, kt - from, var); } } if (dtrack != -1 && kt >= to) { - if (default_anim->track_get_type(j) == Animation::TYPE_TRANSFORM3D) { - Quaternion q; + if (default_anim->track_get_type(j) == Animation::TYPE_POSITION_3D) { Vector3 p; + default_anim->position_track_interpolate(j, to, &p); + new_anim->position_track_insert_key(dtrack, to - from, p); + } else if (default_anim->track_get_type(j) == Animation::TYPE_ROTATION_3D) { + Quaternion r; + default_anim->rotation_track_interpolate(j, to, &r); + new_anim->rotation_track_insert_key(dtrack, to - from, r); + } else if (default_anim->track_get_type(j) == Animation::TYPE_SCALE_3D) { Vector3 s; - default_anim->transform_track_interpolate(j, to, &p, &q, &s); - new_anim->transform_track_insert_key(dtrack, to - from, p, q, s); - } - if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { + default_anim->scale_track_interpolate(j, to, &s); + new_anim->scale_track_insert_key(dtrack, to - from, s); + } else if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { Variant var = default_anim->value_track_interpolate(j, to); new_anim->track_insert_key(dtrack, to - from, var); } @@ -897,16 +1082,25 @@ void ResourceImporterScene::_create_clips(AnimationPlayer *anim, const Array &p_ new_anim->add_track(default_anim->track_get_type(j)); dtrack = new_anim->get_track_count() - 1; new_anim->track_set_path(dtrack, default_anim->track_get_path(j)); - if (default_anim->track_get_type(j) == Animation::TYPE_TRANSFORM3D) { - Quaternion q; + if (default_anim->track_get_type(j) == Animation::TYPE_POSITION_3D) { Vector3 p; + default_anim->position_track_interpolate(j, from, &p); + new_anim->position_track_insert_key(dtrack, 0, p); + default_anim->position_track_interpolate(j, to, &p); + new_anim->position_track_insert_key(dtrack, to - from, p); + } else if (default_anim->track_get_type(j) == Animation::TYPE_ROTATION_3D) { + Quaternion r; + default_anim->rotation_track_interpolate(j, from, &r); + new_anim->rotation_track_insert_key(dtrack, 0, r); + default_anim->rotation_track_interpolate(j, to, &r); + new_anim->rotation_track_insert_key(dtrack, to - from, r); + } else if (default_anim->track_get_type(j) == Animation::TYPE_SCALE_3D) { Vector3 s; - default_anim->transform_track_interpolate(j, from, &p, &q, &s); - new_anim->transform_track_insert_key(dtrack, 0, p, q, s); - default_anim->transform_track_interpolate(j, to, &p, &q, &s); - new_anim->transform_track_insert_key(dtrack, to - from, p, q, s); - } - if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { + default_anim->scale_track_interpolate(j, from, &s); + new_anim->scale_track_insert_key(dtrack, 0, s); + default_anim->scale_track_interpolate(j, to, &s); + new_anim->scale_track_insert_key(dtrack, to - from, s); + } else if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) { Variant var = default_anim->value_track_interpolate(j, from); new_anim->track_insert_key(dtrack, 0, var); Variant to_var = default_anim->value_track_interpolate(j, to); @@ -1000,6 +1194,9 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_linear_error"), 0.05)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_angular_error"), 0.01)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_angle"), 22)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_tracks/position", PROPERTY_HINT_ENUM, "IfPresent,IfPresentForAll,Never"), 1)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_tracks/rotation", PROPERTY_HINT_ENUM, "IfPresent,IfPresentForAll,Never"), 1)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_tracks/scale", PROPERTY_HINT_ENUM, "IfPresent,IfPresentForAll,Never"), 1)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "slices/amount", PROPERTY_HINT_RANGE, "0,256,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0)); for (int i = 0; i < 256; i++) { @@ -1015,6 +1212,10 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p default: { } } + + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->get_internal_import_options(EditorScenePostImportPlugin::InternalImportCategory(p_category), r_options); + } } bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const { @@ -1117,6 +1318,13 @@ bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategor } } + for (int i = 0; i < post_importer_plugins.size(); i++) { + Variant ret = post_importer_plugins.write[i]->get_internal_option_visibility(EditorScenePostImportPlugin::InternalImportCategory(p_category), p_option, p_options); + if (ret.get_type() == Variant::BOOL) { + return ret; + } + } + return true; } @@ -1144,6 +1352,14 @@ bool ResourceImporterScene::get_internal_option_update_view_required(InternalImp default: { } } + + for (int i = 0; i < post_importer_plugins.size(); i++) { + Variant ret = post_importer_plugins.write[i]->get_internal_option_update_view_required(EditorScenePostImportPlugin::InternalImportCategory(p_category), p_option, p_options); + if (ret.get_type() == Variant::BOOL) { + return ret; + } + } + return false; } @@ -1171,11 +1387,14 @@ void ResourceImporterScene::get_import_options(List<ImportOption> *r_options, in r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "meshes/lightmap_texel_size", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 0.1)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "skins/use_named_skins"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/import"), true)); - r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/bake_reset_animation"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "animation/fps", PROPERTY_HINT_RANGE, "1,120,1"), 15)); r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "import_script/path", PROPERTY_HINT_FILE, script_ext_hint), "")); r_options->push_back(ImportOption(PropertyInfo(Variant::DICTIONARY, "_subresources", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), Dictionary())); + + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->get_import_options(r_options); + } } void ResourceImporterScene::_replace_owner(Node *p_node, Node *p_scene, Node *p_new_owner) { @@ -1189,11 +1408,11 @@ void ResourceImporterScene::_replace_owner(Node *p_node, Node *p_scene, Node *p_ } } -Node *ResourceImporterScene::import_scene_from_other_importer(EditorSceneImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps) { - Ref<EditorSceneImporter> importer; +Node *ResourceImporterScene::import_scene_from_other_importer(EditorSceneFormatImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps) { + Ref<EditorSceneFormatImporter> importer; String ext = p_path.get_extension().to_lower(); - for (Set<Ref<EditorSceneImporter>>::Element *E = importers.front(); E; E = E->next()) { + for (Set<Ref<EditorSceneFormatImporter>>::Element *E = importers.front(); E; E = E->next()) { if (E->get().ptr() == p_exception) { continue; } @@ -1219,11 +1438,11 @@ Node *ResourceImporterScene::import_scene_from_other_importer(EditorSceneImporte return importer->import_scene(p_path, p_flags, p_bake_fps, &missing, &err); } -Ref<Animation> ResourceImporterScene::import_animation_from_other_importer(EditorSceneImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps) { - Ref<EditorSceneImporter> importer; +Ref<Animation> ResourceImporterScene::import_animation_from_other_importer(EditorSceneFormatImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps) { + Ref<EditorSceneFormatImporter> importer; String ext = p_path.get_extension().to_lower(); - for (Set<Ref<EditorSceneImporter>>::Element *E = importers.front(); E; E = E->next()) { + for (Set<Ref<EditorSceneFormatImporter>>::Element *E = importers.front(); E; E = E->next()) { if (E->get().ptr() == p_exception) { continue; } @@ -1320,6 +1539,10 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m save_to_file = ""; } } + + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MESH, nullptr, src_mesh_node, src_mesh_node->get_mesh(), mesh_settings); + } } if (generate_lods) { @@ -1422,14 +1645,147 @@ void ResourceImporterScene::_add_shapes(Node *p_node, const Vector<Ref<Shape3D>> } } +void ResourceImporterScene::_optimize_track_usage(AnimationPlayer *p_player, AnimationImportTracks *p_track_actions) { + List<StringName> anims; + p_player->get_animation_list(&anims); + Node *parent = p_player->get_parent(); + ERR_FAIL_COND(parent == nullptr); + OrderedHashMap<NodePath, uint32_t> used_tracks[TRACK_CHANNEL_MAX]; + bool tracks_to_add = false; + static const Animation::TrackType track_types[TRACK_CHANNEL_MAX] = { Animation::TYPE_POSITION_3D, Animation::TYPE_ROTATION_3D, Animation::TYPE_SCALE_3D }; + for (const StringName &I : anims) { + Ref<Animation> anim = p_player->get_animation(I); + for (int i = 0; i < anim->get_track_count(); i++) { + for (int j = 0; j < TRACK_CHANNEL_MAX; j++) { + if (anim->track_get_type(i) != track_types[j]) { + continue; + } + switch (p_track_actions[j]) { + case ANIMATION_IMPORT_TRACKS_IF_PRESENT: { + // Do Nothing. + } break; + case ANIMATION_IMPORT_TRACKS_IF_PRESENT_FOR_ALL: { + used_tracks[j].insert(anim->track_get_path(i), 0); + tracks_to_add = true; + } break; + case ANIMATION_IMPORT_TRACKS_NEVER: { + anim->remove_track(i); + i--; + } break; + } + } + } + } + + if (!tracks_to_add) { + return; + } + + uint32_t pass = 0; + for (const StringName &I : anims) { + Ref<Animation> anim = p_player->get_animation(I); + for (int j = 0; j < TRACK_CHANNEL_MAX; j++) { + if (p_track_actions[j] != ANIMATION_IMPORT_TRACKS_IF_PRESENT_FOR_ALL) { + continue; + } + + pass++; + + for (int i = 0; i < anim->get_track_count(); i++) { + if (anim->track_get_type(i) != track_types[j]) { + continue; + } + + NodePath path = anim->track_get_path(i); + + ERR_CONTINUE(!used_tracks[j].has(path)); // Should never happen. + + used_tracks[j][path] = pass; + } + + for (OrderedHashMap<NodePath, uint32_t>::Element J = used_tracks[j].front(); J; J = J.next()) { + if (J.get() == pass) { + continue; + } + + NodePath path = J.key(); + Node *n = parent->get_node(path); + Skeleton3D *skel = Object::cast_to<Skeleton3D>(n); + Node3D *n3d = Object::cast_to<Node3D>(n); + 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; + } + skel->get_bone_pose(bone_idx); + loc = skel->get_bone_pose_position(bone_idx); + rot = skel->get_bone_pose_rotation(bone_idx); + scale = skel->get_bone_pose_scale(bone_idx); + } else if (n3d) { + loc = n3d->get_position(); + rot = n3d->get_transform().basis.get_rotation_quaternion(); + scale = n3d->get_scale(); + } else { + continue; + } + + // Ensure insertion keeps tracks together and ordered by type (loc/rot/scale) + int insert_at_pos = -1; + for (int k = 0; k < anim->get_track_count(); k++) { + NodePath tpath = anim->track_get_path(k); + + if (path == tpath) { + Animation::TrackType ttype = anim->track_get_type(k); + if (insert_at_pos == -1) { + // First insert, determine whether replacing or kicking back + if (track_types[j] < ttype) { + insert_at_pos = k; + break; // No point in continuing. + } else { + insert_at_pos = k + 1; + } + } else if (ttype < track_types[j]) { + // Kick back. + insert_at_pos = k + 1; + } + } else if (insert_at_pos >= 0) { + break; + } + } + int track_idx = anim->add_track(track_types[j], insert_at_pos); + + anim->track_set_path(track_idx, path); + anim->track_set_imported(track_idx, true); + switch (j) { + case TRACK_CHANNEL_POSITION: { + anim->position_track_insert_key(track_idx, 0, loc); + } break; + case TRACK_CHANNEL_ROTATION: { + anim->rotation_track_insert_key(track_idx, 0, rot); + } break; + case TRACK_CHANNEL_SCALE: { + anim->scale_track_insert_key(track_idx, 0, scale); + } break; + default: { + } + } + } + } + } +} + Node *ResourceImporterScene::pre_import(const String &p_source_file) { - Ref<EditorSceneImporter> importer; + Ref<EditorSceneFormatImporter> importer; String ext = p_source_file.get_extension().to_lower(); EditorProgress progress("pre-import", TTR("Pre-Import Scene"), 0); progress.step(TTR("Importing Scene..."), 0); - for (Set<Ref<EditorSceneImporter>>::Element *E = importers.front(); E; E = E->next()) { + for (Set<Ref<EditorSceneFormatImporter>>::Element *E = importers.front(); E; E = E->next()) { List<String> extensions; E->get()->get_extensions(&extensions); @@ -1448,7 +1804,7 @@ Node *ResourceImporterScene::pre_import(const String &p_source_file) { ERR_FAIL_COND_V(!importer.is_valid(), nullptr); Error err = OK; - Node *scene = importer->import_scene(p_source_file, EditorSceneImporter::IMPORT_ANIMATION | EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS, 15, nullptr, &err); + Node *scene = importer->import_scene(p_source_file, EditorSceneFormatImporter::IMPORT_ANIMATION | EditorSceneFormatImporter::IMPORT_GENERATE_TANGENT_ARRAYS, 15, nullptr, &err); if (!scene || err != OK) { return nullptr; } @@ -1463,13 +1819,13 @@ Node *ResourceImporterScene::pre_import(const String &p_source_file) { Error ResourceImporterScene::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { const String &src_path = p_source_file; - Ref<EditorSceneImporter> importer; + Ref<EditorSceneFormatImporter> importer; String ext = src_path.get_extension().to_lower(); EditorProgress progress("import", TTR("Import Scene"), 104); progress.step(TTR("Importing Scene..."), 0); - for (Set<Ref<EditorSceneImporter>>::Element *E = importers.front(); E; E = E->next()) { + for (Set<Ref<EditorSceneFormatImporter>>::Element *E = importers.front(); E; E = E->next()) { List<String> extensions; E->get()->get_extensions(&extensions); @@ -1492,16 +1848,16 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p int import_flags = 0; if (bool(p_options["animation/import"])) { - import_flags |= EditorSceneImporter::IMPORT_ANIMATION; + import_flags |= EditorSceneFormatImporter::IMPORT_ANIMATION; } if (bool(p_options["skins/use_named_skins"])) { - import_flags |= EditorSceneImporter::IMPORT_USE_NAMED_SKIN_BINDS; + import_flags |= EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS; } bool ensure_tangents = p_options["meshes/ensure_tangents"]; if (ensure_tangents) { - import_flags |= EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS; + import_flags |= EditorSceneFormatImporter::IMPORT_GENERATE_TANGENT_ARRAYS; } Error err = OK; @@ -1532,13 +1888,13 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p Map<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> collision_map; _pre_fix_node(scene, scene, collision_map); - _post_fix_node(scene, scene, collision_map, scanned_meshes, node_data, material_data, animation_data, fps); - bool use_bake_reset_animation = p_options["animation/bake_reset_animation"]; - if (use_bake_reset_animation) { - BakeReset bake_reset; - bake_reset._bake_animation_pose(scene, "RESET"); + + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->pre_process(scene, p_options); } + _post_fix_node(scene, scene, collision_map, scanned_meshes, node_data, material_data, animation_data, fps); + String root_type = p_options["nodes/root_type"]; root_type = root_type.split(" ")[0]; // full root_type is "ClassName (filename.gd)" for a script global class. @@ -1640,6 +1996,10 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p } } + for (int i = 0; i < post_importer_plugins.size(); i++) { + post_importer_plugins.write[i]->post_process(scene, p_options); + } + progress.step(TTR("Saving..."), 104); Ref<PackedScene> packer = memnew(PackedScene); @@ -1671,15 +2031,15 @@ ResourceImporterScene::ResourceImporterScene() { /////////////////////////////////////// -uint32_t EditorSceneImporterESCN::get_import_flags() const { +uint32_t EditorSceneFormatImporterESCN::get_import_flags() const { return IMPORT_SCENE; } -void EditorSceneImporterESCN::get_extensions(List<String> *r_extensions) const { +void EditorSceneFormatImporterESCN::get_extensions(List<String> *r_extensions) const { r_extensions->push_back("escn"); } -Node *EditorSceneImporterESCN::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { +Node *EditorSceneFormatImporterESCN::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { Error error; Ref<PackedScene> ps = ResourceFormatLoaderText::singleton->load(p_path, p_path, &error); ERR_FAIL_COND_V_MSG(!ps.is_valid(), nullptr, "Cannot load scene as text resource from path '" + p_path + "'."); @@ -1690,6 +2050,6 @@ Node *EditorSceneImporterESCN::import_scene(const String &p_path, uint32_t p_fla return scene; } -Ref<Animation> EditorSceneImporterESCN::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { +Ref<Animation> EditorSceneFormatImporterESCN::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { ERR_FAIL_V(Ref<Animation>()); } diff --git a/editor/import/resource_importer_scene.h b/editor/import/resource_importer_scene.h index 2a67fa9aae..fab602b8ff 100644 --- a/editor/import/resource_importer_scene.h +++ b/editor/import/resource_importer_scene.h @@ -42,8 +42,8 @@ class Material; class AnimationPlayer; class ImporterMesh; -class EditorSceneImporter : public RefCounted { - GDCLASS(EditorSceneImporter, RefCounted); +class EditorSceneFormatImporter : public RefCounted { + GDCLASS(EditorSceneFormatImporter, RefCounted); protected: static void _bind_methods(); @@ -70,7 +70,7 @@ public: virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err = nullptr); virtual Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps); - EditorSceneImporter() {} + EditorSceneFormatImporter() {} }; class EditorScenePostImport : public RefCounted { @@ -90,10 +90,64 @@ public: EditorScenePostImport(); }; +class EditorScenePostImportPlugin : public RefCounted { + GDCLASS(EditorScenePostImportPlugin, RefCounted); + +public: + enum InternalImportCategory { + INTERNAL_IMPORT_CATEGORY_NODE, + INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE, + INTERNAL_IMPORT_CATEGORY_MESH, + INTERNAL_IMPORT_CATEGORY_MATERIAL, + INTERNAL_IMPORT_CATEGORY_ANIMATION, + INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, + INTERNAL_IMPORT_CATEGORY_MAX + }; + +private: + mutable const Map<StringName, Variant> *current_options = nullptr; + mutable const Dictionary *current_options_dict = nullptr; + List<ResourceImporter::ImportOption> *current_option_list = nullptr; + InternalImportCategory current_category = INTERNAL_IMPORT_CATEGORY_MAX; + +protected: + GDVIRTUAL1(_get_internal_import_options, int) + GDVIRTUAL2RC(Variant, _get_internal_option_visibility, int, String) + GDVIRTUAL2RC(Variant, _get_internal_option_update_view_required, int, String) + GDVIRTUAL4(_internal_process, int, Node *, Node *, RES) + GDVIRTUAL0(_get_import_options) + GDVIRTUAL1RC(Variant, _get_option_visibility, String) + GDVIRTUAL1(_pre_process, Node *) + GDVIRTUAL1(_post_process, Node *) + + static void _bind_methods(); + +public: + Variant get_option_value(const StringName &p_name) const; + void add_import_option(const String &p_name, Variant p_default_value); + void add_import_option_advanced(Variant::Type p_type, const String &p_name, Variant p_default_value, PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = String(), int p_usage_flags = PROPERTY_USAGE_DEFAULT); + + virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options); + virtual Variant get_internal_option_visibility(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const; + virtual Variant get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const; + + virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, RES p_resource, const Dictionary &p_options); + + virtual void get_import_options(List<ResourceImporter::ImportOption> *r_options); + virtual Variant get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const; + + virtual void pre_process(Node *p_scene, const Map<StringName, Variant> &p_options); + virtual void post_process(Node *p_scene, const Map<StringName, Variant> &p_options); + + EditorScenePostImportPlugin() {} +}; + +VARIANT_ENUM_CAST(EditorScenePostImportPlugin::InternalImportCategory) + class ResourceImporterScene : public ResourceImporter { GDCLASS(ResourceImporterScene, ResourceImporter); - Set<Ref<EditorSceneImporter>> importers; + Set<Ref<EditorSceneFormatImporter>> importers; static ResourceImporterScene *singleton; @@ -144,13 +198,32 @@ class ResourceImporterScene : public ResourceImporter { void _generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<Vector<uint8_t>> &r_lightmap_caches); void _add_shapes(Node *p_node, const Vector<Ref<Shape3D>> &p_shapes); + enum AnimationImportTracks { + ANIMATION_IMPORT_TRACKS_IF_PRESENT, + ANIMATION_IMPORT_TRACKS_IF_PRESENT_FOR_ALL, + ANIMATION_IMPORT_TRACKS_NEVER, + }; + enum TrackChannel { + TRACK_CHANNEL_POSITION, + TRACK_CHANNEL_ROTATION, + TRACK_CHANNEL_SCALE, + TRACK_CHANNEL_MAX + }; + + void _optimize_track_usage(AnimationPlayer *p_player, AnimationImportTracks *p_track_actions); + + mutable Vector<Ref<EditorScenePostImportPlugin>> post_importer_plugins; + public: static ResourceImporterScene *get_singleton() { return singleton; } - const Set<Ref<EditorSceneImporter>> &get_importers() const { return importers; } + void add_post_importer_plugin(const Ref<EditorScenePostImportPlugin> &p_plugin) { post_importer_plugins.push_back(p_plugin); } + void remove_post_importer_plugin(const Ref<EditorScenePostImportPlugin> &p_plugin) { post_importer_plugins.erase(p_plugin); } + + const Set<Ref<EditorSceneFormatImporter>> &get_importers() const { return importers; } - void add_importer(Ref<EditorSceneImporter> p_importer) { importers.insert(p_importer); } - void remove_importer(Ref<EditorSceneImporter> p_importer) { importers.erase(p_importer); } + void add_importer(Ref<EditorSceneFormatImporter> p_importer) { importers.insert(p_importer); } + void remove_importer(Ref<EditorSceneFormatImporter> p_importer) { importers.erase(p_importer); } virtual String get_importer_name() const override; virtual String get_visible_name() const override; @@ -163,13 +236,13 @@ public: virtual String get_preset_name(int p_idx) const override; enum InternalImportCategory { - INTERNAL_IMPORT_CATEGORY_NODE, - INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE, - INTERNAL_IMPORT_CATEGORY_MESH, - INTERNAL_IMPORT_CATEGORY_MATERIAL, - INTERNAL_IMPORT_CATEGORY_ANIMATION, - INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, - INTERNAL_IMPORT_CATEGORY_MAX + INTERNAL_IMPORT_CATEGORY_NODE = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_NODE, + INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE, + INTERNAL_IMPORT_CATEGORY_MESH = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MESH, + INTERNAL_IMPORT_CATEGORY_MATERIAL = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MATERIAL, + INTERNAL_IMPORT_CATEGORY_ANIMATION = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION, + INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, + INTERNAL_IMPORT_CATEGORY_MAX = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MAX }; void get_internal_import_options(InternalImportCategory p_category, List<ImportOption> *r_options) const; @@ -191,8 +264,8 @@ public: Node *pre_import(const String &p_source_file); virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; - Node *import_scene_from_other_importer(EditorSceneImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps); - Ref<Animation> import_animation_from_other_importer(EditorSceneImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps); + Node *import_scene_from_other_importer(EditorSceneFormatImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps); + Ref<Animation> import_animation_from_other_importer(EditorSceneFormatImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps); virtual bool has_advanced_options() const override; virtual void show_advanced_options(const String &p_path) override; @@ -208,8 +281,8 @@ public: static Transform3D get_collision_shapes_transform(const M &p_options); }; -class EditorSceneImporterESCN : public EditorSceneImporter { - GDCLASS(EditorSceneImporterESCN, EditorSceneImporter); +class EditorSceneFormatImporterESCN : public EditorSceneFormatImporter { + GDCLASS(EditorSceneFormatImporterESCN, EditorSceneFormatImporter); public: virtual uint32_t get_import_flags() const override; diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index ef872bcead..8935f715e6 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -1174,7 +1174,6 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo if (!panning) { if (b->is_pressed() && (b->get_button_index() == MOUSE_BUTTON_MIDDLE || - b->get_button_index() == MOUSE_BUTTON_RIGHT || (b->get_button_index() == MOUSE_BUTTON_LEFT && tool == TOOL_PAN) || (b->get_button_index() == MOUSE_BUTTON_LEFT && !EditorSettings::get_singleton()->get("editors/2d/simple_panning") && pan_pressed))) { // Pan the viewport @@ -1232,8 +1231,9 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo } } - if (is_pan_key) { + if (is_pan_key && pan_pressed != k->is_pressed()) { pan_pressed = k->is_pressed(); + _update_cursor(); } } @@ -2615,7 +2615,19 @@ void CanvasItemEditor::_update_cursor() { c = CURSOR_HSIZE; } - viewport->set_default_cursor_shape(c); + if (pan_pressed) { + c = CURSOR_DRAG; + } + + if (c != viewport->get_default_cursor_shape()) { + viewport->set_default_cursor_shape(c); + + // Force refresh cursor if it's over the viewport. + if (viewport->get_global_rect().has_point(get_global_mouse_position())) { + DisplayServer::CursorShape ds_cursor_shape = (DisplayServer::CursorShape)viewport->get_default_cursor_shape(); + DisplayServer::get_singleton()->cursor_set_shape(ds_cursor_shape); + } + } } void CanvasItemEditor::_draw_text_at_position(Point2 p_position, String p_string, Side p_side) { @@ -4254,10 +4266,6 @@ void CanvasItemEditor::_button_tool_select(int p_index) { viewport->update(); _update_cursor(); - - // Request immediate refresh of cursor when using hot-keys to switch between tools - DisplayServer::CursorShape ds_cursor_shape = (DisplayServer::CursorShape)viewport->get_default_cursor_shape(); - DisplayServer::get_singleton()->cursor_set_shape(ds_cursor_shape); } void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, bool p_scale, bool p_on_existing) { @@ -5384,7 +5392,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { pan_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select), make_binds(TOOL_PAN)); pan_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/pan_mode", TTR("Pan Mode"), KEY_G)); pan_button->set_shortcut_context(this); - pan_button->set_tooltip(TTR("Pan Mode")); + pan_button->set_tooltip(TTR("You can also use Pan View shortcut (Space by default) to pan in any mode.")); ruler_button = memnew(Button); ruler_button->set_flat(true); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index ea6ef8ab84..8600ef3ae8 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -3863,7 +3863,7 @@ void Node3DEditorViewport::assign_pending_data_pointers(Node3D *p_preview_node, } Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const { - const float MAX_DISTANCE = 10; + const float MAX_DISTANCE = 50.0; Vector3 world_ray = _get_ray(p_pos); Vector3 world_pos = _get_ray_pos(p_pos); @@ -6469,7 +6469,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() { // We add a bit of margin to the from position to avoid it from snapping // when the spatial is already on a floor and there's another floor under // it - from = from + Vector3(0.0, 0.2, 0.0); + from = from + Vector3(0.0, 1, 0.0); Dictionary d; @@ -6485,7 +6485,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() { Array keys = snap_data.keys(); // The maximum height an object can travel to be snapped - const float max_snap_height = 20.0; + const float max_snap_height = 500.0; // Will be set to `true` if at least one node from the selection was successfully snapped bool snapped_to_floor = false; diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 5df6743f4c..677a5f1f2c 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -214,6 +214,7 @@ Ref<EditorSyntaxHighlighter> EditorPlainTextSyntaxHighlighter::_create() const { void ScriptEditorBase::_bind_methods() { ClassDB::bind_method(D_METHOD("get_base_editor"), &ScriptEditorBase::get_base_editor); + ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &ScriptEditorBase::add_syntax_highlighter); ADD_SIGNAL(MethodInfo("name_changed")); ADD_SIGNAL(MethodInfo("edited_script_changed")); @@ -762,20 +763,24 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tselected); if (current) { - Ref<Script> script = current->get_edited_resource(); - if (p_save && script.is_valid()) { + RES file = current->get_edited_resource(); + if (p_save && file.is_valid()) { // Do not try to save internal scripts, but prompt to save in-memory // scripts which are not saved to disk yet (have empty path). - if (script->get_path().find("local://") == -1 && script->get_path().find("::") == -1) { + if (file->get_path().find("local://") == -1 && file->get_path().find("::") == -1) { save_current_script(); } } - if (script.is_valid()) { - if (!script->get_path().is_empty()) { + if (file.is_valid()) { + if (!file->get_path().is_empty()) { // Only saved scripts can be restored. - previous_scripts.push_back(script->get_path()); + previous_scripts.push_back(file->get_path()); + } + + Ref<Script> script = file; + if (script.is_valid()) { + notify_script_close(script); } - notify_script_close(script); } } @@ -1079,7 +1084,6 @@ void ScriptEditor::_file_dialog_action(String p_file) { memdelete(file); if (EditorFileSystem::get_singleton()) { - const Vector<String> textfile_extensions = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); if (textfile_extensions.has(p_file.get_extension())) { EditorFileSystem::get_singleton()->update_file(p_file); } @@ -1167,9 +1171,8 @@ void ScriptEditor::_menu_option(int p_option) { file_dialog_option = FILE_NEW_TEXTFILE; file_dialog->clear_filters(); - const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); - for (int i = 0; i < textfile_ext.size(); i++) { - file_dialog->add_filter("*." + textfile_ext[i] + " ; " + textfile_ext[i].to_upper()); + for (const String &E : textfile_extensions) { + file_dialog->add_filter("*." + E + " ; " + E.to_upper()); } file_dialog->popup_file_dialog(); file_dialog->set_title(TTR("New Text File...")); @@ -1187,9 +1190,8 @@ void ScriptEditor::_menu_option(int p_option) { file_dialog->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); } - const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); - for (int i = 0; i < textfile_ext.size(); i++) { - file_dialog->add_filter("*." + textfile_ext[i] + " ; " + textfile_ext[i].to_upper()); + for (const String &E : textfile_extensions) { + file_dialog->add_filter("*." + E + " ; " + E.to_upper()); } file_dialog->popup_file_dialog(); @@ -1542,6 +1544,7 @@ void ScriptEditor::_notification(int p_what) { EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ScriptEditor::_editor_settings_changed)); EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &ScriptEditor::_filesystem_changed)); + _editor_settings_changed(); [[fallthrough]]; } case NOTIFICATION_TRANSLATION_CHANGED: @@ -2117,7 +2120,7 @@ void ScriptEditor::_update_script_connections() { } } -Ref<TextFile> ScriptEditor::_load_text_file(const String &p_path, Error *r_error) { +Ref<TextFile> ScriptEditor::_load_text_file(const String &p_path, Error *r_error) const { if (r_error) { *r_error = ERR_FILE_CANT_OPEN; } @@ -2601,6 +2604,12 @@ void ScriptEditor::_save_layout() { } void ScriptEditor::_editor_settings_changed() { + textfile_extensions.clear(); + const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); + for (const String &E : textfile_ext) { + textfile_extensions.insert(E); + } + trim_trailing_whitespace_on_save = EditorSettings::get_singleton()->get("text_editor/behavior/files/trim_trailing_whitespace_on_save"); convert_indent_on_save = EditorSettings::get_singleton()->get("text_editor/behavior/files/convert_indent_on_save"); use_space_indentation = EditorSettings::get_singleton()->get("text_editor/behavior/indent/type"); @@ -2807,12 +2816,22 @@ bool ScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data if (file == "" || !FileAccess::exists(file)) { continue; } - Ref<Script> scr = ResourceLoader::load(file); - if (scr.is_valid()) { - return true; + if (ResourceLoader::exists(file, "Script")) { + Ref<Script> scr = ResourceLoader::load(file); + if (scr.is_valid()) { + return true; + } + } + + if (textfile_extensions.has(file.get_extension())) { + Error err; + Ref<TextFile> text_file = _load_text_file(file, &err); + if (text_file.is_valid() && err == OK) { + return true; + } } } - return true; + return false; } return false; @@ -2877,9 +2896,13 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co if (file == "" || !FileAccess::exists(file)) { continue; } - Ref<Script> scr = ResourceLoader::load(file); - if (scr.is_valid()) { - edit(scr); + + if (!ResourceLoader::exists(file, "Script") && !textfile_extensions.has(file.get_extension())) { + continue; + } + + RES res = open_file(file); + if (res.is_valid()) { if (tab_container->get_child_count() > num_tabs_before) { tab_container->move_child(tab_container->get_child(tab_container->get_child_count() - 1), new_index); num_tabs_before = tab_container->get_child_count(); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index ce26699280..8caebc1c8c 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -453,7 +453,8 @@ class ScriptEditor : public PanelContainer { Ref<Script> _get_current_script(); Array _get_open_scripts() const; - Ref<TextFile> _load_text_file(const String &p_path, Error *r_error); + Set<String> textfile_extensions; + Ref<TextFile> _load_text_file(const String &p_path, Error *r_error) const; Error _save_text_file(Ref<TextFile> p_text_file, const String &p_path); void _on_find_in_files_requested(String text); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 1c251075de..2c02389db2 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1341,8 +1341,6 @@ void ScriptTextEditor::_bind_methods() { ClassDB::bind_method("_get_drag_data_fw", &ScriptTextEditor::get_drag_data_fw); ClassDB::bind_method("_can_drop_data_fw", &ScriptTextEditor::can_drop_data_fw); ClassDB::bind_method("_drop_data_fw", &ScriptTextEditor::drop_data_fw); - - ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &ScriptTextEditor::add_syntax_highlighter); } Control *ScriptTextEditor::get_edit_menu() { diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 531ffc6a73..708eaf2c46 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -49,296 +49,128 @@ void BoneTransformEditor::create_editors() { section = memnew(EditorInspectorSection); section->setup("trf_properties", label, this, section_color, true); + section->unfold(); add_child(section); - enabled_checkbox = memnew(CheckBox(TTR("Pose Enabled"))); - enabled_checkbox->set_flat(true); - enabled_checkbox->set_visible(toggle_enabled); + enabled_checkbox = memnew(EditorPropertyCheck()); + enabled_checkbox->set_label("Pose Enabled"); + enabled_checkbox->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); section->get_vbox()->add_child(enabled_checkbox); - key_button = memnew(Button); - key_button->set_text(TTR("Key Transform")); - key_button->set_visible(keyable); - key_button->set_icon(get_theme_icon(SNAME("Key"), SNAME("EditorIcons"))); - key_button->set_flat(true); - section->get_vbox()->add_child(key_button); - - // Translation property. - translation_property = memnew(EditorPropertyVector3()); - translation_property->setup(-10000, 10000, 0.001f, true); - translation_property->set_label("Translation"); - translation_property->set_use_folding(true); - translation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); - section->get_vbox()->add_child(translation_property); + // Position property. + position_property = memnew(EditorPropertyVector3()); + position_property->setup(-10000, 10000, 0.001f, true); + position_property->set_label("Position"); + position_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); + section->get_vbox()->add_child(position_property); // Rotation property. - rotation_property = memnew(EditorPropertyVector3()); + rotation_property = memnew(EditorPropertyQuaternion()); rotation_property->setup(-10000, 10000, 0.001f, true); - rotation_property->set_label("Rotation Degrees"); - rotation_property->set_use_folding(true); - rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); + rotation_property->set_label("Rotation"); + rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); section->get_vbox()->add_child(rotation_property); // Scale property. scale_property = memnew(EditorPropertyVector3()); scale_property->setup(-10000, 10000, 0.001f, true); scale_property->set_label("Scale"); - scale_property->set_use_folding(true); - scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); + scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); section->get_vbox()->add_child(scale_property); // Transform/Matrix section. - transform_section = memnew(EditorInspectorSection); - transform_section->setup("trf_properties_transform", "Matrix", this, section_color, true); - section->get_vbox()->add_child(transform_section); + rest_section = memnew(EditorInspectorSection); + rest_section->setup("trf_properties_transform", "Rest", this, section_color, true); + section->get_vbox()->add_child(rest_section); // Transform/Matrix property. - transform_property = memnew(EditorPropertyTransform3D()); - transform_property->setup(-10000, 10000, 0.001f, true); - transform_property->set_label("Transform"); - transform_property->set_use_folding(true); - transform_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_transform)); - transform_section->get_vbox()->add_child(transform_property); + rest_matrix = memnew(EditorPropertyTransform3D()); + rest_matrix->setup(-10000, 10000, 0.001f, true); + rest_matrix->set_label("Transform"); + rest_section->get_vbox()->add_child(rest_matrix); } void BoneTransformEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { create_editors(); - key_button->connect("pressed", callable_mp(this, &BoneTransformEditor::_key_button_pressed)); - enabled_checkbox->connect("pressed", callable_mp(this, &BoneTransformEditor::_checkbox_pressed)); - [[fallthrough]]; - } - case NOTIFICATION_SORT_CHILDREN: { - const Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Tree")); - int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Tree")); - - Point2 buffer; - buffer.x += get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); - buffer.y += font->get_height(font_size); - buffer.y += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - - const float vector_height = translation_property->get_size().y; - const float transform_height = transform_property->get_size().y; - const float button_height = key_button->get_size().y; - - const float width = get_size().x - get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); - Vector<Rect2> input_rects; - if (keyable && section->get_vbox()->is_visible()) { - input_rects.push_back(Rect2(key_button->get_position() + buffer, Size2(width, button_height))); - } else { - input_rects.push_back(Rect2(0, 0, 0, 0)); - } - - if (section->get_vbox()->is_visible()) { - input_rects.push_back(Rect2(translation_property->get_position() + buffer, Size2(width, vector_height))); - input_rects.push_back(Rect2(rotation_property->get_position() + buffer, Size2(width, vector_height))); - input_rects.push_back(Rect2(scale_property->get_position() + buffer, Size2(width, vector_height))); - input_rects.push_back(Rect2(transform_property->get_position() + buffer, Size2(width, transform_height))); - } else { - const int32_t start = input_rects.size(); - const int32_t empty_input_rect_elements = 4; - const int32_t end = start + empty_input_rect_elements; - for (int i = start; i < end; ++i) { - input_rects.push_back(Rect2(0, 0, 0, 0)); - } - } - - for (int32_t i = 0; i < input_rects.size(); i++) { - background_rects[i] = input_rects[i]; - } - - update(); - break; - } - case NOTIFICATION_DRAW: { - const Color dark_color = get_theme_color(SNAME("dark_color_2"), SNAME("Editor")); - - for (int i = 0; i < 5; ++i) { - draw_rect(background_rects[i], dark_color); - } - break; } } } -void BoneTransformEditor::_value_changed(const double p_value) { +void BoneTransformEditor::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { if (updating) { return; } - - Transform3D tform = compute_transform_from_vector3s(); - _change_transform(tform); -} - -void BoneTransformEditor::_value_changed_vector3(const String p_property_name, const Vector3 p_vector, const StringName p_edited_property_name, const bool p_boolean) { - if (updating) { - return; - } - Transform3D tform = compute_transform_from_vector3s(); - _change_transform(tform); -} - -Transform3D BoneTransformEditor::compute_transform_from_vector3s() const { - // Convert rotation from degrees to radians. - Vector3 prop_rotation = rotation_property->get_vector(); - prop_rotation.x = Math::deg2rad(prop_rotation.x); - prop_rotation.y = Math::deg2rad(prop_rotation.y); - prop_rotation.z = Math::deg2rad(prop_rotation.z); - - return Transform3D( - Basis(prop_rotation, scale_property->get_vector()), - translation_property->get_vector()); -} - -void BoneTransformEditor::_value_changed_transform(const String p_property_name, const Transform3D p_transform, const StringName p_edited_property_name, const bool p_boolean) { - if (updating) { - return; - } - _change_transform(p_transform); -} - -void BoneTransformEditor::_change_transform(Transform3D p_new_transform) { - if (property.get_slicec('/', 0) == "bones" && property.get_slicec('/', 2) == "custom_pose") { - undo_redo->create_action(TTR("Set Custom Bone Pose Transform"), UndoRedo::MERGE_ENDS); - undo_redo->add_undo_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), skeleton->get_bone_custom_pose(property.get_slicec('/', 1).to_int())); - undo_redo->add_do_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), p_new_transform); - undo_redo->commit_action(); - } else if (property.get_slicec('/', 0) == "bones") { + if (skeleton) { undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); - undo_redo->add_undo_property(skeleton, property, skeleton->get(property)); - undo_redo->add_do_property(skeleton, property, p_new_transform); + undo_redo->add_undo_property(skeleton, p_property, skeleton->get(p_property)); + undo_redo->add_do_property(skeleton, p_property, p_value); undo_redo->commit_action(); } } -void BoneTransformEditor::update_enabled_checkbox() { - if (enabled_checkbox) { - const String path = "bones/" + property.get_slicec('/', 1) + "/enabled"; - const bool is_enabled = skeleton->get(path); - enabled_checkbox->set_pressed(is_enabled); - } -} - -void BoneTransformEditor::_update_properties() { - if (updating) { - return; - } - - if (!skeleton) { - return; - } - - updating = true; - - Transform3D tform = skeleton->get(property); - _update_transform_properties(tform); -} - -void BoneTransformEditor::_update_custom_pose_properties() { - if (updating) { - return; - } - - if (!skeleton) { - return; - } - - updating = true; - - Transform3D tform = skeleton->get_bone_custom_pose(property.to_int()); - _update_transform_properties(tform); -} - -void BoneTransformEditor::_update_transform_properties(Transform3D tform) { - Basis rotation_basis = tform.get_basis(); - Vector3 rotation_radians = rotation_basis.get_rotation_euler(); - Vector3 rotation_degrees = Vector3(Math::rad2deg(rotation_radians.x), Math::rad2deg(rotation_radians.y), Math::rad2deg(rotation_radians.z)); - Vector3 translation = tform.get_origin(); - Vector3 scale = tform.basis.get_scale(); - - translation_property->update_using_vector(translation); - rotation_property->update_using_vector(rotation_degrees); - scale_property->update_using_vector(scale); - transform_property->update_using_transform(tform); - - update_enabled_checkbox(); - updating = false; -} - BoneTransformEditor::BoneTransformEditor(Skeleton3D *p_skeleton) : skeleton(p_skeleton) { undo_redo = EditorNode::get_undo_redo(); } void BoneTransformEditor::set_target(const String &p_prop) { - property = p_prop; -} - -void BoneTransformEditor::set_keyable(const bool p_keyable) { - keyable = p_keyable; -} + enabled_checkbox->set_object_and_property(skeleton, p_prop + "enabled"); + enabled_checkbox->update_property(); -void BoneTransformEditor::_update_key_button(const bool p_keyable) { - bool is_keyable = keyable && p_keyable; - if (key_button) { - key_button->set_visible(is_keyable); - } -} + position_property->set_object_and_property(skeleton, p_prop + "position"); + position_property->update_property(); -void BoneTransformEditor::set_properties_read_only(const bool p_readonly) { - enabled_checkbox->set_disabled(p_readonly); - enabled_checkbox->update(); -} + rotation_property->set_object_and_property(skeleton, p_prop + "rotation"); + rotation_property->update_property(); -void BoneTransformEditor::set_transform_read_only(const bool p_readonly) { - translation_property->set_read_only(p_readonly); - rotation_property->set_read_only(p_readonly); - scale_property->set_read_only(p_readonly); - transform_property->set_read_only(p_readonly); - translation_property->update(); - rotation_property->update(); - scale_property->update(); - transform_property->update(); - _update_key_button(!p_readonly); -} + scale_property->set_object_and_property(skeleton, p_prop + "scale"); + scale_property->update_property(); -void BoneTransformEditor::set_toggle_enabled(const bool p_enabled) { - toggle_enabled = p_enabled; - if (enabled_checkbox) { - enabled_checkbox->set_visible(p_enabled); - } + rest_matrix->set_object_and_property(skeleton, p_prop + "rest"); + rest_matrix->update_property(); } -void BoneTransformEditor::_key_button_pressed() { - if (!skeleton) { - return; - } - - const BoneId bone_id = property.get_slicec('/', 1).to_int(); - const String name = skeleton->get_bone_name(bone_id); - - if (name.is_empty()) { - return; - } - - Transform3D tform = compute_transform_from_vector3s(); - AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_transform_key(skeleton, name, tform); -} - -void BoneTransformEditor::_checkbox_pressed() { +void BoneTransformEditor::_update_properties() { if (!skeleton) { return; } - - const BoneId bone_id = property.get_slicec('/', 1).to_int(); - if (enabled_checkbox) { - undo_redo->create_action(TTR("Set Pose Enabled")); - bool enabled = skeleton->is_bone_enabled(bone_id); - undo_redo->add_do_method(skeleton, "set_bone_enabled", bone_id, !enabled); - undo_redo->add_undo_method(skeleton, "set_bone_enabled", bone_id, enabled); - undo_redo->commit_action(); + int selected = Skeleton3DEditor::get_singleton()->get_selected_bone(); + List<PropertyInfo> props; + skeleton->get_property_list(&props); + for (const PropertyInfo &E : props) { + PackedStringArray spr = E.name.split("/"); + if (spr.size() == 3 && spr[0] == "bones") { + if (spr[1].to_int() == selected) { + if (spr[2] == "enabled") { + enabled_checkbox->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + enabled_checkbox->update_property(); + enabled_checkbox->update(); + } + if (spr[2] == "position") { + position_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + position_property->update_property(); + position_property->update(); + } + if (spr[2] == "rotation") { + rotation_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + rotation_property->update_property(); + rotation_property->update(); + } + if (spr[2] == "scale") { + scale_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + scale_property->update_property(); + scale_property->update(); + } + if (spr[2] == "rest") { + rest_matrix->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + rest_matrix->update_property(); + rest_matrix->update(); + } + } + } } } @@ -354,24 +186,6 @@ void Skeleton3DEditor::set_rest_options_enabled(const bool p_rest_options_enable rest_options->get_popup()->set_item_disabled(REST_OPTION_POSE_TO_REST, !p_rest_options_enabled); }; -void Skeleton3DEditor::_update_show_rest_only() { - _update_pose_enabled(-1); -} - -void Skeleton3DEditor::_update_pose_enabled(int p_bone) { - if (!skeleton) { - return; - } - if (pose_editor) { - pose_editor->set_properties_read_only(skeleton->is_show_rest_only()); - - if (selected_bone > 0) { - pose_editor->set_transform_read_only(skeleton->is_show_rest_only() || !(skeleton->is_bone_enabled(selected_bone))); - } - } - _update_gizmo_visible(); -} - void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) { if (!skeleton) { return; @@ -418,8 +232,13 @@ void Skeleton3DEditor::init_pose() { UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); ur->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); for (int i = 0; i < bone_len; i++) { - ur->add_do_method(skeleton, "set_bone_pose", i, Transform3D()); - ur->add_undo_method(skeleton, "set_bone_pose", i, skeleton->get_bone_pose(i)); + Transform3D rest = skeleton->get_bone_rest(i); + ur->add_do_method(skeleton, "set_bone_pose_position", i, rest.origin); + ur->add_do_method(skeleton, "set_bone_pose_rotation", i, rest.basis.get_rotation_quaternion()); + ur->add_do_method(skeleton, "set_bone_pose_scale", i, rest.basis.get_scale()); + ur->add_undo_method(skeleton, "set_bone_pose_position", i, skeleton->get_bone_pose_position(i)); + ur->add_undo_method(skeleton, "set_bone_pose_rotation", i, skeleton->get_bone_pose_rotation(i)); + ur->add_undo_method(skeleton, "set_bone_pose_scale", i, skeleton->get_bone_pose_scale(i)); } ur->commit_action(); } @@ -459,15 +278,9 @@ void Skeleton3DEditor::pose_to_rest() { // Todo: Do method with multiple bone selection. UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); - - ur->add_do_method(skeleton, "set_bone_pose", selected_bone, Transform3D()); - ur->add_undo_method(skeleton, "set_bone_pose", selected_bone, skeleton->get_bone_pose(selected_bone)); - ur->add_do_method(skeleton, "set_bone_custom_pose", selected_bone, Transform3D()); - ur->add_undo_method(skeleton, "set_bone_custom_pose", selected_bone, skeleton->get_bone_custom_pose(selected_bone)); - ur->add_do_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone) * skeleton->get_bone_custom_pose(selected_bone) * skeleton->get_bone_pose(selected_bone)); + ur->create_action(TTR("Set Bone Rest"), UndoRedo::MERGE_ENDS); + ur->add_do_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_pose(selected_bone)); ur->add_undo_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone)); - ur->commit_action(); } @@ -652,20 +465,14 @@ void Skeleton3DEditor::_joint_tree_selection_changed() { const int b_idx = path.get_slicec('/', 1).to_int(); const String bone_path = "bones/" + itos(b_idx) + "/"; - pose_editor->set_target(bone_path + "pose"); - rest_editor->set_target(bone_path + "rest"); - custom_pose_editor->set_target(bone_path + "custom_pose"); - - pose_editor->set_visible(true); - rest_editor->set_visible(true); - custom_pose_editor->set_visible(true); - + pose_editor->set_target(bone_path); selected_bone = b_idx; } } + pose_editor->set_visible(selected); set_rest_options_enabled(selected); _update_properties(); - _update_pose_enabled(); + _update_gizmo_visible(); } // May be not used with single select mode. @@ -673,16 +480,10 @@ void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos) { } void Skeleton3DEditor::_update_properties() { - if (rest_editor) { - rest_editor->_update_properties(); - } if (pose_editor) { pose_editor->_update_properties(); } - if (custom_pose_editor) { - custom_pose_editor->_update_custom_pose_properties(); - } - _update_gizmo_transform(); + Node3DEditor::get_singleton()->update_transform_gizmo(); } void Skeleton3DEditor::update_joint_tree() { @@ -809,23 +610,9 @@ void Skeleton3DEditor::create_editors() { s_con->add_child(joint_tree); pose_editor = memnew(BoneTransformEditor(skeleton)); - pose_editor->set_label(TTR("Bone Pose")); - pose_editor->set_toggle_enabled(true); - pose_editor->set_keyable(te->has_keying()); + pose_editor->set_label(TTR("Bone Transform")); pose_editor->set_visible(false); add_child(pose_editor); - - rest_editor = memnew(BoneTransformEditor(skeleton)); - rest_editor->set_label(TTR("Bone Rest")); - rest_editor->set_visible(false); - add_child(rest_editor); - rest_editor->set_transform_read_only(true); - - custom_pose_editor = memnew(BoneTransformEditor(skeleton)); - custom_pose_editor->set_label(TTR("Bone Custom Pose")); - custom_pose_editor->set_visible(false); - add_child(custom_pose_editor); - custom_pose_editor->set_transform_read_only(true); } void Skeleton3DEditor::_notification(int p_what) { @@ -844,8 +631,8 @@ void Skeleton3DEditor::_notification(int p_what) { #ifdef TOOLS_ENABLED skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties)); - skeleton->connect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_update_pose_enabled)); - skeleton->connect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_show_rest_only)); + skeleton->connect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed)); + skeleton->connect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible)); #endif break; } @@ -866,8 +653,6 @@ void Skeleton3DEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_node_removed"), &Skeleton3DEditor::_node_removed); ClassDB::bind_method(D_METHOD("_joint_tree_selection_changed"), &Skeleton3DEditor::_joint_tree_selection_changed); ClassDB::bind_method(D_METHOD("_joint_tree_rmb_select"), &Skeleton3DEditor::_joint_tree_rmb_select); - ClassDB::bind_method(D_METHOD("_update_show_rest_only"), &Skeleton3DEditor::_update_show_rest_only); - ClassDB::bind_method(D_METHOD("_update_pose_enabled"), &Skeleton3DEditor::_update_pose_enabled); ClassDB::bind_method(D_METHOD("_update_properties"), &Skeleton3DEditor::_update_properties); ClassDB::bind_method(D_METHOD("_on_click_skeleton_option"), &Skeleton3DEditor::_on_click_skeleton_option); ClassDB::bind_method(D_METHOD("_on_click_rest_option"), &Skeleton3DEditor::_on_click_rest_option); @@ -938,7 +723,9 @@ void Skeleton3DEditor::update_bone_original() { if (skeleton->get_bone_count() == 0 || selected_bone == -1) { return; } - bone_original = skeleton->get_bone_pose(selected_bone); + bone_original_position = skeleton->get_bone_pose_position(selected_bone); + bone_original_rotation = skeleton->get_bone_pose_rotation(selected_bone); + bone_original_scale = skeleton->get_bone_pose_scale(selected_bone); } void Skeleton3DEditor::_hide_handles() { @@ -1066,8 +853,8 @@ void Skeleton3DEditor::select_bone(int p_idx) { Skeleton3DEditor::~Skeleton3DEditor() { if (skeleton) { #ifdef TOOLS_ENABLED - skeleton->disconnect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_show_rest_only)); - skeleton->disconnect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_update_pose_enabled)); + skeleton->disconnect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible)); + skeleton->disconnect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed)); skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties)); skeleton->set_transform_gizmo_visible(true); @@ -1148,9 +935,9 @@ bool Skeleton3DEditorPlugin::handles(Object *p_object) const { return p_object->is_class("Skeleton3D"); } -void Skeleton3DEditor::_update_gizmo_transform() { - Node3DEditor::get_singleton()->update_transform_gizmo(); -}; +void Skeleton3DEditor::_bone_enabled_changed(const int p_bone_id) { + _update_gizmo_visible(); +} void Skeleton3DEditor::_update_gizmo_visible() { _subgizmo_selection_change(); @@ -1289,7 +1076,6 @@ void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gi if (parent_idx >= 0) { original_to_local = original_to_local * skeleton->get_bone_global_pose(parent_idx); } - original_to_local = original_to_local * skeleton->get_bone_rest(p_id) * skeleton->get_bone_custom_pose(p_id); Basis to_local = original_to_local.get_basis().inverse(); // Prepare transform. @@ -1305,7 +1091,9 @@ void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gi t.origin = orig + to_local.xform(sub); // Apply transform. - skeleton->set_bone_pose(p_id, t); + skeleton->set_bone_pose_position(p_id, t.origin); + skeleton->set_bone_pose_rotation(p_id, t.basis.get_rotation_quaternion()); + skeleton->set_bone_pose_scale(p_id, t.basis.get_scale()); } void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) { @@ -1313,12 +1101,30 @@ void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, c ERR_FAIL_COND(!skeleton); Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + Node3DEditor *ne = Node3DEditor::get_singleton(); UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - for (int i = 0; i < p_ids.size(); i++) { - ur->create_action(TTR("Set Bone Transform")); - ur->add_do_method(skeleton, "set_bone_pose", p_ids[i], skeleton->get_bone_pose(p_ids[i])); - ur->add_undo_method(skeleton, "set_bone_pose", p_ids[i], se->get_bone_original()); + ur->create_action(TTR("Set Bone Transform")); + if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || ne->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE) { + for (int i = 0; i < p_ids.size(); i++) { + ur->add_do_method(skeleton, "set_bone_pose_position", p_ids[i], skeleton->get_bone_pose_position(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose_position", p_ids[i], se->get_bone_original_position()); + } + } + if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || ne->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) { + for (int i = 0; i < p_ids.size(); i++) { + ur->add_do_method(skeleton, "set_bone_pose_rotation", p_ids[i], skeleton->get_bone_pose_rotation(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose_rotation", p_ids[i], se->get_bone_original_rotation()); + } + } + if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE) { + for (int i = 0; i < p_ids.size(); i++) { + // If the axis is swapped by scaling, the rotation can be changed. + ur->add_do_method(skeleton, "set_bone_pose_rotation", p_ids[i], skeleton->get_bone_pose_rotation(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose_rotation", p_ids[i], se->get_bone_original_rotation()); + ur->add_do_method(skeleton, "set_bone_pose_scale", p_ids[i], skeleton->get_bone_pose_scale(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose_scale", p_ids[i], se->get_bone_original_scale()); + } } ur->commit_action(); } @@ -1516,5 +1322,5 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } Ref<ArrayMesh> m = surface_tool->commit(); - p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(Ref<Skin>())); + p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(skeleton->create_skin_from_rest_transforms())); } diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h index e2a1d9a628..3b4dd362fb 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -52,21 +52,22 @@ class BoneTransformEditor : public VBoxContainer { EditorInspectorSection *section = nullptr; - EditorPropertyVector3 *translation_property = nullptr; - EditorPropertyVector3 *rotation_property = nullptr; + EditorPropertyCheck *enabled_checkbox = nullptr; + EditorPropertyVector3 *position_property = nullptr; + EditorPropertyQuaternion *rotation_property = nullptr; EditorPropertyVector3 *scale_property = nullptr; - EditorInspectorSection *transform_section = nullptr; - EditorPropertyTransform3D *transform_property = nullptr; + + EditorInspectorSection *rest_section = nullptr; + EditorPropertyTransform3D *rest_matrix = nullptr; Rect2 background_rects[5]; Skeleton3D *skeleton; - String property; + // String property; UndoRedo *undo_redo; - Button *key_button = nullptr; - CheckBox *enabled_checkbox = nullptr; + // Button *key_button = nullptr; bool keyable = false; bool toggle_enabled = false; @@ -76,20 +77,7 @@ class BoneTransformEditor : public VBoxContainer { void create_editors(); - // Called when one of the EditorSpinSliders are changed. - void _value_changed(const double p_value); - // Called when the one of the EditorPropertyVector3 are updated. - void _value_changed_vector3(const String p_property_name, const Vector3 p_vector, const StringName p_edited_property_name, const bool p_boolean); - // Called when the transform_property is updated. - void _value_changed_transform(const String p_property_name, const Transform3D p_transform, const StringName p_edited_property_name, const bool p_boolean); - // Changes the transform to the given transform and updates the UI accordingly. - void _change_transform(Transform3D p_new_transform); - // Update it is truely keyable then. - void _update_key_button(const bool p_keyable); - // Creates a Transform using the EditorPropertyVector3 properties. - Transform3D compute_transform_from_vector3s() const; - - void update_enabled_checkbox(); + void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing); protected: void _notification(int p_what); @@ -102,24 +90,6 @@ public: void set_label(const String &p_label) { label = p_label; } void _update_properties(); - void _update_custom_pose_properties(); - void _update_transform_properties(Transform3D p_transform); - - // Transform can be keyed, whether or not to show the button. - void set_keyable(const bool p_keyable); - - // When rest mode, pose and custom_pose editor are diasbled. - void set_properties_read_only(const bool p_readonly); - void set_transform_read_only(const bool p_readonly); - - // Bone can be toggled enabled or disabled, whether or not to show the checkbox. - void set_toggle_enabled(const bool p_enabled); - - // Key Transform Button pressed. - void _key_button_pressed(); - - // Bone Enabled Checkbox toggled. - void _checkbox_pressed(); }; class Skeleton3DEditor : public VBoxContainer { @@ -151,7 +121,6 @@ class Skeleton3DEditor : public VBoxContainer { Tree *joint_tree = nullptr; BoneTransformEditor *rest_editor = nullptr; BoneTransformEditor *pose_editor = nullptr; - BoneTransformEditor *custom_pose_editor = nullptr; VSeparator *separator; MenuButton *skeleton_options = nullptr; @@ -199,13 +168,12 @@ class Skeleton3DEditor : public VBoxContainer { Ref<ShaderMaterial> handle_material; Ref<Shader> handle_shader; - Transform3D bone_original; - - void _update_pose_enabled(int p_bone = -1); - void _update_show_rest_only(); + Vector3 bone_original_position; + Quaternion bone_original_rotation; + Vector3 bone_original_scale; - void _update_gizmo_transform(); void _update_gizmo_visible(); + void _bone_enabled_changed(const int p_bone_id); void _hide_handles(); @@ -239,7 +207,9 @@ public: bool is_edit_mode() const { return edit_mode; } void update_bone_original(); - Transform3D get_bone_original() { return bone_original; }; + Vector3 get_bone_original_position() const { return bone_original_position; }; + Quaternion get_bone_original_rotation() const { return bone_original_rotation; }; + Vector3 get_bone_original_scale() const { return bone_original_scale; }; Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *skeleton); ~Skeleton3DEditor(); diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 06ba8a6168..1fc7eb98e0 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -407,10 +407,6 @@ void TextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) { code_editor->convert_case(p_case); } -void TextEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &TextEditor::add_syntax_highlighter); -} - static ScriptEditorBase *create_editor(const RES &p_resource) { if (Object::cast_to<TextFile>(*p_resource)) { return memnew(TextEditor); diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h index 9308fec210..7404557f46 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -87,8 +87,6 @@ private: }; protected: - static void _bind_methods(); - void _edit_option(int p_op); void _make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position); void _text_edit_gui_input(const Ref<InputEvent> &ev); diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp index f21d5098d3..c064073b77 100644 --- a/editor/plugins/tiles/tile_atlas_view.cpp +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -97,15 +97,6 @@ Size2i TileAtlasView::_compute_base_tiles_control_size() { if (texture.is_valid()) { size = texture->get_size(); } - - // Extend the size to all existing tiles. - Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); - for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { - Vector2i tile_id = tile_set_atlas_source->get_tile_id(i); - grid_size = grid_size.max(tile_id + Vector2i(1, 1)); - } - size = size.max(grid_size * (tile_set_atlas_source->get_texture_region_size() + tile_set_atlas_source->get_separation()) + tile_set_atlas_source->get_margins()); - return size; } @@ -213,43 +204,56 @@ void TileAtlasView::_draw_base_tiles() { Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); if (texture.is_valid()) { Vector2i margins = tile_set_atlas_source->get_margins(); + Vector2i separation = tile_set_atlas_source->get_separation(); Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size(); - - // Draw the texture, square by square. Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); + + // Draw the texture where there is no tile. for (int x = 0; x < grid_size.x; x++) { for (int y = 0; y < grid_size.y; y++) { Vector2i coords = Vector2i(x, y); if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { - Rect2i rect = Rect2i(texture_region_size * coords + margins, texture_region_size); - base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + Rect2i rect = Rect2i((texture_region_size + separation) * coords + margins, texture_region_size + separation); + rect = rect.intersection(Rect2i(Vector2(), texture->get_size())); + if (rect.size.x > 0 && rect.size.y > 0) { + base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); + } } } } // Draw the texture around the grid. Rect2i rect; + // Top. rect.position = Vector2i(); rect.set_end(Vector2i(texture->get_size().x, margins.y)); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); + // Bottom - int bottom_border = margins.y + (grid_size.y * texture_region_size.y); + int bottom_border = margins.y + (grid_size.y * (texture_region_size.y + separation.y)); if (bottom_border < texture->get_size().y) { rect.position = Vector2i(0, bottom_border); rect.set_end(texture->get_size()); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); } + // Left rect.position = Vector2i(0, margins.y); - rect.set_end(Vector2i(margins.x, margins.y + (grid_size.y * texture_region_size.y))); + rect.set_end(Vector2i(margins.x, margins.y + (grid_size.y * (texture_region_size.y + separation.y)))); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); + // Right. - int right_border = margins.x + (grid_size.x * texture_region_size.x); + int right_border = margins.x + (grid_size.x * (texture_region_size.x + separation.x)); if (right_border < texture->get_size().x) { rect.position = Vector2i(right_border, margins.y); - rect.set_end(Vector2i(texture->get_size().x, margins.y + (grid_size.y * texture_region_size.y))); + rect.set_end(Vector2i(texture->get_size().x, margins.y + (grid_size.y * (texture_region_size.y + separation.y)))); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); } // Draw actual tiles, using their properties (modulation, etc...) @@ -258,12 +262,30 @@ void TileAtlasView::_draw_base_tiles() { for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(atlas_coords); frame++) { // Update the y to max value. - int animation_columns = tile_set_atlas_source->get_tile_animation_columns(atlas_coords); - Vector2i frame_coords = atlas_coords + (tile_set_atlas_source->get_tile_size_in_atlas(atlas_coords) + tile_set_atlas_source->get_tile_animation_separation(atlas_coords)) * ((animation_columns > 0) ? Vector2i(frame % animation_columns, frame / animation_columns) : Vector2i(frame, 0)); - Vector2i offset_pos = (margins + (frame_coords * texture_region_size) + tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame).size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0)); + Rect2i base_frame_rect = tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame); + Vector2i offset_pos = base_frame_rect.get_center() + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0); // Draw the tile. TileMap::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0, frame); + + // Draw, the texture in the separation areas + if (separation.x > 0) { + Rect2i right_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(base_frame_rect.size.x, 0), Vector2i(separation.x, base_frame_rect.size.y)); + right_sep_rect = right_sep_rect.intersection(Rect2i(Vector2(), texture->get_size())); + if (right_sep_rect.size.x > 0 && right_sep_rect.size.y > 0) { + base_tiles_draw->draw_texture_rect_region(texture, right_sep_rect, right_sep_rect); + base_tiles_draw->draw_rect(right_sep_rect, Color(0.0, 0.0, 0.0, 0.5)); + } + } + + if (separation.y > 0) { + Rect2i bottom_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(0, base_frame_rect.size.y), Vector2i(base_frame_rect.size.x + separation.x, separation.y)); + bottom_sep_rect = bottom_sep_rect.intersection(Rect2i(Vector2(), texture->get_size())); + if (bottom_sep_rect.size.x > 0 && bottom_sep_rect.size.y > 0) { + base_tiles_draw->draw_texture_rect_region(texture, bottom_sep_rect, bottom_sep_rect); + base_tiles_draw->draw_rect(bottom_sep_rect, Color(0.0, 0.0, 0.0, 0.5)); + } + } } } } @@ -299,30 +321,6 @@ void TileAtlasView::_draw_base_tiles_texture_grid() { } } -void TileAtlasView::_draw_base_tiles_dark() { - Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); - if (texture.is_valid()) { - Vector2i margins = tile_set_atlas_source->get_margins(); - Vector2i separation = tile_set_atlas_source->get_separation(); - Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size(); - - Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); - - // Draw each tile texture region. - for (int x = 0; x < grid_size.x; x++) { - for (int y = 0; y < grid_size.y; y++) { - Vector2i origin = margins + (Vector2i(x, y) * (texture_region_size + separation)); - Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y)); - - if (base_tile_coords == TileSetSource::INVALID_ATLAS_COORDS) { - // Draw the grid. - base_tiles_dark->draw_rect(Rect2i(origin, texture_region_size), Color(0.0, 0.0, 0.0, 0.5), true); - } - } - } - } -} - void TileAtlasView::_draw_base_tiles_shape_grid() { // Draw the shapes. Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); @@ -453,7 +451,6 @@ void TileAtlasView::set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_ base_tiles_draw->update(); base_tiles_texture_grid->update(); base_tiles_shape_grid->update(); - base_tiles_dark->update(); alternatives_draw->update(); background_left->update(); background_right->update(); @@ -544,7 +541,6 @@ void TileAtlasView::update() { base_tiles_draw->update(); base_tiles_texture_grid->update(); base_tiles_shape_grid->update(); - base_tiles_dark->update(); alternatives_draw->update(); background_left->update(); background_right->update(); @@ -660,12 +656,6 @@ TileAtlasView::TileAtlasView() { base_tiles_shape_grid->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_shape_grid)); base_tiles_drawing_root->add_child(base_tiles_shape_grid); - base_tiles_dark = memnew(Control); - base_tiles_dark->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); - base_tiles_dark->set_anchors_and_offsets_preset(Control::PRESET_WIDE); - base_tiles_dark->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_dark)); - base_tiles_drawing_root->add_child(base_tiles_dark); - // Alternative tiles. Label *alternative_tiles_label = memnew(Label); alternative_tiles_label->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); diff --git a/editor/plugins/tiles/tile_atlas_view.h b/editor/plugins/tiles/tile_atlas_view.h index 5b0df366ae..e1ca3eebee 100644 --- a/editor/plugins/tiles/tile_atlas_view.h +++ b/editor/plugins/tiles/tile_atlas_view.h @@ -93,9 +93,6 @@ private: Control *base_tiles_shape_grid; void _draw_base_tiles_shape_grid(); - Control *base_tiles_dark; - void _draw_base_tiles_dark(); - Size2i _compute_base_tiles_control_size(); // Right side. @@ -124,7 +121,6 @@ public: // Left side. void set_texture_grid_visible(bool p_visible) { base_tiles_texture_grid->set_visible(p_visible); }; - void set_dark_visible(bool p_visible) { base_tiles_dark->set_visible(p_visible); }; void set_tile_shape_grid_visible(bool p_visible) { base_tiles_shape_grid->set_visible(p_visible); }; Vector2i get_atlas_tile_coords_at_pos(const Vector2 p_pos) const; diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 1a69d19d3c..104f46f773 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -38,8 +38,16 @@ #include "editor/editor_properties.h" #include "editor/editor_scale.h" -void TileDataEditor::_call_tile_set_changed() { - _tile_set_changed(); +void TileDataEditor::_tile_set_changed_plan_update() { + _tile_set_changed_update_needed = true; + call_deferred("_tile_set_changed_deferred_update"); +} + +void TileDataEditor::_tile_set_changed_deferred_update() { + if (_tile_set_changed_update_needed) { + _tile_set_changed(); + _tile_set_changed_update_needed = false; + } } TileData *TileDataEditor::_get_tile_data(TileMapCell p_cell) { @@ -59,18 +67,20 @@ TileData *TileDataEditor::_get_tile_data(TileMapCell p_cell) { } void TileDataEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_tile_set_changed_deferred_update"), &TileDataEditor::_tile_set_changed_deferred_update); + ADD_SIGNAL(MethodInfo("needs_redraw")); } void TileDataEditor::set_tile_set(Ref<TileSet> p_tile_set) { if (tile_set.is_valid()) { - tile_set->disconnect("changed", callable_mp(this, &TileDataEditor::_call_tile_set_changed)); + tile_set->disconnect("changed", callable_mp(this, &TileDataEditor::_tile_set_changed_plan_update)); } tile_set = p_tile_set; if (tile_set.is_valid()) { - tile_set->connect("changed", callable_mp(this, &TileDataEditor::_call_tile_set_changed)); + tile_set->connect("changed", callable_mp(this, &TileDataEditor::_tile_set_changed_plan_update)); } - _call_tile_set_changed(); + _tile_set_changed_plan_update(); } bool DummyObject::_set(const StringName &p_name, const Variant &p_value) { diff --git a/editor/plugins/tiles/tile_data_editors.h b/editor/plugins/tiles/tile_data_editors.h index 99998dc779..acb4c5882d 100644 --- a/editor/plugins/tiles/tile_data_editors.h +++ b/editor/plugins/tiles/tile_data_editors.h @@ -45,7 +45,9 @@ class TileDataEditor : public VBoxContainer { GDCLASS(TileDataEditor, VBoxContainer); private: - void _call_tile_set_changed(); + bool _tile_set_changed_update_needed = false; + void _tile_set_changed_plan_update(); + void _tile_set_changed_deferred_update(); protected: Ref<TileSet> tile_set; diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index cc5ff90541..5ca39b82b2 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -456,7 +456,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p case DRAG_TYPE_PAINT: { Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_last_mouse_pos, mpos); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!erase_button->is_pressed() && E.value.source_id == TileSet::INVALID_SOURCE) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } Vector2i coords = E.key; @@ -465,6 +465,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } + _fix_invalid_tiles_in_tile_map_selection(); } break; case DRAG_TYPE_BUCKET: { Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos)); @@ -472,7 +473,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p if (!drag_modified.has(line[i])) { Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_continuous_checkbox->is_pressed()); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!erase_button->is_pressed() && E.value.source_id == TileSet::INVALID_SOURCE) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } Vector2i coords = E.key; @@ -483,6 +484,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } } } + _fix_invalid_tiles_in_tile_map_selection(); } break; default: break; @@ -499,8 +501,12 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); Vector2 mpos = xform.affine_inverse().xform(mb->get_position()); - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT || mb->get_button_index() == MOUSE_BUTTON_RIGHT) { if (mb->is_pressed()) { + if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + rmb_erasing = true; + } + // Pressed if (drag_type == DRAG_TYPE_CLIPBOARD_PASTE) { // Do nothing. @@ -508,6 +514,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p drag_start_mouse_pos = mpos; if (tile_map_selection.has(tile_map->world_to_map(drag_start_mouse_pos)) && !mb->is_shift_pressed()) { // Move the selection + _update_selection_pattern_from_tilemap_selection(); // Make sure the pattern is up to date before moving. drag_type = DRAG_TYPE_MOVE; drag_modified.clear(); for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { @@ -521,18 +528,18 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } } else { // Check if we are picking a tile. - if (picker_button->is_pressed()) { + if (picker_button->is_pressed() || (Input::get_singleton()->is_key_pressed(KEY_CTRL) && !Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { drag_type = DRAG_TYPE_PICK; drag_start_mouse_pos = mpos; } else { // Paint otherwise. - if (tool_buttons_group->get_pressed_button() == paint_tool_button) { + if (tool_buttons_group->get_pressed_button() == paint_tool_button && !Input::get_singleton()->is_key_pressed(KEY_CTRL) && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { drag_type = DRAG_TYPE_PAINT; drag_start_mouse_pos = mpos; drag_modified.clear(); Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, mpos, mpos); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!erase_button->is_pressed() && E.value.source_id == TileSet::INVALID_SOURCE) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } Vector2i coords = E.key; @@ -541,11 +548,12 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } - } else if (tool_buttons_group->get_pressed_button() == line_tool_button) { + _fix_invalid_tiles_in_tile_map_selection(); + } else if (tool_buttons_group->get_pressed_button() == line_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(KEY_SHIFT) && !Input::get_singleton()->is_key_pressed(KEY_CTRL))) { drag_type = DRAG_TYPE_LINE; drag_start_mouse_pos = mpos; drag_modified.clear(); - } else if (tool_buttons_group->get_pressed_button() == rect_tool_button) { + } else if (tool_buttons_group->get_pressed_button() == rect_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(KEY_SHIFT) && Input::get_singleton()->is_key_pressed(KEY_CTRL))) { drag_type = DRAG_TYPE_RECT; drag_start_mouse_pos = mpos; drag_modified.clear(); @@ -558,7 +566,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p if (!drag_modified.has(line[i])) { Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_continuous_checkbox->is_pressed()); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!erase_button->is_pressed() && E.value.source_id == TileSet::INVALID_SOURCE) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } Vector2i coords = E.key; @@ -569,6 +577,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } } } + _fix_invalid_tiles_in_tile_map_selection(); } } } @@ -576,6 +585,9 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } else { // Released _stop_dragging(); + if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + rmb_erasing = false; + } } CanvasItemEditor::get_singleton()->update_viewport(); @@ -682,13 +694,13 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over Vector2i coords = tile_map->map_pattern(tile_map->world_to_map(drag_last_mouse_pos - mouse_offset), clipboard_used_cells[i], tile_map_clipboard); preview[coords] = TileMapCell(tile_map_clipboard->get_cell_source_id(clipboard_used_cells[i]), tile_map_clipboard->get_cell_atlas_coords(clipboard_used_cells[i]), tile_map_clipboard->get_cell_alternative_tile(clipboard_used_cells[i])); } - } else if (!picker_button->is_pressed()) { + } else if (!picker_button->is_pressed() && !(drag_type == DRAG_TYPE_NONE && Input::get_singleton()->is_key_pressed(KEY_CTRL) && !Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { bool expand_grid = false; if (tool_buttons_group->get_pressed_button() == paint_tool_button && drag_type == DRAG_TYPE_NONE) { // Preview for a single pattern. preview = _draw_line(drag_last_mouse_pos, drag_last_mouse_pos, drag_last_mouse_pos); expand_grid = true; - } else if (tool_buttons_group->get_pressed_button() == line_tool_button) { + } else if (tool_buttons_group->get_pressed_button() == line_tool_button || drag_type == DRAG_TYPE_LINE) { if (drag_type == DRAG_TYPE_NONE) { // Preview for a single pattern. preview = _draw_line(drag_last_mouse_pos, drag_last_mouse_pos, drag_last_mouse_pos); @@ -698,12 +710,12 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over preview = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, drag_last_mouse_pos); expand_grid = true; } - } else if (tool_buttons_group->get_pressed_button() == rect_tool_button && drag_type == DRAG_TYPE_RECT) { - // Preview for a line pattern. + } else if (drag_type == DRAG_TYPE_RECT) { + // Preview for a rect pattern. preview = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos)); expand_grid = true; } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button && drag_type == DRAG_TYPE_NONE) { - // Preview for a line pattern. + // Preview for a fill pattern. preview = _draw_bucket_fill(tile_map->world_to_map(drag_last_mouse_pos), bucket_continuous_checkbox->is_pressed()); } @@ -752,7 +764,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over Transform2D tile_xform; tile_xform.set_origin(tile_map->map_to_world(E.key)); tile_xform.set_scale(tile_set->get_tile_size()); - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (!_is_erasing() && random_tile_checkbox->is_pressed()) { tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0, 0.5), true); } else { if (tile_set->has_source(E.value.source_id)) { @@ -877,12 +889,12 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_ // Get or create the pattern. TileMapPattern erase_pattern; erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; + TileMapPattern *pattern = _is_erasing() ? &erase_pattern : selection_pattern; Map<Vector2i, TileMapCell> output; if (!pattern->is_empty()) { // Paint the tiles on the tile map. - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (!_is_erasing() && random_tile_checkbox->is_pressed()) { // Paint a random tile. Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(p_from_mouse_pos), tile_map->world_to_map(p_to_mouse_pos)); for (int i = 0; i < line.size(); i++) { @@ -929,7 +941,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start // Get or create the pattern. TileMapPattern erase_pattern; erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; + TileMapPattern *pattern = _is_erasing() ? &erase_pattern : selection_pattern; Map<Vector2i, TileMapCell> err_output; ERR_FAIL_COND_V(pattern->is_empty(), err_output); @@ -940,7 +952,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start Map<Vector2i, TileMapCell> output; if (!pattern->is_empty()) { - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (!_is_erasing() && random_tile_checkbox->is_pressed()) { // Paint a random tile. for (int x = 0; x < rect.size.x; x++) { for (int y = 0; y < rect.size.y; y++) { @@ -988,7 +1000,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i // Get or create the pattern. TileMapPattern erase_pattern; erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; + TileMapPattern *pattern = _is_erasing() ? &erase_pattern : selection_pattern; if (!pattern->is_empty()) { TileMapCell source = tile_map->get_cell(tile_map_layer, p_coords); @@ -1012,7 +1024,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i source.get_atlas_coords() == tile_map->get_cell_atlas_coords(tile_map_layer, coords) && source.alternative_tile == tile_map->get_cell_alternative_tile(tile_map_layer, coords) && (source.source_id != TileSet::INVALID_SOURCE || boundaries.has_point(coords))) { - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (!_is_erasing() && random_tile_checkbox->is_pressed()) { // Paint a random tile. output.insert(coords, _pick_random_tile(pattern)); } else { @@ -1058,7 +1070,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i source.get_atlas_coords() == tile_map->get_cell_atlas_coords(tile_map_layer, coords) && source.alternative_tile == tile_map->get_cell_alternative_tile(tile_map_layer, coords) && (source.source_id != TileSet::INVALID_SOURCE || boundaries.has_point(coords))) { - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (!_is_erasing() && random_tile_checkbox->is_pressed()) { // Paint a random tile. output.insert(coords, _pick_random_tile(pattern)); } else { @@ -1215,7 +1227,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, mpos); undo_redo->create_action(TTR("Paint tiles")); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!erase_button->is_pressed() && E.value.source_id == TileSet::INVALID_SOURCE) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); @@ -1227,7 +1239,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { Map<Vector2i, TileMapCell> to_draw = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos)); undo_redo->create_action(TTR("Paint tiles")); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!erase_button->is_pressed() && E.value.source_id == TileSet::INVALID_SOURCE) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); @@ -1323,6 +1335,29 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { } } +void TileMapEditorTilesPlugin::_fix_invalid_tiles_in_tile_map_selection() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Set<Vector2i> to_remove; + for (Vector2i selected : tile_map_selection) { + TileMapCell cell = tile_map->get_cell(tile_map_layer, selected); + if (cell.source_id == TileSet::INVALID_SOURCE && cell.get_atlas_coords() == TileSetSource::INVALID_ATLAS_COORDS && cell.alternative_tile == TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) { + to_remove.insert(selected); + } + } + + for (Vector2i cell : to_remove) { + tile_map_selection.erase(cell); + } +} + +bool TileMapEditorTilesPlugin::_is_erasing() const { + return erase_button->is_pressed() || rmb_erasing; +} + void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection() { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { @@ -1423,6 +1458,8 @@ void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern( } } _update_bottom_panel(); + tile_atlas_control->update(); + alternative_tiles_control->update(); } void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { @@ -1804,6 +1841,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { paint_tool_button->set_toggle_mode(true); paint_tool_button->set_button_group(tool_buttons_group); paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", KEY_D)); + paint_tool_button->set_tooltip(TTR("Shift: Draw line.") + "\n" + TTR("Shift+Ctrl: Draw rectangle.")); paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(paint_tool_button); @@ -1844,6 +1882,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { picker_button->set_flat(true); picker_button->set_toggle_mode(true); picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P)); + picker_button->set_tooltip(TTR("Alternatively hold Ctrl with other tools to pick tile.")); picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(picker_button); @@ -1852,6 +1891,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { erase_button->set_flat(true); erase_button->set_toggle_mode(true); erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", KEY_E)); + erase_button->set_tooltip(TTR("Alternatively use RMB to erase tiles.")); erase_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(erase_button); diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h index 6126db59e9..5fbd9cada8 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -104,12 +104,14 @@ private: Vector2 drag_start_mouse_pos; Vector2 drag_last_mouse_pos; Map<Vector2i, TileMapCell> drag_modified; + bool rmb_erasing = false; TileMapCell _pick_random_tile(const TileMapPattern *p_pattern); Map<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos); Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell); Map<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous); void _stop_dragging(); + bool _is_erasing() const; ///// Selection system. ///// Set<Vector2i> tile_map_selection; @@ -124,6 +126,7 @@ private: void _update_selection_pattern_from_tileset_selection(); void _update_tileset_selection_from_selection_pattern(); void _update_fix_selected_and_hovered(); + void _fix_invalid_tiles_in_tile_map_selection(); ///// Bottom panel. ////. Label *missing_source_label; diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 3fbd315aec..c43a854d9a 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -520,9 +520,6 @@ void TileSetAtlasSourceEditor::_update_tile_id_label() { void TileSetAtlasSourceEditor::_update_source_inspector() { // Update the proxy object. atlas_source_proxy_object->edit(tile_set, tile_set_atlas_source, tile_set_atlas_source_id); - - // Update the "clear outside texture" button. - tool_advanced_menu_buttom->get_popup()->set_item_disabled(0, !tile_set_atlas_source->has_tiles_outside_texture()); } void TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles() { @@ -763,7 +760,11 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { // Update visibility. bool is_visible = tools_button_group->get_pressed_button() == tool_paint_button; tile_data_editor_dropdown_button->set_visible(is_visible); - tile_data_editor_dropdown_button->set_text(TTR("Select a property editor")); + if (tile_data_editors_tree->get_selected()) { + tile_data_editor_dropdown_button->set_text(tile_data_editors_tree->get_selected()->get_text(0)); + } else { + tile_data_editor_dropdown_button->set_text(TTR("Select a property editor")); + } tile_data_editors_label->set_visible(is_visible); } @@ -959,7 +960,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven current_tile_data_editor->forward_painting_atlas_gui_input(tile_atlas_view, tile_set_atlas_source, p_event); } // Update only what's needed. - tile_set_atlas_source_changed_needs_update = false; + tile_set_changed_needs_update = false; tile_atlas_control->update(); tile_atlas_control_unscaled->update(); @@ -1061,7 +1062,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven drag_current_tile = coords; // Update only what's needed. - tile_set_atlas_source_changed_needs_update = false; + tile_set_changed_needs_update = false; _update_tile_inspector(); _update_atlas_view(); _update_tile_id_label(); @@ -1101,7 +1102,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven drag_current_tile = new_rect.position; // Update only what's needed. - tile_set_atlas_source_changed_needs_update = false; + tile_set_changed_needs_update = false; _update_tile_inspector(); _update_atlas_view(); _update_tile_id_label(); @@ -1600,9 +1601,6 @@ void TileSetAtlasSourceEditor::_menu_option(int p_option) { undo_redo->commit_action(); _update_tile_id_label(); } break; - case ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE: { - tile_set_atlas_source->clear_tiles_outside_texture(); - } break; case ADVANCED_AUTO_CREATE_TILES: { _auto_create_tiles(); } break; @@ -2012,12 +2010,12 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() { } } -void TileSetAtlasSourceEditor::_tile_set_atlas_source_changed() { - tile_set_atlas_source_changed_needs_update = true; +void TileSetAtlasSourceEditor::_tile_set_changed() { + tile_set_changed_needs_update = true; } void TileSetAtlasSourceEditor::_tile_proxy_object_changed(String p_what) { - tile_set_atlas_source_changed_needs_update = false; // Avoid updating too many things. + tile_set_changed_needs_update = false; // Avoid updating too many things. _update_atlas_view(); } @@ -2035,30 +2033,66 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo #define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); - AtlasTileProxyObject *tile_data = Object::cast_to<AtlasTileProxyObject>(p_edited); - if (tile_data) { + undo_redo->start_force_keep_in_merge_ends(); + AtlasTileProxyObject *tile_data_proxy = Object::cast_to<AtlasTileProxyObject>(p_edited); + if (tile_data_proxy) { Vector<String> components = String(p_property).split("/", true, 2); if (components.size() == 2 && components[1] == "polygons_count") { int layer_index = components[0].trim_prefix("physics_layer_").to_int(); int new_polygons_count = p_new_value; - int old_polygons_count = tile_data->get(vformat("physics_layer_%d/polygons_count", layer_index)); + int old_polygons_count = tile_data_proxy->get(vformat("physics_layer_%d/polygons_count", layer_index)); if (new_polygons_count < old_polygons_count) { for (int i = new_polygons_count - 1; i < old_polygons_count; i++) { - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", layer_index, i)); - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, i)); - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, i)); + ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/points", layer_index, i)); + ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, i)); + ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, i)); } } } else if (p_property == "terrain_set") { - int current_terrain_set = tile_data->get("terrain_set"); + int current_terrain_set = tile_data_proxy->get("terrain_set"); for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); if (tile_set->is_valid_peering_bit_terrain(current_terrain_set, bit)) { - ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i])); + ADD_UNDO(tile_data_proxy, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i])); } } } } + + TileSetAtlasSourceProxyObject *atlas_source_proxy = Object::cast_to<TileSetAtlasSourceProxyObject>(p_edited); + if (atlas_source_proxy) { + TileSetAtlasSource *atlas_source = atlas_source_proxy->get_edited(); + ERR_FAIL_COND(!atlas_source); + + PackedVector2Array arr; + if (p_property == "texture") { + arr = atlas_source->get_tiles_to_be_removed_on_change(p_new_value, atlas_source->get_margins(), atlas_source->get_separation(), atlas_source->get_texture_region_size()); + } else if (p_property == "margins") { + arr = atlas_source->get_tiles_to_be_removed_on_change(atlas_source->get_texture(), p_new_value, atlas_source->get_separation(), atlas_source->get_texture_region_size()); + } else if (p_property == "separation") { + arr = atlas_source->get_tiles_to_be_removed_on_change(atlas_source->get_texture(), atlas_source->get_margins(), p_new_value, atlas_source->get_texture_region_size()); + } else if (p_property == "texture_region_size") { + arr = atlas_source->get_tiles_to_be_removed_on_change(atlas_source->get_texture(), atlas_source->get_margins(), atlas_source->get_separation(), p_new_value); + } + + if (!arr.is_empty()) { + // Get all properties assigned to a tile. + List<PropertyInfo> properties; + atlas_source->get_property_list(&properties); + + for (int i = 0; i < arr.size(); i++) { + Vector2i coords = arr[i]; + String prefix = vformat("%d:%d/", coords.x, coords.y); + for (PropertyInfo pi : properties) { + if (pi.name.begins_with(prefix)) { + ADD_UNDO(atlas_source, pi.name); + } + } + } + } + } + undo_redo->end_force_keep_in_merge_ends(); + #undef ADD_UNDO } @@ -2073,8 +2107,8 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource } // Remove listener for old objects. - if (tile_set_atlas_source) { - tile_set_atlas_source->disconnect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_atlas_source_changed)); + if (tile_set.is_valid()) { + tile_set->disconnect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); } // Clear the selection. @@ -2086,8 +2120,8 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource tile_set_atlas_source_id = p_source_id; // Add the listener again. - if (tile_set_atlas_source) { - tile_set_atlas_source->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_atlas_source_changed)); + if (tile_set.is_valid()) { + tile_set->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); } // Update everything. @@ -2228,7 +2262,7 @@ void TileSetAtlasSourceEditor::_notification(int p_what) { resize_handle_disabled = get_theme_icon(SNAME("EditorHandleDisabled"), SNAME("EditorIcons")); break; case NOTIFICATION_INTERNAL_PROCESS: - if (tile_set_atlas_source_changed_needs_update) { + if (tile_set_changed_needs_update) { // Update everything. _update_source_inspector(); @@ -2241,7 +2275,7 @@ void TileSetAtlasSourceEditor::_notification(int p_what) { _update_tile_data_editors(); _update_current_tile_data_editor(); - tile_set_atlas_source_changed_needs_update = false; + tile_set_changed_needs_update = false; } break; default: @@ -2406,8 +2440,6 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tool_advanced_menu_buttom = memnew(MenuButton); tool_advanced_menu_buttom->set_flat(true); - tool_advanced_menu_buttom->get_popup()->add_item(TTR("Cleanup Tiles Outside Texture"), ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE); - tool_advanced_menu_buttom->get_popup()->set_item_disabled(0, true); tool_advanced_menu_buttom->get_popup()->add_item(TTR("Create Tiles in Non-Transparent Texture Regions"), ADVANCED_AUTO_CREATE_TILES); tool_advanced_menu_buttom->get_popup()->add_item(TTR("Remove Tiles in Fully Transparent Texture Regions"), ADVANCED_AUTO_REMOVE_TILES); tool_advanced_menu_buttom->get_popup()->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option)); @@ -2481,6 +2513,8 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tile_atlas_view_missing_source_label->set_v_size_flags(SIZE_EXPAND_FILL); tile_atlas_view_missing_source_label->hide(); right_panel->add_child(tile_atlas_view_missing_source_label); + + EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetAtlasSourceEditor::_undo_redo_inspector_callback)); } TileSetAtlasSourceEditor::~TileSetAtlasSourceEditor() { diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h index 6448b55feb..ea6f2847ae 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.h +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -78,6 +78,7 @@ private: int get_id(); void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id); + TileSetAtlasSource *get_edited() { return tile_set_atlas_source; }; }; // -- Proxy object for a tile, needed by the inspector -- @@ -112,7 +113,7 @@ private: UndoRedo *undo_redo = EditorNode::get_undo_redo(); - bool tile_set_atlas_source_changed_needs_update = false; + bool tile_set_changed_needs_update = false; // -- Properties painting -- VBoxContainer *tile_data_painting_editor_container; @@ -189,7 +190,6 @@ private: TILE_CREATE_ALTERNATIVE, TILE_DELETE, - ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE, ADVANCED_AUTO_CREATE_TILES, ADVANCED_AUTO_REMOVE_TILES, }; @@ -263,7 +263,7 @@ private: void _auto_remove_tiles(); AcceptDialog *confirm_auto_create_tiles; - void _tile_set_atlas_source_changed(); + void _tile_set_changed(); void _tile_proxy_object_changed(String p_what); void _atlas_source_proxy_object_changed(String p_what); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index b1b64564bb..48f64e041c 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -777,7 +777,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { expand->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_expand_output_port), varray(p_id, i, !vsnode->_is_output_port_expanded(i)), CONNECT_DEFERRED); hb->add_child(expand); } - if (visual_shader->get_shader_type() == VisualShader::TYPE_FRAGMENT && port_right != VisualShaderNode::PORT_TYPE_TRANSFORM && port_right != VisualShaderNode::PORT_TYPE_SAMPLER) { + if (vsnode->has_output_port_preview(i) && port_right != VisualShaderNode::PORT_TYPE_TRANSFORM && port_right != VisualShaderNode::PORT_TYPE_SAMPLER) { TextureButton *preview = memnew(TextureButton); preview->set_toggle_mode(true); preview->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiVisibilityHidden"), SNAME("EditorIcons"))); diff --git a/editor/settings_config_dialog.cpp b/editor/settings_config_dialog.cpp index 9062169e06..78dc85aa7d 100644 --- a/editor/settings_config_dialog.cpp +++ b/editor/settings_config_dialog.cpp @@ -433,7 +433,7 @@ void EditorSettingsDialog::_update_shortcuts() { } Array original = sc->get_meta("original"); - Array shortcuts_array = sc->get_events(); + Array shortcuts_array = sc->get_events().duplicate(true); bool same_as_defaults = Shortcut::is_event_array_equal(original, shortcuts_array); bool collapse = !collapsed.has(E) || (collapsed.has(E) && collapsed[E]); |