Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into release/v7.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
HarrisonHough committed Jan 10, 2025
2 parents e725b7d + d5a685d commit f0d5de4
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 84 deletions.
125 changes: 125 additions & 0 deletions Editor/Core/Scripts/EditorAvatarLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using ReadyPlayerMe.Core;
using ReadyPlayerMe.Loader;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;
using Object = UnityEngine.Object;

public class EditorAvatarLoader
{
private const string TAG = nameof(EditorAvatarLoader);

private readonly bool avatarCachingEnabled;

/// Scriptable Object Avatar API request parameters configuration
public AvatarConfig AvatarConfig;

/// Importer to use to import glTF
public IImporter Importer;

private string avatarUrl;
private OperationExecutor<AvatarContext> executor;
private float startTime;

public Action<AvatarContext> OnCompleted;

/// <summary>
/// This class constructor is used to any required fields.
/// </summary>
/// <param name="useDefaultGLTFDeferAgent">Use default defer agent</param>
public EditorAvatarLoader()
{
AvatarLoaderSettings loaderSettings = AvatarLoaderSettings.LoadSettings();
Importer = new GltFastAvatarImporter();
AvatarConfig = loaderSettings.AvatarConfig != null ? loaderSettings.AvatarConfig : null;
}

/// Set the timeout for download requests
public int Timeout { get; set; } = 20;

/// <summary>
/// Runs through the process of loading the avatar and creating a game object via the <c>OperationExecutor</c>.
/// </summary>
/// <param name="url">The URL to the avatars .glb file.</param>
public async Task<AvatarContext> Load(string url)
{
var context = new AvatarContext();
context.Url = url;
context.AvatarCachingEnabled = false;
context.AvatarConfig = AvatarConfig;
context.ParametersHash = AvatarCache.GetAvatarConfigurationHash(AvatarConfig);

// process url
var urlProcessor = new UrlProcessor();
context = await urlProcessor.Execute(context, CancellationToken.None);
// get metadata
var metadataDownloader = new MetadataDownloader();
context = await metadataDownloader.Execute(context, CancellationToken.None);
//download avatar into asset folder
context.AvatarUri.LocalModelPath = await DownloadAvatarModel(context.AvatarUri);
if (string.IsNullOrEmpty(context.AvatarUri.LocalModelPath))
{
Debug.LogError($"Failed to download avatar model from {context.AvatarUri.ModelUrl}");
return null;
}
// import model
context.Bytes = await File.ReadAllBytesAsync(context.AvatarUri.LocalModelPath);
context = await Importer.Execute(context, CancellationToken.None);
// Process the avatar
var avatarProcessor = new AvatarProcessor();
context = await avatarProcessor.Execute(context, CancellationToken.None);

var avatar = (GameObject) context.Data;
avatar.SetActive(true);

var avatarData = avatar.AddComponent<AvatarData>();
avatarData.AvatarId = avatar.name;
avatarData.AvatarMetadata = context.Metadata;
OnCompleted?.Invoke(context);
return context;
}

private static async Task<string> DownloadAvatarModel(AvatarUri avatarUri)
{
var folderPath = Path.Combine(Application.dataPath, "Ready Player Me/Avatars");
// Ensure the folder exists
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}

// Create the full file path
var fullPath = Path.Combine(folderPath, avatarUri.Guid + ".glb");

// Start the download
using (UnityWebRequest request = UnityWebRequest.Get(avatarUri.ModelUrl))
{
Debug.Log($"Downloading {avatarUri.ModelUrl}...");
var operation = request.SendWebRequest();

while (!operation.isDone)
{
await Task.Yield(); // Await completion of the web request
}

if (request.result == UnityWebRequest.Result.Success)
{
// Write the downloaded data to the file
await File.WriteAllBytesAsync(fullPath, request.downloadHandler.data);
Debug.Log($"File saved to: {fullPath}");

// Refresh the AssetDatabase to recognize the new file
AssetDatabase.Refresh();
Debug.Log("AssetDatabase refreshed.");
return fullPath;
}
Debug.LogError($"Failed to download file: {request.error}");
return null;
}
}

}
11 changes: 11 additions & 0 deletions Editor/Core/Scripts/EditorAvatarLoader.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@

