// Copyright Epic Games, Inc. All Rights Reserved. #include "AnimationBlueprintEditor.h" #include "Algo/Transform.h" #include "AnimGraphCommands.h" #include "AnimGraphNode_AimOffsetLookAt.h" #include "AnimGraphNode_Base.h" #include "AnimGraphNode_BlendListByInt.h" #include "AnimGraphNode_BlendSpaceEvaluator.h" #include "AnimGraphNode_BlendSpaceGraph.h" #include "AnimGraphNode_BlendSpacePlayer.h" #include "AnimGraphNode_LayeredBoneBlend.h" #include "AnimGraphNode_LinkedAnimGraphBase.h" #include "AnimGraphNode_MultiWayBlend.h" #include "AnimGraphNode_PoseBlendNode.h" #include "AnimGraphNode_PoseByName.h" #include "AnimGraphNode_RotationOffsetBlendSpace.h" #include "AnimGraphNode_RotationOffsetBlendSpaceGraph.h" #include "AnimGraphNode_SequenceEvaluator.h" #include "AnimGraphNode_SequencePlayer.h" #include "AnimNodes/AnimNode_AimOffsetLookAt.h" #include "AnimNodes/AnimNode_BlendSpaceEvaluator.h" #include "AnimNodes/AnimNode_BlendSpacePlayer.h" #include "AnimNodes/AnimNode_PoseBlendNode.h" #include "AnimNodes/AnimNode_PoseByName.h" #include "AnimNodes/AnimNode_RotationOffsetBlendSpace.h" #include "AnimNodes/AnimNode_SequenceEvaluator.h" #include "AnimPreviewInstance.h" #include "AnimStateEntryNode.h" #include "AnimStateNodeBase.h" #include "Animation/AnimBlueprint.h" #include "Animation/AnimInstance.h" #include "Animation/AnimNode_SequencePlayer.h" #include "Animation/AnimNotifies/AnimNotifyState.h" #include "Animation/AnimNotifyQueue.h" #include "Animation/AnimSequenceBase.h" #include "Animation/AnimTypes.h" #include "Animation/AnimationAsset.h" #include "Animation/BlendSpace.h" #include "Animation/DebugSkelMeshComponent.h" #include "AnimationBlueprintEditorMode.h" #include "AnimationBlueprintEditorModule.h" #include "AnimationBlueprintEditorSettings.h" #include "AnimationBlueprintInterfaceEditorMode.h" #include "AnimationEditorUtils.h" #include "AnimationGraph.h" #include "AnimationGraphSchema.h" #include "AssetRegistry/AssetData.h" #include "BlendSpaceDocumentTabFactory.h" #include "BlendSpaceGraph.h" #include "Blueprint/BlueprintExceptionInfo.h" #include "Components/SkeletalMeshComponent.h" #include "Containers/EnumAsByte.h" #include "CoreGlobals.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "EdGraphNode_Comment.h" #include "EdGraphSchema_K2.h" #include "EdGraphSchema_K2_Actions.h" #include "EdGraphUtilities.h" #include "Editor.h" #include "Editor/EditorEngine.h" #include "EditorFontGlyphs.h" #include "EditorReimportHandler.h" #include "Engine/Blueprint.h" #include "Engine/PoseWatch.h" #include "Engine/World.h" #include "Fonts/SlateFontInfo.h" #include "Framework/Commands/UIAction.h" #include "Framework/Commands/UICommandList.h" #include "Framework/Docking/TabManager.h" #include "Framework/MultiBox/MultiBoxExtender.h" #include "HAL/PlatformCrt.h" #include "IAnimationBlueprintEditorModule.h" #include "IAssetFamily.h" #include "IDetailsView.h" #include "IPersonaEditorModeManager.h" #include "IPersonaPreviewScene.h" #include "IPersonaToolkit.h" #include "IPersonaViewport.h" #include "ISkeletonEditorModule.h" #include "ISkeletonTree.h" #include "ISkeletonTreeItem.h" #include "Input/Reply.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/DebuggerCommands.h" #include "Layout/Visibility.h" #include "Logging/TokenizedMessage.h" #include "Math/UnrealMathSSE.h" #include "Math/Vector2D.h" #include "Misc/Attribute.h" #include "Modules/ModuleManager.h" #include "PersonaCommonCommands.h" #include "PersonaDelegates.h" #include "PersonaModule.h" #include "PersonaToolMenuContext.h" #include "PersonaUtils.h" // Hide related nodes feature #include "AnimationBlueprintToolMenuContext.h" #include "DetailLayoutBuilder.h" #include "Preferences/AnimationBlueprintEditorOptions.h" #include "PropertyEditorDelegates.h" #include "SBlueprintEditorToolbar.h" #include "SKismetInspector.h" #include "SSingleObjectDetailsPanel.h" #include "ScopedTransaction.h" #include "Settings/AnimBlueprintSettings.h" #include "SlotBase.h" #include "Styling/AppStyle.h" #include "Styling/ISlateStyle.h" #include "Styling/SlateColor.h" #include "Subsystems/AssetEditorSubsystem.h" #include "Subsystems/ImportSubsystem.h" #include "TabPayload_BlendSpaceGraph.h" #include "Templates/Casts.h" #include "Templates/SubclassOf.h" #include "ToolMenuContext.h" #include "ToolMenus.h" #include "Toolkits/AssetEditorToolkit.h" #include "UObject/Object.h" #include "UObject/ObjectPtr.h" #include "UObject/UObjectGlobals.h" #include "UObject/WeakObjectPtr.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Input/SButton.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/SBoxPanel.h" #include "Widgets/Text/STextBlock.h" #include "WorkflowOrientedApp/WorkflowTabManager.h" class FEditorModeTools; class FToolBarBuilder; class IAnimationSequenceBrowser; class SDockTab; class SWidget; #define LOCTEXT_NAMESPACE "AnimationBlueprintEditor" const FName AnimationBlueprintEditorAppName(TEXT("AnimationBlueprintEditorApp")); const FName FAnimationBlueprintEditorModes::AnimationBlueprintEditorMode("GraphName"); // For backwards compatibility we keep the old mode name here const FName FAnimationBlueprintEditorModes::AnimationBlueprintInterfaceEditorMode("Interface"); const FName FAnimationBlueprintEditorModes::AnimationBlueprintTemplateEditorMode("Template"); namespace AnimationBlueprintEditorTabs { const FName DetailsTab(TEXT("DetailsTab")); const FName SkeletonTreeTab(TEXT("SkeletonTreeView")); const FName ViewportTab(TEXT("Viewport")); const FName AdvancedPreviewTab(TEXT("AdvancedPreviewTab")); const FName AssetBrowserTab(TEXT("SequenceBrowser")); const FName AnimBlueprintPreviewEditorTab(TEXT("AnimBlueprintPreviewEditor")); const FName AssetOverridesTab(TEXT("AnimBlueprintParentPlayerEditor")); const FName SlotNamesTab(TEXT("SkeletonSlotNames")); const FName CurveNamesTab(TEXT("AnimCurveViewerTab")); const FName PoseWatchTab(TEXT("PoseWatchManager")); const FName FindReplaceTab(TEXT("FindReplaceTab")); }; ///////////////////////////////////////////////////// // SortedContainerDifference /** Algorithm to find the difference between two sorted sets of unique values - outputs two sets, all the elements that are in set A but not in set B and all the elements that are in set B but not in set A **/ template void SortedContainerDifference(const TContainerType& LhsContainer, const TContainerType& RhsContainer, TContainerType& OutLhsDifference, TContainerType& OutRhsDifference, const TPredicate& SortPredicate) { for (unsigned int LhsIndex = 0, RhsIndex = 0, LhsMax = LhsContainer.Num(), RhsMax = RhsContainer.Num(); (LhsIndex < LhsMax) || (RhsIndex < RhsMax); ) { if ((LhsIndex < LhsMax) && (!(RhsIndex < RhsMax) || SortPredicate(LhsContainer[LhsIndex], RhsContainer[RhsIndex]))) { OutRhsDifference.Add(LhsContainer[LhsIndex]); ++LhsIndex; } else if ((RhsIndex < RhsMax) && (!(LhsIndex < LhsMax) || SortPredicate(RhsContainer[RhsIndex], LhsContainer[LhsIndex]))) { OutLhsDifference.Add(RhsContainer[RhsIndex]); ++RhsIndex; } else { ++LhsIndex; ++RhsIndex; } } } void FAnimationBlueprintEditor::NotifyAllNodesOnSelection(const bool bInIsSelected) { FEditorModeTools& ModeTools = GetEditorModeManager(); for (TWeakObjectPtr< class UAnimGraphNode_Base > CurrentAnimGraphNode : SelectedAnimGraphNodes) { UAnimGraphNode_Base* const CurrentAnimGraphNodePtr = CurrentAnimGraphNode.Get(); FAnimNode_Base* const PreviewNode = FindAnimNode(CurrentAnimGraphNodePtr); // Note: Potentially passing a null PreviewNode ptr when bInIsSelected is false is required to de-select nodes that no longer exist. if (CurrentAnimGraphNodePtr && (!bInIsSelected || PreviewNode)) { CurrentAnimGraphNodePtr->OnNodeSelected(bInIsSelected, ModeTools, PreviewNode); } } } void FAnimationBlueprintEditor::NotifyAllNodesOnPoseWatchChanged(const bool IsPoseWatchEnabled) { UAnimBlueprint* const AnimBP = GetAnimBlueprint(); if (AnimBP) { FEditorModeTools& ModeTools = GetEditorModeManager(); for (const TObjectPtr& CurrentPoseWatch : AnimBP->PoseWatches) { UAnimGraphNode_Base* const CurrentAnimGraphNodePtr = Cast(CurrentPoseWatch->Node.Get()); FAnimNode_Base* const PreviewNode = FindAnimNode(CurrentAnimGraphNodePtr); // Note: Potentially passing a null PreviewNode ptr when IsPoseWatchEnabled is false is required to un-watch nodes that no longer exist. if (CurrentAnimGraphNodePtr && (!IsPoseWatchEnabled || PreviewNode)) { CurrentAnimGraphNodePtr->OnPoseWatchChanged(IsPoseWatchEnabled, CurrentPoseWatch, ModeTools, PreviewNode); } } } } void FAnimationBlueprintEditor::ReleaseAllManagedNodes() { NotifyAllNodesOnPoseWatchChanged(false); NotifyAllNodesOnSelection(false); } void FAnimationBlueprintEditor::AcquireAllManagedNodes() { NotifyAllNodesOnPoseWatchChanged(true); NotifyAllNodesOnSelection(true); } ///////////////////////////////////////////////////// // SAnimBlueprintPreviewPropertyEditor class SAnimBlueprintPreviewPropertyEditor : public SSingleObjectDetailsPanel { public: SLATE_BEGIN_ARGS(SAnimBlueprintPreviewPropertyEditor) {} SLATE_END_ARGS() private: // Pointer back to owning Persona editor instance (the keeper of state) TWeakPtr AnimationBlueprintEditorPtr; public: void Construct(const FArguments& InArgs, TSharedPtr InAnimationBlueprintEditor) { AnimationBlueprintEditorPtr = InAnimationBlueprintEditor; SSingleObjectDetailsPanel::Construct(SSingleObjectDetailsPanel::FArguments().HostCommandList(InAnimationBlueprintEditor->GetToolkitCommands()).HostTabManager(InAnimationBlueprintEditor->GetTabManager()), /*bAutomaticallyObserveViaGetObjectToObserve*/ true, /*bAllowSearch*/ true); PropertyView->SetIsPropertyEditingEnabledDelegate(FIsPropertyEditingEnabled::CreateStatic([] { return !GIntraFrameDebuggingGameThread; })); } // SSingleObjectDetailsPanel interface virtual UObject* GetObjectToObserve() const override { if (UDebugSkelMeshComponent* PreviewMeshComponent = AnimationBlueprintEditorPtr.Pin()->GetPersonaToolkit()->GetPreviewMeshComponent()) { return PreviewMeshComponent->GetAnimInstance(); } return nullptr; } virtual TSharedRef PopulateSlot(TSharedRef PropertyEditorWidget) override { return SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .Padding(0.f, 8.f, 0.f, 0.f) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("Persona.PreviewPropertiesWarning")) [ SNew(STextBlock) .Text(LOCTEXT("AnimBlueprintEditPreviewText", "Changes to preview options are not saved in the asset.")) .Font(FAppStyle::GetFontStyle("PropertyWindow.NormalFont")) .ShadowColorAndOpacity(FLinearColor::Black.CopyWithNewOpacity(0.3f)) .ShadowOffset(FVector2D::UnitVector) ] ] +SVerticalBox::Slot() .FillHeight(1) [ PropertyEditorWidget ]; } // End of SSingleObjectDetailsPanel interface }; ///////////////////////////////////////////////////// // FAnimationBlueprintEditor FAnimationBlueprintEditor::FAnimationBlueprintEditor() : PersonaMeshDetailLayout(nullptr) , DebuggedMeshComponent(nullptr) { GEditor->OnBlueprintPreCompile().AddRaw(this, &FAnimationBlueprintEditor::OnBlueprintPreCompile); LastGraphPinType.ResetToDefaults(); LastGraphPinType.PinCategory = UEdGraphSchema_K2::PC_Boolean; } FAnimationBlueprintEditor::~FAnimationBlueprintEditor() { // Stop watching the settings UAnimationBlueprintEditorSettings* AnimationBlueprintEditorSettings = GetMutableDefault(); AnimationBlueprintEditorSettings->UnregisterOnUpdateSettings(AnimationBlueprintEditorSettingsChangedHandle); // Remove all Pose Watches that were created as a result of selection, otherwise if the editor options are changed // they will still be active if we get recreated even though the nodes won't be selected. RemoveAllSelectionPoseWatches(); GEditor->OnBlueprintPreCompile().RemoveAll(this); GEditor->GetEditorSubsystem()->OnAssetPostImport.RemoveAll(this); FReimportManager::Instance()->OnPostReimport().RemoveAll(this); // NOTE: Any tabs that we still have hanging out when destroyed will be cleaned up by FBaseToolkit's destructor SaveEditorSettings(); // Explicit Reset of the PersonaToolKit to force destruction of the PreviewScene // This will call PreviewWorld->CleanupWorld() while the EditorModeManager still has the PreviewScene PersonaToolkit.Reset(); // Now we have to clean the PersonaToolkit PreviewScene from the EditorModeManager, as it has been destroyed. // This avoids a memory after delete use when any additional PreviewScene calls PreviewWorld->CleanupWorld(), // as that function executes a callback that the EditorModeManager is registered to. FEditorModeTools& ModeTools = GetEditorModeManager(); ((FAssetEditorModeManager&)ModeTools).SetPreviewScene(nullptr); FBlueprintCoreDelegates::OnScriptException.Remove(ScriptExceptionHandle); } void FAnimationBlueprintEditor::HandleUpdateSettings(const UAnimationBlueprintEditorSettings* AnimationBlueprintEditorSettings, EPropertyChangeType::Type ChangeType) { if (AnimationBlueprintEditorSettings->bPoseWatchSelectedNodes != bPreviousPoseWatchSelectedNodes) { bPreviousPoseWatchSelectedNodes = AnimationBlueprintEditorSettings->bPoseWatchSelectedNodes; RemoveAllSelectionPoseWatches(); if (AnimationBlueprintEditorSettings->bPoseWatchSelectedNodes) { HandlePoseWatchSelectedNodes(); } } } UAnimBlueprint* FAnimationBlueprintEditor::GetAnimBlueprint() const { return Cast(GetBlueprintObj()); } void FAnimationBlueprintEditor::ExtendMenu() { if(MenuExtender.IsValid()) { RemoveMenuExtender(MenuExtender); MenuExtender.Reset(); } MenuExtender = MakeShareable(new FExtender); AddMenuExtender(MenuExtender); // add extensible menu if exists FAnimationBlueprintEditorModule& AnimationBlueprintEditorModule = FModuleManager::LoadModuleChecked("AnimationBlueprintEditor"); AddMenuExtender(AnimationBlueprintEditorModule.GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); FToolMenuOwnerScoped OwnerScoped(this); // Add in Editor Specific functionality static const FName MenuName = GetToolMenuName(); static const FName ToolsMenuName = *(MenuName.ToString() + TEXT(".") + TEXT("Tools")); UToolMenu* ToolsMenu = UToolMenus::Get()->ExtendMenu(ToolsMenuName); const FToolMenuInsert SectionInsertLocation("Programming", EToolMenuInsertType::Before); UAnimBlueprint* AnimBlueprint = PersonaToolkit->GetAnimBlueprint(); if (AnimBlueprint && AnimBlueprint->BlueprintType != BPTYPE_Interface && !AnimBlueprint->bIsTemplate) { ToolsMenu->AddDynamicSection("Persona", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InToolMenu) { TSharedPtr AnimationBlueprintEditor = GetAnimationBlueprintEditor(InToolMenu->Context); if (AnimationBlueprintEditor.IsValid() && AnimationBlueprintEditor->PersonaToolkit.IsValid()) { FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked("Persona"); FPersonaModule::FCommonToolbarExtensionArgs Args; Args.bPreviewAnimation = false; Args.bPreviewMesh = true; Args.bReferencePose = false; Args.bCreateAsset = true; PersonaModule.AddCommonMenuExtensions(InToolMenu, Args); } }), SectionInsertLocation); } } void FAnimationBlueprintEditor::RegisterMenus() { FBlueprintEditor::RegisterMenus(); } void FAnimationBlueprintEditor::InitAnimationBlueprintEditor(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UAnimBlueprint* InAnimBlueprint) { // Record if we have been newly created bool bNewlyCreated = InAnimBlueprint->bIsNewlyCreated; InAnimBlueprint->bIsNewlyCreated = false; if (!Toolbar.IsValid()) { Toolbar = MakeShareable(new FBlueprintEditorToolbar(SharedThis(this))); } LoadEditorSettings(); GetToolkitCommands()->Append(FPlayWorldCommands::GlobalPlayWorldActions.ToSharedRef()); FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked("Persona"); PersonaToolkit = PersonaModule.CreatePersonaToolkit(InAnimBlueprint, FPersonaToolkitArgs()); PersonaToolkit->GetPreviewScene()->SetDefaultAnimationMode(EPreviewSceneDefaultAnimationMode::AnimationBlueprint); PersonaToolkit->GetPreviewScene()->RegisterOnPreviewMeshChanged(FOnPreviewMeshChanged::CreateSP(this, &FAnimationBlueprintEditor::HandlePreviewMeshChanged)); // leave some metadata on the world used for debug object labeling if(FWorldContext* WorldContext = GEngine->GetWorldContextFromWorld(PersonaToolkit->GetPreviewScene()->GetWorld())) { static constexpr TCHAR Format[] = TEXT("AnimBPEditor (%s)"); WorldContext->CustomDescription = FString::Printf(Format, *InAnimBlueprint->GetName()); } PersonaModule.RecordAssetOpened(InAnimBlueprint); if(InAnimBlueprint->BlueprintType != BPTYPE_Interface && !InAnimBlueprint->bIsTemplate) { // create the skeleton tree FSkeletonTreeArgs SkeletonTreeArgs; SkeletonTreeArgs.OnSelectionChanged = FOnSkeletonTreeSelectionChanged::CreateSP(this, &FAnimationBlueprintEditor::HandleSelectionChanged); SkeletonTreeArgs.PreviewScene = GetPreviewScene(); SkeletonTreeArgs.ContextName = GetToolkitFName(); ISkeletonEditorModule& SkeletonEditorModule = FModuleManager::LoadModuleChecked("SkeletonEditor"); SkeletonTree = SkeletonEditorModule.CreateSkeletonTree(PersonaToolkit->GetSkeleton(), SkeletonTreeArgs); } // Register for compilation events InAnimBlueprint->OnCompiled().AddSP(this, &FAnimationBlueprintEditor::OnBlueprintPostCompile); // Build up a list of objects being edited in this asset editor TArray ObjectsBeingEdited; ObjectsBeingEdited.Add(InAnimBlueprint); CreateDefaultCommands(); BindCommands(); RegisterMenus(); // Initialize the asset editor and spawn tabs const bool bCreateDefaultStandaloneMenu = true; const bool bCreateDefaultToolbar = true; InitAssetEditor(Mode, InitToolkitHost, AnimationBlueprintEditorAppName, FTabManager::FLayout::NullLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectsBeingEdited); TArray AnimBlueprints; AnimBlueprints.Add(InAnimBlueprint); CommonInitialization(AnimBlueprints, /*bShouldOpenInDefaultsMode=*/ false); // Register document editor for blendspaces DocumentManager->RegisterDocumentFactory(MakeShared(SharedThis(this))); bool bHasBlueprintPreview = false; if(InAnimBlueprint->BlueprintType == BPTYPE_Interface) { AddApplicationMode( FAnimationBlueprintEditorModes::AnimationBlueprintInterfaceEditorMode, MakeShareable(new FAnimationBlueprintInterfaceEditorMode(SharedThis(this)))); ExtendMenu(); ExtendToolbar(); RegenerateMenusAndToolbars(); // Activate the initial mode (which will populate with a real layout) SetCurrentMode(FAnimationBlueprintEditorModes::AnimationBlueprintInterfaceEditorMode); } else if(InAnimBlueprint->bIsTemplate) { AddApplicationMode( FAnimationBlueprintEditorModes::AnimationBlueprintTemplateEditorMode, MakeShareable(new FAnimationBlueprintEditorMode(SharedThis(this)))); bHasBlueprintPreview = true; ExtendMenu(); ExtendToolbar(); RegenerateMenusAndToolbars(); // Activate the initial mode (which will populate with a real layout) SetCurrentMode(FAnimationBlueprintEditorModes::AnimationBlueprintTemplateEditorMode); } else { AddApplicationMode( FAnimationBlueprintEditorModes::AnimationBlueprintEditorMode, MakeShareable(new FAnimationBlueprintEditorMode(SharedThis(this)))); bHasBlueprintPreview = true; ExtendMenu(); ExtendToolbar(); RegenerateMenusAndToolbars(); // Activate the initial mode (which will populate with a real layout) SetCurrentMode(FAnimationBlueprintEditorModes::AnimationBlueprintEditorMode); } if (bHasBlueprintPreview) { UDebugSkelMeshComponent* PreviewMeshComponent = PersonaToolkit->GetPreviewMeshComponent(); UAnimBlueprint* AnimBlueprint = PersonaToolkit->GetAnimBlueprint(); UAnimBlueprint* PreviewAnimBlueprint = AnimBlueprint->GetPreviewAnimationBlueprint(); if (PreviewAnimBlueprint) { PersonaToolkit->GetPreviewScene()->SetPreviewAnimationBlueprint(PreviewAnimBlueprint, AnimBlueprint); PreviewAnimBlueprint->OnCompiled().AddSP(this, &FAnimationBlueprintEditor::HandlePreviewAnimBlueprintCompiled); } else { PersonaToolkit->GetPreviewScene()->SetPreviewAnimationBlueprint(AnimBlueprint, nullptr); } PersonaUtils::SetObjectBeingDebugged(AnimBlueprint, PreviewMeshComponent->GetAnimInstance()); } // Post-layout initialization PostLayoutBlueprintEditorInitialization(); // register customization of Slot node for this Animation Blueprint Editor // this is so that you can open the manage window per Animation Blueprint Editor PersonaModule.CustomizeBlueprintEditorDetails(Inspector->GetPropertyView().ToSharedRef(), FOnInvokeTab::CreateSP(this, &FAssetEditorToolkit::InvokeTab)); if(bNewlyCreated && InAnimBlueprint->BlueprintType == BPTYPE_Interface) { NewDocument_OnClick(CGT_NewAnimationLayer); } // Register for notifications when settings change AnimationBlueprintEditorSettingsChangedHandle = GetMutableDefault()->RegisterOnUpdateSettings( UAnimationBlueprintEditorSettings::FOnUpdateSettingsMulticaster::FDelegate::CreateSP(this, &FAnimationBlueprintEditor::HandleUpdateSettings)); PersonaToolkit->GetPreviewScene()->SetAllowMeshHitProxies(false); ScriptExceptionHandle = FBlueprintCoreDelegates::OnScriptException.AddSP(this, &FAnimationBlueprintEditor::HandleScriptException); } void FAnimationBlueprintEditor::BindCommands() { GetToolkitCommands()->MapAction(FPersonaCommonCommands::Get().TogglePlay, FExecuteAction::CreateRaw(&GetPersonaToolkit()->GetPreviewScene().Get(), &IPersonaPreviewScene::TogglePlayback)); } TSharedPtr FAnimationBlueprintEditor::GetAnimationBlueprintEditor(const FToolMenuContext& InMenuContext) { if (UAnimationBlueprintToolMenuContext* Context = InMenuContext.FindContext()) { if (Context->AnimationBlueprintEditor.IsValid()) { return StaticCastSharedPtr(Context->AnimationBlueprintEditor.Pin()); } } return TSharedPtr(); } void FAnimationBlueprintEditor::ExtendToolbar() { // If the ToolbarExtender is valid, remove it before rebuilding it if(ToolbarExtender.IsValid()) { RemoveToolbarExtender(ToolbarExtender); ToolbarExtender.Reset(); } ToolbarExtender = MakeShareable(new FExtender); AddToolbarExtender(ToolbarExtender); FAnimationBlueprintEditorModule& AnimationBlueprintEditorModule = FModuleManager::LoadModuleChecked("AnimationBlueprintEditor"); AddToolbarExtender(AnimationBlueprintEditorModule.GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); TArray ToolbarExtenderDelegates = AnimationBlueprintEditorModule.GetAllAnimationBlueprintEditorToolbarExtenders(); for (auto& ToolbarExtenderDelegate : ToolbarExtenderDelegates) { if(ToolbarExtenderDelegate.IsBound()) { AddToolbarExtender(ToolbarExtenderDelegate.Execute(GetToolkitCommands(), SharedThis(this))); } } UAnimBlueprint* AnimBlueprint = PersonaToolkit->GetAnimBlueprint(); if(AnimBlueprint && AnimBlueprint->BlueprintType != BPTYPE_Interface && !AnimBlueprint->bIsTemplate) { ToolbarExtender->AddToolBarExtension( "Asset", EExtensionHook::After, GetToolkitCommands(), FToolBarExtensionDelegate::CreateLambda([this](FToolBarBuilder& ParentToolbarBuilder) { FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked("Persona"); TSharedRef AssetFamily = PersonaModule.CreatePersonaAssetFamily(GetBlueprintObj()); AddToolbarWidget(PersonaModule.CreateAssetFamilyShortcutWidget(SharedThis(this), AssetFamily)); } )); } } UBlueprint* FAnimationBlueprintEditor::GetBlueprintObj() const { const TArray& EditingObjs = GetEditingObjects(); for (int32 i = 0; i < EditingObjs.Num(); ++i) { if (EditingObjs[i]->IsA()) {return (UBlueprint*)EditingObjs[i];} } return nullptr; } void FAnimationBlueprintEditor::SetDetailObjects(const TArray& InObjects) { Inspector->ShowDetailsForObjects(InObjects); } void FAnimationBlueprintEditor::SetDetailObject(UObject* Obj) { TArray Objects; if (Obj) { Objects.Add(Obj); } SetDetailObjects(Objects); } /** Called when graph editor focus is changed */ void FAnimationBlueprintEditor::OnGraphEditorFocused(const TSharedRef& InGraphEditor) { // Remove pose watches now before calling the base class implementation because that will switch the focus if (GetDefault()->bPoseWatchSelectedNodes) { RemoveAllSelectionPoseWatches(); } // in the future, depending on which graph editor is this will act different FBlueprintEditor::OnGraphEditorFocused(InGraphEditor); // install callback to allow us to propagate pin default changes live to the preview UAnimationGraph* AnimationGraph = Cast(InGraphEditor->GetCurrentGraph()); if (AnimationGraph) { OnPinDefaultValueChangedHandle = AnimationGraph->OnPinDefaultValueChanged.Add(FOnPinDefaultValueChanged::FDelegate::CreateSP(this, &FAnimationBlueprintEditor::HandlePinDefaultValueChanged)); } if (bHideUnrelatedNodes && GetSelectedNodes().Num() <= 0) { ResetAllNodesUnrelatedStates(); } if (GetDefault()->bPoseWatchSelectedNodes) { HandlePoseWatchSelectedNodes(); } } void FAnimationBlueprintEditor::OnGraphEditorBackgrounded(const TSharedRef& InGraphEditor) { FBlueprintEditor::OnGraphEditorBackgrounded(InGraphEditor); UAnimationGraph* AnimationGraph = Cast(InGraphEditor->GetCurrentGraph()); if (AnimationGraph) { AnimationGraph->OnPinDefaultValueChanged.Remove(OnPinDefaultValueChangedHandle); } } /** Create Default Tabs **/ void FAnimationBlueprintEditor::CreateDefaultCommands() { { FBlueprintEditor::CreateDefaultCommands(); } } void FAnimationBlueprintEditor::OnCreateGraphEditorCommands(TSharedPtr GraphEditorCommandsList) { GraphEditorCommandsList->MapAction(FAnimGraphCommands::Get().TogglePoseWatch, FExecuteAction::CreateSP(this, &FAnimationBlueprintEditor::OnTogglePoseWatch), FCanExecuteAction::CreateSP(this, &FAnimationBlueprintEditor::CanTogglePoseWatch) ); GraphEditorCommandsList->MapAction(FAnimGraphCommands::Get().HideUnboundPropertyPins, FExecuteAction::CreateSP(this, &FAnimationBlueprintEditor::OnHideUnboundPropertyPins), FCanExecuteAction::CreateSP(this, &FAnimationBlueprintEditor::CanHideUnboundPropertyPins) ); GraphEditorCommandsList->MapAction( FAnimGraphCommands::Get().AddBlendListPin, FExecuteAction::CreateSP( this, &FAnimationBlueprintEditor::OnAddPosePin ), FCanExecuteAction::CreateSP( this, &FAnimationBlueprintEditor::CanAddPosePin ) ); GraphEditorCommandsList->MapAction( FAnimGraphCommands::Get().RemoveBlendListPin, FExecuteAction::CreateSP( this, &FAnimationBlueprintEditor::OnRemovePosePin ), FCanExecuteAction::CreateSP( this, &FAnimationBlueprintEditor::CanRemovePosePin ) ); GraphEditorCommandsList->MapAction( FAnimGraphCommands::Get().ConvertToSeqEvaluator, FExecuteAction::CreateSP( this, &FAnimationBlueprintEditor::OnConvertToSequenceEvaluator ) ); GraphEditorCommandsList->MapAction( FAnimGraphCommands::Get().ConvertToSeqPlayer, FExecuteAction::CreateSP( this, &FAnimationBlueprintEditor::OnConvertToSequencePlayer ) ); GraphEditorCommandsList->MapAction( FAnimGraphCommands::Get().ConvertToBSEvaluator, FExecuteAction::CreateSP( this, &FAnimationBlueprintEditor::OnConvertToBlendSpaceEvaluator ) ); GraphEditorCommandsList->MapAction( FAnimGraphCommands::Get().ConvertToBSPlayer, FExecuteAction::CreateSP( this, &FAnimationBlueprintEditor::OnConvertToBlendSpacePlayer ) ); GraphEditorCommandsList->MapAction( FAnimGraphCommands::Get().ConvertToBSGraph, FExecuteAction::CreateSP( this, &FAnimationBlueprintEditor::OnConvertToBlendSpaceGraph ) ); GraphEditorCommandsList->MapAction(FAnimGraphCommands::Get().ConvertToAimOffsetLookAt, FExecuteAction::CreateSP(this, &FAnimationBlueprintEditor::OnConvertToAimOffsetLookAt) ); GraphEditorCommandsList->MapAction(FAnimGraphCommands::Get().ConvertToAimOffsetSimple, FExecuteAction::CreateSP(this, &FAnimationBlueprintEditor::OnConvertToAimOffsetSimple) ); GraphEditorCommandsList->MapAction(FAnimGraphCommands::Get().ConvertToAimOffsetGraph, FExecuteAction::CreateSP(this, &FAnimationBlueprintEditor::OnConvertToAimOffsetGraph) ); GraphEditorCommandsList->MapAction(FAnimGraphCommands::Get().ConvertToPoseBlender, FExecuteAction::CreateSP(this, &FAnimationBlueprintEditor::OnConvertToPoseBlender) ); GraphEditorCommandsList->MapAction(FAnimGraphCommands::Get().ConvertToPoseByName, FExecuteAction::CreateSP(this, &FAnimationBlueprintEditor::OnConvertToPoseByName) ); GraphEditorCommandsList->MapAction( FAnimGraphCommands::Get().OpenRelatedAsset, FExecuteAction::CreateSP( this, &FAnimationBlueprintEditor::OnOpenRelatedAsset ) ); } void FAnimationBlueprintEditor::OnAddPosePin() { const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (SelectedNodes.Num() == 1) { for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UObject* Node = *NodeIt; if (UAnimGraphNode_BlendListByInt* BlendNode = Cast(Node)) { BlendNode->AddPinToBlendList(); break; } else if (UAnimGraphNode_LayeredBoneBlend* FilterNode = Cast(Node)) { FilterNode->AddPinToBlendByFilter(); break; } else if (UAnimGraphNode_MultiWayBlend* MultiBlendNode = Cast(Node)) { MultiBlendNode->AddPinToBlendNode(); break; } } } } bool FAnimationBlueprintEditor::CanAddPosePin() const { return true; } void FAnimationBlueprintEditor::OnRemovePosePin() { const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); UAnimGraphNode_BlendListByInt* BlendListIntNode = nullptr; UAnimGraphNode_LayeredBoneBlend* BlendByFilterNode = nullptr; UAnimGraphNode_MultiWayBlend* BlendByMultiway = nullptr; if (SelectedNodes.Num() == 1) { for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { if (UAnimGraphNode_BlendListByInt* BlendNode = Cast(*NodeIt)) { BlendListIntNode = BlendNode; break; } else if (UAnimGraphNode_LayeredBoneBlend* LayeredBlendNode = Cast(*NodeIt)) { BlendByFilterNode = LayeredBlendNode; break; } else if (UAnimGraphNode_MultiWayBlend* MultiwayBlendNode = Cast(*NodeIt)) { BlendByMultiway = MultiwayBlendNode; break; } } } TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); if (FocusedGraphEd.IsValid()) { // @fixme: I think we can make blendlistbase to have common functionality // and each can implement the common function, but for now, we separate them // each implement their menu, so we still can use listbase as the root if (BlendListIntNode) { // make sure we at least have BlendListNode selected UEdGraphPin* SelectedPin = FocusedGraphEd->GetGraphPinForMenu(); BlendListIntNode->RemovePinFromBlendList(SelectedPin); // Update the graph so that the node will be refreshed FocusedGraphEd->NotifyGraphChanged(); } if (BlendByFilterNode) { // make sure we at least have BlendListNode selected UEdGraphPin* SelectedPin = FocusedGraphEd->GetGraphPinForMenu(); BlendByFilterNode->RemovePinFromBlendByFilter(SelectedPin); // Update the graph so that the node will be refreshed FocusedGraphEd->NotifyGraphChanged(); } if (BlendByMultiway) { // make sure we at least have BlendListNode selected UEdGraphPin* SelectedPin = FocusedGraphEd->GetGraphPinForMenu(); BlendByMultiway->RemovePinFromBlendNode(SelectedPin); // Update the graph so that the node will be refreshed FocusedGraphEd->NotifyGraphChanged(); } } } bool FAnimationBlueprintEditor::CanTogglePoseWatch() { const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); UAnimBlueprint* AnimBP = GetAnimBlueprint(); // Can't add pose watch from a child anim bp if (UAnimBlueprint::FindRootAnimBlueprint(AnimBP)) { return false; } for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { if (UAnimGraphNode_Base* SelectedNode = Cast(*NodeIt)) { UPoseWatch* ExistingPoseWatch = AnimationEditorUtils::FindPoseWatchForNode(SelectedNode, AnimBP); if (ExistingPoseWatch) { return true; } if (SelectedNode->IsPoseWatchable()) { return true; } } } return false; } bool FAnimationBlueprintEditor::CanHideUnboundPropertyPins() { const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { if (UAnimGraphNode_LinkedAnimGraphBase* SelectedNode = Cast(*NodeIt)) { return true; } } return false; } void FAnimationBlueprintEditor::OnTogglePoseWatch() { ReleaseAllManagedNodes(); const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); UAnimBlueprint* AnimBP = GetAnimBlueprint(); // Can't toggle pose watch from a child anim bp if (UAnimBlueprint::FindRootAnimBlueprint(AnimBP)) { return; } for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { if (UAnimGraphNode_Base* SelectedNode = Cast(*NodeIt)) { UPoseWatch* ExistingPoseWatch = AnimationEditorUtils::FindPoseWatchForNode(SelectedNode, AnimBP); if (ExistingPoseWatch) { // Promote the temporary pose watch to permanent if (ExistingPoseWatch->GetShouldDeleteOnDeselect()) { ExistingPoseWatch->SetShouldDeleteOnDeselect(false); } else if (GetDefault()->bPoseWatchSelectedNodes) { ExistingPoseWatch->SetShouldDeleteOnDeselect(true); } else { AnimationEditorUtils::RemovePoseWatch(ExistingPoseWatch, AnimBP); } AnimationEditorUtils::OnPoseWatchesChanged().Broadcast(AnimBP, ExistingPoseWatch->Node.Get()); } else if (SelectedNode->IsPoseWatchable()) { UPoseWatch* NewPoseWatch = AnimationEditorUtils::MakePoseWatchForNode(AnimBP, SelectedNode); AnimationEditorUtils::OnPoseWatchesChanged().Broadcast(AnimBP, NewPoseWatch->Node.Get()); } } } AcquireAllManagedNodes(); } void FAnimationBlueprintEditor::OnHideUnboundPropertyPins() { const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { if (UAnimGraphNode_LinkedAnimGraphBase* SelectedNode = Cast(*NodeIt)) { UAnimationGraphSchema::HideUnboundPropertyPins(SelectedNode); } } } // Helper function for node conversions static void CopyPinData(UEdGraphNode* InOldNode, UEdGraphNode* InNewNode, const TCHAR* InPinName) { UEdGraphPin* OldPin = InOldNode->FindPin(InPinName); UEdGraphPin* NewPin = InNewNode->FindPin(InPinName); if (ensure(OldPin && NewPin)) { NewPin->MovePersistentDataFromOldPin(*OldPin); } }; void FAnimationBlueprintEditor::OnConvertToSequenceEvaluator() { FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (SelectedNodes.Num() > 0) { // convert to sequence evaluator const FScopedTransaction Transaction( LOCTEXT("ConvertToSequenceEvaluator", "Convert to Single Frame Animation") ); for (auto NodeIter = SelectedNodes.CreateIterator(); NodeIter; ++NodeIter) { UAnimGraphNode_SequencePlayer* OldNode = Cast(*NodeIter); // see if sequence player if ( OldNode && OldNode->Node.GetSequence() ) { UEdGraph* TargetGraph = OldNode->GetGraph(); TargetGraph->Modify(); OldNode->Modify(); // create new evaluator FGraphNodeCreator NodeCreator(*TargetGraph); UAnimGraphNode_SequenceEvaluator* NewNode = NodeCreator.CreateNode(); NewNode->Node.SetSequence(OldNode->Node.GetSequence()); NodeCreator.Finalize(); // get default data from old node to new node FEdGraphUtilities::CopyCommonState(OldNode, NewNode); CopyPinData(OldNode, NewNode, TEXT("Pose")); // remove from selection and from graph NodeIter.RemoveCurrent(); TargetGraph->RemoveNode(OldNode); } } // @todo fixme: below code doesn't work // because of SetAndCenterObject kicks in after new node is added // will need to disable that first TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); // Update the graph so that the node will be refreshed FocusedGraphEd->NotifyGraphChanged(); // It's possible to leave invalid objects in the selection set if they get GC'd, so clear it out FocusedGraphEd->ClearSelectionSet(); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetAnimBlueprint()); } } void FAnimationBlueprintEditor::OnConvertToSequencePlayer() { FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (SelectedNodes.Num() > 0) { const FScopedTransaction Transaction( LOCTEXT("ConvertToSequencePlayer", "Convert to Sequence Player") ); for (auto NodeIter = SelectedNodes.CreateIterator(); NodeIter; ++NodeIter) { UAnimGraphNode_SequenceEvaluator* OldNode = Cast(*NodeIter); // see if sequence player if ( OldNode && OldNode->Node.GetSequence() ) { // convert to sequence player UEdGraph* TargetGraph = OldNode->GetGraph(); TargetGraph->Modify(); OldNode->Modify(); // create new player FGraphNodeCreator NodeCreator(*TargetGraph); UAnimGraphNode_SequencePlayer* NewNode = NodeCreator.CreateNode(); NewNode->Node.SetSequence(OldNode->Node.GetSequence()); NodeCreator.Finalize(); // get default data from old node to new node FEdGraphUtilities::CopyCommonState(OldNode, NewNode); CopyPinData(OldNode, NewNode, TEXT("Pose")); // remove from selection and from graph NodeIter.RemoveCurrent(); TargetGraph->RemoveNode(OldNode); } } // @todo fixme: below code doesn't work // because of SetAndCenterObject kicks in after new node is added // will need to disable that first TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); // Update the graph so that the node will be refreshed FocusedGraphEd->NotifyGraphChanged(); // It's possible to leave invalid objects in the selection set if they get GC'd, so clear it out FocusedGraphEd->ClearSelectionSet(); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetAnimBlueprint()); } } void FAnimationBlueprintEditor::OnConvertToBlendSpaceEvaluator() { FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (SelectedNodes.Num() > 0) { const FScopedTransaction Transaction( LOCTEXT("ConvertToBlendSpaceEvaluator", "Convert to Single Frame Blend Space") ); for (auto NodeIter = SelectedNodes.CreateIterator(); NodeIter; ++NodeIter) { UAnimGraphNode_BlendSpacePlayer* OldNode = Cast(*NodeIter); // see if sequence player if ( OldNode && OldNode->Node.GetBlendSpace() ) { // convert to sequence evaluator UEdGraph* TargetGraph = OldNode->GetGraph(); TargetGraph->Modify(); OldNode->Modify(); // create new evaluator FGraphNodeCreator NodeCreator(*TargetGraph); UAnimGraphNode_BlendSpaceEvaluator* NewNode = NodeCreator.CreateNode(); NewNode->Node.SetBlendSpace(OldNode->Node.GetBlendSpace()); NodeCreator.Finalize(); // get default data from old node to new node FEdGraphUtilities::CopyCommonState(OldNode, NewNode); CopyPinData(OldNode, NewNode, TEXT("X")); CopyPinData(OldNode, NewNode, TEXT("Y")); CopyPinData(OldNode, NewNode, TEXT("Pose")); // remove from selection and from graph NodeIter.RemoveCurrent(); TargetGraph->RemoveNode(OldNode); } } // @todo fixme: below code doesn't work // because of SetAndCenterObject kicks in after new node is added // will need to disable that first TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); // Update the graph so that the node will be refreshed FocusedGraphEd->NotifyGraphChanged(); // It's possible to leave invalid objects in the selection set if they get GC'd, so clear it out FocusedGraphEd->ClearSelectionSet(); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetAnimBlueprint()); } } void FAnimationBlueprintEditor::OnConvertToBlendSpacePlayer() { FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (SelectedNodes.Num() > 0) { const FScopedTransaction Transaction( LOCTEXT("ConvertToBlendSpacePlayer", "Convert to Blend Space Player") ); for (auto NodeIter = SelectedNodes.CreateIterator(); NodeIter; ++NodeIter) { UAnimGraphNode_BlendSpaceEvaluator* OldNode = Cast(*NodeIter); // see if sequence player if ( OldNode && OldNode->Node.GetBlendSpace() ) { // convert to sequence player UEdGraph* TargetGraph = OldNode->GetGraph(); TargetGraph->Modify(); OldNode->Modify(); // create new player FGraphNodeCreator NodeCreator(*TargetGraph); UAnimGraphNode_BlendSpacePlayer* NewNode = NodeCreator.CreateNode(); NewNode->Node.SetBlendSpace(OldNode->Node.GetBlendSpace()); NodeCreator.Finalize(); // get default data from old node to new node FEdGraphUtilities::CopyCommonState(OldNode, NewNode); CopyPinData(OldNode, NewNode, TEXT("X")); CopyPinData(OldNode, NewNode, TEXT("Y")); CopyPinData(OldNode, NewNode, TEXT("Pose")); // remove from selection and from graph NodeIter.RemoveCurrent(); TargetGraph->RemoveNode(OldNode); } } // @todo fixme: below code doesn't work // because of SetAndCenterObject kicks in after new node is added // will need to disable that first TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); // Update the graph so that the node will be refreshed FocusedGraphEd->NotifyGraphChanged(); // It's possible to leave invalid objects in the selection set if they get GC'd, so clear it out FocusedGraphEd->ClearSelectionSet(); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetAnimBlueprint()); } } void FAnimationBlueprintEditor::OnConvertToBlendSpaceGraph() { FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (SelectedNodes.Num() > 0) { const FScopedTransaction Transaction( LOCTEXT("ConvertToblendSpaceGraph", "Convert to Blend Space Graph") ); for (auto NodeIter = SelectedNodes.CreateIterator(); NodeIter; ++NodeIter) { UAnimGraphNode_BlendSpacePlayer* OldNode = Cast(*NodeIter); // see if sequence player if (OldNode && OldNode->Node.GetBlendSpace()) { // convert to sequence player UEdGraph* TargetGraph = OldNode->GetGraph(); TargetGraph->Modify(); OldNode->Modify(); // create new player FGraphNodeCreator NodeCreator(*TargetGraph); UAnimGraphNode_BlendSpaceGraph* NewNode = NodeCreator.CreateNode(); if(OldNode->Node.GetGroupName() != NAME_None && OldNode->Node.GetGroupMethod() == EAnimSyncMethod::SyncGroup) { NewNode->SetSyncGroupName(OldNode->Node.GetGroupName()); } NewNode->SetupFromAsset(OldNode->Node.GetBlendSpace(), false); NodeCreator.Finalize(); // get default data from old node to new node FEdGraphUtilities::CopyCommonState(OldNode, NewNode); CopyPinData(OldNode, NewNode, TEXT("X")); CopyPinData(OldNode, NewNode, TEXT("Y")); CopyPinData(OldNode, NewNode, TEXT("Pose")); // remove from selection and from graph NodeIter.RemoveCurrent(); TargetGraph->RemoveNode(OldNode); } } // @todo fixme: below code doesn't work // because of SetAndCenterObject kicks in after new node is added // will need to disable that first TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); // Update the graph so that the node will be refreshed FocusedGraphEd->NotifyGraphChanged(); // It's possible to leave invalid objects in the selection set if they get GC'd, so clear it out FocusedGraphEd->ClearSelectionSet(); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetAnimBlueprint()); } } void FAnimationBlueprintEditor::OnConvertToPoseBlender() { FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (SelectedNodes.Num() > 0) { const FScopedTransaction Transaction( LOCTEXT("ConvertToPoseBlender", "Convert to Pose Blender") ); for (auto NodeIter = SelectedNodes.CreateIterator(); NodeIter; ++NodeIter) { UAnimGraphNode_PoseByName* OldNode = Cast(*NodeIter); // see if sequence player if (OldNode && OldNode->Node.PoseAsset) { // convert to sequence player UEdGraph* TargetGraph = OldNode->GetGraph(); TargetGraph->Modify(); OldNode->Modify(); // create new player FGraphNodeCreator NodeCreator(*TargetGraph); UAnimGraphNode_PoseBlendNode* NewNode = NodeCreator.CreateNode(); NewNode->Node.PoseAsset = OldNode->Node.PoseAsset; NodeCreator.Finalize(); // get default data from old node to new node FEdGraphUtilities::CopyCommonState(OldNode, NewNode); CopyPinData(OldNode, NewNode, TEXT("Pose")); // remove from selection and from graph NodeIter.RemoveCurrent(); TargetGraph->RemoveNode(OldNode); } } // @todo fixme: below code doesn't work // because of SetAndCenterObject kicks in after new node is added // will need to disable that first TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); // Update the graph so that the node will be refreshed FocusedGraphEd->NotifyGraphChanged(); // It's possible to leave invalid objects in the selection set if they get GC'd, so clear it out FocusedGraphEd->ClearSelectionSet(); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetAnimBlueprint()); } } void FAnimationBlueprintEditor::OnConvertToPoseByName() { FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (SelectedNodes.Num() > 0) { const FScopedTransaction Transaction( LOCTEXT("ConvertToPoseByName", "Convert to Pose By Name") ); for (auto NodeIter = SelectedNodes.CreateIterator(); NodeIter; ++NodeIter) { UAnimGraphNode_PoseBlendNode* OldNode = Cast(*NodeIter); // see if sequence player if (OldNode && OldNode->Node.PoseAsset) { // convert to sequence player UEdGraph* TargetGraph = OldNode->GetGraph(); TargetGraph->Modify(); OldNode->Modify(); // create new player FGraphNodeCreator NodeCreator(*TargetGraph); UAnimGraphNode_PoseByName* NewNode = NodeCreator.CreateNode(); NewNode->Node.PoseAsset = OldNode->Node.PoseAsset; NodeCreator.Finalize(); // get default data from old node to new node FEdGraphUtilities::CopyCommonState(OldNode, NewNode); CopyPinData(OldNode, NewNode, TEXT("Pose")); // remove from selection and from graph NodeIter.RemoveCurrent(); TargetGraph->RemoveNode(OldNode); } } // @todo fixme: below code doesn't work // because of SetAndCenterObject kicks in after new node is added // will need to disable that first TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); // Update the graph so that the node will be refreshed FocusedGraphEd->NotifyGraphChanged(); // It's possible to leave invalid objects in the selection set if they get GC'd, so clear it out FocusedGraphEd->ClearSelectionSet(); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetAnimBlueprint()); } } void FAnimationBlueprintEditor::OnConvertToAimOffsetLookAt() { FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (SelectedNodes.Num() > 0) { const FScopedTransaction Transaction( LOCTEXT("ConvertToAimOffsetLookAt", "Convert to Aim Offset LookAt") ); for (auto NodeIter = SelectedNodes.CreateIterator(); NodeIter; ++NodeIter) { UAnimGraphNode_RotationOffsetBlendSpace* OldNode = Cast(*NodeIter); // see if sequence player if (OldNode && OldNode->Node.GetBlendSpace()) { // convert to sequence evaluator UEdGraph* TargetGraph = OldNode->GetGraph(); TargetGraph->Modify(); OldNode->Modify(); // create new evaluator FGraphNodeCreator NodeCreator(*TargetGraph); UAnimGraphNode_AimOffsetLookAt* NewNode = NodeCreator.CreateNode(); NewNode->Node.SetBlendSpace(OldNode->Node.GetBlendSpace()); NodeCreator.Finalize(); // get default data from old node to new node FEdGraphUtilities::CopyCommonState(OldNode, NewNode); CopyPinData(OldNode, NewNode, TEXT("X")); CopyPinData(OldNode, NewNode, TEXT("Y")); CopyPinData(OldNode, NewNode, TEXT("Alpha")); CopyPinData(OldNode, NewNode, TEXT("Pose")); CopyPinData(OldNode, NewNode, TEXT("BasePose")); // remove from selection and from graph NodeIter.RemoveCurrent(); TargetGraph->RemoveNode(OldNode); } } // @todo fixme: below code doesn't work // because of SetAndCenterObject kicks in after new node is added // will need to disable that first TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); // Update the graph so that the node will be refreshed FocusedGraphEd->NotifyGraphChanged(); // It's possible to leave invalid objects in the selection set if they get GC'd, so clear it out FocusedGraphEd->ClearSelectionSet(); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetAnimBlueprint()); } } void FAnimationBlueprintEditor::OnConvertToAimOffsetSimple() { FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (SelectedNodes.Num() > 0) { const FScopedTransaction Transaction( LOCTEXT("ConvertToSimpleAimOffset", "Convert to Simple Aim Offset") ); for (auto NodeIter = SelectedNodes.CreateIterator(); NodeIter; ++NodeIter) { UAnimGraphNode_AimOffsetLookAt* OldNode = Cast(*NodeIter); // see if sequence player if (OldNode && OldNode->Node.GetBlendSpace()) { // convert to sequence player UEdGraph* TargetGraph = OldNode->GetGraph(); TargetGraph->Modify(); OldNode->Modify(); // create new player FGraphNodeCreator NodeCreator(*TargetGraph); UAnimGraphNode_RotationOffsetBlendSpace* NewNode = NodeCreator.CreateNode(); NewNode->Node.SetBlendSpace(OldNode->Node.GetBlendSpace()); NodeCreator.Finalize(); // get default data from old node to new node FEdGraphUtilities::CopyCommonState(OldNode, NewNode); CopyPinData(OldNode, NewNode, TEXT("X")); CopyPinData(OldNode, NewNode, TEXT("Y")); CopyPinData(OldNode, NewNode, TEXT("Pose")); CopyPinData(OldNode, NewNode, TEXT("BasePose")); // remove from selection and from graph NodeIter.RemoveCurrent(); TargetGraph->RemoveNode(OldNode); } } // @todo fixme: below code doesn't work // because of SetAndCenterObject kicks in after new node is added // will need to disable that first TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); // Update the graph so that the node will be refreshed FocusedGraphEd->NotifyGraphChanged(); // It's possible to leave invalid objects in the selection set if they get GC'd, so clear it out FocusedGraphEd->ClearSelectionSet(); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetAnimBlueprint()); } } void FAnimationBlueprintEditor::OnConvertToAimOffsetGraph() { FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (SelectedNodes.Num() > 0) { const FScopedTransaction Transaction( LOCTEXT("ConvertToAimOffsetGraph", "Convert to Aim Offset Graph") ); for (auto NodeIter = SelectedNodes.CreateIterator(); NodeIter; ++NodeIter) { UAnimGraphNode_RotationOffsetBlendSpace* OldNode = Cast(*NodeIter); // see if sequence player if (OldNode && OldNode->Node.GetBlendSpace()) { // convert to sequence player UEdGraph* TargetGraph = OldNode->GetGraph(); TargetGraph->Modify(); OldNode->Modify(); // create new player FGraphNodeCreator NodeCreator(*TargetGraph); UAnimGraphNode_RotationOffsetBlendSpaceGraph* NewNode = NodeCreator.CreateNode(); if(OldNode->Node.GetGroupName() != NAME_None && OldNode->Node.GetGroupMethod() == EAnimSyncMethod::SyncGroup) { NewNode->SetSyncGroupName(OldNode->Node.GetGroupName()); } NewNode->SetupFromAsset(OldNode->Node.GetBlendSpace(), false); NodeCreator.Finalize(); // get default data from old node to new node FEdGraphUtilities::CopyCommonState(OldNode, NewNode); CopyPinData(OldNode, NewNode, TEXT("X")); CopyPinData(OldNode, NewNode, TEXT("Y")); CopyPinData(OldNode, NewNode, TEXT("Alpha")); CopyPinData(OldNode, NewNode, TEXT("Pose")); CopyPinData(OldNode, NewNode, TEXT("BasePose")); // remove from selection and from graph NodeIter.RemoveCurrent(); TargetGraph->RemoveNode(OldNode); } } // @todo fixme: below code doesn't work // because of SetAndCenterObject kicks in after new node is added // will need to disable that first TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); // Update the graph so that the node will be refreshed FocusedGraphEd->NotifyGraphChanged(); // It's possible to leave invalid objects in the selection set if they get GC'd, so clear it out FocusedGraphEd->ClearSelectionSet(); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetAnimBlueprint()); } } void FAnimationBlueprintEditor::OnOpenRelatedAsset() { FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); EToolkitMode::Type Mode = EToolkitMode::Standalone; if (SelectedNodes.Num() > 0) { for (auto NodeIter = SelectedNodes.CreateIterator(); NodeIter; ++NodeIter) { if(UAnimGraphNode_Base* Node = Cast(*NodeIter)) { UAnimationAsset* AnimAsset = Node->GetAnimationAsset(); if(AnimAsset) { GEditor->GetEditorSubsystem()->OpenEditorForAsset(AnimAsset, Mode); } } } } } bool FAnimationBlueprintEditor::CanRemovePosePin() const { return true; } void FAnimationBlueprintEditor::RecompileAnimBlueprintIfDirty() { if (UBlueprint* Blueprint = GetBlueprintObj()) { if (!Blueprint->IsUpToDate()) { Compile(); } } } FName FAnimationBlueprintEditor::GetToolkitFName() const { return FName("AnimationBlueprintEditor"); } FName FAnimationBlueprintEditor::GetToolkitContextFName() const { return FName("AnimationBlueprintEditor"); } FText FAnimationBlueprintEditor::GetBaseToolkitName() const { return LOCTEXT("AppLabel", "Animation Blueprint Editor"); } FText FAnimationBlueprintEditor::GetToolkitToolTipText() const { return FAssetEditorToolkit::GetToolTipTextForObject(GetBlueprintObj()); } FString FAnimationBlueprintEditor::GetWorldCentricTabPrefix() const { return LOCTEXT("WorldCentricTabPrefix", "Animation Blueprint Editor ").ToString(); } FLinearColor FAnimationBlueprintEditor::GetWorldCentricTabColorScale() const { return FLinearColor( 0.5f, 0.25f, 0.35f, 0.5f ); } void FAnimationBlueprintEditor::InitToolMenuContext(FToolMenuContext& MenuContext) { IAnimationBlueprintEditor::InitToolMenuContext(MenuContext); UAnimationBlueprintToolMenuContext* AnimationBlueprintToolMenuContext = NewObject(); AnimationBlueprintToolMenuContext->AnimationBlueprintEditor = SharedThis(this); MenuContext.AddObject(AnimationBlueprintToolMenuContext); UPersonaToolMenuContext* Context = NewObject(); Context->SetToolkit(GetPersonaToolkit()); MenuContext.AddObject(Context); } IAnimationSequenceBrowser* FAnimationBlueprintEditor::GetAssetBrowser() const { return SequenceBrowser.Pin().Get(); } void FAnimationBlueprintEditor::OnActiveTabChanged( TSharedPtr PreviouslyActive, TSharedPtr NewlyActivated ) { if (!NewlyActivated.IsValid()) { TArray ObjArray; Inspector->ShowDetailsForObjects(ObjArray); } else { FBlueprintEditor::OnActiveTabChanged(PreviouslyActive, NewlyActivated); } } void FAnimationBlueprintEditor::SetPreviewMesh(USkeletalMesh* NewPreviewMesh) { if(SkeletonTree.IsValid()) { SkeletonTree->SetSkeletalMesh(NewPreviewMesh); } } void FAnimationBlueprintEditor::RefreshPreviewInstanceTrackCurves() { // need to refresh the preview mesh UDebugSkelMeshComponent* PreviewMeshComponent = PersonaToolkit->GetPreviewMeshComponent(); if(PreviewMeshComponent->PreviewInstance) { PreviewMeshComponent->PreviewInstance->RefreshCurveBoneControllers(); } } void FAnimationBlueprintEditor::PostUndo(bool bSuccess) { DocumentManager->CleanInvalidTabs(); DocumentManager->RefreshAllTabs(); FBlueprintEditor::PostUndo(bSuccess); // If we undid a node creation that caused us to clean up a tab/graph we need to refresh the UI state RefreshEditors(); // PostUndo broadcast OnPostUndo.Broadcast(); RefreshPreviewInstanceTrackCurves(); // clear up preview anim notify states // animnotify states are saved in AnimInstance // if those are undoed or redoed, they have to be // cleared up, otherwise, they might have invalid data ClearupPreviewMeshAnimNotifyStates(); OnPostCompile(); } void FAnimationBlueprintEditor::ClearupPreviewMeshAnimNotifyStates() { UDebugSkelMeshComponent* PreviewMeshComponent = PersonaToolkit->GetPreviewMeshComponent(); if ( PreviewMeshComponent ) { UAnimInstance* AnimInstanace = PreviewMeshComponent->GetAnimInstance(); if (AnimInstanace) { // empty this because otherwise, it can have corrupted data // this will cause state to be interrupted, but that is better // than crashing AnimInstanace->ActiveAnimNotifyState.Empty(); } } } UAnimInstance* FAnimationBlueprintEditor::GetPreviewInstance() const { UDebugSkelMeshComponent* PreviewMeshComponent = PersonaToolkit->GetPreviewMeshComponent(); if (PreviewMeshComponent->IsAnimBlueprintInstanced()) { UAnimInstance* PreviewInstance = PreviewMeshComponent->GetAnimInstance(); UAnimBlueprint* AnimBlueprint = GetAnimBlueprint(); UAnimBlueprint* PreviewAnimBlueprint = AnimBlueprint->GetPreviewAnimationBlueprint(); if (PreviewAnimBlueprint) { EPreviewAnimationBlueprintApplicationMethod ApplicationMethod = AnimBlueprint->GetPreviewAnimationBlueprintApplicationMethod(); if(ApplicationMethod == EPreviewAnimationBlueprintApplicationMethod::LinkedLayers) { PreviewInstance = PreviewInstance->GetLinkedAnimLayerInstanceByClass(AnimBlueprint->GeneratedClass.Get()); } else if(ApplicationMethod == EPreviewAnimationBlueprintApplicationMethod::LinkedAnimGraph) { PreviewInstance = PreviewInstance->GetLinkedAnimGraphInstanceByTag(AnimBlueprint->GetPreviewAnimationBlueprintTag()); } } return PreviewInstance; } return nullptr; } void FAnimationBlueprintEditor::GetCustomDebugObjects(TArray& DebugList) const { UAnimInstance* PreviewInstance = GetPreviewInstance(); if (PreviewInstance == nullptr) { UDebugSkelMeshComponent* PreviewMeshComponent = PersonaToolkit->GetPreviewMeshComponent(); PreviewInstance = PreviewMeshComponent->SavedAnimScriptInstance; } if (PreviewInstance) { new (DebugList) FCustomDebugObject(PreviewInstance, LOCTEXT("PreviewObjectLabel", "Preview Instance").ToString()); } FAnimationBlueprintEditorModule& AnimationBlueprintEditorModule = FModuleManager::GetModuleChecked("AnimationBlueprintEditor"); AnimationBlueprintEditorModule.OnGetCustomDebugObjects().Broadcast(*this, DebugList); } FString FAnimationBlueprintEditor::GetCustomDebugObjectLabel(UObject* ObjectBeingDebugged) const { UAnimInstance* PreviewInstance = GetPreviewInstance(); if (PreviewInstance == nullptr) { UDebugSkelMeshComponent* PreviewMeshComponent = PersonaToolkit->GetPreviewMeshComponent(); PreviewInstance = PreviewMeshComponent->SavedAnimScriptInstance; } if (PreviewInstance == ObjectBeingDebugged) { return LOCTEXT("PreviewObjectLabel", "Preview Instance").ToString(); } return FString(); } void FAnimationBlueprintEditor::CreateDefaultTabContents(const TArray& InBlueprints) { FBlueprintEditor::CreateDefaultTabContents(InBlueprints); PreviewEditor = SNew(SAnimBlueprintPreviewPropertyEditor, SharedThis(this)); } FGraphAppearanceInfo FAnimationBlueprintEditor::GetGraphAppearance(UEdGraph* InGraph) const { FGraphAppearanceInfo AppearanceInfo = FBlueprintEditor::GetGraphAppearance(InGraph); if ( GetBlueprintObj()->IsA(UAnimBlueprint::StaticClass()) ) { AppearanceInfo.CornerText = (GetDefault()->bShowGraphCornerText) ? LOCTEXT("AppearanceCornerText_Animation", "ANIMATION") : FText::GetEmpty(); } return AppearanceInfo; } void FAnimationBlueprintEditor::ClearSelectedActor() { GetPreviewScene()->ClearSelectedActor(); } void FAnimationBlueprintEditor::ClearSelectedAnimGraphNodes() { ReleaseAllManagedNodes(); SelectedAnimGraphNodes.Empty(); } void FAnimationBlueprintEditor::DeselectAll() { if(SkeletonTree) { SkeletonTree->DeselectAll(); } ClearSelectedActor(); ClearSelectedAnimGraphNodes(); } void FAnimationBlueprintEditor::PostRedo(bool bSuccess) { DocumentManager->RefreshAllTabs(); FBlueprintEditor::PostRedo(bSuccess); // PostUndo broadcast, OnPostRedo OnPostUndo.Broadcast(); // clear up preview anim notify states // animnotify states are saved in AnimInstance // if those are undoed or redoed, they have to be // cleared up, otherwise, they might have invalid data ClearupPreviewMeshAnimNotifyStates(); // calls PostCompile to copy proper values between anim nodes OnPostCompile(); } void FAnimationBlueprintEditor::UndoAction() { GEditor->UndoTransaction(); } void FAnimationBlueprintEditor::RedoAction() { GEditor->RedoTransaction(); } void FAnimationBlueprintEditor::NotifyPostChange(const FPropertyChangedEvent& PropertyChangedEvent, FProperty* PropertyThatChanged) { FBlueprintEditor::NotifyPostChange(PropertyChangedEvent, PropertyThatChanged); // When you change properties on a node, call CopyNodeDataToPreviewNode to allow pushing those to preview instance, for live editing for (TWeakObjectPtr< class UAnimGraphNode_Base > CurrentAnimGraphNode : SelectedAnimGraphNodes) { if (UAnimGraphNode_Base* CurrentNode = CurrentAnimGraphNode.Get()) { if (FAnimNode_Base* PreviewNode = FindAnimNode(CurrentNode)) { CurrentNode->CopyNodeDataToPreviewNode(PreviewNode); } } } } void FAnimationBlueprintEditor::Tick(float DeltaTime) { FBlueprintEditor::Tick(DeltaTime); GetPreviewScene()->InvalidateViews(); } bool FAnimationBlueprintEditor::IsEditable(UEdGraph* InGraph) const { bool bEditable = FBlueprintEditor::IsEditable(InGraph); if (InGraph) { bEditable &= (InGraph->GetTypedOuter() == GetBlueprintObj()); } return bEditable; } FText FAnimationBlueprintEditor::GetGraphDecorationString(UEdGraph* InGraph) const { if (!IsGraphInCurrentBlueprint(InGraph)) { return LOCTEXT("PersonaExternalGraphDecoration", " External Graph Preview"); } return FText::GetEmpty(); } TStatId FAnimationBlueprintEditor::GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(FAnimationBlueprintEditor, STATGROUP_Tickables); } void FAnimationBlueprintEditor::OnBlueprintPreCompile(UBlueprint* BlueprintToCompile) { if (PersonaToolkit.IsValid()) { UDebugSkelMeshComponent* PreviewMeshComponent = PersonaToolkit->GetPreviewMeshComponent(); if(PreviewMeshComponent && PreviewMeshComponent->PreviewInstance) { // If we are compiling an anim notify state the class will soon be sanitized and // if an anim instance is running a state when that happens it will likely // crash, so we end any states that are about to compile. UAnimPreviewInstance* Instance = PreviewMeshComponent->PreviewInstance; USkeletalMeshComponent* SkelMeshComp = Instance->GetSkelMeshComponent(); for(int32 Idx = Instance->ActiveAnimNotifyState.Num() - 1 ; Idx >= 0 ; --Idx) { FAnimNotifyEvent& Event = Instance->ActiveAnimNotifyState[Idx]; const FAnimNotifyEventReference& EventReference = Instance->ActiveAnimNotifyEventReference[Idx]; if(Event.NotifyStateClass->GetClass() == BlueprintToCompile->GeneratedClass) { Event.NotifyStateClass->NotifyEnd(SkelMeshComp, Cast(Event.NotifyStateClass->GetOuter()), EventReference); check(Instance->ActiveAnimNotifyState.Num() == Instance->ActiveAnimNotifyEventReference.Num()); Instance->ActiveAnimNotifyState.RemoveAt(Idx); Instance->ActiveAnimNotifyEventReference.RemoveAt(Idx); } } } } if(GetObjectsCurrentlyBeingEdited()->Num() > 0 && BlueprintToCompile == GetBlueprintObj()) { // Grab the currently debugged object, so we can re-set it below in OnBlueprintPostCompile DebuggedMeshComponent = nullptr; UAnimInstance* CurrentDebugObject = Cast(BlueprintToCompile->GetObjectBeingDebugged()); if(CurrentDebugObject) { // Force close any asset editors that are using the AnimScriptInstance (such as the Property Matrix), the class will be garbage collected GEditor->GetEditorSubsystem()->CloseOtherEditors(CurrentDebugObject, nullptr); DebuggedMeshComponent = CurrentDebugObject->GetSkelMeshComponent(); } } } void FAnimationBlueprintEditor::OnBlueprintPostCompile(UBlueprint* InBlueprint) { if(InBlueprint == GetBlueprintObj()) { if (DebuggedMeshComponent != nullptr) { if (DebuggedMeshComponent->GetAnimInstance() == nullptr) { // try reinitialize animation if it doesn't exist DebuggedMeshComponent->InitAnim(true); } // re-apply preview anim bp if needed UAnimBlueprint* AnimBlueprint = GetAnimBlueprint(); UAnimBlueprint* PreviewAnimBlueprint = AnimBlueprint ? AnimBlueprint->GetPreviewAnimationBlueprint() : nullptr; if (PreviewAnimBlueprint) { PersonaToolkit->GetPreviewScene()->SetPreviewAnimationBlueprint(PreviewAnimBlueprint, AnimBlueprint); } if(UAnimInstance* NewInstance = DebuggedMeshComponent->GetAnimInstance()) { if ((AnimBlueprint && NewInstance->IsA(AnimBlueprint->GeneratedClass)) || (PreviewAnimBlueprint && NewInstance->IsA(PreviewAnimBlueprint->GeneratedClass))) { PersonaUtils::SetObjectBeingDebugged(AnimBlueprint, NewInstance); } } } // reset the selected skeletal control nodes ClearSelectedAnimGraphNodes(); // if the user manipulated Pin values directly from the node, then should copy updated values to the internal node to retain data consistency OnPostCompile(); // We dont cache this persistently, only during a pre/post compile bracket DebuggedMeshComponent = nullptr; } // Make sure we re-enable ticking when we recompile (e.g. to fix an infinite loop that paused us) if (USkeletalMeshComponent* SkeletalMeshComponent = GetPreviewScene()->GetPreviewMeshComponent()) { SkeletalMeshComponent->SetComponentTickEnabled(true); } } void FAnimationBlueprintEditor::OnBlueprintChangedImpl(UBlueprint* InBlueprint, bool bIsJustBeingCompiled /*= false*/) { FBlueprintEditor::OnBlueprintChangedImpl(InBlueprint, bIsJustBeingCompiled); // calls PostCompile to copy proper values between anim nodes OnPostCompile(); } void FAnimationBlueprintEditor::CreateEditorModeManager() { EditorModeManager = MakeShareable(FModuleManager::LoadModuleChecked("Persona").CreatePersonaEditorModeManager()); } bool FAnimationBlueprintEditor::IsSectionVisible(NodeSectionID::Type InSectionID) const { const UAnimBlueprintSettings* AnimBlueprintSettings = GetDefault(); switch (InSectionID) { case NodeSectionID::GRAPH: return AnimBlueprintSettings->bAllowEventGraphs; case NodeSectionID::ANIMGRAPH: case NodeSectionID::ANIMLAYER: case NodeSectionID::FUNCTION: case NodeSectionID::FUNCTION_OVERRIDABLE: case NodeSectionID::INTERFACE: return true; case NodeSectionID::MACRO: return AnimBlueprintSettings->bAllowMacros; case NodeSectionID::VARIABLE: return true; case NodeSectionID::COMPONENT: return false; case NodeSectionID::DELEGATE: return AnimBlueprintSettings->bAllowDelegates; case NodeSectionID::USER_ENUM: case NodeSectionID::LOCAL_VARIABLE: case NodeSectionID::USER_STRUCT: case NodeSectionID::USER_SORTED: return true; default: break; } return true; } bool FAnimationBlueprintEditor::AreEventGraphsAllowed() const { const UAnimBlueprintSettings* AnimBlueprintSettings = GetDefault(); return AnimBlueprintSettings->bAllowEventGraphs; } bool FAnimationBlueprintEditor::AreMacrosAllowed() const { const UAnimBlueprintSettings* AnimBlueprintSettings = GetDefault(); return AnimBlueprintSettings->bAllowMacros; } bool FAnimationBlueprintEditor::AreDelegatesAllowed() const { const UAnimBlueprintSettings* AnimBlueprintSettings = GetDefault(); return AnimBlueprintSettings->bAllowDelegates; } void FAnimationBlueprintEditor::OnCreateComment() { TSharedPtr GraphEditor = FocusedGraphEdPtr.Pin(); if (GraphEditor.IsValid()) { if (UEdGraph* Graph = GraphEditor->GetCurrentGraph()) { FEdGraphSchemaAction_K2AddComment CommentAction; CommentAction.PerformAction(Graph, nullptr, GraphEditor->GetPasteLocation2f()); } } } void FAnimationBlueprintEditor::JumpToHyperlink(const UObject* ObjectReference, bool bRequestRename) { if(const UBlendSpaceGraph* BlendSpaceGraph = Cast(ObjectReference)) { TSharedRef Payload = FTabPayload_BlendSpaceGraph::Make(BlendSpaceGraph); DocumentManager->OpenDocument(Payload, FDocumentTracker::OpenNewDocument); } else { FBlueprintEditor::JumpToHyperlink(ObjectReference, bRequestRename); } } TSharedRef FAnimationBlueprintEditor::GetPreviewScene() const { return PersonaToolkit->GetPreviewScene(); } void FAnimationBlueprintEditor::HandleObjectsSelected(const TArray& InObjects) { SetDetailObjects(InObjects); } void FAnimationBlueprintEditor::HandleObjectSelected(UObject* InObject) { SetDetailObject(InObject); } void FAnimationBlueprintEditor::HandleSelectionChanged(const TArrayView>& InSelectedItems, ESelectInfo::Type InSelectInfo) { TArray Objects; Algo::TransformIf(InSelectedItems, Objects, [](const TSharedPtr& InItem) { return InItem->GetObject() != nullptr; }, [](const TSharedPtr& InItem) { return InItem->GetObject(); }); SetDetailObjects(Objects); } UObject* FAnimationBlueprintEditor::HandleGetObject() { return GetEditingObject(); } void FAnimationBlueprintEditor::HandleOpenNewAsset(UObject* InNewAsset) { GEditor->GetEditorSubsystem()->OpenEditorForAsset(InNewAsset); } void FAnimationBlueprintEditor::AddReferencedObjects( FReferenceCollector& Collector ) { Collector.AddReferencedObject( EditorOptions ); } FAnimNode_Base* FAnimationBlueprintEditor::FindAnimNode(UAnimGraphNode_Base* AnimGraphNode) const { FAnimNode_Base* AnimNode = nullptr; if (AnimGraphNode) { USkeletalMeshComponent* SkeletalMeshComponentToUse = nullptr; if(UAnimInstance* AnimInstance = Cast(GetAnimBlueprint()->GetObjectBeingDebugged())) { SkeletalMeshComponentToUse = AnimInstance->GetSkelMeshComponent(); } else { SkeletalMeshComponentToUse = GetPreviewScene()->GetPreviewMeshComponent(); } if (SkeletalMeshComponentToUse != nullptr && SkeletalMeshComponentToUse->GetAnimInstance() != nullptr) { AnimNode = AnimGraphNode->FindDebugAnimNode(SkeletalMeshComponentToUse); } } return AnimNode; } void FAnimationBlueprintEditor::OnSelectedNodesChangedImpl(const TSet& NewSelection) { FBlueprintEditor::OnSelectedNodesChangedImpl(NewSelection); IPersonaEditorModeManager* const PersonaEditorModeManager = static_cast(&GetEditorModeManager()); if (PersonaEditorModeManager) { // Update the list of selected nodes, being careful to maintain the order of the list as this is an important requirement of the UI. using FSelectedNodePtr = TWeakObjectPtr< class UAnimGraphNode_Base >; TArray< FSelectedNodePtr > AddSelection; // Nodes that should be added to the current selection. TArray< FSelectedNodePtr > RemSelection; // Nodes that should be removed from the current selection. // Compare the set of nodes in 'NewSelection' with the list of previously selected nodes to identify nodes that should be added / removed from the selection. { TArray< FSelectedNodePtr > OldSelectionSorted(SelectedAnimGraphNodes); TArray< FSelectedNodePtr > NewSelectionSorted; for (UObject* NewSelectedObject : NewSelection) { if (UAnimGraphNode_Base* NewSelectedAnimGraphNode = Cast(NewSelectedObject)) { NewSelectionSorted.Add(NewSelectedAnimGraphNode); } } auto SortPredicate = [](const FSelectedNodePtr& Lhs, const FSelectedNodePtr& Rhs) { return Lhs.Get() < Rhs.Get(); }; OldSelectionSorted.Sort(SortPredicate); NewSelectionSorted.Sort(SortPredicate); SortedContainerDifference(OldSelectionSorted, NewSelectionSorted, AddSelection, RemSelection, SortPredicate); } ReleaseAllManagedNodes(); // Register de-selection with all the previously selected nodes. // Remove all the nodes that are no longer selected. for (FSelectedNodePtr CurrentAnimGraphNode : RemSelection) { SelectedAnimGraphNodes.Remove(CurrentAnimGraphNode); } // Add all the newly selected nodes. for (FSelectedNodePtr CurrentAnimGraphNode : AddSelection) { SelectedAnimGraphNodes.Add(CurrentAnimGraphNode); } AcquireAllManagedNodes(); // Register re-selection with all the currently selected nodes. } bSelectRegularNode = false; for (FGraphPanelSelectionSet::TConstIterator It(NewSelection); It; ++It) { UEdGraphNode_Comment* SeqNode = Cast(*It); UAnimStateNodeBase* AnimGraphNodeBase = Cast(*It); UAnimStateEntryNode* AnimStateEntryNode = Cast(*It); if (!SeqNode && !AnimGraphNodeBase && !AnimStateEntryNode) { bSelectRegularNode = true; break; } } if (bHideUnrelatedNodes && !bLockNodeFadeState) { ResetAllNodesUnrelatedStates(); if ( bSelectRegularNode ) { HideUnrelatedNodes(); } } if (GetDefault()->bPoseWatchSelectedNodes) { HandlePoseWatchSelectedNodes(); } } void FAnimationBlueprintEditor::HandlePoseWatchSelectedNodes() { TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); if (FocusedGraphEd.IsValid()) { ReleaseAllManagedNodes(); // Register de-selection with all the previously selected nodes. UAnimBlueprint* AnimBP = GetAnimBlueprint(); TArray AllNodes = FocusedGraphEd->GetCurrentGraph()->Nodes; FGraphPanelSelectionSet SelectionNodes = GetSelectedNodes(); for (UEdGraphNode* Node : AllNodes) { UAnimGraphNode_Base* GraphNode = Cast(Node); UPoseWatch* PoseWatch = AnimationEditorUtils::FindPoseWatchForNode(GraphNode, AnimBP); if (GraphNode) { if (SelectionNodes.Contains(Node)) { if (!PoseWatch && GraphNode->IsPoseWatchable()) { PoseWatch = AnimationEditorUtils::MakePoseWatchForNode(AnimBP, GraphNode); PoseWatch->SetShouldDeleteOnDeselect(true); } } else { if (PoseWatch && PoseWatch->GetShouldDeleteOnDeselect()) { AnimationEditorUtils::RemovePoseWatch(PoseWatch, AnimBP); } } } } AcquireAllManagedNodes(); // Register re-selection with all the currently selected nodes. } } void FAnimationBlueprintEditor::RemoveAllSelectionPoseWatches() { TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); if (FocusedGraphEd.IsValid()) { ReleaseAllManagedNodes(); // Register de-selection with all the previously selected nodes. UAnimBlueprint* AnimBP = GetAnimBlueprint(); TArray AllNodes = FocusedGraphEd->GetCurrentGraph()->Nodes; for (UEdGraphNode* Node : AllNodes) { UAnimGraphNode_Base* GraphNode = Cast(Node); if (GraphNode) { UPoseWatch* PoseWatch = AnimationEditorUtils::FindPoseWatchForNode(GraphNode, AnimBP); if (PoseWatch && PoseWatch->GetShouldDeleteOnDeselect()) { AnimationEditorUtils::RemovePoseWatch(PoseWatch, AnimBP); } } } AcquireAllManagedNodes(); // Register re-selection with all the currently selected nodes. } } void FAnimationBlueprintEditor::OnPostCompile() { // act as if we have re-selected, so internal pointers are updated if (CurrentUISelection == FBlueprintEditor::SelectionState_Graph) { FGraphPanelSelectionSet SelectionSet = GetSelectedNodes(); OnSelectedNodesChangedImpl(SelectionSet); FocusInspectorOnGraphSelection(SelectionSet, /*bForceRefresh=*/ true); } // if the user manipulated Pin values directly from the node, then should copy updated values to the internal node to retain data consistency UEdGraph* FocusedGraph = GetFocusedGraph(); if (FocusedGraph) { // find UAnimGraphNode_Base for (UEdGraphNode* Node : FocusedGraph->Nodes) { UAnimGraphNode_Base* AnimGraphNode = Cast(Node); if (AnimGraphNode) { FAnimNode_Base* AnimNode = FindAnimNode(AnimGraphNode); if (AnimNode) { AnimGraphNode->CopyNodeDataToPreviewNode(AnimNode); } } } } } void FAnimationBlueprintEditor::HandlePinDefaultValueChanged(UEdGraphPin* InPinThatChanged) { UAnimGraphNode_Base* AnimGraphNode = Cast(InPinThatChanged->GetOwningNode()); if (AnimGraphNode) { FAnimNode_Base* AnimNode = FindAnimNode(AnimGraphNode); if (AnimNode) { AnimGraphNode->CopyNodeDataToPreviewNode(AnimNode); } } } void FAnimationBlueprintEditor::HandleSetObjectBeingDebugged(UObject* InObject) { FBlueprintEditor::HandleSetObjectBeingDebugged(InObject); // act as if we have re-selected, so internal pointers are updated if (CurrentUISelection == FBlueprintEditor::SelectionState_Graph) { FGraphPanelSelectionSet SelectionSet = GetSelectedNodes(); OnSelectedNodesChangedImpl(SelectionSet); } if (UAnimInstance* AnimInstance = Cast(InObject)) { USkeletalMeshComponent* SkeletalMeshComponent = AnimInstance->GetSkelMeshComponent(); if (SkeletalMeshComponent) { // If we are selecting the preview instance, reset us back to 'normal' if (InObject->GetWorld()->IsPreviewWorld()) { GetPreviewScene()->ShowDefaultMode(); if(GetPreviewScene()->GetPreviewMeshComponent()->PreviewInstance) { GetPreviewScene()->GetPreviewMeshComponent()->PreviewInstance->SetDebugSkeletalMeshComponent(nullptr); } if (GetPreviewScene()->GetPreviewMeshComponent()->bTrackAttachedInstanceLOD) { GetPreviewScene()->GetPreviewMeshComponent()->bTrackAttachedInstanceLOD = false; GetPreviewScene()->GetPreviewMeshComponent()->SetForcedLOD(0); } } else { // Otherwise set us to display the debugged instance via copy-pose GetPreviewScene()->GetPreviewMeshComponent()->EnablePreview(true, nullptr); if (GetPreviewScene()->GetPreviewMeshComponent()->PreviewInstance) { GetPreviewScene()->GetPreviewMeshComponent()->PreviewInstance->SetDebugSkeletalMeshComponent(SkeletalMeshComponent); GetPreviewScene()->GetPreviewMeshComponent()->bTrackAttachedInstanceLOD = true; } } } } else { // Clear the copy-pose component and set us back to 'normal' GetPreviewScene()->ShowDefaultMode(); if(GetPreviewScene()->GetPreviewMeshComponent()->PreviewInstance) { GetPreviewScene()->GetPreviewMeshComponent()->PreviewInstance->SetDebugSkeletalMeshComponent(nullptr); } if (GetPreviewScene()->GetPreviewMeshComponent()->bTrackAttachedInstanceLOD) { GetPreviewScene()->GetPreviewMeshComponent()->bTrackAttachedInstanceLOD = false; GetPreviewScene()->GetPreviewMeshComponent()->SetForcedLOD(0); } } } void FAnimationBlueprintEditor::HandlePreviewMeshChanged(USkeletalMesh* OldPreviewMesh, USkeletalMesh* NewPreviewMesh) { UObject* Object = GetBlueprintObj()->GetObjectBeingDebugged(); if(Object) { HandleSetObjectBeingDebugged(Object); } } void FAnimationBlueprintEditor::HandleViewportCreated(const TSharedRef& InPersonaViewport) { auto GetCompilationStateText = [this]() { if (UBlueprint* Blueprint = GetBlueprintObj()) { switch (Blueprint->Status) { case BS_UpToDate: case BS_UpToDateWithWarnings: // Fall thru and return empty string break; case BS_Dirty: return LOCTEXT("AnimBP_Dirty", "Preview out of date"); case BS_Error: return LOCTEXT("AnimBP_CompileError", "Compile Error"); default: return LOCTEXT("AnimBP_UnknownStatus", "Unknown Status"); } } return FText::GetEmpty(); }; auto GetCompilationStateVisibility = [this]() { if (UBlueprint* Blueprint = GetBlueprintObj()) { const bool bUpToDate = (Blueprint->Status == BS_UpToDate) || (Blueprint->Status == BS_UpToDateWithWarnings); return bUpToDate ? EVisibility::Collapsed : EVisibility::Visible; } return EVisibility::Collapsed; }; auto GetCompileButtonVisibility = [this]() { if (UBlueprint* Blueprint = GetBlueprintObj()) { return (Blueprint->Status == BS_Dirty) ? EVisibility::Visible : EVisibility::Collapsed; } return EVisibility::Collapsed; }; auto CompileBlueprint = [this]() { if (UBlueprint* Blueprint = GetBlueprintObj()) { if (!Blueprint->IsUpToDate()) { Compile(); } } return FReply::Handled(); }; auto GetErrorSeverity = [this]() { if (UBlueprint* Blueprint = GetBlueprintObj()) { return (Blueprint->Status == BS_Error) ? EMessageSeverity::Error : EMessageSeverity::Warning; } return EMessageSeverity::Warning; }; auto GetIcon = [this]() { if (UBlueprint* Blueprint = GetBlueprintObj()) { return (Blueprint->Status == BS_Error) ? FEditorFontGlyphs::Exclamation_Triangle : FEditorFontGlyphs::Eye; } return FEditorFontGlyphs::Eye; }; InPersonaViewport->AddNotification(MakeAttributeLambda(GetErrorSeverity), false, SNew(SHorizontalBox) .Visibility_Lambda(GetCompilationStateVisibility) +SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(4.0f, 4.0f) [ SNew(SHorizontalBox) .ToolTipText_Lambda(GetCompilationStateText) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ SNew(STextBlock) .TextStyle(FAppStyle::Get(), "AnimViewport.MessageText") .Font(FAppStyle::Get().GetFontStyle("FontAwesome.9")) .Text_Lambda(GetIcon) ] +SHorizontalBox::Slot() .VAlign(VAlign_Center) .FillWidth(1.0f) [ SNew(STextBlock) .Text_Lambda(GetCompilationStateText) .TextStyle(FAppStyle::Get(), "AnimViewport.MessageText") ] ] +SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) [ SNew(SButton) .ForegroundColor(FSlateColor::UseForeground()) .ButtonStyle(FAppStyle::Get(), "FlatButton.Success") .Visibility_Lambda(GetCompileButtonVisibility) .ToolTipText(LOCTEXT("AnimBPViewportCompileButtonToolTip", "Compile this Animation Blueprint to update the preview to reflect any recent changes.")) .OnClicked_Lambda(CompileBlueprint) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ SNew(STextBlock) .TextStyle(FAppStyle::Get(), "AnimViewport.MessageText") .Font(FAppStyle::Get().GetFontStyle("FontAwesome.9")) .Text(FEditorFontGlyphs::Cog) ] +SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .TextStyle(FAppStyle::Get(), "AnimViewport.MessageText") .Text(LOCTEXT("AnimBPViewportCompileButtonLabel", "Compile")) ] ] ], FPersonaViewportNotificationOptions(TAttribute::Create(GetCompilationStateVisibility)) ); auto GetInfiniteLoopVisibility = [this]() { if (USkeletalMeshComponent* SkeletalMeshComponent = GetPreviewScene()->GetPreviewMeshComponent()) { return SkeletalMeshComponent->IsComponentTickEnabled() ? EVisibility::Collapsed : EVisibility::Visible; } return EVisibility::Collapsed; }; InPersonaViewport->AddNotification(EMessageSeverity::Error, false, SNew(SHorizontalBox) .Visibility_Lambda(GetInfiniteLoopVisibility) +SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(4.0f, 4.0f) [ SNew(SHorizontalBox) .ToolTipText_Lambda(GetCompilationStateText) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ SNew(STextBlock) .TextStyle(FAppStyle::Get(), "AnimViewport.MessageText") .Font(FAppStyle::Get().GetFontStyle("FontAwesome.9")) .Text(FEditorFontGlyphs::Exclamation_Triangle) ] +SHorizontalBox::Slot() .VAlign(VAlign_Center) .FillWidth(1.0f) [ SNew(STextBlock) .Text(LOCTEXT("InfiniteLoopDetected", "Infinite Loop Detected")) .TextStyle(FAppStyle::Get(), "AnimViewport.MessageText") ] ], FPersonaViewportNotificationOptions(TAttribute::Create(GetInfiniteLoopVisibility)) ); } void FAnimationBlueprintEditor::LoadEditorSettings() { EditorOptions = NewObject(); if (EditorOptions->bHideUnrelatedNodes) { ToggleHideUnrelatedNodes(); } } void FAnimationBlueprintEditor::SaveEditorSettings() { if ( EditorOptions ) { EditorOptions->bHideUnrelatedNodes = bHideUnrelatedNodes; EditorOptions->SaveConfig(); } } void FAnimationBlueprintEditor::HandlePreviewAnimBlueprintCompiled(UBlueprint* InBlueprint) { UAnimBlueprint* AnimBlueprint = GetAnimBlueprint(); UAnimBlueprint* PreviewAnimBlueprint = AnimBlueprint->GetPreviewAnimationBlueprint(); if (PreviewAnimBlueprint) { GetPreviewScene()->SetPreviewAnimationBlueprint(PreviewAnimBlueprint, AnimBlueprint); } } void FAnimationBlueprintEditor::HandleAnimationSequenceBrowserCreated(const TSharedRef& InSequenceBrowser) { SequenceBrowser = InSequenceBrowser; } void FAnimationBlueprintEditor::HandleScriptException(const UObject* InObject, const FFrame& InFrame, const FBlueprintExceptionInfo& InInfo) { // If the object is an anim instance in our preview world and is infinitely looping, disable ticking (renabled on recompilation) if (InInfo.GetType() == EBlueprintExceptionType::InfiniteLoop) { if (InObject && InObject->IsA()) { UWorld* ObjectWorld = InObject->GetWorld(); TSharedRef ThisPreviewScene = GetPreviewScene(); if (ObjectWorld == ThisPreviewScene->GetWorld()) { if (USkeletalMeshComponent* SkeletalMeshComponent = ThisPreviewScene->GetPreviewMeshComponent()) { SkeletalMeshComponent->SetComponentTickEnabled(false); } } } } } #undef LOCTEXT_NAMESPACE