godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
89.5k stars 20.26k forks source link

Godot 4.0 GLTF Importer missing "instanced sub-scenes" storage option from 3.4 (important for asset packs etc.) #56312

Open andyp123 opened 2 years ago

andyp123 commented 2 years ago

Godot version

v4.0.dev.calinou [71616630e]

System information

Windows 10, Ryzen 3700x, 32GB, 3060Ti, Vulkan

Issue description

The GLTF import system has changed substantially between Godot 3.4 and 4.0. Although the 4.0 system has many advantages, one of the more useful import options from the 3.4 system is currently missing.

In Godot 3.4, there is an option to import each object in a GLTF file as a separate scene, which means that you can store an entire set of related assets in a single file and import/reimport them with very little effort. In Godot 4.0, the option to set the storage type is missing, and no extra files are created when a GLTF is imported, which is quite a different experience in terms of ease-of-use compared to 3.4.

Here is the option I am talking about shown in Godot 3.4: godot_importer-sub-scenes

This is the result of this setting in the File Tree: godot_importer-files

The file DefaultShapes.glb is imported using the above settings to automatically create many different sub-scenes. In this case, DefaultShapes contains lots of basic shapes that I can use for blocking out levels, and I'm also running them through a Post Import script to set up collision using convex hulls, boxes, spheres and capsules for each object, but without the option to store objects as individual sub-scenes, none of this works at all.

Godot 4.0 does have the option to store individual objects as Meshes, but this needs to be enabled for each object, and the file path needs to be manually set, which is a lot of extra work. Also, this way you can't add collision data automatically via script because mesh resources do not allow the user to specify collision manually and store it with the mesh, which is one of the reasons that being able to store separate scenes is so valuable.

This seems like a fairly fundamental missing feature between 3.4 and 4.0, so if possible, please add the object storage option back into the basic import settings, and additionally, add an option to set the identity transform on each object during import, as it's convenient to lay out assets in Blender so that they can be viewed and edited together, but the root transform should be reset on import unless we are importing a full scene or level. These issues are related, and there is another request for this feature here: #13980

Steps to reproduce

