Data duplication between glb and res files when using Set Mesh Save Paths

Godot Version

Godot 4.2.1 .NET

Question

I’m currently tring to define the asset workflow of a new project. When I import glb file, I need to use the Set Mesh Save Paths option to allow to reference meshes independantly from various scene. Then assets are automatically updated when the glb file change. This is neet, but when working with other peoples, they see the res files modified after pulling the project (probably the glb file reimporting the res files).

  • Do I need to remove the res files from the version control ? (to avoid data duplication and conflict after reimport)

After some months working with res files un-versioned, we are finally going back to a strategy where any glb files AND res files are versioned.

Here are the issues we encountered with the ignored res file approach:
Res uid are not stable, so this approach generates a lot of warning an asset rewriting : see issue 68672. Godot still find assets when using the path fallback.
When an asset is deleted from a glb files, it just stop being imported. But when you have imported it before, since it is ignored by git, the asset is still in the asset folder.

This problem lead to errors when an asset as been deleted by error (the project continue to works, and crash for new repository checkout). It is also an issue for build size since assets will be packed in an export. It also causes some issues when changing from a branch to another.

I continue to update this log, I hope it will be useful for anyone at some point.

On our new project, we stopped using SetMeshSavePaths. We directly use glb files exported as scene. When we want something specific, we create a custom glb importer. It works a lot better, next update after testing it on Godot 4.4 to see if uid issues are fixed by the new meta files.

1 Like

Can you share parts of your importer script? I also run into these hurdles and appreciate the updates.

What we wanted was to have multiple objects in the same blender session. Initially we used to export mesh to access the different part of the glb independently. Now we added a script in Blender to export the meshes independently:

import re
from pathlib import Path

import bpy
from mathutils import Vector

# Path to godot project
project_path = Path(bpy.path.abspath("//")) / ".." / ".." / "project"
project_path = project_path.resolve()
if not project_path.exists():
    raise Exception(f"project folder {project_path} does not exists")

# Export collections that start with this prefix
export_prefix = "export:"
snake_case_regex = r"^[a-z]+(?:_[a-z0-9]+)*$"

for collection in bpy.data.collections:
    if not collection.name.startswith(export_prefix):
        continue

    # name example: export:environment/level_design
    export_folder = collection.name[len(export_prefix) :].split("/")
    output_folder = project_path / "view"
    for folder in export_folder:
        if re.search(snake_case_regex, folder) is None:
            raise Exception(f"invalid folder nomenclature: '{folder}' generated from collection {collection.name}")
        output_folder /= folder

    file_prefix = export_folder[len(export_folder) - 1]

    output_folder.mkdir(exist_ok=True)
    print(f"export collection '{collection.name}' to folder {output_folder}...")

    for object in collection.objects:
        # Select object.
        bpy.ops.object.select_all(action="DESELECT")
        object.select_set(True)
        for child in object.children_recursive:
            child.select_set(True)

        # Check filename nomenclature.
        filename = f"{file_prefix}_{object.name}"
        filepath = output_folder / f"{filename}.glb"
        if re.search(snake_case_regex, filename) is None:
            raise Exception(f"invalid file nomenclature: '{filename}' generated from object {collection.name}/{object.name}")

        # Move object to (0,0,0)
        last_location = object.location.copy()
        object.location = Vector((0, 0, 0))

        print(f"  export {object.name} at {filepath}...")
        bpy.ops.export_scene.gltf(
            filepath=str(filepath.resolve()),
            use_selection=True,
            export_image_format="NONE",
            export_animations=False,
            export_apply=True,
            export_vertex_color="ACTIVE",
        )

        # Move object back to its original location.
        object.location = last_location

print("export succeeded")

Using that script, we can organize the blender files how we want, then it is exported as independant glb in Godot in the folder architecture specified in the collection naming.

One of the issue we had was to be able to share materials between glb and modify their properties, for example to add some shaders. We extract materials of glb in a specific folder, then we re-link them using the following importer:

[Tool]
public partial class GltfImporterExtension : GltfDocumentExtension
{
    public override Error _ImportPreflight(GltfState state, string[] extensions)
    {
        if (state.BasePath.StartsWith("res://path/to/allow"))
        {
            return Error.Ok;
        }
        
        return Error.Skip;
    }

    public override Error _ImportPost(GltfState state, Node root)
    {
        foreach (ImporterMeshInstance3D node in root.FindChildren("*", type: nameof(ImporterMeshInstance3D), recursive: true, owned: true))
        {
            // Bind external materials after glb import.
            for (int index = 0; index < node.Mesh.GetSurfaceCount(); ++index)
            {
                string materialName = node.Mesh.GetSurfaceName(index);
                Material material = GD.Load<Material>($"{state.BasePath}/../materials/{materialName}.tres");
                if (material != null)
                {
                    node.Mesh.SetSurfaceMaterial(index, material);
                }
                else
                {
                    Trace.TraceError($"Can't load material: '{materialName}'");
                }
            }
            }
        }

        return Error.Ok;
    }
}

Hope it will be useful, don’t hesitate to ask if the information are incomplete.