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);
        }
Beispiel #2
0
        /// <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);
        }
Beispiel #3
0
        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();
            }
        }
Beispiel #5
0
        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();
            }
        }
Beispiel #6
0
        /// <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);
        }
Beispiel #7
0
        /// <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);
        }
Beispiel #8
0
        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);
        }
Beispiel #9
0
        /// <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);
        }
Beispiel #12
0
 /// <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));
 }
Beispiel #13
0
#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));
        }
Beispiel #14
0
        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();
        }
Beispiel #15
0
        private static bool ValidateContextForGameObject()
        {
            var obj = Selection.activeGameObject;

            return(VRCSDKUtility.IsAvatarRoot(obj));
        }