You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Change early-out for exception handling to skip exceptions for all object types in preview worlds, not just actors. Also handle excpetions in this case when the debugger is attached to the object already (allows for break on exceptions to work). Hook the BP exception delegate to disable ticking on the skeletal mesh component so we dont keep ticking runaway scripts Tweaked thumbnail request to not submit the same asset multiple times - this happens multiple times on compilation and can cause longer hitches when the thumbnail is requested and ticked with an infinite loop in place. #jira UE-188277 #rb dan.oconnor [CL 26841592 by Thomas Sarkanen in 5.3 branch]
2684 lines
92 KiB
C++
2684 lines
92 KiB
C++
// 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 "AnimationEditorPreviewScene.h"
|
|
#include "AnimationEditorUtils.h"
|
|
#include "AnimationGraph.h"
|
|
#include "AnimationGraphSchema.h"
|
|
#include "AssetRegistry/AssetData.h"
|
|
#include "BlendSpaceDocumentTabFactory.h"
|
|
#include "BlendSpaceGraph.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 "DetailWidgetRow.h"
|
|
#include "PersonaPreviewSceneDescription.h"
|
|
#include "Preferences/AnimationBlueprintEditorOptions.h"
|
|
#include "Preferences/PersonaOptions.h"
|
|
#include "PropertyCustomizationHelpers.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 <typename TContainerType, typename TPredicate> 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 (TObjectPtr<UPoseWatch> CurrentPoseWatch : AnimBP->PoseWatches)
|
|
{
|
|
UAnimGraphNode_Base* const CurrentAnimGraphNodePtr = Cast<UAnimGraphNode_Base>(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<FAnimationBlueprintEditor> AnimationBlueprintEditorPtr;
|
|
public:
|
|
void Construct(const FArguments& InArgs, TSharedPtr<FAnimationBlueprintEditor> 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<SWidget> PopulateSlot(TSharedRef<SWidget> 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<UAnimationBlueprintEditorSettings>();
|
|
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<UImportSubsystem>()->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<UAnimBlueprint>(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<FAnimationBlueprintEditorModule>("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<FAnimationBlueprintEditor> AnimationBlueprintEditor = GetAnimationBlueprintEditor(InToolMenu->Context);
|
|
if (AnimationBlueprintEditor.IsValid() && AnimationBlueprintEditor->PersonaToolkit.IsValid())
|
|
{
|
|
FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked<FPersonaModule>("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());
|
|
|
|
FPersonaToolkitArgs PersonaToolkitArgs;
|
|
PersonaToolkitArgs.OnPreviewSceneSettingsCustomized = FOnPreviewSceneSettingsCustomized::FDelegate::CreateSP(this, &FAnimationBlueprintEditor::HandleOnPreviewSceneSettingsCustomized);
|
|
|
|
FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>("Persona");
|
|
PersonaToolkit = PersonaModule.CreatePersonaToolkit(InAnimBlueprint, PersonaToolkitArgs);
|
|
|
|
PersonaToolkit->GetPreviewScene()->SetDefaultAnimationMode(EPreviewSceneDefaultAnimationMode::AnimationBlueprint);
|
|
PersonaToolkit->GetPreviewScene()->RegisterOnPreviewMeshChanged(FOnPreviewMeshChanged::CreateSP(this, &FAnimationBlueprintEditor::HandlePreviewMeshChanged));
|
|
|
|
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<ISkeletonEditorModule>("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<UObject*> 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<UBlueprint*> AnimBlueprints;
|
|
AnimBlueprints.Add(InAnimBlueprint);
|
|
|
|
CommonInitialization(AnimBlueprints, /*bShouldOpenInDefaultsMode=*/ false);
|
|
|
|
// Register document editor for blendspaces
|
|
DocumentManager->RegisterDocumentFactory(MakeShared<FBlendSpaceDocumentTabFactory>(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<UAnimationBlueprintEditorSettings>()->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> FAnimationBlueprintEditor::GetAnimationBlueprintEditor(const FToolMenuContext& InMenuContext)
|
|
{
|
|
if (UAnimationBlueprintToolMenuContext* Context = InMenuContext.FindContext<UAnimationBlueprintToolMenuContext>())
|
|
{
|
|
if (Context->AnimationBlueprintEditor.IsValid())
|
|
{
|
|
return StaticCastSharedPtr<FAnimationBlueprintEditor>(Context->AnimationBlueprintEditor.Pin());
|
|
}
|
|
}
|
|
|
|
return TSharedPtr<FAnimationBlueprintEditor>();
|
|
}
|
|
|
|
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<FAnimationBlueprintEditorModule>("AnimationBlueprintEditor");
|
|
AddToolbarExtender(AnimationBlueprintEditorModule.GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
|
|
TArray<IAnimationBlueprintEditorModule::FAnimationBlueprintEditorToolbarExtender> 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<FPersonaModule>("Persona");
|
|
TSharedRef<class IAssetFamily> AssetFamily = PersonaModule.CreatePersonaAssetFamily(GetBlueprintObj());
|
|
AddToolbarWidget(PersonaModule.CreateAssetFamilyShortcutWidget(SharedThis(this), AssetFamily));
|
|
}
|
|
));
|
|
}
|
|
}
|
|
|
|
UBlueprint* FAnimationBlueprintEditor::GetBlueprintObj() const
|
|
{
|
|
const TArray<UObject*>& EditingObjs = GetEditingObjects();
|
|
for (int32 i = 0; i < EditingObjs.Num(); ++i)
|
|
{
|
|
if (EditingObjs[i]->IsA<UAnimBlueprint>()) {return (UBlueprint*)EditingObjs[i];}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::SetDetailObjects(const TArray<UObject*>& InObjects)
|
|
{
|
|
Inspector->ShowDetailsForObjects(InObjects);
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::SetDetailObject(UObject* Obj)
|
|
{
|
|
TArray<UObject*> Objects;
|
|
if (Obj)
|
|
{
|
|
Objects.Add(Obj);
|
|
}
|
|
SetDetailObjects(Objects);
|
|
}
|
|
|
|
/** Called when graph editor focus is changed */
|
|
void FAnimationBlueprintEditor::OnGraphEditorFocused(const TSharedRef<class SGraphEditor>& InGraphEditor)
|
|
{
|
|
// Remove pose watches now before calling the base class implementation because that will switch the focus
|
|
if (GetDefault<UAnimationBlueprintEditorSettings>()->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<UAnimationGraph>(InGraphEditor->GetCurrentGraph());
|
|
if (AnimationGraph)
|
|
{
|
|
OnPinDefaultValueChangedHandle = AnimationGraph->OnPinDefaultValueChanged.Add(FOnPinDefaultValueChanged::FDelegate::CreateSP(this, &FAnimationBlueprintEditor::HandlePinDefaultValueChanged));
|
|
}
|
|
|
|
if (bHideUnrelatedNodes && GetSelectedNodes().Num() <= 0)
|
|
{
|
|
ResetAllNodesUnrelatedStates();
|
|
}
|
|
|
|
if (GetDefault<UAnimationBlueprintEditorSettings>()->bPoseWatchSelectedNodes)
|
|
{
|
|
HandlePoseWatchSelectedNodes();
|
|
}
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::OnGraphEditorBackgrounded(const TSharedRef<SGraphEditor>& InGraphEditor)
|
|
{
|
|
FBlueprintEditor::OnGraphEditorBackgrounded(InGraphEditor);
|
|
|
|
UAnimationGraph* AnimationGraph = Cast<UAnimationGraph>(InGraphEditor->GetCurrentGraph());
|
|
if (AnimationGraph)
|
|
{
|
|
AnimationGraph->OnPinDefaultValueChanged.Remove(OnPinDefaultValueChangedHandle);
|
|
}
|
|
}
|
|
|
|
/** Create Default Tabs **/
|
|
void FAnimationBlueprintEditor::CreateDefaultCommands()
|
|
{
|
|
{
|
|
FBlueprintEditor::CreateDefaultCommands();
|
|
}
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::OnCreateGraphEditorCommands(TSharedPtr<FUICommandList> 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<UAnimGraphNode_BlendListByInt>(Node))
|
|
{
|
|
BlendNode->AddPinToBlendList();
|
|
break;
|
|
}
|
|
else if (UAnimGraphNode_LayeredBoneBlend* FilterNode = Cast<UAnimGraphNode_LayeredBoneBlend>(Node))
|
|
{
|
|
FilterNode->AddPinToBlendByFilter();
|
|
break;
|
|
}
|
|
else if (UAnimGraphNode_MultiWayBlend* MultiBlendNode = Cast<UAnimGraphNode_MultiWayBlend>(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<UAnimGraphNode_BlendListByInt>(*NodeIt))
|
|
{
|
|
BlendListIntNode = BlendNode;
|
|
break;
|
|
}
|
|
else if (UAnimGraphNode_LayeredBoneBlend* LayeredBlendNode = Cast<UAnimGraphNode_LayeredBoneBlend>(*NodeIt))
|
|
{
|
|
BlendByFilterNode = LayeredBlendNode;
|
|
break;
|
|
}
|
|
else if (UAnimGraphNode_MultiWayBlend* MultiwayBlendNode = Cast<UAnimGraphNode_MultiWayBlend>(*NodeIt))
|
|
{
|
|
BlendByMultiway = MultiwayBlendNode;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
TSharedPtr<SGraphEditor> 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();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
if (UAnimGraphNode_Base* SelectedNode = Cast<UAnimGraphNode_Base>(*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<UAnimGraphNode_LinkedAnimGraphBase>(*NodeIt))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::OnTogglePoseWatch()
|
|
{
|
|
ReleaseAllManagedNodes();
|
|
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
UAnimBlueprint* AnimBP = GetAnimBlueprint();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
if (UAnimGraphNode_Base* SelectedNode = Cast<UAnimGraphNode_Base>(*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<UAnimationBlueprintEditorSettings>()->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<UAnimGraphNode_LinkedAnimGraphBase>(*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<UAnimGraphNode_SequencePlayer>(*NodeIter);
|
|
|
|
// see if sequence player
|
|
if ( OldNode && OldNode->Node.GetSequence() )
|
|
{
|
|
UEdGraph* TargetGraph = OldNode->GetGraph();
|
|
TargetGraph->Modify();
|
|
OldNode->Modify();
|
|
|
|
// create new evaluator
|
|
FGraphNodeCreator<UAnimGraphNode_SequenceEvaluator> 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<SGraphEditor> 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<UAnimGraphNode_SequenceEvaluator>(*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<UAnimGraphNode_SequencePlayer> 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<SGraphEditor> 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<UAnimGraphNode_BlendSpacePlayer>(*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<UAnimGraphNode_BlendSpaceEvaluator> 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<SGraphEditor> 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<UAnimGraphNode_BlendSpaceEvaluator>(*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<UAnimGraphNode_BlendSpacePlayer> 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<SGraphEditor> 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<UAnimGraphNode_BlendSpacePlayer>(*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<UAnimGraphNode_BlendSpaceGraph> 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<SGraphEditor> 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<UAnimGraphNode_PoseByName>(*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<UAnimGraphNode_PoseBlendNode> 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<SGraphEditor> 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<UAnimGraphNode_PoseBlendNode>(*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<UAnimGraphNode_PoseByName> 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<SGraphEditor> 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<UAnimGraphNode_RotationOffsetBlendSpace>(*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<UAnimGraphNode_AimOffsetLookAt> 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<SGraphEditor> 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<UAnimGraphNode_AimOffsetLookAt>(*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<UAnimGraphNode_RotationOffsetBlendSpace> 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<SGraphEditor> 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<UAnimGraphNode_RotationOffsetBlendSpace>(*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<UAnimGraphNode_RotationOffsetBlendSpaceGraph> 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<SGraphEditor> 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<UAnimGraphNode_Base>(*NodeIter))
|
|
{
|
|
UAnimationAsset* AnimAsset = Node->GetAnimationAsset();
|
|
if(AnimAsset)
|
|
{
|
|
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->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<UAnimationBlueprintToolMenuContext>();
|
|
AnimationBlueprintToolMenuContext->AnimationBlueprintEditor = SharedThis(this);
|
|
MenuContext.AddObject(AnimationBlueprintToolMenuContext);
|
|
|
|
UPersonaToolMenuContext* Context = NewObject<UPersonaToolMenuContext>();
|
|
Context->SetToolkit(GetPersonaToolkit());
|
|
|
|
MenuContext.AddObject(Context);
|
|
}
|
|
|
|
IAnimationSequenceBrowser* FAnimationBlueprintEditor::GetAssetBrowser() const
|
|
{
|
|
return SequenceBrowser.Pin().Get();
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::OnActiveTabChanged( TSharedPtr<SDockTab> PreviouslyActive, TSharedPtr<SDockTab> NewlyActivated )
|
|
{
|
|
if (!NewlyActivated.IsValid())
|
|
{
|
|
TArray<UObject*> 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<FCustomDebugObject>& 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<FAnimationBlueprintEditorModule>("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<UBlueprint*>& 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<UAnimationBlueprintEditorSettings>()->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<UBlueprint>() == 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<UAnimSequenceBase>(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<UAnimInstance>(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<UAssetEditorSubsystem>()->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<FPersonaModule>("Persona").CreatePersonaEditorModeManager());
|
|
}
|
|
|
|
bool FAnimationBlueprintEditor::IsSectionVisible(NodeSectionID::Type InSectionID) const
|
|
{
|
|
const UAnimBlueprintSettings* AnimBlueprintSettings = GetDefault<UAnimBlueprintSettings>();
|
|
|
|
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<UAnimBlueprintSettings>();
|
|
return AnimBlueprintSettings->bAllowEventGraphs;
|
|
}
|
|
|
|
bool FAnimationBlueprintEditor::AreMacrosAllowed() const
|
|
{
|
|
const UAnimBlueprintSettings* AnimBlueprintSettings = GetDefault<UAnimBlueprintSettings>();
|
|
return AnimBlueprintSettings->bAllowMacros;
|
|
}
|
|
|
|
bool FAnimationBlueprintEditor::AreDelegatesAllowed() const
|
|
{
|
|
const UAnimBlueprintSettings* AnimBlueprintSettings = GetDefault<UAnimBlueprintSettings>();
|
|
return AnimBlueprintSettings->bAllowDelegates;
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::OnCreateComment()
|
|
{
|
|
TSharedPtr<SGraphEditor> GraphEditor = FocusedGraphEdPtr.Pin();
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
if (UEdGraph* Graph = GraphEditor->GetCurrentGraph())
|
|
{
|
|
FEdGraphSchemaAction_K2AddComment CommentAction;
|
|
CommentAction.PerformAction(Graph, nullptr, GraphEditor->GetPasteLocation());
|
|
}
|
|
}
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::JumpToHyperlink(const UObject* ObjectReference, bool bRequestRename)
|
|
{
|
|
if(const UBlendSpaceGraph* BlendSpaceGraph = Cast<UBlendSpaceGraph>(ObjectReference))
|
|
{
|
|
TSharedRef<FTabPayload_BlendSpaceGraph> Payload = FTabPayload_BlendSpaceGraph::Make(BlendSpaceGraph);
|
|
DocumentManager->OpenDocument(Payload, FDocumentTracker::OpenNewDocument);
|
|
}
|
|
else
|
|
{
|
|
FBlueprintEditor::JumpToHyperlink(ObjectReference, bRequestRename);
|
|
}
|
|
}
|
|
|
|
TSharedRef<IPersonaPreviewScene> FAnimationBlueprintEditor::GetPreviewScene() const
|
|
{
|
|
return PersonaToolkit->GetPreviewScene();
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::HandleObjectsSelected(const TArray<UObject*>& InObjects)
|
|
{
|
|
SetDetailObjects(InObjects);
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::HandleObjectSelected(UObject* InObject)
|
|
{
|
|
SetDetailObject(InObject);
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::HandleSelectionChanged(const TArrayView<TSharedPtr<ISkeletonTreeItem>>& InSelectedItems, ESelectInfo::Type InSelectInfo)
|
|
{
|
|
TArray<UObject*> Objects;
|
|
Algo::TransformIf(InSelectedItems, Objects, [](const TSharedPtr<ISkeletonTreeItem>& InItem) { return InItem->GetObject() != nullptr; }, [](const TSharedPtr<ISkeletonTreeItem>& InItem) { return InItem->GetObject(); });
|
|
SetDetailObjects(Objects);
|
|
}
|
|
|
|
UObject* FAnimationBlueprintEditor::HandleGetObject()
|
|
{
|
|
return GetEditingObject();
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::HandleOpenNewAsset(UObject* InNewAsset)
|
|
{
|
|
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->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<UAnimInstance>(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<class UObject*>& NewSelection)
|
|
{
|
|
FBlueprintEditor::OnSelectedNodesChangedImpl(NewSelection);
|
|
|
|
IPersonaEditorModeManager* const PersonaEditorModeManager = static_cast<IPersonaEditorModeManager*>(&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<UAnimGraphNode_Base>(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<UEdGraphNode_Comment>(*It);
|
|
UAnimStateNodeBase* AnimGraphNodeBase = Cast<UAnimStateNodeBase>(*It);
|
|
UAnimStateEntryNode* AnimStateEntryNode = Cast<UAnimStateEntryNode>(*It);
|
|
if (!SeqNode && !AnimGraphNodeBase && !AnimStateEntryNode)
|
|
{
|
|
bSelectRegularNode = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bHideUnrelatedNodes && !bLockNodeFadeState)
|
|
{
|
|
ResetAllNodesUnrelatedStates();
|
|
|
|
if ( bSelectRegularNode )
|
|
{
|
|
HideUnrelatedNodes();
|
|
}
|
|
}
|
|
|
|
if (GetDefault<UAnimationBlueprintEditorSettings>()->bPoseWatchSelectedNodes)
|
|
{
|
|
HandlePoseWatchSelectedNodes();
|
|
}
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::HandlePoseWatchSelectedNodes()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
ReleaseAllManagedNodes(); // Register de-selection with all the previously selected nodes.
|
|
|
|
UAnimBlueprint* AnimBP = GetAnimBlueprint();
|
|
TArray<UEdGraphNode*> AllNodes = FocusedGraphEd->GetCurrentGraph()->Nodes;
|
|
|
|
FGraphPanelSelectionSet SelectionNodes = GetSelectedNodes();
|
|
|
|
for (UEdGraphNode* Node : AllNodes)
|
|
{
|
|
UAnimGraphNode_Base* GraphNode = Cast<UAnimGraphNode_Base>(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::HandleOnPreviewSceneSettingsCustomized(IDetailLayoutBuilder& DetailBuilder)
|
|
{
|
|
const TSharedRef<IPropertyHandle> PreviewAnimationBlueprintProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPersonaPreviewSceneDescription, PreviewAnimationBlueprint));
|
|
const TSharedRef<IPropertyHandle> ApplicationMethodProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPersonaPreviewSceneDescription, ApplicationMethod));
|
|
const TSharedRef<IPropertyHandle> LinkedAnimGraphTagProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPersonaPreviewSceneDescription, LinkedAnimGraphTag));
|
|
|
|
// customize "Preview Animation Blueprint" for animation blueprint classes
|
|
DetailBuilder.EditCategory("Animation Blueprint")
|
|
.AddProperty(PreviewAnimationBlueprintProperty)
|
|
.CustomWidget()
|
|
.NameContent()
|
|
[
|
|
SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
PreviewAnimationBlueprintProperty->CreatePropertyNameWidget()
|
|
]
|
|
]
|
|
.ValueContent()
|
|
.MaxDesiredWidth(250.0f)
|
|
.MinDesiredWidth(250.0f)
|
|
[
|
|
SNew(SObjectPropertyEntryBox)
|
|
.AllowedClass(UAnimBlueprint::StaticClass())
|
|
.PropertyHandle(PreviewAnimationBlueprintProperty)
|
|
.OnShouldFilterAsset(this, &FAnimationBlueprintEditor::HandleShouldFilterAsset, FName("TargetSkeleton"), false)
|
|
.OnObjectChanged(this, &FAnimationBlueprintEditor::HandlePreviewAnimBlueprintChanged)
|
|
.ThumbnailPool(DetailBuilder.GetThumbnailPool())
|
|
];
|
|
|
|
ApplicationMethodProperty->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([this]()
|
|
{
|
|
FScopedTransaction Transaction(LOCTEXT("SetAnimationBlueprintApplicationMethod", "Set Application Method"));
|
|
|
|
const TSharedRef<FAnimationEditorPreviewScene> LocalPreviewScene = StaticCastSharedRef<FAnimationEditorPreviewScene>(PersonaToolkit->GetPreviewScene());
|
|
const UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = LocalPreviewScene->GetPreviewSceneDescription();
|
|
PersonaToolkit->GetAnimBlueprint()->SetPreviewAnimationBlueprintApplicationMethod(PersonaPreviewSceneDescription->ApplicationMethod);
|
|
LocalPreviewScene->SetPreviewAnimationBlueprint(PersonaPreviewSceneDescription->PreviewAnimationBlueprint.Get(), PersonaToolkit->GetAnimBlueprint());
|
|
}));
|
|
|
|
DetailBuilder.EditCategory("Animation Blueprint")
|
|
.AddProperty(ApplicationMethodProperty)
|
|
.IsEnabled(MakeAttributeLambda([this]()
|
|
{
|
|
TSharedRef<FAnimationEditorPreviewScene> LocalPreviewScene = StaticCastSharedRef<FAnimationEditorPreviewScene>(PersonaToolkit->GetPreviewScene());
|
|
UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = LocalPreviewScene->GetPreviewSceneDescription();
|
|
|
|
return PersonaPreviewSceneDescription->PreviewAnimationBlueprint.IsValid();
|
|
}));
|
|
|
|
LinkedAnimGraphTagProperty->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([this]()
|
|
{
|
|
FScopedTransaction Transaction(LOCTEXT("SetAnimationBlueprintTag", "Set Linked Anim Graph Tag"));
|
|
|
|
TSharedRef<FAnimationEditorPreviewScene> LocalPreviewScene = StaticCastSharedRef<FAnimationEditorPreviewScene>(PersonaToolkit->GetPreviewScene());
|
|
UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = LocalPreviewScene->GetPreviewSceneDescription();
|
|
PersonaToolkit->GetAnimBlueprint()->SetPreviewAnimationBlueprintTag(PersonaPreviewSceneDescription->LinkedAnimGraphTag);
|
|
LocalPreviewScene->SetPreviewAnimationBlueprint(PersonaPreviewSceneDescription->PreviewAnimationBlueprint.Get(), PersonaToolkit->GetAnimBlueprint());
|
|
}));
|
|
|
|
DetailBuilder.EditCategory("Animation Blueprint")
|
|
.AddProperty(LinkedAnimGraphTagProperty)
|
|
.IsEnabled(MakeAttributeLambda([this]()
|
|
{
|
|
TSharedRef<FAnimationEditorPreviewScene> LocalPreviewScene = StaticCastSharedRef<FAnimationEditorPreviewScene>(PersonaToolkit->GetPreviewScene());
|
|
UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = LocalPreviewScene->GetPreviewSceneDescription();
|
|
|
|
return PersonaPreviewSceneDescription->PreviewAnimationBlueprint.IsValid() && PersonaPreviewSceneDescription->ApplicationMethod == EPreviewAnimationBlueprintApplicationMethod::LinkedAnimGraph;
|
|
}));
|
|
|
|
if (PersonaToolkit->GetAnimBlueprint())
|
|
{
|
|
PersonaToolkit->GetAnimBlueprint()->OnCompiled().AddSP(this, &FAnimationBlueprintEditor::HandleAnimBlueprintCompiled);
|
|
}
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::HandleAnimBlueprintCompiled(UBlueprint* Blueprint) const
|
|
{
|
|
// Only re-initialize controller if we are not debugging an external instance.
|
|
// If we switch at this point then we will disconnect from the external instance
|
|
const TSharedRef<FAnimationEditorPreviewScene> AnimPreviewScene = StaticCastSharedRef<FAnimationEditorPreviewScene>(GetPreviewScene());
|
|
if(AnimPreviewScene->GetPreviewMeshComponent()->PreviewInstance == nullptr || AnimPreviewScene->GetPreviewMeshComponent()->PreviewInstance->GetDebugSkeletalMeshComponent() == nullptr)
|
|
{
|
|
UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = AnimPreviewScene->GetPreviewSceneDescription();
|
|
PersonaPreviewSceneDescription->PreviewControllerInstance->UninitializeView(PersonaPreviewSceneDescription, &GetPreviewScene().Get());
|
|
PersonaPreviewSceneDescription->PreviewControllerInstance->InitializeView(PersonaPreviewSceneDescription, &GetPreviewScene().Get());
|
|
}
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::HandlePreviewAnimBlueprintChanged(const FAssetData& InAssetData) const
|
|
{
|
|
UAnimBlueprint* NewAnimBlueprint = Cast<UAnimBlueprint>(InAssetData.GetAsset());
|
|
PersonaToolkit->SetPreviewAnimationBlueprint(NewAnimBlueprint);
|
|
}
|
|
|
|
bool FAnimationBlueprintEditor::HandleShouldFilterAsset(
|
|
const FAssetData& InAssetData,
|
|
FName InTag,
|
|
bool bCanUseDifferentSkeleton) const
|
|
{
|
|
if (bCanUseDifferentSkeleton && GetDefault<UPersonaOptions>()->bAllowPreviewMeshCollectionsToSelectFromDifferentSkeletons)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const TSharedRef<FAnimationEditorPreviewScene> AnimPreviewScene = StaticCastSharedRef<FAnimationEditorPreviewScene>(GetPreviewScene());
|
|
const UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = AnimPreviewScene->GetPreviewSceneDescription();
|
|
if(!PersonaPreviewSceneDescription->PreviewMesh.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const USkeleton* Skeleton = PersonaPreviewSceneDescription->PreviewMesh->GetSkeleton();
|
|
const FString SkeletonTag = InAssetData.GetTagValueRef<FString>(InTag);
|
|
if (Skeleton && Skeleton->IsCompatibleForEditor(SkeletonTag))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::RemoveAllSelectionPoseWatches()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
ReleaseAllManagedNodes(); // Register de-selection with all the previously selected nodes.
|
|
|
|
UAnimBlueprint* AnimBP = GetAnimBlueprint();
|
|
TArray<UEdGraphNode*> AllNodes = FocusedGraphEd->GetCurrentGraph()->Nodes;
|
|
|
|
for (UEdGraphNode* Node : AllNodes)
|
|
{
|
|
UAnimGraphNode_Base* GraphNode = Cast<UAnimGraphNode_Base>(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<UAnimGraphNode_Base>(Node);
|
|
if (AnimGraphNode)
|
|
{
|
|
FAnimNode_Base* AnimNode = FindAnimNode(AnimGraphNode);
|
|
if (AnimNode)
|
|
{
|
|
AnimGraphNode->CopyNodeDataToPreviewNode(AnimNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::HandlePinDefaultValueChanged(UEdGraphPin* InPinThatChanged)
|
|
{
|
|
UAnimGraphNode_Base* AnimGraphNode = Cast<UAnimGraphNode_Base>(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<UAnimInstance>(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();
|
|
GetPreviewScene()->GetPreviewMeshComponent()->PreviewInstance->SetDebugSkeletalMeshComponent(nullptr);
|
|
GetPreviewScene()->GetPreviewMeshComponent()->bTrackAttachedInstanceLOD = false;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise set us to display the debugged instance via copy-pose
|
|
GetPreviewScene()->GetPreviewMeshComponent()->EnablePreview(true, nullptr);
|
|
GetPreviewScene()->GetPreviewMeshComponent()->PreviewInstance->SetDebugSkeletalMeshComponent(SkeletalMeshComponent);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Clear the copy-pose component and set us back to 'normal'
|
|
GetPreviewScene()->ShowDefaultMode();
|
|
GetPreviewScene()->GetPreviewMeshComponent()->PreviewInstance->SetDebugSkeletalMeshComponent(nullptr);
|
|
GetPreviewScene()->GetPreviewMeshComponent()->bTrackAttachedInstanceLOD = false;
|
|
}
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::HandlePreviewMeshChanged(USkeletalMesh* OldPreviewMesh, USkeletalMesh* NewPreviewMesh)
|
|
{
|
|
UObject* Object = GetBlueprintObj()->GetObjectBeingDebugged();
|
|
if(Object)
|
|
{
|
|
HandleSetObjectBeingDebugged(Object);
|
|
}
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::HandleViewportCreated(const TSharedRef<IPersonaViewport>& 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<EVisibility>::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<EVisibility>::Create(GetInfiniteLoopVisibility))
|
|
);
|
|
}
|
|
|
|
void FAnimationBlueprintEditor::LoadEditorSettings()
|
|
{
|
|
EditorOptions = NewObject<UAnimationBlueprintEditorOptions>();
|
|
|
|
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<IAnimationSequenceBrowser>& 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<UAnimInstance>())
|
|
{
|
|
UWorld* ObjectWorld = InObject->GetWorld();
|
|
TSharedRef<IPersonaPreviewScene> ThisPreviewScene = GetPreviewScene();
|
|
if (ObjectWorld == ThisPreviewScene->GetWorld())
|
|
{
|
|
if (USkeletalMeshComponent* SkeletalMeshComponent = ThisPreviewScene->GetPreviewMeshComponent())
|
|
{
|
|
SkeletalMeshComponent->SetComponentTickEnabled(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|
|
|