diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.cpp b/editor/plugins/gpu_particles_2d_editor_plugin.cpp index 03d3ac173226..78bbc1484ba9 100644 --- a/editor/plugins/gpu_particles_2d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_2d_editor_plugin.cpp @@ -116,11 +116,8 @@ void GPUParticles2DEditorPlugin::_menu_callback(int p_idx) { EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Convert to CPUParticles2D")); - ur->add_do_method(SceneTreeDock::get_singleton(), "replace_node", particles, cpu_particles, true, false); - ur->add_do_reference(cpu_particles); - ur->add_undo_method(SceneTreeDock::get_singleton(), "replace_node", cpu_particles, particles, false, false); - ur->add_undo_reference(particles); - ur->commit_action(); + SceneTreeDock::get_singleton()->replace_node(particles, cpu_particles); + ur->commit_action(false); } break; case MENU_RESTART: { diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.cpp b/editor/plugins/gpu_particles_3d_editor_plugin.cpp index f0b2e32c723b..108f85152f06 100644 --- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_3d_editor_plugin.cpp @@ -277,11 +277,8 @@ void GPUParticles3DEditor::_menu_option(int p_option) { EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Convert to CPUParticles3D")); - ur->add_do_method(SceneTreeDock::get_singleton(), "replace_node", node, cpu_particles, true, false); - ur->add_do_reference(cpu_particles); - ur->add_undo_method(SceneTreeDock::get_singleton(), "replace_node", cpu_particles, node, false, false); - ur->add_undo_reference(node); - ur->commit_action(); + SceneTreeDock::get_singleton()->replace_node(node, cpu_particles); + ur->commit_action(false); } break; case MENU_OPTION_RESTART: { diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp index 0d00bfa8fdc8..f647bded9547 100644 --- a/editor/plugins/sprite_2d_editor_plugin.cpp +++ b/editor/plugins/sprite_2d_editor_plugin.cpp @@ -346,11 +346,8 @@ void Sprite2DEditor::_convert_to_mesh_2d_node() { EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Convert to MeshInstance2D")); - ur->add_do_method(SceneTreeDock::get_singleton(), "replace_node", node, mesh_instance, true, false); - ur->add_do_reference(mesh_instance); - ur->add_undo_method(SceneTreeDock::get_singleton(), "replace_node", mesh_instance, node, false, false); - ur->add_undo_reference(node); - ur->commit_action(); + SceneTreeDock::get_singleton()->replace_node(node, mesh_instance); + ur->commit_action(false); } void Sprite2DEditor::_convert_to_polygon_2d_node() { @@ -404,11 +401,8 @@ void Sprite2DEditor::_convert_to_polygon_2d_node() { EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Convert to Polygon2D")); - ur->add_do_method(SceneTreeDock::get_singleton(), "replace_node", node, polygon_2d_instance, true, false); - ur->add_do_reference(polygon_2d_instance); - ur->add_undo_method(SceneTreeDock::get_singleton(), "replace_node", polygon_2d_instance, node, false, false); - ur->add_undo_reference(node); - ur->commit_action(); + SceneTreeDock::get_singleton()->replace_node(node, polygon_2d_instance); + ur->commit_action(false); } void Sprite2DEditor::_create_collision_polygon_2d_node() { diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 6adfbef06f92..0ac3875ad6ba 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -2363,16 +2363,12 @@ void SceneTreeDock::_create() { Variant c = create_dialog->instantiate_selected(); ERR_FAIL_COND(!c); - Node *newnode = Object::cast_to(c); - ERR_FAIL_COND(!newnode); - - ur->add_do_method(this, "replace_node", n, newnode, true, false); - ur->add_do_reference(newnode); - ur->add_undo_method(this, "replace_node", newnode, n, false, false); - ur->add_undo_reference(n); + Node *new_node = Object::cast_to(c); + ERR_FAIL_COND(!new_node); + replace_node(n, new_node); } - ur->commit_action(); + ur->commit_action(false); } else if (current_option == TOOL_REPARENT_TO_NEW_NODE) { List selection = editor_selection->get_selected_node_list(); ERR_FAIL_COND(selection.size() <= 0); @@ -2426,7 +2422,25 @@ void SceneTreeDock::_create() { scene_tree->get_scene_tree()->grab_focus(); } -void SceneTreeDock::replace_node(Node *p_node, Node *p_by_node, bool p_keep_properties, bool p_remove_old) { +void SceneTreeDock::replace_node(Node *p_node, Node *p_by_node) { + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change type of node(s)"), UndoRedo::MERGE_DISABLE, p_node); + + ur->add_do_method(this, "replace_node", p_node, p_by_node, true, false); + ur->add_do_reference(p_by_node); + + _replace_node(p_node, p_by_node, true, false); + + ur->add_undo_method(this, "replace_node", p_by_node, p_node, false, false); + ur->add_undo_reference(p_node); + + perform_node_replace(nullptr, p_node, p_by_node); + + ur->commit_action(false); +} + +void SceneTreeDock::_replace_node(Node *p_node, Node *p_by_node, bool p_keep_properties, bool p_remove_old) { + ERR_FAIL_COND_MSG(!p_node->is_inside_tree(), "_replace_node() can't be called on a node outside of tree. You might have called it twice."); Node *n = p_node; Node *newnode = p_by_node; @@ -2499,6 +2513,84 @@ void SceneTreeDock::replace_node(Node *p_node, Node *p_by_node, bool p_keep_prop } } +void SceneTreeDock::perform_node_replace(Node *p_base, Node *p_node, Node *p_by_node) { + if (!p_base) { + p_base = edited_scene; + } + + if (!p_base) { + return; + } + + // Renaming node used in node properties. + List properties; + p_base->get_property_list(&properties); + + for (const PropertyInfo &E : properties) { + if (!(E.usage & (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR))) { + continue; + } + String propertyname = E.name; + Variant old_variant = p_base->get(propertyname); + Variant updated_variant = old_variant; + String warn_message; + + if (_check_node_recursive(updated_variant, p_node, p_by_node, E.hint_string, warn_message)) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->add_do_property(p_base, propertyname, updated_variant); + undo_redo->add_undo_property(p_base, propertyname, old_variant); + p_base->set(propertyname, updated_variant); + if (!warn_message.is_empty()) { + String node_path = (String(edited_scene->get_name()) + "/" + String(edited_scene->get_path_to(p_base))).trim_suffix("/."); + WARN_PRINT(warn_message + vformat(TTR("Removing the node from variable \"%s\" on node \"%s\"."), propertyname, node_path)); + } + } + } + + for (int i = 0; i < p_base->get_child_count(); i++) { + perform_node_replace(p_base->get_child(i), p_node, p_by_node); + } +} + +bool SceneTreeDock::_check_node_recursive(Variant &r_variant, Node *p_node, Node *p_by_node, const String type_hint, String &r_warn_message) { + switch (r_variant.get_type()) { + case Variant::OBJECT: { + if (p_node == r_variant) { + if (p_by_node->is_class(type_hint) || EditorNode::get_singleton()->is_object_of_custom_type(p_by_node, type_hint)) { + r_variant = p_by_node; + } else { + r_variant = memnew(Object); + r_warn_message = vformat(TTR("The node's new type is incompatible with an exported variable (expected %s, but type is %s)."), type_hint, p_by_node->get_class()); + } + return true; + } + } break; + + case Variant::ARRAY: { + Array a = r_variant; + bool updated = false; + for (int i = 0; i < a.size(); i++) { + Variant value = a[i]; + if (_check_node_recursive(value, p_node, p_by_node, type_hint.get_slice(":", 1), r_warn_message)) { + if (!updated) { + a = a.duplicate(); // Need to duplicate for undo-redo to work. + updated = true; + } + a[i] = value; + } + } + if (updated) { + r_variant = a; + return true; + } + } break; + default: { + } + } + + return false; +} + void SceneTreeDock::set_edited_scene(Node *p_scene) { edited_scene = p_scene; } @@ -3635,7 +3727,7 @@ void SceneTreeDock::_bind_methods() { ClassDB::bind_method(D_METHOD("instantiate"), &SceneTreeDock::instantiate); ClassDB::bind_method(D_METHOD("get_tree_editor"), &SceneTreeDock::get_tree_editor); - ClassDB::bind_method(D_METHOD("replace_node"), &SceneTreeDock::replace_node); + ClassDB::bind_method(D_METHOD("replace_node"), &SceneTreeDock::_replace_node); ADD_SIGNAL(MethodInfo("remote_tree_selected")); ADD_SIGNAL(MethodInfo("add_node_used")); diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index 1db73df4c770..21424925c530 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -281,6 +281,8 @@ class SceneTreeDock : public VBoxContainer { bool _update_node_path(Node *p_root_node, NodePath &r_node_path, HashMap *p_renames) const; bool _check_node_path_recursive(Node *p_root_node, Variant &r_variant, HashMap *p_renames) const; + bool _check_node_recursive(Variant &r_variant, Node *p_node, Node *p_by_node, const String type_hint, String &r_warn_message); + void _replace_node(Node *p_node, Node *p_by_node, bool p_keep_properties = true, bool p_remove_old = true); private: static SceneTreeDock *singleton; @@ -306,6 +308,7 @@ class SceneTreeDock : public VBoxContainer { void set_selected(Node *p_node, bool p_emit_selected = false); void fill_path_renames(Node *p_node, Node *p_new_parent, HashMap *p_renames); void perform_node_renames(Node *p_base, HashMap *p_renames, HashMap, HashSet> *r_rem_anims = nullptr); + void perform_node_replace(Node *p_base, Node *p_node, Node *p_by_node); SceneTreeEditor *get_tree_editor() { return scene_tree; } EditorData *get_editor_data() { return editor_data; } @@ -315,7 +318,7 @@ class SceneTreeDock : public VBoxContainer { void show_tab_buttons(); void hide_tab_buttons(); - void replace_node(Node *p_node, Node *p_by_node, bool p_keep_properties = true, bool p_remove_old = true); + void replace_node(Node *p_node, Node *p_by_node); void attach_script_to_selected(bool p_extend); void open_script_dialog(Node *p_for_node, bool p_extend);