1 | /**************************************************************************/ |
2 | /* editor_scene_importer_blend.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
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 | |
31 | #include "editor_scene_importer_blend.h" |
32 | |
33 | #ifdef TOOLS_ENABLED |
34 | |
35 | #include "../gltf_defines.h" |
36 | #include "../gltf_document.h" |
37 | #include "editor_import_blend_runner.h" |
38 | |
39 | #include "core/config/project_settings.h" |
40 | #include "editor/editor_node.h" |
41 | #include "editor/editor_scale.h" |
42 | #include "editor/editor_settings.h" |
43 | #include "editor/editor_string_names.h" |
44 | #include "editor/gui/editor_file_dialog.h" |
45 | #include "main/main.h" |
46 | #include "scene/gui/line_edit.h" |
47 | |
48 | #ifdef WINDOWS_ENABLED |
49 | #include <shlwapi.h> |
50 | #endif |
51 | |
52 | uint32_t EditorSceneFormatImporterBlend::get_import_flags() const { |
53 | return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION; |
54 | } |
55 | |
56 | void EditorSceneFormatImporterBlend::get_extensions(List<String> *r_extensions) const { |
57 | r_extensions->push_back("blend" ); |
58 | } |
59 | |
60 | Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_t p_flags, |
61 | const HashMap<StringName, Variant> &p_options, |
62 | List<String> *r_missing_deps, Error *r_err) { |
63 | // Get global paths for source and sink. |
64 | |
65 | // Escape paths to be valid Python strings to embed in the script. |
66 | const String source_global = ProjectSettings::get_singleton()->globalize_path(p_path).c_escape(); |
67 | const String sink = ProjectSettings::get_singleton()->get_imported_files_path().path_join( |
68 | vformat("%s-%s.gltf" , p_path.get_file().get_basename(), p_path.md5_text())); |
69 | const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink).c_escape(); |
70 | |
71 | // Handle configuration options. |
72 | |
73 | Dictionary request_options; |
74 | Dictionary parameters_map; |
75 | |
76 | parameters_map["filepath" ] = sink_global; |
77 | parameters_map["export_keep_originals" ] = true; |
78 | parameters_map["export_format" ] = "GLTF_SEPARATE" ; |
79 | parameters_map["export_yup" ] = true; |
80 | |
81 | if (p_options.has(SNAME("blender/nodes/custom_properties" )) && p_options[SNAME("blender/nodes/custom_properties" )]) { |
82 | parameters_map["export_extras" ] = true; |
83 | } else { |
84 | parameters_map["export_extras" ] = false; |
85 | } |
86 | if (p_options.has(SNAME("blender/meshes/skins" ))) { |
87 | int32_t skins = p_options["blender/meshes/skins" ]; |
88 | if (skins == BLEND_BONE_INFLUENCES_NONE) { |
89 | parameters_map["export_skins" ] = false; |
90 | } else if (skins == BLEND_BONE_INFLUENCES_COMPATIBLE) { |
91 | parameters_map["export_skins" ] = true; |
92 | parameters_map["export_all_influences" ] = false; |
93 | } else if (skins == BLEND_BONE_INFLUENCES_ALL) { |
94 | parameters_map["export_skins" ] = true; |
95 | parameters_map["export_all_influences" ] = true; |
96 | } |
97 | } else { |
98 | parameters_map["export_skins" ] = false; |
99 | } |
100 | if (p_options.has(SNAME("blender/materials/export_materials" ))) { |
101 | int32_t exports = p_options["blender/materials/export_materials" ]; |
102 | if (exports == BLEND_MATERIAL_EXPORT_PLACEHOLDER) { |
103 | parameters_map["export_materials" ] = "PLACEHOLDER" ; |
104 | } else if (exports == BLEND_MATERIAL_EXPORT_EXPORT) { |
105 | parameters_map["export_materials" ] = "EXPORT" ; |
106 | } |
107 | } else { |
108 | parameters_map["export_materials" ] = "PLACEHOLDER" ; |
109 | } |
110 | if (p_options.has(SNAME("blender/nodes/cameras" )) && p_options[SNAME("blender/nodes/cameras" )]) { |
111 | parameters_map["export_cameras" ] = true; |
112 | } else { |
113 | parameters_map["export_cameras" ] = false; |
114 | } |
115 | if (p_options.has(SNAME("blender/nodes/punctual_lights" )) && p_options[SNAME("blender/nodes/punctual_lights" )]) { |
116 | parameters_map["export_lights" ] = true; |
117 | } else { |
118 | parameters_map["export_lights" ] = false; |
119 | } |
120 | if (p_options.has(SNAME("blender/meshes/colors" )) && p_options[SNAME("blender/meshes/colors" )]) { |
121 | parameters_map["export_colors" ] = true; |
122 | } else { |
123 | parameters_map["export_colors" ] = false; |
124 | } |
125 | if (p_options.has(SNAME("blender/nodes/visible" ))) { |
126 | int32_t visible = p_options["blender/nodes/visible" ]; |
127 | if (visible == BLEND_VISIBLE_VISIBLE_ONLY) { |
128 | parameters_map["use_visible" ] = true; |
129 | } else if (visible == BLEND_VISIBLE_RENDERABLE) { |
130 | parameters_map["use_renderable" ] = true; |
131 | } else if (visible == BLEND_VISIBLE_ALL) { |
132 | parameters_map["use_renderable" ] = false; |
133 | parameters_map["use_visible" ] = false; |
134 | } |
135 | } else { |
136 | parameters_map["use_renderable" ] = false; |
137 | parameters_map["use_visible" ] = false; |
138 | } |
139 | |
140 | if (p_options.has(SNAME("blender/meshes/uvs" )) && p_options[SNAME("blender/meshes/uvs" )]) { |
141 | parameters_map["export_texcoords" ] = true; |
142 | } else { |
143 | parameters_map["export_texcoords" ] = false; |
144 | } |
145 | if (p_options.has(SNAME("blender/meshes/normals" )) && p_options[SNAME("blender/meshes/normals" )]) { |
146 | parameters_map["export_normals" ] = true; |
147 | } else { |
148 | parameters_map["export_normals" ] = false; |
149 | } |
150 | if (p_options.has(SNAME("blender/meshes/tangents" )) && p_options[SNAME("blender/meshes/tangents" )]) { |
151 | parameters_map["export_tangents" ] = true; |
152 | } else { |
153 | parameters_map["export_tangents" ] = false; |
154 | } |
155 | if (p_options.has(SNAME("blender/animation/group_tracks" )) && p_options[SNAME("blender/animation/group_tracks" )]) { |
156 | parameters_map["export_nla_strips" ] = true; |
157 | } else { |
158 | parameters_map["export_nla_strips" ] = false; |
159 | } |
160 | if (p_options.has(SNAME("blender/animation/limit_playback" )) && p_options[SNAME("blender/animation/limit_playback" )]) { |
161 | parameters_map["export_frame_range" ] = true; |
162 | } else { |
163 | parameters_map["export_frame_range" ] = false; |
164 | } |
165 | if (p_options.has(SNAME("blender/animation/always_sample" )) && p_options[SNAME("blender/animation/always_sample" )]) { |
166 | parameters_map["export_force_sampling" ] = true; |
167 | } else { |
168 | parameters_map["export_force_sampling" ] = false; |
169 | } |
170 | if (p_options.has(SNAME("blender/meshes/export_bones_deforming_mesh_only" )) && p_options[SNAME("blender/meshes/export_bones_deforming_mesh_only" )]) { |
171 | parameters_map["export_def_bones" ] = true; |
172 | } else { |
173 | parameters_map["export_def_bones" ] = false; |
174 | } |
175 | if (p_options.has(SNAME("blender/nodes/modifiers" )) && p_options[SNAME("blender/nodes/modifiers" )]) { |
176 | parameters_map["export_apply" ] = true; |
177 | } else { |
178 | parameters_map["export_apply" ] = false; |
179 | } |
180 | |
181 | if (p_options.has(SNAME("blender/materials/unpack_enabled" )) && p_options[SNAME("blender/materials/unpack_enabled" )]) { |
182 | request_options["unpack_all" ] = true; |
183 | } else { |
184 | request_options["unpack_all" ] = false; |
185 | } |
186 | |
187 | request_options["path" ] = source_global; |
188 | request_options["gltf_options" ] = parameters_map; |
189 | |
190 | // Run Blender and export glTF. |
191 | Error err = EditorImportBlendRunner::get_singleton()->do_import(request_options); |
192 | if (err != OK) { |
193 | if (r_err) { |
194 | *r_err = ERR_SCRIPT_FAILED; |
195 | } |
196 | return nullptr; |
197 | } |
198 | |
199 | // Import the generated glTF. |
200 | |
201 | // Use GLTFDocument instead of glTF importer to keep image references. |
202 | Ref<GLTFDocument> gltf; |
203 | gltf.instantiate(); |
204 | Ref<GLTFState> state; |
205 | state.instantiate(); |
206 | |
207 | String base_dir; |
208 | if (p_options.has(SNAME("blender/materials/unpack_enabled" )) && p_options[SNAME("blender/materials/unpack_enabled" )]) { |
209 | base_dir = sink.get_base_dir(); |
210 | } |
211 | err = gltf->append_from_file(sink.get_basename() + ".gltf" , state, p_flags, base_dir); |
212 | if (err != OK) { |
213 | if (r_err) { |
214 | *r_err = FAILED; |
215 | } |
216 | return nullptr; |
217 | } |
218 | |
219 | #ifndef DISABLE_DEPRECATED |
220 | bool trimming = p_options.has("animation/trimming" ) ? (bool)p_options["animation/trimming" ] : false; |
221 | bool remove_immutable = p_options.has("animation/remove_immutable_tracks" ) ? (bool)p_options["animation/remove_immutable_tracks" ] : true; |
222 | return gltf->generate_scene(state, (float)p_options["animation/fps" ], trimming, remove_immutable); |
223 | #else |
224 | return gltf->generate_scene(state, (float)p_options["animation/fps" ], (bool)p_options["animation/trimming" ], (bool)p_options["animation/remove_immutable_tracks" ]); |
225 | #endif |
226 | } |
227 | |
228 | Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, |
229 | const HashMap<StringName, Variant> &p_options) { |
230 | if (p_path.get_extension().to_lower() != "blend" ) { |
231 | return true; |
232 | } |
233 | |
234 | if (p_option.begins_with("animation/" )) { |
235 | if (p_option != "animation/import" && !bool(p_options["animation/import" ])) { |
236 | return false; |
237 | } |
238 | } |
239 | return true; |
240 | } |
241 | |
242 | void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) { |
243 | if (p_path.get_extension().to_lower() != "blend" ) { |
244 | return; |
245 | } |
246 | #define ADD_OPTION_BOOL(PATH, VALUE) \ |
247 | r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, SNAME(PATH)), VALUE)); |
248 | #define ADD_OPTION_ENUM(PATH, ENUM_HINT, VALUE) \ |
249 | r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, SNAME(PATH), PROPERTY_HINT_ENUM, ENUM_HINT), VALUE)); |
250 | |
251 | ADD_OPTION_ENUM("blender/nodes/visible" , "All,Visible Only,Renderable" , BLEND_VISIBLE_ALL); |
252 | ADD_OPTION_BOOL("blender/nodes/punctual_lights" , true); |
253 | ADD_OPTION_BOOL("blender/nodes/cameras" , true); |
254 | ADD_OPTION_BOOL("blender/nodes/custom_properties" , true); |
255 | ADD_OPTION_ENUM("blender/nodes/modifiers" , "No Modifiers,All Modifiers" , BLEND_MODIFIERS_ALL); |
256 | ADD_OPTION_BOOL("blender/meshes/colors" , false); |
257 | ADD_OPTION_BOOL("blender/meshes/uvs" , true); |
258 | ADD_OPTION_BOOL("blender/meshes/normals" , true); |
259 | ADD_OPTION_BOOL("blender/meshes/tangents" , true); |
260 | ADD_OPTION_ENUM("blender/meshes/skins" , "None,4 Influences (Compatible),All Influences" , BLEND_BONE_INFLUENCES_ALL); |
261 | ADD_OPTION_BOOL("blender/meshes/export_bones_deforming_mesh_only" , false); |
262 | ADD_OPTION_BOOL("blender/materials/unpack_enabled" , true); |
263 | ADD_OPTION_ENUM("blender/materials/export_materials" , "Placeholder,Export" , BLEND_MATERIAL_EXPORT_EXPORT); |
264 | ADD_OPTION_BOOL("blender/animation/limit_playback" , true); |
265 | ADD_OPTION_BOOL("blender/animation/always_sample" , true); |
266 | ADD_OPTION_BOOL("blender/animation/group_tracks" , true); |
267 | } |
268 | |
269 | /////////////////////////// |
270 | |
271 | static bool _test_blender_path(const String &p_path, String *r_err = nullptr) { |
272 | String path = p_path; |
273 | #ifdef WINDOWS_ENABLED |
274 | path = path.path_join("blender.exe" ); |
275 | #else |
276 | path = path.path_join("blender" ); |
277 | #endif |
278 | |
279 | #if defined(MACOS_ENABLED) |
280 | if (!FileAccess::exists(path)) { |
281 | path = path.path_join("Blender" ); |
282 | } |
283 | #endif |
284 | |
285 | if (!FileAccess::exists(path)) { |
286 | if (r_err) { |
287 | *r_err = TTR("Path does not contain a Blender installation." ); |
288 | } |
289 | return false; |
290 | } |
291 | List<String> args; |
292 | args.push_back("--version" ); |
293 | String pipe; |
294 | Error err = OS::get_singleton()->execute(path, args, &pipe); |
295 | if (err != OK) { |
296 | if (r_err) { |
297 | *r_err = TTR("Can't execute Blender binary." ); |
298 | } |
299 | return false; |
300 | } |
301 | int bl = pipe.find("Blender " ); |
302 | if (bl == -1) { |
303 | if (r_err) { |
304 | *r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s" ), path); |
305 | } |
306 | return false; |
307 | } |
308 | pipe = pipe.substr(bl); |
309 | pipe = pipe.replace_first("Blender " , "" ); |
310 | int pp = pipe.find("." ); |
311 | if (pp == -1) { |
312 | if (r_err) { |
313 | *r_err = TTR("Path supplied lacks a Blender binary." ); |
314 | } |
315 | return false; |
316 | } |
317 | String v = pipe.substr(0, pp); |
318 | int version = v.to_int(); |
319 | if (version < 3) { |
320 | if (r_err) { |
321 | *r_err = TTR("This Blender installation is too old for this importer (not 3.0+)." ); |
322 | } |
323 | return false; |
324 | } |
325 | if (version > 3) { |
326 | if (r_err) { |
327 | *r_err = TTR("This Blender installation is too new for this importer (not 3.x)." ); |
328 | } |
329 | return false; |
330 | } |
331 | |
332 | return true; |
333 | } |
334 | |
335 | bool EditorFileSystemImportFormatSupportQueryBlend::is_active() const { |
336 | bool blend_enabled = GLOBAL_GET("filesystem/import/blender/enabled" ); |
337 | |
338 | if (blend_enabled && !_test_blender_path(EDITOR_GET("filesystem/import/blender/blender3_path" ).operator String())) { |
339 | // Intending to import Blender, but blend not configured. |
340 | return true; |
341 | } |
342 | |
343 | return false; |
344 | } |
345 | Vector<String> EditorFileSystemImportFormatSupportQueryBlend::get_file_extensions() const { |
346 | Vector<String> ret; |
347 | ret.push_back("blend" ); |
348 | return ret; |
349 | } |
350 | |
351 | void EditorFileSystemImportFormatSupportQueryBlend::_validate_path(String p_path) { |
352 | String error; |
353 | bool success = false; |
354 | if (p_path == "" ) { |
355 | error = TTR("Path is empty." ); |
356 | } else { |
357 | if (_test_blender_path(p_path, &error)) { |
358 | success = true; |
359 | if (auto_detected_path == p_path) { |
360 | error = TTR("Path to Blender installation is valid (Autodetected)." ); |
361 | } else { |
362 | error = TTR("Path to Blender installation is valid." ); |
363 | } |
364 | } |
365 | } |
366 | |
367 | path_status->set_text(error); |
368 | |
369 | if (success) { |
370 | path_status->add_theme_color_override("font_color" , path_status->get_theme_color(SNAME("success_color" ), EditorStringName(Editor))); |
371 | configure_blender_dialog->get_ok_button()->set_disabled(false); |
372 | } else { |
373 | path_status->add_theme_color_override("font_color" , path_status->get_theme_color(SNAME("error_color" ), EditorStringName(Editor))); |
374 | configure_blender_dialog->get_ok_button()->set_disabled(true); |
375 | } |
376 | } |
377 | |
378 | bool EditorFileSystemImportFormatSupportQueryBlend::_autodetect_path(String p_path) { |
379 | if (_test_blender_path(p_path)) { |
380 | auto_detected_path = p_path; |
381 | return true; |
382 | } |
383 | return false; |
384 | } |
385 | |
386 | void EditorFileSystemImportFormatSupportQueryBlend::_path_confirmed() { |
387 | confirmed = true; |
388 | } |
389 | |
390 | void EditorFileSystemImportFormatSupportQueryBlend::_select_install(String p_path) { |
391 | blender_path->set_text(p_path); |
392 | _validate_path(p_path); |
393 | } |
394 | void EditorFileSystemImportFormatSupportQueryBlend::_browse_install() { |
395 | if (blender_path->get_text() != String()) { |
396 | browse_dialog->set_current_dir(blender_path->get_text()); |
397 | } |
398 | |
399 | browse_dialog->popup_centered_ratio(); |
400 | } |
401 | |
402 | bool EditorFileSystemImportFormatSupportQueryBlend::query() { |
403 | if (!configure_blender_dialog) { |
404 | configure_blender_dialog = memnew(ConfirmationDialog); |
405 | configure_blender_dialog->set_title(TTR("Configure Blender Importer" )); |
406 | configure_blender_dialog->set_flag(Window::FLAG_BORDERLESS, true); // Avoid closing accidentally . |
407 | configure_blender_dialog->set_close_on_escape(false); |
408 | |
409 | VBoxContainer *vb = memnew(VBoxContainer); |
410 | vb->add_child(memnew(Label(TTR("Blender 3.0+ is required to import '.blend' files.\nPlease provide a valid path to a Blender installation:" )))); |
411 | |
412 | HBoxContainer *hb = memnew(HBoxContainer); |
413 | |
414 | blender_path = memnew(LineEdit); |
415 | blender_path->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
416 | hb->add_child(blender_path); |
417 | blender_path_browse = memnew(Button); |
418 | hb->add_child(blender_path_browse); |
419 | blender_path_browse->set_text(TTR("Browse" )); |
420 | blender_path_browse->connect("pressed" , callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_browse_install)); |
421 | hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
422 | hb->set_custom_minimum_size(Size2(400 * EDSCALE, 0)); |
423 | |
424 | vb->add_child(hb); |
425 | |
426 | path_status = memnew(Label); |
427 | vb->add_child(path_status); |
428 | |
429 | configure_blender_dialog->add_child(vb); |
430 | |
431 | blender_path->connect("text_changed" , callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_validate_path)); |
432 | |
433 | EditorNode::get_singleton()->get_gui_base()->add_child(configure_blender_dialog); |
434 | |
435 | configure_blender_dialog->set_ok_button_text(TTR("Confirm Path" )); |
436 | configure_blender_dialog->set_cancel_button_text(TTR("Disable '.blend' Import" )); |
437 | configure_blender_dialog->get_cancel_button()->set_tooltip_text(TTR("Disables Blender '.blend' files import for this project. Can be re-enabled in Project Settings." )); |
438 | configure_blender_dialog->connect("confirmed" , callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_path_confirmed)); |
439 | |
440 | browse_dialog = memnew(EditorFileDialog); |
441 | browse_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); |
442 | browse_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR); |
443 | browse_dialog->connect("dir_selected" , callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_select_install)); |
444 | |
445 | EditorNode::get_singleton()->get_gui_base()->add_child(browse_dialog); |
446 | } |
447 | |
448 | String path = EDITOR_GET("filesystem/import/blender/blender3_path" ); |
449 | |
450 | if (path == "" ) { |
451 | // Autodetect |
452 | auto_detected_path = "" ; |
453 | |
454 | #if defined(MACOS_ENABLED) |
455 | |
456 | { |
457 | Vector<String> mdfind_paths; |
458 | { |
459 | List<String> mdfind_args; |
460 | mdfind_args.push_back("kMDItemCFBundleIdentifier=org.blenderfoundation.blender" ); |
461 | |
462 | String output; |
463 | Error err = OS::get_singleton()->execute("mdfind" , mdfind_args, &output); |
464 | if (err == OK) { |
465 | mdfind_paths = output.split("\n" ); |
466 | } |
467 | } |
468 | |
469 | bool found = false; |
470 | for (const String &found_path : mdfind_paths) { |
471 | found = _autodetect_path(found_path.path_join("Contents/MacOS" )); |
472 | if (found) { |
473 | break; |
474 | } |
475 | } |
476 | if (!found) { |
477 | found = _autodetect_path("/opt/homebrew/bin" ); |
478 | } |
479 | if (!found) { |
480 | found = _autodetect_path("/opt/local/bin" ); |
481 | } |
482 | if (!found) { |
483 | found = _autodetect_path("/usr/local/bin" ); |
484 | } |
485 | if (!found) { |
486 | found = _autodetect_path("/usr/local/opt" ); |
487 | } |
488 | if (!found) { |
489 | found = _autodetect_path("/Applications/Blender.app/Contents/MacOS" ); |
490 | } |
491 | } |
492 | #elif defined(WINDOWS_ENABLED) |
493 | { |
494 | char blender_opener_path[MAX_PATH]; |
495 | DWORD path_len = MAX_PATH; |
496 | HRESULT res = AssocQueryString(0, ASSOCSTR_EXECUTABLE, ".blend" , "open" , blender_opener_path, &path_len); |
497 | if (res == S_OK && _autodetect_path(String(blender_opener_path).get_base_dir())) { |
498 | // Good. |
499 | } else if (_autodetect_path("C:\\Program Files\\Blender Foundation" )) { |
500 | // Good. |
501 | } else { |
502 | _autodetect_path("C:\\Program Files (x86)\\Blender Foundation" ); |
503 | } |
504 | } |
505 | |
506 | #elif defined(UNIX_ENABLED) |
507 | if (_autodetect_path("/usr/bin" )) { |
508 | // Good. |
509 | } else if (_autodetect_path("/usr/local/bin" )) { |
510 | // Good |
511 | } else { |
512 | _autodetect_path("/opt/blender/bin" ); |
513 | } |
514 | #endif |
515 | if (auto_detected_path != "" ) { |
516 | path = auto_detected_path; |
517 | } |
518 | } |
519 | |
520 | blender_path->set_text(path); |
521 | |
522 | _validate_path(path); |
523 | |
524 | configure_blender_dialog->popup_centered(); |
525 | confirmed = false; |
526 | |
527 | while (true) { |
528 | OS::get_singleton()->delay_usec(1); |
529 | DisplayServer::get_singleton()->process_events(); |
530 | Main::iteration(); |
531 | if (!configure_blender_dialog->is_visible() || confirmed) { |
532 | break; |
533 | } |
534 | } |
535 | |
536 | if (confirmed) { |
537 | // Can only confirm a valid path. |
538 | EditorSettings::get_singleton()->set("filesystem/import/blender/blender3_path" , blender_path->get_text()); |
539 | EditorSettings::get_singleton()->save(); |
540 | } else { |
541 | // Disable Blender import |
542 | ProjectSettings::get_singleton()->set("filesystem/import/blender/enabled" , false); |
543 | ProjectSettings::get_singleton()->save(); |
544 | |
545 | if (EditorNode::immediate_confirmation_dialog(TTR("Disabling '.blend' file import requires restarting the editor." ), TTR("Save & Restart" ), TTR("Restart" ))) { |
546 | EditorNode::get_singleton()->save_all_scenes(); |
547 | } |
548 | EditorNode::get_singleton()->restart_editor(); |
549 | return true; |
550 | } |
551 | |
552 | return false; |
553 | } |
554 | |
555 | EditorFileSystemImportFormatSupportQueryBlend::EditorFileSystemImportFormatSupportQueryBlend() { |
556 | } |
557 | |
558 | #endif // TOOLS_ENABLED |
559 | |