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]
884 lines
31 KiB
C++
884 lines
31 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "AnimationEditor.h"
|
|
|
|
#include "Algo/Transform.h"
|
|
#include "AnimPreviewInstance.h"
|
|
#include "Animation/AnimCompositeBase.h"
|
|
#include "Animation/AnimCurveTypes.h"
|
|
#include "Animation/AnimData/AnimDataModel.h"
|
|
#include "Animation/AnimData/IAnimationDataController.h"
|
|
#include "Animation/AnimMontage.h"
|
|
#include "Animation/AnimSequence.h"
|
|
#include "Animation/AnimSequenceBase.h"
|
|
#include "Animation/AnimationAsset.h"
|
|
#include "Animation/DebugSkelMeshComponent.h"
|
|
#include "Animation/Skeleton.h"
|
|
#include "Animation/SmartName.h"
|
|
#include "AnimationEditorCommands.h"
|
|
#include "AnimationEditorMode.h"
|
|
#include "AnimationEditorUtils.h"
|
|
#include "AnimationToolMenuContext.h"
|
|
#include "DetailLayoutBuilder.h"
|
|
#include "AssetRegistry/AssetData.h"
|
|
#include "Curves/RichCurve.h"
|
|
#include "Editor.h"
|
|
#include "Editor/EditorEngine.h"
|
|
#include "EditorReimportHandler.h"
|
|
#include "Engine/CurveTable.h"
|
|
#include "Engine/SkeletalMesh.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Framework/Commands/UIAction.h"
|
|
#include "Framework/Commands/UICommandList.h"
|
|
#include "Framework/Docking/TabManager.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "Framework/MultiBox/MultiBoxExtender.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Framework/SlateDelegates.h"
|
|
#include "HAL/PlatformCrt.h"
|
|
#include "HAL/PlatformMisc.h"
|
|
#include "IAnimSequenceCurveEditor.h"
|
|
#include "IAnimationEditorModule.h"
|
|
#include "IAnimationSequenceBrowser.h"
|
|
#include "IAssetFamily.h"
|
|
#include "IDetailsView.h"
|
|
#include "IDocumentation.h"
|
|
#include "IPersonaPreviewScene.h"
|
|
#include "IPersonaToolkit.h"
|
|
#include "ISequenceRecorder.h"
|
|
#include "ISkeletonEditorModule.h"
|
|
#include "ISkeletonTree.h"
|
|
#include "ISkeletonTreeItem.h"
|
|
#include "Internationalization/Internationalization.h"
|
|
#include "Logging/LogMacros.h"
|
|
#include "Misc/AssertionMacros.h"
|
|
#include "Misc/Attribute.h"
|
|
#include "Misc/MessageDialog.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "PersonaCommonCommands.h"
|
|
#include "PersonaDelegates.h"
|
|
#include "PersonaModule.h"
|
|
#include "PersonaToolMenuContext.h"
|
|
#include "Sound/SoundWave.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Subsystems/ImportSubsystem.h"
|
|
#include "Templates/Casts.h"
|
|
#include "Textures/SlateIcon.h"
|
|
#include "ToolMenu.h"
|
|
#include "ToolMenuContext.h"
|
|
#include "ToolMenuDelegates.h"
|
|
#include "ToolMenuEntry.h"
|
|
#include "ToolMenuMisc.h"
|
|
#include "ToolMenuOwner.h"
|
|
#include "ToolMenuSection.h"
|
|
#include "ToolMenus.h"
|
|
#include "Toolkits/AssetEditorToolkit.h"
|
|
#include "UObject/Object.h"
|
|
#include "UObject/ObjectMacros.h"
|
|
#include "UObject/ObjectPtr.h"
|
|
#include "UObject/UObjectGlobals.h"
|
|
#include "UObject/WeakObjectPtr.h"
|
|
#include "UObject/WeakObjectPtrTemplates.h"
|
|
#include "Widgets/DeclarativeSyntaxSupport.h"
|
|
#include "Widgets/Docking/SDockTab.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
|
|
class ITimeSliderController;
|
|
class IToolkitHost;
|
|
class SWidget;
|
|
class UFactory;
|
|
|
|
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::CurveEditorTab(TEXT("CurveEditor"));
|
|
const FName AnimationEditorTabs::AssetBrowserTab(TEXT("SequenceBrowser"));
|
|
const FName AnimationEditorTabs::AssetDetailsTab(TEXT("AnimAssetPropertiesTab"));
|
|
const FName AnimationEditorTabs::CurveNamesTab(TEXT("AnimCurveViewerTab"));
|
|
const FName AnimationEditorTabs::SlotNamesTab(TEXT("SkeletonSlotNames"));
|
|
const FName AnimationEditorTabs::AnimMontageSectionsTab(TEXT("AnimMontageSections"));
|
|
|
|
DEFINE_LOG_CATEGORY(LogAnimationEditor);
|
|
|
|
#define LOCTEXT_NAMESPACE "AnimationEditor"
|
|
|
|
FAnimationEditor::~FAnimationEditor()
|
|
{
|
|
GEditor->GetEditorSubsystem<UImportSubsystem>()->OnAssetPostImport.RemoveAll(this);
|
|
FReimportManager::Instance()->OnPostReimport().RemoveAll(this);
|
|
//Make sure all delegate for preview mesh change are removed, by setting it to nullptr
|
|
if (PersonaToolkit.IsValid())
|
|
{
|
|
constexpr bool bSetPreviewMeshInAsset = false;
|
|
PersonaToolkit->SetPreviewMesh(nullptr, bSetPreviewMeshInAsset);
|
|
}
|
|
}
|
|
|
|
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);
|
|
GEditor->GetEditorSubsystem<UImportSubsystem>()->OnAssetPostImport.AddRaw(this, &FAnimationEditor::HandlePostImport);
|
|
|
|
FPersonaToolkitArgs PersonaToolkitArgs;
|
|
PersonaToolkitArgs.OnPreviewSceneSettingsCustomized = FOnPreviewSceneSettingsCustomized::FDelegate::CreateSP(this, &FAnimationEditor::HandleOnPreviewSceneSettingsCustomized);
|
|
|
|
FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked<FPersonaModule>("Persona");
|
|
PersonaToolkit = PersonaModule.CreatePersonaToolkit(InAnimationAsset, PersonaToolkitArgs);
|
|
|
|
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;
|
|
FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, AnimationEditorAppIdentifier, FTabManager::FLayout::NullLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, InAnimationAsset);
|
|
|
|
BindCommands();
|
|
|
|
AddApplicationMode(
|
|
AnimationEditorModes::AnimationEditorMode,
|
|
MakeShareable(new FAnimationEditorMode(SharedThis(this), SkeletonTree.ToSharedRef())));
|
|
|
|
SetCurrentMode(AnimationEditorModes::AnimationEditorMode);
|
|
|
|
ExtendMenu();
|
|
ExtendToolbar();
|
|
RegenerateMenusAndToolbars();
|
|
|
|
OpenNewAnimationDocumentTab(AnimationAsset);
|
|
|
|
PersonaToolkit->GetPreviewScene()->SetAllowMeshHitProxies(false);
|
|
}
|
|
|
|
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::InitToolMenuContext(FToolMenuContext& MenuContext)
|
|
{
|
|
FAssetEditorToolkit::InitToolMenuContext(MenuContext);
|
|
|
|
UAnimationToolMenuContext* AnimationToolMenuContext = NewObject<UAnimationToolMenuContext>();
|
|
AnimationToolMenuContext->AnimationEditor = SharedThis(this);
|
|
MenuContext.AddObject(AnimationToolMenuContext);
|
|
|
|
UPersonaToolMenuContext* PersonaToolMenuContext = NewObject<UPersonaToolMenuContext>();
|
|
PersonaToolMenuContext->SetToolkit(GetPersonaToolkit());
|
|
MenuContext.AddObject(PersonaToolMenuContext);
|
|
}
|
|
|
|
void FAnimationEditor::Tick(float DeltaTime)
|
|
{
|
|
//Do not tick the animation editor if we are compiling the skeletalmesh we edit
|
|
if (GetPersonaToolkit()->GetMesh() && GetPersonaToolkit()->GetMesh()->IsCompiling())
|
|
{
|
|
return;
|
|
}
|
|
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().ExportToFBX_AnimData,
|
|
FExecuteAction::CreateSP(this, &FAnimationEditor::OnExportToFBX, EExportSourceOption::CurrentAnimation_AnimData),
|
|
FCanExecuteAction::CreateSP(this, &FAnimationEditor::HasValidAnimationSequence));
|
|
|
|
ToolkitCommands->MapAction(FAnimationEditorCommands::Get().ExportToFBX_PreviewMesh,
|
|
FExecuteAction::CreateSP(this, &FAnimationEditor::OnExportToFBX, EExportSourceOption::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));
|
|
}
|
|
|
|
TSharedPtr<FAnimationEditor> FAnimationEditor::GetAnimationEditor(const FToolMenuContext& InMenuContext)
|
|
{
|
|
if (UAnimationToolMenuContext* Context = InMenuContext.FindContext<UAnimationToolMenuContext>())
|
|
{
|
|
if (Context->AnimationEditor.IsValid())
|
|
{
|
|
return StaticCastSharedPtr<FAnimationEditor>(Context->AnimationEditor.Pin());
|
|
}
|
|
}
|
|
|
|
return TSharedPtr<FAnimationEditor>();
|
|
}
|
|
|
|
void FAnimationEditor::HandleOnPreviewSceneSettingsCustomized(IDetailLayoutBuilder& DetailBuilder) const
|
|
{
|
|
DetailBuilder.HideCategory("Animation Blueprint");
|
|
}
|
|
|
|
void FAnimationEditor::ExtendToolbar()
|
|
{
|
|
FToolMenuOwnerScoped OwnerScoped(this);
|
|
|
|
// Add in Editor Specific functionality
|
|
FName ParentName;
|
|
static const FName MenuName = GetToolMenuToolbarName(ParentName);
|
|
|
|
UToolMenu* ToolMenu = UToolMenus::Get()->ExtendMenu(MenuName);
|
|
const FToolMenuInsert SectionInsertLocation("Asset", EToolMenuInsertType::After);
|
|
|
|
{
|
|
ToolMenu->AddDynamicSection("Persona", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InToolMenu)
|
|
{
|
|
TSharedPtr<FAnimationEditor> AnimationEditor = GetAnimationEditor(InToolMenu->Context);
|
|
if (AnimationEditor.IsValid() && AnimationEditor->PersonaToolkit.IsValid())
|
|
{
|
|
FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked<FPersonaModule>("Persona");
|
|
FPersonaModule::FCommonToolbarExtensionArgs Args;
|
|
Args.bPreviewAnimation = false;
|
|
Args.bReferencePose = false;
|
|
PersonaModule.AddCommonToolbarExtensions(InToolMenu, Args);
|
|
}
|
|
}), SectionInsertLocation);
|
|
}
|
|
|
|
{
|
|
FToolMenuSection& AnimationSection = ToolMenu->AddSection("Animation", LOCTEXT("ToolbarAnimationSectionLabel", "Animation"), SectionInsertLocation);
|
|
AnimationSection.AddEntry(FToolMenuEntry::InitToolBarButton(FAnimationEditorCommands::Get().ReimportAnimation));
|
|
AnimationSection.AddEntry(FToolMenuEntry::InitToolBarButton(FAnimationEditorCommands::Get().ApplyCompression, LOCTEXT("Toolbar_ApplyCompression", "Apply Compression")));
|
|
AnimationSection.AddEntry(FToolMenuEntry::InitComboButton(
|
|
"ExportAsset",
|
|
FToolUIActionChoice(FUIAction()),
|
|
FNewToolMenuChoice(FOnGetContent::CreateSP(this, &FAnimationEditor::GenerateExportAssetMenu)),
|
|
LOCTEXT("ExportAsset_Label", "Export Asset"),
|
|
LOCTEXT("ExportAsset_ToolTip", "Export Assets for this skeleton."),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Persona.ExportToFBX")
|
|
));
|
|
}
|
|
|
|
{
|
|
FToolMenuSection& EditingSection = ToolMenu->AddSection("Editing", LOCTEXT("ToolbarEditingSectionLabel", "Editing"), SectionInsertLocation);
|
|
EditingSection.AddEntry(FToolMenuEntry::InitToolBarButton(FAnimationEditorCommands::Get().SetKey, LOCTEXT("Toolbar_SetKey", "Key")));
|
|
}
|
|
|
|
// 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");
|
|
TSharedRef<class IAssetFamily> AssetFamily = PersonaModule.CreatePersonaAssetFamily(AnimationAsset);
|
|
AddToolbarWidget(PersonaModule.CreateAssetFamilyShortcutWidget(SharedThis(this), AssetFamily));
|
|
}
|
|
));
|
|
}
|
|
|
|
void FAnimationEditor::ExtendMenu()
|
|
{
|
|
MenuExtender = MakeShareable(new FExtender);
|
|
|
|
FToolMenuOwnerScoped OwnerScoped(this);
|
|
|
|
// Add in Editor Specific functionality
|
|
UToolMenu* ToolMenu = UToolMenus::Get()->ExtendMenu("AssetEditor.AnimationEditor.MainMenu.Asset");
|
|
const FToolMenuInsert SectionInsertLocation("AssetEditorActions", EToolMenuInsertType::After);
|
|
|
|
FToolMenuSection& AnimationSection = ToolMenu->AddSection("AnimationEditor", LOCTEXT("AnimationEditorAssetMenu_Animation", "Animation"), SectionInsertLocation);
|
|
AnimationSection.AddEntry(FToolMenuEntry::InitMenuEntry(FAnimationEditorCommands::Get().ApplyCompression));
|
|
|
|
AnimationSection.AddEntry(FToolMenuEntry::InitSubMenu(
|
|
"ExportAsset",
|
|
LOCTEXT("ExportAsset_Label", "Export Asset"),
|
|
LOCTEXT("ExportAsset_ToolTip", "Export Assets for this skeleton."),
|
|
FNewToolMenuChoice(FNewMenuDelegate::CreateSP(this, &FAnimationEditor::FillExportAssetMenu)),
|
|
false,
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Persona.ExportToFBX")
|
|
));
|
|
|
|
AnimationSection.AddEntry(FToolMenuEntry::InitMenuEntry(FAnimationEditorCommands::Get().AddLoopingInterpolation));
|
|
AnimationSection.AddEntry(FToolMenuEntry::InitMenuEntry(FAnimationEditorCommands::Get().RemoveBoneTracks));
|
|
|
|
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::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(), 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);
|
|
|
|
// Close existing opened curve tab
|
|
if(AnimCurveDocumentTab.IsValid())
|
|
{
|
|
AnimCurveDocumentTab.Pin()->RequestCloseTab();
|
|
}
|
|
|
|
AnimCurveDocumentTab.Reset();
|
|
|
|
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));
|
|
|
|
const bool bIsReusedEditor = SharedAnimDocumentTab.IsValid();
|
|
if (bIsReusedEditor)
|
|
{
|
|
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())
|
|
.OnTabClosed_Lambda([this](TSharedRef<SDockTab> InTab)
|
|
{
|
|
TSharedPtr<SDockTab> CurveTab = AnimCurveDocumentTab.Pin();
|
|
if(CurveTab.IsValid())
|
|
{
|
|
CurveTab->RequestCloseTab();
|
|
}
|
|
})
|
|
[
|
|
TabContents
|
|
];
|
|
|
|
OpenedTab->SetLeftContent(IDocumentation::Get()->CreateAnchor(DocumentLink));
|
|
|
|
TabManager->InsertNewDocumentTab(AnimationEditorTabs::DocumentTab, FTabManager::ESearchPreference::RequireClosedTab, OpenedTab.ToSharedRef());
|
|
|
|
SharedAnimDocumentTab = OpenedTab;
|
|
}
|
|
|
|
// Invoke the montage sections tab, and make sure the asset browser is there and in focus when we are dealing with a montage.
|
|
if(InAnimAsset->IsA<UAnimMontage>())
|
|
{
|
|
TabManager->TryInvokeTab(AnimationEditorTabs::AnimMontageSectionsTab);
|
|
|
|
// Only activate the asset browser tab when this is a reused Animation Editor window.
|
|
if (bIsReusedEditor)
|
|
{
|
|
TabManager->TryInvokeTab(AnimationEditorTabs::AssetBrowserTab);
|
|
}
|
|
OnSectionsChanged.Broadcast();
|
|
}
|
|
else
|
|
{
|
|
// Close existing opened montage sections tab
|
|
TSharedPtr<SDockTab> OpenMontageSectionsTab = TabManager->FindExistingLiveTab(AnimationEditorTabs::AnimMontageSectionsTab);
|
|
if(OpenMontageSectionsTab.IsValid())
|
|
{
|
|
OpenMontageSectionsTab->RequestCloseTab();
|
|
}
|
|
}
|
|
|
|
if (SequenceBrowser.IsValid())
|
|
{
|
|
SequenceBrowser.Pin()->SelectAsset(InAnimAsset);
|
|
}
|
|
|
|
// let the asset family know too
|
|
PersonaModule.RecordAssetOpened(FAssetData(InAnimAsset));
|
|
}
|
|
|
|
return OpenedTab;
|
|
}
|
|
|
|
void FAnimationEditor::EditCurves(UAnimSequenceBase* InAnimSequence, const TArray<FCurveEditInfo>& InCurveInfo, const TSharedPtr<ITimeSliderController>& InExternalTimeSliderController)
|
|
{
|
|
FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>("Persona");
|
|
|
|
if(!AnimCurveDocumentTab.IsValid())
|
|
{
|
|
TSharedRef<IAnimSequenceCurveEditor> NewCurveEditor = PersonaModule.CreateCurveWidgetForAnimDocument(SharedThis(this), GetPersonaToolkit()->GetPreviewScene(), InAnimSequence, InExternalTimeSliderController, TabManager);
|
|
CurveEditor = NewCurveEditor;
|
|
|
|
TSharedPtr<SDockTab> CurveTab = SNew(SDockTab)
|
|
.Label(LOCTEXT("CurveEditorTabTitle", "Curve Editor"))
|
|
.TabRole(ETabRole::DocumentTab)
|
|
.TabColorScale(GetTabColorScale())
|
|
[
|
|
NewCurveEditor
|
|
];
|
|
|
|
AnimCurveDocumentTab = CurveTab;
|
|
|
|
TabManager->InsertNewDocumentTab(AnimationEditorTabs::CurveEditorTab, FTabManager::ESearchPreference::RequireClosedTab, CurveTab.ToSharedRef());
|
|
}
|
|
else
|
|
{
|
|
TabManager->DrawAttention(AnimCurveDocumentTab.Pin().ToSharedRef());
|
|
}
|
|
|
|
check(CurveEditor.IsValid());
|
|
|
|
for(const FCurveEditInfo& CurveInfo : InCurveInfo)
|
|
{
|
|
CurveEditor.Pin()->AddCurve(CurveInfo.CurveDisplayName, CurveInfo.CurveColor, CurveInfo.Name, CurveInfo.Type, CurveInfo.CurveIndex, CurveInfo.OnCurveModified);
|
|
}
|
|
|
|
CurveEditor.Pin()->ZoomToFit();
|
|
}
|
|
|
|
void FAnimationEditor::StopEditingCurves(const TArray<FCurveEditInfo>& InCurveInfo)
|
|
{
|
|
if(CurveEditor.IsValid())
|
|
{
|
|
for(const FCurveEditInfo& CurveInfo : InCurveInfo)
|
|
{
|
|
CurveEditor.Pin()->RemoveCurve(CurveInfo.Name, CurveInfo.Type, CurveInfo.CurveIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FAnimationEditor::HandleSectionsChanged()
|
|
{
|
|
OnSectionsChanged.Broadcast();
|
|
}
|
|
|
|
void FAnimationEditor::SetAnimationAsset(UAnimationAsset* AnimAsset)
|
|
{
|
|
HandleOpenNewAsset(AnimAsset);
|
|
}
|
|
|
|
IAnimationSequenceBrowser* FAnimationEditor::GetAssetBrowser() const
|
|
{
|
|
return SequenceBrowser.Pin().Get();
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
void FAnimationEditor::OnReimportAnimation()
|
|
{
|
|
UAnimSequence* AnimSequence = Cast<UAnimSequence>(AnimationAsset);
|
|
if (AnimSequence)
|
|
{
|
|
FReimportManager::Instance()->ReimportAsync(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, false);
|
|
}
|
|
}
|
|
|
|
void FAnimationEditor::OnExportToFBX(const EExportSourceOption Option)
|
|
{
|
|
UAnimSequence* AnimSequenceToRecord = nullptr;
|
|
if (Option == EExportSourceOption::CurrentAnimation_AnimData)
|
|
{
|
|
TArray<UObject*> AssetsToExport;
|
|
AssetsToExport.Add(AnimationAsset);
|
|
ExportToFBX(AssetsToExport, false);
|
|
}
|
|
else if (Option == EExportSourceOption::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()->GetSkeletalMeshAsset());
|
|
}
|
|
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)
|
|
{
|
|
UAnimSequence* AnimSequence = Cast<UAnimSequence>(AnimationAsset);
|
|
if (AnimSequence)
|
|
{
|
|
IAnimationDataController& Controller = AnimSequence->GetController();
|
|
IAnimationDataController::FScopedBracket ScopedBracket(Controller, LOCTEXT("OnRemoveBoneTrack_Bracket", "Removing all Bone Animation and Transform Curve Tracks"));
|
|
|
|
Controller.RemoveAllBoneTracks();
|
|
Controller.RemoveAllCurvesOfType(ERawCurveTrackTypes::RCT_Transform);
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedRef< SWidget > FAnimationEditor::GenerateExportAssetMenu() const
|
|
{
|
|
const bool bShouldCloseWindowAfterMenuSelection = true;
|
|
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, GetToolkitCommands());
|
|
FillExportAssetMenu(MenuBuilder);
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
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)
|
|
{
|
|
// Grab existing curve (if present)
|
|
FRichCurve* Curve = CurveTable->FindRichCurve(CurveName, FString());
|
|
if (Curve == nullptr)
|
|
{
|
|
// Or allocate new curve
|
|
Curve = &CurveTable->AddRichCurve(CurveName);
|
|
}
|
|
|
|
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
|
|
for (const FFloatCurve& FloatCurve : Sequence->GetDataModel()->GetFloatCurves())
|
|
{
|
|
FRichCurve* Curve = FindOrAddCurve(CurveTable, FloatCurve.Name.DisplayName);
|
|
*Curve = FloatCurve.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();
|
|
}
|
|
|
|
void FAnimationEditor::ConditionalRefreshEditor(UObject* InObject)
|
|
{
|
|
bool bInterestingAsset = true;
|
|
|
|
if (InObject != GetPersonaToolkit()->GetSkeleton() && (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.GetAnimReference() == InObject)
|
|
{
|
|
bInterestingAsset = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GetPersonaToolkit()->GetSkeleton() == nullptr)
|
|
{
|
|
bInterestingAsset = false;
|
|
}
|
|
|
|
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
|
|
{
|
|
ISequenceRecorder& RecorderModule = FModuleManager::Get().LoadModuleChecked<ISequenceRecorder>("SequenceRecorder");
|
|
return RecorderModule.RecordSingleNodeInstanceToAnimation(PreviewComponent, NewAsset, /*bShowMessage*/false);
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|