Last active
July 14, 2025 20:58
-
-
Save izelnakri/46785412b45aa26bfef143e1ca19ad29 to your computer and use it in GitHub Desktop.
Meta Quest 3 Scene Capture with Godot. Details: https://godotvr.github.io/godot_openxr_vendors/manual/meta/scene_manager.html#requesting-a-scene-capture
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Initial Scene setup: | |
| # Main | |
| # - WorldEnvironment | |
| # - Node3d (which has this script attached) | |
| # -- XROrigin3D | |
| # --- OpenXRFbSceneManager (has Default Scene of static-body-3d.gd with Scene Setup: `setup_scene`) | |
| # --- XRCamera3D | |
| # --- XRController3D (Left) | |
| # --- XRController3D (Right) | |
| extends Node3D | |
| @onready var scene_manager = $XROrigin3D/OpenXRFbSceneManager | |
| func _ready(): | |
| print("_ready call") | |
| scene_manager.openxr_fb_scene_data_missing.connect(_on_scene_data_missing) | |
| scene_manager.openxr_fb_scene_capture_completed.connect(_on_scene_capture_completed) | |
| call_deferred("_start_capture_and_export") | |
| func _on_scene_data_missing(): | |
| scene_manager.request_scene_capture() | |
| func _on_scene_capture_completed(success: bool): | |
| if not success: | |
| print("Capture failed"); | |
| return; | |
| if scene_manager.are_scene_anchors_created(): | |
| scene_manager.remove_scene_anchors() | |
| scene_manager.create_scene_anchors() | |
| await get_tree().create_timer(1.0).timeout # NOTE: wait a bit for mesh processing | |
| print("Exporting meshes...") | |
| _export_meshes() | |
| func _start_capture_and_export(): | |
| print("calling _start_capture_and_export") | |
| scene_manager.request_scene_capture() | |
| # NOTE: This needs optimization in terms of what gets inside the .glb file: | |
| func _export_meshes(): | |
| var mesh_container = Node3D.new() | |
| add_child(mesh_container) | |
| var uuids = scene_manager.get_anchor_uuids() | |
| if uuids.is_empty(): | |
| print("No anchors found. Closing the process..") | |
| return | |
| else: | |
| print("Found %d anchors." % uuids.size()) | |
| var valid_meshes = 0 | |
| for uuid in uuids: | |
| var entity = scene_manager.get_spatial_entity(uuid) | |
| if entity: | |
| print("Processing entity for UUID: %s" % uuid) | |
| # Try to create mesh instance directly - the entity should handle checking capabilities internally | |
| var mesh_instance = entity.create_mesh_instance() | |
| if mesh_instance and mesh_instance.mesh: | |
| # Validate the mesh has actual data | |
| if mesh_instance.mesh.get_surface_count() > 0: | |
| mesh_container.add_child(mesh_instance) | |
| valid_meshes += 1 | |
| print("Added valid mesh for anchor: %s" % uuid) | |
| # Debug: Print mesh info | |
| var mesh = mesh_instance.mesh | |
| print(" Mesh surfaces: %d" % mesh.get_surface_count()) | |
| if mesh.get_surface_count() > 0: | |
| var arrays = mesh.surface_get_arrays(0) | |
| if arrays[Mesh.ARRAY_VERTEX]: | |
| print(" Vertices: %d" % arrays[Mesh.ARRAY_VERTEX].size()) | |
| else: | |
| print("Mesh for anchor %s has no surfaces" % uuid) | |
| else: | |
| print("Failed to create mesh instance for anchor: %s" % uuid) | |
| # Additional debugging - check what type of entity this is | |
| print(" Entity type: %s" % entity.get_class()) | |
| print(" Entity: %s" % str(entity)) | |
| print("Total valid meshes: %d" % valid_meshes) | |
| if valid_meshes == 0: | |
| print("No valid meshes to export!") | |
| return | |
| # Export the meshes | |
| var gltf_state = GLTFState.new() | |
| var gltf_doc = GLTFDocument.new() | |
| var err = gltf_doc.append_from_scene(mesh_container, gltf_state) | |
| if err != OK: | |
| push_error("Failed to append scene: %s" % err) | |
| elif gltf_doc.write_to_filesystem(gltf_state, "user://izel_capture.glb") != OK: | |
| push_error("Failed to write .glb.") | |
| else: | |
| print("Scene exported to: user://izel_capture.glb") | |
| get_tree().quit() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/env bash | |
| PKG_NAME="com.example.myfirstproject" | |
| adb exec-out run-as $PKG_NAME cat files/izel_capture.glb > izel_capture.glb | |
| echo "✅ izel_capture.glb has been saved to your project folder." |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # NOTE: This is essential for the processing to work, maybe needs enhancement | |
| # Initial Scene setup: | |
| # StaticBody3D (which has this script attached) | |
| # - Label3D | |
| # - CollisionShape3D (maybe put another?) | |
| extends StaticBody3D | |
| @onready var label: Label3D = $Label3D | |
| func setup_scene(entity: OpenXRFbSpatialEntity) -> void: | |
| print("Script_for_static_body setup_scene call") | |
| var semantic_labels: PackedStringArray = entity.get_semantic_labels() | |
| label.text = semantic_labels[0] | |
| var collision_shape = entity.create_collision_shape() | |
| if collision_shape: | |
| add_child(collision_shape) | |
| var mesh_instance = entity.create_mesh_instance() | |
| if mesh_instance: | |
| add_child(mesh_instance) | |
| print("Script_for_static_body setup_scene call finish") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment