Search Unity

Editor script for editing animation settings after import

Discussion in 'Editor & General Support' started by JD3D, Jan 15, 2013.

  1. JD3D

    JD3D

    Joined:
    Oct 25, 2012
    Posts:
    9
    I have been coming back to this problem every so often after Unity 4 was released. I am trying write a tool to quickly change the animation settings on a bunch of humanoid animations after they already have been imported so I do not have to manually change it per file, which is a really slow and tediously annoying process. Here is a snippet of code I am using.

    ModelImporter modelImporter = AssetImporter.GetAtPath(fileLocation) as ModelImporter;
    ModelImporterClipAnimation _animationClip = new ModelImporterClipAnimation();
    ModelImporterClipAnimation[] _oldAnimationClip = modelImporter.clipAnimations;
    _animationClip.firstFrame = _oldAnimationClip[0].firstFrame;
    _animationClip.lastFrame = _oldAnimationClip[0].lastFrame;
    _animationClip.name = _oldAnimationClip[0].name;
    _animationClip.takeName = _oldAnimationClip[0].takeName;
    _animationClip.keepOriginalOrientation = true;
    _animationClip.keepAdditionalBonesAnimation = true;
    _animationClip.keepOriginalPositionY = true;
    _animationClip.lockRootHeightY = true;
    _animationClip.lockRootRotation = true;
    ModelImporterClipAnimation[] _clipAnimations = new ModelImporterClipAnimation[1];
    _clipAnimations[0] = _animationClip;
    modelImporter.clipAnimations = _clipAnimations;

    Once in a while it will actually capture the old animation information I need but most of the time this line:

    ModelImporterClipAnimation[] _oldAnimationClip = modelImporter.clipAnimations;

    returns null and I am trying to figure out why and haven't figured anything out.

    Sidenote:
    fileLocation comes from a selection of files I want to edit.
     
  2. Chunks

    Chunks

    Joined:
    Jul 30, 2012
    Posts:
    4
    If you've never changed any of the settings on the imported clip and hit "Apply," the clip is inaccessible via script. You can see this by setting your meta files to be ASCII and looking at the clips listed in the .meta file for the imported animation. No changes, no clips. The reason it's working on a few of your animations is because you've made some kind of edit to that clip at some time in the past, so the .meta file shows existing clips for you to access.

    If someone knows a workaround to this stupid behavior, I'd love to hear it.
     
  3. AlkisFortuneFish

    AlkisFortuneFish

    Joined:
    Apr 26, 2013
    Posts:
    973
    Ugh, I had not noticed that, presumably because all the custom scripts I've made were used on stuff we'd already edited before. I'll see if I can find some way to force it.

    Either way, once you get that to work, you'll see some other annoying behaviour, if you assign clipAnimations, having made changes, all currently set events are lost. The same thing happens if you change the avatar mask source and apply. I've posted a workaround here.
     
  4. AlkisFortuneFish

    AlkisFortuneFish

    Joined:
    Apr 26, 2013
    Posts:
    973
    Right, I cannot figure out how to force it to load those clips via script but there is a rather ridiculous workaround, if you are so inclined.

    The clips are created by ModelImporterClipEditor's SetupDefaultClips, which makes some AnimationClipInfoProperties with default settings out of the FBX's TakeInfo array.

    The implementation can be seen in the Assembly Browser and there's nothing stopping us from adapting this function to write some ModelImporterAnimationClips instead.

    Code (csharp):
    1.  
    2. ModelImporterClipAnimation[] SetupDefaultClips (TakeInfo[] importedTakeInfos)
    3. {
    4.     ModelImporterClipAnimation[] clips = new ModelImporterClipAnimation[importedTakeInfos.Length];
    5.  
    6.     for (int i = 0; i < importedTakeInfos.Length; i++)
    7.     {
    8.         TakeInfo takeInfo = importedTakeInfos [i];
    9.  
    10.         ModelImporterClipAnimation mica = new ModelImporterClipAnimation();
    11.         mica.name = MakeUniqueClipName (clips, takeInfo.defaultClipName, -1);
    12.  
    13.         mica.takeName = takeInfo.name;
    14.         mica.firstFrame = (float)((int)Mathf.Round (takeInfo.bakeStartTime * takeInfo.sampleRate));
    15.         mica.lastFrame = (float)((int)Mathf.Round (takeInfo.bakeStopTime * takeInfo.sampleRate));
    16.  
    17.         mica.maskType = ClipAnimationMaskType.CreateFromThisModel;
    18.         mica.keepOriginalPositionY = true;
    19.  
    20.         clips[i] = mica;
    21.     }
    22.     return clips;
    23. }
    24.  
    25. string MakeUniqueClipName (ModelImporterClipAnimation[] clips, string name, int row)
    26. {
    27.     string text = name;
    28.     int num = 0;
    29.     int i;
    30.     do
    31.     {
    32.         for (i = 0; i < clips.Length; i++)
    33.         {
    34.             if (clips[i] != null  text == clips[i].name  row != i)
    35.             {
    36.                 text = name + num.ToString ();
    37.                 num++;
    38.                 break;
    39.             }
    40.         }
    41.     }
    42.     while (i != clips.Length);
    43.     return text;
    44. }
    45.  
    Note that this is missing some AvatarMask handling but from what I can tell, ModelImporter sorts that out once clipAnimations are assigned, fingers crossed.

    The TakeInfo objects can be taken from ModelImporter's importerTakeInfos internal property, which can be accessed via reflection.

    Code (csharp):
    1.  
    2. PropertyInfo prop = typeof(ModelImporter).GetProperty("importedTakeInfos", BindingFlags.NonPublic | BindingFlags.Instance);
    3. TakeInfo[] ti = (TakeInfo[]) prop.GetValue(modelImporter, null);
    4.  
    5. modelImporter.clipAnimations = SetupDefaultClips(ti);
    6.  
    7. AssetDatabase.WriteImportSettingsIfDirty(path);
    8. AssetDatabase.SaveAssets();
    9.  
    Now, whether you would want to use such a hack is a different story, I had fun figuring it out anyway! :p
     
  5. JD3D

    JD3D

    Joined:
    Oct 25, 2012
    Posts:
    9
    Wow, restumbled across my post as my new project has a lot of animations coming down the pipe and it was getting really tedious setting up each animation setting by hand. Never expected to finally get a solution to this. Thanks for finding this thread after a year and bringing it back up Chunks. And thanks AlkisFortuneFish for providing a solution to this problem. It works as I need it too. Now to figure out how to assign the character's avatar as the source Avatar for my animations.
     
  6. mmerchante

    mmerchante

    Joined:
    Oct 4, 2013
    Posts:
    1
    Hey guys, I've been dealing with a similar script for quite some time, and had a lot of problems with humanoid rigs with additional animated bones (cloth, tentacles, etc.), and wanted to tell how I solved it in case anyone needs it.

    Basically my problem was that Unity does something weird with AvatarMasks when importing clips, and visually (in the clip inspector) all additional transforms are checked as if they are included, but when the character is animated none of those bones move. I tried to fix it creating an AvatarMask in the importer script and activating the transformPaths, but Unity seems to be resetting that mask later, which overrides any change you want to make through the importer.

    If you manually uncheck, apply, and then check again any of the transforms, it somehow works fixes itself, but it is tedious and not very user-friendly.

    To be more concise, the following code seems to do nothing:

    Code (csharp):
    1.  
    2.             mica.maskType = ClipAnimationMaskType.CreateFromThisModel;
    3.             mica.maskSource = new UnityEditorInternal.AvatarMask();
    4.             mica.maskSource.transformCount = transformPaths.Length;
    5.            
    6.             for(int j = 0; j < transformPaths.Length; j++)
    7.             {
    8.                 mica.maskSource.SetTransformPath(j, transformPaths[j]);
    9.                 mica.maskSource.SetTransformActive(j, true);
    10.             }
    11.  
    So for now, the solution I've found is just creating manually an AvatarMask from the source rig with each additional bone and assigning it to each clip, it's way faster than fixing each transform manually :)

    EDIT: Depending on your workflow, you can automate the creation of an avatar mask for your characters when it is a model, and then assign it when it is an animation.
     
    Last edited: May 16, 2014
  7. AlkisFortuneFish

    AlkisFortuneFish

    Joined:
    Apr 26, 2013
    Posts:
    973
    Hey, I found what is causing that, the actual importer calls ModelImporter's internal UpdateTransformMask, which is a static method exposing a native call in the C++ side of things. Grabbing it via reflection and calling it sorts this out!.

    Code (csharp):
    1. Type modelImporterType = typeof(ModelImporter);
    2. MethodInfo updateTransformMaskMethodInfo = modelImporterType.GetMethod("UpdateTransformMask", BindingFlags.NonPublic | BindingFlags.Static);
    3.  
    4. SerializedProperty clipAnimationsProperty = serializedObject.FindProperty("m_ClipAnimations");
    5.  
    6. for (int j = 0; j < clipAnimations.Length; j++)
    7. {
    8.     SerializedProperty transformMaskProperty = clipAnimationsProperty.GetArrayElementAtIndex(j).FindPropertyRelative("transformMask");
    9.     updateTransformMaskMethodInfo.Invoke(modelImporter, new System.Object[]{AvatarMask, transformMaskProperty});
    10. }
    11.  
    12. serializedObject.ApplyModifiedProperties();
     
    Last edited: Jun 10, 2014
    fleity likes this.
  8. Hey, we we're having similar problems and I stumbled upon this thread. I later found out that since Unity 5.0, you have access to ModelImporter.defaultClipAnimations.

    I created this function and it has worked for our needs.
    Code (csharp):
    1.  
    2.     static ModelImporterClipAnimation GetModelImporterClip(ModelImporter mi)
    3.     {
    4.         ModelImporterClipAnimation clip = null;
    5.         if( mi.clipAnimations.Length == 0 )
    6.         {
    7.             //if the animation was never manually changed and saved, we get here. Check defaultClipAnimations
    8.             if( mi.defaultClipAnimations.Length > 0 )
    9.             {
    10.                 clip = mi.defaultClipAnimations[0];
    11.             }
    12.             else
    13.             {
    14.                 Debug.LogError("GetModelImporterClip can't find clip information");
    15.             }
    16.         }
    17.         else
    18.         {
    19.             clip = mi.clipAnimations[0];
    20.         }
    21.         return clip;
    22.     }
    23.  
     
  9. fleity

    fleity

    Joined:
    Oct 13, 2015
    Posts:
    345
    Sigh 2023 here, this is still useful but incomplete. It only works for non-humanoid parts of the mask. In order to get the complete behaviour I tried to see what ModelImporterClipEditor.cs does.

    AvatarMaskUtility.UpdateTransformMask(clipInfo.transformMaskProperty, transformPaths, humanTransforms, animationType == ModelImporterAnimationType.Human);

    But that didn't workout at all in the end I reverse enginered what was missing from the meta file. You need to set the bodyMask integer array property:
    https://forum.unity.com/threads/anim-clip-update-mask-from-script.1517653/
     
    Last edited: Nov 17, 2023
    AlkisFortuneFish likes this.
  10. AlkisFortuneFish

    AlkisFortuneFish

    Joined:
    Apr 26, 2013
    Posts:
    973
    Sigh indeed, the fact that any of this is still at all useful in 2023 is actually quite absurd!