/**************************************************************************/ /* mesh_library_editor_plugin.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /**************************************************************************/ /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ #include "mesh_library_editor_plugin.h" #include "core/io/resource_loader.h" #include "core/object/callable_mp.h" #include "core/object/class_db.h" #include "editor/docks/editor_dock_manager.h" #include "editor/docks/inspector_dock.h" #include "editor/editor_interface.h" #include "editor/editor_node.h" #include "editor/editor_undo_redo_manager.h" #include "editor/gui/editor_file_dialog.h" #include "editor/gui/editor_zoom_widget.h" #include "editor/gui/filter_line_edit.h" #include "editor/inspector/editor_inspector.h" #include "editor/inspector/editor_resource_preview.h" #include "editor/settings/editor_settings.h" #include "editor/themes/editor_scale.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/navigation/navigation_region_3d.h" #include "scene/3d/physics/static_body_3d.h" #include "scene/gui/button.h" #include "scene/gui/item_list.h" #include "scene/gui/menu_button.h" #include "scene/gui/separator.h" #include "scene/gui/split_container.h" #include "scene/main/timer.h" #include "scene/resources/3d/mesh_library.h" #include "scene/resources/packed_scene.h" bool MeshLibraryEditor::MeshLibraryItem::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(mesh_library.is_null(), false); ERR_FAIL_COND_V(mesh_id == -1, false); ERR_FAIL_COND_V(!mesh_library->has_item(mesh_id), false); if (p_name == "name") { mesh_library->set_item_name(mesh_id, p_value); } else if (p_name == "mesh") { mesh_library->set_item_mesh(mesh_id, p_value); } else if (p_name == "mesh_transform") { mesh_library->set_item_mesh_transform(mesh_id, p_value); } else if (p_name == "mesh_cast_shadow") { switch ((int)p_value) { case 0: { mesh_library->set_item_mesh_cast_shadow(mesh_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_OFF); } break; case 1: { mesh_library->set_item_mesh_cast_shadow(mesh_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_ON); } break; case 2: { mesh_library->set_item_mesh_cast_shadow(mesh_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_DOUBLE_SIDED); } break; case 3: { mesh_library->set_item_mesh_cast_shadow(mesh_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_SHADOWS_ONLY); } break; default: { mesh_library->set_item_mesh_cast_shadow(mesh_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_ON); } break; } #ifndef PHYSICS_3D_DISABLED } else if (p_name == "shapes") { mesh_library->call("set_item_shapes", mesh_id, p_value); #endif // PHYSICS_3D_DISABLED } else if (p_name == "preview") { mesh_library->set_item_preview(mesh_id, p_value); } else if (p_name == "navigation_mesh") { mesh_library->set_item_navigation_mesh(mesh_id, p_value); } else if (p_name == "navigation_mesh_transform") { mesh_library->set_item_navigation_mesh_transform(mesh_id, p_value); } else if (p_name == "navigation_layers") { mesh_library->set_item_navigation_layers(mesh_id, p_value); } else { return false; } return false; } bool MeshLibraryEditor::MeshLibraryItem::_get(const StringName &p_name, Variant &r_ret) const { ERR_FAIL_COND_V(mesh_library.is_null(), false); ERR_FAIL_COND_V(mesh_id == -1, false); ERR_FAIL_COND_V(!mesh_library->has_item(mesh_id), false); if (p_name == "name") { r_ret = mesh_library->get_item_name(mesh_id); } else if (p_name == "mesh") { r_ret = mesh_library->get_item_mesh(mesh_id); } else if (p_name == "mesh_transform") { r_ret = mesh_library->get_item_mesh_transform(mesh_id); } else if (p_name == "mesh_cast_shadow") { r_ret = (int)mesh_library->get_item_mesh_cast_shadow(mesh_id); #ifndef PHYSICS_3D_DISABLED } else if (p_name == "shapes") { r_ret = mesh_library->call("get_item_shapes", mesh_id); #endif // PHYSICS_3D_DISABLED } else if (p_name == "navigation_mesh") { r_ret = mesh_library->get_item_navigation_mesh(mesh_id); } else if (p_name == "navigation_mesh_transform") { r_ret = mesh_library->get_item_navigation_mesh_transform(mesh_id); } else if (p_name == "navigation_layers") { r_ret = mesh_library->get_item_navigation_layers(mesh_id); } else if (p_name == "preview") { r_ret = mesh_library->get_item_preview(mesh_id); } else { return false; } return true; } void MeshLibraryEditor::MeshLibraryItem::_get_property_list(List *p_list) const { if (mesh_id == -1 || mesh_library.is_null() || !mesh_library->has_item(mesh_id)) { return; } p_list->push_back(PropertyInfo(Variant::STRING, PNAME("name"))); p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("mesh"), PROPERTY_HINT_RESOURCE_TYPE, Mesh::get_class_static())); p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, PNAME("mesh_transform"), PROPERTY_HINT_NONE, "suffix:m")); p_list->push_back(PropertyInfo(Variant::INT, PNAME("mesh_cast_shadow"), PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only")); p_list->push_back(PropertyInfo(Variant::ARRAY, PNAME("shapes"))); p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("navigation_mesh"), PROPERTY_HINT_RESOURCE_TYPE, NavigationMesh::get_class_static())); p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, PNAME("navigation_mesh_transform"), PROPERTY_HINT_NONE, "suffix:m")); p_list->push_back(PropertyInfo(Variant::INT, PNAME("navigation_layers"), PROPERTY_HINT_LAYERS_3D_NAVIGATION)); p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("preview"), PROPERTY_HINT_RESOURCE_TYPE, Texture2D::get_class_static(), PROPERTY_USAGE_DEFAULT)); } //////////////// void MeshLibraryEditor::edit(const Ref &p_mesh_library) { if (mesh_library.is_valid()) { mesh_library->disconnect_changed(callable_mp(update_items_delay, &Timer::start)); } selected_item = -1; inspector->edit(nullptr); mesh_library = p_mesh_library; if (mesh_library.is_valid()) { // Avoid updating multiple times at once. mesh_library->connect_changed(callable_mp(update_items_delay, &Timer::start).bind(UPDATE_ITEMS_DELAY_TIMEOUT)); _update_mesh_items(); } } Error MeshLibraryEditor::update_library_file(Node *p_base_scene, Ref ml, bool p_merge, bool p_apply_xforms) { _import_scene(p_base_scene, ml, p_merge, p_apply_xforms); return OK; } void MeshLibraryEditor::_update_mesh_items(bool p_reselect, Ref p_lib_check) { if (p_lib_check.is_valid() && mesh_library != p_lib_check) { return; } mesh_library->set_edited(true); mesh_items->clear(); remove_item->set_disabled(true); update_items_delay->stop(); if (mesh_library->get_item_count() == 0) { _select_item(-1); item_split->hide(); empty_lib->show(); return; } empty_lib->hide(); item_split->show(); float min_size = EDITOR_GET("editors/grid_map/preview_size"); min_size *= EDSCALE; mesh_items->set_fixed_column_width(min_size * MAX(zoom_widget->get_zoom(), 1.5)); mesh_items->set_fixed_icon_size(Size2(min_size, min_size)); Vector ids; ids = mesh_library->get_item_list(); struct _CGMEItemSort { String name; int id = 0; _FORCE_INLINE_ bool operator<(const _CGMEItemSort &r_it) const { return name < r_it.name; } }; List<_CGMEItemSort> il; for (const int &E : ids) { _CGMEItemSort is; is.id = E; is.name = mesh_library->get_item_name(E); il.push_back(is); } il.sort(); int item = 0; bool selection_found = false; String filter = search_box->get_text().strip_edges(); for (_CGMEItemSort &E : il) { int id = E.id; String name = mesh_library->get_item_name(id); if (!filter.is_empty() && !filter.is_subsequence_ofn(name)) { if (selected_item == id) { selection_found = true; } continue; } mesh_items->add_item(""); Ref preview = mesh_library->get_item_preview(id); if (preview.is_valid()) { mesh_items->set_item_icon(item, preview); } else { Ref mesh = mesh_library->get_item_mesh(id); if (mesh.is_valid()) { // Fallback to the item's mesh preview. EditorResourcePreview::get_singleton()->queue_edited_resource_preview(mesh, callable_mp(this, &MeshLibraryEditor::_update_resource_preview).bind(item)); } } String idx = "#" + itos(id); if (name.is_empty()) { name = idx; } else { mesh_items->set_item_tooltip(item, name + "\n" + idx); } mesh_items->set_item_text(item, name); mesh_items->set_item_metadata(item, id); if (selected_item == id) { selection_found = true; if (p_reselect) { _select_item(id); mesh_items->select(item); remove_item->set_disabled(false); } } item++; } if (!selection_found) { if (p_reselect && item > 0) { _select_item(mesh_items->get_item_metadata(0)); mesh_items->select(0); remove_item->set_disabled(false); } else { _select_item(-1); } } } void MeshLibraryEditor::_update_resource_preview(const String &p_path, const Ref &p_preview, const Ref &p_small_preview, int p_idx) { if (p_idx < mesh_items->get_item_count()) { mesh_items->set_item_icon(p_idx, p_preview); } } void MeshLibraryEditor::_select_item(int p_id, Ref p_lib_check) { if (p_lib_check.is_valid() && mesh_library != p_lib_check) { return; } selected_item = p_id; if (selected_item == -1) { inspector->edit(nullptr); return; } Object *edited = inspector->get_edited_object(); if (edited) { Ref item(edited); if (item->mesh_id == selected_item) { return; // Already inspecting it. } } mesh_library_item.instantiate(); mesh_library_item->mesh_library = mesh_library; mesh_library_item->mesh_id = selected_item; inspector->edit(*mesh_library_item); } void MeshLibraryEditor::_select_item_and_button(int p_id, Ref p_lib_check) { if (p_lib_check.is_valid() && mesh_library != p_lib_check) { return; } _select_item(p_id); for (int i = 0; i < mesh_items->get_item_count(); i++) { int id = mesh_items->get_item_metadata(i); if (id == p_id) { mesh_items->select(i); remove_item->set_disabled(false); return; } } } void MeshLibraryEditor::_select_prev_item_and_button(int p_id, Ref p_lib_check) { int idx = -1; for (int i = 0; i < mesh_items->get_item_count(); i++) { int id = mesh_items->get_item_metadata(i); if (id == p_id) { idx = MAX(i - 1, 0); break; } } if (idx == -1 && mesh_items->get_item_count() > 0) { idx = 0; } if (idx > -1) { int id = mesh_items->get_item_metadata(idx); _select_item(id); mesh_items->select(idx); remove_item->set_disabled(false); } } void MeshLibraryEditor::_mesh_items_cbk(int p_idx) { int id = mesh_items->get_item_metadata(p_idx); _select_item(id); remove_item->set_disabled(false); } void MeshLibraryEditor::_menu_cbk(int p_option) { switch (p_option) { case MENU_OPTION_ADD_ITEM: { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add MeshLibrary item")); int to_create = mesh_library->get_last_unused_item_id(); undo_redo->add_do_method(*mesh_library, "create_item", to_create); undo_redo->add_undo_method(*mesh_library, "remove_item", to_create); undo_redo->add_do_method(this, "_update_mesh_items", false, *mesh_library); undo_redo->add_undo_method(this, "_update_mesh_items", false, *mesh_library); undo_redo->add_do_method(this, "_select_item_and_button", to_create, *mesh_library); if (mesh_library->get_item_count() > 0) { undo_redo->add_undo_method(this, "_select_prev_item_and_button", to_create, *mesh_library); } undo_redo->commit_action(); } break; case MENU_OPTION_REMOVE_ITEM: { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Remove MeshLibrary item")); if (mesh_library->get_item_count() - 1 > 0) { undo_redo->add_do_method(this, "_select_prev_item_and_button", selected_item, *mesh_library); } undo_redo->add_do_method(*mesh_library, "remove_item", selected_item); Ref mesh = mesh_library->get_item_mesh(selected_item); int mesh_shadow = mesh_library->get_item_mesh_cast_shadow(selected_item); Transform3D mesh_transform = mesh_library->get_item_mesh_transform(selected_item); String mesh_name = mesh_library->get_item_name(selected_item); int nav_layers = mesh_library->get_item_navigation_layers(selected_item); Ref nav_mesh = mesh_library->get_item_navigation_mesh(selected_item); Transform3D nav_mesh_transform = mesh_library->get_item_navigation_mesh_transform(selected_item); Ref preview = mesh_library->get_item_preview(selected_item); Array shapes = mesh_library->call("get_item_shapes", selected_item); undo_redo->add_undo_method(*mesh_library, "create_item", selected_item); undo_redo->add_undo_method(*mesh_library, "set_item_mesh", selected_item, mesh); undo_redo->add_undo_method(*mesh_library, "set_item_mesh_cast_shadow", selected_item, mesh_shadow); undo_redo->add_undo_method(*mesh_library, "set_item_mesh_transform", selected_item, mesh_transform); undo_redo->add_undo_method(*mesh_library, "set_item_name", selected_item, mesh_name); undo_redo->add_undo_method(*mesh_library, "set_item_navigation_layers", selected_item, nav_layers); undo_redo->add_undo_method(*mesh_library, "set_item_navigation_mesh", selected_item, nav_mesh); undo_redo->add_undo_method(*mesh_library, "set_item_navigation_mesh_transform", selected_item, nav_mesh_transform); undo_redo->add_undo_method(*mesh_library, "set_item_preview", selected_item, preview); undo_redo->add_undo_method(*mesh_library, "set_item_shapes", selected_item, shapes); undo_redo->add_do_method(this, "_update_mesh_items", true, *mesh_library); undo_redo->add_undo_method(this, "_update_mesh_items", false, *mesh_library); undo_redo->add_undo_method(this, "_select_item_and_button", selected_item, *mesh_library); undo_redo->commit_action(); } break; case MENU_OPTION_IMPORT_FROM_SCENE: { apply_xforms = false; import_update = false; file->popup_file_dialog(); } break; case MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS: { apply_xforms = true; import_update = false; file->popup_file_dialog(); } break; case MENU_OPTION_UPDATE_FROM_SCENE: { import_update = true; cd_update->set_text(vformat(TTR("Update from existing scene?:\n%s"), String(mesh_library->get_meta("_editor_source_scene")))); cd_update->popup_centered(Size2(500, 60)); } break; } } void MeshLibraryEditor::_menu_update_confirm(bool p_apply_xforms) { cd_update->hide(); apply_xforms = p_apply_xforms; String existing = mesh_library->get_meta("_editor_source_scene"); ERR_FAIL_COND(existing.is_empty()); _import_scene_cbk(existing); } void MeshLibraryEditor::_import_scene(Node *p_scene, Ref p_library, bool p_merge, bool p_apply_xforms) { if (!p_merge) { p_library->clear(); } HashMap mesh_instances; for (int i = 0; i < p_scene->get_child_count(); i++) { _import_scene_parse_node(p_library, mesh_instances, p_scene->get_child(i), p_merge, p_apply_xforms); } // Generate previews. Vector> meshes; Vector transforms; Vector ids = p_library->get_item_list(); for (const int &E : ids) { if (mesh_instances.find(E)) { meshes.push_back(p_library->get_item_mesh(E)); transforms.push_back(mesh_instances[E]->get_transform()); } } Vector> textures = EditorInterface::get_singleton()->make_mesh_previews(meshes, &transforms, EDITOR_GET("editors/grid_map/preview_size")); int idx = 0; for (const int &E : ids) { if (mesh_instances.find(E)) { p_library->set_item_preview(E, textures[idx]); idx++; } } } void MeshLibraryEditor::_import_scene_cbk(const String &p_str) { Ref ps = ResourceLoader::load(p_str, "PackedScene"); ERR_FAIL_COND(ps.is_null()); Node *scene = ps->instantiate(); ERR_FAIL_NULL_MSG(scene, "Cannot create an instance from PackedScene '" + p_str + "'."); // Preserve the data from the current library. Ref old_lib; old_lib = mesh_library->duplicate(); _import_scene(scene, mesh_library, import_update, apply_xforms); memdelete(scene); mesh_library->set_meta("_editor_source_scene", p_str); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Import MeshLibrary from scene")); undo_redo->add_do_method(*mesh_library, "copy_from_resource", *mesh_library->duplicate()); undo_redo->add_undo_method(*mesh_library, "copy_from_resource", old_lib); undo_redo->commit_action(false); import_scene->get_popup()->set_item_disabled(import_scene->get_popup()->get_item_index(MENU_OPTION_UPDATE_FROM_SCENE), false); } void MeshLibraryEditor::_import_scene_parse_node(Ref p_library, HashMap &p_mesh_instances, Node *p_node, bool p_merge, bool p_apply_xforms) { MeshInstance3D *mesh_instance_node = Object::cast_to(p_node); if (!mesh_instance_node) { // No MeshInstance so search deeper. for (int i = 0; i < p_node->get_child_count(); i++) { _import_scene_parse_node(p_library, p_mesh_instances, p_node->get_child(i), p_merge, p_apply_xforms); } return; } Ref source_mesh = mesh_instance_node->get_mesh(); if (source_mesh.is_null()) { return; } int item_id = p_library->find_item_by_name(mesh_instance_node->get_name()); if (item_id < 0) { item_id = p_library->get_last_unused_item_id(); p_library->create_item(item_id); p_library->set_item_name(item_id, mesh_instance_node->get_name()); } else if (!p_merge) { WARN_PRINT(vformat("MeshLibrary export found a MeshInstance3D with a duplicated name '%s' in the exported scene that overrides a previously parsed MeshInstance3D item with the same name.", mesh_instance_node->get_name())); } p_mesh_instances[item_id] = mesh_instance_node; Ref item_mesh = source_mesh->duplicate(); for (int i = 0; i < item_mesh->get_surface_count(); i++) { Ref surface_override_material = mesh_instance_node->get_surface_override_material(i); if (surface_override_material.is_valid()) { item_mesh->surface_set_material(i, surface_override_material); } } p_library->set_item_mesh(item_id, item_mesh); GeometryInstance3D::ShadowCastingSetting gi3d_cast_shadows_setting = mesh_instance_node->get_cast_shadows_setting(); switch (gi3d_cast_shadows_setting) { case GeometryInstance3D::ShadowCastingSetting::SHADOW_CASTING_SETTING_OFF: { p_library->set_item_mesh_cast_shadow(item_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_OFF); } break; case GeometryInstance3D::ShadowCastingSetting::SHADOW_CASTING_SETTING_ON: { p_library->set_item_mesh_cast_shadow(item_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_ON); } break; case GeometryInstance3D::ShadowCastingSetting::SHADOW_CASTING_SETTING_DOUBLE_SIDED: { p_library->set_item_mesh_cast_shadow(item_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_DOUBLE_SIDED); } break; case GeometryInstance3D::ShadowCastingSetting::SHADOW_CASTING_SETTING_SHADOWS_ONLY: { p_library->set_item_mesh_cast_shadow(item_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_SHADOWS_ONLY); } break; default: { p_library->set_item_mesh_cast_shadow(item_id, RSE::ShadowCastingSetting::SHADOW_CASTING_SETTING_ON); } break; } Transform3D item_mesh_transform; if (p_apply_xforms) { item_mesh_transform = mesh_instance_node->get_transform(); } p_library->set_item_mesh_transform(item_id, item_mesh_transform); Vector collisions; for (int i = 0; i < mesh_instance_node->get_child_count(); i++) { StaticBody3D *static_body_node = Object::cast_to(mesh_instance_node->get_child(i)); if (!static_body_node) { continue; } List shapes; static_body_node->get_shape_owners(&shapes); for (uint32_t &E : shapes) { if (static_body_node->is_shape_owner_disabled(E)) { continue; } Transform3D shape_transform; if (p_apply_xforms) { shape_transform = mesh_instance_node->get_transform(); } shape_transform *= static_body_node->get_transform() * static_body_node->shape_owner_get_transform(E); for (int k = 0; k < static_body_node->shape_owner_get_shape_count(E); k++) { Ref collision_shape = static_body_node->shape_owner_get_shape(E, k); if (collision_shape.is_null()) { continue; } MeshLibrary::ShapeData shape_data; shape_data.shape = collision_shape; shape_data.local_transform = shape_transform; collisions.push_back(shape_data); } } } p_library->set_item_shapes(item_id, collisions); for (int i = 0; i < mesh_instance_node->get_child_count(); i++) { NavigationRegion3D *navigation_region_node = Object::cast_to(mesh_instance_node->get_child(i)); if (!navigation_region_node) { continue; } Ref navigation_mesh = navigation_region_node->get_navigation_mesh(); if (navigation_mesh.is_valid()) { Transform3D navigation_mesh_transform = navigation_region_node->get_transform(); p_library->set_item_navigation_mesh(item_id, navigation_mesh); p_library->set_item_navigation_mesh_transform(item_id, navigation_mesh_transform); break; } } } void MeshLibraryEditor::_icon_size_changed(float p_value) { mesh_items->set_icon_scale(p_value); _update_mesh_items(); } void MeshLibraryEditor::_mesh_items_input(const Ref &p_event) { // Zoom in/out using Ctrl + mouse wheel. const Ref mb = p_event; if (mb.is_valid() && mb->is_pressed() && mb->is_command_or_control_pressed()) { if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP) { zoom_widget->set_zoom(zoom_widget->get_zoom() + 0.2); zoom_widget->emit_signal(SNAME("zoom_changed"), zoom_widget->get_zoom()); accept_event(); } if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN) { zoom_widget->set_zoom(zoom_widget->get_zoom() - 0.2); zoom_widget->emit_signal(SNAME("zoom_changed"), zoom_widget->get_zoom()); accept_event(); } } } void MeshLibraryEditor::_notification(int p_what) { if (p_what == NOTIFICATION_THEME_CHANGED) { add_item->set_button_icon(get_editor_theme_icon(SNAME("Add"))); remove_item->set_button_icon(get_editor_theme_icon(SNAME("Remove"))); } } void MeshLibraryEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_mesh_items", "reselect"), &MeshLibraryEditor::_update_mesh_items, DEFVAL(true)); ClassDB::bind_method(D_METHOD("_select_item_and_button", "id", "library"), &MeshLibraryEditor::_select_item_and_button, DEFVAL(Ref())); ClassDB::bind_method(D_METHOD("_select_prev_item_and_button", "id", "library"), &MeshLibraryEditor::_select_prev_item_and_button, DEFVAL(Ref())); } MeshLibraryEditor::MeshLibraryEditor() { set_name(TTRC("MeshLibrary")); set_icon_name("MeshLibraryEditor"); set_default_slot(EditorDock::DOCK_SLOT_BOTTOM); set_available_layouts(EditorDock::DOCK_LAYOUT_HORIZONTAL | EditorDock::DOCK_LAYOUT_FLOATING); set_global(false); set_transient(true); set_custom_minimum_size(Size2(0, 200 * EDSCALE)); VBoxContainer *vb = memnew(VBoxContainer); add_child(vb); HBoxContainer *toolbar = memnew(HBoxContainer); vb->add_child(toolbar); add_item = memnew(Button); add_item->set_text(TTRC("Add")); add_item->set_theme_type_variation(SceneStringName(FlatButton)); add_item->connect(SceneStringName(pressed), callable_mp(this, &MeshLibraryEditor::_menu_cbk).bind(MENU_OPTION_ADD_ITEM)); toolbar->add_child(add_item); remove_item = memnew(Button); remove_item->set_text(TTRC("Remove")); remove_item->set_disabled(true); remove_item->set_theme_type_variation(SceneStringName(FlatButton)); remove_item->connect(SceneStringName(pressed), callable_mp(this, &MeshLibraryEditor::_menu_cbk).bind(MENU_OPTION_REMOVE_ITEM)); toolbar->add_child(remove_item); toolbar->add_child(memnew(VSeparator)); import_scene = memnew(MenuButton); import_scene->set_text(TTRC("Import")); import_scene->set_flat(false); import_scene->set_theme_type_variation(SceneStringName(FlatButton)); toolbar->add_child(import_scene); import_scene->get_popup()->add_item(TTRC("Import from Scene (Ignore Transforms)"), MENU_OPTION_IMPORT_FROM_SCENE); import_scene->get_popup()->add_item(TTRC("Import from Scene (Apply Transforms)"), MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS); import_scene->get_popup()->add_separator(); import_scene->get_popup()->add_item(TTRC("Update from Scene"), MENU_OPTION_UPDATE_FROM_SCENE); import_scene->get_popup()->set_item_disabled(-1, true); import_scene->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &MeshLibraryEditor::_menu_cbk)); toolbar->add_spacer(); update_items_delay = memnew(Timer); update_items_delay->set_wait_time(UPDATE_ITEMS_DELAY_TIMEOUT); update_items_delay->set_one_shot(true); update_items_delay->connect("timeout", callable_mp(this, &MeshLibraryEditor::_update_mesh_items).bind(true, Ref())); add_child(update_items_delay); search_box = memnew(FilterLineEdit); search_box->set_placeholder(TTRC("Filter Meshes")); search_box->set_accessibility_name(TTRC("Filter Meshes")); search_box->add_theme_constant_override("minimum_character_width", 10); search_box->set_h_size_flags(SIZE_EXPAND_FILL); toolbar->add_child(search_box); search_box->connect(SceneStringName(text_changed), callable_mp(update_items_delay, &Timer::start).bind(UPDATE_ITEMS_DELAY_TIMEOUT).unbind(1)); zoom_widget = memnew(EditorZoomWidget); zoom_widget->setup_zoom_limits(0.2, 4); zoom_widget->connect("zoom_changed", callable_mp(this, &MeshLibraryEditor::_icon_size_changed)); zoom_widget->set_shortcut_context(this); toolbar->add_child(zoom_widget); item_split = memnew(HSplitContainer); item_split->set_v_size_flags(SIZE_EXPAND_FILL); vb->add_child(item_split); item_split->hide(); mesh_items = memnew(ItemList); mesh_items->set_max_columns(0); mesh_items->set_icon_mode(ItemList::ICON_MODE_TOP); mesh_items->set_max_text_lines(2); mesh_items->set_theme_type_variation("ItemListSecondary"); mesh_items->set_h_size_flags(SIZE_EXPAND_FILL); mesh_items->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); mesh_items->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); item_split->add_child(mesh_items); mesh_items->connect(SceneStringName(item_selected), callable_mp(this, &MeshLibraryEditor::_mesh_items_cbk)); mesh_items->connect(SceneStringName(gui_input), callable_mp(this, &MeshLibraryEditor::_mesh_items_input)); search_box->set_forward_control(mesh_items); inspector = memnew(EditorInspector); inspector->set_use_doc_hints(true); inspector->set_theme_type_variation("ScrollContainerSecondary"); inspector->set_h_size_flags(SIZE_EXPAND_FILL); inspector->set_stretch_ratio(0.3); inspector->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); item_split->add_child(inspector); inspector->add_custom_property_description("MeshLibraryItem", "name", TTRC("The item's name, shown in the editor. It can also be used to look up the item later using [method MeshLibrary.find_item_by_name].")); inspector->add_custom_property_description("MeshLibraryItem", "mesh", TTRC("The item's mesh. Used by other parts of the engine (e.g. [GridMap], which displays them in a 3D tile).")); inspector->add_custom_property_description("MeshLibraryItem", "mesh_transform", TTRC("The transform to apply to the item's mesh.")); inspector->add_custom_property_description("MeshLibraryItem", "mesh_cast_shadow", TTRC("The shadow casting mode used by the item's mesh. See [enum RenderingServer.ShadowCastingSetting]")); inspector->add_custom_property_description("MeshLibraryItem", "shapes", TTRC("The item's collision shapes.\nThe array should consist of [Shape3D] objects, each followed by a [Transform3D] that will be applied to it. For shapes that should not have a transform, use [constant Transform3D.IDENTITY].")); inspector->add_custom_property_description("MeshLibraryItem", "navigation_mesh", TTRC("The item's navigation mesh.")); inspector->add_custom_property_description("MeshLibraryItem", "navigation_mesh_transform", TTRC("The transform to apply to the item's navigation mesh.")); inspector->add_custom_property_description("MeshLibraryItem", "navigation_layers", TTRC("The item's navigation layers bitmask.")); inspector->add_custom_property_description("MeshLibraryItem", "preview", TTRC("The texture to use as the item's preview icon in the editor.")); empty_lib = memnew(Label); empty_lib->set_text(TTRC("No items found inside the MeshLibrary.\nYou can add some by using the Add button on the left, or by exporting them from a scene file via the Export menu.")); empty_lib->set_focus_mode(FOCUS_ACCESSIBILITY); empty_lib->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); empty_lib->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); empty_lib->set_v_size_flags(SIZE_EXPAND_FILL); empty_lib->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); empty_lib->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); vb->add_child(empty_lib); empty_lib->hide(); file = memnew(EditorFileDialog); file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); List extensions; ResourceLoader::get_recognized_extensions_for_type("PackedScene", &extensions); file->clear_filters(); file->set_title(TTRC("Import Scene")); for (const String &extension : extensions) { file->add_filter("*." + extension, extension.to_upper()); } add_child(file); file->connect("file_selected", callable_mp(this, &MeshLibraryEditor::_import_scene_cbk)); cd_update = memnew(ConfirmationDialog); add_child(cd_update); cd_update->set_ok_button_text(TTRC("Apply without Transforms")); cd_update->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &MeshLibraryEditor::_menu_update_confirm).bind(false)); cd_update->add_button(TTRC("Apply with Transforms"))->connect(SceneStringName(pressed), callable_mp(this, &MeshLibraryEditor::_menu_update_confirm).bind(true)); } //////////////// void MeshLibraryEditorPlugin::edit(Object *p_node) { Ref ml = Object::cast_to(p_node); if (ml.is_valid()) { mesh_library_editor->edit(ml); } } bool MeshLibraryEditorPlugin::handles(Object *p_object) const { return p_object && p_object->is_class("MeshLibrary"); } void MeshLibraryEditorPlugin::make_visible(bool p_visible) { if (p_visible) { mesh_library_editor->make_visible(); } else { mesh_library_editor->close(); } } void MeshLibraryEditorPlugin::open_editor() { mesh_library_editor->open(); } MeshLibraryEditorPlugin::MeshLibraryEditorPlugin() { singleton = this; mesh_library_editor = memnew(MeshLibraryEditor); EditorDockManager::get_singleton()->add_dock(mesh_library_editor); mesh_library_editor->close(); }