From 4a461dff283e1d195db3cdfc97f2281a79086ca6 Mon Sep 17 00:00:00 2001 From: Juan Linietsky Date: Fri, 22 Dec 2023 12:29:45 +0100 Subject: [PATCH] Improved 3D Asset Import Dialog material editing Implements [7238](https://github.com/godotengine/godot-proposals/issues/7238) (for the most part). * Ability to select nodes, meshes or materials by clicking. * Moved mesh/material previews to the inspector area. * Ability to override materials and edit them directly in the inspector. * By default, shows overridden materials in the imported scene, with a button to disable them and show original materials. * Keeps compatibility with the old override format. --- core/math/triangle_mesh.cpp | 4 +- editor/editor_node.cpp | 2 +- editor/editor_resource_picker.cpp | 2 + editor/icons/MaterialSelect.svg | 4 + editor/icons/MaterialSelectDisabled.svg | 42 ++ editor/icons/MeshSelect.svg | 39 ++ editor/icons/NodeSelect.svg | 42 ++ editor/import/3d/resource_importer_scene.cpp | 15 +- editor/import/3d/scene_import_settings.cpp | 507 ++++++++++++++----- editor/import/3d/scene_import_settings.h | 60 ++- editor/plugins/script_editor_plugin.cpp | 2 +- editor/plugins/shader_editor_plugin.cpp | 1 + editor/plugins/shader_editor_plugin.h | 1 + editor/plugins/theme_editor_plugin.cpp | 2 +- scene/resources/mesh.cpp | 2 +- 15 files changed, 586 insertions(+), 139 deletions(-) create mode 100644 editor/icons/MaterialSelect.svg create mode 100644 editor/icons/MaterialSelectDisabled.svg create mode 100644 editor/icons/MeshSelect.svg create mode 100644 editor/icons/NodeSelect.svg diff --git a/core/math/triangle_mesh.cpp b/core/math/triangle_mesh.cpp index 0da1b8c7ad7a..8eb2e7fb057c 100644 --- a/core/math/triangle_mesh.cpp +++ b/core/math/triangle_mesh.cpp @@ -107,13 +107,13 @@ void TriangleMesh::get_indices(Vector *r_triangles_indices) const { void TriangleMesh::create(const Vector &p_faces, const Vector &p_surface_indices) { valid = false; - ERR_FAIL_COND(p_surface_indices.size() && p_surface_indices.size() != p_faces.size()); - int fc = p_faces.size(); ERR_FAIL_COND(!fc || ((fc % 3) != 0)); fc /= 3; triangles.resize(fc); + ERR_FAIL_COND(p_surface_indices.size() && p_surface_indices.size() != triangles.size()); + bvh.resize(fc * 3); //will never be larger than this (todo make better) BVH *bw = bvh.ptrw(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 24bfba3844a5..3d7d3931d16e 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1284,7 +1284,7 @@ void EditorNode::save_resource(const Ref &p_resource) { if (path.is_resource_file() && !FileAccess::exists(path + ".import")) { save_resource_in_path(p_resource, p_resource->get_path()); } else { - save_resource_as(p_resource); + save_resource_as(p_resource, ""); } } diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp index 27ce27591f2e..718f9c32380c 100644 --- a/editor/editor_resource_picker.cpp +++ b/editor/editor_resource_picker.cpp @@ -398,6 +398,7 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) { if (edited_resource.is_null()) { return; } + EditorNode::get_singleton()->save_resource(edited_resource); } break; @@ -405,6 +406,7 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) { if (edited_resource.is_null()) { return; } + EditorNode::get_singleton()->save_resource_as(edited_resource); } break; diff --git a/editor/icons/MaterialSelect.svg b/editor/icons/MaterialSelect.svg new file mode 100644 index 000000000000..9c3439d64786 --- /dev/null +++ b/editor/icons/MaterialSelect.svg @@ -0,0 +1,4 @@ + + + + diff --git a/editor/icons/MaterialSelectDisabled.svg b/editor/icons/MaterialSelectDisabled.svg new file mode 100644 index 000000000000..1ab058d3b31a --- /dev/null +++ b/editor/icons/MaterialSelectDisabled.svg @@ -0,0 +1,42 @@ + + + + + + diff --git a/editor/icons/MeshSelect.svg b/editor/icons/MeshSelect.svg new file mode 100644 index 000000000000..a7b7f6846eba --- /dev/null +++ b/editor/icons/MeshSelect.svg @@ -0,0 +1,39 @@ + + + + + + diff --git a/editor/icons/NodeSelect.svg b/editor/icons/NodeSelect.svg new file mode 100644 index 000000000000..94f137c6810d --- /dev/null +++ b/editor/icons/NodeSelect.svg @@ -0,0 +1,42 @@ + + + + + + diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index dfb14c6741d7..0d21624d4218 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -1192,6 +1192,7 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap< post_importer_plugins.write[j]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MATERIAL, p_root, p_node, mat, matdata); } + // Old style material, keep for compatibility. if (matdata.has("use_external/enabled") && bool(matdata["use_external/enabled"]) && matdata.has("use_external/path")) { String path = matdata["use_external/path"]; Ref external_mat = ResourceLoader::load(path); @@ -1199,6 +1200,13 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap< m->set_surface_material(i, external_mat); } } + // New style custom material + if (matdata.has("custom")) { + Ref external_mat = matdata["custom"]; + if (external_mat.is_valid()) { + m->set_surface_material(i, external_mat); + } + } } } } @@ -1708,8 +1716,11 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "lods/normal_merge_angle", PROPERTY_HINT_RANGE, "0,180,0.1,degrees"), 60.0f)); } break; case INTERNAL_IMPORT_CATEGORY_MATERIAL: { - r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "use_external/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); - r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "use_external/path", PROPERTY_HINT_FILE, "*.material,*.res,*.tres"), "")); + // Compatibility (hidden) settings. + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "use_external/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "use_external/path", PROPERTY_HINT_FILE, "*.material,*.res,*.tres", PROPERTY_USAGE_NONE), "")); + r_options->push_back(ImportOption(PropertyInfo(Variant::OBJECT, "custom", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), Ref())); + } break; case INTERNAL_IMPORT_CATEGORY_ANIMATION: { r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "settings/loop_mode", PROPERTY_HINT_ENUM, "None,Linear,Pingpong"), 0)); diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp index 736f941aa42f..a9b26a914197 100644 --- a/editor/import/3d/scene_import_settings.cpp +++ b/editor/import/3d/scene_import_settings.cpp @@ -37,9 +37,12 @@ #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/gui/editor_file_dialog.h" +#include "editor/plugins/material_editor_plugin.h" +#include "editor/plugins/mesh_editor_plugin.h" #include "editor/themes/editor_scale.h" #include "scene/3d/importer_mesh_instance_3d.h" #include "scene/animation/animation_player.h" +#include "scene/gui/separator.h" #include "scene/resources/importer_mesh.h" #include "scene/resources/surface_tool.h" @@ -70,6 +73,12 @@ class SceneImportSettingsData : public Object { SceneImportSettingsDialog::get_singleton()->request_generate_collider(); } + if (category == ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MATERIAL) { + if (p_name == SNAME("custom")) { + SceneImportSettingsDialog::get_singleton()->_update_mesh_materials(); + } + } + if (SceneImportSettingsDialog::get_singleton()->is_editing_animation()) { if (category == ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MAX) { if (ResourceImporterScene::get_animation_singleton()->get_option_visibility(path, p_name, current)) { @@ -139,7 +148,7 @@ class SceneImportSettingsData : public Object { } }; -void SceneImportSettingsDialog::_fill_material(Tree *p_tree, const Ref &p_material, TreeItem *p_parent) { +String SceneImportSettingsDialog::_fill_material(Tree *p_tree, const Ref &p_material, TreeItem *p_parent, const String &p_surface_name) { String import_id; bool has_import_id = false; @@ -169,15 +178,17 @@ void SceneImportSettingsDialog::_fill_material(Tree *p_tree, const Ref } MaterialData &material_data = material_map[import_id]; - ERR_FAIL_COND(p_material != material_data.material); + ERR_FAIL_COND_V(p_material != material_data.material, String()); Ref icon = get_editor_theme_icon(SNAME("StandardMaterial3D")); TreeItem *item = p_tree->create_item(p_parent); - if (p_material->get_name().is_empty()) { - item->set_text(0, TTR("")); - } else { + if (!p_material->get_name().is_empty()) { item->set_text(0, p_material->get_name()); + } else if (!p_surface_name.is_empty()) { + item->set_text(0, p_surface_name); + } else { + item->set_text(0, TTR("")); } item->set_icon(0, icon); @@ -185,6 +196,7 @@ void SceneImportSettingsDialog::_fill_material(Tree *p_tree, const Ref item->set_meta("import_id", import_id); item->set_tooltip_text(0, vformat(TTR("Import ID: %s"), import_id)); item->set_selectable(0, true); + material_data.name = item->get_text(0); if (p_tree == scene_tree) { material_data.scene_node = item; @@ -197,9 +209,11 @@ void SceneImportSettingsDialog::_fill_material(Tree *p_tree, const Ref if (created) { _fill_material(material_tree, p_material, material_tree->get_root()); } + + return import_id; } -void SceneImportSettingsDialog::_fill_mesh(Tree *p_tree, const Ref &p_mesh, TreeItem *p_parent) { +String SceneImportSettingsDialog::_fill_mesh(Tree *p_tree, const Ref &p_mesh, TreeItem *p_parent) { String import_id; bool has_import_id = false; @@ -213,6 +227,7 @@ void SceneImportSettingsDialog::_fill_mesh(Tree *p_tree, const Ref &p_mesh import_id = "@MESH:" + itos(mesh_set.size()); } + bool mesh_created = false; if (!mesh_map.has(import_id)) { MeshData md; md.has_import_id = has_import_id; @@ -221,6 +236,7 @@ void SceneImportSettingsDialog::_fill_mesh(Tree *p_tree, const Ref &p_mesh _load_default_subresource_settings(md.settings, "meshes", import_id, ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MESH); mesh_map[import_id] = md; + mesh_created = true; } MeshData &mesh_data = mesh_map[import_id]; @@ -229,6 +245,7 @@ void SceneImportSettingsDialog::_fill_mesh(Tree *p_tree, const Ref &p_mesh TreeItem *item = p_tree->create_item(p_parent); item->set_text(0, p_mesh->get_name()); + mesh_data.name = p_mesh->get_name(); item->set_icon(0, icon); bool created = false; @@ -253,14 +270,20 @@ void SceneImportSettingsDialog::_fill_mesh(Tree *p_tree, const Ref &p_mesh for (int i = 0; i < p_mesh->get_surface_count(); i++) { Ref mat = p_mesh->surface_get_material(i); + String id; if (mat.is_valid()) { - _fill_material(p_tree, mat, item); + id = _fill_material(p_tree, mat, item, mesh_data.name + ", Surface " + itos(i + 1)); + } + if (mesh_created) { + mesh_data.material_surface_ids.push_back(id); } } if (created) { _fill_mesh(mesh_tree, p_mesh, mesh_tree->get_root()); } + + return import_id; } void SceneImportSettingsDialog::_fill_animation(Tree *p_tree, const Ref &p_anim, const String &p_name, TreeItem *p_parent) { @@ -292,13 +315,6 @@ void SceneImportSettingsDialog::_fill_animation(Tree *p_tree, const Refhas_meta("import_id")) { - import_id = p_node->get_meta("import_id"); - } else { - import_id = "PATH:" + String(scene->get_path_to(p_node)); - p_node->set_meta("import_id", import_id); - } - ImporterMeshInstance3D *src_mesh_node = Object::cast_to(p_node); if (src_mesh_node) { @@ -317,6 +333,13 @@ void SceneImportSettingsDialog::_fill_scene(Node *p_node, TreeItem *p_parent_ite p_node = mesh_node; } + if (p_node->has_meta("import_id")) { + import_id = p_node->get_meta("import_id"); + } else { + import_id = "PATH:" + String(scene->get_path_to(p_node)); + p_node->set_meta("import_id", import_id); + } + String type = p_node->get_class(); if (!has_theme_icon(type, EditorStringName(EditorIcons))) { @@ -386,7 +409,7 @@ void SceneImportSettingsDialog::_fill_scene(Node *p_node, TreeItem *p_parent_ite MeshInstance3D *mesh_node = Object::cast_to(p_node); if (mesh_node && mesh_node->get_mesh().is_valid()) { if (!editing_animation) { - _fill_mesh(scene_tree, mesh_node->get_mesh(), item); + node_data.mesh_id = _fill_mesh(scene_tree, mesh_node->get_mesh(), item); } // Add the collider view. @@ -423,6 +446,7 @@ void SceneImportSettingsDialog::_update_scene() { mesh_tree->create_item(); _fill_scene(scene, nullptr); + _update_mesh_materials(); } void SceneImportSettingsDialog::_update_view_gizmos() { @@ -523,26 +547,7 @@ void SceneImportSettingsDialog::_update_camera() { float rot_y = cam_rot_y; float zoom = cam_zoom; - if (selected_type == "Node" || selected_type.is_empty()) { - camera_aabb = contents_aabb; - } else { - if (mesh_preview->get_mesh().is_valid()) { - camera_aabb = mesh_preview->get_transform().xform(mesh_preview->get_mesh()->get_aabb()); - } else { - camera_aabb = AABB(Vector3(-1, -1, -1), Vector3(2, 2, 2)); - } - if (selected_type == "Mesh" && mesh_map.has(selected_id)) { - const MeshData &md = mesh_map[selected_id]; - rot_x = md.cam_rot_x; - rot_y = md.cam_rot_y; - zoom = md.cam_zoom; - } else if (selected_type == "Material" && material_map.has(selected_id)) { - const MaterialData &md = material_map[selected_id]; - rot_x = md.cam_rot_x; - rot_y = md.cam_rot_y; - zoom = md.cam_zoom; - } - } + camera_aabb = contents_aabb; Vector3 center = camera_aabb.get_center(); float camera_size = camera_aabb.get_longest_axis_size(); @@ -568,6 +573,22 @@ void SceneImportSettingsDialog::_load_default_subresource_settings(HashMapget_internal_import_options(p_category, &options); } + + if (p_category == ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MATERIAL) { + if (d.has("use_external/enabled")) { // Old file, move to new format. + bool use_external = d["use_external/enabled"]; + if (use_external && d.has("use_external/path")) { + String path = d["use_external/path"]; + Ref material = ResourceLoader::load(path, "Material"); + if (material.is_valid()) { + d["custom"] = material; + d.erase("use_external/enabled"); + d.erase("use_external/path"); + } + } + } + } + for (const ResourceImporterScene::ImportOption &E : options) { String key = E.option.name; if (d.has(key)) { @@ -603,8 +624,8 @@ void SceneImportSettingsDialog::open_settings(const String &p_path, bool p_for_a data_mode->set_current_tab(0); } - action_menu->get_popup()->set_item_disabled(action_menu->get_popup()->get_item_id(ACTION_EXTRACT_MATERIALS), p_for_animation); - action_menu->get_popup()->set_item_disabled(action_menu->get_popup()->get_item_id(ACTION_CHOOSE_MESH_SAVE_PATHS), p_for_animation); + batch_menu->get_popup()->set_item_disabled(batch_menu->get_popup()->get_item_id(ACTION_EXTRACT_MATERIALS), p_for_animation); + batch_menu->get_popup()->set_item_disabled(batch_menu->get_popup()->get_item_id(ACTION_CHOOSE_MESH_SAVE_PATHS), p_for_animation); base_path = p_path; @@ -616,8 +637,6 @@ void SceneImportSettingsDialog::open_settings(const String &p_path, bool p_for_a node_map.clear(); defaults.clear(); - mesh_preview->hide(); - selected_id = ""; selected_type = ""; @@ -655,7 +674,7 @@ void SceneImportSettingsDialog::open_settings(const String &p_path, bool p_for_a _update_scene(); - base_viewport->add_child(scene); + scene_viewport->add_child(scene); inspector->edit(nullptr); @@ -664,6 +683,7 @@ void SceneImportSettingsDialog::open_settings(const String &p_path, bool p_for_a first_aabb = false; } + set_exclusive(false); popup_centered_ratio(); _update_view_gizmos(); _update_camera(); @@ -696,13 +716,9 @@ void SceneImportSettingsDialog::_select(Tree *p_from, String p_type, String p_id scene_import_settings_data->hide_options = false; if (p_type == "Node") { - node_selected->hide(); // Always hide just in case. - mesh_preview->hide(); + mesh_editor->hide(); + material_editor->hide(); _reset_animation(); - - if (Object::cast_to(scene)) { - Object::cast_to(scene)->show(); - } material_tree->deselect_all(); mesh_tree->deselect_all(); NodeData &nd = node_map[p_id]; @@ -741,12 +757,10 @@ void SceneImportSettingsDialog::_select(Tree *p_from, String p_type, String p_id } } else if (p_type == "Animation") { node_selected->hide(); // Always hide just in case. - mesh_preview->hide(); + mesh_editor->hide(); + material_editor->hide(); _reset_animation(p_id); - if (Object::cast_to(scene)) { - Object::cast_to(scene)->show(); - } material_tree->deselect_all(); mesh_tree->deselect_all(); AnimationData &ad = animation_map[p_id]; @@ -755,9 +769,8 @@ void SceneImportSettingsDialog::_select(Tree *p_from, String p_type, String p_id scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_ANIMATION; } else if (p_type == "Mesh") { node_selected->hide(); - if (Object::cast_to(scene)) { - Object::cast_to(scene)->hide(); - } + material_editor->hide(); + mesh_editor->show(); MeshData &md = mesh_map[p_id]; if (md.mesh_node != nullptr) { @@ -773,8 +786,7 @@ void SceneImportSettingsDialog::_select(Tree *p_from, String p_type, String p_id } } - mesh_preview->set_mesh(md.mesh); - mesh_preview->show(); + mesh_editor->edit(md.mesh); _reset_animation(); material_tree->deselect_all(); @@ -783,17 +795,13 @@ void SceneImportSettingsDialog::_select(Tree *p_from, String p_type, String p_id scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MESH; } else if (p_type == "Material") { node_selected->hide(); - if (Object::cast_to(scene)) { - Object::cast_to(scene)->hide(); - } - - mesh_preview->show(); + material_editor->show(); + mesh_editor->hide(); _reset_animation(); MaterialData &md = material_map[p_id]; - material_preview->set_material(md.material); - mesh_preview->set_mesh(material_preview); + material_editor->edit(md.material, Ref()); if (p_from != mesh_tree) { md.mesh_node->uncollapse_tree(); @@ -1023,47 +1031,177 @@ void SceneImportSettingsDialog::_cleanup() { set_process(false); } +void SceneImportSettingsDialog::_popup_menu_pressed(int p_id) { + String id = pick_menu->get_item_metadata(p_id); + _select(nullptr, pick_list_type, id); +} + void SceneImportSettingsDialog::_viewport_input(const Ref &p_input) { - float *rot_x = &cam_rot_x; - float *rot_y = &cam_rot_y; - float *zoom = &cam_zoom; - - if (selected_type == "Mesh" && mesh_map.has(selected_id)) { - MeshData &md = mesh_map[selected_id]; - rot_x = &md.cam_rot_x; - rot_y = &md.cam_rot_y; - zoom = &md.cam_zoom; - } else if (selected_type == "Material" && material_map.has(selected_id)) { - MaterialData &md = material_map[selected_id]; - rot_x = &md.cam_rot_x; - rot_y = &md.cam_rot_y; - zoom = &md.cam_zoom; - } - Ref mm = p_input; - if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { - (*rot_x) -= mm->get_relative().y * 0.01 * EDSCALE; - (*rot_y) -= mm->get_relative().x * 0.01 * EDSCALE; - (*rot_x) = CLAMP((*rot_x), -Math_PI / 2, Math_PI / 2); - _update_camera(); - } - if (mm.is_valid() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE)) { - DisplayServer::get_singleton()->cursor_set_shape(DisplayServer::CursorShape::CURSOR_ARROW); - } Ref mb = p_input; + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { + if (mb->is_pressed()) { + dragging = false; + } else if (!dragging) { + // Okay select something + + Vector3 ray_from = camera->project_ray_origin(mb->get_position()); + Vector3 ray_dir = camera->project_ray_normal(mb->get_position()); + + Vector instances = RenderingServer::get_singleton()->instances_cull_ray(ray_from, ray_from + ray_dir * 10000, scene_viewport->find_world_3d()->get_scenario()); + + if (instances.is_empty()) { + return; + } + + Vector mesh_instances; + for (int i = 0; i < instances.size(); i++) { + MeshInstance3D *mi = Object::cast_to(ObjectDB::get_instance(instances[i])); + if (mi) { + if (!mi->has_meta("import_id")) { + continue; // Not an imported mesh, ignore. + } + Ref mesh = mi->get_mesh(); + if (mesh.is_valid()) { + Ref tm = mesh->generate_triangle_mesh(); + + Vector3 local_pos = mi->get_global_transform().affine_inverse().xform(ray_from); + Vector3 local_dir = mi->get_global_transform().affine_inverse().basis.xform(ray_dir).normalized(); + int surf_index; + Vector3 p; + Vector3 n; + if (tm->intersect_ray(local_pos, local_dir, p, n, &surf_index)) { + MeshInstanceSort misort; + misort.mesh_instance = mi; + misort.surface_index = surf_index; + misort.point_d = ray_dir.dot(p); + mesh_instances.push_back(misort); + } + } + } + } + + if (mesh_instances.is_empty()) { + return; + } + + mesh_instances.sort(); + + Vector pick_list; + Vector pick_names; + + if (tool_buttons[TOOL_SELECT_OBJECT]->is_pressed()) { + pick_list_type = "Node"; + for (int i = 0; i < mesh_instances.size(); i++) { + if (!mesh_instances[i].mesh_instance->has_meta("import_id")) { + continue; + } + String import_id = mesh_instances[i].mesh_instance->get_meta("import_id"); + if (!node_map.has(import_id)) { + continue; + } + pick_list.push_back(import_id); + pick_names.push_back(mesh_instances[i].mesh_instance->get_name()); + } + } else if (tool_buttons[TOOL_SELECT_MESH]->is_pressed()) { + pick_list_type = "Mesh"; + for (int i = 0; i < mesh_instances.size(); i++) { + if (!mesh_instances[i].mesh_instance->has_meta("import_id")) { + continue; + } + + String import_id = mesh_instances[i].mesh_instance->get_meta("import_id"); + if (!node_map.has(import_id)) { + continue; + } + + NodeData &nd = node_map[import_id]; + if (!mesh_map.has(nd.mesh_id)) { + continue; + } + + pick_list.push_back(nd.mesh_id); + pick_names.push_back(mesh_map[nd.mesh_id].name); + } + + } else if (tool_buttons[TOOL_SELECT_MATERIAL]->is_pressed()) { + pick_list_type = "Material"; + for (int i = 0; i < mesh_instances.size(); i++) { + if (!mesh_instances[i].mesh_instance->has_meta("import_id")) { + continue; + } + + String import_id = mesh_instances[i].mesh_instance->get_meta("import_id"); + if (!node_map.has(import_id)) { + continue; + } + + NodeData &nd = node_map[import_id]; + if (!mesh_map.has(nd.mesh_id)) { + continue; + } + + if (mesh_instances[i].surface_index < 0 || mesh_instances[i].surface_index >= mesh_map[nd.mesh_id].material_surface_ids.size()) { + continue; + } + + String material_id = mesh_map[nd.mesh_id].material_surface_ids[mesh_instances[i].surface_index]; + if (!material_map.has(material_id)) { + continue; + } + pick_list.push_back(material_id); + pick_names.push_back(material_map[material_id].name); + } + } + + if (pick_list.is_empty()) { + return; + } + + if (tool_buttons[TOOL_SELECT]->is_pressed()) { + _select(nullptr, pick_list_type, pick_list[0]); + } else { + pick_menu->clear(); + for (int i = 0; i < pick_names.size(); i++) { + pick_menu->add_item(pick_names[i], i); + pick_menu->set_item_metadata(i, pick_list[i]); + } + + Vector2 pos = vp_container->get_screen_rect().position; + pos += mb->get_position(); + pick_menu->set_size(Vector2(0, 0)); + pick_menu->set_position(pos); + pick_menu->popup(); + } + } + } + if (mb.is_valid() && mb->get_button_index() == MouseButton::WHEEL_DOWN) { - (*zoom) *= 1.1; - if ((*zoom) > 10.0) { - (*zoom) = 10.0; + cam_zoom *= 1.1; + if (cam_zoom > 10.0) { + cam_zoom = 10.0; } _update_camera(); } + if (mb.is_valid() && mb->get_button_index() == MouseButton::WHEEL_UP) { - (*zoom) /= 1.1; - if ((*zoom) < 0.1) { - (*zoom) = 0.1; + cam_zoom /= 1.1; + if (cam_zoom < 0.1) { + cam_zoom = 0.1; } _update_camera(); } + + Ref mm = p_input; + if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { + cam_rot_x -= mm->get_relative().y * 0.01 * EDSCALE; + cam_rot_y -= mm->get_relative().x * 0.01 * EDSCALE; + cam_rot_x = CLAMP((cam_rot_x), -Math_PI / 2, Math_PI / 2); + _update_camera(); + dragging = true; + } + if (mm.is_valid() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE)) { + DisplayServer::get_singleton()->cursor_set_shape(DisplayServer::CursorShape::CURSOR_ARROW); + } } void SceneImportSettingsDialog::_re_import() { @@ -1144,11 +1282,11 @@ void SceneImportSettingsDialog::_notification(int p_what) { } break; case NOTIFICATION_THEME_CHANGED: { - action_menu->begin_bulk_theme_override(); - action_menu->add_theme_style_override("normal", get_theme_stylebox("normal", "Button")); - action_menu->add_theme_style_override("hover", get_theme_stylebox("hover", "Button")); - action_menu->add_theme_style_override("pressed", get_theme_stylebox("pressed", "Button")); - action_menu->end_bulk_theme_override(); + batch_menu->begin_bulk_theme_override(); + batch_menu->add_theme_style_override("normal", get_theme_stylebox("normal", "Button")); + batch_menu->add_theme_style_override("hover", get_theme_stylebox("hover", "Button")); + batch_menu->add_theme_style_override("pressed", get_theme_stylebox("pressed", "Button")); + batch_menu->end_bulk_theme_override(); if (animation_player != nullptr && animation_player->is_playing()) { animation_play_button->set_icon(get_editor_theme_icon(SNAME("Pause"))); @@ -1156,6 +1294,31 @@ void SceneImportSettingsDialog::_notification(int p_what) { animation_play_button->set_icon(get_editor_theme_icon(SNAME("MainPlay"))); } animation_stop_button->set_icon(get_editor_theme_icon(SNAME("Stop"))); + + for (int i = 0; i < TOOL_MAX; i++) { + static const char *icons[TOOL_MAX] = { + "ToolSelect", + "ListSelect", + "NodeSelect", + "MeshSelect", + "MaterialSelect", + "StandardMaterial3D", + }; + tool_buttons[i]->set_icon(get_editor_theme_icon(icons[i])); + } + + tool_buttons[TOOL_PREVIEW_MATERIALS]->add_theme_color_override("icon_normal_color", Color(1, 1, 1, 0.5)); + tool_buttons[TOOL_PREVIEW_MATERIALS]->add_theme_color_override("icon_pressed_color", Color(1, 1, 1, 1.0)); + + Ref ls; + ls.instantiate(); + // Needs to be high contrast over checkered background, not depend on theme + ls->set_font_color(Color(1, 1, 1)); + ls->set_font(get_theme_font("bold", EditorStringName(EditorFonts))); + ls->set_outline_color(Color(0, 0, 0)); + ls->set_outline_size(4); + me_label->set_label_settings(ls); + } break; case NOTIFICATION_PROCESS: { @@ -1441,6 +1604,58 @@ void SceneImportSettingsDialog::_save_dir_confirm() { } } +void SceneImportSettingsDialog::_tool_pressed(int p_tool) { + switch (p_tool) { + case TOOL_SELECT: { + tool_buttons[TOOL_SELECT]->set_pressed(true); + tool_buttons[TOOL_SELECT_LIST]->set_pressed(false); + } break; + case TOOL_SELECT_LIST: { + tool_buttons[TOOL_SELECT]->set_pressed(false); + tool_buttons[TOOL_SELECT_LIST]->set_pressed(true); + } break; + case TOOL_SELECT_OBJECT: { + tool_buttons[TOOL_SELECT_OBJECT]->set_pressed(true); + tool_buttons[TOOL_SELECT_MESH]->set_pressed(false); + tool_buttons[TOOL_SELECT_MATERIAL]->set_pressed(false); + } break; + case TOOL_SELECT_MESH: { + tool_buttons[TOOL_SELECT_OBJECT]->set_pressed(false); + tool_buttons[TOOL_SELECT_MESH]->set_pressed(true); + tool_buttons[TOOL_SELECT_MATERIAL]->set_pressed(false); + } break; + case TOOL_SELECT_MATERIAL: { + tool_buttons[TOOL_SELECT_OBJECT]->set_pressed(false); + tool_buttons[TOOL_SELECT_MESH]->set_pressed(false); + tool_buttons[TOOL_SELECT_MATERIAL]->set_pressed(true); + } break; + case TOOL_PREVIEW_MATERIALS: { + _update_mesh_materials(); + } break; + } +} + +void SceneImportSettingsDialog::_update_mesh_materials() { + for (KeyValue &K : mesh_map) { + for (int i = 0; i < K.value.material_surface_ids.size(); i++) { + String mid = K.value.material_surface_ids[i]; + Ref mat; + if (material_map.has(mid)) { + MaterialData &material = material_map[mid]; + mat = material.material; + if (tool_buttons[TOOL_PREVIEW_MATERIALS]->is_pressed() && material.settings.has("custom")) { + Ref custom = material.settings["custom"]; + if (custom.is_valid()) { + mat = custom; + } + } + } + + K.value.mesh->surface_set_material(i, mat); + } + } +} + SceneImportSettingsDialog::SceneImportSettingsDialog() { singleton = this; @@ -1449,19 +1664,47 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { HBoxContainer *menu_hb = memnew(HBoxContainer); main_vb->add_child(menu_hb); - action_menu = memnew(MenuButton); - action_menu->set_text(TTR("Actions...")); + String tool_tooltips[TOOL_MAX] = { + TTR("Select an object/mesh/material by clicking with the right mouse button."), + TTR("Show the list of objects/meshes/materiales at the mouse position by clicking with the right mouse button."), + TTR("Click to select nodes."), + TTR("Click to select meshes."), + TTR("Click to select materials."), + TTR("Show customized materiales (if pressed), or otherwise the original materials that came with the asset."), + }; + + for (int i = 0; i < TOOL_MAX; i++) { + if (i == TOOL_SELECT_OBJECT || i == TOOL_PREVIEW_MATERIALS) { + menu_hb->add_child(memnew(VSeparator)); + } + tool_buttons[i] = memnew(Button); + tool_buttons[i]->set_flat(true); + tool_buttons[i]->set_toggle_mode(true); + tool_buttons[i]->connect("pressed", callable_mp(this, &SceneImportSettingsDialog::_tool_pressed).bind(i)); + tool_buttons[i]->set_focus_mode(Control::FOCUS_NONE); + tool_buttons[i]->set_tooltip_text(tool_tooltips[i]); + menu_hb->add_child(tool_buttons[i]); + } + + tool_buttons[TOOL_SELECT]->set_pressed(true); + tool_buttons[TOOL_SELECT_OBJECT]->set_pressed(true); + tool_buttons[TOOL_PREVIEW_MATERIALS]->set_pressed(true); + + menu_hb->add_child(memnew(VSeparator)); + batch_menu = memnew(MenuButton); + batch_menu->set_text(TTR("Batch...")); + batch_menu->set_tooltip_text(TTR("Batch operations for all nodes/meshes/materials/animations/etc.")); // Style the MenuButton like a regular Button to make it more noticeable. - action_menu->set_flat(false); - action_menu->set_focus_mode(Control::FOCUS_ALL); - menu_hb->add_child(action_menu); + batch_menu->set_flat(false); + batch_menu->set_focus_mode(Control::FOCUS_ALL); + menu_hb->add_child(batch_menu); - action_menu->get_popup()->add_item(TTR("Extract Materials"), ACTION_EXTRACT_MATERIALS); - action_menu->get_popup()->add_separator(); - action_menu->get_popup()->add_item(TTR("Set Animation Save Paths"), ACTION_CHOOSE_ANIMATION_SAVE_PATHS); - action_menu->get_popup()->add_item(TTR("Set Mesh Save Paths"), ACTION_CHOOSE_MESH_SAVE_PATHS); + batch_menu->get_popup()->add_item(TTR("Extract Materials"), ACTION_EXTRACT_MATERIALS); + batch_menu->get_popup()->add_separator(); + batch_menu->get_popup()->add_item(TTR("Set Animation Save Paths"), ACTION_CHOOSE_ANIMATION_SAVE_PATHS); + batch_menu->get_popup()->add_item(TTR("Set Mesh Save Paths"), ACTION_CHOOSE_MESH_SAVE_PATHS); - action_menu->get_popup()->connect("id_pressed", callable_mp(this, &SceneImportSettingsDialog::_menu_callback)); + batch_menu->get_popup()->connect("id_pressed", callable_mp(this, &SceneImportSettingsDialog::_menu_callback)); tree_split = memnew(HSplitContainer); main_vb->add_child(tree_split); @@ -1471,6 +1714,7 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { tree_split->add_child(data_mode); data_mode->set_custom_minimum_size(Size2(300 * EDSCALE, 0)); data_mode->set_theme_type_variation("TabContainerOdd"); + data_mode->connect("tab_changed", callable_mp(this, &SceneImportSettingsDialog::_update_camera)); property_split = memnew(HSplitContainer); tree_split->add_child(property_split); @@ -1500,15 +1744,15 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { vp_vb->set_anchors_and_offsets_preset(Control::LayoutPreset::PRESET_FULL_RECT); property_split->add_child(vp_vb); - SubViewportContainer *vp_container = memnew(SubViewportContainer); + vp_container = memnew(SubViewportContainer); vp_container->set_v_size_flags(Control::SIZE_EXPAND_FILL); vp_container->set_custom_minimum_size(Size2(10, 10)); vp_container->set_stretch(true); vp_container->connect("gui_input", callable_mp(this, &SceneImportSettingsDialog::_viewport_input)); vp_vb->add_child(vp_container); - base_viewport = memnew(SubViewport); - vp_container->add_child(base_viewport); + scene_viewport = memnew(SubViewport); + vp_container->add_child(scene_viewport); animation_preview = memnew(PanelContainer); animation_preview->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -1541,10 +1785,10 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { animation_slider->set_focus_mode(Control::FOCUS_NONE); animation_slider->connect(SNAME("value_changed"), callable_mp(this, &SceneImportSettingsDialog::_animation_slider_value_changed)); - base_viewport->set_use_own_world_3d(true); + scene_viewport->set_use_own_world_3d(true); camera = memnew(Camera3D); - base_viewport->add_child(camera); + scene_viewport->add_child(camera); camera->make_current(); if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) { @@ -1554,7 +1798,7 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { light = memnew(DirectionalLight3D); light->set_transform(Transform3D().looking_at(Vector3(-1, -2, -0.6), Vector3(0, 1, 0))); - base_viewport->add_child(light); + scene_viewport->add_child(light); light->set_shadow(true); { @@ -1588,18 +1832,10 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { node_selected = memnew(MeshInstance3D); node_selected->set_mesh(selection_mesh); node_selected->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF); - base_viewport->add_child(node_selected); + scene_viewport->add_child(node_selected); node_selected->hide(); } - { - mesh_preview = memnew(MeshInstance3D); - base_viewport->add_child(mesh_preview); - mesh_preview->hide(); - - material_preview.instantiate(); - } - { collider_mat.instantiate(); collider_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); @@ -1607,11 +1843,30 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { collider_mat->set_albedo(Color(0.5, 0.5, 1.0)); } + VSplitContainer *inspector_vb = memnew(VSplitContainer); + property_split->add_child(inspector_vb); + + mesh_editor = memnew(MeshEditor); + inspector_vb->add_child(mesh_editor); + mesh_editor->hide(); + + material_editor = memnew(MaterialEditor); + inspector_vb->add_child(material_editor); + + me_label = memnew(Label); + me_label->set_text(TTR("Original")); + material_editor->add_child(me_label); + me_label->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + me_label->set_vertical_alignment(VERTICAL_ALIGNMENT_BOTTOM); + me_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT); + material_editor->hide(); + inspector = memnew(EditorInspector); inspector->set_custom_minimum_size(Size2(300 * EDSCALE, 0)); inspector->connect(SNAME("property_edited"), callable_mp(this, &SceneImportSettingsDialog::_inspector_property_edited)); + inspector->set_v_size_flags(Control::SIZE_EXPAND_FILL); - property_split->add_child(inspector); + inspector_vb->add_child(inspector); scene_import_settings_data = memnew(SceneImportSettingsData); @@ -1661,6 +1916,10 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { update_view_timer->set_wait_time(0.2); update_view_timer->connect("timeout", callable_mp(this, &SceneImportSettingsDialog::_update_view_gizmos)); add_child(update_view_timer); + + pick_menu = memnew(PopupMenu); + add_child(pick_menu); + pick_menu->connect("id_pressed", callable_mp(this, &SceneImportSettingsDialog::_popup_menu_pressed)); } SceneImportSettingsDialog::~SceneImportSettingsDialog() { diff --git a/editor/import/3d/scene_import_settings.h b/editor/import/3d/scene_import_settings.h index c6c416daba97..bd45551fbf2f 100644 --- a/editor/import/3d/scene_import_settings.h +++ b/editor/import/3d/scene_import_settings.h @@ -51,6 +51,8 @@ class EditorFileDialog; class EditorInspector; class SceneImportSettingsData; +class MeshEditor; +class MaterialEditor; class SceneImportSettingsDialog : public ConfirmationDialog { GDCLASS(SceneImportSettingsDialog, ConfirmationDialog) @@ -63,6 +65,12 @@ class SceneImportSettingsDialog : public ConfirmationDialog { ACTION_CHOOSE_ANIMATION_SAVE_PATHS, }; + enum TabType { + TAB_NODES, + TAB_MESHES, + TAB_MATERIALS + }; + Node *scene = nullptr; HSplitContainer *tree_split = nullptr; @@ -74,7 +82,25 @@ class SceneImportSettingsDialog : public ConfirmationDialog { EditorInspector *inspector = nullptr; - SubViewport *base_viewport = nullptr; + SubViewportContainer *vp_container = nullptr; + SubViewport *scene_viewport = nullptr; + + struct MeshInstanceSort { + MeshInstance3D *mesh_instance; + float point_d; + int surface_index; + bool operator<(const MeshInstanceSort &b) const { + return point_d < b.point_d; + } + }; + + String pick_list_type; + + PopupMenu *pick_menu = nullptr; + + MeshEditor *mesh_editor = nullptr; + MaterialEditor *material_editor = nullptr; + Label *me_label = nullptr; Camera3D *camera = nullptr; Ref camera_attributes; @@ -85,9 +111,6 @@ class SceneImportSettingsDialog : public ConfirmationDialog { Ref selection_mesh; MeshInstance3D *node_selected = nullptr; - MeshInstance3D *mesh_preview = nullptr; - Ref material_preview; - AnimationPlayer *animation_player = nullptr; List skeletons; PanelContainer *animation_preview = nullptr; @@ -116,8 +139,11 @@ class SceneImportSettingsDialog : public ConfirmationDialog { float cam_rot_y = -Math_PI / 4; float cam_zoom = 1; + String name; + HashMap settings; }; + HashMap material_map; HashMap, String> unnamed_material_name_map; @@ -130,6 +156,8 @@ class SceneImportSettingsDialog : public ConfirmationDialog { float cam_rot_x = -Math_PI / 4; float cam_rot_y = -Math_PI / 4; float cam_zoom = 1; + String name; + Vector material_surface_ids; HashMap settings; }; HashMap mesh_map; @@ -144,12 +172,13 @@ class SceneImportSettingsDialog : public ConfirmationDialog { struct NodeData { Node *node = nullptr; TreeItem *scene_node = nullptr; + String mesh_id; HashMap settings; }; HashMap node_map; - void _fill_material(Tree *p_tree, const Ref &p_material, TreeItem *p_parent); - void _fill_mesh(Tree *p_tree, const Ref &p_mesh, TreeItem *p_parent); + String _fill_material(Tree *p_tree, const Ref &p_material, TreeItem *p_parent, const String &p_surface_name = ""); + String _fill_mesh(Tree *p_tree, const Ref &p_mesh, TreeItem *p_parent); void _fill_animation(Tree *p_tree, const Ref &p_anim, const String &p_name, TreeItem *p_parent); void _fill_scene(Node *p_node, TreeItem *p_parent_item); @@ -185,7 +214,20 @@ class SceneImportSettingsDialog : public ConfirmationDialog { String base_path; - MenuButton *action_menu = nullptr; + enum Tool { + TOOL_SELECT, + TOOL_SELECT_LIST, + TOOL_SELECT_OBJECT, + TOOL_SELECT_MESH, + TOOL_SELECT_MATERIAL, + TOOL_PREVIEW_MATERIALS, + TOOL_MAX + }; + + Button *tool_buttons[TOOL_MAX] = {}; + void _tool_pressed(int p_tool); + + MenuButton *batch_menu = nullptr; ConfirmationDialog *external_paths = nullptr; Tree *external_path_tree = nullptr; @@ -198,6 +240,7 @@ class SceneImportSettingsDialog : public ConfirmationDialog { void _save_dir_callback(const String &p_path); int current_action = 0; + bool dragging = false; Vector save_path_items; @@ -209,11 +252,14 @@ class SceneImportSettingsDialog : public ConfirmationDialog { Dictionary base_subresource_settings; void _load_default_subresource_settings(HashMap &settings, const String &p_type, const String &p_import_id, ResourceImporterScene::InternalImportCategory p_category); + void _popup_menu_pressed(int p_id); bool editing_animation = false; bool generate_collider = false; Timer *update_view_timer = nullptr; + void _update_mesh_materials(); + friend class SceneImportSettingsData; protected: void _notification(int p_what); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 55191f44d40c..cb0fd0b105ae 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -1367,7 +1367,7 @@ void ScriptEditor::_menu_option(int p_option) { } EditorNode::get_singleton()->push_item(resource.ptr()); - EditorNode::get_singleton()->save_resource_as(resource); + EditorNode::get_singleton()->save_resource_as(resource, ""); if (scr.is_valid()) { update_docs_from_script(scr); diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 5798ff9d99ba..c5a872867159 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -35,6 +35,7 @@ #include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" #include "editor/filesystem_dock.h" +#include "editor/gui/editor_file_dialog.h" #include "editor/inspector_dock.h" #include "editor/plugins/text_shader_editor.h" #include "editor/plugins/visual_shader_editor_plugin.h" diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h index ea50d62b8f21..30f61b450a3a 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -41,6 +41,7 @@ class TabContainer; class TextShaderEditor; class VisualShaderEditor; class WindowWrapper; +class EditorFileDialog; #ifdef MINGW_ENABLED #undef FILE_OPEN diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 8c3fe82f36f4..e12a15cb37cd 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -3535,7 +3535,7 @@ void ThemeEditor::_theme_save_button_cbk(bool p_save_as) { ERR_FAIL_COND_MSG(theme.is_null(), "Invalid state of the Theme Editor; the Theme resource is missing."); if (p_save_as) { - EditorNode::get_singleton()->save_resource_as(theme); + EditorNode::get_singleton()->save_resource_as(theme, ""); } else { EditorNode::get_singleton()->save_resource(theme); } diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index 0abf87865999..dbaaff355d54 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -384,7 +384,7 @@ Ref Mesh::generate_triangle_mesh() const { } triangle_mesh = Ref(memnew(TriangleMesh)); - triangle_mesh->create(faces); + triangle_mesh->create(faces, surface_indices); return triangle_mesh; }