You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Skeleton compatibility is now bi-directional. Specifying a compatible skeleton A -> B now implies B -> A. Skeleton compatibility is now an editor-only concern. The runtime will attempt to do the 'best it can' via name -> name mappings. Only the editor will prevent assigning incompatible skeletons in (e.g.) asset pickers etc. Skeleton compatibility checks in editor can now be disabled in the editor preferences (and each asset picker now has a checkbox option in its view settings that allows for quick access to this). Moves FSkeletonRemapping to its own file (which is now private). Skeleton remappings are now generated on demand on worker threads just before animation decompression and stored in a registry, guarded by FRWScopeLock for thread-safety. Fixed some anim BP compiler edge cases where asset references on pins were not getting preloaded correctly, causing skeletons to be erroneously reported as missing. Exposed the current asset registry filter in SAssetView so that menu extensions can access it (and use it to provide context) #jira UE-166054 #jira UE-167355 #rb Jurre.deBaare,John.vanderBerg #preflight 635902602e6690262afa86f9 [CL 22878911 by Thomas Sarkanen in ue5-main branch]
2576 lines
88 KiB
C++
2576 lines
88 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 "AnimationBlueprintTemplateEditorMode.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"));
|
|
};
|
|
|
|
/////////////////////////////////////////////////////
|
|
// 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();
|
|
}
|
|
|
|
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)));
|
|
|
|
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 FAnimationBlueprintTemplateEditorMode(SharedThis(this))));
|
|
|
|
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))));
|
|
|
|
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());
|
|
|
|
ExtendMenu();
|
|
ExtendToolbar();
|
|
RegenerateMenusAndToolbars();
|
|
|
|
// Activate the initial mode (which will populate with a real layout)
|
|
SetCurrentMode(FAnimationBlueprintEditorModes::AnimationBlueprintEditorMode);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
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)
|
|
{
|
|
GetSkeletonTree()->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)
|
|
{
|
|
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 == 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()
|
|
{
|
|
GetSkeletonTree()->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;
|
|
}
|
|
}
|
|
|
|
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))
|
|
);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|
|
|