Files
UnrealEngineUWP/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifier.cpp
julien stjean e3fb173774 Fixed Static analysis issues in interchange (fbxmesh.h) by changing the function GetMeshUniqueID from FbxHelper to return a empty string when the mesh is empty.
[FYI] Richard.TalbotWatkings

Fixed Static analysis in AnimationModifier.cpp the CurrentAnimSequence ptr can't be null since it was deferenced at the line 75.

Fixed Static analysis in PackageStoreOptimizer for some reason the static analysis did like the way the check was presented.

Fixed Static analysis in SComponentClassCombo.cpp and SDisplayClusterConfiguratorComponentCombo.cpp changed the code so that the static analyser should understand it better.

#jira UE-120410
#rb Jean.MichelDignard

#ROBOMERGE-SOURCE: CL 17071360 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v853-17066230)

[CL 17071396 by julien stjean in ue5-release-engine-test branch]
2021-08-05 13:15:23 -04:00

385 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AnimationModifier.h"
#include "AssetViewUtils.h"
#include "Animation/AnimSequence.h"
#include "Animation/Skeleton.h"
#include "ModifierOutputFilter.h"
#include "ScopedTransaction.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Editor/Transactor.h"
#include "UObject/UObjectIterator.h"
#include "UObject/ReleaseObjectVersion.h"
#include "Misc/MessageDialog.h"
#include "Editor/Transactor.h"
#include "UObject/UObjectIterator.h"
#include "UObject/AnimObjectVersion.h"
#define LOCTEXT_NAMESPACE "AnimationModifier"
int32 UE::Anim::FApplyModifiersScope::ScopesOpened = 0;
TMap<FObjectKey, TOptional<EAppReturnType::Type>> UE::Anim::FApplyModifiersScope::PerClassReturnTypeValues;
TOptional<EAppReturnType::Type> UE::Anim::FApplyModifiersScope::GetReturnType(const UAnimationModifier* InModifier)
{
TOptional<EAppReturnType::Type>* ReturnTypePtr = PerClassReturnTypeValues.Find(FObjectKey(InModifier->GetClass()));
return ReturnTypePtr ? *ReturnTypePtr : TOptional<EAppReturnType::Type>();
}
void UE::Anim::FApplyModifiersScope::SetReturnType(const UAnimationModifier* InModifier, EAppReturnType::Type InReturnType)
{
const FObjectKey Key(InModifier->GetClass());
ensure(!PerClassReturnTypeValues.Contains(Key));
PerClassReturnTypeValues.Add(Key, InReturnType);
}
const FName UAnimationModifier::RevertModifierObjectName("REVERT_AnimationModifier");
UAnimationModifier::UAnimationModifier()
: PreviouslyAppliedModifier(nullptr)
{
}
void UAnimationModifier::ApplyToAnimationSequence(class UAnimSequence* InAnimationSequence)
{
FEditorScriptExecutionGuard ScriptGuard;
CurrentAnimSequence = InAnimationSequence;
checkf(CurrentAnimSequence, TEXT("Invalid Animation Sequence supplied"));
CurrentSkeleton = InAnimationSequence->GetSkeleton();
// Filter to check for warnings / errors thrown from animation blueprint library (rudimentary approach for now)
FCategoryLogOutputFilter OutputLog;
OutputLog.SetAutoEmitLineTerminator(true);
OutputLog.AddCategoryName("LogAnimationBlueprintLibrary");
OutputLog.AddCategoryName("LogAnimation");
GLog->AddOutputDevice(&OutputLog);
// Transact the modifier to prevent instance variables/data to change during applying
FTransaction ModifierTransaction;
ModifierTransaction.SaveObject(this);
FTransaction AnimationDataTransaction;
AnimationDataTransaction.SaveObject(CurrentAnimSequence);
AnimationDataTransaction.SaveObject(CurrentSkeleton);
/** In case this modifier has been previously applied, revert it using the serialised out version at the time */
if (PreviouslyAppliedModifier)
{
PreviouslyAppliedModifier->Modify();
PreviouslyAppliedModifier->OnRevert(CurrentAnimSequence);
}
IAnimationDataController& Controller = CurrentAnimSequence->GetController();
{
IAnimationDataController::FScopedBracket ScopedBracket(Controller, LOCTEXT("ApplyModifierBracket", "Applying Animation Modifier"));
/** Reverting and applying, populates the log with possible warnings and or errors to notify the user about */
OnApply(CurrentAnimSequence);
}
// Apply transaction
ModifierTransaction.BeginOperation();
ModifierTransaction.Apply();
ModifierTransaction.EndOperation();
GLog->RemoveOutputDevice(&OutputLog);
// Check if warnings or errors have occurred and show dialog to user to inform her about this
const bool bWarnings = OutputLog.ContainsWarnings();
const bool bErrors = OutputLog.ContainsErrors();
bool bShouldRevert = bErrors;
// If errors have occured - prompt user with OK and revert
TOptional<EAppReturnType::Type> ScopeReturnType = UE::Anim::FApplyModifiersScope::GetReturnType(this);
static const FText MessageTitle = LOCTEXT("ModifierDialogTitle", "Modifier has generated errors during test run.");
if (bErrors)
{
if (UE::Anim::FApplyModifiersScope::ScopesOpened == 0 || !ScopeReturnType.IsSet() || ScopeReturnType.GetValue() != EAppReturnType::Type::Ok)
{
const FText ErrorMessageFormat = FText::FormatOrdered(LOCTEXT("ModifierErrorDescription", "Modifier: {0}\nAsset: {1}\n{2}\nResolve the errors before trying to apply again."), FText::FromString(GetClass()->GetPathName()),
FText::FromString(CurrentAnimSequence->GetPathName()), FText::FromString(OutputLog));
EAppReturnType::Type ReturnValue = FMessageDialog::Open(EAppMsgType::Ok, ErrorMessageFormat, &MessageTitle);
UE::Anim::FApplyModifiersScope::SetReturnType(this, ReturnValue);
}
}
// If _only_ warnings have occured - check if user has previously said YesAll / NoAll and process the result
if (bWarnings && !bShouldRevert)
{
if (UE::Anim::FApplyModifiersScope::ScopesOpened == 0 || !ScopeReturnType.IsSet())
{
const FText WarningMessage = FText::FormatOrdered(LOCTEXT("ModifierWarningDescription", "Modifier: {0}\nAsset: {1}\n{2}\nAre you sure you want to apply it?"), FText::FromString(GetClass()->GetPathName()),
FText::FromString(CurrentAnimSequence ? CurrentAnimSequence->GetPathName() : CurrentSkeleton->GetPathName()), FText::FromString(OutputLog));
EAppReturnType::Type ReturnValue = FMessageDialog::Open(EAppMsgType::YesNoYesAllNoAll, WarningMessage, &MessageTitle);
bShouldRevert = ReturnValue == EAppReturnType::No || ReturnValue == EAppReturnType::NoAll;
// check if user response should be stored for further modifier applications
if(UE::Anim::FApplyModifiersScope::ScopesOpened > 0)
{
if (ReturnValue == EAppReturnType::Type::YesAll || ReturnValue == EAppReturnType::Type::NoAll)
{
UE::Anim::FApplyModifiersScope::SetReturnType(this, ReturnValue);
}
}
}
else
{
// Revert if previous user prompt return NoAll or if any errors occured previously
bShouldRevert = ScopeReturnType.GetValue() == EAppReturnType::NoAll || ScopeReturnType.GetValue() == EAppReturnType::Ok;
}
}
// Revert changes if necessary, otherwise post edit and refresh animation data
if (bShouldRevert)
{
AnimationDataTransaction.BeginOperation();
AnimationDataTransaction.Apply();
AnimationDataTransaction.EndOperation();
CurrentAnimSequence->RefreshCacheData();
}
else
{
/** Mark the previous modifier pending kill, as it will be replaced with the current modifier state */
if (PreviouslyAppliedModifier)
{
PreviouslyAppliedModifier->MarkPendingKill();
}
PreviouslyAppliedModifier = DuplicateObject(this, GetOuter(), RevertModifierObjectName);
CurrentAnimSequence->PostEditChange();
CurrentSkeleton->PostEditChange();
CurrentAnimSequence->RefreshCacheData();
UpdateStoredRevisions();
}
// Finished
CurrentAnimSequence = nullptr;
CurrentSkeleton = nullptr;
}
void UAnimationModifier::UpdateCompressedAnimationData()
{
if (CurrentAnimSequence->DoesNeedRecompress())
{
CurrentAnimSequence->RequestSyncAnimRecompression(false);
}
}
void UAnimationModifier::RevertFromAnimationSequence(class UAnimSequence* InAnimationSequence)
{
FEditorScriptExecutionGuard ScriptGuard;
/** Can only revert if previously applied, which means there should be a previous modifier */
if (PreviouslyAppliedModifier)
{
checkf(InAnimationSequence, TEXT("Invalid Animation Sequence supplied"));
CurrentAnimSequence = InAnimationSequence;
CurrentSkeleton = InAnimationSequence->GetSkeleton();
// Transact the modifier to prevent instance variables/data to change during reverting
FTransaction Transaction;
Transaction.SaveObject(this);
PreviouslyAppliedModifier->Modify();
IAnimationDataController& Controller = CurrentAnimSequence->GetController();
{
IAnimationDataController::FScopedBracket ScopedBracket(Controller, LOCTEXT("RevertModifierBracket", "Reverting Animation Modifier"));
PreviouslyAppliedModifier->OnRevert(CurrentAnimSequence);
}
// Apply transaction
Transaction.BeginOperation();
Transaction.Apply();
Transaction.EndOperation();
CurrentAnimSequence->PostEditChange();
CurrentSkeleton->PostEditChange();
CurrentAnimSequence->RefreshCacheData();
ResetStoredRevisions();
// Finished
CurrentAnimSequence = nullptr;
CurrentSkeleton = nullptr;
PreviouslyAppliedModifier->MarkPendingKill();
PreviouslyAppliedModifier = nullptr;
}
}
bool UAnimationModifier::IsLatestRevisionApplied() const
{
return (AppliedGuid == RevisionGuid);
}
void UAnimationModifier::PostInitProperties()
{
Super::PostInitProperties();
UpdateNativeRevisionGuid();
}
void UAnimationModifier::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
Ar.UsingCustomVersion(FReleaseObjectVersion::GUID);
/** Back-wards compatibility, assume the current modifier as previously applied */
if (Ar.CustomVer(FReleaseObjectVersion::GUID) < FReleaseObjectVersion::SerializeAnimModifierState)
{
PreviouslyAppliedModifier = DuplicateObject(this, GetOuter());
}
}
void UAnimationModifier::PostLoad()
{
Super::PostLoad();
UClass* Class = GetClass();
UObject* DefaultObject = Class->GetDefaultObject();
// CDO, set GUID if invalid
if(DefaultObject == this)
{
// Ensure we always have a valid guid
if (!RevisionGuid.IsValid())
{
UpdateRevisionGuid(GetClass());
MarkPackageDirty();
}
}
// Non CDO, update revision GUID
else if(UAnimationModifier* TypedDefaultObject = Cast<UAnimationModifier>(DefaultObject))
{
RevisionGuid = TypedDefaultObject->RevisionGuid;
}
}
const USkeleton* UAnimationModifier::GetSkeleton()
{
return CurrentSkeleton;
}
void UAnimationModifier::UpdateRevisionGuid(UClass* ModifierClass)
{
if (ModifierClass)
{
RevisionGuid = FGuid::NewGuid();
// Apply to any currently loaded instances of this class
for (TObjectIterator<UAnimationModifier> It; It; ++It)
{
if (*It != this && It->GetClass() == ModifierClass)
{
It->SetInstanceRevisionGuid(RevisionGuid);
}
}
}
}
void UAnimationModifier::UpdateNativeRevisionGuid()
{
UClass* Class = GetClass();
// Check if this is the class default object
if (this == GetDefault<UAnimationModifier>(Class))
{
// If so check whether or not the config stored revision matches the natively defined one
if (StoredNativeRevision != GetNativeClassRevision())
{
// If not update the blueprint revision GUID
UpdateRevisionGuid(Class);
StoredNativeRevision = GetNativeClassRevision();
MarkPackageDirty();
// Save the new native revision to config files
SaveConfig();
UpdateDefaultConfigFile();
}
}
}
void UAnimationModifier::ApplyToAll(TSubclassOf<UAnimationModifier> ModifierSubClass, bool bForceApply /*= true*/)
{
if (UClass* ModifierClass = ModifierSubClass.Get())
{
// Make sure all packages (in this case UAnimSequences) are loaded to ensure the TObjectIterator has any instances to iterate over
LoadModifierReferencers(ModifierSubClass);
const FScopedTransaction Transaction(LOCTEXT("UndoAction_ApplyModifiers", "Applying Animation Modifier to Animation Sequence(s)"));
for (TObjectIterator<UAnimationModifier> It; It; ++It)
{
// Check if valid, of the required class, not pending kill and not a modifier back-up for reverting
if (*It && It->GetClass() == ModifierClass && !It->IsPendingKill() && It->GetFName() != RevertModifierObjectName)
{
if (bForceApply || !It->IsLatestRevisionApplied())
{
// Go through outer chain to find AnimSequence
UObject* Outer = It->GetOuter();
while(Outer && !Outer->IsA<UAnimSequence>())
{
Outer = Outer->GetOuter();
}
if (UAnimSequence* AnimSequence = Cast<UAnimSequence>(Outer))
{
AnimSequence->Modify();
It->ApplyToAnimationSequence(AnimSequence);
}
}
}
}
}
}
void UAnimationModifier::LoadModifierReferencers(TSubclassOf<UAnimationModifier> ModifierSubClass)
{
if (UClass* ModifierClass = ModifierSubClass.Get())
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
TArray<FName> PackageDependencies;
AssetRegistryModule.GetRegistry().GetReferencers(ModifierClass->GetPackage()->GetFName(), PackageDependencies);
TArray<FString> PackageNames;
Algo::Transform(PackageDependencies, PackageNames, [](FName Name) { return Name.ToString(); });
TArray<UPackage*> Packages = AssetViewUtils::LoadPackages(PackageNames);
}
}
int32 UAnimationModifier::GetNativeClassRevision() const
{
// Overriden in derrived classes to perform native revisioning
return 0;
}
const UAnimSequence* UAnimationModifier::GetAnimationSequence()
{
return CurrentAnimSequence;
}
void UAnimationModifier::UpdateStoredRevisions()
{
AppliedGuid = RevisionGuid;
}
void UAnimationModifier::ResetStoredRevisions()
{
AppliedGuid.Invalidate();
}
void UAnimationModifier::SetInstanceRevisionGuid(FGuid Guid)
{
RevisionGuid = Guid;
}
#undef LOCTEXT_NAMESPACE