Need help with constructing an UAnimSequence from code

I have been struggling with a problem lately, and have been looking for solutions in the documentation and elsewhere on the forums, but haven’t encountered any information about my problem, so I was hoping someone could help me out with this.

I am trying to make an automated tool that can take multiple sections of an animation and make it into separate animation clips.

So far I have managed to make a function that creates a new animation file from an existing animation with the correct length and in theory the animation should be in there too, but the character is just frozen unless I change the AdditiveAnimType to something besides No additive. This isn’t really a solution, as I need to get it to work under normal playback.

I assume there is something I should be doing in the function that I am currently not doing.
So I provide the code I use for the function below so you can have a look and see if you can find what I am doing wrong.

UAnimSequenceBase* UAnimLibrary::GenerateAnimationSegment(UAnimSequenceBase* Anim, FFloatInterval Segment, FString AssetPath)
{
	if(!IsValid(Anim) || Segment.Size() <= 0)
		return nullptr;

	int Start = Anim->GetFrameAtTime(Segment.Min);
	int End = Anim->GetFrameAtTime(Segment.Max);

	auto Factory = NewObject<UAnimSequenceFactory>();
	Factory->TargetSkeleton = Anim->GetSkeleton();

	FString AssetName = FPaths::GetBaseFilename(AssetPath);

	FString FinalPackageName = AssetPath;
	UPackage* Package = CreatePackage(*FinalPackageName);
	UPackage* OutermostPkg = Package->GetPackage();

	auto Sequence = Cast<UAnimSequence>(Factory->FactoryCreateNew(UAnimSequence::StaticClass(), OutermostPkg, *AssetName, RF_Standalone | RF_Public, NULL, GWarn));

	auto& Controller = Sequence->GetController();
	auto DataModel = Sequence->GetDataModel();

	Sequence->MarkPackageDirty();
	Package->SetDirtyFlag(true);

	auto FrameRate = Anim->GetSamplingFrameRate();
	Controller.SetPlayLength(FrameRate.AsInterval() * (End - Start));

	if(auto AnimData = Anim->GetDataModel())
	{
		auto& BoneTracks = AnimData->GetBoneAnimationTracks();
		auto& SequenceBoneTracks = *const_cast<TArray<FBoneAnimationTrack>*>(&DataModel->GetBoneAnimationTracks());

		FBoneAnimationTrack NewTrack;

		for(auto& BoneTrack : BoneTracks)
		{
			NewTrack.BoneTreeIndex = BoneTrack.BoneTreeIndex;
			NewTrack.Name = BoneTrack.Name;

			NewTrack.InternalTrackData.PosKeys.Reset();
			NewTrack.InternalTrackData.RotKeys.Reset();
			NewTrack.InternalTrackData.ScaleKeys.Reset();


			NewTrack.InternalTrackData.PosKeys.Reserve(End - Start);
			auto PosData = BoneTrack.InternalTrackData.PosKeys.GetData();
			NewTrack.InternalTrackData.PosKeys.Append(PosData + Start, End - Start);

			NewTrack.InternalTrackData.RotKeys.Reserve(End - Start);
			auto RotData = BoneTrack.InternalTrackData.RotKeys.GetData();
			NewTrack.InternalTrackData.RotKeys.Append(RotData + Start, End - Start);

			NewTrack.InternalTrackData.ScaleKeys.Reserve(End - Start);
			auto ScaleData = BoneTrack.InternalTrackData.ScaleKeys.GetData();
			NewTrack.InternalTrackData.ScaleKeys.Append(ScaleData + Start, End - Start);

			SequenceBoneTracks.Add(NewTrack);
		}

		Controller.SetFrameRate(FrameRate, true);
		DataModel->GetModifiedEvent().Broadcast(EAnimDataModelNotifyType::Populated, DataModel, FAnimDataModelNotifPayload());

		TSharedPtr<FAnimCompressContext> CompressContext = MakeShareable(new FAnimCompressContext(false, false));

		Sequence->RequestAnimCompression(FRequestAnimCompressionParams(false, CompressContext));
		Sequence->PostEditChange();
		Sequence->AddToRoot();

		FAssetRegistryModule::AssetCreated(Sequence);

		return Sequence;
	}

	return nullptr;
}

I did some more digging and found a different solution to my problem by copying the UFbxAnimSequenceImportData of the original and modifying the FrameImportRange setting to the segment I needed and reimporting the file afterwards. It has it’s limitations like it only working for animations imported as fbx, but it is good enough for our use at the moment.

I wrote example code below if anyone else runs into a similar problem.

UAnimSequence* UAnimLibrary::GenerateAnimationSegment(UAnimSequence* Anim, FFloatInterval Segment, FString AssetPath)
{
	if (!IsValid(Anim) || Segment.Size() <= 0)
		return nullptr;

	if (auto FBXData = Cast<UFbxAnimSequenceImportData>(Anim->AssetImportData))
	{
		if (auto Factory = NewObject<UAnimSequenceFactory>())
		{
			Factory->TargetSkeleton = Anim->GetSkeleton();
		
			FString AssetName = FPaths::GetBaseFilename(AssetPath);

			FString FinalPackageName = AssetPath;
			UPackage* Package = CreatePackage(*FinalPackageName);
			UPackage* OutermostPkg = Package->GetPackage();
		
			if (auto Sequence = Cast<UAnimSequence>(Factory->FactoryCreateNew(UAnimSequence::StaticClass(), OutermostPkg, *AssetName, RF_Standalone | RF_Public, NULL, GWarn)))
			{
				int Start = Anim->GetFrameAtTime(Segment.Min);
				int End = Anim->GetFrameAtTime(Segment.Max);
			
				auto NewSequenceData = DuplicateObject(FBXData, Sequence);
				NewSequenceData->FrameImportRange = FInt32Interval(Start, End);
				NewSequenceData->AnimationLength = EFBXAnimationLengthImportType::FBXALIT_SetRange;

				Sequence->AssetImportData = NewSequenceData;
				FAssetRegistryModule::AssetCreated(Sequence);

				if(FReimportManager::Instance()->Reimport(Sequence, false, true, "", nullptr, -1, false, true))
					return Sequence;

				FAssetRegistryModule::AssetDeleted(Sequence);
				Sequence->MarkAsGarbage();
			}
		}
	}
}

Still don’t know what I did wrong when generating UAnimSequence data manually though, so I would appreciate if someone could explain it to me or direct me to some documentation on it somewhere as it irks me to not know the solution.

1 Like