1 /*************************************************************************/
2 /* editor_mesh_import_plugin.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
10 /* */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the */
13 /* "Software"), to deal in the Software without restriction, including */
14 /* without limitation the rights to use, copy, modify, merge, publish, */
15 /* distribute, sublicense, and/or sell copies of the Software, and to */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions: */
18 /* */
19 /* The above copyright notice and this permission notice shall be */
20 /* included in all copies or substantial portions of the Software. */
21 /* */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29 /*************************************************************************/
30 #include "editor_mesh_import_plugin.h"
31
32 #include "editor/editor_dir_dialog.h"
33 #include "editor/editor_file_dialog.h"
34 #include "editor/editor_node.h"
35 #include "editor/property_editor.h"
36 #include "io/marshalls.h"
37 #include "io/resource_saver.h"
38 #include "os/file_access.h"
39 #include "scene/resources/sample.h"
40 #include "scene/resources/surface_tool.h"
41
42 class _EditorMeshImportOptions : public Object {
43
44 OBJ_TYPE(_EditorMeshImportOptions, Object);
45
46 public:
47 bool generate_tangents;
48 bool generate_normals;
49 bool flip_faces;
50 bool smooth_shading;
51 bool weld_vertices;
52 bool import_material;
53 bool import_textures;
54 float weld_tolerance;
55
_set(const StringName & p_name,const Variant & p_value)56 bool _set(const StringName &p_name, const Variant &p_value) {
57
58 String n = p_name;
59 if (n == "generate/tangents")
60 generate_tangents = p_value;
61 else if (n == "generate/normals")
62 generate_normals = p_value;
63 else if (n == "import/materials")
64 import_material = p_value;
65 else if (n == "import/textures")
66 import_textures = p_value;
67 else if (n == "force/flip_faces")
68 flip_faces = p_value;
69 else if (n == "force/smooth_shading")
70 smooth_shading = p_value;
71 else if (n == "force/weld_vertices")
72 weld_vertices = p_value;
73 else if (n == "force/weld_tolerance")
74 weld_tolerance = p_value;
75 else
76 return false;
77
78 return true;
79 }
80
_get(const StringName & p_name,Variant & r_ret) const81 bool _get(const StringName &p_name, Variant &r_ret) const {
82
83 String n = p_name;
84 if (n == "generate/tangents")
85 r_ret = generate_tangents;
86 else if (n == "generate/normals")
87 r_ret = generate_normals;
88 else if (n == "import/materials")
89 r_ret = import_material;
90 else if (n == "import/textures")
91 r_ret = import_textures;
92 else if (n == "force/flip_faces")
93 r_ret = flip_faces;
94 else if (n == "force/smooth_shading")
95 r_ret = smooth_shading;
96 else if (n == "force/weld_vertices")
97 r_ret = weld_vertices;
98 else if (n == "force/weld_tolerance")
99 r_ret = weld_tolerance;
100 else
101 return false;
102
103 return true;
104 }
_get_property_list(List<PropertyInfo> * p_list) const105 void _get_property_list(List<PropertyInfo> *p_list) const {
106
107 p_list->push_back(PropertyInfo(Variant::BOOL, "generate/tangents"));
108 p_list->push_back(PropertyInfo(Variant::BOOL, "generate/normals"));
109 //not for nowp
110 //p_list->push_back(PropertyInfo(Variant::BOOL,"import/materials"));
111 //p_list->push_back(PropertyInfo(Variant::BOOL,"import/textures"));
112 p_list->push_back(PropertyInfo(Variant::BOOL, "force/flip_faces"));
113 p_list->push_back(PropertyInfo(Variant::BOOL, "force/smooth_shading"));
114 p_list->push_back(PropertyInfo(Variant::BOOL, "force/weld_vertices"));
115 p_list->push_back(PropertyInfo(Variant::REAL, "force/weld_tolerance", PROPERTY_HINT_RANGE, "0.00001,16,0.00001"));
116 //p_list->push_back(PropertyInfo(Variant::BOOL,"compress/enable"));
117 //p_list->push_back(PropertyInfo(Variant::INT,"compress/bitrate",PROPERTY_HINT_ENUM,"64,96,128,192"));
118 }
119
_bind_methods()120 static void _bind_methods() {
121
122 ADD_SIGNAL(MethodInfo("changed"));
123 }
124
_EditorMeshImportOptions()125 _EditorMeshImportOptions() {
126
127 generate_tangents = true;
128 generate_normals = false;
129 flip_faces = false;
130 smooth_shading = false;
131 weld_vertices = true;
132 weld_tolerance = 0.0001;
133 import_material = false;
134 import_textures = false;
135 }
136 };
137
138 class EditorMeshImportDialog : public ConfirmationDialog {
139
140 OBJ_TYPE(EditorMeshImportDialog, ConfirmationDialog);
141
142 EditorMeshImportPlugin *plugin;
143
144 LineEdit *import_path;
145 LineEdit *save_path;
146 EditorFileDialog *file_select;
147 EditorDirDialog *save_select;
148 AcceptDialog *error_dialog;
149 PropertyEditor *option_editor;
150
151 _EditorMeshImportOptions *options;
152
153 public:
_choose_files(const Vector<String> & p_path)154 void _choose_files(const Vector<String> &p_path) {
155
156 String files;
157 for (int i = 0; i < p_path.size(); i++) {
158
159 if (i > 0)
160 files += ",";
161 files += p_path[i];
162 }
163 /*
164 if (p_path.size()) {
165 String srctex=p_path[0];
166 String ipath = EditorImportDB::get_singleton()->find_source_path(srctex);
167
168 if (ipath!="")
169 save_path->set_text(ipath.get_base_dir());
170 }*/
171 import_path->set_text(files);
172 }
_choose_save_dir(const String & p_path)173 void _choose_save_dir(const String &p_path) {
174
175 save_path->set_text(p_path);
176 }
177
_browse()178 void _browse() {
179
180 file_select->popup_centered_ratio();
181 }
182
_browse_target()183 void _browse_target() {
184
185 save_select->popup_centered_ratio();
186 }
187
popup_import(const String & p_path)188 void popup_import(const String &p_path) {
189
190 popup_centered(Size2(400, 400) * EDSCALE);
191
192 if (p_path != "") {
193
194 Ref<ResourceImportMetadata> rimd = ResourceLoader::load_import_metadata(p_path);
195 ERR_FAIL_COND(!rimd.is_valid());
196
197 save_path->set_text(p_path.get_base_dir());
198 List<String> opts;
199 rimd->get_options(&opts);
200 for (List<String>::Element *E = opts.front(); E; E = E->next()) {
201
202 options->_set(E->get(), rimd->get_option(E->get()));
203 }
204
205 String src = "";
206 for (int i = 0; i < rimd->get_source_count(); i++) {
207 if (i > 0)
208 src += ",";
209 src += EditorImportPlugin::expand_source_path(rimd->get_source_path(i));
210 }
211 import_path->set_text(src);
212 }
213 }
214
_import()215 void _import() {
216
217 Vector<String> meshes = import_path->get_text().split(",");
218 if (meshes.size() == 0) {
219 error_dialog->set_text(TTR("No meshes to import!"));
220 error_dialog->popup_centered_minsize();
221 return;
222 }
223
224 String dst = save_path->get_text();
225 if (dst == "") {
226 error_dialog->set_text(TTR("Save path is empty!"));
227 error_dialog->popup_centered_minsize();
228 return;
229 }
230
231 for (int i = 0; i < meshes.size(); i++) {
232
233 Ref<ResourceImportMetadata> imd = memnew(ResourceImportMetadata);
234
235 List<PropertyInfo> pl;
236 options->_get_property_list(&pl);
237 for (List<PropertyInfo>::Element *E = pl.front(); E; E = E->next()) {
238
239 Variant v;
240 String opt = E->get().name;
241 options->_get(opt, v);
242 imd->set_option(opt, v);
243 }
244
245 imd->add_source(EditorImportPlugin::validate_source_path(meshes[i]));
246
247 String file_path = dst.plus_file(meshes[i].get_file().basename() + ".msh");
248
249 plugin->import(file_path, imd);
250 }
251
252 hide();
253 }
254
_notification(int p_what)255 void _notification(int p_what) {
256
257 if (p_what == NOTIFICATION_ENTER_TREE) {
258
259 option_editor->edit(options);
260 }
261 }
262
_bind_methods()263 static void _bind_methods() {
264
265 ObjectTypeDB::bind_method("_choose_files", &EditorMeshImportDialog::_choose_files);
266 ObjectTypeDB::bind_method("_choose_save_dir", &EditorMeshImportDialog::_choose_save_dir);
267 ObjectTypeDB::bind_method("_import", &EditorMeshImportDialog::_import);
268 ObjectTypeDB::bind_method("_browse", &EditorMeshImportDialog::_browse);
269 ObjectTypeDB::bind_method("_browse_target", &EditorMeshImportDialog::_browse_target);
270 }
271
EditorMeshImportDialog(EditorMeshImportPlugin * p_plugin)272 EditorMeshImportDialog(EditorMeshImportPlugin *p_plugin) {
273
274 plugin = p_plugin;
275
276 set_title(TTR("Single Mesh Import"));
277 set_hide_on_ok(false);
278
279 VBoxContainer *vbc = memnew(VBoxContainer);
280 add_child(vbc);
281 set_child_rect(vbc);
282
283 HBoxContainer *hbc = memnew(HBoxContainer);
284 vbc->add_margin_child(TTR("Source Mesh(es):"), hbc);
285
286 import_path = memnew(LineEdit);
287 import_path->set_h_size_flags(SIZE_EXPAND_FILL);
288 hbc->add_child(import_path);
289
290 Button *import_choose = memnew(Button);
291 import_choose->set_text(" .. ");
292 hbc->add_child(import_choose);
293
294 import_choose->connect("pressed", this, "_browse");
295
296 hbc = memnew(HBoxContainer);
297 vbc->add_margin_child(TTR("Target Path:"), hbc);
298
299 save_path = memnew(LineEdit);
300 save_path->set_h_size_flags(SIZE_EXPAND_FILL);
301 hbc->add_child(save_path);
302
303 Button *save_choose = memnew(Button);
304 save_choose->set_text(" .. ");
305 hbc->add_child(save_choose);
306
307 save_choose->connect("pressed", this, "_browse_target");
308
309 file_select = memnew(EditorFileDialog);
310 file_select->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
311 file_select->set_mode(EditorFileDialog::MODE_OPEN_FILES);
312 file_select->add_filter("*.obj ; Wavefront OBJ");
313 add_child(file_select);
314 file_select->connect("files_selected", this, "_choose_files");
315
316 save_select = memnew(EditorDirDialog);
317 add_child(save_select);
318 save_select->connect("dir_selected", this, "_choose_save_dir");
319
320 get_ok()->connect("pressed", this, "_import");
321 get_ok()->set_text(TTR("Import"));
322
323 error_dialog = memnew(AcceptDialog);
324 add_child(error_dialog);
325
326 options = memnew(_EditorMeshImportOptions);
327
328 option_editor = memnew(PropertyEditor);
329 option_editor->hide_top_label();
330 vbc->add_margin_child(TTR("Options:"), option_editor, true);
331 }
332
~EditorMeshImportDialog()333 ~EditorMeshImportDialog() {
334 memdelete(options);
335 }
336 };
337
get_name() const338 String EditorMeshImportPlugin::get_name() const {
339
340 return "mesh";
341 }
get_visible_name() const342 String EditorMeshImportPlugin::get_visible_name() const {
343
344 return TTR("Mesh");
345 }
import_dialog(const String & p_from)346 void EditorMeshImportPlugin::import_dialog(const String &p_from) {
347
348 dialog->popup_import(p_from);
349 }
import(const String & p_path,const Ref<ResourceImportMetadata> & p_from)350 Error EditorMeshImportPlugin::import(const String &p_path, const Ref<ResourceImportMetadata> &p_from) {
351
352 ERR_FAIL_COND_V(p_from->get_source_count() != 1, ERR_INVALID_PARAMETER);
353
354 Ref<ResourceImportMetadata> from = p_from;
355
356 String src_path = EditorImportPlugin::expand_source_path(from->get_source_path(0));
357 FileAccessRef f = FileAccess::open(src_path, FileAccess::READ);
358 ERR_FAIL_COND_V(!f, ERR_CANT_OPEN);
359
360 Ref<Mesh> mesh;
361 Map<String, Ref<Material> > name_map;
362
363 if (FileAccess::exists(p_path)) {
364 mesh = ResourceLoader::load(p_path, "Mesh");
365 if (mesh.is_valid()) {
366 for (int i = 0; i < mesh->get_surface_count(); i++) {
367
368 if (!mesh->surface_get_material(i).is_valid())
369 continue;
370 String name;
371 if (mesh->surface_get_name(i) != "")
372 name = mesh->surface_get_name(i);
373 else
374 name = vformat(TTR("Surface %d"), i + 1);
375
376 name_map[name] = mesh->surface_get_material(i);
377 }
378
379 while (mesh->get_surface_count()) {
380 mesh->surface_remove(0);
381 }
382 }
383 }
384
385 if (!mesh.is_valid())
386 mesh = Ref<Mesh>(memnew(Mesh));
387
388 bool generate_normals = from->get_option("generate/normals");
389 bool generate_tangents = from->get_option("generate/tangents");
390 bool flip_faces = from->get_option("force/flip_faces");
391 bool force_smooth = from->get_option("force/smooth_shading");
392 bool weld_vertices = from->get_option("force/weld_vertices");
393 float weld_tolerance = from->get_option("force/weld_tolerance");
394 Vector<Vector3> vertices;
395 Vector<Vector3> normals;
396 Vector<Vector2> uvs;
397 String name;
398
399 Ref<SurfaceTool> surf_tool = memnew(SurfaceTool);
400 surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES);
401 if (force_smooth)
402 surf_tool->add_smooth_group(true);
403 int has_index_data = false;
404
405 while (true) {
406
407 String l = f->get_line().strip_edges();
408
409 if (l.begins_with("v ")) {
410 //vertex
411 Vector<String> v = l.split(" ", false);
412 ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA);
413 Vector3 vtx;
414 vtx.x = v[1].to_float();
415 vtx.y = v[2].to_float();
416 vtx.z = v[3].to_float();
417 vertices.push_back(vtx);
418 } else if (l.begins_with("vt ")) {
419 //uv
420 Vector<String> v = l.split(" ", false);
421 ERR_FAIL_COND_V(v.size() < 3, ERR_INVALID_DATA);
422 Vector2 uv;
423 uv.x = v[1].to_float();
424 uv.y = 1.0 - v[2].to_float();
425 uvs.push_back(uv);
426
427 } else if (l.begins_with("vn ")) {
428 //normal
429 Vector<String> v = l.split(" ", false);
430 ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA);
431 Vector3 nrm;
432 nrm.x = v[1].to_float();
433 nrm.y = v[2].to_float();
434 nrm.z = v[3].to_float();
435 normals.push_back(nrm);
436 }
437 if (l.begins_with("f ")) {
438 //vertex
439
440 has_index_data = true;
441 Vector<String> v = l.split(" ", false);
442 ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA);
443
444 //not very fast, could be sped up
445
446 Vector<String> face[3];
447 face[0] = v[1].split("/");
448 face[1] = v[2].split("/");
449 ERR_FAIL_COND_V(face[0].size() == 0, ERR_PARSE_ERROR);
450 ERR_FAIL_COND_V(face[0].size() != face[1].size(), ERR_PARSE_ERROR);
451 for (int i = 2; i < v.size() - 1; i++) {
452
453 face[2] = v[i + 1].split("/");
454 ERR_FAIL_COND_V(face[0].size() != face[2].size(), ERR_PARSE_ERROR);
455 for (int j = 0; j < 3; j++) {
456
457 int idx = j;
458
459 if (!flip_faces && idx < 2) {
460 idx = 1 ^ idx;
461 }
462
463 if (face[idx].size() == 3) {
464 int norm = face[idx][2].to_int() - 1;
465 ERR_FAIL_INDEX_V(norm, normals.size(), ERR_PARSE_ERROR);
466 surf_tool->add_normal(normals[norm]);
467 }
468
469 if (face[idx].size() >= 2 && face[idx][1] != String()) {
470
471 int uv = face[idx][1].to_int() - 1;
472 ERR_FAIL_INDEX_V(uv, uvs.size(), ERR_PARSE_ERROR);
473 surf_tool->add_uv(uvs[uv]);
474 }
475
476 int vtx = face[idx][0].to_int() - 1;
477 ERR_FAIL_INDEX_V(vtx, vertices.size(), ERR_PARSE_ERROR);
478
479 Vector3 vertex = vertices[vtx];
480 if (weld_vertices)
481 vertex = vertex.snapped(weld_tolerance);
482 surf_tool->add_vertex(vertex);
483 }
484
485 face[1] = face[2];
486 }
487 } else if (l.begins_with("s ") && !force_smooth) { //smoothing
488 String what = l.substr(2, l.length()).strip_edges();
489 if (what == "off")
490 surf_tool->add_smooth_group(false);
491 else
492 surf_tool->add_smooth_group(true);
493
494 } else if (l.begins_with("o ") || f->eof_reached()) { //new surface or done
495
496 if (has_index_data) {
497 //new object/surface
498 if (generate_normals || force_smooth)
499 surf_tool->generate_normals();
500 if (uvs.size() && (normals.size() || generate_normals) && generate_tangents)
501 surf_tool->generate_tangents();
502
503 surf_tool->index();
504 mesh = surf_tool->commit(mesh);
505 if (name == "")
506 name = vformat(TTR("Surface %d"), mesh->get_surface_count() - 1);
507 mesh->surface_set_name(mesh->get_surface_count() - 1, name);
508 name = "";
509 surf_tool->clear();
510 surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES);
511 if (force_smooth)
512 surf_tool->add_smooth_group(true);
513
514 has_index_data = false;
515 }
516
517 if (l.begins_with("o ")) //name
518 name = l.substr(2, l.length()).strip_edges();
519
520 if (f->eof_reached())
521 break;
522 }
523 }
524
525 from->set_source_md5(0, FileAccess::get_md5(src_path));
526 from->set_editor(get_name());
527 mesh->set_import_metadata(from);
528
529 //re-apply materials if exist
530 for (int i = 0; i < mesh->get_surface_count(); i++) {
531
532 String n = mesh->surface_get_name(i);
533 if (name_map.has(n))
534 mesh->surface_set_material(i, name_map[n]);
535 }
536
537 Error err = ResourceSaver::save(p_path, mesh);
538
539 return err;
540 }
541
import_from_drop(const Vector<String> & p_drop,const String & p_dest_path)542 void EditorMeshImportPlugin::import_from_drop(const Vector<String> &p_drop, const String &p_dest_path) {
543
544 Vector<String> files;
545 for (int i = 0; i < p_drop.size(); i++) {
546 String ext = p_drop[i].extension().to_lower();
547 String file = p_drop[i].get_file();
548 if (ext == "obj") {
549
550 files.push_back(p_drop[i]);
551 }
552 }
553
554 if (files.size()) {
555 import_dialog();
556 dialog->_choose_files(files);
557 dialog->_choose_save_dir(p_dest_path);
558 }
559 }
560
EditorMeshImportPlugin(EditorNode * p_editor)561 EditorMeshImportPlugin::EditorMeshImportPlugin(EditorNode *p_editor) {
562
563 dialog = memnew(EditorMeshImportDialog(this));
564 p_editor->get_gui_base()->add_child(dialog);
565 }
566