// Copyright Epic Games, Inc. All Rights Reserved. #include "AnimationEditorUtils.h" #include "Components/SkeletalMeshComponent.h" #include "Framework/Application/SlateApplication.h" #include "Framework/Commands/UIAction.h" #include "Textures/SlateIcon.h" #include "Misc/MessageDialog.h" #include "Misc/FeedbackContext.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SSeparator.h" #include "Widgets/Layout/SSplitter.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Layout/SUniformGridPanel.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SButton.h" #include "Styling/CoreStyle.h" #include "Styling/AppStyle.h" #include "Animation/AnimMontage.h" #include "Animation/AnimBlueprint.h" #include "Factories/AnimBlueprintFactory.h" #include "Factories/AnimCompositeFactory.h" #include "Factories/AnimMontageFactory.h" #include "Factories/BlendSpaceFactory1D.h" #include "Factories/AimOffsetBlendSpaceFactory1D.h" #include "Factories/BlendSpaceFactoryNew.h" #include "Factories/AimOffsetBlendSpaceFactoryNew.h" #include "Engine/PoseWatch.h" #include "Animation/AnimBlueprintGeneratedClass.h" #include "Animation/AnimBoneCompressionSettings.h" #include "Animation/AnimComposite.h" #include "Animation/AnimCompress.h" #include "Animation/BlendSpace.h" #include "Animation/BlendSpace1D.h" #include "Animation/AimOffsetBlendSpace.h" #include "Animation/AimOffsetBlendSpace1D.h" #include "AnimationGraph.h" #include "AnimStateNodeBase.h" #include "AnimStateTransitionNode.h" #include "Animation/AnimNodeBase.h" #include "AnimGraphNode_Base.h" #include "AnimGraphNode_StateMachineBase.h" #include "AnimationStateMachineGraph.h" #include "K2Node_Composite.h" #include "Kismet2/BlueprintEditorUtils.h" #include "AssetRegistry/AssetRegistryModule.h" #include "Interfaces/IMainFrameModule.h" #define LOCTEXT_NAMESPACE "AnimationEditorUtils" ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // Create Animation dialog to determine a newly created asset's name /////////////////////////////////////////////////////////////////////////////// FText SCreateAnimationAssetDlg::LastUsedAssetPath; void SCreateAnimationAssetDlg::Construct(const FArguments& InArgs) { AssetPath = FText::FromString(FPackageName::GetLongPackagePath(InArgs._DefaultAssetPath.ToString())); AssetName = FText::FromString(FPackageName::GetLongPackageAssetName(InArgs._DefaultAssetPath.ToString())); if (AssetPath.IsEmpty()) { AssetPath = LastUsedAssetPath; } else { LastUsedAssetPath = AssetPath; } FPathPickerConfig PathPickerConfig; PathPickerConfig.DefaultPath = AssetPath.ToString(); PathPickerConfig.OnPathSelected = FOnPathSelected::CreateSP(this, &SCreateAnimationAssetDlg::OnPathChange); PathPickerConfig.bAddDefaultPath = true; FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); SWindow::Construct(SWindow::FArguments() .Title(LOCTEXT("SCreateAnimationAssetDlg_Title", "Create a New Animation Asset")) .SupportsMinimize(false) .SupportsMaximize(false) //.SizingRule( ESizingRule::Autosized ) .ClientSize(FVector2D(450, 450)) [ SNew(SVerticalBox) + SVerticalBox::Slot() // Add user input block .Padding(2) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(STextBlock) .Text(LOCTEXT("SelectPath", "Select Path to create animation")) .Font(FCoreStyle::GetDefaultFontStyle("Regular", 14)) ] + SVerticalBox::Slot() .FillHeight(1) .Padding(3) [ ContentBrowserModule.Get().CreatePathPicker(PathPickerConfig) ] + SVerticalBox::Slot() .AutoHeight() [ SNew(SSeparator) ] + SVerticalBox::Slot() .AutoHeight() .Padding(3) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(0, 0, 10, 0) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("AnimationName", "Animation Name")) ] + SHorizontalBox::Slot() [ SNew(SEditableTextBox) .Text(AssetName) .OnTextCommitted(this, &SCreateAnimationAssetDlg::OnNameChange) .MinDesiredWidth(250) ] ] ] ] + SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Right) .Padding(5) [ SNew(SUniformGridPanel) .SlotPadding(FAppStyle::GetMargin("StandardDialog.SlotPadding")) .MinDesiredSlotWidth(FAppStyle::GetFloat("StandardDialog.MinDesiredSlotWidth")) .MinDesiredSlotHeight(FAppStyle::GetFloat("StandardDialog.MinDesiredSlotHeight")) + SUniformGridPanel::Slot(0, 0) [ SNew(SButton) .HAlign(HAlign_Center) .ContentPadding(FAppStyle::GetMargin("StandardDialog.ContentPadding")) .Text(LOCTEXT("OK", "OK")) .OnClicked(this, &SCreateAnimationAssetDlg::OnButtonClick, EAppReturnType::Ok) ] + SUniformGridPanel::Slot(1, 0) [ SNew(SButton) .HAlign(HAlign_Center) .ContentPadding(FAppStyle::GetMargin("StandardDialog.ContentPadding")) .Text(LOCTEXT("Cancel", "Cancel")) .OnClicked(this, &SCreateAnimationAssetDlg::OnButtonClick, EAppReturnType::Cancel) ] ] ]); } void SCreateAnimationAssetDlg::OnNameChange(const FText& NewName, ETextCommit::Type CommitInfo) { AssetName = NewName; } void SCreateAnimationAssetDlg::OnPathChange(const FString& NewPath) { AssetPath = FText::FromString(NewPath); LastUsedAssetPath = AssetPath; } FReply SCreateAnimationAssetDlg::OnButtonClick(EAppReturnType::Type ButtonID) { UserResponse = ButtonID; if (ButtonID != EAppReturnType::Cancel) { if (!ValidatePackage()) { // reject the request return FReply::Handled(); } } RequestDestroyWindow(); return FReply::Handled(); } /** Ensures supplied package name information is valid */ bool SCreateAnimationAssetDlg::ValidatePackage() { FText Reason; FString FullPath = GetFullAssetPath(); if (!FPackageName::IsValidLongPackageName(FullPath, false, &Reason) || !FName(*AssetName.ToString()).IsValidObjectName(Reason)) { FMessageDialog::Open(EAppMsgType::Ok, Reason); return false; } return true; } EAppReturnType::Type SCreateAnimationAssetDlg::ShowModal() { GEditor->EditorAddModalWindow(SharedThis(this)); return UserResponse; } FString SCreateAnimationAssetDlg::GetAssetPath() { return AssetPath.ToString(); } FString SCreateAnimationAssetDlg::GetAssetName() { return AssetName.ToString(); } FString SCreateAnimationAssetDlg::GetFullAssetPath() { return AssetPath.ToString() + "/" + AssetName.ToString(); } ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// // Dialog to prompt user to select an animation compression settings asset. ///////////////////////////////////////////////////// SAnimationCompressionSelectionDialog::SAnimationCompressionSelectionDialog() : bValidAssetChosen(false) {} SAnimationCompressionSelectionDialog::~SAnimationCompressionSelectionDialog() {} void SAnimationCompressionSelectionDialog::SetOnAssetSelected(const FOnAssetSelected& InHandler) { OnAssetSelectedHandler = InHandler; } void SAnimationCompressionSelectionDialog::DoSelectAsset(const FAssetData& SelectedAsset) { bValidAssetChosen = true; OnAssetSelectedHandler.ExecuteIfBound(SelectedAsset); CloseDialog(); } FReply SAnimationCompressionSelectionDialog::OnConfirmClicked() { TArray SelectedAssets = GetCurrentSelectionDelegate.Execute(); if (SelectedAssets.Num() > 0) { DoSelectAsset(SelectedAssets[0]); } return FReply::Handled(); } FReply SAnimationCompressionSelectionDialog::OnCancelClicked() { CloseDialog(); return FReply::Handled(); } void SAnimationCompressionSelectionDialog::CloseDialog() { TSharedPtr ContainingWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); if (ContainingWindow.IsValid()) { ContainingWindow->RequestDestroyWindow(); } } void SAnimationCompressionSelectionDialog::OnAssetSelected(const FAssetData& AssetData) { CurrentlySelectedAssets = GetCurrentSelectionDelegate.Execute(); } void SAnimationCompressionSelectionDialog::OnAssetsActivated(const TArray& SelectedAssets, EAssetTypeActivationMethod::Type ActivationType) { const bool bCorrectActivationMethod = ActivationType == EAssetTypeActivationMethod::DoubleClicked || ActivationType == EAssetTypeActivationMethod::Opened; if (SelectedAssets.Num() > 0 && bCorrectActivationMethod) { DoSelectAsset(SelectedAssets[0]); } } bool SAnimationCompressionSelectionDialog::IsConfirmButtonEnabled() const { return CurrentlySelectedAssets.Num() > 0; } void SAnimationCompressionSelectionDialog::Construct(const FArguments& InArgs, const FAnimationCompressionSelectionDialogConfig& InConfig) { FAssetPickerConfig AssetPickerConfig; AssetPickerConfig.Filter.ClassPaths.Push(UAnimBoneCompressionSettings::StaticClass()->GetClassPathName()); AssetPickerConfig.Filter.bRecursiveClasses = true; AssetPickerConfig.InitialAssetViewType = EAssetViewType::List; AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateSP(this, &SAnimationCompressionSelectionDialog::OnAssetSelected); AssetPickerConfig.OnAssetsActivated = FOnAssetsActivated::CreateSP(this, &SAnimationCompressionSelectionDialog::OnAssetsActivated); AssetPickerConfig.GetCurrentSelectionDelegates.Add(&GetCurrentSelectionDelegate); AssetPickerConfig.SaveSettingsName = TEXT("AnimationCompressionSelectionDialog"); AssetPickerConfig.bCanShowFolders = false; AssetPickerConfig.bCanShowDevelopersFolder = true; AssetPickerConfig.bAllowNullSelection = false; AssetPickerConfig.bAllowDragging = false; AssetPickerConfig.SelectionMode = ESelectionMode::Single; AssetPickerConfig.bFocusSearchBoxWhenOpened = true; AssetPickerConfig.InitialAssetSelection = InConfig.DefaultSelectedAsset != nullptr ? InConfig.DefaultSelectedAsset : FAnimationUtils::GetDefaultAnimationBoneCompressionSettings(); AssetPickerConfig.bForceShowEngineContent = true; if (AssetPickerConfig.InitialAssetSelection.IsValid()) { CurrentlySelectedAssets.Add(AssetPickerConfig.InitialAssetSelection); } FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked(TEXT("ContentBrowser")); AssetPicker = ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig); // The root widget in this dialog. TSharedRef MainVerticalBox = SNew(SVerticalBox); // Asset view MainVerticalBox->AddSlot() .FillHeight(1) .Padding(0, 0, 0, 4) [ SNew(SSplitter) + SSplitter::Slot() .Value(0.75f) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) [ AssetPicker.ToSharedRef() ] ] ]; // Buttons and asset name TSharedRef ButtonsAndNameBox = SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Right) .VAlign(VAlign_Bottom) .Padding(4, 3) [ SNew(SButton) .Text(LOCTEXT("AnimationCompressionSelectionDialogSelectButton", "Select")) .ContentPadding(FMargin(8, 2, 8, 2)) .IsEnabled(this, &SAnimationCompressionSelectionDialog::IsConfirmButtonEnabled) .OnClicked(this, &SAnimationCompressionSelectionDialog::OnConfirmClicked) ] + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Bottom) .Padding(4, 3) [ SNew(SButton) .ContentPadding(FMargin(8, 2, 8, 2)) .Text(LOCTEXT("AnimationCompressionSelectionDialogCancelButton", "Cancel")) .OnClicked(this, &SAnimationCompressionSelectionDialog::OnCancelClicked) ]; MainVerticalBox->AddSlot() .AutoHeight() .HAlign(HAlign_Right) .Padding(0) [ ButtonsAndNameBox ]; ChildSlot [ MainVerticalBox ]; } ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// // Animation editor utility functions ///////////////////////////////////////////////////// namespace AnimationEditorUtils { FAssetData CreateModalAnimationCompressionSelectionDialog(const FAnimationCompressionSelectionDialogConfig& InConfig) { struct FModalResult { void OnAssetSelected(const FAssetData& SelectedAsset) { SavedResult = SelectedAsset; } FAssetData SavedResult; }; FModalResult ModalWindowResult; auto OnAssetSelectedDelegate = SAnimationCompressionSelectionDialog::FOnAssetSelected::CreateRaw(&ModalWindowResult, &FModalResult::OnAssetSelected); TSharedRef Dialog = SNew(SAnimationCompressionSelectionDialog, InConfig); Dialog->SetOnAssetSelected(OnAssetSelectedDelegate); const FVector2D DefaultWindowSize(400.0f, 500.0f); const FVector2D WindowSize = InConfig.WindowSizeOverride.IsZero() ? DefaultWindowSize : InConfig.WindowSizeOverride; const FText WindowTitle = InConfig.DialogTitleOverride.IsEmpty() ? LOCTEXT("GenericAnimationCompressionSelectionDialogWindowHeader", "Select compression settings") : InConfig.DialogTitleOverride; TSharedRef DialogWindow = SNew(SWindow) .Title(WindowTitle) .ClientSize(WindowSize); DialogWindow->SetContent(Dialog); IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked(TEXT("MainFrame")); const TSharedPtr& MainFrameParentWindow = MainFrameModule.GetParentWindow(); if (MainFrameParentWindow.IsValid()) { FSlateApplication::Get().AddModalWindow(DialogWindow, MainFrameParentWindow.ToSharedRef()); } return ModalWindowResult.SavedResult; } /** Creates a unique package and asset name taking the form InBasePackageName+InSuffix */ void CreateUniqueAssetName(const FString& InBasePackageName, const FString& InSuffix, FString& OutPackageName, FString& OutAssetName) { FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); AssetToolsModule.Get().CreateUniqueAssetName(InBasePackageName, InSuffix, OutPackageName, OutAssetName); } void CreateAnimationAssets(const TArray>& SkeletonsOrSkeletalMeshes, TSubclassOf AssetClass, const FString& InPrefix, FAnimAssetCreated AssetCreated, UObject* NameBaseObject /*= nullptr*/, bool bDoNotShowNameDialog /*= false*/, bool bAllowReplaceExisting /*= false*/) { TArray ObjectsToSync; for (auto SkelIt = SkeletonsOrSkeletalMeshes.CreateConstIterator(); SkelIt; ++SkelIt) { UObject* SkeletonOrSkeletalMeshObject = SkelIt->LoadSynchronous(); USkeletalMesh* SkeletalMesh = nullptr; USkeleton* Skeleton = Cast(SkeletonOrSkeletalMeshObject); if (Skeleton == nullptr) { SkeletalMesh = Cast(SkeletonOrSkeletalMeshObject); if (SkeletalMesh) { Skeleton = SkeletalMesh->GetSkeleton(); } else { UE_LOG(LogAnimation, Warning, TEXT("Invalid skeleton or skeletal mesh passed to CreateAnimationAssets. No asset will be generated.")); } } if (Skeleton) { FString Name; FString PackageName; FString AssetPath = (NameBaseObject)? NameBaseObject->GetOutermost()->GetName(): Skeleton->GetOutermost()->GetName(); // Determine an appropriate name CreateUniqueAssetName(AssetPath, InPrefix, PackageName, Name); if (bDoNotShowNameDialog == false) { // set the unique asset as a default name TSharedRef NewAnimDlg = SNew(SCreateAnimationAssetDlg) .DefaultAssetPath(FText::FromString(PackageName)); // show a dialog to determine a new asset name if (NewAnimDlg->ShowModal() == EAppReturnType::Cancel) { return; } PackageName = NewAnimDlg->GetFullAssetPath(); Name = NewAnimDlg->GetAssetName(); } // Create the asset, and assign its skeleton FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked("AssetTools"); UAnimationAsset* NewAsset = nullptr; if (bAllowReplaceExisting) { UPackage* ExistingPackage = FindPackage(nullptr, *PackageName); UObject* ExistingObject = StaticFindObject(AssetClass.Get(), ExistingPackage, *Name); if (ExistingObject) { EAppReturnType::Type UserResponse = FMessageDialog::Open( EAppMsgType::YesNo, FText::Format(LOCTEXT("CreateAnimationAssetsAlreadyExists", "Do you want to replace the existing asset?\n\nAn asset already exists at the import location: {0}"), FText::FromString(PackageName))); if (UserResponse == EAppReturnType::Yes) { NewAsset = Cast(ExistingObject); } else { return; } } } if (!NewAsset) { NewAsset = Cast(AssetToolsModule.Get().CreateAsset(Name, FPackageName::GetLongPackagePath(PackageName), AssetClass, nullptr)); } if(NewAsset) { NewAsset->SetSkeleton(Skeleton); if (UAnimSequenceBase* SequenceBase = Cast(NewAsset)) { SequenceBase->GetController().InitializeModel(); } if (SkeletalMesh) { NewAsset->SetPreviewMesh(SkeletalMesh); } NewAsset->MarkPackageDirty(); ObjectsToSync.Add(NewAsset); } } } if (AssetCreated.IsBound()) { if (!AssetCreated.Execute(ObjectsToSync)) { // Rename the objects we created out of the way for (UObject* ObjectToDelete : ObjectsToSync) { // Notify the asset registry FAssetRegistryModule::AssetDeleted(ObjectToDelete); ObjectToDelete->Rename(nullptr, GetTransientPackage(), REN_DontCreateRedirectors | REN_NonTransactional); } } } } void CreateNewAnimBlueprint(TArray> SkeletonsOrSkeletalMeshes, FAnimAssetCreated AssetCreated, bool bInContentBrowser) { TArray> SkeletonsOrSkeletalMeshesLoaded; for (TSoftObjectPtr& SkeletonsOrSkeletalMesh : SkeletonsOrSkeletalMeshes) { if (UObject* SkeletonOrSkeletalMeshObject = SkeletonsOrSkeletalMesh.LoadSynchronous()) { SkeletonsOrSkeletalMeshesLoaded.Add(SkeletonOrSkeletalMeshObject); } } CreateNewAnimBlueprint(SkeletonsOrSkeletalMeshesLoaded, AssetCreated, bInContentBrowser); } void CreateNewAnimBlueprint(TArray> SkeletonsOrSkeletalMeshes, FAnimAssetCreated AssetCreated, bool bInContentBrowser) { const FString DefaultSuffix = TEXT("_AnimBlueprint"); if (SkeletonsOrSkeletalMeshes.Num() == 1) { UObject* SkeletonOrSkeletalMeshObject = SkeletonsOrSkeletalMeshes[0].Get(); USkeletalMesh* SkeletalMesh = nullptr; USkeleton* Skeleton = Cast(SkeletonOrSkeletalMeshObject); if (Skeleton == nullptr) { SkeletalMesh = CastChecked(SkeletonOrSkeletalMeshObject); Skeleton = SkeletalMesh->GetSkeleton(); } if (Skeleton) { // Determine an appropriate name for inline-rename FString Name; FString PackageName; CreateUniqueAssetName(Skeleton->GetOutermost()->GetName(), DefaultSuffix, PackageName, Name); UAnimBlueprintFactory* Factory = NewObject(); Factory->TargetSkeleton = Skeleton; Factory->PreviewSkeletalMesh = SkeletalMesh; if (bInContentBrowser) { FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); ContentBrowserModule.Get().CreateNewAsset(Name, FPackageName::GetLongPackagePath(PackageName), UAnimBlueprint::StaticClass(), Factory); } else { FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked("AssetTools"); UAnimBlueprint* NewAsset = CastChecked(AssetToolsModule.Get().CreateAsset(Name, FPackageName::GetLongPackagePath(PackageName), UAnimBlueprint::StaticClass(), Factory)); if (NewAsset && AssetCreated.IsBound()) { TArray NewObjects; NewObjects.Add(NewAsset); if (!AssetCreated.Execute(NewObjects)) { //Destroy the assets we just create for (UObject* ObjectToDelete : NewObjects) { ObjectToDelete->ClearFlags(RF_Standalone | RF_Public); ObjectToDelete->RemoveFromRoot(); ObjectToDelete->MarkAsGarbage(); } CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); } } } } } else { TArray AssetsToSync; for (auto ObjIt = SkeletonsOrSkeletalMeshes.CreateConstIterator(); ObjIt; ++ObjIt) { USkeletalMesh* SkeletalMesh = nullptr; USkeleton* Skeleton = Cast(ObjIt->Get()); if (Skeleton == nullptr) { SkeletalMesh = CastChecked(ObjIt->Get()); Skeleton = SkeletalMesh->GetSkeleton(); } if(Skeleton) { // Determine an appropriate name FString Name; FString PackageName; CreateUniqueAssetName(Skeleton->GetOutermost()->GetName(), DefaultSuffix, PackageName, Name); // Create the anim blueprint factory used to generate the asset UAnimBlueprintFactory* Factory = NewObject(); Factory->TargetSkeleton = Skeleton; Factory->PreviewSkeletalMesh = SkeletalMesh; FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked("AssetTools"); UObject* NewAsset = AssetToolsModule.Get().CreateAsset(Name, FPackageName::GetLongPackagePath(PackageName), UAnimBlueprint::StaticClass(), Factory); if (NewAsset) { AssetsToSync.Add(NewAsset); } } } if (AssetCreated.IsBound()) { if (!AssetCreated.Execute(AssetsToSync)) { //Destroy the assets we just create for (UObject* ObjectToDelete : AssetsToSync) { ObjectToDelete->ClearFlags(RF_Standalone | RF_Public); ObjectToDelete->RemoveFromRoot(); ObjectToDelete->MarkAsGarbage(); } CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); } } } } bool CanCreateAssetOfType(const UClass* InClass) { IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); return AssetTools.IsAssetClassSupported(InClass); } void FillCreateAssetMenu(FMenuBuilder& MenuBuilder, const TArray>& SkeletonsOrSkeletalMeshes, FAnimAssetCreated AssetCreated, bool bInContentBrowser) { const bool bAllowReplaceExisting = false; MenuBuilder.BeginSection("CreateAnimAssets", LOCTEXT("CreateAnimAssetsMenuHeading", "Anim Assets")); { if(CanCreateAssetOfType(UAnimBlueprint::StaticClass())) { // only allow for content browser until we support multi assets so we can open new persona with this BP MenuBuilder.AddMenuEntry( LOCTEXT("Skeleton_NewAnimBlueprint", "Anim Blueprint"), LOCTEXT("Skeleton_NewAnimBlueprintTooltip", "Creates an Anim Blueprint using the selected skeleton."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.AnimBlueprint"), FUIAction( FExecuteAction::CreateStatic(&CreateNewAnimBlueprint, SkeletonsOrSkeletalMeshes, AssetCreated, bInContentBrowser), FCanExecuteAction() ) ); } if(CanCreateAssetOfType(UAnimComposite::StaticClass())) { MenuBuilder.AddMenuEntry( LOCTEXT("Skeleton_NewAnimComposite", "Anim Composite"), LOCTEXT("Skeleton_NewAnimCompositeTooltip", "Creates an AnimComposite using the selected skeleton."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.AnimComposite"), FUIAction( FExecuteAction::CreateStatic(&ExecuteNewAnimAsset, SkeletonsOrSkeletalMeshes, FString("_Composite"), AssetCreated, bInContentBrowser, bAllowReplaceExisting), FCanExecuteAction() ) ); } if(CanCreateAssetOfType(UAnimMontage::StaticClass())) { MenuBuilder.AddMenuEntry( LOCTEXT("Skeleton_NewAnimMontage", "Anim Montage"), LOCTEXT("Skeleton_NewAnimMontageTooltip", "Creates an AnimMontage using the selected skeleton."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.AnimMontage"), FUIAction( FExecuteAction::CreateStatic(&ExecuteNewAnimAsset, SkeletonsOrSkeletalMeshes, FString("_Montage"), AssetCreated, bInContentBrowser, bAllowReplaceExisting), FCanExecuteAction() ) ); } } MenuBuilder.EndSection(); MenuBuilder.BeginSection("CreateBlendSpace", LOCTEXT("CreateBlendSpaceMenuHeading", "Blend Spaces")); { if (CanCreateAssetOfType(UBlendSpace::StaticClass())) { MenuBuilder.AddMenuEntry( LOCTEXT("SkeletalMesh_New2DBlendspace", "Blend Space"), LOCTEXT("SkeletalMesh_New2DBlendspaceTooltip", "Creates a Blend Space using the selected skeleton."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.BlendSpace"), FUIAction( FExecuteAction::CreateStatic(&ExecuteNewAnimAsset, SkeletonsOrSkeletalMeshes, FString("_BlendSpace"), AssetCreated, bInContentBrowser, bAllowReplaceExisting), FCanExecuteAction() ) ); } if (CanCreateAssetOfType(UBlendSpace1D::StaticClass())) { MenuBuilder.AddMenuEntry( LOCTEXT("SkeletalMesh_New1DBlendspace", "Blend Space 1D"), LOCTEXT("SkeletalMesh_New1DBlendspaceTooltip", "Creates a 1D Blend Space using the selected skeleton."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.BlendSpace1D"), FUIAction( FExecuteAction::CreateStatic(&ExecuteNewAnimAsset, SkeletonsOrSkeletalMeshes, FString("_BlendSpace1D"), AssetCreated, bInContentBrowser, bAllowReplaceExisting), FCanExecuteAction() ) ); } } MenuBuilder.EndSection(); MenuBuilder.BeginSection("CreateAimOffset", LOCTEXT("CreateAimOffsetMenuHeading", "Aim Offsets")); { if (CanCreateAssetOfType(UAimOffsetBlendSpace::StaticClass())) { MenuBuilder.AddMenuEntry( LOCTEXT("SkeletalMesh_New2DAimOffset", "Aim Offset"), LOCTEXT("SkeletalMesh_New2DAimOffsetTooltip", "Creates a Aim Offset blendspace using the selected skeleton."), FSlateIcon(), FUIAction( FExecuteAction::CreateStatic(&ExecuteNewAnimAsset, SkeletonsOrSkeletalMeshes, FString("_AimOffset2D"), AssetCreated, bInContentBrowser, bAllowReplaceExisting), FCanExecuteAction() ) ); } if (CanCreateAssetOfType(UAimOffsetBlendSpace1D::StaticClass())) { MenuBuilder.AddMenuEntry( LOCTEXT("SkeletalMesh_New1DAimOffset", "Aim Offset 1D"), LOCTEXT("SkeletalMesh_New1DAimOffsetTooltip", "Creates a 1D Aim Offset blendspace using the selected skeleton."), FSlateIcon(), FUIAction( FExecuteAction::CreateStatic(&ExecuteNewAnimAsset, SkeletonsOrSkeletalMeshes, FString("_AimOffset1D"), AssetCreated, bInContentBrowser, bAllowReplaceExisting), FCanExecuteAction() ) ); } } MenuBuilder.EndSection(); } bool ApplyCompressionAlgorithm(TArray& AnimSequencePtrs, UAnimBoneCompressionSettings* OverrideSettings) { const bool bProceed = (AnimSequencePtrs.Num() > 1)? EAppReturnType::Yes == FMessageDialog::Open(EAppMsgType::YesNo, FText::Format(NSLOCTEXT("UnrealEd", "AboutToCompressAnimations_F", "About to compress {0} animations. Proceed?"), FText::AsNumber(AnimSequencePtrs.Num()))) : true; if(bProceed) { GWarn->BeginSlowTask(LOCTEXT("AnimCompressing", "Compressing"), true); { UE::Anim::Compression::FAnimationCompressionMemorySummaryScope Scope; for (UAnimSequence* AnimSeq : AnimSequencePtrs) { if (OverrideSettings != nullptr) { AnimSeq->BoneCompressionSettings = OverrideSettings; } // Clear CompressCommandletVersion so we can recompress these animations later. AnimSeq->CompressCommandletVersion = 0; AnimSeq->ClearAllCachedCookedPlatformData(); AnimSeq->CacheDerivedDataForCurrentPlatform(); } } GWarn->EndSlowTask(); return true; } return false; } bool IsAnimGraph(UEdGraph* Graph) { return Cast(Graph) != nullptr; } void RegenerateSubGraphArrays(UAnimBlueprint* Blueprint) { // The anim graph should be the first function graph on the blueprint if(Blueprint->FunctionGraphs.Num() > 0) { if(UAnimationGraph* AnimGraph = Cast(Blueprint->FunctionGraphs[0])) { RegenerateGraphSubGraphs(Blueprint, AnimGraph); } } } void RegenerateGraphSubGraphs(UAnimBlueprint* OwningBlueprint, UEdGraph* GraphToFix) { TArray ChildGraphs; FindChildGraphsFromNodes(GraphToFix, ChildGraphs); for(UEdGraph* Child : ChildGraphs) { RegenerateGraphSubGraphs(OwningBlueprint, Child); } if(ChildGraphs != GraphToFix->SubGraphs) { UE_LOG(LogAnimation, Log, TEXT("Fixed missing or duplicated graph entries in SubGraph array for graph %s in AnimBP %s"), *GraphToFix->GetName(), *OwningBlueprint->GetName()); GraphToFix->SubGraphs = ChildGraphs; } } void RemoveDuplicateSubGraphs(UEdGraph* GraphToClean) { TArray NewSubGraphArray; for(UEdGraph* SubGraph : GraphToClean->SubGraphs) { NewSubGraphArray.AddUnique(SubGraph); } if(NewSubGraphArray.Num() != GraphToClean->SubGraphs.Num()) { GraphToClean->SubGraphs = NewSubGraphArray; } } void FindChildGraphsFromNodes(UEdGraph* GraphToSearch, TArray& ChildGraphs) { for(UEdGraphNode* CurrentNode : GraphToSearch->Nodes) { if(UAnimGraphNode_StateMachineBase* StateMachine = Cast(CurrentNode)) { ChildGraphs.AddUnique(StateMachine->EditorStateMachineGraph); } else if(UAnimStateNodeBase* StateNode = Cast(CurrentNode)) { UEdGraph* BoundGraph = StateNode->GetBoundGraph(); if (BoundGraph == nullptr) { continue; } ChildGraphs.AddUnique(BoundGraph); if(UAnimStateTransitionNode* TransitionNode = Cast(StateNode)) { if(TransitionNode->CustomTransitionGraph) { ChildGraphs.AddUnique(TransitionNode->CustomTransitionGraph); } } } else if(UK2Node_Composite* CompositeNode = Cast(CurrentNode)) { ChildGraphs.AddUnique(CompositeNode->BoundGraph); } } } static FOnPoseWatchesChanged OnPoseWatchesChangedDelegate; void SetPoseWatch(UPoseWatch* PoseWatch, UAnimBlueprint* AnimBlueprintIfKnown) { #if WITH_EDITORONLY_DATA if (UAnimGraphNode_Base* TargetNode = Cast(PoseWatch->Node)) { UAnimBlueprint* AnimBlueprint = AnimBlueprintIfKnown ? AnimBlueprintIfKnown : Cast(FBlueprintEditorUtils::FindBlueprintForNode(TargetNode)); if ((AnimBlueprint != nullptr) && (AnimBlueprint->GeneratedClass != nullptr)) { if (UAnimBlueprintGeneratedClass* AnimBPGenClass = Cast(*AnimBlueprint->GeneratedClass)) { // Find the insertion point from the debugging data const int32 LinkID = AnimBPGenClass->GetLinkIDForNode(TargetNode); for (const TObjectPtr& PoseWatchElement : PoseWatch->GetElements()) { if (UPoseWatchPoseElement* PoseWatchPoseElement = Cast(PoseWatchElement.Get())) { AnimBPGenClass->GetAnimBlueprintDebugData().AddPoseWatch(LinkID, PoseWatchPoseElement); } } OnPoseWatchesChangedDelegate.Broadcast(AnimBlueprint, TargetNode); } } } #endif } void RemovePoseWatchesFromGraph(UAnimBlueprint* AnimBlueprint, class UEdGraph* Graph) { #if WITH_EDITORONLY_DATA for (UEdGraphNode* Node : Graph->Nodes) { RemovePoseWatchFromNode(Node, AnimBlueprint); } #endif } UPoseWatch* FindPoseWatchForNode(const UEdGraphNode* Node, UAnimBlueprint* AnimBlueprintIfKnown) { #if WITH_EDITORONLY_DATA UAnimBlueprint* AnimBlueprint = AnimBlueprintIfKnown ? AnimBlueprintIfKnown : Cast(FBlueprintEditorUtils::FindBlueprintForNode(Node)); if(AnimBlueprint) { // iterate backwards so we can remove invalid pose watches as we go for (int32 Index = AnimBlueprint->PoseWatches.Num() - 1; Index >= 0; --Index) { UPoseWatch* PoseWatch = AnimBlueprint->PoseWatches[Index]; if (PoseWatch == nullptr || PoseWatch->Node == nullptr) { AnimBlueprint->PoseWatches.RemoveAtSwap(Index); continue; } // Return this pose watch if the node location matches the given node if (PoseWatch->Node == Node) { return PoseWatch; } } } return nullptr; #endif } UPoseWatch* MakePoseWatchForNode(UAnimBlueprint* AnimBlueprint, UEdGraphNode* Node) { #if WITH_EDITORONLY_DATA check(CastChecked(Node)->IsPoseWatchable()); UPoseWatch* NewPoseWatch = NewObject(AnimBlueprint); NewPoseWatch->Node = Node; NewPoseWatch->SetUniqueDefaultLabel(); NewPoseWatch->AddElement(LOCTEXT("PoseWatchElementLabel_PoseWatch", "Pose Watch"), TEXT("AnimGraph.PoseWatch.Icon")); AnimBlueprint->PoseWatches.Add(NewPoseWatch); SetPoseWatch(NewPoseWatch, AnimBlueprint); return NewPoseWatch; #else return nullptr; #endif } void RemovePoseWatch(UPoseWatch* PoseWatch, UAnimBlueprint* AnimBlueprintIfKnown) { #if WITH_EDITORONLY_DATA if (UAnimGraphNode_Base* TargetNode = Cast(PoseWatch->Node)) { UAnimBlueprint* AnimBlueprint = AnimBlueprintIfKnown ? AnimBlueprintIfKnown : Cast(FBlueprintEditorUtils::FindBlueprintForNode(TargetNode)); if (AnimBlueprint) { AnimBlueprint->PoseWatches.Remove(PoseWatch); if (UAnimBlueprintGeneratedClass* AnimBPGenClass = AnimBlueprint->GetAnimBlueprintGeneratedClass()) { int32 LinkID = AnimBPGenClass->GetLinkIDForNode(Cast(PoseWatch->Node)); AnimBPGenClass->GetAnimBlueprintDebugData().RemovePoseWatch(LinkID); OnPoseWatchesChangedDelegate.Broadcast(AnimBlueprint, TargetNode); } } } #endif } void RemovePoseWatchFromNode(UEdGraphNode* Node, UAnimBlueprint* AnimBlueprint) { #if WITH_EDITORONLY_DATA if (AnimBlueprint) { for (UPoseWatch* SomePoseWatch : AnimBlueprint->PoseWatches) { if (SomePoseWatch->Node == Node) { if (UAnimBlueprintGeneratedClass* AnimBPGenClass = AnimBlueprint->GetAnimBlueprintGeneratedClass()) { int32 LinkID = AnimBPGenClass->GetLinkIDForNode(Cast(SomePoseWatch->Node)); AnimBPGenClass->GetAnimBlueprintDebugData().RemovePoseWatch(LinkID); OnPoseWatchesChangedDelegate.Broadcast(AnimBlueprint, Node); } SomePoseWatch->OnRemoved(); return; } } } #endif } FOnPoseWatchesChanged& OnPoseWatchesChanged() { return OnPoseWatchesChangedDelegate; } int32 GetPoseWatchNodeLinkID(UPoseWatch* PoseWatch, UAnimBlueprintGeneratedClass*& OutAnimBPGenClass) { #if WITH_EDITORONLY_DATA if (UAnimGraphNode_Base* TargetNode = Cast(PoseWatch->Node)) { UAnimBlueprint* AnimBlueprint = Cast(FBlueprintEditorUtils::FindBlueprintForNode(TargetNode)); if ((AnimBlueprint != nullptr) && (AnimBlueprint->GeneratedClass != nullptr)) { if (UAnimBlueprintGeneratedClass* AnimBPGenClass = Cast(*AnimBlueprint->GeneratedClass)) { // Find the insertion point from the debugging data OutAnimBPGenClass = AnimBPGenClass; return AnimBPGenClass->GetLinkIDForNode(TargetNode); } } } #endif return INDEX_NONE; } void SetupDebugLinkedAnimInstances(UAnimBlueprint* InAnimBlueprint, UObject* InRootObjectBeingDebugged) { check(IsInGameThread()); static bool bSettingDebugInstances = false; if(!bSettingDebugInstances) { TGuardValue GuardValue(bSettingDebugInstances, true); if(InRootObjectBeingDebugged) { if(const USkeletalMeshComponent* Component = Cast(InRootObjectBeingDebugged->GetOuter())) { // See if we have any linked instances const TArray LinkedInstances = Component->GetLinkedAnimInstances(); for(UAnimInstance* LinkedInstance : LinkedInstances) { if(UAnimBlueprint* LinkedAnimBlueprint = Cast(LinkedInstance->GetClass()->ClassGeneratedBy)) { LinkedAnimBlueprint->SetObjectBeingDebugged(LinkedInstance); } } } } else if(UObject* OldDebuggedObject = InAnimBlueprint->GetObjectBeingDebugged()) { if(const USkeletalMeshComponent* Component = Cast(OldDebuggedObject->GetOuter())) { // See if we have any linked instances const TArray LinkedInstances = Component->GetLinkedAnimInstances(); for(UAnimInstance* LinkedInstance : LinkedInstances) { if(UAnimBlueprint* LinkedAnimBlueprint = Cast(LinkedInstance->GetClass()->ClassGeneratedBy)) { LinkedAnimBlueprint->SetObjectBeingDebugged(nullptr); } } } } } } } #undef LOCTEXT_NAMESPACE