How to load glb-model from the internet and put it into the scene?

Godot Version

4.2

Question

In my editor plugin I try to load glb-model and put in on the scene

var glb_scene:PackedScene = load("https://mysite/mymodel.glb")
var glb_model:Node3D = glb_scene.instantiate()
add_child(glb_model)

It works for local resources (res://…), but doesn’t work if http/https (Resource file not found)

You can’t use load() for that. load() only works with imported Resource files in res:// You’ll need to use a HTTPRequest node to download the file and then load it with any of the methods listed here Runtime file loading and saving — Godot Engine (stable) documentation in English

1 Like

Do I correct understand that there is no way to load glb-model and put it in the scene without saving it to the file (by RAM only)?

Anyway, If I try to save my model to the file

...

$HTTPManager.job("https://mysite/mymodel.glb").on_success(self.downloadModelSuccess).fetch()

...

func downloadModelSuccess(result):
    # result = <RefCounted#-9223367904727083315>
    var file = FileAccess.open("src://scenes/model.glb", FileAccess.WRITE)
    file.store_buffer(result)

I have the error “Attempt to call function ‘store_buffer’ in base ‘null instance’ on a null instance.”

Do yo have some working example?

P.S. I tried to use native HTTP client and checked that I get PackedByteArray but the same issue

You can load it directly from a buffer with GLTFDocument.append_from_buffer()

src:// is not a valid notation, you’ll need to use user:// or the full path in your exported game/app because res:// is read-only and you can’t write in it in an exported project. More info here File paths in Godot projects — Godot Engine (stable) documentation in English

Also, the path to the file needs to exists before being able to open it.

No, sorry.

1 Like

Yes, src:// instead of res:// is my fault, but the source of the problem was that the scenes directory didn’t exist in my file system. I thought that Godot should create it, but not… (Contributors, fix it plz)

So I made the working example with file saving

var editorRootScene
var isModelLoading = false

func _ready():
	editorRootScene = plugin.get_editor_interface().get_edited_scene_root()
	
func _process(delta):
	if isModelLoading and not editor_fs.is_scanning():
		isModelLoading = false
		self.loadModel()

func downloadModelSuccess(result, response_code, headers, body):
	var file = FileAccess.open("res://scenes/model.glb", FileAccess.WRITE)
	
	if file == null:
		printt("FileAccess error: ", FileAccess.get_open_error())
		
	file.store_buffer(body)
	file.close()
	editor_fs.scan_sources() # or scan()?
	isModelLoading = true
	
func loadModel():
	var glb_scene:PackedScene = load("res://scenes/model.glb")
	var glb_model:Node3D = glb_scene.instantiate()
	
	editorRootScene.add_child(glb_model)
	glb_model.set_owner(editorRootScene)
	glb_model.translate(Vector3(0.0, 0.0, -2.0))

...

And a few more questions:

  1. Which is preferable in my case scan or scan_sources for importing my model? Maybe there is some better way to import assets?
  2. Why do I need GLTFDocument, GLTFState from article above if I can implement loadModel other way (see the example)? Or it is only for loading without file saving (using appenf_from_buffer)?
  3. Can we speed up the import of assets? I’m not trying to use appenf_from_buffer yet. Does it faster?

I’m not sure.

You only need it for runtime loading of a GLTF file. I didn’t see that this was for an editor plugin. You can use your method for that.

No, there’s no way to speed up the process that I’m aware of.

1 Like

So, I trying to use GLTFDocument and GLTFState:

var doc = GLTFDocument.new()
var state = GLTFState.new()
var error = body == null ? doc.append_from_file("res://scenes/model.glb", state) : doc.append_from_buffer(body, "", state)
if error == OK:
	for gltfMesh in state.get_meshes(): 
		var glb_model = MeshInstance3D.new()
		glb_model.mesh = gltfMesh.get_mesh().get_mesh()
			
		editorRootScene.add_child(glb_model)
		glb_model.set_owner(editorRootScene)	

If I have body == null and imported glb-file inside the res://scenes/model.glb it works correct and I see the textured MeshInstance3D inside my scene.
If I use model data inside the body I see the mesh only without textures etc. Why append_from_file and append_from_buffer work differently?

Upd. Some how I knew that the ImporterMeshInstance3D (which is in the scene from the GLTFDocument) is replaced by the MeshInstance3D during the import process. But I don’t have an import process in case of reading GLB data from RAM. So how do you convert ImporterMeshInstance3D to MeshInstance3D? I’m able to use the mesh, but I’m losing materials, etc.

Short PoC for several ways how to load models from the internet.

Common code:

var editor_fs = EditorInterface.get_resource_filesystem()
var editorRootScene
var isModelLoading = false

func _ready():
	editorRootScene = plugin.get_editor_interface().get_edited_scene_root()

# Download model by HTTP and run `downloadModelSuccess` if OK
func downloadModel():
	var http_request = HTTPRequest.new()
	add_child(http_request)
	http_request.request_completed.connect(self.downloadModelSuccess)
	
	var error = http_request.request("https://mysite/mymodel.glb")
	if error != OK:
		push_error("An error occurred in the HTTP request.")

func placeModelToEditorScene(model):
	editorRootScene.add_child(model)
	model.set_owner(editorRootScene)
	model.translate(Vector3(0.0, 0.0, -1.0))

Using the model through the file (with “(Re) Import assets” progress dialog)

func _process(delta):
	if isModelLoading and not editor_fs.is_scanning():
		isModelLoading = false
		self.loadModelFromFileAsScene()
		#self.loadModelFromFileByGLTFDocument() # Use it instead of loadModelFromFileAsScene if you want to use GLTFDocument way

func downloadModelSuccess(result, response_code, headers, body):
    extractModelToFile(body)
	
func extractModelToFile(body):
	var file = FileAccess.open("res://scenes/model.glb", FileAccess.WRITE)
	
	if file == null:
		printt("FileAccess error: ", FileAccess.get_open_error())
		
	file.store_buffer(body)
	file.close()
	editor_fs.scan_sources() # or scan()? # Extract textures
	isModelLoading = true

func loadModelFromFileAsScene():
	var glb_scene:PackedScene = load("res://scenes/model.glb")
	var glb_model:Node3D = glb_scene.instantiate()
	
	self.placeModelToEditorScene(glb_model)
		
func loadModelFromFileByGLTFDocument():	
	var doc = GLTFDocument.new()
	var state = GLTFState.new()
	# state.set_handle_binary_image(GLTFState.HANDLE_BINARY_EMBED_AS_UNCOMPRESSED) # setup if you want to use the model without scan resources and extract textures
	var error = doc.append_from_file("res://scenes/model.glb", state)
	
	if error == OK:
		print("Found scene: ", state.get_scene_name(), ", uniques_names: ", state.get_unique_names())
		var glb_scene: Node3D = doc.generate_scene(state)
		var glb_importer_model: ImporterMeshInstance3D = glb_scene.get_child(0)
		var glb_importer_model_mesh: ImporterMesh = glb_importer_model.get_mesh()
		var glb_model: MeshInstance3D = MeshInstance3D.new()
		
		#glb_model.name = glb_importer_model.get_name()
		glb_model.mesh = glb_importer_model_mesh.get_mesh()
		glb_model.material_override = glb_importer_model_mesh.get_surface_material(0)
		
		self.placeModelToEditorScene(glb_model)
				
	else:
		print("Couldn't load glTF scene (error code: %s)." % error_string(error))

Using the model through the RAM buffer (faster)

func downloadModelSuccess(result, response_code, headers, body):
    loadModelFromBufferByGLTFDocument(body)

func loadModelFromBufferByGLTFDocument(body):
	var doc = GLTFDocument.new()
	var state = GLTFState.new()
    state.set_handle_binary_image(GLTFState.HANDLE_BINARY_EMBED_AS_BASISU) # Fixed in new Godot version (4.3 as I see) https://github.com/godotengine/godot/blob/17e7f85c06366b427e5068c5b3e2940e27ff5f1d/scene/resources/portable_compressed_texture.cpp#L116
	
	var error = doc.append_from_buffer(body, "", state)
	
	if error == OK:
		var glb_importer_model: GLTFMesh = state.get_meshes()[0]
		var glb_importer_model_mesh: ImporterMesh = glb_importer_model.get_mesh()
		var glb_model: MeshInstance3D = MeshInstance3D.new()
		
		glb_model.mesh = glb_importer_model_mesh.get_mesh()
		glb_model.material_override = state.get_materials()[0]

		self.placeModelToEditorScene(glb_model)
			
	else:
		print("Couldn't load glTF scene (error code: %s)." % error_string(error))
2 Likes

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