Update blender export flags for 3.6.

Fixes #76338.

Blender 3.6 imports fail with:

```
TypeError: Converting py args to operator properties: : keyword "export_nla_strips" unrecognized
```

The `export_nla_strips` flag was removed and replaced with `export_animation_mode`.
In 3.6.0-3.6.21, this option does not exist at all and causes the failure above.
In 3.6.22, this option was re-added, but does nothing.
See 96a73cb664.

We now need to check the blender version to determine what flags to use.
This adds an additional shell command before every import.
We might consider caching the version, but we'd have to invalidate the cache if the blender version or path changes.

As an aside, the "group animations" setting in Godot does the opposite of what I'd expect.
When `group_tracks=true`, each animation is exported individually.
When `group_tracks=false`, all animations are exported as a single track.
This seems backwards, but I've kept the 3.6 behavior consistent with 3.5.

From https://docs.blender.org/api/3.6/bpy.ops.export_scene.html:

> ACTIONS Actions – Export actions (actives and on NLA tracks) as separate animations.
> ACTIVE_ACTIONS Active actions merged – All the currently assigned actions become one glTF animation.

Co-authored-by: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com>
(cherry picked from commit 7e64c6c399)
This commit is contained in:
Ryan Roden-Corrent 2023-08-31 08:33:01 -04:00 committed by Yuri Sizov
parent 578fa8603d
commit c93d74aca0
2 changed files with 82 additions and 64 deletions

View File

