godotengine / godot

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

Importing blend files with textures not in project folder fails #74037

Open Jummit opened 1 year ago

Jummit commented 1 year ago

Godot version

v4.0 ec8475af5eb61e988b389d032adc548f6ef39be6

System information

Linux, Gnome on Arch

Issue description

When importing blend files which use textures outside the Godot project folder, they are copied to .godot/imported but an error message says they can't be used:

Resource file not found: res://Folder/In/Home/CookieDough/cookie_dough_albedo.png.
  Can't find file 'res://.godot/imported/cube-872180318f08982aecd5834ac734b268_cookie_dough_albedo.png'.
  No loader found for resource: res://.godot/imported/cube-872180318f08982aecd5834ac734b268_cookie_dough_albedo.png.
  modules/gltf/gltf_document.cpp:3276 - glTF: Image index '0' couldn't be loaded with the name: cookie_dough_albedo. Skipping it.

res://.godot/imported/cube-872180318f08982aecd5834ac734b268_cookie_dough_albedo.png exists in this case.

Steps to reproduce

  1. Create a blend file using a texture outside the project directory
  2. Place it in the Godot project

Minimal reproduction project

BlendImportExternalTextures.zip

The errors are shown immediately after opening the project.

lyuma commented 1 year ago

The original blend file references ../icon.png one level above the project directory.

The resulting .gltf in .godot/imported has this, which is correct given the original path.

    "images" : [
        {
            "mimeType" : "image/png",
            "name" : "icon.png",
            "uri" : "../../../icon.png"
        }
    ],

Godot then resolves that to res://icon.png since ../ in a path is not allowed. Copying icon.png into the root of the project seems to work as a workaround because of this reason.

The question is if this is a bug, and if perhaps there is a better strategy to deal with blend files with absolute or bad relative paths.

Jummit commented 1 year ago

The GLTF import solves this by placing the textures next to the model file. Maybe the blend import could do something like that as well?

philipp-gaddi commented 1 year ago

hello, i'm looking kinda into this and after some debugging i found, in the file godot/editor/editor_file_system.cpp in the _find_file function at line 1368, there's a check for hidden folders

