How to add new curves or animation events to an imported animation?

The new Animation View in Unity 2.6 can be used to add Animation Curves to material or script properties or to add Animation Events. However, Animation Clips that are imported (from e.g. 3ds Max or Maya) are read-only.

I know it is possible to make a duplicate of an imported clip, and then add extra curves or animation events to that one in the Animation View. Just select the AnimationClip (not the whole imported model, but just the actual clip) and copy and paste it, or select Duplicate from the edit menu.

However then I don't get the updates in the duplicated clip any longer if I modify the original animation in my external animation software like Maya or 3ds Max.

How can I use the duplicate clip to add extra curves but still get the changes I make in the original clip?

You can use an editor script that copies over the curves from the original imported Animation Clip into the duplicated Animation Clip.

Here is such an editor script.

  • Place it in a folder called Editor, located somewhere inside the Assets folder.
  • The script assumes that you have already made a duplicate of the imported clip and called it the same name but with a *copy postfix. For example, if you have an imported clip called MyAnimation, it will search for *MyAnimation_copy*.
  • Select the original imported Animation Clip in the Project View.
  • You can now use the menu Assets -> Transfer Clip Curves to Copy

And the script:

using UnityEditor;
using UnityEngine;
using System.Collections;

public class CurvesTransferer {

    const string duplicatePostfix = "_copy";

    [MenuItem ("Assets/Transfer Clip Curves to Copy")]
    static void CopyCurvesToDuplicate () {
        // Get selected AnimationClip
        AnimationClip imported = Selection.activeObject as AnimationClip;
        if (imported == null) {
            Debug.Log("Selected object is not an AnimationClip");
            return;
        }

        // Find path of copy
        string importedPath = AssetDatabase.GetAssetPath(imported);
        string copyPath = importedPath.Substring(0, importedPath.LastIndexOf("/"));
        copyPath += "/" + imported.name + duplicatePostfix + ".anim";

        // Get copy AnimationClip
        AnimationClip copy = AssetDatabase.LoadAssetAtPath(copyPath, typeof(AnimationClip)) as AnimationClip;
        if (copy == null) {
            Debug.Log("No copy found at "+copyPath);
            return;
        }

        // Copy curves from imported to copy
        AnimationClipCurveData[] curveDatas = AnimationUtility.GetAllCurves(imported, true);
        for (int i=0; i<curveDatas.Length; i++) {
            AnimationUtility.SetEditorCurve(
            	copy,
            	curveDatas*.path,*
 _	curveDatas*.type,*_
 <em>_	curveDatas*.propertyName,*_</em>
 <em><em>_	curveDatas*.curve*_</em></em>
 <em><em>_*);*_</em></em>
 <em><em>_*}*_</em></em>
 <em><em>_*Debug.Log("Copying curves into "+copy.name+" is done");*_</em></em>
 <em><em>_*}*_</em></em>
<em><em>_*}*_</em></em>
<em><em>_*```*_</em></em>

There is more simple variant. This script creates a new animation file itself and makes all copying operations.

`

using UnityEditor;
using UnityEngine;

public class CurvesTransferer
{
const string duplicatePostfix = "_copy";

static void CopyClip(string importedPath, string copyPath)
{
    AnimationClip src = AssetDatabase.LoadAssetAtPath(importedPath, typeof(AnimationClip)) as AnimationClip;
    AnimationClip newClip = new AnimationClip();
    newClip.name = src.name + duplicatePostfix;
    AssetDatabase.CreateAsset(newClip, copyPath);
    AssetDatabase.Refresh();
}

[MenuItem("Assets/Transfer Clip Curves to Copy")]
static void CopyCurvesToDuplicate()
{
    // Get selected AnimationClip
    AnimationClip imported = Selection.activeObject as AnimationClip;
    if (imported == null)
    {
        Debug.Log("Selected object is not an AnimationClip");
        return;
    }

    // Find path of copy
    string importedPath = AssetDatabase.GetAssetPath(imported);
    string copyPath = importedPath.Substring(0, importedPath.LastIndexOf("/"));
    copyPath += "/" + imported.name + duplicatePostfix + ".anim";

    CopyClip(importedPath, copyPath);

    AnimationClip copy = AssetDatabase.LoadAssetAtPath(copyPath, typeof(AnimationClip)) as AnimationClip;
    if (copy == null)
    {
        Debug.Log("No copy found at " + copyPath);
        return;
    }
    // Copy curves from imported to copy
    AnimationClipCurveData[] curveDatas = AnimationUtility.GetAllCurves(imported, true);
    for (int i = 0; i < curveDatas.Length; i++)
    {
        AnimationUtility.SetEditorCurve(
            copy,
            curveDatas*.path,*
 _curveDatas*.type,*_
 <em>_curveDatas*.propertyName,*_</em>
 <em><em>_curveDatas*.curve*_</em></em>
 <em><em>_*);*_</em></em>
 <em><em>_*}*_</em></em>
 <em><em>_*Debug.Log("Copying curves into " + copy.name + " is done");*_</em></em>
<em><em>_*}*_</em></em>
<em><em>_*}*_</em></em>
<em><em>_*```*_</em></em>
<em><em>_*<p>*_</em></em>
<em><em>_*`</p>*_</em></em>

