Files
UnrealEngine/Engine/Source/Editor/SkeletonEditor/Private/SkeletonEditor.cpp
2025-05-18 13:04:45 +08:00

418 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SkeletonEditor.h"
#include "DetailLayoutBuilder.h"
#include "Modules/ModuleManager.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Animation/DebugSkelMeshComponent.h"
#include "Toolkits/AssetEditorToolkit.h"
#include "AssetRegistry/AssetData.h"
#include "EdGraph/EdGraphSchema.h"
#include "Editor/EditorEngine.h"
#include "EngineGlobals.h"
#include "ISkeletonTree.h"
#include "ISkeletonEditorModule.h"
#include "IPersonaToolkit.h"
#include "PersonaModule.h"
#include "SkeletonEditorMode.h"
#include "IAnimationSequenceBrowser.h"
#include "IPersonaPreviewScene.h"
#include "SkeletonEditorCommands.h"
#include "IAssetFamily.h"
#include "PersonaCommonCommands.h"
#include "IEditableSkeleton.h"
#include "ISkeletonTreeItem.h"
#include "Algo/Transform.h"
#include "PersonaToolMenuContext.h"
#include "ToolMenus.h"
#include "ToolMenuMisc.h"
#include "SkeletonToolMenuContext.h"
const FName SkeletonEditorAppIdentifier = FName(TEXT("SkeletonEditorApp"));
const FName SkeletonEditorModes::SkeletonEditorMode(TEXT("SkeletonEditorMode"));
const FName SkeletonEditorTabs::DetailsTab(TEXT("DetailsTab"));
const FName SkeletonEditorTabs::SkeletonTreeTab(TEXT("SkeletonTreeView"));
const FName SkeletonEditorTabs::ViewportTab(TEXT("Viewport"));
const FName SkeletonEditorTabs::AssetBrowserTab(TEXT("SequenceBrowser"));
const FName SkeletonEditorTabs::AnimNotifiesTab(TEXT("SkeletonAnimNotifies"));
const FName SkeletonEditorTabs::CurveMetadataTab(TEXT("AnimCurveMetadataEditorTab"));
const FName SkeletonEditorTabs::CurveDebuggerTab("AnimCurveViewerTab");
const FName SkeletonEditorTabs::AdvancedPreviewTab(TEXT("AdvancedPreviewTab"));
const FName SkeletonEditorTabs::RetargetManagerTab(TEXT("RetargetManager"));
const FName SkeletonEditorTabs::SlotNamesTab("SkeletonSlotNames");
const FName SkeletonEditorTabs::FindReplaceTab("FindReplaceTab");
DEFINE_LOG_CATEGORY(LogSkeletonEditor);
#define LOCTEXT_NAMESPACE "SkeletonEditor"
FSkeletonEditor::FSkeletonEditor()
{
UEditorEngine* Editor = Cast<UEditorEngine>(GEngine);
if (Editor != nullptr)
{
Editor->RegisterForUndo(this);
}
}
FSkeletonEditor::~FSkeletonEditor()
{
UEditorEngine* Editor = Cast<UEditorEngine>(GEngine);
if (Editor != nullptr)
{
Editor->UnregisterForUndo(this);
}
if (PersonaToolkit.IsValid())
{
constexpr bool bSetPreviewMeshInAsset = false;
PersonaToolkit->SetPreviewMesh(nullptr, bSetPreviewMeshInAsset);
}
}
void FSkeletonEditor::HandleOpenNewAsset(UObject* InNewAsset)
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(InNewAsset);
}
void FSkeletonEditor::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_SkeletonEditor", "Skeleton Editor"));
FAssetEditorToolkit::RegisterTabSpawners( InTabManager );
}
void FSkeletonEditor::UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
}
void FSkeletonEditor::InitSkeletonEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, USkeleton* InSkeleton)
{
Skeleton = InSkeleton;
FPersonaToolkitArgs PersonaToolkitArgs;
PersonaToolkitArgs.OnPreviewSceneSettingsCustomized = FOnPreviewSceneSettingsCustomized::FDelegate::CreateSP(this, &FSkeletonEditor::HandleOnPreviewSceneSettingsCustomized);
FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked<FPersonaModule>("Persona");
PersonaToolkit = PersonaModule.CreatePersonaToolkit(InSkeleton, PersonaToolkitArgs);
PersonaToolkit->GetPreviewScene()->SetDefaultAnimationMode(EPreviewSceneDefaultAnimationMode::ReferencePose);
PersonaModule.RecordAssetOpened(FAssetData(InSkeleton));
FSkeletonTreeArgs SkeletonTreeArgs;
SkeletonTreeArgs.OnSelectionChanged = FOnSkeletonTreeSelectionChanged::CreateSP(this, &FSkeletonEditor::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, SkeletonEditorAppIdentifier, FTabManager::FLayout::NullLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, InSkeleton);
BindCommands();
AddApplicationMode(
SkeletonEditorModes::SkeletonEditorMode,
MakeShareable(new FSkeletonEditorMode(SharedThis(this), SkeletonTree.ToSharedRef())));
SetCurrentMode(SkeletonEditorModes::SkeletonEditorMode);
ExtendMenu();
ExtendToolbar();
RegenerateMenusAndToolbars();
PersonaToolkit->GetPreviewScene()->SetAllowMeshHitProxies(false);
}
FName FSkeletonEditor::GetToolkitFName() const
{
return FName("SkeletonEditor");
}
FText FSkeletonEditor::GetBaseToolkitName() const
{
return LOCTEXT("AppLabel", "SkeletonEditor");
}
FString FSkeletonEditor::GetWorldCentricTabPrefix() const
{
return LOCTEXT("WorldCentricTabPrefix", "SkeletonEditor ").ToString();
}
FLinearColor FSkeletonEditor::GetWorldCentricTabColorScale() const
{
return FLinearColor(0.3f, 0.2f, 0.5f, 0.5f);
}
void FSkeletonEditor::InitToolMenuContext(FToolMenuContext& MenuContext)
{
FAssetEditorToolkit::InitToolMenuContext(MenuContext);
UPersonaToolMenuContext* PersonaContext = NewObject<UPersonaToolMenuContext>();
PersonaContext->SetToolkit(GetPersonaToolkit());
MenuContext.AddObject(PersonaContext);
USkeletonToolMenuContext* SkeletonContext = NewObject<USkeletonToolMenuContext>();
SkeletonContext->SkeletonEditor = SharedThis(this);
MenuContext.AddObject(SkeletonContext);
}
void FSkeletonEditor::BindCommands()
{
FSkeletonEditorCommands::Register();
ToolkitCommands->MapAction( FSkeletonEditorCommands::Get().RemoveUnusedBones,
FExecuteAction::CreateSP(this, &FSkeletonEditor::RemoveUnusedBones),
FCanExecuteAction::CreateSP(this, &FSkeletonEditor::CanRemoveBones));
ToolkitCommands->MapAction(FSkeletonEditorCommands::Get().TestSkeletonCurveMetaDataForUse,
FExecuteAction::CreateSP(this, &FSkeletonEditor::TestSkeletonCurveMetaDataForUse));
ToolkitCommands->MapAction(FSkeletonEditorCommands::Get().UpdateSkeletonRefPose,
FExecuteAction::CreateSP(this, &FSkeletonEditor::UpdateSkeletonRefPose));
ToolkitCommands->MapAction(FSkeletonEditorCommands::Get().AnimNotifyWindow,
FExecuteAction::CreateSP(this, &FSkeletonEditor::OnAnimNotifyWindow));
ToolkitCommands->MapAction(FSkeletonEditorCommands::Get().Retargeting,
FExecuteAction::CreateSP(this, &FSkeletonEditor::OnRetargetManager));
ToolkitCommands->MapAction(FSkeletonEditorCommands::Get().ImportMesh,
FExecuteAction::CreateSP(this, &FSkeletonEditor::OnImportAsset));
ToolkitCommands->MapAction(FPersonaCommonCommands::Get().TogglePlay,
FExecuteAction::CreateRaw(&GetPersonaToolkit()->GetPreviewScene().Get(), &IPersonaPreviewScene::TogglePlayback));
}
TSharedPtr<FSkeletonEditor> FSkeletonEditor::GetSkeletonEditor(const FToolMenuContext& InMenuContext)
{
if (USkeletonToolMenuContext* Context = InMenuContext.FindContext<USkeletonToolMenuContext>())
{
if (Context->SkeletonEditor.IsValid())
{
return StaticCastSharedPtr<FSkeletonEditor>(Context->SkeletonEditor.Pin());
}
}
return TSharedPtr<FSkeletonEditor>();
}
void FSkeletonEditor::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<FSkeletonEditor> SkeletonEditor = GetSkeletonEditor(InToolMenu->Context);
if (SkeletonEditor.IsValid() && SkeletonEditor->PersonaToolkit.IsValid())
{
FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked<FPersonaModule>("Persona");
PersonaModule.AddCommonToolbarExtensions(InToolMenu);
}
}), SectionInsertLocation);
}
{
FToolMenuSection& SkeletonSection = ToolMenu->AddSection("Skeleton", LOCTEXT("ToolbarSkeletonSectionLabel", "Skeleton"), SectionInsertLocation);
SkeletonSection.AddEntry(FToolMenuEntry::InitToolBarButton(FSkeletonEditorCommands::Get().AnimNotifyWindow));
SkeletonSection.AddEntry(FToolMenuEntry::InitToolBarButton(FSkeletonEditorCommands::Get().Retargeting, LOCTEXT("Toolbar_RetargetManager", "Retarget Manager")));
SkeletonSection.AddEntry(FToolMenuEntry::InitToolBarButton(FSkeletonEditorCommands::Get().ImportMesh));
}
// If the ToolbarExtender is valid, remove it before rebuilding it
if (ToolbarExtender.IsValid())
{
RemoveToolbarExtender(ToolbarExtender);
ToolbarExtender.Reset();
}
ToolbarExtender = MakeShareable(new FExtender);
AddToolbarExtender(ToolbarExtender);
ISkeletonEditorModule& SkeletonEditorModule = FModuleManager::GetModuleChecked<ISkeletonEditorModule>("SkeletonEditor");
AddToolbarExtender(SkeletonEditorModule.GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
TArray<ISkeletonEditorModule::FSkeletonEditorToolbarExtender> ToolbarExtenderDelegates = SkeletonEditorModule.GetAllSkeletonEditorToolbarExtenders();
for (auto& ToolbarExtenderDelegate : ToolbarExtenderDelegates)
{
if (ToolbarExtenderDelegate.IsBound())
{
AddToolbarExtender(ToolbarExtenderDelegate.Execute(GetToolkitCommands(), SharedThis(this)));
}
}
ToolbarExtender->AddToolBarExtension(
"Asset",
EExtensionHook::After,
GetToolkitCommands(),
FToolBarExtensionDelegate::CreateLambda([this](FToolBarBuilder& ToolbarBuilder)
{
FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked<FPersonaModule>("Persona");
TSharedRef<class IAssetFamily> AssetFamily = PersonaModule.CreatePersonaAssetFamily(Skeleton);
AddToolbarWidget(PersonaModule.CreateAssetFamilyShortcutWidget(SharedThis(this), AssetFamily));
}
));
}
void FSkeletonEditor::ExtendMenu()
{
MenuExtender = MakeShareable(new FExtender);
struct Local
{
static void ExtendMenu(FMenuBuilder& MenuBuilder)
{
// View
MenuBuilder.BeginSection("SkeletonEditor", LOCTEXT("SkeletonEditorAssetMenu_Skeleton", "Skeleton"));
{
MenuBuilder.AddMenuEntry(FSkeletonEditorCommands::Get().RemoveUnusedBones);
MenuBuilder.AddMenuEntry(FSkeletonEditorCommands::Get().UpdateSkeletonRefPose);
MenuBuilder.AddMenuEntry(FSkeletonEditorCommands::Get().TestSkeletonCurveMetaDataForUse);
}
MenuBuilder.EndSection();
}
};
MenuExtender->AddMenuExtension(
"AssetEditorActions",
EExtensionHook::After,
GetToolkitCommands(),
FMenuExtensionDelegate::CreateStatic(&Local::ExtendMenu)
);
AddMenuExtender(MenuExtender);
ISkeletonEditorModule& SkeletonEditorModule = FModuleManager::GetModuleChecked<ISkeletonEditorModule>("SkeletonEditor");
AddMenuExtender(SkeletonEditorModule.GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
}
UObject* FSkeletonEditor::HandleGetAsset()
{
return GetEditingObject();
}
void FSkeletonEditor::HandleObjectsSelected(const TArray<UObject*>& InObjects)
{
if (DetailsView.IsValid())
{
DetailsView->SetObjects(InObjects);
}
}
void FSkeletonEditor::HandleObjectSelected(UObject* InObject)
{
if (DetailsView.IsValid())
{
DetailsView->SetObject(InObject);
}
}
void FSkeletonEditor::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 FSkeletonEditor::PostUndo(bool bSuccess)
{
OnPostUndo.Broadcast();
}
void FSkeletonEditor::PostRedo(bool bSuccess)
{
OnPostUndo.Broadcast();
}
void FSkeletonEditor::Tick(float DeltaTime)
{
GetPersonaToolkit()->GetPreviewScene()->InvalidateViews();
}
TStatId FSkeletonEditor::GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FSkeletonEditor, STATGROUP_Tickables);
}
bool FSkeletonEditor::CanRemoveBones() const
{
UDebugSkelMeshComponent* PreviewMeshComponent = PersonaToolkit->GetPreviewMeshComponent();
return PreviewMeshComponent && PreviewMeshComponent->GetSkeletalMeshAsset();
}
void FSkeletonEditor::RemoveUnusedBones()
{
GetSkeletonTree()->GetEditableSkeleton()->RemoveUnusedBones();
}
void FSkeletonEditor::TestSkeletonCurveMetaDataForUse() const
{
FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked<FPersonaModule>("Persona");
PersonaModule.TestSkeletonCurveMetaDataForUse(GetSkeletonTree()->GetEditableSkeleton());
}
void FSkeletonEditor::UpdateSkeletonRefPose()
{
if (PersonaToolkit->GetPreviewMeshComponent()->GetSkeletalMeshAsset())
{
GetSkeletonTree()->GetEditableSkeleton()->UpdateSkeletonReferencePose(PersonaToolkit->GetPreviewMeshComponent()->GetSkeletalMeshAsset());
}
}
void FSkeletonEditor::OnAnimNotifyWindow()
{
InvokeTab(SkeletonEditorTabs::AnimNotifiesTab);
}
void FSkeletonEditor::OnRetargetManager()
{
InvokeTab(SkeletonEditorTabs::RetargetManagerTab);
}
void FSkeletonEditor::OnImportAsset()
{
FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked<FPersonaModule>("Persona");
PersonaModule.ImportNewAsset(Skeleton, FBXIT_SkeletalMesh);
}
void FSkeletonEditor::HandleOnPreviewSceneSettingsCustomized(IDetailLayoutBuilder& DetailBuilder)
{
DetailBuilder.HideCategory("Animation Blueprint");
}
void FSkeletonEditor::HandleDetailsCreated(const TSharedRef<IDetailsView>& InDetailsView)
{
DetailsView = InDetailsView;
}
void FSkeletonEditor::HandleAnimationSequenceBrowserCreated(const TSharedRef<IAnimationSequenceBrowser>& InSequenceBrowser)
{
SequenceBrowser = InSequenceBrowser;
}
IAnimationSequenceBrowser* FSkeletonEditor::GetAssetBrowser() const
{
return SequenceBrowser.Pin().Get();
}
#undef LOCTEXT_NAMESPACE