Files
UnrealEngineUWP/Engine/Source/Editor/AnimationEditor/Private/AnimationEditor.cpp
JeanMichel Dignard e6d45383d5 Copying //UE4/Dev-Enterprise to //UE4/Dev-Main (Source: //UE4/Dev-Enterprise @ 4341740)
#lockdown Nick.Penwarden
#rb none

============================
  MAJOR FEATURES & CHANGES
============================

Change 4280523 by Patrick.Boutot

	Add option in AjaCustomTimeStep to wait until the frame to be ready. Previously, the frame was there but not yet processed so it was possible that it was not ready by the time we wanted to read it. It won't work with interlaced because the 2 fields are processed at the same time. In interlaced, will get a 30fps behaviour when we actually want a 60fps.
	Fix bug that didn't set and reset bIsOwned properly when it was first initialized as not owned.

Change 4280526 by Patrick.Boutot

	Add accessor to get the leaf media source or output.

Change 4280624 by Patrick.Boutot

	Add timecode acessor to media samples

Change 4280626 by Patrick.Boutot

	Rework the timing for AJA Media Player. Previously, we took the timing of the frame. That was a bad idea because if 2 incomings video frames were coming a the same time, you would only show one. Making the buffering system useless.
	That affects the Custom Time Step since it was waiting for the interrupt signal and in some behavior we would like the frame to be ready to be used by UE. Same the timecode in the MediaSample because we may not used it to stamps the frame.

Change 4283022 by Patrick.Boutot

	[EditorScriptingUtilitites] Check folder names invalid characters separatly from the object's name.
	#jira UE-59886,  UE-62333

Change 4283112 by Patrick.Boutot

	Remove MediaFrameworkUtilititesModule dependency to the Settings module at runtime.
	Rename TimemanagemenetEditor module names.

Change 4283426 by JeanLuc.Corenthin

	Fix crash with FBX file

	#jira UE-62501

Change 4284940 by Patrick.Boutot

	A widget that let you select a single permutation from a list. It groups the values into categories and removes duplicates inside that category.

Change 4285471 by Patrick.Boutot

	Remove MediaFrameworkUtilititesModule dependency to the Settings module at runtime.

Change 4286925 by Patrick.Boutot

	[AJA] Add support to read LTC from the reference In.
	Add more detail on video format and the device.
	MediaSource use the Permutations Selection widget to select his mode and device.
	Remove debugging option to trigger an AJA custom time step and timecode provider.
	Remove the UYVY pixel option from AJA. It's better do to the conversion on the AJA card that on the GPU.
	Change the tooltip and category for some AjaMediaSource properties.

Change 4287026 by Julien.StJean

	Modifed the file STimeCodeProviderTab.cpp to fix the position of a SComboButton that wasn't properly place.

Change 4287663 by Jon.Nabozny

	Add timecode messages into nDisplay, and sync those between Master and Slave

Change 4287884 by Jon.Nabozny

	Create a TimecodeProvider for SystemTime and introduce a notion for DefaultTimecodeProvider in Engine.

Change 4288050 by Jon.Nabozny

	Rework the TimeSynchronization implementation for usability and functionality.

Change 4288283 by Jon.Nabozny

	Fixed swapped MetaClass and DisplayName options on UEngine::DefaultTimecodeProviderClassName;

Change 4288352 by Jon.Nabozny

	Set TimecodeProviderClassName and DefaultTimecodeProviderClassName in BaseEngine.ini

Change 4288378 by Jon.Nabozny

	Fixup some issues in TimecodeSynchronizer where code was reset improperly due to multiple unshelves / resolves.

Change 4288394 by Jon.Nabozny

	Add TimeSync functionality into LiveLink. Also add test cases for this. This should allow us to easily synchronize multiple LiveLink sources together, as well as synchronize those to anything else using the sync system (Relies on CL-4235417)

Change 4288899 by Patrick.Boutot

	Fix initialization order of FMediaIOCorePlayerBase variables

Change 4289157 by Patrick.Boutot

	Allow the user to change the source of a capture without stopping the current capture.
	[AJA] AjaMediaCapture, add support for UpdateSceneViewport & UpdateRenderTarget
	@made by julien.stjean

Change 4291328 by Jon.Nabozny

	Report the Skeleton Guid with TimeSyncData and track sync state in LiveLinkTimeSynchronizationSource.
	This prevents a crash that can happen if a source is quickly cleared and reset before the next tick of Time Synchronization.

Change 4296294 by Jon.Nabozny

	Fixup errors when TimecodeProviderClassName is empty. It's valid to leave this empty.

Change 4297122 by Patrick.Boutot

	Media Profile with timecode provider & custom time step

Change 4301855 by Austin.Crismore

	Fix for movment scaling and virtual joystick controls. Movement scaling in for truck and dolly is locked to the world xy plane, and virtual joysticks use their own method for movement scaling now.

	#jira UE-61762, UE-62187

Change 4301856 by Austin.Crismore

	Virtual sequence level controller now listens to on object spawned, so that it can intercept the camera actor and disable attatching to HMD to prevent camera movement that isn't from the level sequence

	#jira UE-61766

Change 4301860 by Austin.Crismore

	Fix for touch scrubbing. Added default values back in. Added logic to only allow scrubbing when touch focus was off.

	#jira UE-61865

Change 4302294 by Jamie.Dale

	Added functions to get your the localized spoken and subtitle text from a dialogue wave

Change 4304393 by Jamie.Dale

	Added support for BlueprintAssignable properties in Python

Change 4305852 by Jamie.Dale

	Removed hard-dependency between EditorScriptingUtilities and PythonScriptPlugin

	Backed-out changelist 4259264 and query Python availability based on whether anything is available to handle the command

	#jira UE-62318

Change 4308550 by Jamie.Dale

	Fixed crash when passing a null world to Python actor iterators

Change 4311867 by Homam.Bahnassi

	Revit master material with exposed parameters matching the API when possible.

Change 4314428 by Francis.Hurteau

	Made the usage of the bBuildDeveloperTools switch independent of the bCompileAgainstEngine switch.
	Changed bBuildDeveloperTools TargetRule in UnrealBuildTool to a nullable to keep the old behavior in case where bBuildDeveloperTools wasn't explicitly set in TargetRules

Change 4315134 by Jamie.Dale

	Defer editable text focus selection until mouse-up to allow the user to make an initial selection

	#jira UE-58086

Change 4318615 by Johan.Duparc

	EditorFactories: consistent return values after asset import.