Guys, thanks a lot for the help, very useful, but allow me to say that it's really annoying when you see an apparently cool chunk of code, tries to use it and get a "parsing error". There's a missing semicolon near the top and a missing closing bracket in the end, here goes the fixed version:

using UnityEditor;
using UnityEngine;
using System.Collections;

public class CurvesTransferer
{
const string duplicatePostfix = "_copy";

static void CopyClip(string importedPath, string copyPath)
{
    AnimationClip src = AssetDatabase.LoadAssetAtPath(importedPath, typeof(AnimationClip)) as AnimationClip;
    AnimationClip newClip = new AnimationClip();
    newClip.name = src.name + duplicatePostfix;
    AssetDatabase.CreateAsset(newClip, copyPath);
    AssetDatabase.Refresh();
}

    [MenuItem("Assets/Transfer Clip Curves to Copy")]
    static void CopyCurvesToDuplicate()
    {
        // Get selected AnimationClip
        AnimationClip imported = Selection.activeObject as AnimationClip;
        if (imported == null)
        {
            Debug.Log("Selected object is not an AnimationClip");
            return;
        }

        // Find path of copy
        string importedPath = AssetDatabase.GetAssetPath(imported);
        string copyPath = importedPath.Substring(0, importedPath.LastIndexOf("/"));
        copyPath += "/" + imported.name + duplicatePostfix + ".anim";

        CopyClip(importedPath, copyPath);

        AnimationClip copy = AssetDatabase.LoadAssetAtPath(copyPath, typeof(AnimationClip)) as AnimationClip;
        if (copy == null)
        {
            Debug.Log("No copy found at " + copyPath);
            return;
        }
        // Copy curves from imported to copy
        AnimationClipCurveData[] curveDatas = AnimationUtility.GetAllCurves(imported, true);
        for (int i = 0; i < curveDatas.Length; i++)
        {
            AnimationUtility.SetEditorCurve(
                copy,
                curveDatas*.path,*
 _curveDatas*.type,*_
 <em>_curveDatas*.propertyName,*_</em>
 <em><em>_curveDatas*.curve*_</em></em>
 <em><em>_*);*_</em></em>
 <em><em>_*}*_</em></em>
 <em><em>_*Debug.Log("Copying curves into " + copy.name + " is done");*_</em></em>
 <em><em>_*}*_</em></em>
<em><em>_*}*_</em></em>
<em><em>_*```*_</em></em>

The scripts give me several errors of this type: Assets/Editor/Editor.js(1,6): UCE0001: ‘;’ expected. Insert a semicolon at the end.
Any ideas? Thanks

Guys, I loved this script so much, I went ahead and added a couple of features.

Here is a modified version of MaDDoX’s edit that includes logic for automatically placing the animations into folders.

It first uses an animations folder to contain them all and then if the animation came from an FBX file, it will use the FBX’s name to create a subfolder for those animations. Hopefully, this helps y’all keep things organized.


using UnityEditor;
using UnityEngine;

using System.IO;
using System.Collections;

public class MultipleCurvesTransferer {
const string duplicatePostfix = "Edit";
const string animationFolder = "Animations";

static void CopyClip(string importedPath, string copyPath) {
    AnimationClip src = AssetDatabase.LoadAssetAtPath(importedPath, typeof(AnimationClip)) as AnimationClip;
    AnimationClip newClip = new AnimationClip();
    newClip.name = src.name + duplicatePostfix;
    AssetDatabase.CreateAsset(newClip, copyPath);
    AssetDatabase.Refresh();
}

    [MenuItem("Assets/Transfer Multiple Clips Curves to Copy")]
    static void CopyCurvesToDuplicate()
    {
        // Get selected AnimationClip
        Object[] imported = Selection.GetFiltered(typeof(AnimationClip), SelectionMode.Unfiltered);
        if (imported.Length == 0)
        {
            Debug.LogWarning("Either no objects were selected or the objects selected were not AnimationClips.");
            return;
        }
		
		//If necessary, create the animations folder.
		if (Directory.Exists("Assets/" + animationFolder) == false) {
			AssetDatabase.CreateFolder("Assets", animationFolder);
		}
	
		foreach (AnimationClip clip in imported) {
			
			
			
			string importedPath = AssetDatabase.GetAssetPath(clip);
			
			//If the animation came from an FBX, then use the FBX name as a subfolder to contain the animations.
			string copyPath;
			if (importedPath.Contains(".fbx")) {
				//With subfolder.
				string folder = importedPath.Substring(importedPath.LastIndexOf("/") + 1, importedPath.LastIndexOf(".") - importedPath.LastIndexOf("/") - 1);
				if (!Directory.Exists("Assets/Animations/" + folder)) {
					AssetDatabase.CreateFolder("Assets/Animations", folder);
				}
				copyPath = "Assets/Animations/" + folder + "/" + clip.name + duplicatePostfix + ".anim";
			} else {
				//No Subfolder
				copyPath = "Assets/Animations/" + clip.name + duplicatePostfix + ".anim";
			}
			
			Debug.Log("CopyPath: " + copyPath);
			
	        CopyClip(importedPath, copyPath);
	
	        AnimationClip copy = AssetDatabase.LoadAssetAtPath(copyPath, typeof(AnimationClip)) as AnimationClip;
	        if (copy == null)
	        {
	            Debug.Log("No copy found at " + copyPath);
	            return;
	        }
	        // Copy curves from imported to copy
	        AnimationClipCurveData[] curveDatas = AnimationUtility.GetAllCurves(clip, true);
	        for (int i = 0; i < curveDatas.Length; i++)
	        {
	            AnimationUtility.SetEditorCurve(
	                copy,
	                curveDatas*.path,*

_ curveDatas*.type,_
_ curveDatas.propertyName,
curveDatas.curve*

* );
}*_

* Debug.Log(“Copying curves into " + copy.name + " is done”);*
* }*
}
}
----------

Is it possible to use such logic (get curves/keyframes information)for mecanim animation system?

Hi, this script is so beautiful, but there is some problems in TLoch14’ edit:

1.if (importedPath.Contains(".fbx")) ==> if (importedPath.Contains(".fbx") || importedPath.Contains(".FBX"))

  1.        `AnimationUtility.SetEditorCurve(
                copy,
                curveDatas*.path,*
    

curveDatas*.type,*
curveDatas*.propertyName,*
curveDatas*.curve*
);`
====>

AnimationUtility.SetEditorCurve(*_</em></em> <em><em>_*clip,*_</em></em> <em><em><em><em>EditorCurveBinding.FloatCurve(curveDatas<em>.path, curveDatas_.type, curveDatas*.propertyName),*_</em></em></em></em></em> <em><em><em><em><em><em>_curveDatas*.curve*_</em></em></em></em></em></em> <em><em><em><em><em><em>_*);

----------
OK,perfect!
using UnityEditor;
using UnityEngine;
using System.IO;
using System.Collections;

public class MultipleCurvesTransferer
{
const string duplicatePostfix = “_copy”;
const string animationFolder = “Animations”;

static void CopyClip(string importedPath, string copyPath)
{
AnimationClip src = AssetDatabase.LoadAssetAtPath(importedPath, typeof(AnimationClip)) as AnimationClip;
AnimationClip newClip = new AnimationClip();
newClip.name = src.name + duplicatePostfix;
AssetDatabase.CreateAsset(newClip, copyPath);
AssetDatabase.Refresh();
}

[MenuItem(“Assets/Transfer Multiple Clips Curves to Copy”)]
static void CopyCurvesToDuplicate()
{
// Get selected AnimationClip
Object[] imported = Selection.GetFiltered(typeof(AnimationClip), SelectionMode.Unfiltered);
if (imported.Length == 0)
{
Debug.LogWarning(“Either no objects were selected or the objects selected were not AnimationClips.”);
return;
}

//If necessary, create the animations folder.
if (Directory.Exists(“Assets/” + animationFolder) == false)
{
AssetDatabase.CreateFolder(“Assets”, animationFolder);
}

foreach (AnimationClip clip in imported)
{
string importedPath = AssetDatabase.GetAssetPath(clip);

Debug.Log("clipPath: "+importedPath);

//If the animation came from an FBX, then use the FBX name as a subfolder to contain the animations.
string copyPath;
if (importedPath.Contains(“.fbx”) || importedPath.Contains(“.FBX”))
{
//With subfolder.
string folder = importedPath.Substring(importedPath.LastIndexOf(“/”) + 1, importedPath.LastIndexOf(“.”) - importedPath.LastIndexOf(“/”) - 1);
if (!Directory.Exists(“Assets/Animations/” + folder))
{
AssetDatabase.CreateFolder(“Assets/Animations”, folder);
}
copyPath = “Assets/Animations/” + folder + “/” + clip.name + duplicatePostfix + “.anim”;
}
else
{
//No Subfolder
copyPath = “Assets/Animations/” + clip.name + duplicatePostfix + “.anim”;
}

Debug.Log("CopyPath: " + copyPath);

CopyClip(importedPath, copyPath);

AnimationClip copy = AssetDatabase.LoadAssetAtPath(copyPath, typeof(AnimationClip)) as AnimationClip;
if (copy == null)
{
Debug.Log("No copy found at " + copyPath);
return;
}
// Copy curves from imported to copy
AnimationClipCurveData[] curveDatas = AnimationUtility.GetAllCurves(clip, true);
for (int i = 0; i < curveDatas.Length; i++)
{
AnimationUtility.SetEditorCurve(
clip,
EditorCurveBinding.FloatCurve(curveDatas.path, curveDatas_.type, curveDatas*.propertyName),_
_curveDatas.curve*
);
// AnimationUtility.SetEditorCurve(
// copy,
// curveDatas*.path,
// curveDatas.type,
// curveDatas.propertyName,
// curveDatas.curve*

// );
}
Debug.Log(“Copying curves into " + copy.name + " is done”);
}
}
}_