Last active
May 7, 2024 05:21
-
-
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.
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.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