Created
October 25, 2022 11:29
-
-
Save TobiasPott/c794e754b3e148e4c62f38c1efb7a3e5 to your computer and use it in GitHub Desktop.
Extension to allow shortcut-based save and load of selection to disk (.sel files)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Text; | |
using UnityEditor; | |
using UnityEngine; | |
using UnityEngine.SceneManagement; | |
public class SelectionExtensions | |
{ | |
public const string SelectionFileExt = "sel"; | |
[MenuItem("Edit/Selection/Load Selection from file %&l")] | |
private static void LoadSelectionFromFile() | |
{ | |
string filePath = EditorUtility.OpenFilePanel("Select file", Application.dataPath, SelectionFileExt); | |
if (!string.IsNullOrEmpty(filePath)) | |
SelectFromFile(filePath); | |
} | |
[MenuItem("Edit/Selection/Save Selection to file %&s")] | |
private static void SaveSelectionToFile() | |
{ | |
// ToDo: | |
// use "Temp + DateTime" as default anonymous selection identifier | |
// add method overload with 'shift' modifier to prompt for name/path on save | |
// add method overload with 'shift' modifier to prompt for file selection on load | |
// define no-shift modifier and anonymous naming as default behaviour on save | |
// define no-shift modifier and anonymous naming as default behaviour on load | |
// add caching of last path to EditorPrefs | |
// -> should reset to default anonymous location when anonymous save/load is used | |
// -> default anonymous location should be a subfolder of the project folder (NOT asset folder) | |
if (Selection.gameObjects.Length != 0) | |
SelectionToFile(Selection.gameObjects); | |
else | |
Debug.LogWarning("No objects are selected. An empty selection cannot be saved to disk."); | |
} | |
private static void SelectionToFile(ICollection<GameObject> selection) | |
{ | |
List<MappedObject> objects = new List<MappedObject>(); | |
foreach (GameObject go in selection) | |
{ | |
string objectPath = go.transform.GetParentPath(); | |
string hierarchyPath = objectPath.Replace(go.name, ""); | |
objects.Add(new MappedObject(go, objectPath)); | |
} | |
// writing selection files per material to disk | |
IEnumerable<IGrouping<string, MappedObject>> groups = objects.OrderBy(x => x.Path).ThenBy(x => x.GameObject.name).GroupBy(x => x.MaterialName); | |
foreach (IGrouping<string, MappedObject> group in groups) | |
{ | |
Debug.Log("Material: " + group.Key); | |
string filename = "Selection_" + group.Key + "." + SelectionFileExt; | |
StringBuilder sbOutput = new StringBuilder(); | |
foreach (MappedObject obj in group) | |
sbOutput.AppendLine(obj.Path); | |
System.IO.File.WriteAllText(filename, sbOutput.ToString()); | |
} | |
} | |
private static void SelectFromFile(string filepath) | |
{ | |
try | |
{ | |
FileInfo fi = new FileInfo(filepath); | |
List<GameObject> rootObjects = new List<GameObject>(); | |
for (int i = 0; i < SceneManager.sceneCount; i++) | |
{ | |
Scene scene = SceneManager.GetSceneAt(i); | |
if (scene.isLoaded) | |
rootObjects.AddRange(scene.GetRootGameObjects()); | |
} | |
List<GameObject> selected = new List<GameObject>(); | |
List<string> missing = new List<string>(); | |
if (fi.Exists) | |
{ | |
string[] entries = File.ReadAllLines(filepath); | |
foreach (string e in entries) | |
{ | |
bool found = false; | |
for (int i = 0; i < rootObjects.Count; i++) | |
{ | |
GameObject go = rootObjects[i]; | |
if (e.Equals(go.name)) | |
{ | |
selected.Add(go); | |
found = true; | |
break; | |
} | |
else if (e.StartsWith(go.name + TransformExtensions.TransformPathSeparatorChar)) | |
{ | |
string childHierarchyPath = e.Replace(go.name + TransformExtensions.TransformPathSeparatorChar, ""); | |
Transform foundChild = go.transform.Find(childHierarchyPath); | |
if (foundChild != null && !selected.Contains(foundChild.gameObject)) | |
{ | |
selected.Add(foundChild.gameObject); | |
found = true; | |
break; | |
} | |
} | |
} | |
if (!found) | |
{ | |
missing.Add(e); | |
} | |
} | |
} | |
string output = $"Loaded {fi.Name}{Environment.NewLine}=> Selected: {selected.Count}; Missing: {missing.Count}"; | |
if (missing.Count > 0) | |
{ | |
string unfoundOutput = string.Join(Environment.NewLine, missing); | |
output += Environment.NewLine + "List of missing:" + Environment.NewLine + unfoundOutput; | |
} | |
Debug.Log(output); | |
Selection.objects = selected.ToArray(); | |
} | |
catch (Exception e) | |
{ | |
Debug.LogError(e); | |
} | |
} | |
public struct MappedObject | |
{ | |
// Fields | |
private GameObject gameObject; | |
private string path; | |
private Material material; | |
// Properties | |
public GameObject GameObject { get => gameObject; } | |
public string Path { get => path; } | |
public string MaterialName { get => material != null ? material.name : String.Empty; } | |
public MappedObject(GameObject gameObject, string path) | |
{ | |
this.gameObject = gameObject; | |
this.path = path; | |
if (gameObject != null && gameObject.TryGetComponent<Renderer>(out Renderer renderer)) | |
this.material = renderer.sharedMaterial; | |
else | |
this.material = null; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment