/*************************************************************************/ /* editor_mesh_import_plugin.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "editor_mesh_import_plugin.h" #include "editor/editor_dir_dialog.h" #include "editor/editor_file_dialog.h" #include "editor/editor_node.h" #include "editor/property_editor.h" #include "io/marshalls.h" #include "io/resource_saver.h" #include "os/file_access.h" #include "scene/resources/sample.h" #include "scene/resources/surface_tool.h" class _EditorMeshImportOptions : public Object { OBJ_TYPE(_EditorMeshImportOptions, Object); public: bool generate_tangents; bool generate_normals; bool flip_faces; bool smooth_shading; bool weld_vertices; bool import_material; bool import_textures; float weld_tolerance; bool _set(const StringName &p_name, const Variant &p_value) { String n = p_name; if (n == "generate/tangents") generate_tangents = p_value; else if (n == "generate/normals") generate_normals = p_value; else if (n == "import/materials") import_material = p_value; else if (n == "import/textures") import_textures = p_value; else if (n == "force/flip_faces") flip_faces = p_value; else if (n == "force/smooth_shading") smooth_shading = p_value; else if (n == "force/weld_vertices") weld_vertices = p_value; else if (n == "force/weld_tolerance") weld_tolerance = p_value; else return false; return true; } bool _get(const StringName &p_name, Variant &r_ret) const { String n = p_name; if (n == "generate/tangents") r_ret = generate_tangents; else if (n == "generate/normals") r_ret = generate_normals; else if (n == "import/materials") r_ret = import_material; else if (n == "import/textures") r_ret = import_textures; else if (n == "force/flip_faces") r_ret = flip_faces; else if (n == "force/smooth_shading") r_ret = smooth_shading; else if (n == "force/weld_vertices") r_ret = weld_vertices; else if (n == "force/weld_tolerance") r_ret = weld_tolerance; else return false; return true; } void _get_property_list(List *p_list) const { p_list->push_back(PropertyInfo(Variant::BOOL, "generate/tangents")); p_list->push_back(PropertyInfo(Variant::BOOL, "generate/normals")); //not for nowp //p_list->push_back(PropertyInfo(Variant::BOOL,"import/materials")); //p_list->push_back(PropertyInfo(Variant::BOOL,"import/textures")); p_list->push_back(PropertyInfo(Variant::BOOL, "force/flip_faces")); p_list->push_back(PropertyInfo(Variant::BOOL, "force/smooth_shading")); p_list->push_back(PropertyInfo(Variant::BOOL, "force/weld_vertices")); p_list->push_back(PropertyInfo(Variant::REAL, "force/weld_tolerance", PROPERTY_HINT_RANGE, "0.00001,16,0.00001")); //p_list->push_back(PropertyInfo(Variant::BOOL,"compress/enable")); //p_list->push_back(PropertyInfo(Variant::INT,"compress/bitrate",PROPERTY_HINT_ENUM,"64,96,128,192")); } static void _bind_methods() { ADD_SIGNAL(MethodInfo("changed")); } _EditorMeshImportOptions() { generate_tangents = true; generate_normals = false; flip_faces = false; smooth_shading = false; weld_vertices = true; weld_tolerance = 0.0001; import_material = false; import_textures = false; } }; class EditorMeshImportDialog : public ConfirmationDialog { OBJ_TYPE(EditorMeshImportDialog, ConfirmationDialog); EditorMeshImportPlugin *plugin; LineEdit *import_path; LineEdit *save_path; EditorFileDialog *file_select; EditorDirDialog *save_select; AcceptDialog *error_dialog; PropertyEditor *option_editor; _EditorMeshImportOptions *options; public: void _choose_files(const Vector &p_path) { String files; for (int i = 0; i < p_path.size(); i++) { if (i > 0) files += ","; files += p_path[i]; } /* if (p_path.size()) { String srctex=p_path[0]; String ipath = EditorImportDB::get_singleton()->find_source_path(srctex); if (ipath!="") save_path->set_text(ipath.get_base_dir()); }*/ import_path->set_text(files); } void _choose_save_dir(const String &p_path) { save_path->set_text(p_path); } void _browse() { file_select->popup_centered_ratio(); } void _browse_target() { save_select->popup_centered_ratio(); } void popup_import(const String &p_path) { popup_centered(Size2(400, 400) * EDSCALE); if (p_path != "") { Ref rimd = ResourceLoader::load_import_metadata(p_path); ERR_FAIL_COND(!rimd.is_valid()); save_path->set_text(p_path.get_base_dir()); List opts; rimd->get_options(&opts); for (List::Element *E = opts.front(); E; E = E->next()) { options->_set(E->get(), rimd->get_option(E->get())); } String src = ""; for (int i = 0; i < rimd->get_source_count(); i++) { if (i > 0) src += ","; src += EditorImportPlugin::expand_source_path(rimd->get_source_path(i)); } import_path->set_text(src); } } void _import() { Vector meshes = import_path->get_text().split(","); if (meshes.size() == 0) { error_dialog->set_text(TTR("No meshes to import!")); error_dialog->popup_centered_minsize(); return; } String dst = save_path->get_text(); if (dst == "") { error_dialog->set_text(TTR("Save path is empty!")); error_dialog->popup_centered_minsize(); return; } for (int i = 0; i < meshes.size(); i++) { Ref imd = memnew(ResourceImportMetadata); List pl; options->_get_property_list(&pl); for (List::Element *E = pl.front(); E; E = E->next()) { Variant v; String opt = E->get().name; options->_get(opt, v); imd->set_option(opt, v); } imd->add_source(EditorImportPlugin::validate_source_path(meshes[i])); String file_path = dst.plus_file(meshes[i].get_file().basename() + ".msh"); plugin->import(file_path, imd); } hide(); } void _notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { option_editor->edit(options); } } static void _bind_methods() { ObjectTypeDB::bind_method("_choose_files", &EditorMeshImportDialog::_choose_files); ObjectTypeDB::bind_method("_choose_save_dir", &EditorMeshImportDialog::_choose_save_dir); ObjectTypeDB::bind_method("_import", &EditorMeshImportDialog::_import); ObjectTypeDB::bind_method("_browse", &EditorMeshImportDialog::_browse); ObjectTypeDB::bind_method("_browse_target", &EditorMeshImportDialog::_browse_target); } EditorMeshImportDialog(EditorMeshImportPlugin *p_plugin) { plugin = p_plugin; set_title(TTR("Single Mesh Import")); set_hide_on_ok(false); VBoxContainer *vbc = memnew(VBoxContainer); add_child(vbc); set_child_rect(vbc); HBoxContainer *hbc = memnew(HBoxContainer); vbc->add_margin_child(TTR("Source Mesh(es):"), hbc); import_path = memnew(LineEdit); import_path->set_h_size_flags(SIZE_EXPAND_FILL); hbc->add_child(import_path); Button *import_choose = memnew(Button); import_choose->set_text(" .. "); hbc->add_child(import_choose); import_choose->connect("pressed", this, "_browse"); hbc = memnew(HBoxContainer); vbc->add_margin_child(TTR("Target Path:"), hbc); save_path = memnew(LineEdit); save_path->set_h_size_flags(SIZE_EXPAND_FILL); hbc->add_child(save_path); Button *save_choose = memnew(Button); save_choose->set_text(" .. "); hbc->add_child(save_choose); save_choose->connect("pressed", this, "_browse_target"); file_select = memnew(EditorFileDialog); file_select->set_access(EditorFileDialog::ACCESS_FILESYSTEM); file_select->set_mode(EditorFileDialog::MODE_OPEN_FILES); file_select->add_filter("*.obj ; Wavefront OBJ"); add_child(file_select); file_select->connect("files_selected", this, "_choose_files"); save_select = memnew(EditorDirDialog); add_child(save_select); save_select->connect("dir_selected", this, "_choose_save_dir"); get_ok()->connect("pressed", this, "_import"); get_ok()->set_text(TTR("Import")); error_dialog = memnew(AcceptDialog); add_child(error_dialog); options = memnew(_EditorMeshImportOptions); option_editor = memnew(PropertyEditor); option_editor->hide_top_label(); vbc->add_margin_child(TTR("Options:"), option_editor, true); } ~EditorMeshImportDialog() { memdelete(options); } }; String EditorMeshImportPlugin::get_name() const { return "mesh"; } String EditorMeshImportPlugin::get_visible_name() const { return TTR("Mesh"); } void EditorMeshImportPlugin::import_dialog(const String &p_from) { dialog->popup_import(p_from); } Error EditorMeshImportPlugin::import(const String &p_path, const Ref &p_from) { ERR_FAIL_COND_V(p_from->get_source_count() != 1, ERR_INVALID_PARAMETER); Ref from = p_from; String src_path = EditorImportPlugin::expand_source_path(from->get_source_path(0)); FileAccessRef f = FileAccess::open(src_path, FileAccess::READ); ERR_FAIL_COND_V(!f, ERR_CANT_OPEN); Ref mesh; Map > name_map; if (FileAccess::exists(p_path)) { mesh = ResourceLoader::load(p_path, "Mesh"); if (mesh.is_valid()) { for (int i = 0; i < mesh->get_surface_count(); i++) { if (!mesh->surface_get_material(i).is_valid()) continue; String name; if (mesh->surface_get_name(i) != "") name = mesh->surface_get_name(i); else name = vformat(TTR("Surface %d"), i + 1); name_map[name] = mesh->surface_get_material(i); } while (mesh->get_surface_count()) { mesh->surface_remove(0); } } } if (!mesh.is_valid()) mesh = Ref(memnew(Mesh)); bool generate_normals = from->get_option("generate/normals"); bool generate_tangents = from->get_option("generate/tangents"); bool flip_faces = from->get_option("force/flip_faces"); bool force_smooth = from->get_option("force/smooth_shading"); bool weld_vertices = from->get_option("force/weld_vertices"); float weld_tolerance = from->get_option("force/weld_tolerance"); Vector vertices; Vector normals; Vector uvs; String name; Ref surf_tool = memnew(SurfaceTool); surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES); if (force_smooth) surf_tool->add_smooth_group(true); int has_index_data = false; while (true) { String l = f->get_line().strip_edges(); if (l.begins_with("v ")) { //vertex Vector v = l.split(" ", false); ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA); Vector3 vtx; vtx.x = v[1].to_float(); vtx.y = v[2].to_float(); vtx.z = v[3].to_float(); vertices.push_back(vtx); } else if (l.begins_with("vt ")) { //uv Vector v = l.split(" ", false); ERR_FAIL_COND_V(v.size() < 3, ERR_INVALID_DATA); Vector2 uv; uv.x = v[1].to_float(); uv.y = 1.0 - v[2].to_float(); uvs.push_back(uv); } else if (l.begins_with("vn ")) { //normal Vector v = l.split(" ", false); ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA); Vector3 nrm; nrm.x = v[1].to_float(); nrm.y = v[2].to_float(); nrm.z = v[3].to_float(); normals.push_back(nrm); } if (l.begins_with("f ")) { //vertex has_index_data = true; Vector v = l.split(" ", false); ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA); //not very fast, could be sped up Vector face[3]; face[0] = v[1].split("/"); face[1] = v[2].split("/"); ERR_FAIL_COND_V(face[0].size() == 0, ERR_PARSE_ERROR); ERR_FAIL_COND_V(face[0].size() != face[1].size(), ERR_PARSE_ERROR); for (int i = 2; i < v.size() - 1; i++) { face[2] = v[i + 1].split("/"); ERR_FAIL_COND_V(face[0].size() != face[2].size(), ERR_PARSE_ERROR); for (int j = 0; j < 3; j++) { int idx = j; if (!flip_faces && idx < 2) { idx = 1 ^ idx; } if (face[idx].size() == 3) { int norm = face[idx][2].to_int() - 1; ERR_FAIL_INDEX_V(norm, normals.size(), ERR_PARSE_ERROR); surf_tool->add_normal(normals[norm]); } if (face[idx].size() >= 2 && face[idx][1] != String()) { int uv = face[idx][1].to_int() - 1; ERR_FAIL_INDEX_V(uv, uvs.size(), ERR_PARSE_ERROR); surf_tool->add_uv(uvs[uv]); } int vtx = face[idx][0].to_int() - 1; ERR_FAIL_INDEX_V(vtx, vertices.size(), ERR_PARSE_ERROR); Vector3 vertex = vertices[vtx]; if (weld_vertices) vertex = vertex.snapped(weld_tolerance); surf_tool->add_vertex(vertex); } face[1] = face[2]; } } else if (l.begins_with("s ") && !force_smooth) { //smoothing String what = l.substr(2, l.length()).strip_edges(); if (what == "off") surf_tool->add_smooth_group(false); else surf_tool->add_smooth_group(true); } else if (l.begins_with("o ") || f->eof_reached()) { //new surface or done if (has_index_data) { //new object/surface if (generate_normals || force_smooth) surf_tool->generate_normals(); if (uvs.size() && (normals.size() || generate_normals) && generate_tangents) surf_tool->generate_tangents(); surf_tool->index(); mesh = surf_tool->commit(mesh); if (name == "") name = vformat(TTR("Surface %d"), mesh->get_surface_count() - 1); mesh->surface_set_name(mesh->get_surface_count() - 1, name); name = ""; surf_tool->clear(); surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES); if (force_smooth) surf_tool->add_smooth_group(true); has_index_data = false; } if (l.begins_with("o ")) //name name = l.substr(2, l.length()).strip_edges(); if (f->eof_reached()) break; } } from->set_source_md5(0, FileAccess::get_md5(src_path)); from->set_editor(get_name()); mesh->set_import_metadata(from); //re-apply materials if exist for (int i = 0; i < mesh->get_surface_count(); i++) { String n = mesh->surface_get_name(i); if (name_map.has(n)) mesh->surface_set_material(i, name_map[n]); } Error err = ResourceSaver::save(p_path, mesh); return err; } void EditorMeshImportPlugin::import_from_drop(const Vector &p_drop, const String &p_dest_path) { Vector files; for (int i = 0; i < p_drop.size(); i++) { String ext = p_drop[i].extension().to_lower(); String file = p_drop[i].get_file(); if (ext == "obj") { files.push_back(p_drop[i]); } } if (files.size()) { import_dialog(); dialog->_choose_files(files); dialog->_choose_save_dir(p_dest_path); } } EditorMeshImportPlugin::EditorMeshImportPlugin(EditorNode *p_editor) { dialog = memnew(EditorMeshImportDialog(this)); p_editor->get_gui_base()->add_child(dialog); }