namespace ReadyPlayerMe.Core.Editor
{
public class AvatarLoaderEditor : EditorWindow
public class AvatarLoaderWindow : EditorWindow
{
private const string TAG = nameof(AvatarLoaderEditor);
private const string AVATAR_LOADER = "Avatar Loader";
private const string LOAD_AVATAR_BUTTON = "LoadAvatarButton";
private const string HEADER_LABEL = "HeaderLabel";
Expand All @@ -25,11 +24,12 @@ public class AvatarLoaderEditor : EditorWindow

private bool useEyeAnimations;
private bool useVoiceToAnim;
private EditorAvatarLoader editorAvatarLoader;

[MenuItem("Tools/Ready Player Me/Avatar Loader", priority = 1)]
public static void ShowWindow()
{
var window = GetWindow<AvatarLoaderEditor>();
var window = GetWindow<AvatarLoaderWindow>();
window.titleContent = new GUIContent(AVATAR_LOADER);
window.minSize = new Vector2(500, 300);
}
Expand Down Expand Up @@ -82,53 +82,23 @@ private void LoadAvatar(string url)
{
avatarLoaderSettings = AvatarLoaderSettings.LoadSettings();
}
var avatarLoader = new AvatarObjectLoader();
avatarLoader.OnFailed += Failed;
avatarLoader.OnCompleted += Completed;
avatarLoader.OperationCompleted += OnOperationCompleted;
if (avatarLoaderSettings != null)
{
avatarLoader.AvatarConfig = avatarLoaderSettings.AvatarConfig;
if (avatarLoaderSettings.GLTFDeferAgent != null)
{
avatarLoader.GLTFDeferAgent = avatarLoaderSettings.GLTFDeferAgent;
}
}
avatarLoader.LoadAvatar(url);
editorAvatarLoader = new EditorAvatarLoader();
editorAvatarLoader.OnCompleted += Completed;
editorAvatarLoader.Load(url);
}

private void OnOperationCompleted(object sender, IOperation<AvatarContext> e)
{
if (e.GetType() == typeof(MetadataDownloader))
{
AnalyticsEditorLogger.EventLogger.LogMetadataDownloaded(EditorApplication.timeSinceStartup - startTime);
}
}

private void Failed(object sender, FailureEventArgs args)
{
Debug.LogError($"{args.Type} - {args.Message}");
}

private void Completed(object sender, CompletionEventArgs args)
private void Completed(AvatarContext context)
{
AnalyticsEditorLogger.EventLogger.LogAvatarLoaded(EditorApplication.timeSinceStartup - startTime);

if (avatarLoaderSettings == null)
{
avatarLoaderSettings = AvatarLoaderSettings.LoadSettings();
}
var paramHash = AvatarCache.GetAvatarConfigurationHash(avatarLoaderSettings.AvatarConfig);
var path = $"{DirectoryUtility.GetRelativeProjectPath(args.Avatar.name, paramHash)}/{args.Avatar.name}";
if (!avatarLoaderSettings.AvatarCachingEnabled)
{
SDKLogger.LogWarning(TAG, "Enable Avatar Caching to generate a prefab in the project folder.");
return;
}
var avatar = PrefabHelper.CreateAvatarPrefab(args.Metadata, path, avatarConfig: avatarLoaderSettings.AvatarConfig);
var path = $@"Assets\Ready Player Me\Avatars\{context.AvatarUri.Guid}";
var avatar = PrefabHelper.CreateAvatarPrefab(context.Metadata, path, avatarConfig: avatarLoaderSettings.AvatarConfig);
if (useEyeAnimations) avatar.AddComponent<EyeAnimationHandler>();
if (useVoiceToAnim) avatar.AddComponent<VoiceHandler>();
DestroyImmediate(args.Avatar, true);
DestroyImmediate((GameObject) context.Data, true);
Selection.activeObject = avatar;
}
}
Expand Down
5 changes: 3 additions & 2 deletions Editor/Core/Scripts/Utilities/PrefabHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace ReadyPlayerMe.Core.Editor
public static class PrefabHelper
{
private const string TAG = nameof(PrefabHelper);

public static void TransferPrefabByGuid(string guid, string newPath)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
Expand All @@ -18,7 +19,7 @@ public static void TransferPrefabByGuid(string guid, string newPath)
AssetDatabase.Refresh();
Selection.activeObject = AssetDatabase.LoadAssetAtPath(newPath, typeof(GameObject));
}