To compare the feature between Godot 3.4 and Godot 4.0 do the following (note that I've provided a test file attached to this bug report)

3.4

  1. Create a new empty project
  2. Drag in the attached file (DefaultShapes.glb)
  3. In the import settings tab, change 'Storage' to 'Instanced Sub-scenes'
  4. Hit the reimport button. This will create a single scene for each object in the gltf file.

4.0

  1. Create a new empty project
  2. Drag in the attached file
  3. Note that in neither the import settings, nor the advanced settings is an option to import objects as individual scenes. Perhaps this feature would make sense as an 'Import as:' preset, but currently it is missing.

Minimal reproduction project

DefaultShapes.zip

fire commented 2 years ago

Do you think you can make a proposal for an 3d import toggle to:

Godot 4.0 store individual objects as Meshes, but this needs to be enabled for the entire scene, and the file path needs to be set to a folder.

andyp123 commented 2 years ago

Do you want make a feature proposal for a way to make the storage location for meshes be easier to set up? Sure, I can do that. Just to be clear, this would be separate to this issue with missing instanced sub-scenes option for import, correct?

Because I initially misunderstood the question, I made a mock-up of how I might expect the instanced sub-scenes option to be added back. It could either be under a "Storage", as with Godot 3.4, or perhaps it could be a different preset, like this: image

fire commented 2 years ago

From the point of view of the gltf2 importer it sends a node tree to the scene system. Since it can't write to the file system at this point there's no way to do missing instanced sub-scenes here by design.

From the scene import system we want to propose a feature enabled for the entire scene where each mesh is saved, and the file path needs to be set to a folder. This is after auto LODing, collision generation, post import scripts and other processes.

JoanPotatoes2021 commented 2 years ago

I just bumped into this and noticed, it seems atm that importing gltfs in 4.0 alpha there isn't a way to set meshes automatically to be exported as individual files after import? This would be extremely useful, for instance, allowing us to do wardrobe systems for the mesh files, systems that allows us to easily load meshes to do a armor change or weapon part change, similar to this:

https://godotforums.org/discussion/26026/how-to-correctly-swap-the-mesh-of-a-meshinstance-when-its-also-parented-to-a-skeleton-node

I would think this is related to this issue, pointing a folder or allowing it by default to create a folder with the name of the gltf, and inside separate what the user wants, in the case of meshes they could use the naming that comes from blender already.

fire commented 2 years ago

Anything that creates dependencies introduces what I call the ".material" problem from godot 3. There's a multi step reproduction case that cause flaws.

https://github.com/godotengine/godot/issues/42278

Will need some new approaches.

JoanPotatoes2021 commented 2 years ago

Well, some type of dependencies are good I suppose, you update the source and all dependencies are updated as well. If you are not meant to touch some kind of instanced data we could block it or warn that might break linked content. Just to contextualize this is a big problem in blender right now with the asset manager as well. I wouldn't mind if godot used it's own naming convention to automate some import functionality, right now this is a problem for me not being able to automatically import all meshes to individual files into a folder, preferable named to it's .gltf source.

Calinou commented 2 years ago

it seems atm that importing gltfs in 4.0 alpha there isn't a way to set meshes automatically to be exported as individual files after import?

This can be done in the Advanced Import Settings dialog on the Meshes tab. You can save individual Mesh resources to .res files (or .tres, but it's not recommended as it slows down saving and loading).

JoanPotatoes2021 commented 2 years ago

I see, yea it works, still it would be pretty nice if this was possible to be done with all meshes from a .gltf automatically from a single tickbox, the names would be gathered from the source, ak blender's mesh data name, currently as you mentioned, one have to select all meshes tick save to file, and you have to type a name, otherwise it save as .res by default, tested it.

individualgltfimport

Maybe we can do that through import scripts? Although I am sure in later versions of 4.0 we will have something for this, many people want to import various assets bundled together, having automatically they being split is a must, something to look in for in the future I guess.

JoanPotatoes2021 commented 2 years ago

This issue is directly linked now to this problem #28275 regarding the issue on how keep custom tracks on imported animation tracks, as a workaround one can develop tools to edit animations after import, an example of needing custom tracks would be creating footsteps sounds, which are easily done from the animation player using call tracks. I guess should be worth mentioning this as well: https://github.com/godotengine/godot-proposals/issues/4296#issue-1182621274

fire commented 2 years ago

The animation library feature was merged recently to solve animation editing troubles.

Geekotron commented 2 years ago

I also used instanced sub-scenes in 3.5. Since they aren't supported (yet) I just created a post-import script to handle it. Seems to work fine for both GLB & Blender import.

using System;
using Godot;

[Tool]
public partial class SceneSplit : EditorScenePostImport
{
    public override Godot.Object _PostImport(Node scene)
    {
        Console.WriteLine($"Starting split of {scene.Name}");

        var path = GetSourceFile().Substring(0, GetSourceFile().LastIndexOf('/'));

        foreach (var child in scene.GetChildren()) {
            var meshInstance = child as MeshInstance3D;
            if (meshInstance == null) {
                Console.WriteLine($"Skipping child {child.Name}, not a mesh");
            }

            // I like all my objects to be at origin, even if they're spread out in the source file.
            meshInstance.Position = Vector3.Zero;

            Console.WriteLine($"Saving child {child.Name}");
            SetOwnerOnNodeDescendents(child, child);

            var packed = new PackedScene();
            packed.Pack(child);
            ResourceSaver.Save(packed, FileNameForScene(child.Name, path));
    }

        new EditorPlugin().GetEditorInterface().GetResourceFilesystem().Scan();

    return scene;
    }

    private string FileNameForScene(string nodeName, string path) {
        return $"{path}/{nodeName}.scn";
    }

    private void SetOwnerOnNodeDescendents(Node node, Node owner) {
        foreach (var child in node.GetChildren()) {
            child.Owner = owner;
            SetOwnerOnNodeDescendents(child, owner); // recurse through all descendents
        }
    }
}

BTW Godot devs, I just started with the Godot 4 beta today and I'm loving the control in the 3d model importer dialog. And the direct .blend import. Thank you!

andyp123 commented 2 years ago

I didn't know this was possible, but I managed to port the above c# script by @Geekotron to GDScript and it works fine.

@tool
extends EditorScenePostImport

func _post_import(scene):
    var path = get_source_file()
    print("Running post import script on file '{file}'".format({'file': path}))

    path = path.substr(0, path.rfind('/'));

    # Get valid mesh objects
    for object in scene.get_children():
        if !(object is MeshInstance3D):
            continue

        set_owner_on_node_descendents(object, object)

        # Save each object to a file based on its object name       
        var packed = PackedScene.new()
        packed.pack(object)
        var scene_path = "{path}/{name}.scn".format({'path': path, 'name': object.name})
        print(object.name, " -> ", scene_path)
        ResourceSaver.save(packed, scene_path)

    return scene

func set_owner_on_node_descendents(node : Node, owner : Node):
    for child in node.get_children():
        child.owner = owner
        set_owner_on_node_descendents(child, owner)
donn-xx commented 1 year ago

This issue was the closest I could find to one I was going to post as new.

My basic concern is to not duplicate meshes (well, all that array data).

Say the imported glb/gltf file has 1 mesh. If you make a new packed scene (like those cool scripts up ^ there) then you have a 2nd mesh in the .scn file. A copy.

Is there any way (in editor) to make a meshInstance3D who's mesh refers directly to the glb/gltf file?

For example, I'd export a .glb with a cube and a sphere. I then want to pull the sphere into scene01 and the cube into scene02 and I don't want the meshes (etc.) to appear in either scene source file, only a resource pointing to the glb file.

As things stand it feels like the glb/gltf files are sitting at the bottom of all things and taking-up memory. Am I on the wrong track?

NetroScript commented 1 year ago

I am currently migrating one of my projects to Godot 4, and I need to access the mesh and materials of imported glTF files. (I have a plugin which manages the import, and more or less collects mesh instances, and then uses them for instanced rendering if specific scenes reuse base components). In Godot 3 I just used the option to import the mesh and material separately, which doesn’t exist anymore. And ResourceLoader does not support loading only the sub resource (like a mesh) from a scene (or if it does, I was not able to find out how to do it).

But the import scripts above gave me an idea how to achieve the same in Godot 4.0 with an import plugin. So I also wanted to share it here, should someone else want to do the same thing as me.

@tool
extends EditorScenePostImport

func _post_import(scene):
    var path := get_source_file()
    print("Running post import script on file '{file}'".format({'file': path}))

    var file_name := path.get_file()

    path = path.substr(0, path.rfind('/'))

    # Have a subfolder to store all the individual meshes and materials for each imported glTF scene
    # Prefix it with _import to not clutter the directories as much
    var new_subfolder := path + "/_import/" + file_name + "/"

    # if the subfolder for resources already exists, clear it
    recursively_delete_dir_absolute(new_subfolder)

    # Now create the empty subfolder
    DirAccess.make_dir_recursive_absolute(new_subfolder)

    var mesh_count : int = 0

    # Get valid mesh objects
    for object in get_all_children_recursive(scene):
        if !(object is MeshInstance3D):
            continue

        # Reference to the mesh
        var mesh : MeshInstance3D = object 

        # First iterate all materials of the mesh and save them to disk
        for mat in mesh.mesh.get_surface_count():

            # Path where to store the extracted material
            var surface_path = new_subfolder + str(mesh_count) + "_" + str(mat) + "_" + str(mesh.name) + ".res"

            var material := mesh.mesh.surface_get_material(mat)

            # Store the material to disk as a resource file
            if ResourceSaver.save(material, surface_path, ResourceSaver.FLAG_CHANGE_PATH) == OK:

                # Now using a resource loader load the same material and overwrite the old one
                var new_material = ResourceLoader.load(surface_path) as Material
                # This prevents the imported scene from storing the material as a sub resource
                mesh.mesh.surface_set_material(mat, new_material)
                print("Saved Material and overwrote resource: " + surface_path)

        # Now save the mesh of our mesh object to disk
        var mesh_path = new_subfolder + str(mesh_count) + "_" + str(mesh.name) + ".res"

        if ResourceSaver.save(mesh.mesh, mesh_path, ResourceSaver.FLAG_CHANGE_PATH) == OK:

            # Now using a resource loader load the same mesh again and overwrite our scene mesh with it
            var new_mesh = ResourceLoader.load(mesh_path)
            # This prevents the imported scene from storing the mesh as a sub resource
            mesh.mesh = new_mesh
            print("Saved Mesh and overwrote resource: " + mesh_path)
            mesh_count += 1

    return scene

func get_all_children_recursive(node: Node, include_self: bool = false) -> Array:
    var nodes : Array = []

    if include_self:
        nodes.append(node)

    for N in node.get_children():
        if N.get_child_count() > 0:
            nodes.append(N)
            nodes.append_array(get_all_children_recursive(N))
        else:
            nodes.append(N)

    return nodes

func recursively_delete_dir_absolute(folder: String) -> bool:
    # Delete folder if it exists
    if DirAccess.dir_exists_absolute(folder):
        # Open folder
        var access := DirAccess.open(folder)
        # Delete all files within the folder
        access.list_dir_begin() 
        var file_name = access.get_next()
        while file_name != "":
            if not access.current_is_dir():
                access.remove(file_name)
            else:
                # If it contains more folders, first delete that folder recursively
                recursively_delete_dir_absolute(folder + "/" + file_name)

            file_name = access.get_next()
        # Delete the now empty folder
        if DirAccess.remove_absolute(folder) != OK:
            return false
        return true
    return false

Edit:

By merging my code, and the Code posted above, @donn-xx should also be able to do what they wanted.

HappyLDE commented 5 months ago

Is this done yet?

I want to import a whole car made in Blender through one .blend file and use it's meshes into an existing car scene, and be able to duplicate a tire as an instance so that they both get updated upon reimport when changed in Blender.