From f44bce2ee0774bef4aadffe556ce2016cdc38be9 Mon Sep 17 00:00:00 2001 From: SaracenOne Date: Fri, 9 Sep 2022 19:39:37 +0100 Subject: [PATCH] Add support for resource conversion plugins in filesystem dock. --- editor/editor_node.cpp | 120 ++++++++++++++++++++- editor/editor_node.h | 10 +- editor/editor_resource_picker.cpp | 14 +-- editor/filesystem_dock.cpp | 169 +++++++++++++++++++++++++++++- editor/filesystem_dock.h | 12 +++ editor/import_dock.cpp | 55 +++------- editor/import_dock.h | 2 - 7 files changed, 324 insertions(+), 58 deletions(-) diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 66fd2cf904e..0889415d5a4 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -3436,6 +3436,98 @@ void EditorNode::_update_file_menu_closed() { file_menu->set_item_disabled(file_menu->get_item_index(FILE_OPEN_PREV), false); } +void EditorNode::replace_resources_in_object(Object *p_object, const Vector> &p_source_resources, const Vector> &p_target_resource) { + List pi; + p_object->get_property_list(&pi); + + for (const PropertyInfo &E : pi) { + if (!(E.usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + + switch (E.type) { + case Variant::OBJECT: { + if (E.hint == PROPERTY_HINT_RESOURCE_TYPE) { + const Variant &v = p_object->get(E.name); + Ref res = v; + + if (res.is_valid()) { + int res_idx = p_source_resources.find(res); + if (res_idx != -1) { + p_object->set(E.name, p_target_resource.get(res_idx)); + } else { + replace_resources_in_object(v, p_source_resources, p_target_resource); + } + } + } + } break; + case Variant::ARRAY: { + Array varray = p_object->get(E.name); + int len = varray.size(); + bool array_requires_updating = false; + for (int i = 0; i < len; i++) { + const Variant &v = varray.get(i); + Ref res = v; + + if (res.is_valid()) { + int res_idx = p_source_resources.find(res); + if (res_idx != -1) { + varray.set(i, p_target_resource.get(res_idx)); + array_requires_updating = true; + } else { + replace_resources_in_object(v, p_source_resources, p_target_resource); + } + } + } + if (array_requires_updating) { + p_object->set(E.name, varray); + } + } break; + case Variant::DICTIONARY: { + Dictionary d = p_object->get(E.name); + List keys; + bool dictionary_requires_updating = false; + d.get_key_list(&keys); + for (const Variant &F : keys) { + Variant v = d[F]; + Ref res = v; + + if (res.is_valid()) { + int res_idx = p_source_resources.find(res); + if (res_idx != -1) { + d[F] = p_target_resource.get(res_idx); + dictionary_requires_updating = true; + } else { + replace_resources_in_object(v, p_source_resources, p_target_resource); + } + } + } + if (dictionary_requires_updating) { + p_object->set(E.name, d); + } + } break; + default: { + } + } + } + + Node *n = Object::cast_to(p_object); + if (n) { + for (int i = 0; i < n->get_child_count(); i++) { + replace_resources_in_object(n->get_child(i), p_source_resources, p_target_resource); + } + } +} + +void EditorNode::replace_resources_in_scenes(const Vector> &p_source_resources, const Vector> &p_target_resource) { + for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { + Node *edited_scene_root = editor_data.get_edited_scene_root(i); + if (edited_scene_root) { + replace_resources_in_object(edited_scene_root, p_source_resources, p_target_resource); + } + } +} + void EditorNode::add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed) { if (p_editor->has_main_screen()) { singleton->editor_main_screen->add_main_plugin(p_editor); @@ -6350,12 +6442,32 @@ void EditorNode::remove_resource_conversion_plugin(const Ref> EditorNode::find_resource_conversion_plugin(const Ref &p_for_resource) { +Vector> EditorNode::find_resource_conversion_plugin_for_resource(const Ref &p_for_resource) { + if (p_for_resource.is_null()) { + return Vector>(); + } + + Vector> ret; + for (Ref resource_conversion_plugin : resource_conversion_plugins) { + if (resource_conversion_plugin.is_valid() && resource_conversion_plugin->handles(p_for_resource)) { + ret.push_back(resource_conversion_plugin); + } + } + + return ret; +} + +Vector> EditorNode::find_resource_conversion_plugin_for_type_name(const String &p_type) { Vector> ret; - for (int i = 0; i < resource_conversion_plugins.size(); i++) { - if (resource_conversion_plugins[i].is_valid() && resource_conversion_plugins[i]->handles(p_for_resource)) { - ret.push_back(resource_conversion_plugins[i]); + if (ClassDB::can_instantiate(p_type)) { + Ref temp = Object::cast_to(ClassDB::instantiate(p_type)); + if (temp.is_valid()) { + for (Ref resource_conversion_plugin : resource_conversion_plugins) { + if (resource_conversion_plugin.is_valid() && resource_conversion_plugin->handles(temp)) { + ret.push_back(resource_conversion_plugin); + } + } } } diff --git a/editor/editor_node.h b/editor/editor_node.h index 4127dd1539d..109cacdf0e4 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -754,6 +754,13 @@ public: void push_node_item(Node *p_node); void hide_unused_editors(const Object *p_editing_owner = nullptr); + void replace_resources_in_object( + Object *p_object, + const Vector> &p_source_resources, + const Vector> &p_target_resource); + void replace_resources_in_scenes( + const Vector> &p_source_resources, + const Vector> &p_target_resource); void open_request(const String &p_path); void edit_foreign_resource(Ref p_resource); @@ -934,7 +941,8 @@ public: void add_resource_conversion_plugin(const Ref &p_plugin); void remove_resource_conversion_plugin(const Ref &p_plugin); - Vector> find_resource_conversion_plugin(const Ref &p_for_resource); + Vector> find_resource_conversion_plugin_for_resource(const Ref &p_for_resource); + Vector> find_resource_conversion_plugin_for_type_name(const String &p_type); bool ensure_main_scene(bool p_from_native); }; diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp index f20dd992bba..b6b195101c4 100644 --- a/editor/editor_resource_picker.cpp +++ b/editor/editor_resource_picker.cpp @@ -286,12 +286,13 @@ void EditorResourcePicker::_update_menu_items() { // Add options to convert existing resource to another type of resource. if (is_editable() && edited_resource.is_valid()) { - Vector> conversions = EditorNode::get_singleton()->find_resource_conversion_plugin(edited_resource); - if (conversions.size()) { + Vector> conversions = EditorNode::get_singleton()->find_resource_conversion_plugin_for_resource(edited_resource); + if (!conversions.is_empty()) { edit_menu->add_separator(); } - for (int i = 0; i < conversions.size(); i++) { - String what = conversions[i]->converts_to(); + int relative_id = 0; + for (const Ref &conversion : conversions) { + String what = conversion->converts_to(); Ref icon; if (has_theme_icon(what, EditorStringName(EditorIcons))) { icon = get_editor_theme_icon(what); @@ -299,7 +300,8 @@ void EditorResourcePicker::_update_menu_items() { icon = get_theme_icon(what, SNAME("Resource")); } - edit_menu->add_icon_item(icon, vformat(TTR("Convert to %s"), what), CONVERT_BASE_ID + i); + edit_menu->add_icon_item(icon, vformat(TTR("Convert to %s"), what), CONVERT_BASE_ID + relative_id); + relative_id++; } } } @@ -451,7 +453,7 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) { if (p_which >= CONVERT_BASE_ID) { int to_type = p_which - CONVERT_BASE_ID; - Vector> conversions = EditorNode::get_singleton()->find_resource_conversion_plugin(edited_resource); + Vector> conversions = EditorNode::get_singleton()->find_resource_conversion_plugin_for_resource(edited_resource); ERR_FAIL_INDEX(to_type, conversions.size()); edited_resource = conversions[to_type]->convert(edited_resource); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 25725635e3b..b0c87d1781e 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -45,11 +45,13 @@ #include "editor/editor_resource_preview.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" +#include "editor/editor_undo_redo_manager.h" #include "editor/gui/editor_dir_dialog.h" #include "editor/gui/editor_scene_tabs.h" #include "editor/import/3d/scene_import_settings.h" #include "editor/import_dock.h" #include "editor/plugins/editor_context_menu_plugin.h" +#include "editor/plugins/editor_resource_conversion_plugin.h" #include "editor/plugins/editor_resource_tooltip_plugins.h" #include "editor/scene_create_dialog.h" #include "editor/scene_tree_dock.h" @@ -1188,6 +1190,47 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { } } +HashSet FileSystemDock::_get_valid_conversions_for_file_paths(const Vector &p_paths) { + HashSet all_valid_conversion_to_targets; + for (const String &fpath : p_paths) { + if (fpath.is_empty() || fpath == "res://" || !FileAccess::exists(fpath) || FileAccess::exists(fpath + ".import")) { + return HashSet(); + } + + Vector> conversions = EditorNode::get_singleton()->find_resource_conversion_plugin_for_type_name(EditorFileSystem::get_singleton()->get_file_type(fpath)); + + if (conversions.is_empty()) { + // This resource can't convert to anything, so return an empty list. + return HashSet(); + } + + // Get a list of all potentional conversion-to targets. + HashSet current_valid_conversion_to_targets; + for (const Ref &E : conversions) { + const String what = E->converts_to(); + current_valid_conversion_to_targets.insert(what); + } + + if (all_valid_conversion_to_targets.is_empty()) { + // If we have no existing valid conversions, this is the first one, so copy them directly. + all_valid_conversion_to_targets = current_valid_conversion_to_targets; + } else { + // Check existing conversion targets and remove any which are not in the current list. + for (const String &S : all_valid_conversion_to_targets) { + if (!current_valid_conversion_to_targets.has(S)) { + all_valid_conversion_to_targets.erase(S); + } + } + // We have no more remaining valid conversions, so break the loop. + if (all_valid_conversion_to_targets.is_empty()) { + break; + } + } + } + + return all_valid_conversion_to_targets; +} + void FileSystemDock::_select_file(const String &p_path, bool p_select_in_favorites) { String fpath = p_path; if (fpath.ends_with("/")) { @@ -1908,6 +1951,54 @@ void FileSystemDock::_overwrite_dialog_action(bool p_overwrite) { _move_operation_confirm(to_move_path, to_move_or_copy, p_overwrite ? OVERWRITE_REPLACE : OVERWRITE_RENAME); } +void FileSystemDock::_convert_dialog_action() { + Vector> selected_resources; + for (const String &S : to_convert) { + Ref res = ResourceLoader::load(S); + ERR_FAIL_COND(res.is_null()); + selected_resources.push_back(res); + } + + Vector> converted_resources; + HashSet> resources_to_erase_history_for; + for (Ref res : selected_resources) { + Vector> conversions = EditorNode::get_singleton()->find_resource_conversion_plugin_for_resource(res); + for (const Ref &conversion : conversions) { + int conversion_id = 0; + for (const String &target : cached_valid_conversion_targets) { + if (conversion_id == selected_conversion_id && conversion->converts_to() == target) { + Ref converted_res = conversion->convert(res); + ERR_FAIL_COND(res.is_null()); + converted_resources.push_back(converted_res); + resources_to_erase_history_for.insert(res); + break; + } + conversion_id++; + } + } + } + + // Clear history for the objects being replaced. + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + for (Ref res : resources_to_erase_history_for) { + undo_redo->clear_history(true, undo_redo->get_history_id_for_object(res.ptr())); + } + + // Updates all the resources existing as node properties. + EditorNode::get_singleton()->replace_resources_in_scenes(selected_resources, converted_resources); + + // Overwrite the old resources. + for (int i = 0; i < converted_resources.size(); i++) { + Ref original_resource = selected_resources.get(i); + Ref new_resource = converted_resources.get(i); + + // Overwrite the path. + new_resource->set_path(original_resource->get_path(), true); + + ResourceSaver::save(new_resource); + } +} + Vector FileSystemDock::_check_existing() { Vector conflicting_items; for (const FileOrFolder &item : to_move) { @@ -2126,6 +2217,16 @@ void FileSystemDock::_file_list_rmb_option(int p_option) { _file_option(p_option, selected); } +void FileSystemDock::_generic_rmb_option_selected(int p_option) { + // Used for submenu commands where we don't know whether we're + // calling from the file_list_rmb menu or the _tree_rmb option. + if (files->has_focus()) { + _file_list_rmb_option(p_option); + } else { + _tree_rmb_option(p_option); + } +} + void FileSystemDock::_file_option(int p_option, const Vector &p_selected) { // The first one should be the active item. @@ -2568,9 +2669,31 @@ void FileSystemDock::_file_option(int p_option, const Vector &p_selected } break; default: { - if (!EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM, p_option, p_selected)) { - EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM_CREATE, p_option, p_selected); + // Resource conversion commands: + if (p_option >= CONVERT_BASE_ID) { + selected_conversion_id = p_option - CONVERT_BASE_ID; + ERR_FAIL_INDEX(selected_conversion_id, (int)cached_valid_conversion_targets.size()); + + to_convert.clear(); + for (const String &S : p_selected) { + to_convert.push_back(S); + } + + int conversion_id = 0; + for (const String &E : cached_valid_conversion_targets) { + if (conversion_id == selected_conversion_id) { + conversion_dialog->set_text(vformat(TTR("Do you wish to convert these files to %s? (This operation cannot be undone!)"), E)); + conversion_dialog->popup_centered(); + break; + } + conversion_id++; + } + } else { + if (!EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM, p_option, p_selected)) { + EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM_CREATE, p_option, p_selected); + } } + break; } } } @@ -3184,7 +3307,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect if (p_paths.size() == 1 && p_display_path_dependent_options) { PopupMenu *new_menu = memnew(PopupMenu); - new_menu->connect(SceneStringName(id_pressed), callable_mp(this, &FileSystemDock::_tree_rmb_option)); + new_menu->connect(SceneStringName(id_pressed), callable_mp(this, &FileSystemDock::_generic_rmb_option_selected)); p_popup->add_submenu_node_item(TTR("Create New"), new_menu, FILE_NEW); p_popup->set_item_icon(p_popup->get_item_index(FILE_NEW), get_editor_theme_icon(SNAME("Add"))); @@ -3256,6 +3379,41 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect p_popup->add_icon_item(get_editor_theme_icon(SNAME("NonFavorite")), TTR("Remove from Favorites"), FILE_REMOVE_FAVORITE); } + if (p_paths.size() > 1 || p_paths[0] != "res://") { + cached_valid_conversion_targets = _get_valid_conversions_for_file_paths(p_paths); + + int relative_id = 0; + if (!cached_valid_conversion_targets.is_empty()) { + p_popup->add_separator(); + + // If we have more than one type we can convert into, collapse it into a submenu. + const int CONVERSION_SUBMENU_THRESHOLD = 1; + + PopupMenu *container_menu = p_popup; + String conversion_string_template = "Convert to %s"; + + if (cached_valid_conversion_targets.size() > CONVERSION_SUBMENU_THRESHOLD) { + container_menu = memnew(PopupMenu); + container_menu->connect("id_pressed", callable_mp(this, &FileSystemDock::_generic_rmb_option_selected)); + + p_popup->add_submenu_node_item(TTR("Convert to..."), container_menu, FILE_NEW); + conversion_string_template = "%s"; + } + + for (const String &E : cached_valid_conversion_targets) { + Ref icon; + if (has_theme_icon(E, SNAME("EditorIcons"))) { + icon = get_editor_theme_icon(E); + } else { + icon = get_editor_theme_icon(SNAME("Resource")); + } + + container_menu->add_icon_item(icon, vformat(TTR(conversion_string_template), E), CONVERT_BASE_ID + relative_id); + relative_id++; + } + } + } + { List resource_extensions; ResourceFormatImporter::get_singleton()->get_recognized_extensions_for_type("Resource", &resource_extensions); @@ -4193,6 +4351,11 @@ FileSystemDock::FileSystemDock() { new_resource_dialog->set_base_type("Resource"); new_resource_dialog->connect("create", callable_mp(this, &FileSystemDock::_resource_created)); + conversion_dialog = memnew(ConfirmationDialog); + add_child(conversion_dialog); + conversion_dialog->set_ok_button_text(TTR("Convert")); + conversion_dialog->connect(SceneStringName(confirmed), callable_mp(this, &FileSystemDock::_convert_dialog_action)); + uncollapsed_paths_before_search = Vector(); tree_update_id = 0; diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 2f54cb91db0..e1553f040ab 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -140,6 +140,7 @@ private: FILE_NEW_FOLDER, FILE_NEW_SCRIPT, FILE_NEW_SCENE, + CONVERT_BASE_ID = 1000, }; HashMap folder_colors; @@ -201,6 +202,8 @@ private: Label *overwrite_dialog_footer = nullptr; Label *overwrite_dialog_file_list = nullptr; + ConfirmationDialog *conversion_dialog = nullptr; + SceneCreateDialog *make_scene_dialog = nullptr; ScriptCreateDialog *make_script_dialog = nullptr; ShaderCreateDialog *make_shader_dialog = nullptr; @@ -226,6 +229,9 @@ private: String to_move_path; bool to_move_or_copy = false; + Vector to_convert; + int selected_conversion_id = 0; + Vector history; int history_pos; int history_max_size; @@ -245,6 +251,8 @@ private: LocalVector> tooltip_plugins; + HashSet cached_valid_conversion_targets; + void _tree_mouse_exited(); void _reselect_items_selected_on_drag_begin(bool reset = false); @@ -256,6 +264,8 @@ private: void _file_list_gui_input(Ref p_event); void _tree_gui_input(Ref p_event); + HashSet _get_valid_conversions_for_file_paths(const Vector &p_paths); + void _update_file_list(bool p_keep_selection); void _toggle_file_display(); void _set_file_display(bool p_active); @@ -292,11 +302,13 @@ private: void _rename_operation_confirm(); void _duplicate_operation_confirm(); void _overwrite_dialog_action(bool p_overwrite); + void _convert_dialog_action(); Vector _check_existing(); void _move_operation_confirm(const String &p_to_path, bool p_copy = false, Overwrite p_overwrite = OVERWRITE_UNDECIDED); void _tree_rmb_option(int p_option); void _file_list_rmb_option(int p_option); + void _generic_rmb_option_selected(int p_option); void _file_option(int p_option, const Vector &p_selected); void _fw_history(); diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp index 16f4aeda957..14065abf734 100644 --- a/editor/import_dock.cpp +++ b/editor/import_dock.cpp @@ -606,23 +606,25 @@ void ImportDock::_reimport_and_cleanup() { List> external_resources; ResourceCache::get_cached_resources(&external_resources); + Vector> old_resources_to_replace; + Vector> new_resources_to_replace; for (const String &path : need_cleanup) { Ref old_res = old_resources[path]; - Ref new_res; if (params->importer.is_valid()) { - new_res = ResourceLoader::load(path); - } - - for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) { - Node *edited_scene_root = EditorNode::get_editor_data().get_edited_scene_root(i); - if (likely(edited_scene_root)) { - _replace_resource_in_object(edited_scene_root, old_res, new_res); + Ref new_res = ResourceLoader::load(path); + if (new_res.is_valid()) { + old_resources_to_replace.append(old_res); + new_resources_to_replace.append(new_res); } } - for (Ref res : external_resources) { - _replace_resource_in_object(res.ptr(), old_res, new_res); - } } + + EditorNode::get_singleton()->replace_resources_in_scenes(old_resources_to_replace, new_resources_to_replace); + + for (Ref res : external_resources) { + EditorNode::get_singleton()->replace_resources_in_object(res.ptr(), old_resources_to_replace, new_resources_to_replace); + } + need_cleanup.clear(); } @@ -693,37 +695,6 @@ void ImportDock::_reimport() { _set_dirty(false); } -void ImportDock::_replace_resource_in_object(Object *p_object, const Ref &old_resource, const Ref &new_resource) { - ERR_FAIL_NULL(p_object); - - List props; - p_object->get_property_list(&props); - - for (const PropertyInfo &p : props) { - if (p.type != Variant::OBJECT || p.hint != PROPERTY_HINT_RESOURCE_TYPE) { - continue; - } - - Ref res = p_object->get(p.name); - if (res.is_null()) { - continue; - } - - if (res == old_resource) { - p_object->set(p.name, new_resource); - } else { - _replace_resource_in_object(res.ptr(), old_resource, new_resource); - } - } - - Node *n = Object::cast_to(p_object); - if (n) { - for (int i = 0; i < n->get_child_count(); i++) { - _replace_resource_in_object(n->get_child(i), old_resource, new_resource); - } - } -} - void ImportDock::_notification(int p_what) { switch (p_what) { case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { diff --git a/editor/import_dock.h b/editor/import_dock.h index c0a1dee7cab..6a6c54f1ceb 100644 --- a/editor/import_dock.h +++ b/editor/import_dock.h @@ -81,8 +81,6 @@ class ImportDock : public VBoxContainer { void _reimport_and_cleanup(); void _reimport(); - void _replace_resource_in_object(Object *p_object, const Ref &old_resource, const Ref &new_resource); - void _advanced_options(); enum { ITEM_SET_AS_DEFAULT = 100,