using GLTFast; using System; using System.Diagnostics; using System.IO; using System.Threading.Tasks; using UnityEngine; public class ModelGenerationUtils : MonoBehaviour { public static ModelGenerationUtils Instance { get; private set; } private void Awake() { Instance = this; } // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } /** * Generate model by starting a new Python subprocess * NOT USED IN LATEST VERSION **/ public async Task GenerateModelAsync(string inputPrompt) { return await Task.Run(() => { // Path to your virtual environment's python.exe string pythonExe = @"D:\users\henrisel\DeltaVR3DModelGeneration\3d-generation-pipeline\.venv\Scripts\python.exe"; // Path to your Python script string scriptPath = @"D:\users\henrisel\DeltaVR3DModelGeneration\3d-generation-pipeline\start_pipeline.py"; // Arguments to pass to the script string arguments = $"{scriptPath} --prompt \"{inputPrompt}\""; ProcessStartInfo psi = new ProcessStartInfo { FileName = pythonExe, Arguments = arguments, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true }; using (Process process = new Process()) { process.StartInfo = psi; process.OutputDataReceived += (sender, e) => UnityEngine.Debug.Log(e.Data); process.ErrorDataReceived += (sender, e) => UnityEngine.Debug.LogError(e.Data); process.Start(); string output = process.StandardOutput.ReadToEnd(); string error = process.StandardError.ReadToEnd(); process.WaitForExit(); // Extract model path from output foreach (string line in output.Split('\n')) { if (line.StartsWith("Generated 3D model file: ")) { return line.Replace("Generated 3D model file: ", "").Trim(); } } throw new Exception("Failed to generate 3D model!"); } }); } /** * Spawn model stored on disk * NOT USED IN LATEST VERSION * **/ public async Task SpawnModel(string modelPath) { var gltf = new GltfImport(); bool loadSuccess = await gltf.Load(modelPath); if (loadSuccess) { string objectName = Path.GetFileName(modelPath); return await SpawnModel(gltf, objectName); } throw new Exception("Failed to load GameObject from model" + modelPath); } public async Task SpawnModel(byte[] modelBinary, float boundingSphereDiameter = 0) { var gltf = new GltfImport(); bool loadSuccess = await gltf.Load(modelBinary); if (loadSuccess) { return await SpawnModel(gltf, "GeneratedModel", boundingSphereDiameter); } throw new Exception("Failed to load GameObject from binary!"); } public async Task SpawnModel(GltfImport gltfImport, string objectName, float boundingSphereDiameter = 0) { GameObject spawningParent = new GameObject("Parent-" + objectName); bool spawnSuccess = await gltfImport.InstantiateMainSceneAsync(spawningParent.transform); if (spawnSuccess) { Transform spawnedObjectWorldTransform = spawningParent.transform.GetChild(0).transform; GameObject spawnedObjectBody = spawnedObjectWorldTransform.GetChild(0).transform.gameObject; MeshCollider collider = spawnedObjectBody.AddComponent(); collider.convex = true; MeshRenderer renderer = spawnedObjectBody.GetComponent(); renderer.material.SetFloat("metallicFactor", 0); AdjustScale(spawnedObjectBody, collider, boundingSphereDiameter); spawnedObjectBody.name = objectName; return spawnedObjectBody; } throw new Exception("Failed to spawn GameObject " + objectName); } private async void AdjustScale(GameObject obj, MeshCollider collider, float boundingSphereDiameter) { // Wait one frame to ensure collider cooking & bounds are valid await Task.Yield(); // --- Compute current bounding sphere diameter (from world AABB) --- Bounds b = collider.bounds; // world-space AABB float currentDiameter = 2f * b.extents.magnitude; if (boundingSphereDiameter <= 0f) { UnityEngine.Debug.Log("Not adjusting scale"); return; } if (currentDiameter <= Mathf.Epsilon) throw new Exception("Spawned mesh has zero-sized bounds; cannot scale to target diameter."); // --- Compute and apply uniform scale --- float scaleFactor = boundingSphereDiameter / currentDiameter; obj.transform.localScale *= scaleFactor; // Give Unity a moment to recompute bounds after scaling, then do a corrective pass await Task.Yield(); // Corrective pass (optional): re-measure and nudge scale to reduce residual error Bounds b2 = collider.bounds; float newDiameter = 2f * b2.extents.magnitude; // Only correct if off by more than a tiny epsilon (1%) const float tolerance = 0.01f; // 1% float err = Mathf.Abs(newDiameter - boundingSphereDiameter) / boundingSphereDiameter; if (err > tolerance && newDiameter > Mathf.Epsilon) { float correction = boundingSphereDiameter / newDiameter; obj.transform.localScale *= correction; // No need to wait again unless you need to read back the final diameter immediately } } public static Texture2D CreateTexture(byte[] imageBytes) { var tex = new Texture2D(2, 2, TextureFormat.RGBA32, false); // ImageConversion.LoadImage returns bool (true = success) if (!ImageConversion.LoadImage(tex, imageBytes, markNonReadable: false)) { Destroy(tex); throw new InvalidOperationException("Failed to decode image bytes into Texture2D."); } tex.filterMode = FilterMode.Bilinear; tex.wrapMode = TextureWrapMode.Clamp; return tex; } public static Sprite CreateSprite(Texture2D tex) { var sprite = Sprite.Create( tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), pixelsPerUnit: 100f ); return sprite; } }