Skip to content

Instantly share code, notes, and snippets.

@unitycoder
Created May 10, 2024 07:07
Show Gist options
  • Save unitycoder/53689b2cfac470f7561a283fcd19934b to your computer and use it in GitHub Desktop.
Save unitycoder/53689b2cfac470f7561a283fcd19934b to your computer and use it in GitHub Desktop.
FBX Animation Organizer
// https://forum.unity.com/threads/reordering-fbx-animation-clips.1168847/#post-9825564
/*******************
WARNING: MAKE SURE YOUR METADATA FILES ARE BACKED UP WITH VERSION CONTROL BEFORE USING
Installation:
Copy this C# script into Scripts\Editor.
Usage:
1) To open, go to Window -> FBX Animation Organizer
2) Enter the local directory location of your fbx meta files in the "Directory" field (Right click on the folder in Project window -> Copy Path)
3) Select the meta file to edit from the "File" dropdown
4) Press the "Import Metadata" button
5) Select the animations and move them with the "Move Up" or "Move Down" buttons
6) Press "Overwrite Metadata" button to save and overwrite the old metadata file
7) Press "Refresh Inspector" to update your changes
Known Bugs:
1) Pressing "Overwrite Metadata" 2x in a row will duplicate the animations list.
1) Pressing "Overwrite Metadata" again after changing the meta file, but not importing the new metafile will cause errors
******************/
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Linq;
using System.IO;
public class FBXAnimationOrganizer : EditorWindow {
GUIStyle itemStyle = new GUIStyle();
string inputPath = "Assets\\";
string fileName;
List<string> metadataHeader = new List<string>();
List<string> metadata = new List<string>();
List<List<string>> animations = new List<List<string>>(); //List that holds each animation's data as another list
List<string> animNames = new List<string>(); //List of animation names
int selectedAnimationIndex = -1;
int selectedFileNameIndex = -1;
[MenuItem("Window/FBX Animation Organizer")]
static void Init() {
var window = GetWindow(typeof(FBXAnimationOrganizer));
window.titleContent = new GUIContent("FBX Animation Organizer");
window.Show();
}
private void OnGUI() {
//Directory Field
inputPath = EditorGUILayout.TextField("Directory:", inputPath);
if (GUI.changed) {
inputPath = EditorGUILayout.TextField(inputPath);
GUILayout.Label(fileName);
}
//Metadata File Dropdown
GUILayout.BeginHorizontal();
if (Directory.Exists(inputPath)) {
GUILayout.Label("File:");
int oldSelectedFileNameIndex = selectedFileNameIndex;
string[] filesUnedited = Directory.GetFiles(inputPath, "*meta", SearchOption.AllDirectories);
//Edit the filename so it looks nice
string[] filesEdited = new string[filesUnedited.Count()];
for (var i = 0; i < filesUnedited.Count(); i++) {
filesEdited[i] = filesUnedited[i];
int lastSlash = filesEdited[i].LastIndexOf("\\");
filesEdited[i] = filesEdited[i].Substring(lastSlash + 1, filesEdited[i].Count() - 1 - lastSlash);
}
selectedFileNameIndex = EditorGUILayout.Popup(selectedFileNameIndex, filesEdited);
if (selectedFileNameIndex != -1) fileName = filesEdited[selectedFileNameIndex];
//Clear out data if selecting a new file
if (oldSelectedFileNameIndex != selectedFileNameIndex) {
ClearData();
}
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
//Import Metadata Button
if (GUILayout.Button("Import Metadata")) {
ClearData();
//Read the input file into a list and copy them to another list for editing
var logFile = File.ReadAllLines(inputPath + "\\" + fileName);
metadata = new List<string>(logFile);
//Get the header of the metadata file
int pTo = metadata.IndexOf(" clipAnimations:");
for (int x = 0; x <= pTo; x++) {
metadataHeader.Add(metadata[0]);
metadata.RemoveAt(0);
}
//Get all the animation data
for (int i = 0; i <= 50; i++) {
//Get the start and end points of the animation entry
int pFrom = metadata.IndexOf(" - serializedVersion: 16");
pTo = metadata.IndexOf(" additiveReferencePoseFrame: 0");
//Exit when you get the last entry
if (pFrom < 0) break;
//Add the names and animation entries to the respective lists
animNames.Add(metadata[pFrom + 1].Replace(" name: ", ""));
//Save the animation entry and remove from the edited metadata
List<string> animData = new List<string>();
for (int x = 0; x <= pTo - pFrom; x++) {
animData.Insert(0, metadata[pFrom]);
metadata.RemoveAt(pFrom);
}
animations.Add(animData);
}
}
//Overwrite Metadata Button
if (GUILayout.Button("Overwrite Metadata")) {
string savePath = inputPath + "/" + fileName;
using StreamWriter sw = new StreamWriter(savePath, false);
//Copy in the animations
for (int i = animations.Count() - 1; i >= 0; i--) {
foreach (string entry in animations[i])
metadata.Insert(0, entry);
}
//Copy in the header
for (var i = metadataHeader.Count() - 1; i >= 0; i--) {
metadata.Insert(0, metadataHeader[i]);
}
//Write all the lines into the metadata file
for (int i = 0; i < metadata.Count(); i++) {
sw.WriteLine(metadata[i]);
}
Debug.Log("Saved " + savePath);
}
GUILayout.EndHorizontal();
//Refresh Button - updates the inspector
if (GUILayout.Button("Refresh Inspector")) {
AssetDatabase.Refresh();
}
//Lazy spacer
GUILayout.Label("");
//Move Up Button
GUILayout.BeginHorizontal();
if (GUILayout.Button("Move Up")) {
if (selectedAnimationIndex != 0 && selectedAnimationIndex != -1) {
List<string> animBackup = new List<string>();
animBackup = animations[selectedAnimationIndex];
animations.RemoveAt(selectedAnimationIndex);
animations.Insert(selectedAnimationIndex - 1, animBackup);
string nameBackup = animNames[selectedAnimationIndex];
animNames.RemoveAt(selectedAnimationIndex);
animNames.Insert(selectedAnimationIndex - 1, nameBackup);
selectedAnimationIndex -= 1;
}
}
//Move Down Button
if (GUILayout.Button("Move Down")) {
if (selectedAnimationIndex != animations.Count() - 1 && selectedAnimationIndex != -1) {
List<string> animBackup = new List<string>();
animBackup = animations[selectedAnimationIndex];
animations.RemoveAt(selectedAnimationIndex);
animations.Insert(selectedAnimationIndex + 1, animBackup);
string nameBackup = animNames[selectedAnimationIndex];
animNames.RemoveAt(selectedAnimationIndex);
animNames.Insert(selectedAnimationIndex + 1, nameBackup);
selectedAnimationIndex += 1;
}
}
GUILayout.EndHorizontal();
//Selectable Animations List
for (int i = 0; i < animNames.Count(); i++) {
itemStyle = new GUIStyle(GUI.skin.button);
//Set style
Color color_default = GUI.backgroundColor;
Color color_selected = Color.gray;
GUI.changed = false;
itemStyle.alignment = TextAnchor.MiddleLeft;
itemStyle.active.background = itemStyle.normal.background;
itemStyle.margin = new RectOffset(0, 0, 0, 0);
GUI.backgroundColor = (selectedAnimationIndex == i) ? color_selected : Color.clear;
//Change the index when selecting the animation
if (GUILayout.Button(animNames[i], itemStyle)) {
selectedAnimationIndex = i;
}
}
}
//Resets all the data
private void ClearData() {
animNames.Clear();
animations.Clear();
metadataHeader.Clear();
metadata.Clear();
selectedAnimationIndex = -1;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment