Skip to content

Instantly share code, notes, and snippets.

@SolarianZ
Last active May 7, 2024 05:21
Show Gist options
  • Save SolarianZ/c0237d4b6dc3d2133d2c1bf6c9df2a5d to your computer and use it in GitHub Desktop.
Save SolarianZ/c0237d4b6dc3d2133d2c1bf6c9df2a5d to your computer and use it in GitHub Desktop.
{"category": "Unity Engine/Editor/Extensions", "keywords": "Unity, Editor, AnimationClip, Curve, Bind, PropertyStreamHandle, RootMotion, Pose"} AnimationClip extensions for process curves.
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
using UDebug = UnityEngine.Debug;
public static class AnimationClipCurveTool
{
#region Playable Binding
[MenuItem("Tools/Bamboo/Animation/Print Property Stream Bindable Curve Names of Selected AnimationClips")]
[MenuItem("Assets/Bamboo/Animation/Print Property Stream Bindable Curve Names")]
public static void PrintPropertyStreamBindableCurveNamesOfSelectedAnimationClips()
{
bool hasAnimation = false;
Object[] objs = Selection.objects;
foreach (Object obj in objs)
{
if (obj is AnimationClip clip)
{
hasAnimation = true;
List<string> curveNames = clip.GetPropertyStreamBindableCurveNames();
if (curveNames.Count == 0)
{
UDebug.LogError($"No property stream bindable curve in AnimationClip '{clip.name}'.", clip);
break;
}
StringBuilder builder = new StringBuilder();
builder.AppendLine($"Property stream bindable curve names of AnimationClip '{clip.name}':");
for (int i = 0; i < curveNames.Count; i++)
{
string curveName = curveNames[i];
builder.Append(curveName);
if (i < objs.Length - 1)
{
builder.Append(", ");
}
}
UDebug.LogError(builder.ToString(), clip);
}
}
if (!hasAnimation)
{
UDebug.LogError("Print bindable curve names failed. No AnimationClip selected.");
}
}
public static readonly string[] PropertyStreamBindingExcludedPropertyNames = new string[] {
"RootT.x", "RootT.y", "RootT.z",
"RootQ.x", "RootQ.y", "RootQ.z", "RootQ.w",
"LeftFootT.x", "LeftFootT.y", "LeftFootT.z",
"LeftFootQ.x", "LeftFootQ.y", "LeftFootQ.z", "LeftFootQ.w",
"RightFootT.x", "RightFootT.y", "RightFootT.z",
"RightFootQ.x", "RightFootQ.y", "RightFootQ.z", "RightFootQ.w",
"LeftHandT.x", "LeftHandT.y", "LeftHandT.z",
"LeftHandQ.x", "LeftHandQ.y", "LeftHandQ.z", "LeftHandQ.w",
"RightHandT.x", "RightHandT.y", "RightHandT.z",
"RightHandQ.x", "RightHandQ.y", "RightHandQ.z", "RightHandQ.w",
};
/// <summary>
/// Get the names of the curves in the AnimationClip that can be bound to the PropertyStreamHandle.
/// </summary>
/// <param name="clip"></param>
/// <returns></returns>
public static List<string> GetPropertyStreamBindableCurveNames(this AnimationClip clip)
{
List<string> curveNames = new List<string>();
List<string> muscleNames = new List<string>(HumanTrait.MuscleName);
EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(clip);
foreach (var binding in curveBindings)
{
if (!string.IsNullOrEmpty(binding.path))
{
continue;
}
if (muscleNames.Contains(binding.propertyName))
{
continue;
}
if (PropertyStreamBindingExcludedPropertyNames.Contains(binding.propertyName))
{
continue;
}
const string LeftHandPrefix = "LeftHand.";
if (binding.propertyName.StartsWith(LeftHandPrefix))
{
string propertyName = "Left " + binding.propertyName.Substring(LeftHandPrefix.Length).Replace('.', ' ');
if (muscleNames.Contains(propertyName))
{
continue;
}
}
const string RightHandPrefix = "RightHand.";
if (binding.propertyName.StartsWith(RightHandPrefix))
{
string propertyName = "Right " + binding.propertyName.Substring(RightHandPrefix.Length).Replace('.', ' ');
if (muscleNames.Contains(propertyName))
{
continue;
}
}
curveNames.Add(binding.propertyName);
}
return curveNames;
}
#endregion
#region Motion and Pose
[MenuItem("Tools/Bamboo/Animation/Extract Motion and Pose of Selected AnimationClips")]
[MenuItem("Assets/Bamboo/Animation/Extract Motion and Pose")]
public static void ExtractMotionAndPoseOfSelectedAnimationClips()
{
bool hasAnimation = false;
Object[] objs = Selection.objects;
foreach (Object obj in objs)
{
if (obj is AnimationClip clip)
{
hasAnimation = true;
clip.ExtractMotionAndPose();
}
}
if (!hasAnimation)
{
UDebug.LogError("Extract motion and pose failed. No AnimationClip selected.");
}
}
public static readonly string[] RootMotionPropertyNames = new string[]
{
"RootT.x", "RootT.y", "RootT.z",
"RootQ.x", "RootQ.y", "RootQ.z", "RootQ.w",
"m_LocalPosition.x", "m_LocalPosition.y", "m_LocalPosition.z",
"m_LocalRotation.x", "m_LocalRotation.y", "m_LocalRotation.z", "m_LocalRotation.w"
};
public static void ExtractMotionAndPose(this AnimationClip srcClip)
{
AnimationClip motionClip = new AnimationClip();
motionClip.name = srcClip.name + "_Motion";
srcClip.CopyPropertiesTo(motionClip);
AnimationClip poseClip = new AnimationClip();
poseClip.name = srcClip.name + "_Pose";
srcClip.CopyPropertiesTo(poseClip);
//bool hasRootMotion = (bool)typeof(AnimationClip)
// .GetProperty("hasRootMotion", BindingFlags.Instance | BindingFlags.NonPublic)
// .GetValue(srcClip);
//UDebug.Log($"hasRootCurves={srcClip.hasRootCurves}, hasRootMotion={hasRootMotion}");
EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(srcClip);
for (int i = 0; i < bindings.Length; i++)
{
EditorCurveBinding binding = bindings[i];
if (string.IsNullOrEmpty(binding.path) && RootMotionPropertyNames.Contains(binding.propertyName))
{
//UDebug.Log($"MotionCurve: type={binding.type}, propertyName={binding.propertyName}");
AnimationCurve motionCurve = AnimationUtility.GetEditorCurve(srcClip, binding);
motionClip.SetCurve(binding.path, binding.type, binding.propertyName, motionCurve);
continue;
}
//UDebug.Log($"PoseCurve: type={binding.type}, path={binding.path}, propertyName={binding.propertyName}");
AnimationCurve poseCurve = AnimationUtility.GetEditorCurve(srcClip, binding);
poseClip.SetCurve(binding.path, binding.type, binding.propertyName, poseCurve);
}
string motionPath, posePath;
string srcPath = AssetDatabase.GetAssetPath(srcClip);
if (srcPath.EndsWith(".anim"))
{
motionPath = srcPath.Replace(".anim", "_Motion.anim");
posePath = srcPath.Replace(".anim", "_Pose.anim");
}
else
{
int dotIndex = srcPath.LastIndexOf('.');
motionPath = srcPath.Remove(dotIndex) + "_Motion.anim";
posePath = srcPath.Remove(dotIndex) + "_Pose.anim";
}
AssetDatabase.CreateAsset(motionClip, motionPath);
AssetDatabase.CreateAsset(poseClip, posePath);
AssetDatabase.Refresh();
UDebug.Log($"Extract motion and pose from {srcClip.name} to {motionClip.name} and {poseClip.name}.");
}
public static void CopyPropertiesTo(this AnimationClip srcClip, AnimationClip destClip)
{
destClip.frameRate = srcClip.frameRate;
destClip.wrapMode = srcClip.wrapMode;
destClip.localBounds = srcClip.localBounds;
destClip.legacy = srcClip.legacy;
AnimationClipSettings srcSettings = AnimationUtility.GetAnimationClipSettings(srcClip);
AnimationUtility.SetAnimationClipSettings(destClip, srcSettings);
}
#endregion
[MenuItem("Tools/Bamboo/Animation/Print Property Stream Bindable Curve Names of Selected AnimationClips", validate = true)]
[MenuItem("Assets/Bamboo/Animation/Print Property Stream Bindable Curve Names", validate = true)]
[MenuItem("Tools/Bamboo/Animation/Extract Motion and Pose of Selected AnimationClips", validate = true)]
[MenuItem("Assets/Bamboo/Animation/Extract Motion and Pose", validate = true)]
private static bool IsAnyAnimationClipSelected()
{
Object[] objs = Selection.objects;
foreach (Object obj in objs)
{
if (obj is AnimationClip)
{
return true;
}
}
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment