Error when loading FBX file in EditorImportPlugin - how to load fbx files?

Godot Version

Godot v4.4.1 stable_mono_win64

Question

I am trying to create an EditorPlugin/EditorImportPlugin to import fbx files. More specifically I want to extract the animations from the files and save them as a .res file.
Normally I go over the advanced import tab select the animation and enable save to file:

I have my EditorPlugin:

[Tool]
public partial class FbxAnimationImportPlugin : EditorPlugin
{
    private FbxAnimationImporter fbxAnimationImporter;

    public override void _EnterTree()
    {
        fbxAnimationImporter = new FbxAnimationImporter();
        AddImportPlugin(fbxAnimationImporter);
    }

    public override void _ExitTree()
    {
        RemoveImportPlugin(fbxAnimationImporter);
        fbxAnimationImporter = null;
    }
}

And the corresponding EditorImportPlugin:

[Tool]
public partial class FbxAnimationImporter : EditorImportPlugin
{
    public override string _GetImporterName()
    {
        return "fbx_animation_importer";
    }
    public override string _GetVisibleName()
    {
        return "FbxAnimationImporter";
    }
    public override string[] _GetRecognizedExtensions()
    {
        return ["fbx"];
    }
    public override string _GetSaveExtension()
    {
        return "res";
    }
    public override string _GetResourceType()
    {
        return "PackedScene";
    }

    public override Error _Import(string sourceFile, string savePath, Dictionary options, Array<string> platformVariants, Array<string> genFiles)
    {
        Resource scene = ResourceLoader.Load(sourceFile, "PackedScene");
        //Doing stuff with the scene and animations...
        return Error.Ok;
    }
}

However I get the error:

ERROR: Failed loading resource: res://assets/protof/FileName.fbx. Make sure resources have been imported by opening the project in the editor at least once.

How can I load the fbx file into a PackedScene so I can access the embedded AnimationPlayer?

This project might give you some pointers: GitHub - RaidTheory/Godot-Mixamo-Animation-Retargeter: Easily Retarget Mixamo Animations to Godot

I found it in this thread a while back and tried it: Mixamo Animations to Godot [Plugin]

1 Like

You should not use an EditorImportPlugin for this as this class is for importing a whole custom resource and not extending importing a resource with an already implemented resource importer.

You should use an EditorScenePostImport script if you only want to do it for specific files.

Or use an EditorScenePostImportPlugin if you want to do it for all files.

Here’s an example using the latter which exposes saving the materials and animations to the Import dock without having to use the Advanced import popup.

@tool
extends EditorScenePostImportPlugin

func _get_import_options(path: String) -> void:
	add_import_option_advanced(TYPE_BOOL,"materials/save_to_file", true, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED)
	add_import_option_advanced(TYPE_STRING, "materials/dir", "", PROPERTY_HINT_DIR)
	add_import_option_advanced(TYPE_BOOL,"animation/save_animations_to_file", false, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED)
	add_import_option_advanced(TYPE_STRING, "animation/animations_dir", "", PROPERTY_HINT_DIR)
	add_import_option("animation/keep_custom_tracks", false)


func _get_option_visibility(path: String, for_animation: bool, option: String) -> Variant:
	var save_animations = get_option_value("animation/save_animations_to_file")
	var save_materials = get_option_value("materials/save_to_file")
	match option:
		"animation/animations_dir":
			return save_animations
		"animation/keep_custom_tracks":
			return save_animations
		"materials/dir":
			return save_materials

	return true


func _pre_process(scene: Node) -> void:
	var out = get_option_value("_subresources")

	var subresources = {}
	iterate(scene, subresources)

	var save_animations = get_option_value("animation/save_animations_to_file")
	var animations_dir = get_option_value("animation/animations_dir")
	var keep_custom_tracks = get_option_value("animation/keep_custom_tracks")

	if save_animations:
		if not EditorInterface.get_resource_filesystem().get_filesystem_path(animations_dir):
			push_warning("The animations path does not exist. Disabling saving animations.")
			save_animations = false
	else:
		animations_dir = ""
		keep_custom_tracks = false

	var save_materials = get_option_value("materials/save_to_file")
	var materials_dir = get_option_value("materials/dir")

	if save_materials:
		if not EditorInterface.get_resource_filesystem().get_filesystem_path(materials_dir):
			push_warning("The materials path does not exist. Disabling saving materials.")
			save_materials = false
	else:
		materials_dir = ""

	var animations = subresources.get("animations", [])
	if not animations.is_empty():
		out["animations"] = {}
		for animation in animations:
			var dir = animations_dir + "/" + animation + ".res"
			out["animations"][animation] = {
				"save_to_file/enabled": save_animations,
				"save_to_file/path": dir,
				"save_to_file/keep_custom_tracks": keep_custom_tracks
			}


func iterate(node:Node, subresources:Dictionary) -> void:
	if node == null:
		return

	if node is AnimationPlayer:
		if not subresources.has("animations"):
			subresources["animations"] = []
		var animations = subresources["animations"]
		animations.append_array(node.get_animation_list())

	if node is MeshInstance3D:
		if not subresources.has("materials"):
			subresources["materials"] = []
		var materials = subresources["materials"]
		materials.append_array(node.mesh)


	for child in node.get_children():
		iterate(child, subresources)
2 Likes

Thank you - this project gave me some ideas I might use in the future. Ultimately I used an EditorScenePostImportPlugin to fix my current issue - will post my solution soon

1 Like

Thank you. An EditorScenePostImportPlugin was exactly what I needed.
My current solution looks like this:

[Tool]
public partial class FbxAnimationImporter : EditorScenePostImportPlugin
{
    public override void _PostProcess(Node scene)
    {
        if ((bool)GetOptionValue("Save Animation to .res"))
        {
            AnimationPlayer animationPlayer = scene.GetNodeOrNull<AnimationPlayer>("AnimationPlayer");
            if (animationPlayer != null)
            {
                string sourcePath = (string)GetOptionValue("file_path_hack");
                foreach (string animationName in animationPlayer.GetAnimationList())
                {
                    Animation animation = animationPlayer.GetAnimation(animationName);
                    string saveFile = sourcePath + "_" + animationName + ".res";
                    ResourceSaver.Save(animation, saveFile);
                }
            }
            else
            {
                GD.PushError("FBX/Scene has NO Animation(Player)");
            }
        }
    }

    public override void _GetImportOptions(string path)
    {
        AddImportOption("Save Animation to .res", false);
        AddImportOption("file_path_hack", path);
    }

    public override Variant _GetOptionVisibility(string path, bool forAnimation, string option)
    {
        if (option.Equals("file_path_hack")) return false;
        return base._GetOptionVisibility(path, forAnimation, option);
    }
   
}

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.