Skip to content

Instantly share code, notes, and snippets.

@geowarin
Created May 5, 2020 12:29
Show Gist options
  • Save geowarin/2542fdabe48674a8518bc0d0da0b4c1e to your computer and use it in GitHub Desktop.
Save geowarin/2542fdabe48674a8518bc0d0da0b4c1e to your computer and use it in GitHub Desktop.
Editor Window UIElements
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Animations;
using UnityEditor.ShortcutManagement;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
// Assets/MixamoImport/Editor/MixamoImportTool.cs
namespace MixamoImport.Editor
{
public class MixamoImportTool : EditorWindow
{
private VisualElement _root;
private static MixamoImportTool _window;
private Avatar _selectedAvatar;
public void OnEnable()
{
_root = rootVisualElement;
var visualTree = Resources.Load<VisualTreeAsset>("MixamoImportTool");
visualTree.CloneTree(_root);
_root.styleSheets.Add(Resources.Load<StyleSheet>("MixamoImportTool"));
_root.Q<Button>("modifyButton").clicked += ProcessSelectedAssets;
_root.Q<Button>("testButton").clicked += AddAnimationToController;
_root.Q<ObjectField>("avatar").RegisterValueChangedCallback(SelectAvatar);
}
private void AddAnimationToController()
{
var selectedAssetsInfo = GetSelectedAssetsInfo();
var directoryName = Path.GetDirectoryName(selectedAssetsInfo.First().assetPath) ??
throw new InvalidOperationException();
var controllersDirectoryPath = Path.Combine(directoryName, "Controllers");
Directory.CreateDirectory(controllersDirectoryPath);
var mixamoControllerPath = controllersDirectoryPath + "/Mixamo.controller";
var controller =
AssetDatabase.LoadAssetAtPath<AnimatorController>(mixamoControllerPath) ??
AnimatorController.CreateAnimatorControllerAtPath(mixamoControllerPath);
var rootStateMachine = controller.layers[0].stateMachine;
var stateNames = rootStateMachine.states.Select(state => state.state.name).ToList();
foreach (var assetInfo in selectedAssetsInfo)
{
var animationClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(assetInfo.assetPath);
if (stateNames.Contains(animationClip.name))
continue;
var animatorState = controller.AddMotion(animationClip);
}
}
private void SelectAvatar(ChangeEvent<Object> evt) => _selectedAvatar = (Avatar) evt.newValue;
private void OnDisable()
{
_root.Q<Button>("modifyButton").clicked -= ProcessSelectedAssets;
_root.Q<Button>("testButton").clicked -= AddAnimationToController;
_root.Q<ObjectField>("avatar").UnregisterValueChangedCallback(SelectAvatar);
}
private void OnGUI()
{
var selectedAssetsInfo = GetSelectedAssetsInfo();
_root.Q<Label>("numObjects").text = $"{selectedAssetsInfo.Count} objects selected";
_root.Q<Label>("avatarName").text = _selectedAvatar != null
? $"{_selectedAvatar.name} selected"
: "One avatar will be created for each animation";
}
private void ProcessSelectedAssets()
{
var selectedAssets = GetSelectedAssetsInfo();
var count = selectedAssets.Count;
for (var i = 0; i < count; i++)
{
var assetInfo = selectedAssets[i];
var assetName = assetInfo.gameObject.name;
EditorUtility.DisplayProgressBar($"Processing {count} files", assetName, (1f / count) * i);
ModifyModelImporter(assetInfo);
}
EditorUtility.ClearProgressBar();
}
private void ModifyModelImporter(AssetInfo assetInfo)
{
var assetName = assetInfo.gameObject.name;
var modelImporter = assetInfo.modelImporter;
ModelImporterClipAnimation[] clipAnimations = modelImporter.defaultClipAnimations;
// Rig => Humanoid
modelImporter.animationType = ModelImporterAnimationType.Human;
// Change avatar
if (_selectedAvatar != null)
{
modelImporter.sourceAvatar = _selectedAvatar;
}
else
{
modelImporter.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel;
}
var parentDirectory = Path.GetDirectoryName(assetInfo.assetPath) ?? throw new InvalidOperationException();
if (assetInfo.isModel)
{
var textureDirectoryPath = Path.Combine(parentDirectory, "Textures");
Directory.CreateDirectory(textureDirectoryPath);
modelImporter.ExtractTextures(textureDirectoryPath);
}
assetName = assetName.Replace(' ', '_').ToLower();
// change names
foreach (var clip in clipAnimations)
{
clip.name = assetName;
}
modelImporter.clipAnimations = clipAnimations;
modelImporter.SaveAndReimport();
var animationClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(assetInfo.assetPath);
ExtractAnimationClip(animationClip, parentDirectory + "/" + assetName + ".anim");
}
private static void ExtractAnimationClip(AnimationClip orgClip, string destinationPath)
{
AnimationClip placeClip = new AnimationClip();
if (!Resources.Load(destinationPath))
{
EditorUtility.CopySerialized(orgClip, placeClip);
AssetDatabase.CreateAsset(placeClip, destinationPath);
AssetDatabase.Refresh();
}
}
struct AssetInfo
{
public string assetPath;
public bool isModel;
public GameObject gameObject;
public ModelImporter modelImporter;
}
private static List<AssetInfo> GetSelectedAssetsInfo()
{
var selectedUniqueAssetPaths = Selection.gameObjects
.GroupBy(AssetDatabase.GetAssetPath)
.Select(g => g.Key)
.ToList();
var assets = selectedUniqueAssetPaths
.Select(assetPath => new AssetInfo
{
assetPath = assetPath,
isModel = AssetDatabase.LoadAssetAtPath<Mesh>(assetPath) != null,
gameObject = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath),
modelImporter = (ModelImporter) AssetImporter.GetAtPath(assetPath)
})
// Models first
.OrderByDescending(a => a.isModel)
.ToList();
return assets;
}
#region Show Window
// [MenuItem("My Tools/Mixamo import")]
// [Shortcut("Refresh Window", KeyCode.F9)]
// public static void ShowExample()
// {
// if (_window) _window.Close();
//
// _window = GetWindow<MixamoImportTool>();
// _window.minSize = new Vector2(350, 200);
// _window.titleContent = new GUIContent("Mixamo import");
// }
#endregion
}
}
# Assets/MixamoImport/Resources/MixamoImportTool.uss
.row {
display: flex;
flex-direction: row;
justify-content: center;
}
.header {
font-size: 24px;
color: #11ff00;
}
Label {
font-size: 12px;
}
<!-- Assets/MixamoImport/Resources/MixamoImportTool.uxml -->
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements">
<ui:VisualElement class ="row">
<ui:Label text="Import Animations" class="header"/>
</ui:VisualElement>
<ui:Label name="numObjects" />
<uie:ObjectField name="avatar" label="Avatar" type="UnityEngine.Avatar, UnityEngine" binding-path="avatar" allow-scene-objects="false"/>
<ui:Label name="avatarName" />
<ui:Button text="Modify animations" name="modifyButton" />
<ui:Button text="Test" name="testButton" />
</UXML>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment