Creating a UAnimSequence from scratch using AddKeyToSequence()

When populating a UAnimSequence from scratch, is AddKeyToSequence() the correct function to use? I am using a UFactory to open a file (.npz) and extract animation data from it, and then populate a UAnimSequence. My code does what I want it to do, but it is very very slow.

There are two things about UAnimSequence::AddKeyToSequence() that make me think that it’s not what I am supposed to be using. The first is that it tries to AddCurve every time AddKeyToSequence is called. As a result, a warning is thrown every time I add a new key to an existing curve. Since I’m populating a UAnimSequence from scratch, this happens on every bone, on every frame. I’m working with a lot of data, so this is happening a LOT. (A side note is that AnimDataController.cpp uses ReportWarningf() to log it’s warnings and afaik, there’s no way to mute this because it doesn’t use traditional log categories.)

Additionally, UAnimSequence does RequestAnimCompression a lot while using AddKeyToSequence(). I still haven’t figured out a good way to stop it. I modified the source code so that RequestAnimCompression doesn’t do anything and it made my import much faster, but still not fast enough. I’m guessing there are other things that are happening more frequently then they should with UAnimSequence::AddKeyToSequence().

What the correct way to populate a UAnimSequence from text data? If I shouldn’t be using AddKeyToSequence(), what should I be using? If I should be using AddKeyToSequence() what am I doing wrong?

Thanks!

I was able to solve this by modifying the engine code. This is a hack.

I have two versions of the engine on my machine now, one for importing and processing .npz files, and a regular one. The one I’m using for processing, I made the following changes:

  • AnimSequence.cpp - RequestAnimCompression() immediately returns. I’m doing this because this is getting called a lot while importing and slowing down the import process. I import much faster, but the tradeoff is that my animations never build DDC. This is fixed by opening the project in the unmodified unreal branch. It will build the DDCs whenever the animation is needed.
  • AnimDataController.cpp - I commented out the ReportWarningf() that says Curve with name {0} and type {1} ({2}) already exists. This is getting called every single time I AddKeyToSequence(), meaning it’s called for every bone on every keyframe. Since ReportWarningf() doesn’t use a log category, there’s no way (afaik) to mute this without modifying the editor code.
  • AnimSequence.cpp - comment out the IAnimationDataController::FScopedBracket inside AddKeyToSequence(). I’m not sure what this does, but it slows the import down and not having it doesn’t affect my result.
  • AnimDataController.cpp - in SetTransformCurveKey() comment out the CONDITIONAL_BRACKET. In SetCurveKey() comment out all of the CONDITIONAL_TRANSACTION, CONDITIONAL_ACTION, and Model->Notify() statements. Again I don’t know what these do but they slow the import down and don’t affect the end result.

One solution would be for there to be an AddKeysToSequence() function that takes an array of FTransform for a single curve. That way all of this extra stuff would only get called once per curve/bone. It would still be slower than what I did above, but it would be faster. The best solution would be if you only had to call AddKeysToSequence() one time per UAnimSequence, not per bone. Then you’d only have to wait for the DDC to build once per UAnimSequence, which I think is hard to object to.

I created a fork of the 5.1.1 release with my changes in it here: https://github.com/n-baja/UnrealEngine

The unreal source code is private, if you can’t access the link above, go here: Unreal Engine on GitHub - Unreal Engine

Hi! After you added new keys using AddKeyToSequence, have you found a proper way to bake that into the newly created UAnimSequence? I’m using SequenceRecorder and Persona module but it only records TPose, I think I’m missing something but not sure what. Ideally I would have the keyframes into raw data like as if you export previewmesh animation as a new asset from editor.

FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>("Persona");
				TSharedRef<IPersonaToolkit> PersonaToolkit = PersonaModule.CreatePersonaToolkit(NewAnimationSeq);
				ISequenceRecorder& RecorderModule = FModuleManager::Get().LoadModuleChecked<ISequenceRecorder>("SequenceRecorder");
				RecorderModule.RecordSingleNodeInstanceToAnimation(PersonaToolkit->GetPreviewMeshComponent(), NewAnimationSeq);

Unfortunately I’ve never worked with the SequenceRecorder or Persona, so I can’t help you there. What I was doing here was reading joint angles out of a text file and writing them as a UAnimSequence. The joint angles were generated by other software and used in other software and we wanted to use them in unreal.

What I ended up doing was using a UFactory to generate my UAnimSequence by reading from my text file. I think instead of a UFactory, you are using the SequenceRecorder. Here’s the meat and potatoes of my code for this, in case it helps. It is pretty straightforward, except for the setting of the framerate in the animation clip was annoying to figure out. I’m guessing this isn’t an issue with the SequenceRecorder, but in case it is, here’s what I have:

UObject* UNPZ_AssetFactory::FactoryCreateFile(	UClass* InClass, 
												UObject* InParent, 
												FName InName, 
												EObjectFlags Flags, 
												const FString& Filename, 
												const TCHAR* Parms, 
												FFeedbackContext* Warn, 
												bool& bOutOperationCanceled){

...

	UAnimSequence* AnimSequence = NewObject<UAnimSequence>(InParent, InClass, InName, Flags);

	// Notify asset registry
	FAssetRegistryModule::AssetCreated(AnimSequence);

	// Set skeleton (you need to do this before you add animations to it or it will throw an error)
	AnimSequence->ResetAnimation();
	AnimSequence->SetSkeleton(skeleton);
	AnimSequence->ImportFileFramerate = (float)fps;	
	AnimSequence->ImportResampleFramerate = fps;	

	// Setting the framerate has to be done inside the controller.  You can't just set the variables.  
	// I don't know why, I just know that this is how you have to do it. Code comes from here:
	// https://forums.unrealengine.com/t/is-there-any-way-to-modify-bone-tracks-with-c/500162/6

	#if WITH_EDITOR
	#define LOCTEXT_NAMESPACE "UpdateFrameRate"

	// Initialize data model
	// https://docs.unrealengine.com/5.0/en-US/API/Developer/AnimationDataController/UAnimDataController/
	IAnimationDataController& Controller = AnimSequence->GetController();

	Controller.OpenBracket(LOCTEXT("InitializeAnimation", "Initialize New Anim Sequence"));
	{
		// This line is to set actual frame rate
		Controller.SetFrameRate(FFrameRate(fps, 1), true);  //FFrameRate(numberator, denominator)

		Controller.SetPlayLength(double(NumberOfFramesInThisAnimationClip / fps), true);
		Controller.NotifyPopulated();
	}
	Controller.CloseBracket();

	#undef LOCTEXT_NAMESPACE
	#endif


...

	//[loop through file and figure out a time, a bone, and a transform]

			AnimSequence->AddKeyToSequence(CurrentTime, CurrentBone, T);

...

	return AnimSequence;
}