@ -48,6 +48,67 @@
#include <shlwapi.h> #include <shlwapi.h>
#endif #endif
static bool _get_blender_version(const String &p_path, int &r_major, int &r_minor, String *r_err = nullptr) {
String path = p_path;
#ifdef WINDOWS_ENABLED
path = path.path_join("blender.exe");
#else
path = path.path_join("blender");
#endif
#if defined(MACOS_ENABLED)
if (!FileAccess::exists(path)) {
path = p_path.path_join("Blender");
}
#endif
if (!FileAccess::exists(path)) {
if (r_err) {
*r_err = TTR("Path does not contain a Blender installation.");
}
return false;
}
List<String> args;
args.push_back("--version");
String pipe;
Error err = OS::get_singleton()->execute(path, args, &pipe);
if (err != OK) {
if (r_err) {
*r_err = TTR("Can't execute Blender binary.");
}
return false;
}
int bl = pipe.find("Blender ");
if (bl == -1) {
if (r_err) {
*r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s."), path);
}
return false;
}
pipe = pipe.substr(bl);
pipe = pipe.replace_first("Blender ", "");
int pp = pipe.find(".");
if (pp == -1) {
if (r_err) {
*r_err = TTR("Path supplied lacks a Blender binary.");
}
return false;
}
String v = pipe.substr(0, pp);
r_major = v.to_int();
if (r_major < 3) {
if (r_err) {
*r_err = TTR("This Blender installation is too old for this importer (not 3.0+).");
}
return false;
}
int pp2 = pipe.find(".", pp + 1);
r_minor = pp2 > pp ? pipe.substr(pp + 1, pp2 - pp - 1).to_int() : 0;
return true;
}
uint32_t EditorSceneFormatImporterBlend::get_import_flags() const { uint32_t EditorSceneFormatImporterBlend::get_import_flags() const {
return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION; return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION;
} }
@ -59,8 +120,13 @@ void EditorSceneFormatImporterBlend::get_extensions(List<String> *r_extensions)
Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_t p_flags, Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_t p_flags,
const HashMap<StringName, Variant> &p_options, const HashMap<StringName, Variant> &p_options,
List<String> *r_missing_deps, Error *r_err) { List<String> *r_missing_deps, Error *r_err) {
// Get global paths for source and sink. String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path");
if (blender_major_version == -1 || blender_minor_version == -1) {
_get_blender_version(blender_path, blender_major_version, blender_minor_version, nullptr);
}
// Get global paths for source and sink.
// Escape paths to be valid Python strings to embed in the script. // Escape paths to be valid Python strings to embed in the script.
const String source_global = ProjectSettings::get_singleton()->globalize_path(p_path).c_escape(); const String source_global = ProjectSettings::get_singleton()->globalize_path(p_path).c_escape();
const String sink = ProjectSettings::get_singleton()->get_imported_files_path().path_join( const String sink = ProjectSettings::get_singleton()->get_imported_files_path().path_join(
@ -152,10 +218,18 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
parameters_map["export_tangents"] = false; parameters_map["export_tangents"] = false;
} }
if (p_options.has(SNAME("blender/animation/group_tracks")) && p_options[SNAME("blender/animation/group_tracks")]) { if (p_options.has(SNAME("blender/animation/group_tracks")) && p_options[SNAME("blender/animation/group_tracks")]) {
if (blender_major_version > 3 || (blender_major_version == 3 && blender_minor_version >= 6)) {
parameters_map["export_animation_mode"] = "ACTIONS";
} else {
parameters_map["export_nla_strips"] = true; parameters_map["export_nla_strips"] = true;
}
} else {
if (blender_major_version > 3 || (blender_major_version == 3 && blender_minor_version >= 6)) {
parameters_map["export_animation_mode"] = "ACTIVE_ACTIONS";
} else { } else {
parameters_map["export_nla_strips"] = false; parameters_map["export_nla_strips"] = false;
} }
}
if (p_options.has(SNAME("blender/animation/limit_playback")) && p_options[SNAME("blender/animation/limit_playback")]) { if (p_options.has(SNAME("blender/animation/limit_playback")) && p_options[SNAME("blender/animation/limit_playback")]) {
parameters_map["export_frame_range"] = true; parameters_map["export_frame_range"] = true;
} else { } else {
@ -268,67 +342,8 @@ void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, Li
/////////////////////////// ///////////////////////////
static bool _test_blender_path(const String &p_path, String *r_err = nullptr) { static bool _test_blender_path(const String &p_path, String *r_err = nullptr) {
String path = p_path; int major, minor;
#ifdef WINDOWS_ENABLED return _get_blender_version(p_path, major, minor, r_err);
path = path.path_join("blender.exe");
#else
path = path.path_join("blender");
#endif
#if defined(MACOS_ENABLED)
if (!FileAccess::exists(path)) {
path = path.path_join("Blender");
}
#endif
if (!FileAccess::exists(path)) {
if (r_err) {
*r_err = TTR("Path does not contain a Blender installation.");
}
return false;
}
List<String> args;
args.push_back("--version");
String pipe;
Error err = OS::get_singleton()->execute(path, args, &pipe);
if (err != OK) {
if (r_err) {
*r_err = TTR("Can't execute Blender binary.");
}
return false;
}
int bl = pipe.find("Blender ");
if (bl == -1) {
if (r_err) {
*r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s"), path);
}
return false;
}
pipe = pipe.substr(bl);
pipe = pipe.replace_first("Blender ", "");
int pp = pipe.find(".");
if (pp == -1) {
if (r_err) {
*r_err = TTR("Path supplied lacks a Blender binary.");
}
return false;
}
String v = pipe.substr(0, pp);
int version = v.to_int();
if (version < 3) {
if (r_err) {
*r_err = TTR("This Blender installation is too old for this importer (not 3.0+).");
}
return false;
}
if (version > 3) {
if (r_err) {
*r_err = TTR("This Blender installation is too new for this importer (not 3.x).");
}
return false;
}
return true;
} }
bool EditorFileSystemImportFormatSupportQueryBlend::is_active() const { bool EditorFileSystemImportFormatSupportQueryBlend::is_active() const {

View File

@ -43,6 +43,9 @@ class ConfirmationDialog;
class EditorSceneFormatImporterBlend : public EditorSceneFormatImporter { class EditorSceneFormatImporterBlend : public EditorSceneFormatImporter {
GDCLASS(EditorSceneFormatImporterBlend, EditorSceneFormatImporter); GDCLASS(EditorSceneFormatImporterBlend, EditorSceneFormatImporter);
int blender_major_version = -1;
int blender_minor_version = -1;
public: public:
enum { enum {
BLEND_VISIBLE_ALL, BLEND_VISIBLE_ALL,