for (int i = 0; i < path.size(); i++) {
        if (path[i].begins_with(".")) {
            return false;
        }

as far as i can tell, the textures are imported into the .godot/imported folder and at this if statement it aborts the process, because of the .godot. then an error message is issued for the file it couldn't import, although the path is valid (that made me curious).

with a little hack (cuz i don't know if that behavior is intended or not), the importing works.

for (int i = 0; i < path.size(); i++) {
        if (path[i].begins_with(".") && !path[i].begins_with(".godot")) {
            return false;
        }

although now i get an alert every time i start a scene with assets from a blend file with the message "Requested file format unknown: blend".

does this help to solve this issue? can someone confirm it. i was using godot 4 master branch.

Jummit commented 1 year ago

does this help to solve this issue? can someone confirm it. i was using godot 4 master branch.

Maybe it solves the issue, but I don't think imported assets should be re-imported again. I'd say the best solution would be to make it possible to ignore that the file is inside a hidden directory and replace the relative paths with paths to res://.godot/imported.

I would implement that, but I don't know enough about the asset importing process to make a clean implementation.

philipp-gaddi commented 1 year ago

... I'd say the best solution would be to make it possible to ignore that the file is inside a hidden directory and replace the relative paths with paths to res://.godot/imported.

In Blender you can turn off the setting in the gltf exporter to keep originals, this saves the relative paths from the gltf file location, instead of the global paths to the texture.

When I set that in the editor_scene_importer_blend.cpp, the filepath are looking good. It's just that a loader isn't found for those files. I try to debug this, but maybe this helps someone. if the error really just a missing loader, then adding one or fixing that error would be a preferable fix as too add an exception.

  No loader found for resource: res://.godot/imported/narrowbrick1_normal-ogl.png.
  No loader found for resource: res://.godot/imported/narrowbrick1_albedo.png.
  No loader found for resource: res://.godot/imported/narrowbrick1_arm.png.
philipp-gaddi commented 1 year ago

I found a workaround for current versions, in the project settings under Application/Config, with activated advanced settings. Deactivate the setting "Use Hidden Project Data Directory". Then restart godot and now all the blend files should be textured. I tested that on 4.0.2 stable archlinux build.

So right now i think the _find_files function needs either to consider the setting or just find files also in hidden folders (i don't know enough, to see the reason to exclude hidden folders). In the code decisions are made based on the Boolean this functions returns and right now it's just lying ;) when files are in the hidden project data directory.

philipp-gaddi commented 1 year ago
  No loader found for resource: res://.godot/imported/narrowbrick1_normal-ogl.png.
  No loader found for resource: res://.godot/imported/narrowbrick1_albedo.png.
  No loader found for resource: res://.godot/imported/narrowbrick1_arm.png.

I think i understand now, those messages. Godot needs an my_image.png.import file for each png image you want to import. That is usually generated when you for example drag such a file into the editor filesystem.

These .import files just don't exist for the texture pngs in the import folder after godot executed the blender gltf export, hence the error message "no loader found for ressource ...".

The Godot editor, then proceeds to start the import process for those file (only when you allow hidden folders or the .godot folder) and generates those .import files (and compressed version of the files), and at the end of that process it calls Resource::load(uri) again, only this time it works. That happens in the _parse_image_save_image(...) function of the gltf_document.cpp called at line 3303.

the _find_files(...) function plays are role in the reimport process. If the .godot/imported folder has to be excluded it's maybe a good idea to copy the textures into the project folders, next to the blend file? This would also enable the user to set custom import settings for the texture files.

edit: i forget the fix for it, it's just checking if the .import file exists and if not updating and reimporting the image file, and then call ResourceLoader::load(uri)

akien-mga commented 1 year ago

CC @godotengine/import

Seems like the Blender importer doesn't properly handle textures. They should likely either:

  • Get extracted next to the .blend file so they can be exported like any other PNG in the project, or
  • Get imported directly as .ctex as part of the .blend import workflow, so only the final proprietary asset is included in the .godot folder. But that raises question regarding how to handle changing their import settings / reimporting, etc.
fire commented 1 year ago

I'm trending towards Get extracted next to the .blend file so they can be exported like any other PNG in the project, or like how glb is done.

philipp-gaddi commented 1 year ago
  • Get imported directly as .ctex as part of the .blend import workflow, so only the final proprietary asset is included in the .godot folder. But that raises question regarding how to handle changing their import settings / reimporting, etc.

i like that option, because only the blend file (and its import options) is responsible for updating and changing, instead of multiple files. it would be great if the importer tab for blend files, offers setting the import options for its textures. I can imagine this to be added in the advanced version of the import tab.

An other reason is, that it strikes right at the problem. The error messages, "Can't find file ..." are due, using the Editorfilesystem functions, which reject files in .godot/. This is I believe for the reason, files that are visible in the editor need some treatment, like registering for auto reload etc. this is not necessary for files in the .godot/imported folder, in fact they are dependent on the files in the visible editorfilesystem (e.g. for every or most png a compressed version is created and located in the .godot/imported folder). So it needs functionality that writes .import files and generate ctex for textures that are imported through the blender gltf exporter. The import part can be mostly taken from EditorFileSystem::_reimport_file minus the _find_file and probably the updating part at the end (maybe this can be put into some utility class for import file writing?). I can't remember if this also creates the ctex file.

philipp-gaddi commented 1 year ago

Hi, after giving it some thought and research, i think this could be a solution:

To handle the texture problem during the import process of a blend file you have to import the texture files, when they are encountered during the parsing of the gltf document, happens in /godot/modules/gltf/gltf_document.cpp:3251 and write their .import files. Then the Resourceloader can load those textures. The import will be using the default settings, the first time.

If the user now clicks in the ImportDock on Reimport, we have to overwrite the .import files for the textures with new settings. To find the .import files for the textures, from the my.blend.import file, i suggest to add the filepaths/names in the [deps] field. This way you can easily read them out via a ConfigFile Object (or they are in the params object already).

For this to work, you can use the gen_files, r_missing_deps, etc. parameter in the import function which are passed through to collect dependencies, and we have to add a parameter for it in the GLTFDocument::append_from_file and probably further down. So that at the place where you load the texture files, you can add the paths. If it's done right, the framework will add those path under [deps] at the end of the blend file import process.

The ImportDock or more specific the scene_import_settings class (responsible for the advanced import setttings popup) would need to be extended, to also show the textures for each meterial and offer UI for the params/settings to be manipulated. The settings are saved if you look into blendfile.blend.import files under _subresources, here's an example:

_subresources={
"materials": {
    "brown-leather": {
           "use_external/enabled": false,
           "use_external/path": "res://blendfiles/brown-leather.tres"
                                  }
                     }
}

This is how materials are saved, and you could add a field like "colortexture_name": { params } or an array of textures if that is supported.

This way you can read from the params object _subresources to update the textures .import file params section.

in short steps:

  1. write checks for .import file and import texture files ingltf_document.cpp
  2. write checks for deps in /godot/modules/gltf/editor/editor_scene_importer_blend.cpp in import_scene at line 197
  3. fix or implement the passing of the gen_files/r_missing_deps parameter from scene importer to GLTFDocument::_parse_images
  4. Extend the UI of the advanced settings to offer texture input
  5. The parameters have to be added under _subresources per material

This approach will use the textures that are generated/copied during the blender gltf export, the reason i choose that is, that it creates a consistent situation, the texture files will always be relative to the gltf file. And the paths in the gltf file will be correct (given the setting for the blender importer "export_keep_originals" is set to false). Also it centers the management of the textures on the blend file, like how we can generate collisionshapes, and extract and handle materials in the advanced importer settings.

any thoughts, improvements, do i miss something, i'm really not an expert on the godot framework (but i have fun). i will work on it, the next days.

ZedCode commented 9 months ago

I found a workaround for current versions, in the project settings under Application/Config, with activated advanced settings. Deactivate the setting "Use Hidden Project Data Directory". Then restart godot and now all the blend files should be textured. I tested that on 4.0.2 stable archlinux build.

FYI - I tried this solution and it seems to break on export:

Godot Engine v4.2.stable.official.46dc27791 - https://godotengine.org
Vulkan API 1.3.260 - Forward+ - Using Vulkan Device #0: NVIDIA - NVIDIA GeForce RTX 4070

ERROR: No loader found for resource: res://godot/imported/male-1a0462bf2ed8a75caa3785056f889913_body_male_texture.png.png (expected type: Texture2D)
   at: _load (core/io/resource_loader.cpp:282)
ERROR: Can't load dependency: res://godot/imported/male-1a0462bf2ed8a75caa3785056f889913_body_male_texture.png.png.
   at: parse_variant (core/io/resource_format_binary.cpp:459)
ERROR: Failed loading resource: res://godot/imported/male.blend-1a0462bf2ed8a75caa3785056f889913.scn. Make sure resources have been imported by opening the project in the editor at least once.
   at: _load (core/io/resource_loader.cpp:274)
ERROR: Failed loading resource: res://blends/male/male.blend. Make sure resources have been imported by opening the project in the editor at least once.
   at: _load (core/io/resource_loader.cpp:274)
ERROR: Can't load dependency: res://blends/male/male.blend.
   at: parse_variant (core/io/resource_format_binary.cpp:459)
ERROR: Error when trying to parse Variant.
   at: parse_variant (core/io/resource_format_binary.cpp:503)
ERROR: Error when trying to parse Variant.
   at: parse_variant (core/io/resource_format_binary.cpp:490)
ERROR: Failed loading resource: res://godot/exported/133200997/export-c2c62cb8c73458cd122faf659769dc80-player.scn. Make sure resources have been imported by opening the project in the editor at least once.
   at: _load (core/io/resource_loader.cpp:274)
ERROR: Can't load dependency: res://scenes/player/player.tscn.
   at: parse_variant (core/io/resource_format_binary.cpp:459)
ERROR: Error when trying to parse Variant.
   at: parse_variant (core/io/resource_format_binary.cpp:503)
ERROR: Error when trying to parse Variant.
   at: parse_variant (core/io/resource_format_binary.cpp:490)
ERROR: Failed loading resource: res://godot/exported/133200997/export-3070c538c03ee49b7677ff960a3f5195-main.scn. Make sure resources have been imported by opening the project in the editor at least once.
   at: _load (core/io/resource_loader.cpp:274)
ERROR: Failed loading scene: res://main.tscn.

I'm not sure if I've done something wrong, but after following the steps in this post the editor worked fine, but the exported build had these weird errors.

lyuma commented 9 months ago

If we are getting files showing up in the internal hidden directory like .godot/imported/male-1a0462bf2ed8a75caa3785056f889913_body_male_texture.png.png then something has gone horribly wrong in the importer.

my understanding is the texture files should generally be relative to the file. I noticed we needed to add a bunch of logic to the fbx importet to handle texture references.

but what I am confused about here is I thought we are importing via the glb importer which should already be handling this. How are textures getting into the internal project directory.

All these workarounds suggested in this thread are way too complicated: the code should already exist to handle this correctly. I almost wonder if it could be fixed by passing the correct base_path into the gltf importer (the location of the blend file instead of the imported directory)