using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using UnityEditor; using UnityEngine; namespace Thry.ThryEditor { public class Presets : AssetPostprocessor { const string TAG_IS_PRESET = "isPreset"; const string TAG_POSTFIX_IS_PRESET = "_isPreset"; const string TAG_PRESET_NAME = "presetName"; const string FILE_NAME_CACHE = "Thry/preset_cache.txt"; static Dictionary s_appliedPresets = new Dictionary(); static string[] p_presetNames; static Dictionary s_presetGuids; static Dictionary s_presetMaterials; static string[] s_presetNames { get { if (p_presetNames == null) { // Get current time var time = System.DateTime.Now; // Check if cache exists if(File.Exists(FILE_NAME_CACHE)) { string raw = File.ReadAllText(FILE_NAME_CACHE); // If file is empty (no presets), create empty, parsing would throw error if(string.IsNullOrWhiteSpace(raw)) { s_presetGuids = new Dictionary(); p_presetNames = new string[0]; s_presetMaterials = new Dictionary(); } else { // Load from cache string[][] lines = raw.Split('\n').Select(s => s.Split(';')).ToArray(); // Split into lines s_presetGuids = lines.Select(l => (l[0], l[1])).ToDictionary(t => t.Item1, t => t.Item2); p_presetNames = lines.Select(l => l[0]).Prepend("").ToArray(); s_presetMaterials = new Dictionary(); } }else { CreatePresetCache(); } // Log time // Debug.Log($"Presets: {p_presetNames.Length} presets found in {System.DateTime.Now - time}"); } return p_presetNames; } } static void CreatePresetCache() { // Create cache // Find all materials string[] guids = AssetDatabase.FindAssets("t:material"); List presetMaterials = new List(); for(int guid = 0; guid < guids.Length; guid++) { EditorUtility.DisplayProgressBar("Creating Preset Cache", $"Loading material {guid + 1}/{guids.Length}", (float)guid / guids.Length); // Load material Material material = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guids[guid])); // Check if material is preset if (IsPreset(material)) { // Add to list presetMaterials.Add(material); } } EditorUtility.ClearProgressBar(); // Create data p_presetNames = new string[presetMaterials.Count]; s_presetMaterials = new Dictionary(); s_presetGuids = new Dictionary(); for(int i = 0; i < presetMaterials.Count; i++) { p_presetNames[i] = presetMaterials[i].GetTag(TAG_PRESET_NAME, false, presetMaterials[i].name).Replace(';', '_'); s_presetMaterials[p_presetNames[i]] = presetMaterials[i]; s_presetGuids[p_presetNames[i]] = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(presetMaterials[i])); } // Save cache Save(); } static void Save() { // Save cache FileHelper.CreateFileWithDirectories(FILE_NAME_CACHE); File.WriteAllText(FILE_NAME_CACHE, string.Join("\n", s_presetGuids.Select(kvp => $"{kvp.Key};{kvp.Value}"))); } // On Asset Delete remove presets from cache static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { if(s_presetNames == null) return; // should init stuff if(importedAssets.Length > 0) { // Check if any presets were imported foreach (string asset in importedAssets) { // Check if asset is material if (asset.EndsWith(".mat")) { Material material = AssetDatabase.LoadAssetAtPath(asset); // Check if asset is preset if (IsPreset(material)) { // Add preset AddPreset(material); } } } } if(deletedAssets.Length > 0) { Dictionary presetPaths = new Dictionary(); // get all paths from guids foreach (var kvp in s_presetGuids) { presetPaths.Add(AssetDatabase.GUIDToAssetPath(kvp.Value), kvp.Key); } // Check if any presets were deleted foreach (string asset in deletedAssets) { // Check if asset is material if (asset.EndsWith(".mat")) { // Check if asset is preset if (presetPaths.ContainsKey(asset)) { // Remove preset RemovePreset(presetPaths[asset]); } } } } } static void AddPreset(Material material) { Debug.Log($"AddPreset: {material.name}"); // Get preset name string presetName = material.GetTag(TAG_PRESET_NAME, false, material.name).Replace(';', '_'); // Get preset guid string presetGuid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(material)); // Add to cache p_presetNames = s_presetNames.Append(presetName).ToArray(); s_presetGuids[presetName] = presetGuid; s_presetMaterials[presetName] = material; // Save cache Save(); } static void RemovePreset(Material material) { // Get preset name string presetName = material.GetTag(TAG_PRESET_NAME, false, material.name).Replace(';', '_'); // Remove from cache p_presetNames = s_presetNames.Where(n => n != presetName).ToArray(); s_presetGuids.Remove(presetName); s_presetMaterials.Remove(presetName); // Save cache Save(); } static void RemovePreset(string name) { Debug.Log($"RemovePreset: {name}"); // Get preset name string presetName = name.Replace(';', '_'); // Remove from cache p_presetNames = s_presetNames.Where(n => n != presetName).ToArray(); s_presetGuids.Remove(presetName); s_presetMaterials.Remove(presetName); // Save cache Save(); } public static Material GetPresetMaterial(string presetName) { if (s_presetMaterials.ContainsKey(presetName)) { return s_presetMaterials[presetName]; } else if(s_presetGuids.ContainsKey(presetName)) { Material m = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(s_presetGuids[presetName])); s_presetMaterials[presetName] = m; return m; } return null; } public static bool DoesPresetExist(string presetName) { return s_presetGuids.ContainsKey(presetName); } private static PresetsPopupGUI window; public static void OpenPresetsMenu(Rect r, ShaderEditor shaderEditor) { Event.current.Use(); if (Event.current.button == 0) { Vector2 pos = GUIUtility.GUIToScreenPoint(Event.current.mousePosition); pos.x = Mathf.Min(EditorWindow.focusedWindow.position.x + EditorWindow.focusedWindow.position.width - 250, pos.x); pos.y = Mathf.Min(EditorWindow.focusedWindow.position.y + EditorWindow.focusedWindow.position.height - 200, pos.y); if (window != null) window.Close(); window = ScriptableObject.CreateInstance(); window.position = new Rect(pos.x, pos.y, 250, 200); window.Init(s_presetNames, shaderEditor); window.titleContent = new GUIContent("Preset List"); window.ShowUtility(); } else { EditorUtility.DisplayCustomMenu(GUILayoutUtility.GetLastRect(), s_presetNames.Select(s => new GUIContent(s)).ToArray(), 0, ApplyQuickPreset, shaderEditor); } } static void ApplyQuickPreset(object userData, string[] options, int selected) { Apply(GetPresetMaterial(p_presetNames[selected - 1]), userData as ShaderEditor); } public static void PresetEditorGUI(ShaderEditor shaderEditor) { if (shaderEditor.IsPresetEditor) { EditorGUILayout.LabelField(EditorLocale.editor.Get("preset_material_notify"), Styles.greenStyle); string name = shaderEditor.Materials[0].GetTag(TAG_PRESET_NAME, false, ""); EditorGUI.BeginChangeCheck(); name = EditorGUILayout.TextField(EditorLocale.editor.Get("preset_name"), name); if (EditorGUI.EndChangeCheck()) { shaderEditor.Materials[0].SetOverrideTag(TAG_PRESET_NAME, name); p_presetNames = null; } } if (s_appliedPresets.ContainsKey(shaderEditor.Materials[0])) { if(GUILayout.Button(EditorLocale.editor.Get("preset_revert")+s_appliedPresets[shaderEditor.Materials[0]].Item1.name)) { Revert(shaderEditor); } } } public static void Apply(Material preset, ShaderEditor shaderEditor) { s_appliedPresets[shaderEditor.Materials[0]] = (preset, new Material(shaderEditor.Materials[0])); foreach (ShaderPart prop in shaderEditor.ShaderParts) { if (IsPreset(preset, prop)) { prop.CopyFromMaterial(preset); } } foreach (Material m in shaderEditor.Materials) MaterialEditor.ApplyMaterialPropertyDrawers(m); } static void Revert(ShaderEditor shaderEditor) { Material key = shaderEditor.Materials[0]; Material preset = s_appliedPresets[key].Item1; Material prePreset = s_appliedPresets[key].Item2; foreach (ShaderPart prop in shaderEditor.ShaderParts) { if (IsPreset(preset, prop)) { prop.CopyFromMaterial(prePreset); } } foreach (Material m in shaderEditor.Materials) MaterialEditor.ApplyMaterialPropertyDrawers(m); s_appliedPresets.Remove(key); } public static void ApplyList(ShaderEditor shaderEditor, Material[] originals, List presets) { for(int i=0;i IsPreset(m)); } public static bool IsPreset(Material m) { return m.GetTag(TAG_IS_PRESET, false, "false") == "true"; } [MenuItem("Assets/Thry/Mark as preset")] static void MarkAsPreset() { IEnumerable mats = Selection.assetGUIDs.Select(g => AssetDatabase.GUIDToAssetPath(g)). Where(p => AssetDatabase.GetMainAssetTypeAtPath(p) == typeof(Material)).Select(p => AssetDatabase.LoadAssetAtPath(p)); foreach (Material m in mats) { m.SetOverrideTag(TAG_IS_PRESET, "true"); if (m.GetTag("presetName", false, "") == "") m.SetOverrideTag("presetName", m.name); Presets.AddPreset(m); } p_presetNames = null; } [MenuItem("Assets/Thry/Mark as preset", true)] static bool MarkAsPresetValid() { return Selection.assetGUIDs.Select(g => AssetDatabase.GUIDToAssetPath(g)). All(p => AssetDatabase.GetMainAssetTypeAtPath(p) == typeof(Material)); } [MenuItem("Assets/Thry/Remove as preset")] static void RemoveAsPreset() { IEnumerable mats = Selection.assetGUIDs.Select(g => AssetDatabase.GUIDToAssetPath(g)). Where(p => AssetDatabase.GetMainAssetTypeAtPath(p) == typeof(Material)).Select(p => AssetDatabase.LoadAssetAtPath(p)); foreach (Material m in mats) { m.SetOverrideTag(TAG_IS_PRESET, ""); Presets.RemovePreset(m); } p_presetNames = null; } [MenuItem("Assets/Thry/Remove as preset", true)] static bool RemoveAsPresetValid() { return Selection.assetGUIDs.Select(g => AssetDatabase.GUIDToAssetPath(g)). All(p => AssetDatabase.GetMainAssetTypeAtPath(p) == typeof(Material)); } [MenuItem("Thry/Presets/Rebuild Cache", priority = 100)] static void RebuildCache() { Presets.CreatePresetCache(); } } public class PresetsPopupGUI : EditorWindow { class PresetStruct { public Dictionary dict; string name; string fullName; bool hasPreset; bool isOpen = false; bool isOn; public PresetStruct(string name) { this.name = name; dict = new Dictionary(); } public PresetStruct GetSubStruct(string name) { name = name.Trim(); if (dict.ContainsKey(name) == false) dict.Add(name, new PresetStruct(name)); return dict[name]; } public void SetHasPreset(bool b, string fullName) { this.hasPreset = b; this.fullName = fullName; } public void StructGUI(PresetsPopupGUI popupGUI) { if(hasPreset) { EditorGUI.BeginChangeCheck(); isOn = EditorGUILayout.ToggleLeft(name, isOn); if (EditorGUI.EndChangeCheck()) { popupGUI.ToggelPreset(Presets.GetPresetMaterial(fullName), isOn); } } if(dict.Count > 0) { Rect r = GUILayoutUtility.GetRect(new GUIContent(), Styles.dropDownHeader); r.x = EditorGUI.indentLevel * 15; r.width -= r.x; GUI.Box(r, name, Styles.dropDownHeader); if (Event.current.type == EventType.Repaint) { var toggleRect = new Rect(r.x + 4f, r.y + 2f, 13f, 13f); EditorStyles.foldout.Draw(toggleRect, false, false, isOpen, false); } if (Event.current.type == EventType.MouseDown && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) { isOpen = !isOpen; ShaderEditor.Input.Use(); } if (isOpen) { EditorGUI.indentLevel += 1; foreach (KeyValuePair struc in dict) { struc.Value.StructGUI(popupGUI); } EditorGUI.indentLevel -= 1; } } } public void Reset() { isOn = false; foreach (KeyValuePair struc in dict) struc.Value.Reset(); } } Material[] beforePreset; List tickedPresets = new List(); PresetStruct mainStruct; ShaderEditor shaderEditor; public void Init(string[] names, ShaderEditor shaderEditor) { this.shaderEditor = shaderEditor; ShaderOptimizer.DetourApplyMaterialPropertyDrawers(); this.beforePreset = shaderEditor.Materials.Select(m => new Material(m)).ToArray(); ShaderOptimizer.RestoreApplyMaterialPropertyDrawers(); mainStruct = new PresetStruct(""); backgroundTextrure = new Texture2D(1,1); if (EditorGUIUtility.isProSkin) backgroundTextrure.SetPixel(0, 0, new Color(0.18f, 0.18f, 0.18f, 1)); else backgroundTextrure.SetPixel(0, 0, new Color(0.9f, 0.9f, 0.9f, 1)); backgroundTextrure.Apply(); for (int i = 1; i < names.Length; i++) { string[] path = names[i].Split('/'); PresetStruct addUnder = mainStruct; for (int j=0;j struc in mainStruct.dict) { struc.Value.StructGUI(this); } } void Revert() { EditorUtility.DisplayProgressBar("Reverting", "Reverting", 0); for (int i = 0; i < shaderEditor.Materials.Length; i++) { EditorUtility.DisplayProgressBar("Reverting", "Reverting", (float)i / shaderEditor.Materials.Length); shaderEditor.Materials[i].CopyPropertiesFromMaterial(beforePreset[i]); MaterialEditor.ApplyMaterialPropertyDrawers(shaderEditor.Materials[i]); } EditorUtility.ClearProgressBar(); mainStruct.Reset(); shaderEditor.Repaint(); } } }