Change 4322459 by Jamie.Dale

	Made SequencerScripting an Editor plugin as it depends on PythonScriptPlugin which is an Editor plugin

	This was causing issues at runtime when SequencerScripting was enabled, as it failed to load PythonScriptPlugin (which hadn't been built).

Change 4323341 by Francis.Hurteau

	Implement proper message bus protocol version negociation with static nodes

Change 4323733 by Francis.Hurteau

	Fix VR Pausing Sequence Scrubbing just setting playback speed to 0.0

Change 4324319 by Jamie.Dale

	Exposed transactions to Blueprints

Change 4325847 by Alistair.White

	Copying //Tasks/UE4/Private-PixelStreaming@4325566 to Dev-Enterprise-Minimal (//UE4/Dev-Enterprise-Minimal)

	This adds the new experimental PixelStreaming plugin to allow streaming of an Unreal client's audio & video stream to a browser through the WebRTC protocol to support new uses for enterprise customers.

Change 4326282 by Simon.Tourangeau

	nDisplay native present handler

Change 4326581 by Jamie.Dale

	Replacing FDateTime with int64 Ticks value to workaround UE-63485

Change 4326599 by Homam.Bahnassi

	Moving texture coords outside UVEdit function to allow using different UV channels.

Change 4333250 by Francis.Hurteau

	Small TFuture changes:
	* cleans up TFuture::Then with usage of TUniqueFunction
	* added TFuture::Reset to invalidate it and remove continuation from a future shared state

Change 4333359 by Homam.Bahnassi

	Support scaling and rotating UVs around arbitrary pivot

Change 4333566 by Johan.Duparc

	Expose ProxyLOD functionalities to Scripting
	#jira UEENT-1788

Change 4333988 by Jamie.Dale

	Allow UHT to parse FText default parameter values

	INVTEXT, NSLOCTEXT, LOCTABLE, and FText::GetEmpty() are supported. LOCTEXT isn't as it relies on an external macro that is known to C++ but not to UHT (NSLOCTEXT can easily be used instead).

Change 4335020 by Francis.Hurteau

	Uncomment MessageBus::Send deprecation notice for 4.21
	Update MessageBus Send usage to new API

Change 4335195 by JeanMichel.Dignard

	Add a SetLodFromStaticMesh script utility function

	#jira UEENT-1789

Change 4335231 by Anousack.Kitisa

	Added functions to generate planar, cylindrical, box UV mapping.

	#jira UEENT-1598

Change 4335373 by Jamie.Dale

	Cleaned up some places creating empty literal texts

Change 4335458 by Jamie.Dale

	Allow UHT to parse FText() as an alias of FText::GetEmpty() when processing default values

Change 4335875 by Max.Chen

	Sequencer: Clear RF_Transient on pasted tracks/sections

	#jira UE-63537

Change 4336497 by Johan.Duparc

	ProxyLOD: Fix progress bar issue
	- removed duplicated code
	- removed duplicated LongTask object
	#jira UEENT-1788

Change 4336723 by Jamie.Dale

	Ensure that Python generated types create their CDO at the correct point

	#jira UE-62895

Change 4340594 by Ben.Marsh

	Fix manifest being invalidated when building two enterprise targets in a row. Fixes CIS error.

	#jira UE-63644

[CL 4342443 by JeanMichel Dignard in Main branch]
2018-09-04 16:35:02 -04:00

1197 lines
40 KiB
C++

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "AnimationEditor.h"
#include "Misc/MessageDialog.h"
#include "Modules/ModuleManager.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "EditorStyleSet.h"
#include "EditorReimportHandler.h"
#include "Animation/SmartName.h"
#include "Animation/AnimationAsset.h"
#include "Animation/DebugSkelMeshComponent.h"
#include "AssetData.h"
#include "EdGraph/EdGraphSchema.h"
#include "Animation/AnimSequence.h"
#include "Animation/AnimMontage.h"
#include "Editor/EditorEngine.h"
#include "Factories/AnimSequenceFactory.h"
#include "Factories/PoseAssetFactory.h"
#include "EngineGlobals.h"
#include "Editor.h"
#include "IAnimationEditorModule.h"
#include "IPersonaToolkit.h"
#include "PersonaModule.h"
#include "AnimationEditorMode.h"
#include "IPersonaPreviewScene.h"
#include "AnimationEditorCommands.h"
#include "IDetailsView.h"
#include "ISkeletonTree.h"
#include "ISkeletonEditorModule.h"
#include "IDocumentation.h"
#include "Widgets/Docking/SDockTab.h"
#include "Animation/PoseAsset.h"
#include "AnimPreviewInstance.h"
#include "ScopedTransaction.h"
#include "IContentBrowserSingleton.h"
#include "ContentBrowserModule.h"
#include "AnimationEditorUtils.h"
#include "AssetRegistryModule.h"
#include "IAssetFamily.h"
#include "IAnimationSequenceBrowser.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "PersonaCommonCommands.h"
#include "Sound/SoundWave.h"
#include "Engine/CurveTable.h"
#include "Developer/AssetTools/Public/IAssetTools.h"
#include "Developer/AssetTools/Public/AssetToolsModule.h"
#include "SequenceRecorderUtils.h"
#include "ISkeletonTreeItem.h"
#include "Algo/Transform.h"
const FName AnimationEditorAppIdentifier = FName(TEXT("AnimationEditorApp"));
const FName AnimationEditorModes::AnimationEditorMode(TEXT("AnimationEditorMode"));
const FName AnimationEditorTabs::DetailsTab(TEXT("DetailsTab"));
const FName AnimationEditorTabs::SkeletonTreeTab(TEXT("SkeletonTreeView"));
const FName AnimationEditorTabs::ViewportTab(TEXT("Viewport"));
const FName AnimationEditorTabs::AdvancedPreviewTab(TEXT("AdvancedPreviewTab"));
const FName AnimationEditorTabs::DocumentTab(TEXT("Document"));
const FName AnimationEditorTabs::AssetBrowserTab(TEXT("SequenceBrowser"));
const FName AnimationEditorTabs::AssetDetailsTab(TEXT("AnimAssetPropertiesTab"));
const FName AnimationEditorTabs::CurveNamesTab(TEXT("AnimCurveViewerTab"));
const FName AnimationEditorTabs::SlotNamesTab(TEXT("SkeletonSlotNames"));
DEFINE_LOG_CATEGORY(LogAnimationEditor);
#define LOCTEXT_NAMESPACE "AnimationEditor"
FAnimationEditor::FAnimationEditor()
{
UEditorEngine* Editor = Cast<UEditorEngine>(GEngine);
if (Editor != nullptr)
{
Editor->RegisterForUndo(this);
}
}
FAnimationEditor::~FAnimationEditor()
{
UEditorEngine* Editor = Cast<UEditorEngine>(GEngine);
if (Editor != nullptr)
{
Editor->UnregisterForUndo(this);
}
FEditorDelegates::OnAssetPostImport.RemoveAll(this);
FReimportManager::Instance()->OnPostReimport().RemoveAll(this);
}
void FAnimationEditor::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_AnimationEditor", "Animation Editor"));
FAssetEditorToolkit::RegisterTabSpawners( InTabManager );
}
void FAnimationEditor::UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
}
void FAnimationEditor::InitAnimationEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, UAnimationAsset* InAnimationAsset)
{
AnimationAsset = InAnimationAsset;
// Register post import callback to catch animation imports when we have the asset open (we need to reinit)
FReimportManager::Instance()->OnPostReimport().AddRaw(this, &FAnimationEditor::HandlePostReimport);
FEditorDelegates::OnAssetPostImport.AddRaw(this, &FAnimationEditor::HandlePostImport);
FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked<FPersonaModule>("Persona");
PersonaToolkit = PersonaModule.CreatePersonaToolkit(InAnimationAsset);
PersonaToolkit->GetPreviewScene()->SetDefaultAnimationMode(EPreviewSceneDefaultAnimationMode::Animation);
FSkeletonTreeArgs SkeletonTreeArgs;
SkeletonTreeArgs.OnSelectionChanged = FOnSkeletonTreeSelectionChanged::CreateSP(this, &FAnimationEditor::HandleSelectionChanged);
SkeletonTreeArgs.PreviewScene = PersonaToolkit->GetPreviewScene();
SkeletonTreeArgs.ContextName = GetToolkitFName();
ISkeletonEditorModule& SkeletonEditorModule = FModuleManager::GetModuleChecked<ISkeletonEditorModule>("SkeletonEditor");
SkeletonTree = SkeletonEditorModule.CreateSkeletonTree(PersonaToolkit->GetSkeleton(), SkeletonTreeArgs);
const bool bCreateDefaultStandaloneMenu = true;
const bool bCreateDefaultToolbar = true;
const TSharedRef<FTabManager::FLayout> DummyLayout = FTabManager::NewLayout("NullLayout")->AddArea(FTabManager::NewPrimaryArea());
FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, AnimationEditorAppIdentifier, DummyLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, InAnimationAsset);
BindCommands();
AddApplicationMode(
AnimationEditorModes::AnimationEditorMode,
MakeShareable(new FAnimationEditorMode(SharedThis(this), SkeletonTree.ToSharedRef())));
SetCurrentMode(AnimationEditorModes::AnimationEditorMode);
ExtendMenu();
ExtendToolbar();
RegenerateMenusAndToolbars();
OpenNewAnimationDocumentTab(AnimationAsset);
}
FName FAnimationEditor::GetToolkitFName() const
{
return FName("AnimationEditor");
}
FText FAnimationEditor::GetBaseToolkitName() const
{
return LOCTEXT("AppLabel", "AnimationEditor");
}
FString FAnimationEditor::GetWorldCentricTabPrefix() const
{
return LOCTEXT("WorldCentricTabPrefix", "AnimationEditor ").ToString();
}
FLinearColor FAnimationEditor::GetWorldCentricTabColorScale() const
{
return FLinearColor(0.3f, 0.2f, 0.5f, 0.5f);
}
void FAnimationEditor::Tick(float DeltaTime)
{
GetPersonaToolkit()->GetPreviewScene()->InvalidateViews();
}
TStatId FAnimationEditor::GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FAnimationEditor, STATGROUP_Tickables);
}
void FAnimationEditor::AddReferencedObjects(FReferenceCollector& Collector)
{
Collector.AddReferencedObject(AnimationAsset);
}
void FAnimationEditor::BindCommands()
{
FAnimationEditorCommands::Register();
ToolkitCommands->MapAction(FAnimationEditorCommands::Get().ApplyCompression,
FExecuteAction::CreateSP(this, &FAnimationEditor::OnApplyCompression),
FCanExecuteAction::CreateSP(this, &FAnimationEditor::HasValidAnimationSequence));
ToolkitCommands->MapAction(FAnimationEditorCommands::Get().SetKey,
FExecuteAction::CreateSP(this, &FAnimationEditor::OnSetKey),
FCanExecuteAction::CreateSP(this, &FAnimationEditor::CanSetKey));
ToolkitCommands->MapAction(FAnimationEditorCommands::Get().ReimportAnimation,
FExecuteAction::CreateSP(this, &FAnimationEditor::OnReimportAnimation),
FCanExecuteAction::CreateSP(this, &FAnimationEditor::HasValidAnimationSequence));
ToolkitCommands->MapAction(FAnimationEditorCommands::Get().ApplyAnimation,
FExecuteAction::CreateSP(this, &FAnimationEditor::OnApplyRawAnimChanges),
FCanExecuteAction::CreateSP(this, &FAnimationEditor::CanApplyRawAnimChanges));
ToolkitCommands->MapAction(FAnimationEditorCommands::Get().ExportToFBX_AnimData,
FExecuteAction::CreateSP(this, &FAnimationEditor::OnExportToFBX, EPoseSourceOption::CurrentAnimation_AnimData),
FCanExecuteAction::CreateSP(this, &FAnimationEditor::HasValidAnimationSequence));
ToolkitCommands->MapAction(FAnimationEditorCommands::Get().ExportToFBX_PreviewMesh,
FExecuteAction::CreateSP(this, &FAnimationEditor::OnExportToFBX, EPoseSourceOption::CurrentAnimation_PreviewMesh),
FCanExecuteAction::CreateSP(this, &FAnimationEditor::HasValidAnimationSequence));
ToolkitCommands->MapAction(FAnimationEditorCommands::Get().AddLoopingInterpolation,
FExecuteAction::CreateSP(this, &FAnimationEditor::OnAddLoopingInterpolation),
FCanExecuteAction::CreateSP(this, &FAnimationEditor::HasValidAnimationSequence));
ToolkitCommands->MapAction(FAnimationEditorCommands::Get().RemoveBoneTracks,
FExecuteAction::CreateSP(this, &FAnimationEditor::OnRemoveBoneTrack),
FCanExecuteAction::CreateSP(this, &FAnimationEditor::HasValidAnimationSequence));
ToolkitCommands->MapAction(FPersonaCommonCommands::Get().TogglePlay,
FExecuteAction::CreateRaw(&GetPersonaToolkit()->GetPreviewScene().Get(), &IPersonaPreviewScene::TogglePlayback));
}
void FAnimationEditor::ExtendToolbar()
{
// If the ToolbarExtender is valid, remove it before rebuilding it
if (ToolbarExtender.IsValid())
{
RemoveToolbarExtender(ToolbarExtender);
ToolbarExtender.Reset();
}
ToolbarExtender = MakeShareable(new FExtender);
AddToolbarExtender(ToolbarExtender);
IAnimationEditorModule& AnimationEditorModule = FModuleManager::GetModuleChecked<IAnimationEditorModule>("AnimationEditor");
AddToolbarExtender(AnimationEditorModule.GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
TArray<IAnimationEditorModule::FAnimationEditorToolbarExtender> ToolbarExtenderDelegates = AnimationEditorModule.GetAllAnimationEditorToolbarExtenders();
for (auto& ToolbarExtenderDelegate : ToolbarExtenderDelegates)
{
if (ToolbarExtenderDelegate.IsBound())
{
AddToolbarExtender(ToolbarExtenderDelegate.Execute(GetToolkitCommands(), SharedThis(this)));
}
}
// extend extra menu/toolbars
ToolbarExtender->AddToolBarExtension(
"Asset",
EExtensionHook::After,
GetToolkitCommands(),
FToolBarExtensionDelegate::CreateLambda([this](FToolBarBuilder& ToolbarBuilder)
{
FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked<FPersonaModule>("Persona");
FPersonaModule::FCommonToolbarExtensionArgs Args;
Args.bPreviewAnimation = false;
Args.bReferencePose = false;
PersonaModule.AddCommonToolbarExtensions(ToolbarBuilder, PersonaToolkit.ToSharedRef(), Args);
ToolbarBuilder.BeginSection("Animation");
{
// create button
{
ToolbarBuilder.AddComboButton(
FUIAction(),
FOnGetContent::CreateSP(this, &FAnimationEditor::GenerateCreateAssetMenu),
LOCTEXT("CreateAsset_Label", "Create Asset"),
LOCTEXT("CreateAsset_ToolTip", "Create Assets for this skeleton."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.CreateAsset")
);
}
ToolbarBuilder.AddToolBarButton(FAnimationEditorCommands::Get().ReimportAnimation);
ToolbarBuilder.AddToolBarButton(FAnimationEditorCommands::Get().ApplyCompression, NAME_None, LOCTEXT("Toolbar_ApplyCompression", "Compression"));
{
ToolbarBuilder.AddComboButton(
FUIAction(),
FOnGetContent::CreateSP(this, &FAnimationEditor::GenerateExportAssetMenu),
LOCTEXT("ExportAsset_Label", "Export Asset"),
LOCTEXT("ExportAsset_ToolTip", "Export Assets for this skeleton."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.ExportToFBX")
);
}
}
ToolbarBuilder.EndSection();
ToolbarBuilder.BeginSection("Editing");
{
ToolbarBuilder.AddToolBarButton(FAnimationEditorCommands::Get().SetKey, NAME_None, LOCTEXT("Toolbar_SetKey", "Key"));
ToolbarBuilder.AddToolBarButton(FAnimationEditorCommands::Get().ApplyAnimation, NAME_None, LOCTEXT("Toolbar_ApplyAnimation", "Apply"));
}
ToolbarBuilder.EndSection();
TSharedRef<class IAssetFamily> AssetFamily = PersonaModule.CreatePersonaAssetFamily(AnimationAsset);
AddToolbarWidget(PersonaModule.CreateAssetFamilyShortcutWidget(SharedThis(this), AssetFamily));
}
));
}
void FAnimationEditor::ExtendMenu()
{
MenuExtender = MakeShareable(new FExtender);
struct Local
{
static void AddAssetMenu(FMenuBuilder& MenuBuilder, FAnimationEditor* InAnimationEditor)
{
MenuBuilder.BeginSection("AnimationEditor", LOCTEXT("AnimationEditorAssetMenu_Animation", "Animation"));
{
MenuBuilder.AddMenuEntry(FAnimationEditorCommands::Get().ApplyCompression);
MenuBuilder.AddSubMenu(
LOCTEXT("ExportToFBX", "Export to FBX"),
LOCTEXT("ExportToFBX_ToolTip", "Export current animation to FBX"),
FNewMenuDelegate::CreateSP(InAnimationEditor, &FAnimationEditor::FillExportAssetMenu),
false,
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.")
);
MenuBuilder.AddMenuEntry(FAnimationEditorCommands::Get().AddLoopingInterpolation);
MenuBuilder.AddMenuEntry(FAnimationEditorCommands::Get().RemoveBoneTracks);
MenuBuilder.AddSubMenu(
LOCTEXT("CopyCurvesToSoundWave", "Copy Curves To SoundWave"),
LOCTEXT("CopyCurvesToSoundWave_ToolTip", "Copy curves from this animation to the selected SoundWave"),
FNewMenuDelegate::CreateSP(InAnimationEditor, &FAnimationEditor::FillCopyToSoundWaveMenu),
false,
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.")
);
}
MenuBuilder.EndSection();
}
};
MenuExtender->AddMenuExtension(
"AssetEditorActions",
EExtensionHook::After,
GetToolkitCommands(),
FMenuExtensionDelegate::CreateStatic(&Local::AddAssetMenu, this)
);
AddMenuExtender(MenuExtender);
IAnimationEditorModule& AnimationEditorModule = FModuleManager::GetModuleChecked<IAnimationEditorModule>("AnimationEditor");
AddMenuExtender(AnimationEditorModule.GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
}
void FAnimationEditor::HandleObjectsSelected(const TArray<UObject*>& InObjects)
{
if (DetailsView.IsValid())
{
DetailsView->SetObjects(InObjects);
}
}
void FAnimationEditor::HandleSelectionChanged(const TArrayView<TSharedPtr<ISkeletonTreeItem>>& InSelectedItems, ESelectInfo::Type InSelectInfo)
{
if (DetailsView.IsValid())
{
TArray<UObject*> Objects;
Algo::TransformIf(InSelectedItems, Objects, [](const TSharedPtr<ISkeletonTreeItem>& InItem) { return InItem->GetObject() != nullptr; }, [](const TSharedPtr<ISkeletonTreeItem>& InItem) { return InItem->GetObject(); });
DetailsView->SetObjects(Objects);
}
}
void FAnimationEditor::HandleObjectSelected(UObject* InObject)
{
if (DetailsView.IsValid())
{
DetailsView->SetObject(InObject);
}
}
void FAnimationEditor::PostUndo(bool bSuccess)
{
OnPostUndo.Broadcast();
}
void FAnimationEditor::PostRedo(bool bSuccess)
{
OnPostUndo.Broadcast();
}
void FAnimationEditor::HandleDetailsCreated(const TSharedRef<IDetailsView>& InDetailsView)
{
DetailsView = InDetailsView;
}
TSharedPtr<SDockTab> FAnimationEditor::OpenNewAnimationDocumentTab(UAnimationAsset* InAnimAsset)
{
TSharedPtr<SDockTab> OpenedTab;
if (InAnimAsset != nullptr)
{
FString DocumentLink;
FAnimDocumentArgs Args(PersonaToolkit->GetPreviewScene(), GetPersonaToolkit(), GetSkeletonTree()->GetEditableSkeleton(), OnPostUndo, OnSectionsChanged);
Args.OnDespatchObjectsSelected = FOnObjectsSelected::CreateSP(this, &FAnimationEditor::HandleObjectsSelected);
Args.OnDespatchInvokeTab = FOnInvokeTab::CreateSP(this, &FAssetEditorToolkit::InvokeTab);
Args.OnDespatchSectionsChanged = FSimpleDelegate::CreateSP(this, &FAnimationEditor::HandleSectionsChanged);
FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>("Persona");
TSharedRef<SWidget> TabContents = PersonaModule.CreateEditorWidgetForAnimDocument(SharedThis(this), InAnimAsset, Args, DocumentLink);
if (AnimationAsset)
{
RemoveEditingObject(AnimationAsset);
}
AddEditingObject(InAnimAsset);
AnimationAsset = InAnimAsset;
GetPersonaToolkit()->GetPreviewScene()->SetPreviewAnimationAsset(InAnimAsset);
GetPersonaToolkit()->SetAnimationAsset(InAnimAsset);
struct Local
{
static FText GetObjectName(UObject* Object)
{
return FText::FromString(Object->GetName());
}
};
TAttribute<FText> NameAttribute = TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateStatic(&Local::GetObjectName, (UObject*)InAnimAsset));
if (SharedAnimDocumentTab.IsValid())
{
OpenedTab = SharedAnimDocumentTab.Pin();
OpenedTab->SetContent(TabContents);
OpenedTab->ActivateInParent(ETabActivationCause::SetDirectly);
OpenedTab->SetLabel(NameAttribute);
OpenedTab->SetLeftContent(IDocumentation::Get()->CreateAnchor(DocumentLink));
}
else
{
OpenedTab = SNew(SDockTab)
.Label(NameAttribute)
.TabRole(ETabRole::DocumentTab)
.TabColorScale(GetTabColorScale())
[
TabContents
];
OpenedTab->SetLeftContent(IDocumentation::Get()->CreateAnchor(DocumentLink));
TabManager->InsertNewDocumentTab(AnimationEditorTabs::DocumentTab, FTabManager::ESearchPreference::RequireClosedTab, OpenedTab.ToSharedRef());
SharedAnimDocumentTab = OpenedTab;
}
if (SequenceBrowser.IsValid())
{
SequenceBrowser.Pin()->SelectAsset(InAnimAsset);
}
// let the asset family know too
TSharedRef<IAssetFamily> AssetFamily = PersonaModule.CreatePersonaAssetFamily(InAnimAsset);
AssetFamily->RecordAssetOpened(FAssetData(InAnimAsset));
}
return OpenedTab;
}
void FAnimationEditor::HandleSectionsChanged()
{
OnSectionsChanged.Broadcast();
}
void FAnimationEditor::SetAnimationAsset(UAnimationAsset* AnimAsset)
{
HandleOpenNewAsset(AnimAsset);
}
void FAnimationEditor::HandleOpenNewAsset(UObject* InNewAsset)
{
if (UAnimationAsset* NewAnimationAsset = Cast<UAnimationAsset>(InNewAsset))
{
OpenNewAnimationDocumentTab(NewAnimationAsset);
}
}
UObject* FAnimationEditor::HandleGetAsset()
{
return GetEditingObject();
}
bool FAnimationEditor::HasValidAnimationSequence() const
{
UAnimSequence* AnimSequence = Cast<UAnimSequence>(AnimationAsset);
return (AnimSequence != nullptr);
}
bool FAnimationEditor::CanSetKey() const
{
UDebugSkelMeshComponent* PreviewMeshComponent = PersonaToolkit->GetPreviewMeshComponent();
return (HasValidAnimationSequence() && PreviewMeshComponent->BonesOfInterest.Num() > 0);
}
void FAnimationEditor::OnSetKey()
{
if (AnimationAsset)
{
UDebugSkelMeshComponent* Component = PersonaToolkit->GetPreviewMeshComponent();
Component->PreviewInstance->SetKey();
}
}
bool FAnimationEditor::CanApplyRawAnimChanges() const
{
UAnimSequence* AnimSequence = Cast<UAnimSequence>(AnimationAsset);
// ideally would be great if we can only show if something changed
return (AnimSequence && (AnimSequence->DoesNeedRebake() || AnimSequence->DoesNeedRecompress()));
}
void FAnimationEditor::OnApplyRawAnimChanges()
{
UAnimSequence* AnimSequence = Cast<UAnimSequence>(AnimationAsset);
if (AnimSequence)
{
if (AnimSequence->DoesNeedRebake() || AnimSequence->DoesNeedRecompress())
{
FScopedTransaction ScopedTransaction(LOCTEXT("BakeAnimation", "Bake Animation"));
if (AnimSequence->DoesNeedRebake())
{
AnimSequence->Modify(true);
AnimSequence->BakeTrackCurvesToRawAnimation();
}
if (AnimSequence->DoesNeedRecompress())
{
AnimSequence->Modify(true);
AnimSequence->RequestSyncAnimRecompression(false);
}
}
}
}
void FAnimationEditor::OnReimportAnimation()
{
UAnimSequence* AnimSequence = Cast<UAnimSequence>(AnimationAsset);
if (AnimSequence)
{
FReimportManager::Instance()->Reimport(AnimSequence, true);
}
}
void FAnimationEditor::OnApplyCompression()
{
UAnimSequence* AnimSequence = Cast<UAnimSequence>(AnimationAsset);
if (AnimSequence)
{
TArray<TWeakObjectPtr<UAnimSequence>> AnimSequences;
AnimSequences.Add(AnimSequence);
FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>("Persona");
PersonaModule.ApplyCompression(AnimSequences);
}
}
void FAnimationEditor::OnExportToFBX(const EPoseSourceOption Option)
{
UAnimSequence* AnimSequenceToRecord = nullptr;
if (Option == EPoseSourceOption::CurrentAnimation_AnimData)
{
TArray<UObject*> AssetsToExport;
AssetsToExport.Add(AnimationAsset);
ExportToFBX(AssetsToExport, false);
}
else if (Option == EPoseSourceOption::CurrentAnimation_PreviewMesh)
{
TArray<TWeakObjectPtr<UObject>> Skeletons;
Skeletons.Add(PersonaToolkit->GetSkeleton());
AnimationEditorUtils::CreateAnimationAssets(Skeletons, UAnimSequence::StaticClass(), FString("_PreviewMesh"), FAnimAssetCreated::CreateSP(this, &FAnimationEditor::ExportToFBX, true), AnimationAsset, true);
}
else
{
ensure(false);
}
}
bool FAnimationEditor::ExportToFBX(const TArray<UObject*> AssetsToExport, bool bRecordAnimation)
{
bool AnimSequenceExportResult = false;
TArray<TWeakObjectPtr<UAnimSequence>> AnimSequences;
if (AssetsToExport.Num() > 0)
{
UAnimSequence* AnimationToRecord = Cast<UAnimSequence>(AssetsToExport[0]);
if (AnimationToRecord)
{
if (bRecordAnimation)
{
USkeletalMeshComponent* MeshComponent = PersonaToolkit->GetPreviewMeshComponent();
RecordMeshToAnimation(MeshComponent, AnimationToRecord);
}
AnimSequences.Add(AnimationToRecord);
}
}
if (AnimSequences.Num() > 0)
{
FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>("Persona");
AnimSequenceExportResult = PersonaModule.ExportToFBX(AnimSequences, GetPersonaToolkit()->GetPreviewScene()->GetPreviewMeshComponent()->SkeletalMesh);
}
return AnimSequenceExportResult;
}
void FAnimationEditor::OnAddLoopingInterpolation()
{
UAnimSequence* AnimSequence = Cast<UAnimSequence>(AnimationAsset);
if (AnimSequence)
{
TArray<TWeakObjectPtr<UAnimSequence>> AnimSequences;
AnimSequences.Add(AnimSequence);
FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>("Persona");
PersonaModule.AddLoopingInterpolation(AnimSequences);
}
}
void FAnimationEditor::OnRemoveBoneTrack()
{
if ( FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("WarningOnRemovingBoneTracks", "This will clear all bone transform of the animation, source data, and edited layer information. This doesn't remove notifies, and curves. Do you want to continue?")) == EAppReturnType::Yes)
{
FScopedTransaction ScopedTransaction(LOCTEXT("RemoveAnimation", "Remove Track"));
UAnimSequence* AnimSequence = Cast<UAnimSequence>(AnimationAsset);
if (AnimSequence)
{
AnimSequence->Modify();
AnimSequence->RemoveAllTracks();
}
}
}
TSharedRef< SWidget > FAnimationEditor::GenerateExportAssetMenu() const
{
const bool bShouldCloseWindowAfterMenuSelection = true;
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, GetToolkitCommands());
FillExportAssetMenu(MenuBuilder);
return MenuBuilder.MakeWidget();
}
TSharedRef< SWidget > FAnimationEditor::GenerateCreateAssetMenu() const
{
const bool bShouldCloseWindowAfterMenuSelection = true;
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, NULL);
// Create Animation menu
MenuBuilder.BeginSection("CreateAnimation", LOCTEXT("CreateAnimationMenuHeading", "Animation"));
{
// create menu
MenuBuilder.AddSubMenu(
LOCTEXT("CreateAnimationSubmenu", "Create Animation"),
LOCTEXT("CreateAnimationSubmenu_ToolTip", "Create Animation for this skeleton"),
FNewMenuDelegate::CreateSP(this, &FAnimationEditor::FillCreateAnimationMenu),
false,
FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.AssetActions.CreateAnimAsset")
);
MenuBuilder.AddSubMenu(
LOCTEXT("CreatePoseAssetSubmenu", "Create PoseAsset"),
LOCTEXT("CreatePoseAsssetSubmenu_ToolTip", "Create PoseAsset for this skeleton"),
FNewMenuDelegate::CreateSP(this, &FAnimationEditor::FillCreatePoseAssetMenu),
false,
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.PoseAsset")
);
}
MenuBuilder.EndSection();
TArray<TWeakObjectPtr<UObject>> Objects;
if (PersonaToolkit->GetPreviewMesh())
{
Objects.Add(PersonaToolkit->GetPreviewMesh());
}
else
{
Objects.Add(PersonaToolkit->GetSkeleton());
}
AnimationEditorUtils::FillCreateAssetMenu(MenuBuilder, Objects, FAnimAssetCreated::CreateSP(this, &FAnimationEditor::HandleAssetCreated), false);
return MenuBuilder.MakeWidget();
}
void FAnimationEditor::FillCreateAnimationMenu(FMenuBuilder& MenuBuilder) const
{
TArray<TWeakObjectPtr<UObject>> Objects;
if (PersonaToolkit->GetPreviewMesh())
{
Objects.Add(PersonaToolkit->GetPreviewMesh());
}
else
{
Objects.Add(PersonaToolkit->GetSkeleton());
}
// create rig
MenuBuilder.BeginSection("CreateAnimationSubMenu", LOCTEXT("CreateAnimationSubMenuHeading", "Create Animation"));
{
MenuBuilder.AddMenuEntry(
LOCTEXT("CreateAnimation_RefPose", "Reference Pose"),
LOCTEXT("CreateAnimation_RefPose_Tooltip", "Create Animation from reference pose."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset<UAnimSequenceFactory, UAnimSequence>, Objects, FString("_Sequence"), FAnimAssetCreated::CreateSP(this, &FAnimationEditor::CreateAnimation, EPoseSourceOption::ReferencePose), false),
FCanExecuteAction()
)
);
MenuBuilder.AddMenuEntry(
LOCTEXT("CreateAnimation_CurrentPose", "Current Pose"),
LOCTEXT("CreateAnimation_CurrentPose_Tooltip", "Create Animation from current pose."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset<UAnimSequenceFactory, UAnimSequence>, Objects, FString("_Sequence"), FAnimAssetCreated::CreateSP(this, &FAnimationEditor::CreateAnimation, EPoseSourceOption::CurrentPose), false),
FCanExecuteAction()
)
);
MenuBuilder.AddSubMenu(
LOCTEXT("CreateAnimation_CurrenAnimationSubMenu", "Current Animation"),
LOCTEXT("CreateAnimation_CurrenAnimationSubMenu_ToolTip", "Create Animation from current animation"),
FNewMenuDelegate::CreateSP(this, &FAnimationEditor::FillCreateAnimationFromCurrentAnimationMenu),
false,
FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.AssetActions.CreateAnimAsset")
);
}
MenuBuilder.EndSection();
}
void FAnimationEditor::FillCreateAnimationFromCurrentAnimationMenu(FMenuBuilder& MenuBuilder) const
{
{
TArray<TWeakObjectPtr<UObject>> Objects;
if (PersonaToolkit->GetPreviewMesh())
{
Objects.Add(PersonaToolkit->GetPreviewMesh());
}
else
{
Objects.Add(PersonaToolkit->GetSkeleton());
}
// create rig
MenuBuilder.BeginSection("CreateAnimationSubMenu", LOCTEXT("CreateAnimationFromCurrentAnimationSubmenuHeading", "Create Animation"));
{
MenuBuilder.AddMenuEntry(
LOCTEXT("CreateAnimation_CurrentAnimation_AnimData", "Animation Data"),
LOCTEXT("CreateAnimation_CurrentAnimation_AnimData_Tooltip", "Create Animation from Animation Source Data."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset<UAnimSequenceFactory, UAnimSequence>, Objects, FString("_Sequence"), FAnimAssetCreated::CreateSP(this, &FAnimationEditor::CreateAnimation, EPoseSourceOption::CurrentAnimation_AnimData), false),
FCanExecuteAction::CreateSP(this, &FAnimationEditor::HasValidAnimationSequence)
)
);
MenuBuilder.AddMenuEntry(
LOCTEXT("CreateAnimation_CurrentAnimation_PreviewMesh", "Preview Mesh"),
LOCTEXT("CreateAnimation_CurrentAnimation_PreviewMesh_Tooltip", "Create Animation by playing on the Current Preview Mesh, including Retargeting, Post Process Graph, or anything you see on the preview mesh."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset<UAnimSequenceFactory, UAnimSequence>, Objects, FString("_Sequence"), FAnimAssetCreated::CreateSP(this, &FAnimationEditor::CreateAnimation, EPoseSourceOption::CurrentAnimation_PreviewMesh), false),
FCanExecuteAction::CreateSP(this, &FAnimationEditor::HasValidAnimationSequence)
)
);
}
MenuBuilder.EndSection();
}
}
void FAnimationEditor::FillCreatePoseAssetMenu(FMenuBuilder& MenuBuilder) const
{
TArray<TWeakObjectPtr<UObject>> Objects;
if (PersonaToolkit->GetPreviewMesh())
{
Objects.Add(PersonaToolkit->GetPreviewMesh());
}
else
{
Objects.Add(PersonaToolkit->GetSkeleton());
}
// create rig
MenuBuilder.BeginSection("CreatePoseAssetSubMenu", LOCTEXT("CreatePoseAssetSubMenuHeading", "Create PoseAsset"));
{
MenuBuilder.AddMenuEntry(
LOCTEXT("CreatePoseAsset_CurrentPose", "Current Pose"),
LOCTEXT("CreatePoseAsset_CurrentPose_Tooltip", "Create PoseAsset from current pose."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset<UPoseAssetFactory, UPoseAsset>, Objects, FString("_PoseAsset"), FAnimAssetCreated::CreateSP(this, &FAnimationEditor::CreatePoseAsset, EPoseSourceOption::CurrentPose), false),
FCanExecuteAction()
)
);
MenuBuilder.AddMenuEntry(
LOCTEXT("CreatePoseAsset_CurrentAnimation", "Current Animation"),
LOCTEXT("CreatePoseAsset_CurrentAnimation_Tooltip", "Create Animation from current animation."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset<UPoseAssetFactory, UPoseAsset>, Objects, FString("_PoseAsset"), FAnimAssetCreated::CreateSP(this, &FAnimationEditor::CreatePoseAsset, EPoseSourceOption::CurrentAnimation_AnimData), false),
FCanExecuteAction()
)
);
}
MenuBuilder.EndSection();
// create pose asset
MenuBuilder.BeginSection("InsertPoseSubMenuSection", LOCTEXT("InsertPoseSubMenuSubMenuHeading", "Insert Pose"));
{
MenuBuilder.AddSubMenu(
LOCTEXT("InsertPoseSubmenu", "Insert Pose"),
LOCTEXT("InsertPoseSubmenu_ToolTip", "Insert current pose to selected PoseAsset"),
FNewMenuDelegate::CreateSP(this, &FAnimationEditor::FillInsertPoseMenu),
false,
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.PoseAsset")
);
}
MenuBuilder.EndSection();
}
void FAnimationEditor::FillInsertPoseMenu(FMenuBuilder& MenuBuilder) const
{
FAssetPickerConfig AssetPickerConfig;
USkeleton* Skeleton = GetPersonaToolkit()->GetSkeleton();
/** The asset picker will only show skeletons */
AssetPickerConfig.Filter.ClassNames.Add(*UPoseAsset::StaticClass()->GetName());
AssetPickerConfig.Filter.bRecursiveClasses = false;
AssetPickerConfig.bAllowNullSelection = false;
AssetPickerConfig.Filter.TagsAndValues.Add(TEXT("Skeleton"), FAssetData(Skeleton).GetExportTextName());
/** The delegate that fires when an asset was selected */
AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateRaw(this, &FAnimationEditor::InsertCurrentPoseToAsset);
/** The default view mode should be a list view */
AssetPickerConfig.InitialAssetViewType = EAssetViewType::List;
FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
MenuBuilder.AddWidget(
ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig),
FText::GetEmpty()
);
}
void FAnimationEditor::InsertCurrentPoseToAsset(const FAssetData& NewPoseAssetData)
{
UPoseAsset* PoseAsset = Cast<UPoseAsset>(NewPoseAssetData.GetAsset());
FScopedTransaction ScopedTransaction(LOCTEXT("InsertPose", "Insert Pose"));
if (PoseAsset)
{
PoseAsset->Modify();
UDebugSkelMeshComponent* PreviewMeshComponent = PersonaToolkit->GetPreviewMeshComponent();
if (PreviewMeshComponent)
{
FSmartName NewPoseName;
bool bSuccess = PoseAsset->AddOrUpdatePoseWithUniqueName(PreviewMeshComponent, &NewPoseName);
if (bSuccess)
{
FFormatNamedArguments Args;
Args.Add(TEXT("PoseAsset"), FText::FromString(PoseAsset->GetName()));
Args.Add(TEXT("PoseName"), FText::FromName(NewPoseName.DisplayName));
FNotificationInfo Info(FText::Format(LOCTEXT("InsertPoseSucceeded", "The current pose has inserted to {PoseAsset} with {PoseName}"), Args));
Info.ExpireDuration = 7.0f;
Info.bUseLargeFont = false;
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
if (Notification.IsValid())
{
Notification->SetCompletionState(SNotificationItem::CS_Success);
}
}
else
{
FFormatNamedArguments Args;
Args.Add(TEXT("PoseAsset"), FText::FromString(PoseAsset->GetName()));
FNotificationInfo Info(FText::Format(LOCTEXT("InsertPoseFailed", "Inserting pose to asset {PoseAsset} has failed"), Args));
Info.ExpireDuration = 7.0f;
Info.bUseLargeFont = false;
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
if (Notification.IsValid())
{
Notification->SetCompletionState(SNotificationItem::CS_Fail);
}
}
}
}
// it doesn't work well if I leave the window open. The delegate goes weired or it stop showing the popups.
FSlateApplication::Get().DismissAllMenus();
}
void FAnimationEditor::FillCopyToSoundWaveMenu(FMenuBuilder& MenuBuilder) const
{
FAssetPickerConfig AssetPickerConfig;
AssetPickerConfig.Filter.ClassNames.Add(*USoundWave::StaticClass()->GetName());
AssetPickerConfig.bAllowNullSelection = false;
AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateRaw(this, &FAnimationEditor::CopyCurveToSoundWave);
AssetPickerConfig.InitialAssetViewType = EAssetViewType::List;
FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
MenuBuilder.AddWidget(
SNew(SBox)
.WidthOverride(300.0f)
.HeightOverride(300.0f)
[
ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig)
],
FText::GetEmpty()
);
}
void FAnimationEditor::FillExportAssetMenu(FMenuBuilder& MenuBuilder) const
{
MenuBuilder.BeginSection("AnimationExport", LOCTEXT("ExportAssetMenuHeading", "Export"));
{
MenuBuilder.AddMenuEntry(FAnimationEditorCommands::Get().ExportToFBX_AnimData);
MenuBuilder.AddMenuEntry(FAnimationEditorCommands::Get().ExportToFBX_PreviewMesh);
}
MenuBuilder.EndSection();
}
FRichCurve* FindOrAddCurve(UCurveTable* CurveTable, FName CurveName)
{
FRichCurve* Curve = nullptr;
// Grab existing curve (if present)
FRichCurve** ExistingCurvePtr = CurveTable->RowMap.Find(CurveName);
if (ExistingCurvePtr)
{
check(*ExistingCurvePtr);
Curve = *ExistingCurvePtr;
}
// Or allocate new curve
else
{
Curve = new FRichCurve();
CurveTable->RowMap.Add(CurveName, Curve);
}
return Curve;
}
void FAnimationEditor::CopyCurveToSoundWave(const FAssetData& SoundWaveAssetData) const
{
USoundWave* SoundWave = Cast<USoundWave>(SoundWaveAssetData.GetAsset());
UAnimSequence* Sequence = Cast<UAnimSequence>(AnimationAsset);
if (!SoundWave || !Sequence)
{
return;
}
// If no internal table, create one now
if (!SoundWave->GetInternalCurveData())
{
static const FName InternalCurveTableName("InternalCurveTable");
UCurveTable* NewCurves = NewObject<UCurveTable>(SoundWave, InternalCurveTableName);
NewCurves->ClearFlags(RF_Public);
NewCurves->SetFlags(NewCurves->GetFlags() | RF_Standalone | RF_Transactional);
SoundWave->SetCurveData(NewCurves);
SoundWave->SetInternalCurveData(NewCurves);
}
UCurveTable* CurveTable = SoundWave->GetInternalCurveData();
// iterate over curves in anim data
const int32 NumCurves = Sequence->RawCurveData.FloatCurves.Num();
for (int32 CurveIdx = 0; CurveIdx < NumCurves; CurveIdx++)
{
FFloatCurve& AnimCurve = Sequence->RawCurveData.FloatCurves[CurveIdx];
FRichCurve* Curve = FindOrAddCurve(CurveTable, AnimCurve.Name.DisplayName);
*Curve = AnimCurve.FloatCurve; // copy data
}
// we will need to add a curve to tell us the time we want to start playing audio
float PreRollTime = 0.f;
static const FName AudioCurveName("Audio");
FRichCurve* AudioCurve = FindOrAddCurve(CurveTable, AudioCurveName);
AudioCurve->Reset();
AudioCurve->AddKey(PreRollTime, 1.0f);
// Mark dirty after
SoundWave->MarkPackageDirty();
FNotificationInfo Notification(FText::Format(LOCTEXT("AddedClassSuccessNotification", "Copied curves to {0}"), FText::FromString(SoundWave->GetName())));
FSlateNotificationManager::Get().AddNotification(Notification);
// Close menu after picking sound
FSlateApplication::Get().DismissAllMenus();
}
bool FAnimationEditor::CreateAnimation(const TArray<UObject*> NewAssets, const EPoseSourceOption Option)
{
bool bResult = true;
if (NewAssets.Num() > 0)
{
USkeletalMeshComponent* MeshComponent = PersonaToolkit->GetPreviewMeshComponent();
UAnimSequence* Sequence = Cast<UAnimSequence>(AnimationAsset);
for (auto NewAsset : NewAssets)
{
UAnimSequence* NewAnimSequence = Cast<UAnimSequence>(NewAsset);
if (NewAnimSequence)
{
switch (Option)
{
case EPoseSourceOption::ReferencePose:
bResult &= NewAnimSequence->CreateAnimation(MeshComponent->SkeletalMesh);
break;
case EPoseSourceOption::CurrentPose:
bResult &= NewAnimSequence->CreateAnimation(MeshComponent);
break;
case EPoseSourceOption::CurrentAnimation_AnimData:
bResult &= NewAnimSequence->CreateAnimation(Sequence);
break;
case EPoseSourceOption::CurrentAnimation_PreviewMesh:
bResult &= RecordMeshToAnimation(MeshComponent, NewAnimSequence);
break;
default:
ensure(false);
break;
}
}
}
if (bResult)
{
HandleAssetCreated(NewAssets);
// if it created based on current mesh component,
if (Option == EPoseSourceOption::CurrentPose)
{
UDebugSkelMeshComponent* PreviewMeshComponent = PersonaToolkit->GetPreviewMeshComponent();
if (PreviewMeshComponent && PreviewMeshComponent->PreviewInstance)
{
PreviewMeshComponent->PreviewInstance->ResetModifiedBone();
}
}
}
}
return true;
}
bool FAnimationEditor::CreatePoseAsset(const TArray<UObject*> NewAssets, const EPoseSourceOption Option)
{
bool bResult = false;
if (NewAssets.Num() > 0)
{
UDebugSkelMeshComponent* PreviewComponent = PersonaToolkit->GetPreviewMeshComponent();
UAnimSequence* Sequence = Cast<UAnimSequence>(AnimationAsset);
for (auto NewAsset : NewAssets)
{
UPoseAsset* NewPoseAsset = Cast<UPoseAsset>(NewAsset);
if (NewPoseAsset)
{
switch (Option)
{
case EPoseSourceOption::CurrentPose:
NewPoseAsset->AddOrUpdatePoseWithUniqueName(PreviewComponent);
bResult = true;
break;
case EPoseSourceOption::CurrentAnimation_AnimData:
NewPoseAsset->CreatePoseFromAnimation(Sequence);
bResult = true;
break;
default:
ensure(false);
bResult = false;
break;
}
}
}
// if it contains error, warn them
if (bResult)
{
HandleAssetCreated(NewAssets);
// if it created based on current mesh component,
if (Option == EPoseSourceOption::CurrentPose)
{
PreviewComponent->PreviewInstance->ResetModifiedBone();
}
}
else
{
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("FailedToCreateAsset", "Failed to create asset"));
}
}
return true;
}
bool FAnimationEditor::HandleAssetCreated(const TArray<UObject*> NewAssets)
{
if (NewAssets.Num() > 0)
{
FAssetRegistryModule::AssetCreated(NewAssets[0]);
if (UAnimationAsset* NewAnimAsset = Cast<UAnimationAsset>(NewAssets[0]))
{
OpenNewAnimationDocumentTab(NewAnimAsset);
}
else
{
// if not, we forward to asset manager to open the asset for us
// this is path for animation blueprint
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
TWeakPtr<IAssetTypeActions> AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(NewAssets[0]->GetClass());
if (AssetTypeActions.IsValid())
{
AssetTypeActions.Pin()->OpenAssetEditor(NewAssets);
}
}
}
return true;
}
void FAnimationEditor::ConditionalRefreshEditor(UObject* InObject)
{
bool bInterestingAsset = true;
if (InObject != GetPersonaToolkit()->GetSkeleton() && InObject != GetPersonaToolkit()->GetSkeleton()->GetPreviewMesh() && Cast<UAnimationAsset>(InObject) != AnimationAsset)
{
bInterestingAsset = false;
}
// Check that we aren't a montage that uses an incoming animation
if (UAnimMontage* Montage = Cast<UAnimMontage>(AnimationAsset))
{
for (FSlotAnimationTrack& Slot : Montage->SlotAnimTracks)
{
if (bInterestingAsset)
{
break;
}
for (FAnimSegment& Segment : Slot.AnimTrack.AnimSegments)
{
if (Segment.AnimReference == InObject)
{
bInterestingAsset = true;
break;
}
}
}
}
if (bInterestingAsset)
{
GetPersonaToolkit()->GetPreviewScene()->InvalidateViews();
OpenNewAnimationDocumentTab(Cast<UAnimationAsset>(InObject));
}
}
void FAnimationEditor::HandlePostReimport(UObject* InObject, bool bSuccess)
{
if (bSuccess)
{
ConditionalRefreshEditor(InObject);
}
}
void FAnimationEditor::HandlePostImport(UFactory* InFactory, UObject* InObject)
{
ConditionalRefreshEditor(InObject);
}
void FAnimationEditor::HandleAnimationSequenceBrowserCreated(const TSharedRef<IAnimationSequenceBrowser>& InSequenceBrowser)
{
SequenceBrowser = InSequenceBrowser;
}
bool FAnimationEditor::RecordMeshToAnimation(USkeletalMeshComponent* PreviewComponent, UAnimSequence* NewAsset) const
{
return SequenceRecorderUtils::RecordSingleNodeInstanceToAnimation(PreviewComponent, NewAsset);
}
#undef LOCTEXT_NAMESPACE