diff options
| author | tylermurphy534 <tylermurphy534@gmail.com> | 2022-11-06 15:12:42 -0500 |
|---|---|---|
| committer | tylermurphy534 <tylermurphy534@gmail.com> | 2022-11-06 15:12:42 -0500 |
| commit | eb84bb298d2b95aec7b2ae12cbf25ac64f25379a (patch) | |
| tree | efd616a157df06ab661c6d56651853431ac6b08b /VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel | |
| download | unityprojects-eb84bb298d2b95aec7b2ae12cbf25ac64f25379a.tar.gz unityprojects-eb84bb298d2b95aec7b2ae12cbf25ac64f25379a.tar.bz2 unityprojects-eb84bb298d2b95aec7b2ae12cbf25ac64f25379a.zip | |
move to self host
Diffstat (limited to 'VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel')
18 files changed, 3725 insertions, 0 deletions
diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/IVRCSdkControlPanelBuilder.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/IVRCSdkControlPanelBuilder.cs new file mode 100644 index 00000000..3dbb60d1 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/IVRCSdkControlPanelBuilder.cs @@ -0,0 +1,9 @@ + +public interface IVRCSdkControlPanelBuilder +{ + void ShowSettingsOptions(); + bool IsValidBuilder(out string message); + void ShowBuilder(); + void RegisterBuilder(VRCSdkControlPanel baseBuilder); + void SelectAllComponents(); +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/IVRCSdkControlPanelBuilder.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/IVRCSdkControlPanelBuilder.cs.meta new file mode 100644 index 00000000..685eb8e9 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/IVRCSdkControlPanelBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 310a760e312f2984e85eece367bab19a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanel.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanel.cs new file mode 100644 index 00000000..b8e81972 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanel.cs @@ -0,0 +1,253 @@ +using UnityEditor; +using UnityEngine; +using VRC.Core; +using VRC.SDKBase.Editor; + +[ExecuteInEditMode] +public partial class VRCSdkControlPanel : EditorWindow +{ + public static VRCSdkControlPanel window; + + [MenuItem("VRChat SDK/Show Control Panel", false, 600)] + static void ShowControlPanel() + { + if (!ConfigManager.RemoteConfig.IsInitialized()) + { + VRC.Core.API.SetOnlineMode(true, "vrchat"); + ConfigManager.RemoteConfig.Init(() => ShowControlPanel()); + return; + } + + window = (VRCSdkControlPanel)EditorWindow.GetWindow(typeof(VRCSdkControlPanel)); + window.titleContent.text = "VRChat SDK"; + window.minSize = new Vector2(SdkWindowWidth + 4, 600); + window.maxSize = new Vector2(SdkWindowWidth + 4, 2000); + window.Init(); + window.Show(); + } + + public static GUIStyle titleGuiStyle; + public static GUIStyle boxGuiStyle; + public static GUIStyle infoGuiStyle; + public static GUIStyle listButtonStyleEven; + public static GUIStyle listButtonStyleOdd; + public static GUIStyle listButtonStyleSelected; + public static GUIStyle scrollViewSeparatorStyle; + public static GUIStyle searchBarStyle; + + void InitializeStyles() + { + titleGuiStyle = new GUIStyle(); + titleGuiStyle.fontSize = 15; + titleGuiStyle.fontStyle = FontStyle.BoldAndItalic; + titleGuiStyle.alignment = TextAnchor.MiddleCenter; + titleGuiStyle.wordWrap = true; + if (EditorGUIUtility.isProSkin) + titleGuiStyle.normal.textColor = Color.white; + else + titleGuiStyle.normal.textColor = Color.black; + + boxGuiStyle = new GUIStyle(); + if (EditorGUIUtility.isProSkin) + { + boxGuiStyle.normal.background = CreateBackgroundColorImage(new Color(0.3f, 0.3f, 0.3f)); + boxGuiStyle.normal.textColor = Color.white; + } + else + { + boxGuiStyle.normal.background = CreateBackgroundColorImage(new Color(0.85f, 0.85f, 0.85f)); + boxGuiStyle.normal.textColor = Color.black; + } + + infoGuiStyle = new GUIStyle(); + infoGuiStyle.wordWrap = true; ; + if (EditorGUIUtility.isProSkin) + infoGuiStyle.normal.textColor = Color.white; + else + infoGuiStyle.normal.textColor = Color.black; + infoGuiStyle.margin = new RectOffset(10, 10, 10, 10); + + listButtonStyleEven = new GUIStyle(); + listButtonStyleEven.margin = new RectOffset(0, 0, 0, 0); + listButtonStyleEven.border = new RectOffset(0, 0, 0, 0); + if (EditorGUIUtility.isProSkin) + { + listButtonStyleEven.normal.textColor = new Color(0.8f, 0.8f, 0.8f); + listButtonStyleEven.normal.background = CreateBackgroundColorImage(new Color(0.540f, 0.540f, 0.54f)); + } + else + { + listButtonStyleEven.normal.textColor = Color.black; + listButtonStyleEven.normal.background = CreateBackgroundColorImage(new Color(0.85f, 0.85f, 0.85f)); + } + + listButtonStyleOdd = new GUIStyle(); + listButtonStyleOdd.margin = new RectOffset(0, 0, 0, 0); + listButtonStyleOdd.border = new RectOffset(0, 0, 0, 0); + if (EditorGUIUtility.isProSkin) + { + listButtonStyleOdd.normal.textColor = new Color(0.8f, 0.8f, 0.8f); + //listButtonStyleOdd.normal.background = CreateBackgroundColorImage(new Color(0.50f, 0.50f, 0.50f)); + } + else + { + listButtonStyleOdd.normal.textColor = Color.black; + listButtonStyleOdd.normal.background = CreateBackgroundColorImage(new Color(0.90f, 0.90f, 0.90f)); + } + + listButtonStyleSelected = new GUIStyle(); + listButtonStyleSelected.normal.textColor = Color.white; + listButtonStyleSelected.margin = new RectOffset(0, 0, 0, 0); + if (EditorGUIUtility.isProSkin) + { + listButtonStyleSelected.normal.textColor = new Color(0.8f, 0.8f, 0.8f); + listButtonStyleSelected.normal.background = CreateBackgroundColorImage(new Color(0.4f, 0.4f, 0.4f)); + } + else + { + listButtonStyleSelected.normal.textColor = Color.black; + listButtonStyleSelected.normal.background = CreateBackgroundColorImage(new Color(0.75f, 0.75f, 0.75f)); + } + + scrollViewSeparatorStyle = new GUIStyle("Toolbar"); + scrollViewSeparatorStyle.fixedWidth = SdkWindowWidth + 10; + scrollViewSeparatorStyle.fixedHeight = 4; + scrollViewSeparatorStyle.margin.top = 1; + + searchBarStyle = new GUIStyle("Toolbar"); + searchBarStyle.fixedWidth = SdkWindowWidth; + searchBarStyle.fixedHeight = 23; + searchBarStyle.padding.top = 3; + + } + + void Init() + { + InitializeStyles(); + ResetIssues(); + InitAccount(); + } + + void OnEnable() + { + OnEnableAccount(); + AssemblyReloadEvents.afterAssemblyReload += BuilderAssemblyReload; + } + + void OnDisable() + { + AssemblyReloadEvents.afterAssemblyReload -= BuilderAssemblyReload; + } + + void OnDestroy() + { + AccountDestroy(); + } + + public const int SdkWindowWidth = 518; + + private readonly GUIContent[] _toolbarLabels = new GUIContent[4] + { + new GUIContent("Authentication"), + new GUIContent("Builder"), + new GUIContent("Content Manager"), + new GUIContent("Settings") + }; + + private readonly bool[] _toolbarOptionsLoggedIn = new bool[4] {true, true, true, true}; + private readonly bool[] _toolbarOptionsNotLoggedIn = new bool[4] {true, false, false, true}; + + void OnGUI() + { + if (window == null) + { + window = (VRCSdkControlPanel)EditorWindow.GetWindow(typeof(VRCSdkControlPanel)); + InitializeStyles(); + } + + if (_bannerImage == null) + _bannerImage = Resources.Load<Texture2D>("SDK_Panel_Banner"); + + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.BeginVertical(); + + GUILayout.Box(_bannerImage); + + if (Application.isPlaying) + { + GUI.enabled = false; + GUILayout.Space(20); + EditorGUILayout.LabelField("Unity Application is running ...\nStop it to access the Control Panel", titleGuiStyle, GUILayout.Width(SdkWindowWidth)); + GUI.enabled = true; + GUILayout.EndVertical(); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + return; + } + + EditorGUILayout.Space(); + + EnvConfig.SetActiveSDKDefines(); + + int showPanel = GUILayout.Toolbar(VRCSettings.ActiveWindowPanel, _toolbarLabels, APIUser.IsLoggedIn ? _toolbarOptionsLoggedIn : _toolbarOptionsNotLoggedIn, null, GUILayout.Width(SdkWindowWidth)); + + // Only show Account or Settings panels if not logged in + if (APIUser.IsLoggedIn == false && showPanel != 3) + { + showPanel = 0; + } + + if (showPanel != VRCSettings.ActiveWindowPanel) + { + VRCSettings.ActiveWindowPanel = showPanel; + } + + GUILayout.EndVertical(); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + switch (showPanel) + { + case 1: + ShowBuilders(); + break; + case 2: + ShowContent(); + break; + case 3: + ShowSettings(); + break; + case 0: + default: + ShowAccount(); + break; + } + } + + [UnityEditor.Callbacks.PostProcessScene] + static void OnPostProcessScene() + { + if (window != null) + window.Reset(); + } + + private void OnFocus() + { + Reset(); + } + + public void Reset() + { + ResetIssues(); + // style backgrounds may be nulled on scene load. detect if so has happened + if((boxGuiStyle != null) && (boxGuiStyle.normal.background == null)) + InitializeStyles(); + } + + [UnityEditor.Callbacks.DidReloadScripts(int.MaxValue)] + static void DidReloadScripts() + { + RefreshApiUrlSetting(); + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanel.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanel.cs.meta new file mode 100644 index 00000000..2602c029 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanel.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 20b4cdbdda9655947aab6f8f2c90690f +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelAccount.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelAccount.cs new file mode 100644 index 00000000..0e96a6a4 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelAccount.cs @@ -0,0 +1,551 @@ +using UnityEngine; +using UnityEditor; +using VRC.Core; +using System.Text.RegularExpressions; +using VRC.SDKBase.Editor; + +public partial class VRCSdkControlPanel : EditorWindow +{ + static bool isInitialized = false; + static string clientInstallPath; + static bool signingIn = false; + static string error = null; + + public static bool FutureProofPublishEnabled { get { return UnityEditor.EditorPrefs.GetBool("futureProofPublish", DefaultFutureProofPublishEnabled); } } + //public static bool DefaultFutureProofPublishEnabled { get { return !SDKClientUtilities.IsInternalSDK(); } } + public static bool DefaultFutureProofPublishEnabled { get { return false; } } + + static string storedUsername + { + get + { + return null; + } + set + { + EditorPrefs.DeleteKey("sdk#username"); + } + } + + static string storedPassword + { + get + { + return null; + } + set + { + EditorPrefs.DeleteKey("sdk#password"); + } + } + + static string username { get; set; } = null; + static string password { get; set; } = null; + + static ApiServerEnvironment serverEnvironment + { + get + { + ApiServerEnvironment env = ApiServerEnvironment.Release; + try + { + env = (ApiServerEnvironment)System.Enum.Parse(typeof(ApiServerEnvironment), UnityEditor.EditorPrefs.GetString("VRC_ApiServerEnvironment", env.ToString())); + } + catch (System.Exception e) + { + Debug.LogError("Invalid server environment name - " + e.ToString()); + } + + return env; + } + set + { + UnityEditor.EditorPrefs.SetString("VRC_ApiServerEnvironment", value.ToString()); + + API.SetApiUrlFromEnvironment(value); + } + } + + private void OnEnableAccount() + { + entered2faCodeIsInvalid = false; + warningIconGraphic = Resources.Load("2FAIcons/SDK_Warning_Triangle_icon") as Texture2D; + } + + public static void RefreshApiUrlSetting() + { + // this forces the static api url variable to be reset from the server environment set in editor prefs. + // needed because the static variable states get cleared when entering / exiting play mode + ApiServerEnvironment env = serverEnvironment; + serverEnvironment = env; + } + + public static void InitAccount() + { + if (isInitialized) + return; + + if (!APIUser.IsLoggedIn && ApiCredentials.Load()) + APIUser.InitialFetchCurrentUser((c) => AnalyticsSDK.LoggedInUserChanged(c.Model as APIUser), null); + + clientInstallPath = SDKClientUtilities.GetSavedVRCInstallPath(); + if (string.IsNullOrEmpty(clientInstallPath)) + clientInstallPath = SDKClientUtilities.LoadRegistryVRCInstallPath(); + + signingIn = false; + isInitialized = true; + + ClearContent(); + } + + public static bool OnShowStatus() + { + API.SetOnlineMode(true); + + SignIn(false); + + EditorGUILayout.BeginVertical(); + + if (APIUser.IsLoggedIn) + { + OnCreatorStatusGUI(); + } + + EditorGUILayout.EndVertical(); + + return APIUser.IsLoggedIn; + } + + static bool OnAccountGUI() + { + const int ACCOUNT_LOGIN_BORDER_SPACING = 20; + + EditorGUILayout.Separator(); + EditorGUILayout.Separator(); + EditorGUILayout.Separator(); + EditorGUILayout.Separator(); + + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.Space(ACCOUNT_LOGIN_BORDER_SPACING); + GUILayout.BeginVertical("Account", "window", GUILayout.Height(150), GUILayout.Width(340)); + + if (signingIn) + { + EditorGUILayout.LabelField("Signing in as " + username + "."); + } + else if (APIUser.IsLoggedIn) + { + if (Status != "Connected") + EditorGUILayout.LabelField(Status); + + OnCreatorStatusGUI(); + + GUILayout.BeginHorizontal(); + GUILayout.Label(""); + + if (GUILayout.Button("Logout")) + { + storedUsername = username = null; + storedPassword = password = null; + + VRC.Tools.ClearCookies(); + APIUser.Logout(); + ClearContent(); + } + GUILayout.EndHorizontal(); + } + else + { + InitAccount(); + + ApiServerEnvironment newEnv = ApiServerEnvironment.Release; + if (VRCSettings.DisplayAdvancedSettings) + newEnv = (ApiServerEnvironment)EditorGUILayout.EnumPopup("Use API", serverEnvironment); + if (serverEnvironment != newEnv) + serverEnvironment = newEnv; + + username = EditorGUILayout.TextField("Username/Email", username); + password = EditorGUILayout.PasswordField("Password", password); + + if (GUILayout.Button("Sign In")) + SignIn(true); + if (GUILayout.Button("Sign up")) + Application.OpenURL("https://vrchat.com/register"); + } + + if (showTwoFactorAuthenticationEntry) + { + OnTwoFactorAuthenticationGUI(); + } + + GUILayout.EndVertical(); + GUILayout.Space(ACCOUNT_LOGIN_BORDER_SPACING); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + return !signingIn; + } + + static void OnCreatorStatusGUI() + { + EditorGUILayout.LabelField("Logged in as:", APIUser.CurrentUser.displayName); + + //if (SDKClientUtilities.IsInternalSDK()) + // EditorGUILayout.LabelField("Developer Status: ", APIUser.CurrentUser.developerType.ToString()); + + EditorGUILayout.BeginHorizontal(); + + EditorGUILayout.BeginVertical(); + EditorGUILayout.LabelField("World Creator Status: ", APIUser.CurrentUser.canPublishWorlds ? "Allowed to publish worlds" : "Not yet allowed to publish worlds"); + EditorGUILayout.LabelField("Avatar Creator Status: ", APIUser.CurrentUser.canPublishAvatars ? "Allowed to publish avatars" : "Not yet allowed to publish avatars"); + EditorGUILayout.EndVertical(); + + if (!APIUser.CurrentUser.canPublishAllContent) + { + if (GUILayout.Button("More Info...")) + { + VRCSdkControlPanel.ShowContentPublishPermissionsDialog(); + } + } + + + EditorGUILayout.EndHorizontal(); + } + + void ShowAccount() + { + if (VRC.Core.ConfigManager.RemoteConfig.IsInitialized()) + { + if (VRC.Core.ConfigManager.RemoteConfig.HasKey("sdkUnityVersion")) + { + string sdkUnityVersion = VRC.Core.ConfigManager.RemoteConfig.GetString("sdkUnityVersion"); + if (string.IsNullOrEmpty(sdkUnityVersion)) + EditorGUILayout.LabelField("Could not fetch remote config."); + else if (Application.unityVersion != sdkUnityVersion) + { + EditorGUILayout.LabelField("Unity Version", EditorStyles.boldLabel); + EditorGUILayout.LabelField("Wrong Unity version. Please use " + sdkUnityVersion); + } + } + } + else + { + VRC.Core.API.SetOnlineMode(true, "vrchat"); + VRC.Core.ConfigManager.RemoteConfig.Init(); + } + + OnAccountGUI(); + } + + + private const string TWO_FACTOR_AUTHENTICATION_HELP_URL = "https://docs.vrchat.com/docs/setup-2fa"; + + private const string ENTER_2FA_CODE_TITLE_STRING = "Enter a numeric code from your authenticator app (or one of your saved recovery codes)."; + private const string ENTER_2FA_CODE_LABEL_STRING = "Code:"; + + private const string CHECKING_2FA_CODE_STRING = "Checking code..."; + private const string ENTER_2FA_CODE_INVALID_CODE_STRING = "Invalid Code"; + + private const string ENTER_2FA_CODE_VERIFY_STRING = "Verify"; + private const string ENTER_2FA_CODE_CANCEL_STRING = "Cancel"; + private const string ENTER_2FA_CODE_HELP_STRING = "Help"; + + private const int WARNING_ICON_SIZE = 60; + private const int WARNING_FONT_HEIGHT = 18; + + static private Texture2D warningIconGraphic; + + static bool entered2faCodeIsInvalid; + static bool authorizationCodeWasVerified; + + static private int previousAuthenticationCodeLength = 0; + static bool checkingCode; + static string authenticationCode = ""; + + static System.Action onAuthenticationVerifiedAction; + + static bool _showTwoFactorAuthenticationEntry = false; + + static bool showTwoFactorAuthenticationEntry + { + get + { + return _showTwoFactorAuthenticationEntry; + } + set + { + _showTwoFactorAuthenticationEntry = value; + authenticationCode = ""; + if (!_showTwoFactorAuthenticationEntry && !authorizationCodeWasVerified) + Logout(); + } + } + + static bool IsValidAuthenticationCodeFormat() + { + bool isValid2faAuthenticationCode = false; + + if (!string.IsNullOrEmpty(authenticationCode)) + { + // check if the input is a valid 6-digit numberic code (ignoring spaces) + Regex rx = new Regex(@"^(\s*\d\s*){6}$", RegexOptions.Compiled); + MatchCollection matches6DigitCode = rx.Matches(authenticationCode); + isValid2faAuthenticationCode = (matches6DigitCode.Count == 1); + } + + return isValid2faAuthenticationCode; + } + + static bool IsValidRecoveryCodeFormat() + { + bool isValid2faRecoveryCode = false; + + if (!string.IsNullOrEmpty(authenticationCode)) + { + // check if the input is a valid 8-digit alpha-numberic code (format xxxx-xxxx) "-" is optional & ignore any spaces + // OTP codes also exclude the letters i,l,o and the digit 1 to prevent any confusion + Regex rx = new Regex(@"^(\s*[a-hj-km-np-zA-HJ-KM-NP-Z02-9]\s*){4}-?(\s*[a-hj-km-np-zA-HJ-KM-NP-Z02-9]\s*){4}$", RegexOptions.Compiled); + MatchCollection matchesRecoveryCode = rx.Matches(authenticationCode); + isValid2faRecoveryCode = (matchesRecoveryCode.Count == 1); + } + + return isValid2faRecoveryCode; + } + + static void OnTwoFactorAuthenticationGUI() + { + const int ENTER_2FA_CODE_BORDER_SIZE = 20; + const int ENTER_2FA_CODE_BUTTON_WIDTH = 260; + const int ENTER_2FA_CODE_VERIFY_BUTTON_WIDTH = ENTER_2FA_CODE_BUTTON_WIDTH / 2; + const int ENTER_2FA_CODE_ENTRY_REGION_WIDTH = 130; + const int ENTER_2FA_CODE_MIN_WINDOW_WIDTH = ENTER_2FA_CODE_VERIFY_BUTTON_WIDTH + ENTER_2FA_CODE_ENTRY_REGION_WIDTH + (ENTER_2FA_CODE_BORDER_SIZE * 3); + + bool isValidAuthenticationCode = IsValidAuthenticationCodeFormat(); + + + // Invalid code text + if (entered2faCodeIsInvalid) + { + GUIStyle s = new GUIStyle(EditorStyles.label); + s.alignment = TextAnchor.UpperLeft; + s.normal.textColor = Color.red; + s.fontSize = WARNING_FONT_HEIGHT; + s.padding = new RectOffset(0, 0, (WARNING_ICON_SIZE - s.fontSize) / 2, 0); + s.fixedHeight = WARNING_ICON_SIZE; + + EditorGUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + + EditorGUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + EditorGUILayout.BeginHorizontal(); + var textDimensions = s.CalcSize(new GUIContent(ENTER_2FA_CODE_INVALID_CODE_STRING)); + GUILayout.Label(new GUIContent(warningIconGraphic), GUILayout.Width(WARNING_ICON_SIZE), GUILayout.Height(WARNING_ICON_SIZE)); + EditorGUILayout.LabelField(ENTER_2FA_CODE_INVALID_CODE_STRING, s, GUILayout.Width(textDimensions.x)); + EditorGUILayout.EndHorizontal(); + GUILayout.FlexibleSpace(); + EditorGUILayout.EndVertical(); + + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + } + else if (checkingCode) + { + // Display checking code message + EditorGUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + EditorGUILayout.BeginHorizontal(); + GUIStyle s = new GUIStyle(EditorStyles.label); + s.alignment = TextAnchor.MiddleCenter; + s.fixedHeight = WARNING_ICON_SIZE; + EditorGUILayout.LabelField(CHECKING_2FA_CODE_STRING, s, GUILayout.Height(WARNING_ICON_SIZE)); + EditorGUILayout.EndHorizontal(); + GUILayout.FlexibleSpace(); + EditorGUILayout.EndVertical(); + } + else + { + EditorGUILayout.BeginHorizontal(); + GUILayout.Space(ENTER_2FA_CODE_BORDER_SIZE); + GUILayout.FlexibleSpace(); + GUIStyle titleStyle = new GUIStyle(EditorStyles.label); + titleStyle.alignment = TextAnchor.MiddleCenter; + titleStyle.wordWrap = true; + EditorGUILayout.LabelField(ENTER_2FA_CODE_TITLE_STRING, titleStyle, GUILayout.Width(ENTER_2FA_CODE_MIN_WINDOW_WIDTH - (2 * ENTER_2FA_CODE_BORDER_SIZE)), GUILayout.Height(WARNING_ICON_SIZE), GUILayout.ExpandHeight(true)); + GUILayout.FlexibleSpace(); + GUILayout.Space(ENTER_2FA_CODE_BORDER_SIZE); + EditorGUILayout.EndHorizontal(); + } + + EditorGUILayout.BeginHorizontal(); + GUILayout.Space(ENTER_2FA_CODE_BORDER_SIZE); + GUILayout.FlexibleSpace(); + Vector2 size = EditorStyles.boldLabel.CalcSize(new GUIContent(ENTER_2FA_CODE_LABEL_STRING)); + EditorGUILayout.LabelField(ENTER_2FA_CODE_LABEL_STRING, EditorStyles.boldLabel, GUILayout.MaxWidth(size.x)); + authenticationCode = EditorGUILayout.TextField(authenticationCode); + + // Verify 2FA code button + if (GUILayout.Button(ENTER_2FA_CODE_VERIFY_STRING, GUILayout.Width(ENTER_2FA_CODE_VERIFY_BUTTON_WIDTH))) + { + checkingCode = true; + APIUser.VerifyTwoFactorAuthCode(authenticationCode, isValidAuthenticationCode ? API2FA.TIME_BASED_ONE_TIME_PASSWORD_AUTHENTICATION : API2FA.ONE_TIME_PASSWORD_AUTHENTICATION, username, password, + delegate + { + // valid 2FA code submitted + entered2faCodeIsInvalid = false; + authorizationCodeWasVerified = true; + checkingCode = false; + showTwoFactorAuthenticationEntry = false; + if (null != onAuthenticationVerifiedAction) + onAuthenticationVerifiedAction(); + }, + delegate + { + entered2faCodeIsInvalid = true; + checkingCode = false; + } + ); + } + + GUILayout.FlexibleSpace(); + GUILayout.Space(ENTER_2FA_CODE_BORDER_SIZE); + EditorGUILayout.EndHorizontal(); + + GUILayout.FlexibleSpace(); + + EditorGUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + + // after user has entered an invalid code causing the invalid code message to be displayed, + // edit the code will change it's length meaning it is invalid format, so we can clear the invalid code setting until they resubmit + if (previousAuthenticationCodeLength != authenticationCode.Length) + { + previousAuthenticationCodeLength = authenticationCode.Length; + entered2faCodeIsInvalid = false; + } + + GUI.enabled = true; + GUILayout.FlexibleSpace(); + GUILayout.Space(ENTER_2FA_CODE_BORDER_SIZE); + EditorGUILayout.EndHorizontal(); + + GUILayout.FlexibleSpace(); + + // Two-Factor Authentication Help button + EditorGUILayout.BeginHorizontal(); + if (GUILayout.Button(ENTER_2FA_CODE_HELP_STRING)) + { + Application.OpenURL(TWO_FACTOR_AUTHENTICATION_HELP_URL); + } + EditorGUILayout.EndHorizontal(); + + // Cancel button + EditorGUILayout.BeginHorizontal(); + if (GUILayout.Button(ENTER_2FA_CODE_CANCEL_STRING)) + { + showTwoFactorAuthenticationEntry = false; + Logout(); + } + EditorGUILayout.EndHorizontal(); + } + + private static string Status + { + get + { + if (!APIUser.IsLoggedIn) + return error == null ? "Please log in." : "Error in authenticating: " + error; + if (signingIn) + return "Logging in."; + else + { + if( serverEnvironment == ApiServerEnvironment.Dev ) + return "Connected to " + serverEnvironment.ToString(); + return "Connected"; + } + } + } + + private static void OnAuthenticationCompleted() + { + AttemptLogin(); + } + + private static void AttemptLogin() + { + APIUser.Login(username, password, + delegate (ApiModelContainer<APIUser> c) + { + APIUser user = c.Model as APIUser; + if (c.Cookies.ContainsKey("twoFactorAuth")) + ApiCredentials.Set(user.username, username, "vrchat", c.Cookies["auth"], c.Cookies["twoFactorAuth"]); + else if (c.Cookies.ContainsKey("auth")) + ApiCredentials.Set(user.username, username, "vrchat", c.Cookies["auth"]); + else + ApiCredentials.SetHumanName(user.username); + signingIn = false; + error = null; + storedUsername = null; + storedPassword = null; + AnalyticsSDK.LoggedInUserChanged(user); + + if (!APIUser.CurrentUser.canPublishAllContent) + { + if (UnityEditor.SessionState.GetString("HasShownContentPublishPermissionsDialogForUser", "") != user.id) + { + UnityEditor.SessionState.SetString("HasShownContentPublishPermissionsDialogForUser", user.id); + VRCSdkControlPanel.ShowContentPublishPermissionsDialog(); + } + } + }, + delegate (ApiModelContainer<APIUser> c) + { + Logout(); + error = c.Error; + VRC.Core.Logger.Log("Error logging in: " + error); + }, + delegate (ApiModelContainer<API2FA> c) + { + if (c.Cookies.ContainsKey("auth")) + ApiCredentials.Set(username, username, "vrchat", c.Cookies["auth"]); + showTwoFactorAuthenticationEntry = true; + onAuthenticationVerifiedAction = OnAuthenticationCompleted; + } + ); + } + + + private static object syncObject = new object(); + private static void SignIn(bool explicitAttempt) + { + lock (syncObject) + { + if (signingIn + || APIUser.IsLoggedIn + || (!explicitAttempt && string.IsNullOrEmpty(storedUsername))) + return; + + signingIn = true; + } + + InitAccount(); + + AttemptLogin(); + } + + public static void Logout() + { + signingIn = false; + storedUsername = null; + storedPassword = null; + VRC.Tools.ClearCookies(); + APIUser.Logout(); + } + + private void AccountDestroy() + { + signingIn = false; + isInitialized = false; + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelAccount.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelAccount.cs.meta new file mode 100644 index 00000000..33a77121 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelAccount.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5066cd5c1cc208143a1253cac821714a +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelAvatarBuilder.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelAvatarBuilder.cs new file mode 100644 index 00000000..9efdbc2d --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelAvatarBuilder.cs @@ -0,0 +1,1154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEngine; +using UnityEngine.Networking; +using VRC.SDKBase.Editor.BuildPipeline; +using VRC.SDKBase.Validation.Performance; +using VRC.SDKBase.Validation.Performance.Stats; +using Object = UnityEngine.Object; + +namespace VRC.SDKBase.Editor +{ + public class VRCSdkControlPanelAvatarBuilder : IVRCSdkControlPanelBuilder + { + protected VRCSdkControlPanel _builder; + private VRC_AvatarDescriptor[] _avatars; + private static VRC_AvatarDescriptor _selectedAvatar; + private Vector2 _avatarListScrollPos; + private Vector2 _scrollPos; + + + protected const int MAX_ACTION_TEXTURE_SIZE = 256; + + private bool showAvatarPerformanceDetails + { + get => EditorPrefs.GetBool("VRC.SDKBase_showAvatarPerformanceDetails", false); + set => EditorPrefs.SetBool("VRC.SDKBase_showAvatarPerformanceDetails", + value); //Do we ever actually set this? + } + + private static PropertyInfo _legacyBlendShapeNormalsPropertyInfo; + + private static PropertyInfo LegacyBlendShapeNormalsPropertyInfo + { + get + { + if (_legacyBlendShapeNormalsPropertyInfo != null) + { + return _legacyBlendShapeNormalsPropertyInfo; + } + + Type modelImporterType = typeof(ModelImporter); + _legacyBlendShapeNormalsPropertyInfo = modelImporterType.GetProperty( + "legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes", + BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public + ); + + return _legacyBlendShapeNormalsPropertyInfo; + } + } + + public void ShowSettingsOptions() + { + EditorGUILayout.BeginVertical(VRCSdkControlPanel.boxGuiStyle); + GUILayout.Label("Avatar Options", EditorStyles.boldLabel); + bool prevShowPerfDetails = showAvatarPerformanceDetails; + bool showPerfDetails = + EditorGUILayout.ToggleLeft("Show All Avatar Performance Details", prevShowPerfDetails); + if (showPerfDetails != prevShowPerfDetails) + { + showAvatarPerformanceDetails = showPerfDetails; + _builder.ResetIssues(); + } + + EditorGUILayout.EndVertical(); + } + + public bool IsValidBuilder(out string message) + { + FindAvatars(); + message = null; + if (_avatars != null && _avatars.Length > 0) return true; +#if VRC_SDK_VRCSDK2 + message = "A VRC_SceneDescriptor or VRC_AvatarDescriptor\nis required to build VRChat SDK Content"; +#elif VRC_SDK_VRCSDK3 + message = "A VRCSceneDescriptor or VRCAvatarDescriptor\nis required to build VRChat SDK Content"; +#endif + return false; + } + + public virtual void ShowBuilder() + { + if (_avatars.Length > 0) + { + if (!_builder.CheckedForIssues) + { + _builder.ResetIssues(); + foreach (VRC_AvatarDescriptor t in _avatars) + OnGUIAvatarCheck(t); + _builder.CheckedForIssues = true; + } + + bool drawList = true; + if (_avatars.Length == 1) + { + drawList = false; + _selectedAvatar = _avatars[0]; + } + + if (drawList) + { + EditorGUILayout.BeginVertical(GUI.skin.GetStyle("HelpBox"), + GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth), + GUILayout.MaxHeight(150)); + _avatarListScrollPos = EditorGUILayout.BeginScrollView(_avatarListScrollPos, false, false); + + for (int i = 0; i < _avatars.Length; ++i) + { + VRC_AvatarDescriptor av = _avatars[i]; + EditorGUILayout.Space(); + if (_selectedAvatar == av) + { + if (GUILayout.Button(av.gameObject.name, + VRCSdkControlPanel.listButtonStyleSelected, + GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth - 50))) + _selectedAvatar = null; + } + else + { + if (GUILayout.Button(av.gameObject.name, + ((i & 0x01) > 0) + ? (VRCSdkControlPanel.listButtonStyleOdd) + : (VRCSdkControlPanel.listButtonStyleEven), + GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth - 50))) + _selectedAvatar = av; + } + } + + EditorGUILayout.EndScrollView(); + EditorGUILayout.EndVertical(); + } + + EditorGUILayout.BeginVertical(GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth)); + _builder.OnGUIShowIssues(); + EditorGUILayout.EndVertical(); + + EditorGUILayout.Separator(); + + if (_selectedAvatar != null) + { + EditorGUILayout.BeginVertical(VRCSdkControlPanel.boxGuiStyle); + OnGUIAvatarSettings(_selectedAvatar); + EditorGUILayout.EndVertical(); + + _scrollPos = EditorGUILayout.BeginScrollView(_scrollPos, + false, + false, + GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth)); + _builder.OnGUIShowIssues(_selectedAvatar); + EditorGUILayout.EndScrollView(); + + GUILayout.FlexibleSpace(); + + OnGUIAvatar(_selectedAvatar); + } + } + else + { + EditorGUILayout.Space(); + if (UnityEditor.BuildPipeline.isBuildingPlayer) + { + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Building – Please Wait ...", + VRCSdkControlPanel.titleGuiStyle, + GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth)); + } + else + { +#if VRC_SDK_VRCSDK2 + EditorGUILayout.LabelField( + "A VRC_SceneDescriptor or VRC_AvatarDescriptor\nis required to build VRChat SDK Content", + VRCSdkControlPanel.titleGuiStyle, GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth)); +#elif VRC_SDK_VRCSDK3 + EditorGUILayout.LabelField( + "A VRCSceneDescriptor or VRCAvatarDescriptor\nis required to build VRChat SDK Content", + VRCSdkControlPanel.titleGuiStyle, GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth)); +#else + EditorGUILayout.LabelField("A SceneDescriptor or AvatarDescriptor\nis required to build VRChat SDK Content", VRCSdkControlPanel.titleGuiStyle, GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth)); +#endif + } + } + } + + public void RegisterBuilder(VRCSdkControlPanel baseBuilder) + { + _builder = baseBuilder; + } + + public void SelectAllComponents() + { + List<Object> show = new List<Object>(Selection.objects); + foreach (VRC_AvatarDescriptor a in _avatars) + show.Add(a.gameObject); + Selection.objects = show.ToArray(); + } + + private void FindAvatars() + { + List<VRC_AvatarDescriptor> allAvatars = Tools.FindSceneObjectsOfTypeAll<VRC_AvatarDescriptor>().ToList(); + // Select only the active avatars + VRC_AvatarDescriptor[] newAvatars = + allAvatars.Where(av => null != av && av.gameObject.activeInHierarchy).ToArray(); + + if (_avatars != null) + { + foreach (VRC_AvatarDescriptor a in newAvatars) + if (_avatars.Contains(a) == false) + _builder.CheckedForIssues = false; + } + + _avatars = newAvatars; + } + + public virtual void OnGUIAvatarCheck(VRC_AvatarDescriptor avatar) + { + string vrcFilePath = UnityWebRequest.UnEscapeURL(EditorPrefs.GetString("currentBuildingAssetBundlePath")); + if (!string.IsNullOrEmpty(vrcFilePath) && + ValidationHelpers.CheckIfAssetBundleFileTooLarge(ContentType.Avatar, vrcFilePath, out int fileSize)) + { + _builder.OnGUIWarning(avatar, + ValidationHelpers.GetAssetBundleOverSizeLimitMessageSDKWarning(ContentType.Avatar, fileSize), + delegate { Selection.activeObject = avatar.gameObject; }, null); + } + + AvatarPerformanceStats perfStats = new AvatarPerformanceStats(); + AvatarPerformance.CalculatePerformanceStats(avatar.Name, avatar.gameObject, perfStats); + + OnGUIPerformanceInfo(avatar, perfStats, AvatarPerformanceCategory.Overall, + GetAvatarSubSelectAction(avatar, typeof(VRC_AvatarDescriptor)), null); + OnGUIPerformanceInfo(avatar, perfStats, AvatarPerformanceCategory.PolyCount, + GetAvatarSubSelectAction(avatar, new[] {typeof(MeshRenderer), typeof(SkinnedMeshRenderer)}), null); + OnGUIPerformanceInfo(avatar, perfStats, AvatarPerformanceCategory.AABB, + GetAvatarSubSelectAction(avatar, typeof(VRC_AvatarDescriptor)), null); + + if (avatar.lipSync == VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape && + avatar.VisemeSkinnedMesh == null) + _builder.OnGUIError(avatar, "This avatar uses Visemes but the Face Mesh is not specified.", + delegate { Selection.activeObject = avatar.gameObject; }, null); + + if (ShaderKeywordsUtility.DetectCustomShaderKeywords(avatar)) + _builder.OnGUIWarning(avatar, + "A Material on this avatar has custom shader keywords. Please consider optimizing it using the Shader Keywords Utility.", + () => { Selection.activeObject = avatar.gameObject; }, + () => + { + EditorApplication.ExecuteMenuItem("VRChat SDK/Utilities/Avatar Shader Keywords Utility"); + }); + + VerifyAvatarMipMapStreaming(avatar); + + Animator anim = avatar.GetComponent<Animator>(); + if (anim == null) + { + _builder.OnGUIWarning(avatar, + "This avatar does not contain an Animator, and will not animate in VRChat.", + delegate { Selection.activeObject = avatar.gameObject; }, null); + } + else if (anim.isHuman == false) + { + _builder.OnGUIWarning(avatar, + "This avatar is not imported as a humanoid rig and will not play VRChat's provided animation set.", + delegate { Selection.activeObject = avatar.gameObject; }, null); + } + else if (avatar.gameObject.activeInHierarchy == false) + { + _builder.OnGUIError(avatar, "Your avatar is disabled in the scene hierarchy!", + delegate { Selection.activeObject = avatar.gameObject; }, null); + } + else + { + Transform lFoot = anim.GetBoneTransform(HumanBodyBones.LeftFoot); + Transform rFoot = anim.GetBoneTransform(HumanBodyBones.RightFoot); + if ((lFoot == null) || (rFoot == null)) + _builder.OnGUIError(avatar, "Your avatar is humanoid, but its feet aren't specified!", + delegate { Selection.activeObject = avatar.gameObject; }, null); + if (lFoot != null && rFoot != null) + { + Vector3 footPos = lFoot.position - avatar.transform.position; + if (footPos.y < 0) + _builder.OnGUIWarning(avatar, + "Avatar feet are beneath the avatar's origin (the floor). That's probably not what you want.", + delegate + { + List<Object> gos = new List<Object> {rFoot.gameObject, lFoot.gameObject}; + Selection.objects = gos.ToArray(); + }, null); + } + + Transform lShoulder = anim.GetBoneTransform(HumanBodyBones.LeftUpperArm); + Transform rShoulder = anim.GetBoneTransform(HumanBodyBones.RightUpperArm); + if (lShoulder == null || rShoulder == null) + _builder.OnGUIError(avatar, "Your avatar is humanoid, but its upper arms aren't specified!", + delegate { Selection.activeObject = avatar.gameObject; }, null); + if (lShoulder != null && rShoulder != null) + { + Vector3 shoulderPosition = lShoulder.position - avatar.transform.position; + if (shoulderPosition.y < 0.2f) + _builder.OnGUIError(avatar, "This avatar is too short. The minimum is 20cm shoulder height.", + delegate { Selection.activeObject = avatar.gameObject; }, null); + else if (shoulderPosition.y < 1.0f) + _builder.OnGUIWarning(avatar, "This avatar is shorter than average.", + delegate { Selection.activeObject = avatar.gameObject; }, null); + else if (shoulderPosition.y > 5.0f) + _builder.OnGUIWarning(avatar, "This avatar is too tall. The maximum is 5m shoulder height.", + delegate { Selection.activeObject = avatar.gameObject; }, null); + else if (shoulderPosition.y > 2.5f) + _builder.OnGUIWarning(avatar, "This avatar is taller than average.", + delegate { Selection.activeObject = avatar.gameObject; }, null); + } + + if (AnalyzeIK(avatar, anim) == false) + _builder.OnGUILink(avatar, "See Avatar Rig Requirements for more information.", + VRCSdkControlPanel.AVATAR_RIG_REQUIREMENTS_URL); + } + + ValidateFeatures(avatar, anim, perfStats); + + Core.PipelineManager pm = avatar.GetComponent<Core.PipelineManager>(); + + PerformanceRating rating = perfStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.Overall); + if (_builder.NoGuiErrors()) + { + if (!anim.isHuman) + { + if (pm != null) pm.fallbackStatus = Core.PipelineManager.FallbackStatus.InvalidRig; + _builder.OnGUIInformation(avatar, "This avatar does not have a humanoid rig, so it can not be used as a custom fallback."); + } + else if (rating > PerformanceRating.Good) + { + if (pm != null) pm.fallbackStatus = Core.PipelineManager.FallbackStatus.InvalidPerformance; + _builder.OnGUIInformation(avatar, "This avatar does not have an overall rating of Good or better, so it can not be used as a custom fallback. See the link below for details on Avatar Optimization."); + } + else + { + if (pm != null) pm.fallbackStatus = Core.PipelineManager.FallbackStatus.Valid; + _builder.OnGUIInformation(avatar, "This avatar can be used as a custom fallback. This avatar must be uploaded for every supported platform to be valid for fallback selection."); + if (perfStats.animatorCount.HasValue && perfStats.animatorCount.Value > 1) + _builder.OnGUIInformation(avatar, "This avatar uses additional animators, they will be disabled when used as a fallback."); + } + + // additional messages for Poor and Very Poor Avatars +#if UNITY_ANDROID + if (rating > PerformanceRating.Poor) + _builder.OnGUIInformation(avatar, "This avatar will be blocked by default due to performance. Your fallback will be shown instead."); + else if (rating > PerformanceRating.Medium) + _builder.OnGUIInformation(avatar, "Other users may choose to block this avatar due to performance. Your fallback will be shown instead."); +#else + if (rating > PerformanceRating.Medium) + _builder.OnGUIInformation(avatar, "Other users may choose to block this avatar due to performance. Your fallback will be shown instead."); +#endif + } + else + { + // shouldn't matter because we can't hit upload button + if (pm != null) pm.fallbackStatus = Core.PipelineManager.FallbackStatus.InvalidPlatform; + } + } + + public virtual void ValidateFeatures(VRC_AvatarDescriptor avatar, Animator anim, AvatarPerformanceStats perfStats) + { + // stub, used in SDK3A for Expression Menu, etc. + } + + protected void OnGUIPerformanceInfo(VRC_AvatarDescriptor avatar, AvatarPerformanceStats perfStats, + AvatarPerformanceCategory perfCategory, Action show, Action fix) + { + PerformanceRating rating = perfStats.GetPerformanceRatingForCategory(perfCategory); + SDKPerformanceDisplay.GetSDKPerformanceInfoText(perfStats, perfCategory, out string text, + out PerformanceInfoDisplayLevel displayLevel); + + switch (displayLevel) + { + case PerformanceInfoDisplayLevel.None: + { + break; + } + case PerformanceInfoDisplayLevel.Verbose: + { + if (showAvatarPerformanceDetails) + { + _builder.OnGUIStat(avatar, text, rating, show, fix); + } + + break; + } + case PerformanceInfoDisplayLevel.Info: + { + _builder.OnGUIStat(avatar, text, rating, show, fix); + break; + } + case PerformanceInfoDisplayLevel.Warning: + { + _builder.OnGUIStat(avatar, text, rating, show, fix); + break; + } + case PerformanceInfoDisplayLevel.Error: + { + _builder.OnGUIStat(avatar, text, rating, show, fix); + _builder.OnGUIError(avatar, text, delegate { Selection.activeObject = avatar.gameObject; }, null); + break; + } + default: + { + _builder.OnGUIError(avatar, "Unknown performance display level.", + delegate { Selection.activeObject = avatar.gameObject; }, null); + break; + } + } + } + + public virtual void OnGUIAvatar(VRC_AvatarDescriptor avatar) + { + EditorGUILayout.BeginVertical(VRCSdkControlPanel.boxGuiStyle); + EditorGUILayout.BeginHorizontal(); + + EditorGUILayout.BeginVertical(GUILayout.Width(300)); + EditorGUILayout.Space(); + + GUILayout.Label("Online Publishing", VRCSdkControlPanel.infoGuiStyle); + GUILayout.Label( + "In order for other people to see your avatar in VRChat it must be built and published to our game servers.", + VRCSdkControlPanel.infoGuiStyle); + + EditorGUILayout.EndVertical(); + + EditorGUILayout.BeginVertical(GUILayout.Width(200)); + EditorGUILayout.Space(); + + GUI.enabled = _builder.NoGuiErrorsOrIssues() || + Core.APIUser.CurrentUser.developerType == Core.APIUser.DeveloperType.Internal; + if (GUILayout.Button(VRCSdkControlPanel.GetBuildAndPublishButtonString())) + { + bool buildBlocked = !VRCBuildPipelineCallbacks.OnVRCSDKBuildRequested(VRCSDKRequestedBuildType.Avatar); + if (!buildBlocked) + { + if (Core.APIUser.CurrentUser.canPublishAvatars) + { + EnvConfig.FogSettings originalFogSettings = EnvConfig.GetFogSettings(); + EnvConfig.SetFogSettings( + new EnvConfig.FogSettings(EnvConfig.FogSettings.FogStrippingMode.Custom, true, true, true)); + +#if UNITY_ANDROID + EditorPrefs.SetBool("VRC.SDKBase_StripAllShaders", true); +#else + EditorPrefs.SetBool("VRC.SDKBase_StripAllShaders", false); +#endif + +#if VRC_SDK_VRCSDK2 + VRC_SdkBuilder.shouldBuildUnityPackage = VRCSdkControlPanel.FutureProofPublishEnabled; + VRC_SdkBuilder.ExportAndUploadAvatarBlueprint(avatar.gameObject); +#endif + + EnvConfig.SetFogSettings(originalFogSettings); + + // this seems to workaround a Unity bug that is clearing the formatting of two levels of Layout + // when we call the upload functions + return; + } + else + { + VRCSdkControlPanel.ShowContentPublishPermissionsDialog(); + } + } + } + + EditorGUILayout.EndVertical(); + EditorGUILayout.Space(); + + EditorGUILayout.EndHorizontal(); + EditorGUILayout.EndVertical(); + + GUI.enabled = true; + } + + private static void OnGUIAvatarSettings(VRC_AvatarDescriptor avatar) + { + EditorGUILayout.BeginVertical(VRCSdkControlPanel.boxGuiStyle, GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth)); + + string name = "Unpublished Avatar - " + avatar.gameObject.name; + if (avatar.apiAvatar != null) + name = (avatar.apiAvatar as Core.ApiAvatar)?.name; + EditorGUILayout.Space(); + EditorGUILayout.LabelField(name, VRCSdkControlPanel.titleGuiStyle); + + Core.PipelineManager pm = avatar.GetComponent<Core.PipelineManager>(); + if (pm != null && !string.IsNullOrEmpty(pm.blueprintId)) + { + if (avatar.apiAvatar == null) + { + Core.ApiAvatar av = Core.API.FromCacheOrNew<Core.ApiAvatar>(pm.blueprintId); + av.Fetch( + c => avatar.apiAvatar = c.Model as Core.ApiAvatar, + c => + { + if (c.Code == 404) + { + Core.Logger.Log( + $"Could not load avatar {pm.blueprintId} because it didn't exist.", + Core.DebugLevel.API); + Core.ApiCache.Invalidate<Core.ApiWorld>(pm.blueprintId); + } + else + Debug.LogErrorFormat("Could not load avatar {0} because {1}", pm.blueprintId, c.Error); + }); + avatar.apiAvatar = av; + } + } + + if (avatar.apiAvatar != null) + { + Core.ApiAvatar a = (avatar.apiAvatar as Core.ApiAvatar); + DrawContentInfoForAvatar(a); + VRCSdkControlPanel.DrawContentPlatformSupport(a); + } + + VRCSdkControlPanel.DrawBuildTargetSwitcher(); + EditorGUILayout.EndVertical(); + } + + private static void DrawContentInfoForAvatar(Core.ApiAvatar a) + { + VRCSdkControlPanel.DrawContentInfo(a.name, a.version.ToString(), a.description, null, a.releaseStatus, + a.tags); + } + + protected static Action GetAvatarSubSelectAction(Component avatar, Type[] types) + { + return () => + { + List<Object> gos = new List<Object>(); + foreach (Type t in types) + { + Component[] components = avatar.GetComponentsInChildren(t, true); + foreach (Component c in components) + gos.Add(c.gameObject); + } + + Selection.objects = gos.Count > 0 ? gos.ToArray() : new Object[] {avatar.gameObject}; + }; + } + + protected static Action GetAvatarSubSelectAction(Component avatar, Type type) + { + List<Type> t = new List<Type> {type}; + return GetAvatarSubSelectAction(avatar, t.ToArray()); + } + + private void VerifyAvatarMipMapStreaming(Component avatar) + { + List<Object> badTextures = new List<Object>(); + foreach (Renderer r in avatar.GetComponentsInChildren<Renderer>(true)) + { + foreach (Material m in r.sharedMaterials) + { + if (!m) + continue; + int[] texIDs = m.GetTexturePropertyNameIDs(); + if (texIDs == null) + continue; + foreach (int i in texIDs) + { + Texture t = m.GetTexture(i); + if (!t) + continue; + string path = AssetDatabase.GetAssetPath(t); + if (string.IsNullOrEmpty(path)) + continue; + TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter; + if (importer != null && importer.mipmapEnabled && !importer.streamingMipmaps) + badTextures.Add(importer); + } + } + } + + if (badTextures.Count == 0) + return; + + _builder.OnGUIError(avatar, "This avatar has mipmapped textures without 'Streaming Mip Maps' enabled.", + () => { Selection.objects = badTextures.ToArray(); }, + () => + { + List<string> paths = new List<string>(); + foreach (Object o in badTextures) + { + TextureImporter t = (TextureImporter) o; + Undo.RecordObject(t, "Set Mip Map Streaming"); + t.streamingMipmaps = true; + t.streamingMipmapsPriority = 0; + EditorUtility.SetDirty(t); + paths.Add(t.assetPath); + } + + AssetDatabase.ForceReserializeAssets(paths); + AssetDatabase.Refresh(); + }); + } + + private bool AnalyzeIK(Object ad, Animator anim) + { + bool hasHead; + bool hasFeet; + bool hasHands; +#if VRC_SDK_VRCSDK2 + bool hasThreeFingers; +#endif + bool correctSpineHierarchy; + bool correctLeftArmHierarchy; + bool correctRightArmHierarchy; + bool correctLeftLegHierarchy; + bool correctRightLegHierarchy; + + bool status = true; + + Transform head = anim.GetBoneTransform(HumanBodyBones.Head); + Transform lFoot = anim.GetBoneTransform(HumanBodyBones.LeftFoot); + Transform rFoot = anim.GetBoneTransform(HumanBodyBones.RightFoot); + Transform lHand = anim.GetBoneTransform(HumanBodyBones.LeftHand); + Transform rHand = anim.GetBoneTransform(HumanBodyBones.RightHand); + + hasHead = null != head; + hasFeet = (null != lFoot && null != rFoot); + hasHands = (null != lHand && null != rHand); + + if (!hasHead || !hasFeet || !hasHands) + { + _builder.OnGUIError(ad, "Humanoid avatar must have head, hands and feet bones mapped.", + delegate { Selection.activeObject = anim.gameObject; }, null); + return false; + } + + Transform lThumb = anim.GetBoneTransform(HumanBodyBones.LeftThumbProximal); + Transform lIndex = anim.GetBoneTransform(HumanBodyBones.LeftIndexProximal); + Transform lMiddle = anim.GetBoneTransform(HumanBodyBones.LeftMiddleProximal); + Transform rThumb = anim.GetBoneTransform(HumanBodyBones.RightThumbProximal); + Transform rIndex = anim.GetBoneTransform(HumanBodyBones.RightIndexProximal); + Transform rMiddle = anim.GetBoneTransform(HumanBodyBones.RightMiddleProximal); + +#if VRC_SDK_VRCSDK2 + // Finger test, only for v2 + hasThreeFingers = null != lThumb && null != lIndex && null != lMiddle && null != rThumb && null != rIndex && + null != rMiddle; + + if (!hasThreeFingers) + { + // although its only a warning, we return here because the rest + // of the analysis is for VR IK + _builder.OnGUIWarning(ad, + "Thumb, Index, and Middle finger bones are not mapped, Full-Body IK will be disabled.", + delegate { Selection.activeObject = anim.gameObject; }, null); + status = false; + } +#endif + + Transform pelvis = anim.GetBoneTransform(HumanBodyBones.Hips); + Transform chest = anim.GetBoneTransform(HumanBodyBones.Chest); + Transform upperChest = anim.GetBoneTransform(HumanBodyBones.UpperChest); + Transform torso = anim.GetBoneTransform(HumanBodyBones.Spine); + + Transform neck = anim.GetBoneTransform(HumanBodyBones.Neck); + Transform lClav = anim.GetBoneTransform(HumanBodyBones.LeftShoulder); + Transform rClav = anim.GetBoneTransform(HumanBodyBones.RightShoulder); + + + if (null == neck || null == lClav || null == rClav || null == pelvis || null == torso || null == chest) + { + string missingElements = + ((null == neck) ? "Neck, " : "") + + (((null == lClav) || (null == rClav)) ? "Shoulders, " : "") + + ((null == pelvis) ? "Pelvis, " : "") + + ((null == torso) ? "Spine, " : "") + + ((null == chest) ? "Chest, " : ""); + missingElements = missingElements.Remove(missingElements.LastIndexOf(',')) + "."; + _builder.OnGUIError(ad, "Spine hierarchy missing elements, please map: " + missingElements, + delegate { Selection.activeObject = anim.gameObject; }, null); + return false; + } + + if (null != upperChest) + correctSpineHierarchy = + lClav.parent == upperChest && rClav.parent == upperChest && neck.parent == upperChest; + else + correctSpineHierarchy = lClav.parent == chest && rClav.parent == chest && neck.parent == chest; + + if (!correctSpineHierarchy) + { + _builder.OnGUIError(ad, + "Spine hierarchy incorrect. Make sure that the parent of both Shoulders and the Neck is the Chest (or UpperChest if set).", + delegate + { + List<Object> gos = new List<Object> + { + lClav.gameObject, + rClav.gameObject, + neck.gameObject, + null != upperChest ? upperChest.gameObject : chest.gameObject + }; + Selection.objects = gos.ToArray(); + }, null); + return false; + } + + Transform lShoulder = anim.GetBoneTransform(HumanBodyBones.LeftUpperArm); + Transform lElbow = anim.GetBoneTransform(HumanBodyBones.LeftLowerArm); + Transform rShoulder = anim.GetBoneTransform(HumanBodyBones.RightUpperArm); + Transform rElbow = anim.GetBoneTransform(HumanBodyBones.RightLowerArm); + + correctLeftArmHierarchy = lShoulder && lElbow && lShoulder.GetChild(0) == lElbow && lHand && + lElbow.GetChild(0) == lHand; + correctRightArmHierarchy = rShoulder && rElbow && rShoulder.GetChild(0) == rElbow && rHand && + rElbow.GetChild(0) == rHand; + + if (!(correctLeftArmHierarchy && correctRightArmHierarchy)) + { + _builder.OnGUIWarning(ad, + "LowerArm is not first child of UpperArm or Hand is not first child of LowerArm: you may have problems with Forearm rotations.", + delegate + { + List<Object> gos = new List<Object>(); + if (!correctLeftArmHierarchy && lShoulder) + gos.Add(lShoulder.gameObject); + if (!correctRightArmHierarchy && rShoulder) + gos.Add(rShoulder.gameObject); + if (gos.Count > 0) + Selection.objects = gos.ToArray(); + else + Selection.activeObject = anim.gameObject; + }, null); + status = false; + } + + Transform lHip = anim.GetBoneTransform(HumanBodyBones.LeftUpperLeg); + Transform lKnee = anim.GetBoneTransform(HumanBodyBones.LeftLowerLeg); + Transform rHip = anim.GetBoneTransform(HumanBodyBones.RightUpperLeg); + Transform rKnee = anim.GetBoneTransform(HumanBodyBones.RightLowerLeg); + + correctLeftLegHierarchy = lHip && lKnee && lHip.GetChild(0) == lKnee && lKnee.GetChild(0) == lFoot; + correctRightLegHierarchy = rHip && rKnee && rHip.GetChild(0) == rKnee && rKnee.GetChild(0) == rFoot; + + if (!(correctLeftLegHierarchy && correctRightLegHierarchy)) + { + _builder.OnGUIWarning(ad, + "LowerLeg is not first child of UpperLeg or Foot is not first child of LowerLeg: you may have problems with Shin rotations.", + delegate + { + List<Object> gos = new List<Object>(); + if (!correctLeftLegHierarchy && lHip) + gos.Add(lHip.gameObject); + if (!correctRightLegHierarchy && rHip) + gos.Add(rHip.gameObject); + if (gos.Count > 0) + Selection.objects = gos.ToArray(); + else + Selection.activeObject = anim.gameObject; + }, null); + status = false; + } + + if (!(IsAncestor(pelvis, rFoot) && IsAncestor(pelvis, lFoot) && IsAncestor(pelvis, lHand) && + IsAncestor(pelvis, rHand))) + { + _builder.OnGUIWarning(ad, + "This avatar has a split hierarchy (Hips bone is not the ancestor of all humanoid bones). IK may not work correctly.", + delegate + { + List<Object> gos = new List<Object> {pelvis.gameObject}; + if (!IsAncestor(pelvis, rFoot)) + gos.Add(rFoot.gameObject); + if (!IsAncestor(pelvis, lFoot)) + gos.Add(lFoot.gameObject); + if (!IsAncestor(pelvis, lHand)) + gos.Add(lHand.gameObject); + if (!IsAncestor(pelvis, rHand)) + gos.Add(rHand.gameObject); + Selection.objects = gos.ToArray(); + }, null); + status = false; + } + + // if thigh bone rotations diverge from 180 from hip bone rotations, full-body tracking/ik does not work well + if (!lHip || !rHip) return status; + { + Vector3 hipLocalUp = pelvis.InverseTransformVector(Vector3.up); + Vector3 legLDir = lHip.TransformVector(hipLocalUp); + Vector3 legRDir = rHip.TransformVector(hipLocalUp); + float angL = Vector3.Angle(Vector3.up, legLDir); + float angR = Vector3.Angle(Vector3.up, legRDir); + if (!(angL < 175f) && !(angR < 175f)) return status; + string angle = $"{Mathf.Min(angL, angR):F1}"; + _builder.OnGUIWarning(ad, + $"The angle between pelvis and thigh bones should be close to 180 degrees (this avatar's angle is {angle}). Your avatar may not work well with full-body IK and Tracking.", + delegate + { + List<Object> gos = new List<Object>(); + if (angL < 175f) + gos.Add(rFoot.gameObject); + if (angR < 175f) + gos.Add(lFoot.gameObject); + Selection.objects = gos.ToArray(); + }, null); + status = false; + } + + return status; + } + + private static bool IsAncestor(Object ancestor, Transform child) + { + bool found = false; + Transform thisParent = child.parent; + while (thisParent != null) + { + if (thisParent == ancestor) + { + found = true; + break; + } + + thisParent = thisParent.parent; + } + + return found; + } + + protected void CheckAvatarMeshesForLegacyBlendShapesSetting(Component avatar) + { + if (LegacyBlendShapeNormalsPropertyInfo == null) + { + Debug.LogError( + "Could not check for legacy blend shape normals because 'legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes' was not found."); + return; + } + + // Get all of the meshes used by skinned mesh renderers. + HashSet<Mesh> avatarMeshes = GetAllMeshesInGameObjectHierarchy(avatar.gameObject); + HashSet<Mesh> incorrectlyConfiguredMeshes = + ScanMeshesForIncorrectBlendShapeNormalsSetting(avatarMeshes); + if (incorrectlyConfiguredMeshes.Count > 0) + { + _builder.OnGUIError( + avatar, + "This avatar contains skinned meshes that were imported with Blendshape Normals set to 'Calculate' but aren't using 'Legacy Blendshape Normals'. This will significantly increase the size of the uploaded avatar. This must be fixed in the mesh import settings before uploading.", + null, + () => { EnableLegacyBlendShapeNormals(incorrectlyConfiguredMeshes); }); + } + } + + private static HashSet<Mesh> ScanMeshesForIncorrectBlendShapeNormalsSetting(IEnumerable<Mesh> avatarMeshes) + { + HashSet<Mesh> incorrectlyConfiguredMeshes = new HashSet<Mesh>(); + foreach (Mesh avatarMesh in avatarMeshes) + { + // Can't get ModelImporter if the model isn't an asset. + if (!AssetDatabase.Contains(avatarMesh)) + { + continue; + } + + string meshAssetPath = AssetDatabase.GetAssetPath(avatarMesh); + if (string.IsNullOrEmpty(meshAssetPath)) + { + continue; + } + + ModelImporter avatarImporter = AssetImporter.GetAtPath(meshAssetPath) as ModelImporter; + if (avatarImporter == null) + { + continue; + } + + if (avatarImporter.importBlendShapeNormals != ModelImporterNormals.Calculate) + { + continue; + } + + bool useLegacyBlendShapeNormals = (bool) LegacyBlendShapeNormalsPropertyInfo.GetValue(avatarImporter); + if (useLegacyBlendShapeNormals) + { + continue; + } + + if (!incorrectlyConfiguredMeshes.Contains(avatarMesh)) + { + incorrectlyConfiguredMeshes.Add(avatarMesh); + } + } + + return incorrectlyConfiguredMeshes; + } + + private static void EnableLegacyBlendShapeNormals(IEnumerable<Mesh> meshesToFix) + { + HashSet<string> meshAssetPaths = new HashSet<string>(); + foreach (Mesh meshToFix in meshesToFix) + { + // Can't get ModelImporter if the model isn't an asset. + if (!AssetDatabase.Contains(meshToFix)) + { + continue; + } + + string meshAssetPath = AssetDatabase.GetAssetPath(meshToFix); + if (string.IsNullOrEmpty(meshAssetPath)) + { + continue; + } + + if (meshAssetPaths.Contains(meshAssetPath)) + { + continue; + } + + meshAssetPaths.Add(meshAssetPath); + } + + foreach (string meshAssetPath in meshAssetPaths) + { + ModelImporter avatarImporter = AssetImporter.GetAtPath(meshAssetPath) as ModelImporter; + if (avatarImporter == null) + { + continue; + } + + if (avatarImporter.importBlendShapeNormals != ModelImporterNormals.Calculate) + { + continue; + } + + LegacyBlendShapeNormalsPropertyInfo.SetValue(avatarImporter, true); + avatarImporter.SaveAndReimport(); + } + } + + protected void CheckAvatarMeshesForMeshReadWriteSetting(Component avatar) + { + // Get all of the meshes used by skinned mesh renderers. + HashSet<Mesh> avatarMeshes = GetAllMeshesInGameObjectHierarchy(avatar.gameObject); + HashSet<Mesh> incorrectlyConfiguredMeshes = + ScanMeshesForDisabledMeshReadWriteSetting(avatarMeshes); + if (incorrectlyConfiguredMeshes.Count > 0) + { + _builder.OnGUIError( + avatar, + "This avatar contains meshes that were imported with Read/Write disabled. This must be fixed in the mesh import settings before uploading.", + null, + () => { EnableMeshReadWrite(incorrectlyConfiguredMeshes); }); + } + } + + private static HashSet<Mesh> ScanMeshesForDisabledMeshReadWriteSetting(IEnumerable<Mesh> avatarMeshes) + { + HashSet<Mesh> incorrectlyConfiguredMeshes = new HashSet<Mesh>(); + foreach (Mesh avatarMesh in avatarMeshes) + { + // Can't get ModelImporter if the model isn't an asset. + if (!AssetDatabase.Contains(avatarMesh)) + { + continue; + } + + string meshAssetPath = AssetDatabase.GetAssetPath(avatarMesh); + if (string.IsNullOrEmpty(meshAssetPath)) + { + continue; + } + + ModelImporter avatarImporter = AssetImporter.GetAtPath(meshAssetPath) as ModelImporter; + if (avatarImporter == null) + { + continue; + } + + if (avatarImporter.isReadable) + { + continue; + } + + if (!incorrectlyConfiguredMeshes.Contains(avatarMesh)) + { + incorrectlyConfiguredMeshes.Add(avatarMesh); + } + } + + return incorrectlyConfiguredMeshes; + } + + private static void EnableMeshReadWrite(IEnumerable<Mesh> meshesToFix) + { + HashSet<string> meshAssetPaths = new HashSet<string>(); + foreach (Mesh meshToFix in meshesToFix) + { + // Can't get ModelImporter if the model isn't an asset. + if (!AssetDatabase.Contains(meshToFix)) + { + continue; + } + + string meshAssetPath = AssetDatabase.GetAssetPath(meshToFix); + if (string.IsNullOrEmpty(meshAssetPath)) + { + continue; + } + + if (meshAssetPaths.Contains(meshAssetPath)) + { + continue; + } + + meshAssetPaths.Add(meshAssetPath); + } + + foreach (string meshAssetPath in meshAssetPaths) + { + ModelImporter avatarImporter = AssetImporter.GetAtPath(meshAssetPath) as ModelImporter; + if (avatarImporter == null) + { + continue; + } + + if (avatarImporter.isReadable) + { + continue; + } + + avatarImporter.isReadable = true; + avatarImporter.SaveAndReimport(); + } + } + + private static HashSet<Mesh> GetAllMeshesInGameObjectHierarchy(GameObject avatar) + { + HashSet<Mesh> avatarMeshes = new HashSet<Mesh>(); + foreach (SkinnedMeshRenderer avatarSkinnedMeshRenderer in avatar + .GetComponentsInChildren<SkinnedMeshRenderer>(true)) + { + if (avatarSkinnedMeshRenderer == null) + { + continue; + } + + Mesh skinnedMesh = avatarSkinnedMeshRenderer.sharedMesh; + if (skinnedMesh == null) + { + continue; + } + + if (avatarMeshes.Contains(skinnedMesh)) + { + continue; + } + + avatarMeshes.Add(skinnedMesh); + } + + foreach (MeshFilter avatarMeshFilter in avatar.GetComponentsInChildren<MeshFilter>(true)) + { + if (avatarMeshFilter == null) + { + continue; + } + + Mesh skinnedMesh = avatarMeshFilter.sharedMesh; + if (skinnedMesh == null) + { + continue; + } + + if (avatarMeshes.Contains(skinnedMesh)) + { + continue; + } + + avatarMeshes.Add(skinnedMesh); + } + + foreach (ParticleSystemRenderer avatarParticleSystemRenderer in avatar + .GetComponentsInChildren<ParticleSystemRenderer>(true)) + { + if (avatarParticleSystemRenderer == null) + { + continue; + } + + Mesh[] avatarParticleSystemRendererMeshes = new Mesh[avatarParticleSystemRenderer.meshCount]; + avatarParticleSystemRenderer.GetMeshes(avatarParticleSystemRendererMeshes); + foreach (Mesh avatarParticleSystemRendererMesh in avatarParticleSystemRendererMeshes) + { + if (avatarParticleSystemRendererMesh == null) + { + continue; + } + + if (avatarMeshes.Contains(avatarParticleSystemRendererMesh)) + { + continue; + } + + avatarMeshes.Add(avatarParticleSystemRendererMesh); + } + } + + return avatarMeshes; + } + + protected void OpenAnimatorControllerWindow(object animatorController) + { + Assembly asm = Assembly.Load("UnityEditor.Graphs"); + Module editorGraphModule = asm.GetModule("UnityEditor.Graphs.dll"); + Type animatorWindowType = editorGraphModule.GetType("UnityEditor.Graphs.AnimatorControllerTool"); + EditorWindow animatorWindow = EditorWindow.GetWindow(animatorWindowType, false, "Animator", false); + PropertyInfo propInfo = animatorWindowType.GetProperty("animatorController"); + if (propInfo != null) propInfo.SetValue(animatorWindow, animatorController, null); + } + + protected static void ShowRestrictedComponents(IEnumerable<Component> componentsToRemove) + { + List<Object> gos = new List<Object>(); + foreach (Component c in componentsToRemove) + gos.Add(c.gameObject); + Selection.objects = gos.ToArray(); + } + + protected static void FixRestrictedComponents(IEnumerable<Component> componentsToRemove) + { + if (!(componentsToRemove is List<Component> list)) return; + for (int v = list.Count - 1; v > -1; v--) + { + Object.DestroyImmediate(list[v]); + } + } + + public static void SelectAvatar(VRC_AvatarDescriptor avatar) + { + if (VRCSdkControlPanel.window != null) + _selectedAvatar = avatar; + } + + + List<Transform> FindBonesBetween(Transform top, Transform bottom) + { + List<Transform> list = new List<Transform>(); + if (top == null || bottom == null) return list; + Transform bt = top.parent; + while (bt != bottom && bt != null) + { + list.Add(bt); + bt = bt.parent; + } + + return list; + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelAvatarBuilder.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelAvatarBuilder.cs.meta new file mode 100644 index 00000000..2dc1efd5 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelAvatarBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d90918c7fdc97d04f918868742746f67 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelBuilder.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelBuilder.cs new file mode 100644 index 00000000..68dcc421 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelBuilder.cs @@ -0,0 +1,655 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEditor; +using UnityEditor.SceneManagement; +using VRC.SDKBase.Validation.Performance; +using Object = UnityEngine.Object; +using VRC.SDKBase.Editor; + +public partial class VRCSdkControlPanel : EditorWindow +{ + public static System.Action _EnableSpatialization = null; // assigned in AutoAddONSPAudioSourceComponents + + public const string AVATAR_OPTIMIZATION_TIPS_URL = "https://docs.vrchat.com/docs/avatar-optimizing-tips"; + public const string AVATAR_RIG_REQUIREMENTS_URL = "https://docs.vrchat.com/docs/rig-requirements"; + + const string kCantPublishContent = "Before you can upload avatars or worlds, you will need to spend some time in VRChat."; + const string kCantPublishAvatars = "Before you can upload avatars, you will need to spend some time in VRChat."; + const string kCantPublishWorlds = "Before you can upload worlds, you will need to spend some time in VRChat."; + private const string FIX_ISSUES_TO_BUILD_OR_TEST_WARNING_STRING = "You must address the above issues before you can build or test this content!"; + + static Texture _perfIcon_Excellent; + static Texture _perfIcon_Good; + static Texture _perfIcon_Medium; + static Texture _perfIcon_Poor; + static Texture _perfIcon_VeryPoor; + static Texture _bannerImage; + + public void ResetIssues() + { + GUIErrors.Clear(); + GUIInfos.Clear(); + GUIWarnings.Clear(); + GUILinks.Clear(); + GUIStats.Clear(); + CheckedForIssues = false; + } + + public bool CheckedForIssues { get; set; } = false; + + class Issue + { + public string issueText; + public System.Action showThisIssue; + public System.Action fixThisIssue; + public PerformanceRating performanceRating; + + public Issue(string text, System.Action show, System.Action fix, PerformanceRating rating = PerformanceRating.None) + { + issueText = text; + showThisIssue = show; + fixThisIssue = fix; + performanceRating = rating; + } + + public class Equality : IEqualityComparer<Issue>, IComparer<Issue> + { + public bool Equals(Issue b1, Issue b2) + { + return (b1.issueText == b2.issueText); + } + public int Compare(Issue b1, Issue b2) + { + return string.Compare(b1.issueText, b2.issueText); + } + public int GetHashCode(Issue bx) + { + return bx.issueText.GetHashCode(); + } + } + } + + Dictionary<Object, List<Issue>> GUIErrors = new Dictionary<Object, List<Issue>>(); + Dictionary<Object, List<Issue>> GUIWarnings = new Dictionary<Object, List<Issue>>(); + Dictionary<Object, List<Issue>> GUIInfos = new Dictionary<Object, List<Issue>>(); + Dictionary<Object, List<Issue>> GUILinks = new Dictionary<Object, List<Issue>>(); + Dictionary<Object, List<Issue>> GUIStats = new Dictionary<Object, List<Issue>>(); + + public bool NoGuiErrors() + { + return GUIErrors.Count == 0; + } + + public bool NoGuiErrorsOrIssues() + { + return GUIErrors.Count == 0 && CheckedForIssues; + } + + void AddToReport(Dictionary<Object, List<Issue>> report, Object subject, string output, System.Action show, System.Action fix) + { + if (subject == null) + subject = this; + if (!report.ContainsKey(subject)) + report.Add(subject, new List<Issue>()); + + var issue = new Issue(output, show, fix); + if (!report[subject].Contains(issue, new Issue.Equality())) + { + report[subject].Add(issue); + report[subject].Sort(new Issue.Equality()); + } + } + + void BuilderAssemblyReload() + { + ResetIssues(); + } + + public void OnGUIError(Object subject, string output, System.Action show, System.Action fix) + { + AddToReport(GUIErrors, subject, output, show, fix); + } + + public void OnGUIWarning(Object subject, string output, System.Action show, System.Action fix) + { + AddToReport(GUIWarnings, subject, output, show, fix); + } + + public void OnGUIInformation(Object subject, string output) + { + AddToReport(GUIInfos, subject, output, null, null); + } + + public void OnGUILink(Object subject, string output, string link) + { + AddToReport(GUILinks, subject, output + "\n" + link, null, null); + } + + public void OnGUIStat(Object subject, string output, PerformanceRating rating, System.Action show, System.Action fix) + { + if (subject == null) + subject = this; + if (!GUIStats.ContainsKey(subject)) + GUIStats.Add(subject, new List<Issue>()); + GUIStats[subject].Add(new Issue(output, show, fix, rating)); + } + + public int triggerLineMode + { + get { return EditorPrefs.GetInt("VRC.SDKBase_triggerLineMode", 0); } + set { EditorPrefs.SetInt("VRC.SDKBase_triggerLineMode", value); } + } + + private void ShowSettingsOptionsForBuilders() + { + if (_sdkBuilders == null) + { + PopulateSdkBuilders(); + } + for (int i = 0; i < _sdkBuilders.Length; i++) + { + IVRCSdkControlPanelBuilder builder = _sdkBuilders[i]; + builder.ShowSettingsOptions(); + if (i < _sdkBuilders.Length - 1) + { + EditorGUILayout.Separator(); + } + } + } + + private IVRCSdkControlPanelBuilder[] _sdkBuilders; + + private static List<Type> GetSdkBuilderTypesFromAttribute() + { + Type sdkBuilderInterfaceType = typeof(IVRCSdkControlPanelBuilder); + Type sdkBuilderAttributeType = typeof(VRCSdkControlPanelBuilderAttribute); + + List<Type> moduleTypesFromAttribute = new List<Type>(); + foreach(Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + VRCSdkControlPanelBuilderAttribute[] sdkBuilderAttributes; + try + { + sdkBuilderAttributes = (VRCSdkControlPanelBuilderAttribute[])assembly.GetCustomAttributes(sdkBuilderAttributeType, true); + } + catch + { + sdkBuilderAttributes = new VRCSdkControlPanelBuilderAttribute[0]; + } + + foreach(VRCSdkControlPanelBuilderAttribute udonWrapperModuleAttribute in sdkBuilderAttributes) + { + if(udonWrapperModuleAttribute == null) + { + continue; + } + + if(!sdkBuilderInterfaceType.IsAssignableFrom(udonWrapperModuleAttribute.Type)) + { + continue; + } + + moduleTypesFromAttribute.Add(udonWrapperModuleAttribute.Type); + } + } + + return moduleTypesFromAttribute; + } + + private void PopulateSdkBuilders() + { + if (_sdkBuilders != null) + { + return; + } + List<IVRCSdkControlPanelBuilder> builders = new List<IVRCSdkControlPanelBuilder>(); + foreach (Type type in GetSdkBuilderTypesFromAttribute()) + { + IVRCSdkControlPanelBuilder builder = (IVRCSdkControlPanelBuilder)Activator.CreateInstance(type); + builder.RegisterBuilder(this); + builders.Add(builder); + } + _sdkBuilders = builders.ToArray(); + } + + void ShowBuilders() + { + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.BeginVertical(); + + if (VRC.Core.ConfigManager.RemoteConfig.IsInitialized()) + { + string sdkUnityVersion = VRC.Core.ConfigManager.RemoteConfig.GetString("sdkUnityVersion"); + if (Application.unityVersion != sdkUnityVersion) + { + OnGUIWarning(null, "You are not using the recommended Unity version for the VRChat SDK. Content built with this version may not work correctly. Please use Unity " + sdkUnityVersion, + null, + () => { Application.OpenURL("https://unity3d.com/get-unity/download/archive"); } + ); + } + } + + if (VRCSdk3Analysis.IsSdkDllActive(VRCSdk3Analysis.SdkVersion.VRCSDK2) && VRCSdk3Analysis.IsSdkDllActive(VRCSdk3Analysis.SdkVersion.VRCSDK3)) + { + List<Component> sdk2Components = VRCSdk3Analysis.GetSDKInScene(VRCSdk3Analysis.SdkVersion.VRCSDK2); + List<Component> sdk3Components = VRCSdk3Analysis.GetSDKInScene(VRCSdk3Analysis.SdkVersion.VRCSDK3); + if (sdk2Components.Count > 0 && sdk3Components.Count > 0) + { + OnGUIError(null, + "This scene contains components from the VRChat SDK version 2 and version 3. Version two elements will have to be replaced with their version 3 counterparts to build with SDK3 and UDON.", + () => { Selection.objects = sdk2Components.ToArray(); }, + null + ); + } + } + + if (Lightmapping.giWorkflowMode == Lightmapping.GIWorkflowMode.Iterative) + { + OnGUIWarning(null, + "Automatic lightmap generation is enabled, which may stall the Unity build process. Before building and uploading, consider turning off 'Auto Generate' at the bottom of the Lighting Window.", + () => + { + EditorWindow lightingWindow = GetLightingWindow(); + if (lightingWindow) + { + lightingWindow.Show(); + lightingWindow.Focus(); + } + }, + () => + { + Lightmapping.giWorkflowMode = Lightmapping.GIWorkflowMode.OnDemand; + EditorWindow lightingWindow = GetLightingWindow(); + if (!lightingWindow) return; + lightingWindow.Repaint(); + Focus(); + } + ); + } + + PopulateSdkBuilders(); + IVRCSdkControlPanelBuilder selectedBuilder = null; + string errorMessage = null; + foreach (IVRCSdkControlPanelBuilder sdkBuilder in _sdkBuilders) + { + if (!sdkBuilder.IsValidBuilder(out string message)) + { + if (selectedBuilder == null) + { + errorMessage = message; + } + } + else + { + if (selectedBuilder == null) + { + selectedBuilder = sdkBuilder; + errorMessage = null; + } + else + { + errorMessage = + "A Unity scene cannot contain a VRChat Scene Descriptor and also contain VRChat Avatar Descriptors"; + } + } + } + if (selectedBuilder == null) + { + string message = ""; +#if VRC_SDK_VRCSDK2 + message = "A VRC_SceneDescriptor or VRC_AvatarDescriptor\nis required to build VRChat SDK Content"; +#elif UDON + message = "A VRCSceneDescriptor is required to build a World"; +#elif VRC_SDK_VRCSDK3 + message = "A VRCAvatarDescriptor is required to build an Avatar"; +#else + message = "The SDK did not load properly. Try this - In the Project window, navigate to Assets/VRCSDK/Plugins. Select all the DLLs, then right click and choose 'Reimport'"; +#endif + EditorGUILayout.LabelField(message, titleGuiStyle, GUILayout.Width(SdkWindowWidth)); + } + else if (errorMessage != null) + { + OnGUIError(null, + errorMessage, + () => { + foreach (IVRCSdkControlPanelBuilder builder in _sdkBuilders) + { + builder.SelectAllComponents(); + } }, + null + ); + OnGUIShowIssues(); + } + else + { + selectedBuilder.ShowBuilder(); + } + + if (Event.current.type == EventType.Used) return; + GUILayout.EndVertical(); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + } + + public bool showLayerHelp = false; + + bool ShouldShowLightmapWarning + { + get + { + const string GraphicsSettingsAssetPath = "ProjectSettings/GraphicsSettings.asset"; + SerializedObject graphicsManager = new SerializedObject(UnityEditor.AssetDatabase.LoadAllAssetsAtPath(GraphicsSettingsAssetPath)[0]); + SerializedProperty lightmapStripping = graphicsManager.FindProperty("m_LightmapStripping"); + return lightmapStripping.enumValueIndex == 0; + } + } + + bool ShouldShowFogWarning + { + get + { + const string GraphicsSettingsAssetPath = "ProjectSettings/GraphicsSettings.asset"; + SerializedObject graphicsManager = new SerializedObject(UnityEditor.AssetDatabase.LoadAllAssetsAtPath(GraphicsSettingsAssetPath)[0]); + SerializedProperty lightmapStripping = graphicsManager.FindProperty("m_FogStripping"); + return lightmapStripping.enumValueIndex == 0; + } + } + + void DrawIssueBox(MessageType msgType, Texture icon, string message, System.Action show, System.Action fix) + { + bool haveButtons = ((show != null) || (fix != null)); + + GUIStyle style = new GUIStyle("HelpBox"); + style.fixedWidth = (haveButtons ? (SdkWindowWidth - 90) : SdkWindowWidth); + float minHeight = 40; + + try + { + EditorGUILayout.BeginHorizontal(); + if (icon != null) + { + GUIContent c = new GUIContent(message, icon); + float height = style.CalcHeight(c, style.fixedWidth); + GUILayout.Box(c, style, GUILayout.MinHeight(Mathf.Max(minHeight, height))); + } + else + { + GUIContent c = new GUIContent(message); + float height = style.CalcHeight(c, style.fixedWidth); + Rect rt = GUILayoutUtility.GetRect(c, style, GUILayout.MinHeight(Mathf.Max(minHeight, height))); + EditorGUI.HelpBox(rt, message, msgType); // note: EditorGUILayout resulted in uneven button layout in this case + } + + if (haveButtons) + { + EditorGUILayout.BeginVertical(); + float buttonHeight = ((show == null || fix == null) ? minHeight : (minHeight * 0.5f)); + if ((show != null) && GUILayout.Button("Select", GUILayout.Height(buttonHeight))) + show(); + if ((fix != null) && GUILayout.Button("Auto Fix", GUILayout.Height(buttonHeight))) + { + fix(); + EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene()); + CheckedForIssues = false; + Repaint(); + } + EditorGUILayout.EndVertical(); + } + + EditorGUILayout.EndHorizontal(); + } + catch + { + // mutes 'ArgumentException: Getting control 0's position in a group with only 0 controls when doing repaint' + } + } + + public void OnGuiFixIssuesToBuildOrTest() + { + GUIStyle s = new GUIStyle(EditorStyles.label) { alignment = TextAnchor.MiddleCenter }; + EditorGUILayout.Space(); + GUILayout.BeginVertical(boxGuiStyle, GUILayout.Height(WARNING_ICON_SIZE), GUILayout.Width(SdkWindowWidth)); + GUILayout.FlexibleSpace(); + EditorGUILayout.BeginHorizontal(); + var textDimensions = s.CalcSize(new GUIContent(FIX_ISSUES_TO_BUILD_OR_TEST_WARNING_STRING)); + GUILayout.Label(new GUIContent(warningIconGraphic), GUILayout.Width(WARNING_ICON_SIZE), GUILayout.Height(WARNING_ICON_SIZE)); + EditorGUILayout.LabelField(FIX_ISSUES_TO_BUILD_OR_TEST_WARNING_STRING, s, GUILayout.Width(textDimensions.x), GUILayout.Height(WARNING_ICON_SIZE)); + EditorGUILayout.EndHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + } + + public void OnGUIShowIssues(Object subject = null) + { + if (subject == null) + subject = this; + + EditorGUI.BeginChangeCheck(); + + GUIStyle style = GUI.skin.GetStyle("HelpBox"); + + if (GUIErrors.ContainsKey(subject)) + foreach (Issue error in GUIErrors[subject].Where(s => !string.IsNullOrEmpty(s.issueText))) + DrawIssueBox(MessageType.Error, null, error.issueText, error.showThisIssue, error.fixThisIssue); + if (GUIWarnings.ContainsKey(subject)) + foreach (Issue error in GUIWarnings[subject].Where(s => !string.IsNullOrEmpty(s.issueText))) + DrawIssueBox(MessageType.Warning, null, error.issueText, error.showThisIssue, error.fixThisIssue); + + if (GUIStats.ContainsKey(subject)) + { + foreach (var kvp in GUIStats[subject].Where(k => k.performanceRating == PerformanceRating.VeryPoor)) + DrawIssueBox(MessageType.Warning, GetPerformanceIconForRating(kvp.performanceRating), kvp.issueText, kvp.showThisIssue, kvp.fixThisIssue); + + foreach (var kvp in GUIStats[subject].Where(k => k.performanceRating == PerformanceRating.Poor)) + DrawIssueBox(MessageType.Warning, GetPerformanceIconForRating(kvp.performanceRating), kvp.issueText, kvp.showThisIssue, kvp.fixThisIssue); + + foreach (var kvp in GUIStats[subject].Where(k => k.performanceRating == PerformanceRating.Medium)) + DrawIssueBox(MessageType.Warning, GetPerformanceIconForRating(kvp.performanceRating), kvp.issueText, kvp.showThisIssue, kvp.fixThisIssue); + + foreach (var kvp in GUIStats[subject].Where(k => k.performanceRating == PerformanceRating.Good || k.performanceRating == PerformanceRating.Excellent)) + DrawIssueBox(MessageType.Warning, GetPerformanceIconForRating(kvp.performanceRating), kvp.issueText, kvp.showThisIssue, kvp.fixThisIssue); + } + + if (GUIInfos.ContainsKey(subject)) + foreach (Issue error in GUIInfos[subject].Where(s => !string.IsNullOrEmpty(s.issueText))) + EditorGUILayout.HelpBox(error.issueText, MessageType.Info); + if (GUILinks.ContainsKey(subject)) + { + EditorGUILayout.BeginVertical(style); + foreach (Issue error in GUILinks[subject].Where(s => !string.IsNullOrEmpty(s.issueText))) + { + var s = error.issueText.Split('\n'); + EditorGUILayout.BeginHorizontal(); + GUILayout.Label(s[0]); + if (GUILayout.Button("Open Link", GUILayout.Width(100))) + Application.OpenURL(s[1]); + EditorGUILayout.EndHorizontal(); + } + EditorGUILayout.EndVertical(); + } + + if (EditorGUI.EndChangeCheck()) + { + EditorUtility.SetDirty(subject); + UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene()); + } + } + + private Texture GetPerformanceIconForRating(PerformanceRating value) + { + if (_perfIcon_Excellent == null) + _perfIcon_Excellent = Resources.Load<Texture>("PerformanceIcons/Perf_Great_32"); + if (_perfIcon_Good == null) + _perfIcon_Good = Resources.Load<Texture>("PerformanceIcons/Perf_Good_32"); + if (_perfIcon_Medium == null) + _perfIcon_Medium = Resources.Load<Texture>("PerformanceIcons/Perf_Medium_32"); + if (_perfIcon_Poor == null) + _perfIcon_Poor = Resources.Load<Texture>("PerformanceIcons/Perf_Poor_32"); + if (_perfIcon_VeryPoor == null) + _perfIcon_VeryPoor = Resources.Load<Texture>("PerformanceIcons/Perf_Horrible_32"); + + switch (value) + { + case PerformanceRating.Excellent: + return _perfIcon_Excellent; + case PerformanceRating.Good: + return _perfIcon_Good; + case PerformanceRating.Medium: + return _perfIcon_Medium; + case PerformanceRating.Poor: + return _perfIcon_Poor; + case PerformanceRating.None: + case PerformanceRating.VeryPoor: + return _perfIcon_VeryPoor; + } + + return _perfIcon_Excellent; + } + + Texture2D CreateBackgroundColorImage(UnityEngine.Color color) + { + int w = 4, h = 4; + Texture2D back = new Texture2D(w, h); + UnityEngine.Color[] buffer = new UnityEngine.Color[w * h]; + for (int i = 0; i < w; ++i) + for (int j = 0; j < h; ++j) + buffer[i + w * j] = color; + back.SetPixels(buffer); + back.Apply(false); + return back; + } + + public static void DrawContentInfo(string name, string version, string description, string capacity, string releaseStatus, List<string> tags) + { + EditorGUILayout.LabelField("Name: " + name); + EditorGUILayout.LabelField("Version: " + version.ToString()); + EditorGUILayout.LabelField("Description: " + description); + if (capacity != null) + EditorGUILayout.LabelField("Capacity: " + capacity); + EditorGUILayout.LabelField("Release: " + releaseStatus); + if (tags != null) + { + string tagString = ""; + for (int i = 0; i < tags.Count; i++) + { + if (i != 0) tagString += ", "; + tagString += tags[i]; + } + EditorGUILayout.LabelField("Tags: " + tagString); + + } + } + public static void DrawContentPlatformSupport(VRC.Core.ApiModel m) + { + if (m.supportedPlatforms == VRC.Core.ApiModel.SupportedPlatforms.StandaloneWindows || m.supportedPlatforms == VRC.Core.ApiModel.SupportedPlatforms.All) + EditorGUILayout.LabelField("Windows Support: YES"); + else + EditorGUILayout.LabelField("Windows Support: NO"); + + if (m.supportedPlatforms == VRC.Core.ApiModel.SupportedPlatforms.Android || m.supportedPlatforms == VRC.Core.ApiModel.SupportedPlatforms.All) + EditorGUILayout.LabelField("Android Support: YES"); + else + EditorGUILayout.LabelField("Android Support: NO"); + } + + public static void DrawBuildTargetSwitcher() + { + EditorGUILayout.LabelField("Active Build Target: " + EditorUserBuildSettings.activeBuildTarget); + if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.StandaloneWindows || EditorUserBuildSettings.activeBuildTarget == BuildTarget.StandaloneWindows64 && GUILayout.Button("Switch Build Target to Android")) + { + if (EditorUtility.DisplayDialog("Build Target Switcher", "Are you sure you want to switch your build target to Android? This could take a while.", "Confirm", "Cancel")) + { + EditorUserBuildSettings.selectedBuildTargetGroup = BuildTargetGroup.Android; + EditorUserBuildSettings.SwitchActiveBuildTargetAsync(BuildTargetGroup.Android, BuildTarget.Android); + } + } + if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android && GUILayout.Button("Switch Build Target to Windows")) + { + if (EditorUtility.DisplayDialog("Build Target Switcher", "Are you sure you want to switch your build target to Windows? This could take a while.", "Confirm", "Cancel")) + { + EditorUserBuildSettings.selectedBuildTargetGroup = BuildTargetGroup.Standalone; + EditorUserBuildSettings.SwitchActiveBuildTargetAsync(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows64); + } + } + } + + public static string GetBuildAndPublishButtonString() + { + string buildButtonString = "Build & Publish for UNSUPPORTED"; + if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.StandaloneWindows || EditorUserBuildSettings.activeBuildTarget == BuildTarget.StandaloneWindows64) + buildButtonString = "Build & Publish for Windows"; + if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) + buildButtonString = "Build & Publish for Android"; + + return buildButtonString; + } + + public static Object[] GetSubstanceObjects(GameObject obj = null, bool earlyOut = false) + { + // if 'obj' is null we check entire scene + // if 'earlyOut' is true we only return 1st object (to detect if substances are present) + + List<Object> objects = new List<Object>(); + if (obj == null) return objects.Count < 1 ? null : objects.ToArray(); + Renderer[] renderers = obj ? obj.GetComponentsInChildren<Renderer>(true) : FindObjectsOfType<Renderer>(); + + if (renderers == null || renderers.Length < 1) + return null; + foreach (Renderer r in renderers) + { + if (r.sharedMaterials.Length < 1) + continue; + foreach (Material m in r.sharedMaterials) + { + if (!m) + continue; + string path = AssetDatabase.GetAssetPath(m); + if (string.IsNullOrEmpty(path)) + continue; + if (path.EndsWith(".sbsar", true, System.Globalization.CultureInfo.InvariantCulture)) + { + objects.Add(r.gameObject); + if (earlyOut) + return objects.ToArray(); + } + } + } + + return objects.Count < 1 ? null : objects.ToArray(); + } + + public static bool HasSubstances(GameObject obj = null) + { + return (GetSubstanceObjects(obj, true) != null); + } + + EditorWindow GetLightingWindow() + { + var editorAsm = typeof(UnityEditor.Editor).Assembly; + return EditorWindow.GetWindow(editorAsm.GetType("UnityEditor.LightingWindow")); + } + + public static void ShowContentPublishPermissionsDialog() + { + if (!VRC.Core.ConfigManager.RemoteConfig.IsInitialized()) + { + VRC.Core.ConfigManager.RemoteConfig.Init(() => ShowContentPublishPermissionsDialog()); + return; + } + + string message = VRC.Core.ConfigManager.RemoteConfig.GetString("sdkNotAllowedToPublishMessage"); + int result = UnityEditor.EditorUtility.DisplayDialogComplex("VRChat SDK", message, "Developer FAQ", "VRChat Discord", "OK"); + if (result == 0) + { + ShowDeveloperFAQ(); + } + if (result == 1) + { + ShowVRChatDiscord(); + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelBuilder.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelBuilder.cs.meta new file mode 100644 index 00000000..747cd3d2 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelBuilder.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4c73e735ee0380241b186a8993fa56bf +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelBuilderAttribute.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelBuilderAttribute.cs new file mode 100644 index 00000000..84344b02 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelBuilderAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using JetBrains.Annotations; +using UnityEngine; + +namespace VRC.SDKBase.Editor +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + [MeansImplicitUse] + public class VRCSdkControlPanelBuilderAttribute : Attribute + { + public Type Type { get; } + public VRCSdkControlPanelBuilderAttribute(Type type) + { + Type = type; + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelBuilderAttribute.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelBuilderAttribute.cs.meta new file mode 100644 index 00000000..32f46ed8 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelBuilderAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c768b42ca9a2f2b48afeb1fa03d5e1bf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelContent.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelContent.cs new file mode 100644 index 00000000..22320b54 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelContent.cs @@ -0,0 +1,690 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.IO; +using UnityEngine; +using UnityEditor; +using UnityEngine.Networking; +using VRC.Core; + +public partial class VRCSdkControlPanel : EditorWindow +{ + const int PageLimit = 20; + + static List<ApiAvatar> uploadedAvatars = null; + static List<ApiWorld> uploadedWorlds = null; + static List<ApiAvatar> testAvatars = null; + + public static Dictionary<string, Texture2D> ImageCache = new Dictionary<string, Texture2D>(); + + static List<string> justDeletedContents; + static List<ApiAvatar> justUpdatedAvatars; + + static EditorCoroutine fetchingAvatars = null, fetchingWorlds = null; + + private static string searchString = ""; + private static bool WorldsToggle = true; + private static bool AvatarsToggle = true; + private static bool TestAvatarsToggle = true; + + const int SCROLLBAR_RESERVED_REGION_WIDTH = 50; + + const int WORLD_DESCRIPTION_FIELD_WIDTH = 140; + const int WORLD_IMAGE_BUTTON_WIDTH = 100; + const int WORLD_IMAGE_BUTTON_HEIGHT = 100; + const int WORLD_RELEASE_STATUS_FIELD_WIDTH = 150; + const int COPY_WORLD_ID_BUTTON_WIDTH = 75; + const int DELETE_WORLD_BUTTON_WIDTH = 75; + const int WORLD_ALL_INFORMATION_MAX_WIDTH = WORLD_DESCRIPTION_FIELD_WIDTH + WORLD_IMAGE_BUTTON_WIDTH + WORLD_RELEASE_STATUS_FIELD_WIDTH + COPY_WORLD_ID_BUTTON_WIDTH + DELETE_WORLD_BUTTON_WIDTH + SCROLLBAR_RESERVED_REGION_WIDTH; + const int WORLD_REDUCED_INFORMATION_MAX_WIDTH = WORLD_DESCRIPTION_FIELD_WIDTH + WORLD_IMAGE_BUTTON_WIDTH + WORLD_RELEASE_STATUS_FIELD_WIDTH + SCROLLBAR_RESERVED_REGION_WIDTH; + + const int AVATAR_DESCRIPTION_FIELD_WIDTH = 140; + const int AVATAR_IMAGE_BUTTON_WIDTH = WORLD_IMAGE_BUTTON_WIDTH; + const int AVATAR_IMAGE_BUTTON_HEIGHT = WORLD_IMAGE_BUTTON_HEIGHT; + const int AVATAR_RELEASE_STATUS_FIELD_WIDTH = 150; + const int SET_AVATAR_STATUS_BUTTON_WIDTH = 100; + const int COPY_AVATAR_ID_BUTTON_WIDTH = COPY_WORLD_ID_BUTTON_WIDTH; + const int DELETE_AVATAR_BUTTON_WIDTH = DELETE_WORLD_BUTTON_WIDTH; + const int AVATAR_ALL_INFORMATION_MAX_WIDTH = AVATAR_DESCRIPTION_FIELD_WIDTH + AVATAR_IMAGE_BUTTON_WIDTH + AVATAR_RELEASE_STATUS_FIELD_WIDTH + SET_AVATAR_STATUS_BUTTON_WIDTH + COPY_AVATAR_ID_BUTTON_WIDTH + DELETE_AVATAR_BUTTON_WIDTH + SCROLLBAR_RESERVED_REGION_WIDTH; + const int AVATAR_REDUCED_INFORMATION_MAX_WIDTH = AVATAR_DESCRIPTION_FIELD_WIDTH + AVATAR_IMAGE_BUTTON_WIDTH + AVATAR_RELEASE_STATUS_FIELD_WIDTH + SCROLLBAR_RESERVED_REGION_WIDTH; + + const int MAX_ALL_INFORMATION_WIDTH = WORLD_ALL_INFORMATION_MAX_WIDTH > AVATAR_ALL_INFORMATION_MAX_WIDTH ? WORLD_ALL_INFORMATION_MAX_WIDTH : AVATAR_ALL_INFORMATION_MAX_WIDTH; + const int MAX_REDUCED_INFORMATION_WIDTH = WORLD_REDUCED_INFORMATION_MAX_WIDTH > AVATAR_REDUCED_INFORMATION_MAX_WIDTH ? WORLD_REDUCED_INFORMATION_MAX_WIDTH : AVATAR_REDUCED_INFORMATION_MAX_WIDTH; + + public static void ClearContent() + { + if (uploadedWorlds != null) + uploadedWorlds = null; + if (uploadedAvatars != null) + uploadedAvatars = null; + if (testAvatars != null) + testAvatars = null; + ImageCache.Clear(); + } + + IEnumerator FetchUploadedData() + { + if (!ConfigManager.RemoteConfig.IsInitialized()) + ConfigManager.RemoteConfig.Init(); + + if (!APIUser.IsLoggedIn) + yield break; + + ApiCache.ClearResponseCache(); + VRCCachedWebRequest.ClearOld(); + + if (fetchingAvatars == null) + fetchingAvatars = EditorCoroutine.Start(() => FetchAvatars()); + if (fetchingWorlds == null) + fetchingWorlds = EditorCoroutine.Start(() => FetchWorlds()); + FetchTestAvatars(); + } + + private static void FetchAvatars(int offset = 0) + { + ApiAvatar.FetchList( + delegate (IEnumerable<ApiAvatar> obj) + { + if (obj.FirstOrDefault() != null) + fetchingAvatars = EditorCoroutine.Start(() => + { + var l = obj.ToList(); + int count = l.Count; + SetupAvatarData(l); + FetchAvatars(offset + count); + }); + else + { + fetchingAvatars = null; + foreach (ApiAvatar a in uploadedAvatars) + DownloadImage(a.id, a.thumbnailImageUrl); + } + }, + delegate (string obj) + { + Debug.LogError("Error fetching your uploaded avatars:\n" + obj); + fetchingAvatars = null; + }, + ApiAvatar.Owner.Mine, + ApiAvatar.ReleaseStatus.All, + null, + PageLimit, + offset, + ApiAvatar.SortHeading.None, + ApiAvatar.SortOrder.Descending, + null, + null, + true, + false, + null, + false + ); + } + + private static void FetchTestAvatars() + { +#if VRC_SDK_VRCSDK3 + string sdkAvatarFolder = VRC.SDKBase.Editor.VRC_SdkBuilder.GetKnownFolderPath(VRC.SDKBase.Editor.VRC_SdkBuilder.LocalLowGUID) + "/VRChat/vrchat/Avatars/"; + string[] sdkavatars = Directory.GetFiles(sdkAvatarFolder); + string filename = ""; + List<ApiAvatar> avatars = new List<ApiAvatar>(); + foreach(string sdkap in sdkavatars) + { + if(Path.GetExtension(sdkap) != ".vrca") + continue; + + filename = Path.GetFileNameWithoutExtension(sdkap); + ApiAvatar sdka = API.FromCacheOrNew<ApiAvatar>("local:sdk_" + filename); + sdka.assetUrl = sdkap; + sdka.name = filename; + sdka.releaseStatus = "public"; + ApiAvatar.AddLocal(sdka); + avatars.Add(sdka); + } + + testAvatars = avatars; +#else + testAvatars = new List<ApiAvatar>(); +#endif + } + + private static void FetchWorlds(int offset = 0) + { + ApiWorld.FetchList( + delegate (IEnumerable<ApiWorld> obj) + { + if (obj.FirstOrDefault() != null) + fetchingWorlds = EditorCoroutine.Start(() => + { + var l = obj.ToList(); + int count = l.Count; + SetupWorldData(l); + FetchWorlds(offset + count); + }); + else + { + fetchingWorlds = null; + + foreach (ApiWorld w in uploadedWorlds) + DownloadImage(w.id, w.thumbnailImageUrl); + } + }, + delegate (string obj) + { + Debug.LogError("Error fetching your uploaded worlds:\n" + obj); + fetchingWorlds = null; + }, + ApiWorld.SortHeading.Updated, + ApiWorld.SortOwnership.Mine, + ApiWorld.SortOrder.Descending, + offset, + PageLimit, + "", + null, + null, + null, + null, + "", + ApiWorld.ReleaseStatus.All, + null, + null, + true, + false); + } + + static void SetupWorldData(List<ApiWorld> worlds) + { + if (worlds == null || uploadedWorlds == null) + return; + + worlds.RemoveAll(w => w == null || w.name == null || uploadedWorlds.Any(w2 => w2.id == w.id)); + + if (worlds.Count > 0) + { + uploadedWorlds.AddRange(worlds); + uploadedWorlds.Sort((w1, w2) => w1.name.CompareTo(w2.name)); + } + } + + static void SetupAvatarData(List<ApiAvatar> avatars) + { + if (avatars == null || uploadedAvatars == null ) + return; + + avatars.RemoveAll(a => a == null || uploadedAvatars.Any(a2 => a2.id == a.id)); + foreach(var avatar in avatars) + { + if (string.IsNullOrEmpty(avatar.name)) + avatar.name = "(unnamed)"; + } + + if (avatars.Count > 0) + { + uploadedAvatars.AddRange(avatars); + uploadedAvatars.Sort((w1, w2) => w1.name.CompareTo(w2.name)); + } + } + + private static void DownloadImage(string id, string url) + { + if (string.IsNullOrEmpty(url)) + { + return; + } + + if (ImageCache.ContainsKey(id) && ImageCache[id] != null) + { + return; + } + + EditorCoroutine.Start(VRCCachedWebRequest.Get(url, OnDone)); + void OnDone(Texture2D texture) + { + if (texture != null) + { + ImageCache[id] = texture; + } + else if (ImageCache.ContainsKey(id)) + { + ImageCache.Remove(id); + } + } + } + + Vector2 contentScrollPos; + + bool OnGUIUserInfo() + { + bool updatedContent = false; + + if (!ConfigManager.RemoteConfig.IsInitialized()) + ConfigManager.RemoteConfig.Init(); + + if (APIUser.IsLoggedIn && uploadedWorlds != null && uploadedAvatars != null && testAvatars != null) + { + + bool expandedLayout = false; // (position.width > MAX_ALL_INFORMATION_WIDTH); // uncomment for future wide layouts + + if (!expandedLayout) + { + EditorGUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + } + + GUILayout.BeginHorizontal(); + + GUILayout.BeginVertical(searchBarStyle); + + EditorGUILayout.BeginHorizontal(); + + float searchFieldShrinkOffset = 30f; + GUILayoutOption layoutOption = (expandedLayout ? GUILayout.Width(position.width - searchFieldShrinkOffset) : GUILayout.Width(SdkWindowWidth - searchFieldShrinkOffset)); + searchString = EditorGUILayout.TextField(searchString, GUI.skin.FindStyle("SearchTextField"), layoutOption); + GUIStyle searchButtonStyle = searchString == string.Empty + ? GUI.skin.FindStyle("SearchCancelButtonEmpty") + : GUI.skin.FindStyle("SearchCancelButton"); + if (GUILayout.Button(string.Empty, searchButtonStyle)) + { + searchString = string.Empty; + GUI.FocusControl(null); + } + EditorGUILayout.EndHorizontal(); + EditorGUILayout.EndVertical(); + EditorGUILayout.EndHorizontal(); + + if (!expandedLayout) + { + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + } + + layoutOption = (expandedLayout ? GUILayout.Width(position.width) : GUILayout.Width(SdkWindowWidth)); + contentScrollPos = EditorGUILayout.BeginScrollView(contentScrollPos, layoutOption); + + GUIStyle descriptionStyle = new GUIStyle(EditorStyles.wordWrappedLabel); + descriptionStyle.wordWrap = true; + + if (uploadedWorlds.Count > 0) + { + EditorGUILayout.Space(); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("WORLDS", EditorStyles.boldLabel, GUILayout.ExpandWidth(false), GUILayout.Width(58)); + WorldsToggle = EditorGUILayout.Foldout(WorldsToggle, new GUIContent("")); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + + if (WorldsToggle) + { + + List<ApiWorld> tmpWorlds = new List<ApiWorld>(); + + if (uploadedWorlds.Count > 0) + tmpWorlds = new List<ApiWorld>(uploadedWorlds); + + foreach (ApiWorld w in tmpWorlds) + { + if (justDeletedContents != null && justDeletedContents.Contains(w.id)) + { + uploadedWorlds.Remove(w); + continue; + } + + if (!w.name.ToLowerInvariant().Contains(searchString.ToLowerInvariant())) + { + continue; + } + + EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); + EditorGUILayout.BeginHorizontal(GUILayout.Width(WORLD_IMAGE_BUTTON_WIDTH)); + + if (ImageCache.ContainsKey(w.id)) + { + if (GUILayout.Button(ImageCache[w.id], GUILayout.Height(WORLD_IMAGE_BUTTON_HEIGHT), + GUILayout.Width(WORLD_IMAGE_BUTTON_WIDTH))) + { + Application.OpenURL(w.imageUrl); + } + } + else + { + if (GUILayout.Button("", GUILayout.Height(WORLD_IMAGE_BUTTON_HEIGHT), + GUILayout.Width(WORLD_IMAGE_BUTTON_WIDTH))) + { + Application.OpenURL(w.imageUrl); + } + } + + if (expandedLayout) + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(w.name, descriptionStyle, + GUILayout.Width(position.width - MAX_ALL_INFORMATION_WIDTH + + WORLD_DESCRIPTION_FIELD_WIDTH)); + } + else + { + EditorGUILayout.BeginVertical(); + EditorGUILayout.LabelField(w.name, descriptionStyle); + } + + EditorGUILayout.LabelField("Release Status: " + w.releaseStatus, + GUILayout.Width(WORLD_RELEASE_STATUS_FIELD_WIDTH)); + if (GUILayout.Button("Copy ID", GUILayout.Width(COPY_WORLD_ID_BUTTON_WIDTH))) + { + TextEditor te = new TextEditor(); + te.text = w.id; + te.SelectAll(); + te.Copy(); + } + + if (GUILayout.Button("Delete", GUILayout.Width(DELETE_WORLD_BUTTON_WIDTH))) + { + if (EditorUtility.DisplayDialog("Delete " + w.name + "?", + "Are you sure you want to delete " + w.name + "? This cannot be undone.", "Delete", + "Cancel")) + { + foreach (VRC.Core.PipelineManager pm in FindObjectsOfType<VRC.Core.PipelineManager>() + .Where(pm => pm.blueprintId == w.id)) + { + pm.blueprintId = ""; + pm.completedSDKPipeline = false; + + UnityEditor.EditorUtility.SetDirty(pm); + UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(pm.gameObject.scene); + UnityEditor.SceneManagement.EditorSceneManager.SaveScene(pm.gameObject.scene); + } + + API.Delete<ApiWorld>(w.id); + uploadedWorlds.RemoveAll(world => world.id == w.id); + if (ImageCache.ContainsKey(w.id)) + ImageCache.Remove(w.id); + + if (justDeletedContents == null) justDeletedContents = new List<string>(); + justDeletedContents.Add(w.id); + updatedContent = true; + } + } + + if (expandedLayout) + EditorGUILayout.EndHorizontal(); + else + EditorGUILayout.EndVertical(); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(); + } + } + } + + if (uploadedAvatars.Count > 0) + { + EditorGUILayout.Space(); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("AVATARS", EditorStyles.boldLabel, GUILayout.ExpandWidth(false), GUILayout.Width(65)); + AvatarsToggle = EditorGUILayout.Foldout(AvatarsToggle, new GUIContent("")); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + + if (AvatarsToggle) + { + + List<ApiAvatar> tmpAvatars = new List<ApiAvatar>(); + + if (uploadedAvatars.Count > 0) + tmpAvatars = new List<ApiAvatar>(uploadedAvatars); + + if (justUpdatedAvatars != null) + { + foreach (ApiAvatar a in justUpdatedAvatars) + { + int index = tmpAvatars.FindIndex((av) => av.id == a.id); + if (index != -1) + tmpAvatars[index] = a; + } + } + + foreach (ApiAvatar a in tmpAvatars) + { + if (justDeletedContents != null && justDeletedContents.Contains(a.id)) + { + uploadedAvatars.Remove(a); + continue; + } + + if (!a.name.ToLowerInvariant().Contains(searchString.ToLowerInvariant())) + { + continue; + } + + EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); + EditorGUILayout.BeginHorizontal(GUILayout.Width(AVATAR_DESCRIPTION_FIELD_WIDTH)); + if (ImageCache.ContainsKey(a.id)) + { + if (GUILayout.Button(ImageCache[a.id], GUILayout.Height(AVATAR_IMAGE_BUTTON_HEIGHT), + GUILayout.Width(AVATAR_IMAGE_BUTTON_WIDTH))) + { + Application.OpenURL(a.imageUrl); + } + } + else + { + if (GUILayout.Button("", GUILayout.Height(AVATAR_IMAGE_BUTTON_HEIGHT), + GUILayout.Width(AVATAR_IMAGE_BUTTON_WIDTH))) + { + Application.OpenURL(a.imageUrl); + } + } + + if (expandedLayout) + EditorGUILayout.BeginHorizontal(); + else + EditorGUILayout.BeginVertical(); + + EditorGUILayout.LabelField(a.name, descriptionStyle, + GUILayout.Width(expandedLayout + ? position.width - MAX_ALL_INFORMATION_WIDTH + AVATAR_DESCRIPTION_FIELD_WIDTH + : AVATAR_DESCRIPTION_FIELD_WIDTH)); + EditorGUILayout.LabelField("Release Status: " + a.releaseStatus, + GUILayout.Width(AVATAR_RELEASE_STATUS_FIELD_WIDTH)); + + string oppositeReleaseStatus = a.releaseStatus == "public" ? "private" : "public"; + if (GUILayout.Button("Make " + oppositeReleaseStatus, + GUILayout.Width(SET_AVATAR_STATUS_BUTTON_WIDTH))) + { + a.releaseStatus = oppositeReleaseStatus; + + a.SaveReleaseStatus((c) => + { + ApiAvatar savedBP = (ApiAvatar) c.Model; + + if (justUpdatedAvatars == null) justUpdatedAvatars = new List<ApiAvatar>(); + justUpdatedAvatars.Add(savedBP); + + }, + (c) => + { + Debug.LogError(c.Error); + EditorUtility.DisplayDialog("Avatar Updated", + "Failed to change avatar release status", "OK"); + }); + } + + if (GUILayout.Button("Copy ID", GUILayout.Width(COPY_AVATAR_ID_BUTTON_WIDTH))) + { + TextEditor te = new TextEditor(); + te.text = a.id; + te.SelectAll(); + te.Copy(); + } + + if (GUILayout.Button("Delete", GUILayout.Width(DELETE_AVATAR_BUTTON_WIDTH))) + { + if (EditorUtility.DisplayDialog("Delete " + a.name + "?", + "Are you sure you want to delete " + a.name + "? This cannot be undone.", "Delete", + "Cancel")) + { + foreach (VRC.Core.PipelineManager pm in FindObjectsOfType<VRC.Core.PipelineManager>() + .Where(pm => pm.blueprintId == a.id)) + { + pm.blueprintId = ""; + pm.completedSDKPipeline = false; + + UnityEditor.EditorUtility.SetDirty(pm); + UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(pm.gameObject.scene); + UnityEditor.SceneManagement.EditorSceneManager.SaveScene(pm.gameObject.scene); + } + + API.Delete<ApiAvatar>(a.id); + uploadedAvatars.RemoveAll(avatar => avatar.id == a.id); + if (ImageCache.ContainsKey(a.id)) + ImageCache.Remove(a.id); + + if (justDeletedContents == null) justDeletedContents = new List<string>(); + justDeletedContents.Add(a.id); + updatedContent = true; + } + } + + if (expandedLayout) + EditorGUILayout.EndHorizontal(); + else + EditorGUILayout.EndVertical(); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(); + } + } + } + + if (testAvatars.Count > 0) + { + EditorGUILayout.Space(); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Test Avatars", EditorStyles.boldLabel, GUILayout.ExpandWidth(false), GUILayout.Width(100)); + TestAvatarsToggle = EditorGUILayout.Foldout(TestAvatarsToggle, new GUIContent("")); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + + if (TestAvatarsToggle) + { + List<ApiAvatar> tmpAvatars = new List<ApiAvatar>(); + + if (testAvatars.Count > 0) + tmpAvatars = new List<ApiAvatar>(testAvatars); + + foreach (ApiAvatar a in tmpAvatars) + { + if (!a.name.ToLowerInvariant().Contains(searchString.ToLowerInvariant())) + { + continue; + } + + EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); + + if (expandedLayout) + EditorGUILayout.BeginHorizontal(); + else + EditorGUILayout.BeginVertical(); + + EditorGUILayout.LabelField(a.name, descriptionStyle, + GUILayout.Width(expandedLayout + ? position.width - MAX_ALL_INFORMATION_WIDTH + AVATAR_DESCRIPTION_FIELD_WIDTH + : AVATAR_DESCRIPTION_FIELD_WIDTH)); + + if (GUILayout.Button("Delete", GUILayout.Width(DELETE_AVATAR_BUTTON_WIDTH))) + { + if (EditorUtility.DisplayDialog("Delete " + a.name + "?", + "Are you sure you want to delete " + a.name + "? This cannot be undone.", "Delete", + "Cancel")) + { + API.Delete<ApiAvatar>(a.id); + testAvatars.RemoveAll(avatar => avatar.id == a.id); + File.Delete(a.assetUrl); + + updatedContent = true; + } + } + + if (expandedLayout) + EditorGUILayout.EndHorizontal(); + else + EditorGUILayout.EndVertical(); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(); + } + } + } + + EditorGUILayout.EndScrollView(); + if (!expandedLayout) + { + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + } + if ((updatedContent) && (null != window)) window.Reset(); + + return true; + } + else + { + return false; + } + } + + void ShowContent() + { + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.BeginVertical(); + + if (uploadedWorlds == null || uploadedAvatars == null || testAvatars == null) + { + if (uploadedWorlds == null) + uploadedWorlds = new List<ApiWorld>(); + if (uploadedAvatars == null) + uploadedAvatars = new List<ApiAvatar>(); + if (testAvatars == null) + testAvatars = new List<ApiAvatar>(); + + EditorCoroutine.Start(FetchUploadedData()); + } + + if( fetchingWorlds != null || fetchingAvatars != null ) + { + GUILayout.BeginVertical(boxGuiStyle, GUILayout.Width(SdkWindowWidth)); + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Fetching Records", titleGuiStyle); + EditorGUILayout.Space(); + GUILayout.EndVertical(); + } + else + { + GUILayout.BeginVertical(boxGuiStyle, GUILayout.Width(SdkWindowWidth)); + EditorGUILayout.Space(); + EditorGUILayout.BeginHorizontal(); + GUILayout.Label("Fetch updated records from the VRChat server"); + if( GUILayout.Button("Fetch") ) + { + ClearContent(); + } + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(); + GUILayout.EndVertical(); + } + + GUILayout.EndVertical(); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + OnGUIUserInfo(); + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelContent.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelContent.cs.meta new file mode 100644 index 00000000..705c058d --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelContent.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c7333cdb3df19724b84b4a1b05093fe0 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelHelp.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelHelp.cs new file mode 100644 index 00000000..20db725e --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelHelp.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; +using VRC; +using VRC.Core; +using VRC.SDKBase; + +public partial class VRCSdkControlPanel : EditorWindow +{ + [MenuItem("VRChat SDK/Help/Developer FAQ")] + public static void ShowDeveloperFAQ() + { + if (!ConfigManager.RemoteConfig.IsInitialized()) + { + ConfigManager.RemoteConfig.Init(() => ShowDeveloperFAQ()); + return; + } + + Application.OpenURL(ConfigManager.RemoteConfig.GetString("sdkDeveloperFaqUrl")); + } + + [MenuItem("VRChat SDK/Help/VRChat Discord")] + public static void ShowVRChatDiscord() + { + if (!ConfigManager.RemoteConfig.IsInitialized()) + { + ConfigManager.RemoteConfig.Init(() => ShowVRChatDiscord()); + return; + } + + Application.OpenURL(ConfigManager.RemoteConfig.GetString("sdkDiscordUrl")); + } + + [MenuItem("VRChat SDK/Help/Avatar Optimization Tips")] + public static void ShowAvatarOptimizationTips() + { + if (!ConfigManager.RemoteConfig.IsInitialized()) + { + ConfigManager.RemoteConfig.Init(() => ShowAvatarOptimizationTips()); + return; + } + + Application.OpenURL(AVATAR_OPTIMIZATION_TIPS_URL); + } + + [MenuItem("VRChat SDK/Help/Avatar Rig Requirements")] + public static void ShowAvatarRigRequirements() + { + if (!ConfigManager.RemoteConfig.IsInitialized()) + { + ConfigManager.RemoteConfig.Init(() => ShowAvatarRigRequirements()); + return; + } + + Application.OpenURL(AVATAR_RIG_REQUIREMENTS_URL); + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelHelp.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelHelp.cs.meta new file mode 100644 index 00000000..a201ff75 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelHelp.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f3507a74e4b8cfd469afac127fa5f4e5 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelSettings.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelSettings.cs new file mode 100644 index 00000000..2df9b7c5 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelSettings.cs @@ -0,0 +1,260 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using VRC.Core; +using VRC.SDKBase.Editor; + +public partial class VRCSdkControlPanel : EditorWindow +{ + bool UseDevApi + { + get + { + return VRC.Core.API.GetApiUrl() == VRC.Core.API.devApiUrl; + } + } + + string clientVersionDate; + string sdkVersionDate; + Vector2 settingsScroll; + + private void Awake() + { + GetClientSdkVersionInformation(); + } + + public void GetClientSdkVersionInformation() + { + clientVersionDate = VRC.Core.SDKClientUtilities.GetTestClientVersionDate(); + sdkVersionDate = VRC.Core.SDKClientUtilities.GetSDKVersionDate(); + } + + public void OnConfigurationChanged() + { + GetClientSdkVersionInformation(); + } + + void ShowSettings() + { + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.BeginVertical(); + + settingsScroll = EditorGUILayout.BeginScrollView(settingsScroll, GUILayout.Width(SdkWindowWidth)); + + EditorGUILayout.BeginVertical(boxGuiStyle); + EditorGUILayout.LabelField("Developer", EditorStyles.boldLabel); + + VRCSettings.DisplayAdvancedSettings = EditorGUILayout.ToggleLeft("Show Extra Options on build page and account page", VRCSettings.DisplayAdvancedSettings); + bool prevDisplayHelpBoxes = VRCSettings.DisplayHelpBoxes; + VRCSettings.DisplayHelpBoxes = EditorGUILayout.ToggleLeft("Show Help Boxes on SDK components", VRCSettings.DisplayHelpBoxes); + if (VRCSettings.DisplayHelpBoxes != prevDisplayHelpBoxes) + { + Editor[] editors = (Editor[])Resources.FindObjectsOfTypeAll<Editor>(); + for (int i = 0; i < editors.Length; i++) + { + editors[i].Repaint(); + } + } + EditorGUILayout.EndVertical(); + + EditorGUILayout.Separator(); + + ShowSdk23CompatibilitySettings(); + EditorGUILayout.Separator(); + + ShowSettingsOptionsForBuilders(); + + + // debugging + if (APIUser.CurrentUser != null && APIUser.CurrentUser.hasSuperPowers) + { + EditorGUILayout.Separator(); + EditorGUILayout.BeginVertical(boxGuiStyle); + + EditorGUILayout.LabelField("Logging", EditorStyles.boldLabel); + + // API logging + { + bool isLoggingEnabled = UnityEditor.EditorPrefs.GetBool("apiLoggingEnabled"); + bool enableLogging = EditorGUILayout.ToggleLeft("API Logging Enabled", isLoggingEnabled); + if (enableLogging != isLoggingEnabled) + { + if (enableLogging) + VRC.Core.Logger.AddDebugLevel(DebugLevel.API); + else + VRC.Core.Logger.RemoveDebugLevel(DebugLevel.API); + + UnityEditor.EditorPrefs.SetBool("apiLoggingEnabled", enableLogging); + } + } + + // All logging + { + bool isLoggingEnabled = UnityEditor.EditorPrefs.GetBool("allLoggingEnabled"); + bool enableLogging = EditorGUILayout.ToggleLeft("All Logging Enabled", isLoggingEnabled); + if (enableLogging != isLoggingEnabled) + { + if (enableLogging) + VRC.Core.Logger.AddDebugLevel(DebugLevel.All); + else + VRC.Core.Logger.RemoveDebugLevel(DebugLevel.All); + + UnityEditor.EditorPrefs.SetBool("allLoggingEnabled", enableLogging); + } + } + EditorGUILayout.EndVertical(); + } + else + { + if (UnityEditor.EditorPrefs.GetBool("apiLoggingEnabled")) + UnityEditor.EditorPrefs.SetBool("apiLoggingEnabled", false); + if (UnityEditor.EditorPrefs.GetBool("allLoggingEnabled")) + UnityEditor.EditorPrefs.SetBool("allLoggingEnabled", false); + } + + // Future proof upload + { + EditorGUILayout.Separator(); + EditorGUILayout.BeginVertical(boxGuiStyle); + + EditorGUILayout.LabelField("Publish", EditorStyles.boldLabel); + bool futureProofPublish = UnityEditor.EditorPrefs.GetBool("futureProofPublish", DefaultFutureProofPublishEnabled); + + futureProofPublish = EditorGUILayout.ToggleLeft("Future Proof Publish", futureProofPublish); + + if (UnityEditor.EditorPrefs.GetBool("futureProofPublish", DefaultFutureProofPublishEnabled) != futureProofPublish) + { + UnityEditor.EditorPrefs.SetBool("futureProofPublish", futureProofPublish); + } + EditorGUILayout.LabelField("Client Version Date", clientVersionDate); + EditorGUILayout.LabelField("SDK Version Date", sdkVersionDate); + + EditorGUILayout.EndVertical(); + } + + + if (APIUser.CurrentUser != null) + { + EditorGUILayout.Separator(); + EditorGUILayout.BeginVertical(boxGuiStyle); + + // custom vrchat install location + OnVRCInstallPathGUI(); + + EditorGUILayout.EndVertical(); + } + + EditorGUILayout.EndScrollView(); + + GUILayout.EndVertical(); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + } + + static void OnVRCInstallPathGUI() + { + EditorGUILayout.LabelField("VRChat Client", EditorStyles.boldLabel); + EditorGUILayout.LabelField("Installed Client Path: ", clientInstallPath); + EditorGUILayout.BeginHorizontal(); + GUILayout.Label(""); + if (GUILayout.Button("Edit")) + { + string initPath = ""; + if (!string.IsNullOrEmpty(clientInstallPath)) + initPath = clientInstallPath; + + clientInstallPath = EditorUtility.OpenFilePanel("Choose VRC Client Exe", initPath, "exe"); + SDKClientUtilities.SetVRCInstallPath(clientInstallPath); + window.OnConfigurationChanged(); + } + if (GUILayout.Button("Revert to Default")) + { + clientInstallPath = SDKClientUtilities.LoadRegistryVRCInstallPath(); + window.OnConfigurationChanged(); + } + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Separator(); + } + + static void ShowSdk23CompatibilitySettings() + { + return; + +// EditorGUILayout.BeginVertical(boxGuiStyle); +// EditorGUILayout.LabelField("VRCSDK2 & VRCSDK3 Compatibility", EditorStyles.boldLabel); + +//#if !VRC_CLIENT +// bool sdk2Present = VRCSdk3Analysis.GetSDKInScene(VRCSdk3Analysis.SdkVersion.VRCSDK2).Count > 0; +// bool sdk3Present = VRCSdk3Analysis.GetSDKInScene(VRCSdk3Analysis.SdkVersion.VRCSDK3).Count > 0; +// bool sdk2DllActive = VRCSdk3Analysis.IsSdkDllActive(VRCSdk3Analysis.SdkVersion.VRCSDK2); +// bool sdk3DllActive = VRCSdk3Analysis.IsSdkDllActive(VRCSdk3Analysis.SdkVersion.VRCSDK3); + +// if ( sdk2DllActive && sdk3DllActive) +// { +// GUILayout.TextArea("You have not yet configured this project for development with VRCSDK2 and Triggers or VRCSDK3 and Udon. "); +// if (sdk2Present && sdk3Present) +// { +// GUILayout.TextArea("This scene contains both SDK2 and SDK3 elements. " + +// "Please modify this scene to contain only one type or the other before completing your configuration."); +// } +// else if (sdk2Present) +// { +// GUILayout.TextArea("This scene contains SDK2 scripts. " + +// "Check below to configure this project for use with VRCSDK2 or remove your VRCSDK2 scripts to upgrade to VRCSDK3"); +// bool downgrade = EditorGUILayout.ToggleLeft("Configure for use with VRCSDK2 and Triggers", false); +// if (downgrade) +// VRCSdk3Analysis.SetSdkVersionActive(VRCSdk3Analysis.SdkVersion.VRCSDK2); +// } +// else if (sdk3Present) +// { +// GUILayout.TextArea("This scene contains only SDK3 scripts and it ready to upgrade. " + +// "Click below to get started."); +// bool upgrade = EditorGUILayout.ToggleLeft("Configure for use with VRCSDK3 and Udon - Let's Rock!", false); +// if (upgrade) +// VRCSdk3Analysis.SetSdkVersionActive(VRCSdk3Analysis.SdkVersion.VRCSDK3); +// } +// else +// { +// GUILayout.TextArea("This scene is a blank slate. " + +// "Click below to get started."); +// bool upgrade = EditorGUILayout.ToggleLeft("Configure for use with VRCSDK3 and Udon - Let's Rock!", false); +// if (upgrade) +// VRCSdk3Analysis.SetSdkVersionActive(VRCSdk3Analysis.SdkVersion.VRCSDK3); +// } +// } +// else if (sdk2DllActive) +// { +// GUILayout.TextArea("This project has been configured to be built with VRCSDK2. " + +// "To upgrade, VRCSDK3 must be enabled here."); +// bool upgrade = EditorGUILayout.ToggleLeft("VRCSDK3 Scripts can be used", false); +// if (upgrade) +// VRCSdk3Analysis.SetSdkVersionActive(VRCSdk3Analysis.SdkVersion.VRCSDK3); +// } +// else if (sdk3DllActive) +// { +// GUILayout.TextArea("This project has been configured to be built with VRCSDK3. " + +// "Congratulations, you're ready to go. " + +// "You can still downgrade by activating VRCSDK2 here."); +// bool downgrade = EditorGUILayout.ToggleLeft("VRCSDK2 Scripts can be used", false); +// if (downgrade) +// VRCSdk3Analysis.SetSdkVersionActive(VRCSdk3Analysis.SdkVersion.VRCSDK2); +// } +// else +// { +// GUILayout.TextArea("Somehow you have disabled both VRCSDK2 and VRCSDK3. Oops. " + +// "Click here to begin development with VRCSDK3."); +// bool begin = EditorGUILayout.ToggleLeft("VRCSDK3 Scripts can be used", false); +// if (begin) +// VRCSdk3Analysis.SetSdkVersionActive(VRCSdk3Analysis.SdkVersion.VRCSDK3); +// } +//#else +// GUILayout.TextArea("I think you're in the main VRChat project. " + +// "You should not be enabling or disabling SDKs from here."); +//#endif + +// EditorGUILayout.EndVertical(); + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelSettings.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelSettings.cs.meta new file mode 100644 index 00000000..0dd5e76d --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelSettings.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8357b9b7ef2416946ae86f465a64c0e0 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: |