using System.Linq; using System.Diagnostics; using AnimatorAsCode.V1; using UnityEditor; using UnityEngine; using VRC.SDK3.Avatars.Components; using VRC.SDK3.Dynamics.Constraint.Components; using System.Collections.Generic; using UnityEditor.Animations; namespace gay.lilyy.SoldAvatarBootstrap { public class AvatarAssets { public AacFlBase aac; public GameObject root; public AacFlController fx; public AacFlClip emptyClip; public bool experimentalEnabled; public bool isPC; } public static class AvatarUtils { public static GameObject FindChildRecursive(GameObject parent, string name) { return FindChildRecursive(parent.transform, name)?.gameObject; } public static Transform FindChildRecursive(Transform parent, string name) { return parent.GetComponentsInChildren(true).FirstOrDefault(t => t.gameObject.name == name); } public static Transform[] FindChildrenRecursive(Transform parent, string name) { return parent.GetComponentsInChildren(true).Where(t => t.gameObject.name == name).ToArray(); } public static AacFlClip CreateConstraintWeightClip(AvatarAssets assets, VRCParentConstraint constraint, int index, int indexCount) { return assets.aac.NewClip() .Animating(action => { action.Animates(constraint, $"Sources.source{index}.Weight").WithOneFrame(1f); for (int i = 0; i < indexCount; i++) { if (i == index) continue; action.Animates(constraint, $"Sources.source{i}.Weight").WithOneFrame(0f); } }); } public static AacFlClip CreateEmptyClipWithFrames(AvatarAssets assets, int frames) { if (frames == 0) return assets.emptyClip; var emptyGo = new GameObject(); emptyGo.name = "_EmptyClipInstant"; emptyGo.transform.SetParent(assets.root.transform); var clip = assets.aac.NewClip().Animating(action => { action.Animates(emptyGo.transform, "m_LocalScale.z").WithFrameCountUnit(unit => { unit.Constant(0, 0); unit.Constant(frames, 1); }); }); Object.DestroyImmediate(emptyGo); return clip; } public static AacFlClip CreateEmptyClipWithSeconds(AvatarAssets assets, float seconds) { if (seconds == 0) return assets.emptyClip; var emptyGo = new GameObject(); emptyGo.name = "_EmptyClipInstant"; emptyGo.transform.SetParent(assets.root.transform); var clip = assets.aac.NewClip().Animating(action => { action.Animates(emptyGo.transform, "m_LocalScale.z").WithFrameCountUnit(unit => { unit.Constant(0, 0); unit.Constant(seconds, 1); }); }); Object.DestroyImmediate(emptyGo); return clip; } public static List FindWithBlendshape(AvatarAssets assets, string shapeName) { var list = new List(); foreach (var smr in assets.root.GetComponentsInChildren(true)) { var mesh = smr.sharedMesh; if (!mesh) continue; int count = mesh.blendShapeCount; for (int i = 0; i < count; i++) { if (mesh.GetBlendShapeName(i) == shapeName) { list.Add(smr); break; } } } return list; } public static List FindNamedComponents(Transform parent, string name) where T : Component { var list = new List(); foreach (var component in parent.GetComponentsInChildren(true)) { if (component.gameObject.name == name) { list.Add(component); } } UnityEngine.Debug.Log($"Found {list.Count} {name} components"); foreach (var component in list) { UnityEngine.Debug.Log($"Component: {component.gameObject.name}"); } return list; } public static List FindNamedComponents(AvatarAssets assets, string name) where T : Component { return FindNamedComponents(assets.root.transform, name); } } public class AvatarBootstrapCore { private static AnimatorController GetController(AvatarDefinition definition) { string metaPath = definition.FXLayerPath + ".meta"; string originalGuid = null; if (System.IO.File.Exists(metaPath)) { foreach (var line in System.IO.File.ReadAllLines(metaPath)) { if (line.StartsWith("guid: ")) { originalGuid = line.Substring(6).Trim(); break; } } } if (AssetDatabase.LoadAssetAtPath(definition.FXLayerPath) != null) { AssetDatabase.DeleteAsset(definition.FXLayerPath); AssetDatabase.Refresh(); } var newController = AnimatorController.CreateAnimatorControllerAtPath(definition.FXLayerPath); if (originalGuid != null && System.IO.File.Exists(metaPath)) { var lines = System.IO.File.ReadAllLines(metaPath); for (int i = 0; i < lines.Length; ++i) { if (lines[i].StartsWith("guid: ")) { lines[i] = "guid: " + originalGuid; } } System.IO.File.WriteAllLines(metaPath, lines); AssetDatabase.Refresh(); } return AssetDatabase.LoadAssetAtPath(definition.FXLayerPath); } public static void RunForCurrentAvatar(AvatarDefinition definition) { var allLayerGroups = LayerGroup.Instances.ToList(); var layerGroups = allLayerGroups .Where(lg => lg.TargetDefinitions.Contains(definition)) .ToList(); // Start overall timer var overallStopwatch = Stopwatch.StartNew(); AvatarLogger.LogInfo("Starting SoldAvatarBootstrap generation for definition: " + definition.DisplayName); GameObject root = null; foreach (var candidate in GameObject.FindObjectsOfType(true)) { if (definition.IsApplicable(candidate.gameObject)) { AvatarLogger.LogInfo($"Found suitable avatar: {candidate.gameObject.name}"); root = candidate.gameObject; break; } } if (root == null) { AvatarLogger.LogError($"No suitable avatar found for definition: {definition.DisplayName}"); return; } // Skip if the root GameObject or the component itself is disabled if (!root.activeSelf || !root.gameObject.activeInHierarchy) { AvatarLogger.LogWarning($"Avatar root '{root.name}' is disabled or inactive. Skipping."); return; } AvatarAssets assets = new(); assets.root = root; assets.isPC = EditorUserBuildSettings.activeBuildTarget == BuildTarget.StandaloneWindows || EditorUserBuildSettings.activeBuildTarget == BuildTarget.StandaloneWindows64; // Time AAC initialization var initStopwatch = Stopwatch.StartNew(); AvatarLogger.LogInfo("Initializing Animator As Code..."); assets.aac = AacV1.Create(new AacConfiguration { SystemName = definition.SystemName, AnimatorRoot = root.transform, DefaultValueRoot = root.transform, AssetKey = GUID.Generate().ToString(), AssetContainer = GetController(definition), ContainerMode = AacConfiguration.Container.Everything, DefaultsProvider = new AacDefaultsProvider(true) }); assets.emptyClip = assets.aac.NewClip(); assets.fx = assets.aac.NewAnimatorController(); initStopwatch.Stop(); AvatarLogger.LogInfo($"AAC initialization completed in {initStopwatch.ElapsedMilliseconds}ms"); // Process layer groups with individual timing // Filter out the template LayerGroup (by SystemName) // AACLogger.LogInfo($"Processing {layerGroups.Count} layer groups..."); AvatarLogger.LogInfo($"Processing {layerGroups.Count} layer groups..."); var totalLayerGroupTime = 0L; var processedCount = 0; var skippedCount = 0; foreach (var layerGroup in layerGroups) { var layerStopwatch = Stopwatch.StartNew(); bool skipped = false; if (!layerGroup.enabled) { AvatarLogger.LogWarning($"Skipping layer group: {layerGroup.DisplayName} (disabled)"); skipped = true; } if (!skipped) { AvatarLogger.LogInfo($"Running layer group: {layerGroup.DisplayName}"); layerGroup.Run(assets); processedCount++; } else { skippedCount++; } layerStopwatch.Stop(); totalLayerGroupTime += layerStopwatch.ElapsedMilliseconds; AvatarLogger.LogInfo($"Layer group '{layerGroup.DisplayName}' completed in {layerStopwatch.ElapsedMilliseconds}ms"); } // Final timing summary overallStopwatch.Stop(); AvatarLogger.LogSuccess($"SoldAvatarBootstrap generation completed successfully!"); AvatarLogger.LogInfo($"=== TIMING SUMMARY ==="); AvatarLogger.LogInfo($"Total time: {overallStopwatch.ElapsedMilliseconds}ms ({overallStopwatch.Elapsed.TotalSeconds:F2}s)"); AvatarLogger.LogInfo($"AAC initialization: {initStopwatch.ElapsedMilliseconds}ms"); AvatarLogger.LogInfo($"Layer groups: {totalLayerGroupTime}ms (processed: {processedCount}, skipped: {skippedCount})"); AvatarLogger.LogInfo($"Average per layer group: {(processedCount > 0 ? totalLayerGroupTime / processedCount : 0)}ms"); } } }