Files
UnrealEngine/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifiersTab.cpp
2025-05-18 13:04:45 +08:00

689 lines
23 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SAnimationModifiersTab.h"
#include "Animation/AnimSequence.h"
#include "Animation/Skeleton.h"
#include "AnimationModifier.h"
#include "AnimationModifierHelpers.h"
#include "AnimationModifiersAssetUserData.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "ClassViewerModule.h"
#include "Delegates/Delegate.h"
#include "DetailsViewArgs.h"
#include "Editor.h"
#include "Editor/EditorEngine.h"
#include "Engine/Blueprint.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "Framework/SlateDelegates.h"
#include "HAL/Platform.h"
#include "HAL/PlatformCrt.h"
#include "HAL/PlatformMisc.h"
#include "IDetailsView.h"
#include "Interfaces/Interface_AssetUserData.h"
#include "Internationalization/Internationalization.h"
#include "Internationalization/Text.h"
#include "Layout/Children.h"
#include "Layout/Margin.h"
#include "Misc/AssertionMacros.h"
#include "Misc/Attribute.h"
#include "Misc/MessageDialog.h"
#include "Misc/ScopedSlowTask.h"
#include "Modules/ModuleManager.h"
#include "PropertyEditorModule.h"
#include "SModifierListview.h"
#include "ScopedTransaction.h"
#include "SlotBase.h"
#include "Styling/AppStyle.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "Templates/Casts.h"
#include "Templates/SubclassOf.h"
#include "Toolkits/AssetEditorToolkit.h"
#include "Types/SlateEnums.h"
#include "UObject/NameTypes.h"
#include "UObject/Object.h"
#include "UObject/WeakObjectPtr.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SComboButton.h"
#include "Widgets/Input/SMenuAnchor.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SSplitter.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SOverlay.h"
#include "Widgets/SWidget.h"
#include "Widgets/Text/STextBlock.h"
class UClass;
struct FGeometry;
#define LOCTEXT_NAMESPACE "SAnimationModifiersTab"
SAnimationModifiersTab::SAnimationModifiersTab()
: Skeleton(nullptr), AnimationSequence(nullptr), AssetUserData(nullptr), bDirty(false)
{
}
SAnimationModifiersTab::~SAnimationModifiersTab()
{
if (GEditor)
{
GEditor->UnregisterForUndo(this);
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OnAssetOpenedInEditor().RemoveAll(this);
}
}
void SAnimationModifiersTab::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
if (bDirty)
{
RetrieveModifierData();
ModifierListView->Refresh();
bDirty = false;
}
}
void SAnimationModifiersTab::PostUndo(bool bSuccess)
{
Refresh();
}
void SAnimationModifiersTab::PostRedo(bool bSuccess)
{
Refresh();
}
void SAnimationModifiersTab::Construct(const FArguments& InArgs)
{
HostingApp = InArgs._InHostingApp;
// Retrieve asset and modifier data
RetrieveAnimationAsset();
RetrieveModifierData();
CreateInstanceDetailsView();
FOnGetContent GetContent = FOnGetContent::CreateLambda(
[this]()
{
return FAnimationModifierHelpers::GetModifierPicker(FOnClassPicked::CreateRaw(this, &SAnimationModifiersTab::OnModifierPicked));
});
this->ChildSlot
[
SNew(SOverlay)
+SOverlay::Slot()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBorder)
.Padding(2.0f)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(3.0f, 3.0f)
.AutoWidth()
[
SAssignNew(AddModifierCombobox, SComboButton)
.OnGetMenuContent(GetContent)
.ButtonContent()
[
SNew(STextBlock)
.Text(LOCTEXT("AddModifier", "Add Modifier"))
]
]
+ SHorizontalBox::Slot()
.Padding(3.0f, 3.0f)
.AutoWidth()
[
SNew(SButton)
.OnClicked(this, &SAnimationModifiersTab::OnApplyAllModifiersClicked)
.ContentPadding(FMargin(5))
.Content()
[
SNew(STextBlock)
.Text(LOCTEXT("ApplyAllModifiers", "Apply All Modifiers"))
]
]
]
]
+ SVerticalBox::Slot()
[
SNew(SBorder)
.Padding(2.0f)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
SNew(SSplitter)
.Orientation(EOrientation::Orient_Vertical)
+ SSplitter::Slot()
.Value(.5f)
[
SNew(SBox)
.Padding(2.0f)
[
SAssignNew(ModifierListView, SModifierListView)
.Items(&ModifierItems)
.InstanceDetailsView(ModifierInstanceDetailsView)
.OnApplyModifier(FOnModifierArray::CreateSP(this, &SAnimationModifiersTab::OnApplyModifier))
.OnCanRevertModifier(FOnCanModifierArray::CreateSP(this, &SAnimationModifiersTab::OnCanRevertModifier))
.OnRevertModifier(FOnModifierArray::CreateSP(this, &SAnimationModifiersTab::OnRevertModifier))
.OnRemoveModifier(FOnModifierArray::CreateSP(this, &SAnimationModifiersTab::OnRemoveModifier))
.OnOpenModifier(FOnSingleModifier::CreateSP(this, &SAnimationModifiersTab::OnOpenModifier))
.OnMoveUpModifier(FOnSingleModifier::CreateSP(this, &SAnimationModifiersTab::OnMoveModifierUp))
.OnMoveDownModifier(FOnSingleModifier::CreateSP(this, &SAnimationModifiersTab::OnMoveModifierDown))
]
]
+ SSplitter::Slot()
.Value(.5f)
[
SNew(SBox)
.Padding(2.0f)
[
ModifierInstanceDetailsView->AsShared()
]
]
]
]
]
];
// Ensure that this tab is only enabled if we have a valid AssetUserData instance
this->ChildSlot.GetWidget()->SetEnabled(TAttribute<bool>::Create([this]() { return AssetUserData != nullptr; }));
// Register delegates
if (GEditor)
{
GEditor->RegisterForUndo(this);
}
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OnAssetOpenedInEditor().AddSP(this, &SAnimationModifiersTab::OnAssetOpened);
}
void SAnimationModifiersTab::OnModifierPicked(UClass* PickedClass)
{
FScopedTransaction Transaction(LOCTEXT("AddModifierTransaction", "Adding Animation Modifier"));
UObject* Outer = AssetUserData;
UAnimationModifier* Processor = FAnimationModifierHelpers::CreateModifierInstance(Outer, PickedClass);
AssetUserData->Modify();
AssetUserData->AddAnimationModifier(Processor);
// Close the combo box
AddModifierCombobox->SetIsOpen(false);
// Refresh the UI
Refresh();
}
void SAnimationModifiersTab::RetrieveAnimationAsset()
{
TSharedPtr<FAssetEditorToolkit> AssetEditor = HostingApp.Pin();
const TArray<UObject*>* EditedObjects = AssetEditor->GetObjectsCurrentlyBeingEdited();
AssetUserData = nullptr;
if (EditedObjects)
{
// Try and find an AnimSequence or Skeleton asset in the currently being edited objects, and retrieve or add ModfiersAssetUserDat
for (UObject* Object : (*EditedObjects))
{
if (Object->IsA<UAnimSequence>())
{
AnimationSequence = Cast<UAnimSequence>(Object);
AssetUserData = FAnimationModifierHelpers::RetrieveOrCreateModifierUserData(AnimationSequence);
break;
}
else if (Object->IsA<USkeleton>())
{
Skeleton = Cast<USkeleton>(Object);
AssetUserData = FAnimationModifierHelpers::RetrieveOrCreateModifierUserData(Skeleton);
break;
}
}
}
}
void SAnimationModifiersTab::CreateInstanceDetailsView()
{
// Create a property view
FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
FDetailsViewArgs DetailsViewArgs;
DetailsViewArgs.bAllowSearch = false;
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
DetailsViewArgs.bHideSelectionTip = true;
DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Automatic;
DetailsViewArgs.bShowOptions = false;
ModifierInstanceDetailsView = EditModule.CreateDetailView(DetailsViewArgs);
ModifierInstanceDetailsView->SetDisableCustomDetailLayouts(true);
}
FReply SAnimationModifiersTab::OnApplyAllModifiersClicked()
{
FScopedTransaction Transaction(LOCTEXT("ApplyAllModifiersTransaction", "Applying All Animation Modifier(s)"));
const TArray<UAnimationModifier*>& ModifierInstances = AssetUserData->GetAnimationModifierInstances();
ApplyModifiers(ModifierInstances);
return FReply::Handled();
}
void SAnimationModifiersTab::OnApplyModifier(const TArray<TWeakObjectPtr<UAnimationModifier>>& Instances)
{
TArray<UAnimationModifier*> ModifierInstances;
for (TWeakObjectPtr<UAnimationModifier> InstancePtr : Instances)
{
checkf(InstancePtr.IsValid(), TEXT("Invalid weak object ptr to modifier instance"));
UAnimationModifier* Instance = InstancePtr.Get();
ModifierInstances.Add(Instance);
}
FScopedTransaction Transaction(LOCTEXT("ApplyModifiersTransaction", "Applying Animation Modifier(s)"));
ApplyModifiers(ModifierInstances);
Refresh();
}
void SAnimationModifiersTab::FindAnimSequencesForSkeleton(TArray<UAnimSequence *>& ReferencedAnimSequences)
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(FName("AssetRegistry"));
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
// Search for referencing packages to the currently open skeleton
TArray<FAssetIdentifier> Referencers;
AssetRegistry.GetReferencers(Skeleton->GetOuter()->GetFName(), Referencers);
for (const FAssetIdentifier& Identifier : Referencers)
{
TArray<FAssetData> Assets;
AssetRegistry.GetAssetsByPackageName(Identifier.PackageName, Assets);
for (const FAssetData& Asset : Assets)
{
// Only add assets whos class is of UAnimSequence
if (Asset.IsInstanceOf(UAnimSequence::StaticClass()))
{
ReferencedAnimSequences.Add(CastChecked<UAnimSequence>(Asset.GetAsset()));
}
}
}
}
void SAnimationModifiersTab::OnRevertModifier(const TArray<TWeakObjectPtr<UAnimationModifier>>& Instances)
{
TArray<UAnimationModifier*> ModifierInstances;
for (TWeakObjectPtr<UAnimationModifier> InstancePtr : Instances)
{
checkf(InstancePtr.IsValid(), TEXT("Invalid weak object ptr to modifier instance"));
UAnimationModifier* Instance = InstancePtr.Get();
ModifierInstances.Add(Instance);
}
FScopedTransaction Transaction(LOCTEXT("RevertModifiersTransaction", "Reverting Animation Modifier(s)"));
RevertModifiers(ModifierInstances);
Refresh();
}
bool SAnimationModifiersTab::OnCanRevertModifier(const TArray<TWeakObjectPtr<UAnimationModifier>>& Instances)
{
bool bCanRevert = false;
if (AnimationSequence)
{
for (const TWeakObjectPtr<UAnimationModifier>& InstancePtr : Instances)
{
checkf(InstancePtr.IsValid(), TEXT("Invalid weak object ptr to modifier instance"));
const UAnimationModifier* Modifier = InstancePtr.Get();
// At least one instance has to be revert-able
if (Modifier->CanRevert(AnimationSequence))
{
bCanRevert = true;
break;
}
}
}
else if (Skeleton)
{
// Check for modifiers applied on previous version
// Where asset registry tags were not written
for (const TWeakObjectPtr<UAnimationModifier>& Modifier : Instances)
{
if (Modifier->HasLegacyPreviousAppliedModifierOnSkeleton())
{
bCanRevert = true;
return true;
}
}
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(FName("AssetRegistry"));
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
TSet<FString> Modifiers;
Algo::Transform(Instances, Modifiers, [](const TWeakObjectPtr<UAnimationModifier>& Ptr)
{
return Ptr.Get()->GetName();
});
FARFilter Filter;
Filter.ClassPaths.Add(UAnimSequence::StaticClass()->GetClassPathName());
Filter.TagsAndValues.Add(FName{"Skeleton"}) = FAssetData(Skeleton).GetExportTextName();
AssetRegistry.EnumerateAssets(Filter, [&](const FAssetData& Asset)
{
FString Tag = Asset.GetTagValueRef<FString>(UAnimationModifier::AnimationModifiersTag);
FAnimationModifierHelpers::EnumerateAnimationModifierTags(Tag, [&](FStringView Name, FGuid Revision)
{
bCanRevert = Modifiers.ContainsByHash(GetTypeHash(Name), Name);
return !bCanRevert; // Break if bCanRevert is already true
});
return !bCanRevert; // Break if bCanRevert is already true
});
}
return bCanRevert;
}
void SAnimationModifiersTab::OnRemoveModifier(const TArray<TWeakObjectPtr<UAnimationModifier>>& Instances)
{
const bool bShouldRevert = FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("RemoveAndRevertPopupText", "Should the Modifiers be reverted before removing them?"), LOCTEXT("RemoveAndRevertPopupTitle", "Revert before Removing")) == EAppReturnType::Yes;
FScopedTransaction Transaction(LOCTEXT("RemoveModifiersTransaction", "Removing Animation Modifier(s)"));
AssetUserData->Modify();
if (bShouldRevert)
{
TArray<UAnimationModifier*> ModifierInstances;
for (TWeakObjectPtr<UAnimationModifier> InstancePtr : Instances)
{
checkf(InstancePtr.IsValid(), TEXT("Invalid weak object ptr to modifier instance"));
UAnimationModifier* Instance = InstancePtr.Get();
ModifierInstances.Add(Instance);
}
RevertModifiers(ModifierInstances);
}
for (TWeakObjectPtr<UAnimationModifier> InstancePtr : Instances)
{
checkf(InstancePtr.IsValid(), TEXT("Invalid weak object ptr to modifier instance"));
UAnimationModifier* Instance = InstancePtr.Get();
AssetUserData->Modify();
AssetUserData->RemoveAnimationModifierInstance(Instance);
}
Refresh();
}
void SAnimationModifiersTab::OnOpenModifier(const TWeakObjectPtr<UAnimationModifier>& Instance)
{
checkf(Instance.IsValid(), TEXT("Invalid weak object ptr to modifier instance"));
const UAnimationModifier* ModifierInstance = Instance.Get();
const UBlueprintGeneratedClass* BPGeneratedClass = Cast<UBlueprintGeneratedClass>(ModifierInstance->GetClass());
if (BPGeneratedClass && BPGeneratedClass->ClassGeneratedBy)
{
UBlueprint* Blueprint = Cast<UBlueprint>(BPGeneratedClass->ClassGeneratedBy);
if (Blueprint)
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(Blueprint);
}
}
}
void SAnimationModifiersTab::OnMoveModifierUp(const TWeakObjectPtr<UAnimationModifier>& Instance)
{
FScopedTransaction Transaction(LOCTEXT("MoveModifierUpTransaction", "Moving Animation Modifier Up"));
checkf(Instance.IsValid(), TEXT("Invalid weak object ptr to modifier instance"));
AssetUserData->Modify();
AssetUserData->ChangeAnimationModifierIndex(Instance.Get(), -1);
Refresh();
}
void SAnimationModifiersTab::OnMoveModifierDown(const TWeakObjectPtr<UAnimationModifier>& Instance)
{
FScopedTransaction Transaction(LOCTEXT("MoveModifierDownTransaction", "Moving Animation Modifier Down"));
checkf(Instance.IsValid(), TEXT("Invalid weak object ptr to modifier instance"));
AssetUserData->Modify();
AssetUserData->ChangeAnimationModifierIndex(Instance.Get(), 1);
Refresh();
}
void SAnimationModifiersTab::Refresh()
{
bDirty = true;
}
void SAnimationModifiersTab::OnBlueprintCompiled(UBlueprint* Blueprint)
{
if (Blueprint)
{
Refresh();
}
}
void SAnimationModifiersTab::OnAssetOpened(UObject* Object, IAssetEditorInstance* Instance)
{
RetrieveAnimationAsset();
RetrieveModifierData();
ModifierListView->Refresh();
}
void SAnimationModifiersTab::ApplyModifiers(const TArray<UAnimationModifier*>& Modifiers)
{
bool bApply = true;
TArray<UAnimSequence*> AnimSequences;
if (AnimationSequence != nullptr)
{
AnimSequences.Add(AnimationSequence);
}
else if (Skeleton != nullptr)
{
// Double check with the user for applying all modifiers to referenced animation sequences for the skeleton
bApply = FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("ApplyingSkeletonModifierPopupText", "Are you sure you want to apply the modifiers to all animation sequences referenced by the current skeleton?"), LOCTEXT("ApplyingSkeletonModifierPopupTitle", "Are you sure?")) == EAppReturnType::Yes;
if (bApply)
{
FindAnimSequencesForSkeleton(AnimSequences);
}
}
if (bApply)
{
UE::Anim::FApplyModifiersScope Scope;
FScopedSlowTask SlowTaskModifier(Modifiers.Num(), LOCTEXT("SlowTaskApplyingModifier_Modifier_Start", "Applying Animation Modifier(s) to Animation Sequence(s)...") );
SlowTaskModifier.MakeDialog();
for (UAnimationModifier* Instance : Modifiers)
{
checkf(Instance, TEXT("Invalid modifier instance"));
SlowTaskModifier.EnterProgressFrame(1.0f, FText::Format(LOCTEXT("SlowTaskApplyingModifier_Modifier_Update", "Applying Animation Modifier {0}..."), Instance->GetClass()->GetDisplayNameText()));
bool AppliedOnAny = false;
FScopedSlowTask SlowTaskSequences(AnimSequences.Num(), LOCTEXT("SlowTaskApplyingModifier_Sequence_Start", "Applying Animation Modifier to Animation Sequence(s)..."));
SlowTaskSequences.MakeDialog();
for (UAnimSequence* AnimSequence : AnimSequences)
{
FText AnimSequenceName = FText::FromString(IsValid(AnimSequence) ? AnimSequence->GetName() : TEXT(""));
SlowTaskSequences.EnterProgressFrame(1.0f, FText::Format(LOCTEXT("SlowTaskApplyingModifier_Sequence_Update", "Applying Animation Modifier to Animation Sequence {0}..."), AnimSequenceName));
ensure(!(Skeleton != nullptr) || AnimSequence->GetSkeleton() == Skeleton);
Instance->ApplyToAnimationSequence(AnimSequence);
AppliedOnAny = AppliedOnAny || Instance->CanRevert(AnimSequence);
}
if (AppliedOnAny && Skeleton)
{
Instance->RemoveLegacyPreviousAppliedModifierOnSkeleton(Skeleton);
}
}
}
}
void SAnimationModifiersTab::RevertModifiers(const TArray<UAnimationModifier*>& Modifiers)
{
bool bRevert = true;
TArray<UAnimSequence*> AnimSequences;
if (AnimationSequence != nullptr)
{
AnimSequences.Add(AnimationSequence);
}
else if (Skeleton != nullptr)
{
// Double check with the user for reverting all modifiers from referenced animation sequences for the skeleton
bRevert = FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("RevertingSkeletonModifierPopupText", "Are you sure you want to revert the modifiers from all animation sequences referenced by the current skeleton?"), LOCTEXT("RevertingSkeletonModifierPopupTitle", "Are you sure?")) == EAppReturnType::Yes;
if ( bRevert)
{
FindAnimSequencesForSkeleton(AnimSequences);
}
}
if (bRevert)
{
for (UAnimationModifier* Instance : Modifiers)
{
checkf(Instance, TEXT("Invalid modifier instance"));
for (UAnimSequence* AnimSequence : AnimSequences)
{
ensure(!(Skeleton != nullptr) || AnimSequence->GetSkeleton() == Skeleton);
Instance->RevertFromAnimationSequence(AnimSequence);
}
if (Skeleton)
{
// Revert can not fail, thus we can always mark reverted
Instance->RemoveLegacyPreviousAppliedModifierOnSkeleton(Skeleton);
}
}
}
}
void SAnimationModifiersTab::RetrieveModifierData()
{
ResetModifierData();
if (AssetUserData)
{
const TArray<UAnimationModifier*>& ModifierInstances = AssetUserData->GetAnimationModifierInstances();
for (int32 ModifierIndex = 0; ModifierIndex < ModifierInstances.Num(); ++ModifierIndex)
{
UAnimationModifier* Modifier = ModifierInstances[ModifierIndex];
checkf(Modifier != nullptr, TEXT("Invalid modifier ptr entry"));
FModifierListviewItem* Item = new FModifierListviewItem();
Item->Instance = Modifier;
Item->Class = Modifier->GetClass();
Item->Index = ModifierIndex;
Item->OuterClass = AssetUserData->GetOuter()->GetClass();
Item->OutOfDate = AnimationSequence && !Modifier->IsLatestRevisionApplied(AnimationSequence);
ModifierItems.Add(ModifierListviewItem(Item));
// Register a delegate for when a BP is compiled, this so we can refresh the UI and prevent issues with invalid instance data
if (Item->Class->ClassGeneratedBy)
{
checkf(Item->Class->ClassGeneratedBy != nullptr, TEXT("Invalid ClassGeneratedBy value"));
UBlueprint* Blueprint = CastChecked<UBlueprint>(Item->Class->ClassGeneratedBy);
Blueprint->OnCompiled().AddSP(this, &SAnimationModifiersTab::OnBlueprintCompiled);
DelegateRegisteredBlueprints.Add(Blueprint);
}
}
// Refresh OutOfDate data for modifiers on Skeleton
if (Skeleton)
{
TArray<FModifierListviewItem*> ModifiersToCheck; // Modifiers that are up to date (until current search progress)
TArray<FModifierListviewItem*> NextModifiersToCheck; // Double buffer for ModifiersToCheck, store the UpToDate modifiers on this animation
ModifiersToCheck.Reserve(ModifierItems.Num());
// Handle backward compatibility
// Modifier applied in previous version will not have the applied version stored in asset registry tags
// Exclude them in asset registry search and use the legacy OutOfDate check
for (TSharedPtr<FModifierListviewItem>& Item : ModifierItems)
{
ensure(!Item->OutOfDate);
if (Item->Instance->HasLegacyPreviousAppliedModifierOnSkeleton())
{
Item->OutOfDate = !Item->Instance->IsLatestRevisionApplied(Skeleton);
}
else
{
ModifiersToCheck.Add(Item.Get());
}
}
if (ModifiersToCheck.IsEmpty())
{
return;
}
NextModifiersToCheck.Reserve(ModifiersToCheck.Num());
// Modifier.OutOfDate = Any(Animation in Skeleton.Animations, Animation=>Modifier.OutOfDateOn(Animation))
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(FName("AssetRegistry"));
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
FARFilter Filter;
Filter.ClassPaths.Add(UAnimSequence::StaticClass()->GetClassPathName());
Filter.TagsAndValues.Add(FName{"Skeleton"}) = FAssetData(Skeleton).GetExportTextName();
AssetRegistry.EnumerateAssets(Filter, [&](const FAssetData& Asset) {
FString Tag = Asset.GetTagValueRef<FString>(UAnimationModifier::AnimationModifiersTag);
// First mark all modifiers to check as out of date
// Since modifiers omitted from the tag were not applied and thus out of date
for (FModifierListviewItem* Item : ModifiersToCheck)
{
Item->OutOfDate = true;
}
// Enumerate the current asset's tag
// To check the revision of each applied modifier
// UpToDate modifiers will be add to NextModifiersToCheck to continue the search in next Asset
NextModifiersToCheck.Empty();
FAnimationModifierHelpers::EnumerateAnimationModifierTags(Tag, [&](FStringView Name, FGuid Revision) {
FName ModifierName {Name};
auto CompareModifierListViewItemName = [ModifierName](const FModifierListviewItem* Item) {
return Item->Instance->GetFName() == ModifierName;
};
if (FModifierListviewItem** PtrItem = ModifiersToCheck.FindByPredicate(CompareModifierListViewItemName))
{
FModifierListviewItem* Item = *PtrItem;
Item->OutOfDate = Item->Instance->GetLatestRevisionGuid() != Revision;
if (!Item->OutOfDate)
{
NextModifiersToCheck.Add(Item); // Only need to continue check modifiers that is update to date
}
}
return true;
});
// Swap the double buffer
Swap(ModifiersToCheck, NextModifiersToCheck);
// Break if all modifiers are out of date
return !ModifiersToCheck.IsEmpty();
});
}
}
}
void SAnimationModifiersTab::ResetModifierData()
{
const int32 NumProcessors = (AssetUserData != nullptr) ? AssetUserData->GetAnimationModifierInstances().Num() : 0;
DelegateRegisteredBlueprints.Empty(NumProcessors);
ModifierItems.Empty(NumProcessors);
for (UBlueprint* Blueprint : DelegateRegisteredBlueprints)
{
Blueprint->OnCompiled().RemoveAll(this);
}
}
#undef LOCTEXT_NAMESPACE //"SAnimationModifiersTab"