private static void RemoveMissingComponents() { var i18n = VRCQuestToolsSettings.I18nResource; var obj = Selection.activeGameObject; var count = VRCSDKUtility.CountMissingComponentsInChildren(obj, true); Debug.Log($"[{VRCQuestTools.Name}] {obj.name} has {count} missing scripts in children"); if (count == 0) { EditorUtility.DisplayDialog(VRCQuestTools.Name, i18n.NoMissingComponentsMessage(obj.name), "OK"); return; } var needsToUnpackPrefab = PrefabUtility.IsPartOfPrefabInstance(obj); var message = i18n.MissingRemoverConfirmationMessage(obj.name); if (needsToUnpackPrefab) { message += $" ({i18n.UnpackPrefabMessage})"; } if (!EditorUtility.DisplayDialog(VRCQuestTools.Name, message, "OK", i18n.CancelLabel)) { return; } if (needsToUnpackPrefab) { Undo.SetCurrentGroupName("Remove Missing Components"); // Somehow unpacking is needed to apply changes to the scene file. PrefabUtility.UnpackPrefabInstance(obj, PrefabUnpackMode.OutermostRoot, InteractionMode.UserAction); Debug.Log($"[{VRCQuestTools.Name}] {obj.name} has been unpacked"); } VRCSDKUtility.RemoveMissingComponentsInChildren(obj, true); }
/// <summary> /// VRChat用のCustomOverrideControllerの複製を取得する /// </summary> /// <param name="animFolder"></param> /// <returns></returns> private AnimatorOverrideController InstantiateVrcCustomOverideController(string newFilePath) { string path = VRCSDKUtility.GetVRCSDKFilePath("CustomOverrideEmpty"); newFilePath = AssetDatabase.GenerateUniqueAssetPath(newFilePath); AssetDatabase.CopyAsset(path, newFilePath); var overrideController = AssetDatabase.LoadAssetAtPath(newFilePath, typeof(AnimatorOverrideController)) as AnimatorOverrideController; return(overrideController); }
private static AnimatorController InstantiateFxController(string newFilePath) { string path = VRCSDKUtility.GetVRCSDKFilePath("vrc_AvatarV3HandsLayer"); newFilePath = AssetDatabase.GenerateUniqueAssetPath(newFilePath); AssetDatabase.CopyAsset(path, newFilePath); var controller = AssetDatabase.LoadAssetAtPath(newFilePath, typeof(AnimatorController)) as AnimatorController; return(controller); }
private static void RemoveAllVertexColorsFromAvatars(Scene scene) { var model = new VertexColorRemoverViewModel(); var avatars = VRCSDKUtility.GetAvatarsFromScene(scene); foreach (var a in avatars) { model.target = a.gameObject; model.RemoveVertexColor(); } }
private static void InitFromMenu() { var target = Selection.activeGameObject; if (target != null && VRCSDKUtility.IsAvatarRoot(target)) { var avatar = target.GetComponent <VRC_AvatarDescriptor>(); AvatarConverterWindow.ShowWindow(avatar); } else { AvatarConverterWindow.ShowWindow(); } }
/// <summary> /// Converts animation clips. /// </summary> /// <param name="controllers">Controllers to convert clips.</param> /// <param name="assetsDirectory">Root directory for converted avatar.</param> /// <param name="convertedMaterials">Converted materials.</param> /// <param name="progressCallback">Callback to show progress.</param> /// <returns>Converted controllers (key: original controller).</returns> internal Dictionary <AnimationClip, AnimationClip> ConvertAnimationClipsForQuest(RuntimeAnimatorController[] controllers, string assetsDirectory, Dictionary <Material, Material> convertedMaterials, AnimationClipProgressCallback progressCallback) { var saveDirectory = $"{assetsDirectory}/Animations"; Directory.CreateDirectory(saveDirectory); AssetDatabase.Refresh(); var animationClips = controllers .SelectMany(c => c.animationClips) .Where(a => a != null) .Distinct() .ToArray(); var convertedAnimationClips = new Dictionary <AnimationClip, AnimationClip>(); for (var i = 0; i < animationClips.Length; i++) { var clip = animationClips[i]; if (VRCSDKUtility.IsProxyAnimationClip(clip)) { continue; } progressCallback(animationClips.Length, i, null, clip); try { AssetDatabase.TryGetGUIDAndLocalFileIdentifier(animationClips[i], out string guid, out long localid); var outFile = $"{saveDirectory}/{animationClips[i].name}_from_{guid}.anim"; var anim = UnityAnimationUtility.ReplaceAnimationClipMaterials(animationClips[i], convertedMaterials); AssetDatabase.CreateAsset(anim, outFile); convertedAnimationClips.Add(clip, anim); Debug.Log("create asset: " + outFile); } catch (System.Exception e) { progressCallback(animationClips.Length, i, e, clip); throw e; } } return(convertedAnimationClips); }
/// <summary> /// Converts materials for ToonLit. /// </summary> /// <param name="materials">Materials to convert.</param> /// <param name="assetsDirectory">Root directory for converted avatar.</param> /// <returns>Converted materials (key: original material).</returns> internal Dictionary <Material, Material> ConvertMaterialsForToonLit(Material[] materials, string assetsDirectory) { var saveDirectory = $"{assetsDirectory}/Materials"; Directory.CreateDirectory(saveDirectory); AssetDatabase.Refresh(); var materialsToConvert = materials.Where(m => !VRCSDKUtility.IsMaterialAllowedForQuestAvatar(m)).ToArray(); var convertedMaterials = new Dictionary <Material, Material>(); for (int i = 0; i < materialsToConvert.Length; i++) { var m = materialsToConvert[i]; AssetDatabase.TryGetGUIDAndLocalFileIdentifier(m, out string guid, out long localId); var material = new MaterialWrapperBuilder().Build(m); var toonlit = material.ConvertToToonLit(); AssetDatabase.CreateAsset(toonlit, $"{saveDirectory}/{m.name}_from_{guid}.mat"); convertedMaterials.Add(m, toonlit); } return(convertedMaterials); }
private bool AvatarHasErrorForQuest(VRChatAvatar avatar) { if (!avatar.GameObject.activeInHierarchy) { return(false); } foreach (var m in avatar.GetRendererMaterials()) { if (!VRCSDKUtility.IsMaterialAllowedForQuestAvatar(m)) { return(true); } } if (new ComponentRemover().GetUnsupportedComponentsInChildren(avatar.GameObject, true).Length > 0) { return(true); } return(false); }
/// <summary> /// Generates textures for Toon Lit. /// </summary> /// <param name="materials">Materials to generate textures.</param> /// <param name="assetsDirectory">Root directory for converted avatar.</param> /// <param name="maxTextureSize">Max texture size. 0 for no limits.</param> /// <param name="progressCallback">Callback to show progress.</param> /// <returns>Converted textures (key: original material).</returns> internal Dictionary <Material, Texture2D> GenrateToonLitTextures(Material[] materials, string assetsDirectory, int maxTextureSize, TextureProgressCallback progressCallback) { var saveDirectory = $"{assetsDirectory}/Textures"; Directory.CreateDirectory(saveDirectory); AssetDatabase.Refresh(); var materialsToConvert = materials.Where(m => !VRCSDKUtility.IsMaterialAllowedForQuestAvatar(m)).ToArray(); var convertedTextures = new Dictionary <Material, Texture2D>(); for (int i = 0; i < materialsToConvert.Length; i++) { var m = materialsToConvert[i]; progressCallback(materialsToConvert.Length, i, null, m); try { AssetDatabase.TryGetGUIDAndLocalFileIdentifier(m, out string guid, out long localId); var material = MaterialWrapperBuilder.Build(m); using (var image = material.GenerateToonLitImage()) { Texture2D texture = null; if (image != null) { if (maxTextureSize > 0 && Math.Max(image.Width, image.Height) > maxTextureSize) { image.Resize(maxTextureSize, maxTextureSize); } texture = MagickImageUtility.SaveAsAsset($"{saveDirectory}/{m.name}_from_{guid}.png", image); } convertedTextures.Add(m, texture); } } catch (Exception e) { progressCallback(materialsToConvert.Length, i, e, m); throw e; } } return(convertedTextures); }
private static void Update() { var scene = SceneManager.GetActiveScene(); var avatars = VRCSDKUtility.GetAvatarsFromScene(scene); foreach (var avatar in avatars) { foreach (var rule in AvatarValidationRules.Rules) { var result = rule.Validate(new VRChatAvatar(avatar)); var key = $"{rule.GetType().FullName}-{avatar.gameObject.GetInstanceID()}"; if (result != null) { NotificationWindow.instance.RegisterNotification(key, result); } else { NotificationWindow.instance.RemoveNotification(key); } } } }
/// <inheritdoc/> public NotificationItem Validate(VRChatAvatar avatar) { if (!avatar.GameObject.activeInHierarchy) { return(null); } var hasDynamicBone = AssetUtility.IsDynamicBoneImported(); if (VRCSDKUtility.CountMissingComponentsInChildren(avatar.GameObject, true) > 0) { return(new NotificationItem(() => { if (avatar.AvatarDescriptor == null) { return true; } var i18n = VRCQuestToolsSettings.I18nResource; if (hasDynamicBone) { GUILayout.Label(i18n.MissingScripts, EditorStyles.wordWrappedLabel); } else { GUILayout.Label(i18n.MissingDynamicBone, EditorStyles.wordWrappedLabel); } GUILayout.Label($"- {avatar.GameObject.name}", EditorStyles.wordWrappedLabel); if (GUILayout.Button(i18n.RemoveMissing)) { Selection.activeGameObject = avatar.GameObject; EditorApplication.ExecuteMenuItem(VRCQuestToolsMenus.MenuPaths.RemoveMissingComponents); return false; } return false; })); } return(null); }
/// <summary> /// Whether a component type is not supported for Quest. /// </summary> /// <param name="type">Component type to check.</param> /// <returns>true when unsuported.</returns> internal virtual bool IsUnsupportedComponent(System.Type type) { return(VRCSDKUtility.IsUnsupportedComponentType(type)); }
#pragma warning restore SA1600 // Elements should be documented /// <summary> /// Covert the avatar for Quest. /// </summary> /// <param name="avatar">Avatar to convert.</param> /// <param name="assetsDirectory">Root directory to save.</param> /// <param name="generateQuestTextures">Whether to generate textures.</param> /// <param name="maxTextureSize">Max textures size. 0 for no limits.</param> /// <param name="remover">ComponentRemover object.</param> /// <param name="progressCallback">Callback to show progress.</param> /// <returns>Converted avatar.</returns> internal virtual Tuple <VRChatAvatar, string> ConvertForQuest(VRChatAvatar avatar, string assetsDirectory, bool generateQuestTextures, int maxTextureSize, ComponentRemover remover, ProgressCallback progressCallback) { // Convert materials and generate textures. var convertedMaterials = ConvertMaterialsForToonLit(avatar.Materials, assetsDirectory); if (generateQuestTextures) { var generatedTextures = GenrateToonLitTextures(avatar.Materials, assetsDirectory, maxTextureSize, progressCallback.onTextureProgress); foreach (var tex in generatedTextures) { if (convertedMaterials.ContainsKey(tex.Key)) { convertedMaterials[tex.Key].mainTexture = tex.Value; } } AssetDatabase.SaveAssets(); } // Duplicate the original gameobject. var questAvatarObject = UnityEngine.Object.Instantiate(avatar.AvatarDescriptor.gameObject); // Convert animator controllers and their animation clips. if (avatar.HasAnimatedMaterials) { var convertedAnimationClips = ConvertAnimationClipsForQuest(avatar.GetRuntimeAnimatorControllers(), assetsDirectory, convertedMaterials, progressCallback.onAnimationClipProgress); var convertedAnimatorControllers = ConvertAnimatorControllersForQuest(avatar.GetRuntimeAnimatorControllers(), assetsDirectory, convertedAnimationClips, progressCallback.onRuntimeAnimatorProgress); // Apply converted animator controllers. #if VRC_SDK_VRCSDK3 var layers = questAvatarObject.GetComponent <VRC.SDK3.Avatars.Components.VRCAvatarDescriptor>().baseAnimationLayers; for (int i = 0; i < layers.Length; i++) { if (!layers[i].isDefault && layers[i].animatorController != null) { if (convertedAnimatorControllers.ContainsKey(layers[i].animatorController)) { layers[i].animatorController = convertedAnimatorControllers[layers[i].animatorController]; } } } #endif foreach (var animator in questAvatarObject.GetComponentsInChildren <Animator>(true)) { if (animator.runtimeAnimatorController != null) { if (convertedAnimatorControllers.ContainsKey(animator.runtimeAnimatorController)) { animator.runtimeAnimatorController = convertedAnimatorControllers[animator.runtimeAnimatorController]; } } } } // Apply converted materials to renderers. // Apply AFTER animator controllers because original animation clips overwrite sharedMaterials. foreach (var renderer in questAvatarObject.GetComponentsInChildren <Renderer>(true)) { renderer.sharedMaterials = renderer.sharedMaterials.Select(m => { if (m == null) { return(null); } if (convertedMaterials.ContainsKey(m)) { return(convertedMaterials[m]); } return(m); }).ToArray(); } VRCSDKUtility.RemoveMissingComponentsInChildren(questAvatarObject, true); remover.RemoveUnsupportedComponentsInChildren(questAvatarObject, true); questAvatarObject.name = avatar.AvatarDescriptor.gameObject.name + " (Quest)"; questAvatarObject.SetActive(true); var prefabName = $"{assetsDirectory}/{questAvatarObject.name}.prefab"; return(new Tuple <VRChatAvatar, string>(new VRChatAvatar(questAvatarObject.GetComponent <VRC_AvatarDescriptor>()), prefabName)); }
private void OnGUI() { if (LocalizeText.instance.langPair is null) { DrawNowLoading(); return; } using (new EditorGUILayout.HorizontalScope(GUILayout.Height(EditorGUIUtility.singleLineHeight * 1.5f))) { GUILayout.FlexibleSpace(); GatoGUILayout.Button( LocalizeText.instance.langPair.reloadAvatarButtonText, () => { OnChangedAvatar(); }, originalAvatar != null); EditorGUILayout.Space(); var toolInfoButtonText = (!isShowingToolInfo) ? LocalizeText.instance.langPair.toolInfoButtonText : LocalizeText.instance.langPair.close; var settingButtonText = (!isShowingSetting) ? LocalizeText.instance.langPair.settingButtonText : LocalizeText.instance.langPair.close; GatoGUILayout.Button( toolInfoButtonText, () => { isShowingToolInfo = !isShowingToolInfo; isShowingSetting = false; }, true, GUILayout.MinWidth(50)); GatoGUILayout.Button( settingButtonText, () => { isShowingSetting = !isShowingSetting; isShowingToolInfo = false; if (!isShowingSetting) { EditorSetting.instance.ApplySettingsToEditorGUI( edittingAvatar, faceEmotionGUI); } }, true, GUILayout.MinWidth(50)); } if (!isShowingToolInfo && !isShowingSetting) { using (new EditorGUILayout.VerticalScope()) { // アバター選択 using (var check = new EditorGUI.ChangeCheckScope()) { targetAvatarDescriptor = GatoGUILayout.ObjectField( LocalizeText.instance.langPair.avatarLabel, targetAvatarDescriptor); if (check.changed) { // アバター変更時の処理 if (targetAvatarDescriptor != null) { OnChangedAvatar(); } } } using (new EditorGUI.DisabledGroupScope(edittingAvatar.Descriptor == null)) { // LayoutType: Default if (layoutType == LayoutType.Default) { using (new EditorGUILayout.HorizontalScope()) { needRepaint = avatarMonitorGUI.DrawGUI(null); if (needRepaint) { Repaint(); } else { animationsGUI.DrawGUI(layoutOptions[0]); } } DrawToolSwitchTab(); selectedToolGUI.DrawGUI(null); } // LayoutType: Half else { using (new EditorGUILayout.HorizontalScope()) { needRepaint = avatarMonitorGUI.DrawGUI(null); if (needRepaint) { Repaint(); } using (new EditorGUILayout.VerticalScope()) { DrawToolSwitchTab(); if (CurrentTool == ToolFunc.AvatarInfo) { using (new EditorGUILayout.HorizontalScope()) { if (!needRepaint) { animationsGUI.DrawGUI(layoutOptions[1]); } } // アバター情報 avatarInfoGUI.DrawGUI(null); } else { selectedToolGUI.DrawGUI(null); } } } } EditorGUILayout.Space(); // ポーズ修正 GatoGUILayout.Button( LocalizeText.instance.langPair.resetPoseButtonText, () => { HumanoidPose.ResetPose(edittingAvatar.Descriptor.gameObject); HumanoidPose.ResetPose(originalAvatar.Descriptor.gameObject); }); // アップロード GatoGUILayout.Button( LocalizeText.instance.langPair.uploadAvatarButtonText, () => { VRCSDKUtility.UploadAvatar(VRCSDKUtility.IsNewSDKUI()); }); } } } else if (isShowingToolInfo) { ToolInfoGUI(); } else { SettingGUI(); } EditorGUILayout.Space(); }
private static bool ValidateContextForGameObject() { var obj = Selection.activeGameObject; return(VRCSDKUtility.IsAvatarRoot(obj)); }