// Copyright Epic Games, Inc. All Rights Reserved. #include "AnimAssetFindReplaceNotifies.h" #include "IEditableSkeleton.h" #include "ISkeletonEditorModule.h" #include "PersonaModule.h" #include "SAnimAssetFindReplace.h" #include "Animation/Skeleton.h" #include "Animation/AnimationAsset.h" #include "Animation/AnimSequence.h" #include "Engine/SkeletalMesh.h" #include "String/ParseTokens.h" #define LOCTEXT_NAMESPACE "AnimAssetFindReplaceNotifies" FString UAnimAssetFindReplaceNotifies::GetFindResultStringFromAssetData(const FAssetData& InAssetData) const { if(GetFindString().IsEmpty()) { return FString(); } auto GetMatchingNotifyNamesForAsset = [this](const FAssetData& InAssetData, TArray& OutNotifyNames) { const FString TagValue = InAssetData.GetTagValueRef(USkeleton::AnimNotifyTag); if (!TagValue.IsEmpty()) { UE::String::ParseTokens(TagValue, *USkeleton::AnimNotifyTagDelimiter, [this, &OutNotifyNames](FStringView InToken) { if(GetFindWholeWord()) { if(InToken.Compare(GetFindString(), GetSearchCase()) == 0) { OutNotifyNames.Add(FString(InToken)); } } else { if(UE::String::FindFirst(InToken, GetFindString(), GetSearchCase()) != INDEX_NONE) { OutNotifyNames.Add(FString(InToken)); } } }, UE::String::EParseTokensOptions::SkipEmpty); } }; TStringBuilder<128> Builder; TArray NotifyNames; GetMatchingNotifyNamesForAsset(InAssetData, NotifyNames); if(NotifyNames.Num() > 0) { for(int32 NameIndex = 0; NameIndex < NotifyNames.Num(); ++NameIndex) { Builder.Append(NotifyNames[NameIndex]); if(NameIndex != NotifyNames.Num() - 1) { Builder.Append(TEXT(", ")); } } } return FString(Builder.ToString()); } TConstArrayView UAnimAssetFindReplaceNotifies::GetSupportedAssetTypes() const { FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked("Persona"); static TArray Types; static uint32 Version = 0; if(Types.Num() == 0 || Version != PersonaModule.GetNotifyHostAssetVersion()) { Types.Reset(); Types.Append({ UAnimSequenceBase::StaticClass(), USkeleton::StaticClass() }); for(const FTopLevelAssetPath& TopLevelAssetPath : PersonaModule.GetAllNotifyHostAssetClassPaths()) { FSoftClassPath SoftClassPath(TopLevelAssetPath.ToString()); if(UClass* ResolvedClass = SoftClassPath.ResolveClass()) { Types.Add(ResolvedClass); } } Version = PersonaModule.GetNotifyHostAssetVersion(); } return Types; } bool UAnimAssetFindReplaceNotifies::ShouldFilterOutAsset(const FAssetData& InAssetData, bool& bOutIsOldAsset) const { FString TagValue; if(InAssetData.GetTagValue(USkeleton::AnimNotifyTag, TagValue)) { bOutIsOldAsset = false; if(GetFindString().IsEmpty()) { return true; } if(GetSkeletonFilter().IsValid() ) { if(InAssetData.GetClass() != USkeleton::StaticClass()) { FString SkeletonPath; if(InAssetData.GetTagValue(TEXT("Skeleton"), SkeletonPath)) { if(SkeletonPath != GetSkeletonFilter().GetExportTextName()) { return true; } } } else { if(InAssetData.ToSoftObjectPath() != GetSkeletonFilter().ToSoftObjectPath()) { return true; } } } bool bFoundMatch = false; UE::String::ParseTokens(TagValue, *USkeleton::AnimNotifyTagDelimiter, [this, &bFoundMatch](FStringView InToken) { if(NameMatches(InToken)) { bFoundMatch = true; } }, UE::String::EParseTokensOptions::SkipEmpty); if(bFoundMatch) { return false; } } else { const UClass* AssetClass = InAssetData.GetClass(); if(AssetClass->IsChildOf(UAnimSequenceBase::StaticClass())) { bOutIsOldAsset = true; } else if(AssetClass->IsChildOf(USkeleton::StaticClass())) { bOutIsOldAsset = true; } else { // Assume unknown assets are not 'old' bOutIsOldAsset = false; } } return true; } void UAnimAssetFindReplaceNotifies::ReplaceInAsset(const FAssetData& InAssetData) const { if(UObject* Asset = InAssetData.GetAsset()) { if(UAnimSequenceBase* AnimSequenceBase = Cast(Asset)) { if(GetFindWholeWord()) { Asset->Modify(); AnimSequenceBase->RenameNotifies(FName(GetFindString()), FName(GetReplaceString())); } else { TArray> FindReplacePairs; for(const FAnimNotifyEvent& Notify : AnimSequenceBase->Notifies) { // Only handle named notifiesif(!Notify.IsBlueprintNotify()) { FString NotifyName = Notify.NotifyName.ToString(); if(NameMatches(NotifyName)) { const FName FindNotifyName(*NotifyName); const FString NewName = NotifyName.Replace(*GetFindStringRef(), *GetReplaceStringRef(), GetSearchCase()); const FName ReplaceNotifyName(*NewName); FindReplacePairs.AddUnique(TPair(FindNotifyName, ReplaceNotifyName)); } } } if(FindReplacePairs.Num() > 0) { Asset->Modify(); for(const TPair& FindReplacePair : FindReplacePairs) { AnimSequenceBase->RenameNotifies(FindReplacePair.Key, FindReplacePair.Value); } } } } else if(USkeleton* Skeleton = Cast(Asset)) { ISkeletonEditorModule& SkeletonEditorModule = FModuleManager::GetModuleChecked("SkeletonEditor"); TSharedRef EditableSkeleton = SkeletonEditorModule.CreateEditableSkeleton(Skeleton); if(GetFindWholeWord()) { Asset->Modify(); EditableSkeleton->RenameNotify(FName(GetReplaceString()), FName(GetFindString()), false); } else { TArray> FindReplacePairs; for(const FName& NotifyName : Skeleton->AnimationNotifies) { FString NotifyString = NotifyName.ToString(); if(NameMatches(NotifyString)) { const FString NewName = NotifyString.Replace(*GetFindStringRef(), *GetReplaceStringRef(), GetSearchCase()); const FName ReplaceNotifyName(*NewName); FindReplacePairs.AddUnique(TPair(NotifyName, ReplaceNotifyName)); } } if(FindReplacePairs.Num() > 0) { Asset->Modify(); for(const TPair& FindReplacePair : FindReplacePairs) { EditableSkeleton->RenameNotify(FindReplacePair.Value, FindReplacePair.Key, false); } } } } else { FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked("Persona"); if(const FPersonaModule::FNotifyHostAssetParameters* NotifyHostParameters = PersonaModule.FindNotifyHostAsset(Asset)) { NotifyHostParameters->OnReplaceNotify.ExecuteIfBound(Asset, GetFindStringRef(), GetReplaceStringRef(), GetFindWholeWord(), GetSearchCase()); } } } } void UAnimAssetFindReplaceNotifies::RemoveInAsset(const FAssetData& InAssetData) const { if(UObject* Asset = InAssetData.GetAsset()) { if(UAnimSequenceBase* AnimSequenceBase = Cast(Asset)) { if(GetFindWholeWord()) { Asset->Modify(); AnimSequenceBase->RemoveNotifies( { FName(GetFindString()) } ); } else { TArray NotifiesToRemove; for(const FAnimNotifyEvent& Notify : AnimSequenceBase->Notifies) { FString NotifyNameString = Notify.NotifyName.ToString(); if(NameMatches(NotifyNameString)) { NotifiesToRemove.AddUnique(Notify.NotifyName); } } if(NotifiesToRemove.Num() > 0) { Asset->Modify(); AnimSequenceBase->RemoveNotifies(NotifiesToRemove); } } } else if(USkeleton* Skeleton = Cast(Asset)) { ISkeletonEditorModule& SkeletonEditorModule = FModuleManager::GetModuleChecked("SkeletonEditor"); TSharedRef EditableSkeleton = SkeletonEditorModule.CreateEditableSkeleton(Skeleton); if(GetFindWholeWord()) { EditableSkeleton->DeleteAnimNotifies( { FName(GetFindString()) }, false); } else { TArray NotifiesToRemove; for(const FName& NotifyName : Skeleton->AnimationNotifies) { if(NameMatches(NotifyName.ToString())) { NotifiesToRemove.AddUnique(NotifyName); } } if(NotifiesToRemove.Num() > 0) { EditableSkeleton->DeleteAnimNotifies(NotifiesToRemove, false); } } } } else { FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked("Persona"); if(const FPersonaModule::FNotifyHostAssetParameters* NotifyHostParameters = PersonaModule.FindNotifyHostAsset(Asset)) { NotifyHostParameters->OnRemoveNotify.ExecuteIfBound(Asset, GetFindStringRef(), GetFindWholeWord(), GetSearchCase()); } } } void UAnimAssetFindReplaceNotifies::GetAutoCompleteNames(TArrayView InAssetDatas, TSet& OutUniqueNames) const { for (const FAssetData& AssetData : InAssetDatas) { const FString TagValue = AssetData.GetTagValueRef(USkeleton::AnimNotifyTag); if (!TagValue.IsEmpty()) { UE::String::ParseTokens(TagValue, *USkeleton::AnimNotifyTagDelimiter, [&OutUniqueNames](FStringView InToken) { OutUniqueNames.Add(FString(InToken)); }, UE::String::EParseTokensOptions::SkipEmpty); } } } #undef LOCTEXT_NAMESPACE