#if PROBUILDER_DEBUG using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEngine; using UnityEditor; using UObject = UnityEngine.Object; using UnityEditor.IMGUI.Controls; namespace UnityEngine.ProBuilder.AssetIdRemapUtility { /// /// Utility class for creating GUID remap files. /// sealed class AssetIdRemapBuilderEditor : EditorWindow { const string k_RemapFilePath = "AssetIdRemap.json"; const string k_NamespaceRemapFilePath = "NamespaceRemap.json"; static string remapFilePath { get { return "Assets/" + k_RemapFilePath; } } static string namespaceRemapFilePath { get { return "Assets/" + k_NamespaceRemapFilePath; } } static readonly string[] k_DirectoryExcludeFilter = new string[] { "ProBuilder/About", "ProBuilder/AssetIdRemapUtility", "ProBuilder/API Examples", "ProBuilder/Data", "ProBuilder/Icons", "ProBuilder/Upgrade", }; static GUIContent m_SourceGuiContent = new GUIContent("Source", "The old GUID and FileId."); static GUIContent m_DestinationGuiContent = new GUIContent("Destination", "The new GUID and FileId."); [SerializeField] TextAsset m_RemapTextAsset = null; [SerializeField] TextAsset m_NamespaceRemapTextAsset = null; [SerializeField] string m_SourceDirectory; [SerializeField] string m_DestinationDirectory; [SerializeField] TreeViewState m_TreeViewState; [SerializeField] MultiColumnHeaderState m_MultiColumnHeaderState; [SerializeField] bool m_DetailsExpanded; MultiColumnHeader m_MultiColumnHeader; AssetIdRemapBuilderTreeView m_TreeView; SearchField m_SearchField; [MenuItem("Tools/GUID Remap Editor")] static void MenuOpenGuidEditor() { var win = GetWindow(true, "GUID Remap Editor", true); win.m_DetailsExpanded = true; } static class Styles { static GUIStyle m_Container = null; public static GUIStyle container { get { if (m_Container == null) { m_Container = new GUIStyle(EditorStyles.helpBox); m_Container.padding = new RectOffset(4, 4, 4, 4); } return m_Container; } } } void OnEnable() { // Check whether there is already a serialized view state (state // that survived assembly reloading) if (m_TreeViewState == null) m_TreeViewState = new TreeViewState(); if (m_MultiColumnHeaderState == null) m_MultiColumnHeaderState = new MultiColumnHeaderState(new MultiColumnHeaderState.Column[] { new MultiColumnHeaderState.Column() { headerContent = m_SourceGuiContent, autoResize = true }, new MultiColumnHeaderState.Column() { headerContent = m_DestinationGuiContent, autoResize = true }, }); m_MultiColumnHeader = new MultiColumnHeader(m_MultiColumnHeaderState); m_MultiColumnHeader.ResizeToFit(); m_TreeView = new AssetIdRemapBuilderTreeView(m_TreeViewState, m_MultiColumnHeader); m_TreeView.remapObject = GetGuidRemapObject(); m_TreeView.Reload(); m_SearchField = new SearchField(); } void OnDestroy() { if (m_TreeView.isDirty) { if (EditorUtility.DisplayDialog("Unsaved Changes", "There are unsaved changes to the remap file. Save these changes?", "Save", "Discard")) Save(); } } string GetRemapFilePath() { if (m_RemapTextAsset != null) return AssetDatabase.GetAssetPath(m_RemapTextAsset); return remapFilePath; } void Save() { File.WriteAllText(GetRemapFilePath(), JsonUtility.ToJson(m_TreeView.remapObject, true)); AssetDatabase.ImportAsset(GetRemapFilePath()); EditorGUIUtility.PingObject(AssetDatabase.LoadAssetAtPath(GetRemapFilePath())); m_TreeView.isDirty = false; } void OnGUI() { GUILayout.BeginHorizontal(EditorStyles.toolbar); if (GUILayout.Button(m_DetailsExpanded ? "Hide" : "Show", EditorStyles.toolbarButton)) m_DetailsExpanded = !m_DetailsExpanded; GUILayout.FlexibleSpace(); GUI.enabled = m_TreeView.isDirty; if (GUILayout.Button("Revert", EditorStyles.toolbarButton)) { m_TreeView.remapObject = null; m_TreeView.remapObject = GetGuidRemapObject(); m_TreeView.Reload(); m_TreeView.isDirty = false; } if (GUILayout.Button("Save", EditorStyles.toolbarButton)) Save(); GUI.enabled = true; GUILayout.EndHorizontal(); if (m_DetailsExpanded) { EditorGUI.BeginChangeCheck(); m_RemapTextAsset = (TextAsset)EditorGUILayout.ObjectField("Remap", m_RemapTextAsset, typeof(TextAsset), false); m_NamespaceRemapTextAsset = (TextAsset)EditorGUILayout.ObjectField("Namespace", m_NamespaceRemapTextAsset, typeof(TextAsset), false); if (EditorGUI.EndChangeCheck()) { m_TreeView.remapObject = null; m_TreeView.remapObject = GetGuidRemapObject(); m_TreeView.Reload(); Repaint(); } EditorGUILayout.BeginVertical(Styles.container); GUILayout.Label("Package Directories", EditorStyles.boldLabel); EditorGUILayout.BeginVertical(Styles.container); m_SourceDirectory = DoDirectoryField("Source", m_SourceDirectory); EditorGUI.BeginChangeCheck(); if (GUILayout.Button("Collect Source (Old) Asset Identifiers")) GetRemapSource(m_SourceDirectory); EditorGUILayout.EndVertical(); EditorGUILayout.BeginVertical(Styles.container); m_DestinationDirectory = DoDirectoryField("Destination", m_DestinationDirectory); if (GUILayout.Button("Collect Destination (New) Asset Identifiers")) GetRemapDestination(m_DestinationDirectory); if (EditorGUI.EndChangeCheck()) m_TreeView.Reload(); EditorGUILayout.EndVertical(); EditorGUILayout.EndVertical(); } GUILayout.BeginHorizontal(); GUILayout.Label("Asset Id Mapping", EditorStyles.boldLabel); GUILayout.FlexibleSpace(); if (GUILayout.Button("...")) { var menu = new GenericMenu(); menu.AddItem(new GUIContent("Clear Source", ""), false, () => { GetGuidRemapObject().Clear(Origin.Source); m_TreeView.Reload(); m_TreeView.isDirty = true; }); menu.AddItem(new GUIContent("Clear Destination", ""), false, () => { GetGuidRemapObject().Clear(Origin.Destination); m_TreeView.Reload(); m_TreeView.isDirty = true; }); menu.ShowAsContext(); } GUILayout.EndHorizontal(); Rect last = GUILayoutUtility.GetLastRect(); m_TreeView.searchString = m_SearchField.OnGUI(new Rect(last.x, last.y + last.height + 4, position.width - last.x * 2f, 20f), m_TreeView.searchString); Vector2 treeStart = new Vector2(last.x, last.y + last.height + 4 + 20f + 4f); m_TreeView.SetRowHeight(); m_TreeView.OnGUI(new Rect(treeStart.x, treeStart.y, position.width - treeStart.x * 2, position.height - treeStart.y)); } static string DoDirectoryField(string title, string value) { GUILayout.BeginHorizontal(); EditorGUI.BeginChangeCheck(); value = EditorGUILayout.TextField(title, value); if (GUILayout.Button("Select", GUILayout.MaxWidth(60))) value = GetSelectedDirectory(); bool didChange = EditorGUI.EndChangeCheck(); bool doOpenFolderPanel = GUILayout.Button("...", GUILayout.MaxWidth(32)); GUILayout.EndHorizontal(); if (doOpenFolderPanel) { value = EditorUtility.OpenFolderPanel(title, value, ""); didChange = true; } return didChange ? value = value.Replace("\\", "/").Replace(Application.dataPath, "Assets") : value; } void GetRemapSource(string directory) { if (string.IsNullOrEmpty(directory) || !Directory.Exists(directory)) { Debug.LogWarning("No source directory selected."); return; } var remapObject = GetGuidRemapObject(); string localDirectory = directory.Replace("\\", "/").Replace(Application.dataPath, "Assets") + "/"; if (!remapObject.sourceDirectory.Contains(localDirectory)) remapObject.sourceDirectory.Add(localDirectory); List map = remapObject.map; foreach (var id in GetAssetIdentifiersInDirectory(localDirectory, k_DirectoryExcludeFilter)) { id.SetPathRelativeTo(localDirectory); if (map.Any(x => x.source != null && x.source.Equals(id))) continue; // the only time where a destination can exist with a null source is when a single destination is in the // map, so it's okay to grab the first and not bother searching for more dangling destination entries AssetIdentifierTuple matchingDestination = map.FirstOrDefault(x => { return x.destination != null && x.destination.AssetEquals(id); }); if (matchingDestination != null) { if (AssetId.IsValid(matchingDestination.source)) map.Add(new AssetIdentifierTuple(id, matchingDestination.destination)); else matchingDestination.source = id; } else { map.Add(new AssetIdentifierTuple(id, null)); } } m_TreeView.isDirty = true; } void GetRemapDestination(string directory) { if (string.IsNullOrEmpty(directory) || !Directory.Exists(directory)) { Debug.LogWarning("No destination directory selected."); return; } var remapObject = GetGuidRemapObject(); if (!string.IsNullOrEmpty(remapObject.destinationDirectory)) { if (!EditorUtility.DisplayDialog("Destination Directory Already Mapped", "The destination directory has already been mapped. Continuing will overwrite the existing data. Are you sure you wish to continue?", "Continue", "Cancel")) return; } string localDirectory = directory.Replace("\\", "/").Replace(Application.dataPath, "Assets") + "/"; remapObject.destinationDirectory = localDirectory; List map = remapObject.map; foreach (var id in GetAssetIdentifiersInDirectory(localDirectory, k_DirectoryExcludeFilter)) { if (map.Any(x => x.destination.Equals(id))) continue; id.SetPathRelativeTo(localDirectory); IEnumerable matchingSources = map.Where(x => x.source != null && x.source.AssetEquals(id)); if (matchingSources.Any()) { foreach (var tup in matchingSources) tup.destination = id; } else { map.Add(new AssetIdentifierTuple(null, id)); } } m_TreeView.isDirty = true; } /// /// Collect asset identifier information from all files in a directory. /// /// /// static List GetAssetIdentifiersInDirectory(string directory, string[] directoryIgnoreFilter) { List ids = new List(); string unixPath = directory.Replace("\\", "/"); string packageAbsolutePath = null; string packageRelativePath = null; if (directory.StartsWith("Packages/")) { // +1 because we want the trailing "/" to be with the packageRelativePath var en = unixPath.IndexOf("/", "Packages/".Length, StringComparison.InvariantCulture) + 1; packageRelativePath = unixPath.Substring(0, en); var targetPathRelative = unixPath.Substring(en, unixPath.Length - en); packageAbsolutePath = Path.GetFullPath(unixPath).Replace("\\", "/"); if (!string.IsNullOrEmpty(targetPathRelative)) packageAbsolutePath = packageAbsolutePath.Replace(targetPathRelative, ""); } if (directoryIgnoreFilter != null && directoryIgnoreFilter.Any(x => unixPath.Contains(x))) return ids; foreach (string file in Directory.GetFiles(Path.GetFullPath(directory), "*", SearchOption.TopDirectoryOnly)) { if (file.EndsWith(".meta") || Path.GetFileName(file).StartsWith(".")) continue; string localPath = file.Replace("\\", "/").Replace(Application.dataPath, "Assets"); if (!string.IsNullOrEmpty(packageAbsolutePath)) localPath = localPath.Replace(packageAbsolutePath, packageRelativePath); ids.AddRange(GetAssetIdentifiers(localPath)); } foreach (string dir in Directory.GetDirectories(directory, "*", SearchOption.TopDirectoryOnly)) { if (Path.GetDirectoryName(dir).StartsWith(".")) continue; string path = !string.IsNullOrEmpty(packageAbsolutePath) ? dir.Replace("\\", "/").Replace(packageAbsolutePath, packageRelativePath) : dir; ids.AddRange(GetAssetIdentifiersInDirectory(path, directoryIgnoreFilter)); } return ids; } static List GetAssetIdentifiers(string assetPath) { List ids = new List(); if (assetPath.EndsWith(".unity")) return ids; foreach (UnityEngine.Object o in AssetDatabase.LoadAllAssetsAtPath(assetPath)) { string g; long file; if (o != null && AssetDatabase.TryGetGUIDAndLocalFileIdentifier(o, out g, out file)) ids.Add(new AssetId(o, file.ToString(), g.ToString(), assetPath)); } return ids; } /// /// Load a remap json file from a relative path (Assets/MyRemapFile.json). /// /// A GuidRemapObject from the path, or if not found, a new GuidRemapObject AssetIdRemapObject GetGuidRemapObject() { var remapObject = m_TreeView.remapObject; if (remapObject != null) return remapObject; if (m_RemapTextAsset == null) m_RemapTextAsset = AssetDatabase.LoadAssetAtPath(remapFilePath); remapObject = new AssetIdRemapObject(); if (m_RemapTextAsset != null) JsonUtility.FromJsonOverwrite(m_RemapTextAsset.text, remapObject); return remapObject; } static string GetSelectedDirectory() { UObject o = Selection.activeObject; if (o != null) { string path = AssetDatabase.GetAssetPath(o.GetInstanceID()); if (!string.IsNullOrEmpty(path)) { if (Directory.Exists(path)) return Path.GetFullPath(path); string res = Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(res) && System.IO.Directory.Exists(res)) return Path.GetFullPath(res); } } return Path.GetFullPath("Assets"); } } } #endif