public static GameObject CreateAvatarPrefab(AvatarMetadata avatarMetadata, string path, string prefabPath = null, AvatarConfig avatarConfig = null)
{
var modelFilePath = $"{path}.glb";
Expand All @@ -34,7 +35,7 @@ public static GameObject CreateAvatarPrefab(AvatarMetadata avatarMetadata, strin
CreatePrefab(newAvatar, prefabPath ?? $"{path}.prefab");
return newAvatar;
}

public static void CreatePrefab(GameObject source, string path)
{
PrefabUtility.SaveAsPrefabAssetAndConnect(source, path, InteractionMode.AutomatedAction, out var success);
Expand Down
33 changes: 10 additions & 23 deletions Runtime/Core/Scripts/Caching/AvatarCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@ public static string GetAvatarConfigurationHash(AvatarConfig avatarConfig = null
/// Clears the avatars from the persistent cache.
public static void Clear()
{
var path = DirectoryUtility.GetAvatarsDirectoryPath();
DeleteFolder(path);
#if UNITY_EDITOR
DeleteFolder(DirectoryUtility.GetAvatarsPersistantPath());
#endif
}

private static void DeleteFolder(string path)
Expand All @@ -45,7 +41,7 @@ private static void DeleteFolder(string path)

public static string[] GetExistingAvatarIds()
{
var path = DirectoryUtility.GetAvatarsDirectoryPath();
var path = DirectoryUtility.GetAvatarsPersistantPath();
if (!Directory.Exists(path)) return Array.Empty<string>();
var directoryInfo = new DirectoryInfo(path);
var avatarIds = directoryInfo.GetDirectories().Select(subdir => subdir.Name).ToArray();
Expand All @@ -55,79 +51,70 @@ public static string[] GetExistingAvatarIds()
/// Deletes all data for a specific avatar variant (based on parameter hash) from persistent cache.
public static void DeleteAvatarVariantFolder(string guid, string paramHash)
{
DeleteFolder($"{DirectoryUtility.GetAvatarsDirectoryPath()}/{guid}/{paramHash}");
DeleteFolder($"{DirectoryUtility.GetAvatarsPersistantPath()}/{guid}/{paramHash}");
}

/// Deletes stored data a specific avatar from persistent cache.
public static void DeleteAvatarFolder(string guid)
{
var path = $"{DirectoryUtility.GetAvatarsDirectoryPath()}/{guid}";
var path = $"{DirectoryUtility.GetAvatarsPersistantPath()}/{guid}";
DeleteFolder(path);
}

/// deletes a specific avatar model (.glb file) from persistent cache, while leaving the metadata.json file
public static void DeleteAvatarModel(string guid, string parametersHash)
{
var path = $"{DirectoryUtility.GetAvatarsDirectoryPath()}/{guid}/{parametersHash}";
var path = $"{DirectoryUtility.GetAvatarsPersistantPath()}/{guid}/{parametersHash}";
if (Directory.Exists(path))
{
var info = new DirectoryInfo(path);

#if UNITY_EDITOR
foreach (DirectoryInfo dir in info.GetDirectories())
{
AssetDatabase.DeleteAsset($"Assets/{DirectoryUtility.DefaultAvatarFolder}/{guid}/{dir.Name}");
}

#else
foreach (DirectoryInfo dir in info.GetDirectories())
{
Directory.Delete(dir.FullName, true);
}
#endif
}
}

/// Is there any avatars present in the persistent cache.
public static bool IsCacheEmpty()
{
var path = DirectoryUtility.GetAvatarsDirectoryPath();
var path = DirectoryUtility.GetAvatarsPersistantPath();
return !Directory.Exists(path) ||
Directory.GetFiles(path).Length == 0 && Directory.GetDirectories(path).Length == 0;
}

/// Total Avatars stored in persistent cache.
public static int GetAvatarCount()
{
var path = DirectoryUtility.GetAvatarsDirectoryPath();
var path = DirectoryUtility.GetAvatarsPersistantPath();
return !Directory.Exists(path) ? 0 : new DirectoryInfo(path).GetDirectories().Length;

}

/// Total Avatar variants stored for specific avatar GUID in persistent cache.
public static int GetAvatarVariantCount(string avatarGuid)
{
var path = $"{DirectoryUtility.GetAvatarsDirectoryPath()}/{avatarGuid}";
var path = $"{DirectoryUtility.GetAvatarsPersistantPath()}/{avatarGuid}";
return !Directory.Exists(path) ? 0 : new DirectoryInfo(path).GetDirectories().Length;

}

/// Total size of avatar stored in persistent cache. Returns total bytes.
public static long GetCacheSize()
{
var path = DirectoryUtility.GetAvatarsDirectoryPath();
var path = DirectoryUtility.GetAvatarsPersistantPath();
return !Directory.Exists(path) ? 0 : DirectoryUtility.GetDirectorySize(new DirectoryInfo(path));
}

public static float GetCacheSizeInMb()
{
var path = DirectoryUtility.GetAvatarsDirectoryPath();
var path = DirectoryUtility.GetAvatarsPersistantPath();
return !Directory.Exists(path) ? 0 : DirectoryUtility.GetFolderSizeInMb(path);
}

public static float GetAvatarDataSizeInMb(string avatarGuid)
{
var path = $"{DirectoryUtility.GetAvatarsDirectoryPath()}/{avatarGuid}";
var path = $"{DirectoryUtility.GetAvatarsPersistantPath()}/{avatarGuid}";
return DirectoryUtility.GetFolderSizeInMb(path);
}
}
Expand Down
3 changes: 1 addition & 2 deletions Runtime/Core/Scripts/Caching/AvatarManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,9 @@ private void WriteToFile(string json)

private string GetFilePath()
{
return $"{DirectoryUtility.GetAvatarsDirectoryPath()}{RELATIVE_PATH}";
return $"{DirectoryUtility.GetAvatarsPersistantPath()}{RELATIVE_PATH}";
}


private string ReadFromFile()
{
var path = GetFilePath();
Expand Down
Loading

0 comments on commit f0d5de4

Please sign in to comment.