// Copyright Epic Games, Inc. All Rights Reserved. #include "AnimationModifiersModule.h" #include "Algo/Copy.h" #include "Animation/AnimSequence.h" #include "AnimationModifier.h" #include "AnimationModifierDetailCustomization.h" #include "AnimationModifierHelpers.h" #include "AnimationModifierSettings.h" #include "AnimationModifiersAssetUserData.h" #include "AnimationModifiersTabSummoner.h" #include "Delegates/Delegate.h" #include "Editor.h" #include "Editor/EditorEngine.h" #include "Framework/Application/SlateApplication.h" #include "HAL/Platform.h" #include "HAL/PlatformCrt.h" #include "Interfaces/IMainFrameModule.h" #include "Interfaces/Interface_AssetUserData.h" #include "Internationalization/Internationalization.h" #include "Math/Vector2D.h" #include "Misc/CoreDelegates.h" #include "Misc/ScopedSlowTask.h" #include "Modules/ModuleManager.h" #include "PropertyEditorDelegates.h" #include "PropertyEditorModule.h" #include "SAnimationModifierContentBrowserWindow.h" #include "ScopedTransaction.h" #include "Subsystems/ImportSubsystem.h" #include "Templates/Casts.h" #include "Templates/SubclassOf.h" #include "UObject/AssetRegistryTagsContext.h" #include "UObject/Object.h" #include "UObject/UObjectGlobals.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SWindow.h" #include "WorkflowOrientedApp/ApplicationMode.h" #include "AnimationModifierSettings.h" #include "AnimationModifiersAssetUserData.h" #include "AssetToolsModule.h" #include "AssetViewUtils.h" #include "ContentBrowserMenuContexts.h" #include "ToolMenuDelegates.h" #include "ToolMenus.h" #include "AssetRegistry/AssetRegistryModule.h" class UFactory; #define LOCTEXT_NAMESPACE "AnimationModifiersModule" IMPLEMENT_MODULE(FAnimationModifiersModule, AnimationModifiers); void FAnimationModifiersModule::StartupModule() { // Register class/struct customizations FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked("PropertyEditor"); PropertyEditorModule.RegisterCustomClassLayout("AnimationModifier", FOnGetDetailCustomizationInstance::CreateStatic(&FAnimationModifierDetailCustomization::MakeInstance)); // Add application mode extender Extender = FWorkflowApplicationModeExtender::CreateRaw(this, &FAnimationModifiersModule::ExtendApplicationMode); FWorkflowCentricApplication::GetModeExtenderList().Add(Extender); // Register delegates during PostEngineInit as this module is part of preload phase and GEditor is not valid yet DelegateHandle = FCoreDelegates::OnPostEngineInit.AddLambda([this]() { if (GEditor) { IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); AssetAction = MakeShared(); AssetTools.RegisterAssetTypeActions(AssetAction.ToSharedRef()); GEditor->GetEditorSubsystem()->OnAssetPostImport.AddRaw(this, &FAnimationModifiersModule::OnAssetPostImport); GEditor->GetEditorSubsystem()->OnAssetReimport.AddRaw(this, &FAnimationModifiersModule::OnAssetPostReimport); RegisterMenus(); const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().OnInMemoryAssetCreated().AddRaw(this, &FAnimationModifiersModule::OnInMemoryAssetCreated); } }); // Register extra asset registry tags for UAnimSequence OnGetExtraObjectTagsHandle = UObject::FAssetRegistryTag::OnGetExtraObjectTagsWithContext.AddStatic(&UAnimationModifier::GetAssetRegistryTagsForAppliedModifiersFromSkeleton); } void FAnimationModifiersModule::ShutdownModule() { // Make sure we unregister the class layout FPropertyEditorModule* PropertyEditorModule = FModuleManager::GetModulePtr("PropertyEditor"); if (PropertyEditorModule) { PropertyEditorModule->UnregisterCustomClassLayout("AnimationModifier"); } UToolMenus::UnregisterOwner(this); FCoreDelegates::OnPostEngineInit.Remove(DelegateHandle); UObject::FAssetRegistryTag::OnGetExtraObjectTagsWithContext.Remove(OnGetExtraObjectTagsHandle); // Remove extender delegate FWorkflowCentricApplication::GetModeExtenderList().RemoveAll([this](FWorkflowApplicationModeExtender& StoredExtender) { return StoredExtender.GetHandle() == Extender.GetHandle(); }); // During shutdown clean up all factories from any modes which are still active/alive for (TWeakPtr WeakMode : RegisteredApplicationModes) { if (WeakMode.IsValid()) { TSharedPtr Mode = WeakMode.Pin(); Mode->RemoveTabFactory(FAnimationModifiersTabSummoner::AnimationModifiersName); } } if (FModuleManager::Get().IsModuleLoaded(TEXT("AssetTools"))) { IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); AssetTools.UnregisterAssetTypeActions(AssetAction.ToSharedRef()); } if (FModuleManager::Get().IsModuleLoaded(TEXT("AssetRegistry"))) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::GetModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().OnInMemoryAssetCreated().RemoveAll(this); } RegisteredApplicationModes.Empty(); if (GEditor) { GEditor->GetEditorSubsystem()->OnAssetPostImport.RemoveAll(this); GEditor->GetEditorSubsystem()->OnAssetReimport.RemoveAll(this); } } TSharedRef FAnimationModifiersModule::ExtendApplicationMode(const FName ModeName, TSharedRef InMode) { // For skeleton and animation editor modes add our custom tab factory to it if (ModeName == TEXT("SkeletonEditorMode") || ModeName == TEXT("AnimationEditorMode")) { InMode->AddTabFactory(FCreateWorkflowTabFactory::CreateStatic(&FAnimationModifiersTabSummoner::CreateFactory)); RegisteredApplicationModes.Add(InMode); } return InMode; } void FAnimationModifiersModule::RegisterMenus() { FToolMenuOwnerScoped OwnerScoped(this); UToolMenus* ToolMenus = UToolMenus::Get(); UToolMenu* Menu = ToolMenus->ExtendMenu("ContentBrowser.AssetContextMenu.AnimSequence"); if (!Menu) { return; } FToolMenuSection& Section = Menu->FindOrAddSection("GetAssetActions"); Section.AddDynamicEntry("AnimModifierActions", FNewToolMenuSectionDelegate::CreateLambda([this](FToolMenuSection& InSection) { const UContentBrowserAssetContextMenuContext* Context = InSection.FindContext(); if (!Context) { return; } TArray AnimSequenceAssets; Algo::CopyIf(Context->SelectedAssets, AnimSequenceAssets, [](const FAssetData& AssetData) { return AssetData.AssetClassPath == UAnimSequence::StaticClass()->GetClassPathName(); }); auto GetAnimSequences = [AnimSequenceAssets](TArray& OutSequences) { TArray Objects; AssetViewUtils::LoadAssetsIfNeeded(AnimSequenceAssets, Objects, AssetViewUtils::FLoadAssetsSettings{}); Algo::TransformIf(Objects, OutSequences, [](const UObject* Object) { return Cast(Object) != nullptr; }, [](UObject* Object) { return Cast(Object); }); }; const FNewMenuDelegate MenuDelegate = FNewMenuDelegate::CreateLambda([GetAnimSequences, this](FMenuBuilder& MenuBuilder) { MenuBuilder.AddMenuEntry( LOCTEXT("AnimSequence_AddAnimationModifier", "Add Modifiers"), LOCTEXT("AnimSequence_AddAnimationModifierTooltip", "Add new animation modifier(s)."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.AnimationModifier"), FUIAction(FExecuteAction::CreateLambda([GetAnimSequences, this]() { TArray AnimSequences; GetAnimSequences(AnimSequences); ShowAddAnimationModifierWindow(AnimSequences); })) ); MenuBuilder.AddMenuEntry( LOCTEXT("AnimSequence_ApplyAnimationModifier", "Apply Modifiers"), LOCTEXT("AnimSequence_ApplyAnimationModifierTooltip", "Applies all contained animation modifier(s)."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.AnimationModifier"), FUIAction(FExecuteAction::CreateLambda([GetAnimSequences, this]() { TArray AnimSequences; GetAnimSequences(AnimSequences); ApplyAnimationModifiers(AnimSequences); })) ); MenuBuilder.AddMenuEntry( LOCTEXT("AnimSequence_ApplyOutOfDataAnimationModifier", "Apply out-of-date Modifiers"), LOCTEXT("AnimSequence_ApplyOutOfDataAnimationModifierTooltip", "Applies all contained animation modifier(s), if they are out of date."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.AnimationModifier"), FUIAction(FExecuteAction::CreateLambda([GetAnimSequences, this]() { TArray AnimSequences; GetAnimSequences(AnimSequences); ApplyAnimationModifiers(AnimSequences, false); })) ); MenuBuilder.AddMenuEntry( LOCTEXT("AnimSequence_RemoveAnimationModifier", "Remove Modifiers"), LOCTEXT("AnimSequence_RemoveAnimationModifierTooltip", "Remove animation modifier(s)"), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.AnimationModifier"), FUIAction(FExecuteAction::CreateLambda([GetAnimSequences, this]() { TArray AnimSequences; GetAnimSequences(AnimSequences); ShowRemoveAnimationModifierWindow(AnimSequences); }))); }); IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); if (AssetTools.IsAssetClassSupported(UAnimationModifier::StaticClass())) { InSection.AddSubMenu("AnimSequence_AnimationModifiers", LOCTEXT("AnimSequence_AnimationModifiers", "Animation Modifier(s)"), LOCTEXT("AnimSequence_AnimationModifiersTooltip", "Animation Modifier actions"), FNewToolMenuChoice(MenuDelegate), false, FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.AnimationModifier") ); } }) ); } void FAnimationModifiersModule::OnAssetPostImport(UFactory* ImportFactory, UObject* ImportedObject) { // Check whether or not the imported asset is a AnimSequence if (UAnimSequence* AnimationSequence = Cast(ImportedObject)) { // Check whether or not there are any default modifiers which should be added to the new sequence const TArray>& DefaultModifiers = GetDefault()->DefaultAnimationModifiers; if (DefaultModifiers.Num()) { UAnimationModifiersAssetUserData* AssetUserData = FAnimationModifierHelpers::RetrieveOrCreateModifierUserData(AnimationSequence); for (TSubclassOf ModifierClass : DefaultModifiers) { if (ModifierClass.Get()) { UObject* Outer = AssetUserData; UAnimationModifier* Processor = FAnimationModifierHelpers::CreateModifierInstance(Outer, *ModifierClass); AssetUserData->Modify(); AssetUserData->AddAnimationModifier(Processor); } } if (GetDefault()->bApplyAnimationModifiersOnImport) { ApplyAnimationModifiers({AnimationSequence}); } } } } void FAnimationModifiersModule::OnAssetPostReimport(UObject* ReimportedObject) { // Check whether or not the reimported asset is a AnimSequence if (UAnimSequence* AnimationSequence = Cast(ReimportedObject)) { // Check whether or not any contained modifiers should be applied if (GetDefault()->bApplyAnimationModifiersOnImport) { ApplyAnimationModifiers({AnimationSequence}); } } } void FAnimationModifiersModule::OnInMemoryAssetCreated(UObject* Object) { if (Object->GetClass() == UAnimSequence::StaticClass()) { FAnimationModifierHelpers::RetrieveOrCreateModifierUserData(Cast(Object)); } } void FAnimationModifiersModule::ShowAddAnimationModifierWindow(const TArray& InSequences) { TSharedPtr WindowContent; TSharedRef Window = SNew(SWindow) .Title(LOCTEXT("WindowTitle", "Add Animation Modifier(s)")) .SizingRule(ESizingRule::UserSized) .ClientSize(FVector2D(500, 500)); Window->SetContent ( SAssignNew(WindowContent, SAnimationModifierContentBrowserWindow) .WidgetWindow(Window) .AnimSequences(InSequences) ); TSharedPtr ParentWindow; if (FModuleManager::Get().IsModuleLoaded("MainFrame")) { IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked("MainFrame"); ParentWindow = MainFrame.GetParentWindow(); } FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); } void FAnimationModifiersModule::ShowRemoveAnimationModifierWindow(const TArray& InSequences) { TSharedPtr WindowContent; TSharedRef Window = SNew(SWindow) .Title(LOCTEXT("RemoveModifiersWindowTitle", "Remove Animation Modifier(s)")) .SizingRule(ESizingRule::UserSized) .ClientSize(FVector2D(500, 500)); Window->SetContent ( SAssignNew(WindowContent, SRemoveAnimationModifierContentBrowserWindow) .WidgetWindow(Window) .AnimSequences(InSequences) ); TSharedPtr ParentWindow; if (FModuleManager::Get().IsModuleLoaded("MainFrame")) { IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked("MainFrame"); ParentWindow = MainFrame.GetParentWindow(); } FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); } void FAnimationModifiersModule::ApplyAnimationModifiers(const TArray& InSequences, bool bForceApply /*= true*/) { const FScopedTransaction Transaction(LOCTEXT("UndoAction_ApplyModifiers", "Applying Animation Modifier(s) to Animation Sequence(s)")); // Iterate over each Animation Sequence and all of its contained modifiers, applying each one UE::Anim::FApplyModifiersScope Scope; TArray AssetUserData; FScopedSlowTask SlowTaskSequences(InSequences.Num(), LOCTEXT("SlowTaskApplyingModifier_Sequence_Start", "Applying Animation Modifier(s) to Animation Sequence(s)...")); SlowTaskSequences.MakeDialog(); for (UAnimSequence* AnimationSequence : InSequences) { FText AnimSequenceName = FText::FromString(IsValid(AnimationSequence) ? AnimationSequence->GetName() : TEXT("")); SlowTaskSequences.EnterProgressFrame(1.0f, FText::Format(LOCTEXT("SlowTaskApplyingModifier_Sequence_Update", "Applying Animation Modifier(s) to Animation Sequence {0}..."), AnimSequenceName)); if (AnimationSequence) { UAnimationModifiersAssetUserData* UserData = AnimationSequence->GetAssetUserData(); if (UserData) { const TArray& ModifierInstances = UserData->GetAnimationModifierInstances(); FScopedSlowTask SlowTaskModifiers(ModifierInstances.Num(), LOCTEXT("SlowTaskApplyingModifier_Modifier_Start", "Applying Animation Modifiers...")); SlowTaskModifiers.MakeDialog(); for (UAnimationModifier* Modifier : ModifierInstances) { SlowTaskModifiers.EnterProgressFrame(1.0f, FText::Format(LOCTEXT("SlowTaskApplyingModifier_Modifier_Update", "Applying Animation Modifier {0}..."), Modifier->GetClass()->GetDisplayNameText())); if (bForceApply || !Modifier->IsLatestRevisionApplied(AnimationSequence)) { Modifier->ApplyToAnimationSequence(AnimationSequence); } } } } } } #undef LOCTEXT_NAMESPACE // "AnimationModifiersModule"