You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
3347 lines
99 KiB
C++
3347 lines
99 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "PersonaPrivatePCH.h"
|
|
#include "Persona.h"
|
|
#include "SPersonaToolbar.h"
|
|
#include "PersonaModule.h"
|
|
#include "AnimGraphDefinitions.h"
|
|
#include "IDetailsView.h"
|
|
|
|
#include "Toolkits/IToolkitHost.h"
|
|
|
|
// this one needed?
|
|
#include "SAnimationEditorViewport.h"
|
|
// end of questionable
|
|
|
|
#include "SSequenceEditor.h"
|
|
#include "SMontageEditor.h"
|
|
#include "SAnimCompositeEditor.h"
|
|
#include "SAnimationBlendSpace.h"
|
|
#include "SAnimationBlendSpace1D.h"
|
|
#include "SKismetInspector.h"
|
|
#include "SSkeletonWidget.h"
|
|
|
|
#include "Editor/Kismet/Public/BlueprintEditorTabs.h"
|
|
#include "Editor/Kismet/Public/BlueprintEditorModes.h"
|
|
|
|
#include "ScopedTransaction.h"
|
|
#include "Editor/UnrealEd/Public/EdGraphUtilities.h"
|
|
#include "Editor/UnrealEd/Public/Kismet2/BlueprintEditorUtils.h"
|
|
#include "Editor/UnrealEd/Public/Kismet2/DebuggerCommands.h"
|
|
|
|
//#include "SkeletonMode/SkeletonMode.h"
|
|
#include "MeshMode/MeshMode.h"
|
|
#include "PhysicsMode/PhysicsMode.h"
|
|
#include "AnimationMode/AnimationMode.h"
|
|
#include "BlueprintMode/AnimBlueprintMode.h"
|
|
#include "Runtime/AssetRegistry/Public/AssetRegistryModule.h"
|
|
|
|
#include "Editor/Persona/Private/AnimationEditorViewportClient.h"
|
|
|
|
#include "ComponentAssetBroker.h"
|
|
#include "AnimGraphNode_BlendListByInt.h"
|
|
#include "AnimGraphNode_BlendSpaceEvaluator.h"
|
|
#include "AnimGraphNode_BlendSpacePlayer.h"
|
|
#include "AnimGraphNode_LayeredBoneBlend.h"
|
|
#include "AnimGraphNode_SequencePlayer.h"
|
|
#include "AnimGraphNode_SequenceEvaluator.h"
|
|
#include "AnimGraphNode_Slot.h"
|
|
#include "Customization/AnimGraphNodeSlotDetails.h"
|
|
|
|
#include "AnimPreviewInstance.h"
|
|
|
|
#include "Particles/ParticleSystemComponent.h"
|
|
|
|
#include "DesktopPlatformModule.h"
|
|
#include "MainFrame.h"
|
|
#include "AnimationCompressionPanel.h"
|
|
#include "FbxAnimUtils.h"
|
|
|
|
#include "SAnimationDlgs.h"
|
|
#include "Developer/AssetTools/Public/AssetToolsModule.h"
|
|
#include "FbxMeshUtils.h"
|
|
#include "AnimationEditorUtils.h"
|
|
#include "SAnimationSequenceBrowser.h"
|
|
#include "SDockTab.h"
|
|
#include "GenericCommands.h"
|
|
#include "SNotificationList.h"
|
|
#include "NotificationManager.h"
|
|
|
|
#include "Editor/KismetWidgets/Public/SSingleObjectDetailsPanel.h"
|
|
#include "Animation/AimOffsetBlendSpace.h"
|
|
#include "Animation/AimOffsetBlendSpace1D.h"
|
|
#include "Animation/AnimNotifies/AnimNotifyState.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "FPersona"
|
|
|
|
/////////////////////////////////////////////////////
|
|
// FLocalCharEditorCallbacks
|
|
|
|
struct FLocalCharEditorCallbacks
|
|
{
|
|
static FText GetObjectName(UObject* Object)
|
|
{
|
|
return FText::FromString( Object->GetName() );
|
|
}
|
|
};
|
|
|
|
/////////////////////////////////////////////////////
|
|
// SPersonaPreviewPropertyEditor
|
|
|
|
class SPersonaPreviewPropertyEditor : public SSingleObjectDetailsPanel
|
|
{
|
|
public:
|
|
SLATE_BEGIN_ARGS(SPersonaPreviewPropertyEditor) {}
|
|
SLATE_END_ARGS()
|
|
|
|
private:
|
|
// Pointer back to owning Persona editor instance (the keeper of state)
|
|
TWeakPtr<FPersona> PersonaPtr;
|
|
public:
|
|
void Construct(const FArguments& InArgs, TSharedPtr<FPersona> InPersona)
|
|
{
|
|
PersonaPtr = InPersona;
|
|
|
|
SSingleObjectDetailsPanel::Construct(SSingleObjectDetailsPanel::FArguments().HostCommandList(InPersona->GetToolkitCommands()), /*bAutomaticallyObserveViaGetObjectToObserve*/ true, /*bAllowSearch*/ true);
|
|
|
|
PropertyView->SetIsPropertyEditingEnabledDelegate(FIsPropertyEditingEnabled::CreateStatic([] { return !GIntraFrameDebuggingGameThread; }));
|
|
}
|
|
|
|
// SSingleObjectDetailsPanel interface
|
|
virtual UObject* GetObjectToObserve() const override
|
|
{
|
|
if (UDebugSkelMeshComponent* PreviewComponent = PersonaPtr.Pin()->GetPreviewMeshComponent())
|
|
{
|
|
if (PreviewComponent->AnimScriptInstance != nullptr)
|
|
{
|
|
return PreviewComponent->AnimScriptInstance;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
virtual TSharedRef<SWidget> PopulateSlot(TSharedRef<SWidget> PropertyEditorWidget) override
|
|
{
|
|
return SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0.f, 8.f, 0.f, 0.f)
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FEditorStyle::GetBrush("Persona.PreviewPropertiesWarning"))
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("AnimBlueprintEditPreviewText", "Changes to preview options are not saved in the asset."))
|
|
.Font(FEditorStyle::GetFontStyle("PropertyWindow.NormalFont"))
|
|
.ShadowColorAndOpacity(FLinearColor::Black.CopyWithNewOpacity(0.3f))
|
|
.ShadowOffset(FVector2D::UnitVector)
|
|
]
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.FillHeight(1)
|
|
[
|
|
PropertyEditorWidget
|
|
];
|
|
}
|
|
// End of SSingleObjectDetailsPanel interface
|
|
};
|
|
|
|
//////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////
|
|
// FSkeletonEditAppMode
|
|
|
|
class FSkeletonEditAppMode : public FPersonaAppMode
|
|
{
|
|
public:
|
|
FSkeletonEditAppMode(TSharedPtr<FPersona> InPersona)
|
|
: FPersonaAppMode(InPersona, FPersonaModes::SkeletonDisplayMode)
|
|
{
|
|
PersonaTabFactories.RegisterFactory(MakeShareable(new FSelectionDetailsSummoner(InPersona)));
|
|
|
|
TabLayout = FTabManager::NewLayout( "Persona_SkeletonEditMode_Layout_v3" )
|
|
->AddArea
|
|
(
|
|
FTabManager::NewPrimaryArea() ->SetOrientation(Orient_Vertical)
|
|
->Split
|
|
(
|
|
// Top toolbar area
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient(0.186721f)
|
|
->SetHideTabWell(true)
|
|
->AddTab( InPersona->GetToolbarTabId(), ETabState::OpenedTab )
|
|
)
|
|
->Split
|
|
(
|
|
// Rest of screen
|
|
FTabManager::NewSplitter() ->SetOrientation(Orient_Horizontal)
|
|
->Split
|
|
(
|
|
// Left 1/3rd - Skeleton tree and mesh panel
|
|
FTabManager::NewSplitter()
|
|
->SetOrientation(Orient_Vertical)
|
|
->SetSizeCoefficient(0.3f)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->AddTab( FPersonaTabs::SkeletonTreeViewID, ETabState::OpenedTab )
|
|
)
|
|
)
|
|
->Split
|
|
(
|
|
// Middle 1/3rd - Viewport
|
|
FTabManager::NewSplitter()
|
|
->SetOrientation(Orient_Vertical)
|
|
->SetSizeCoefficient(0.5f)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetHideTabWell(true)
|
|
->AddTab( FPersonaTabs::PreviewViewportID, ETabState::OpenedTab )
|
|
)
|
|
)
|
|
->Split
|
|
(
|
|
// Right 1/3rd - Details panel
|
|
FTabManager::NewSplitter()
|
|
->SetOrientation(Orient_Vertical)
|
|
->SetSizeCoefficient(0.2f)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->AddTab( FBlueprintEditorTabs::DetailsID, ETabState::OpenedTab ) //@TODO: FPersonaTabs::AnimPropertiesID
|
|
)
|
|
)
|
|
)
|
|
);
|
|
|
|
}
|
|
};
|
|
|
|
|
|
/////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////
|
|
|
|
TArray<UObject*> GetEditorObjectsOfClass( const TArray< UObject* >& Objects, const UClass* ClassToFind )
|
|
{
|
|
TArray<UObject*> ObjectsToReturn;
|
|
for( auto ObjectIter = Objects.CreateConstIterator(); ObjectIter; ++ObjectIter )
|
|
{
|
|
// Don't allow user to perform certain actions on objects that aren't actually assets (e.g. Level Script blueprint objects)
|
|
const auto EditingObject = *ObjectIter;
|
|
if( EditingObject != NULL && EditingObject->IsAsset() && EditingObject->GetClass()->IsChildOf( ClassToFind ) )
|
|
{
|
|
ObjectsToReturn.Add(EditingObject);
|
|
}
|
|
}
|
|
return ObjectsToReturn;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////
|
|
// FPersona
|
|
|
|
FPersona::FPersona()
|
|
: TargetSkeleton(NULL)
|
|
, PreviewComponent(NULL)
|
|
, PersonaMeshDetailLayout(NULL)
|
|
, PreviewScene(FPreviewScene::ConstructionValues().AllowAudioPlayback(true).ShouldSimulatePhysics(true))
|
|
{
|
|
// Register to be notified when properties are edited
|
|
OnPropertyChangedHandle = FCoreUObjectDelegates::FOnObjectPropertyChanged::FDelegate::CreateRaw(this, &FPersona::OnPropertyChanged);
|
|
OnPropertyChangedHandleDelegateHandle = FCoreUObjectDelegates::OnObjectPropertyChanged.Add(OnPropertyChangedHandle);
|
|
|
|
GEditor->OnBlueprintPreCompile().AddRaw(this, &FPersona::OnBlueprintPreCompile);
|
|
|
|
//Temporary fix for missing attached assets - MDW
|
|
PreviewScene.GetWorld()->GetWorldSettings()->SetIsTemporarilyHiddenInEditor(false);
|
|
|
|
}
|
|
|
|
FPersona::~FPersona()
|
|
{
|
|
GEditor->OnBlueprintPreCompile().RemoveAll(this);
|
|
|
|
FEditorDelegates::OnAssetPostImport.RemoveAll(this);
|
|
FReimportManager::Instance()->OnPostReimport().RemoveAll(this);
|
|
|
|
|
|
if(TargetSkeleton)
|
|
{
|
|
TargetSkeleton->UnregisterOnSkeletonHierarchyChanged(this);
|
|
}
|
|
|
|
if(Viewport.IsValid())
|
|
{
|
|
Viewport.Pin()->CleanupPersonaReferences();
|
|
}
|
|
|
|
FCoreUObjectDelegates::OnObjectPropertyChanged.Remove(OnPropertyChangedHandleDelegateHandle);
|
|
|
|
if(PreviewComponent)
|
|
{
|
|
PreviewComponent->RemoveFromRoot();
|
|
}
|
|
|
|
// NOTE: Any tabs that we still have hanging out when destroyed will be cleaned up by FBaseToolkit's destructor
|
|
}
|
|
|
|
TSharedPtr<SWidget> FPersona::CreateEditorWidgetForAnimDocument(UObject* InAnimAsset, FString& DocumentLink)
|
|
{
|
|
TSharedPtr<SWidget> Result;
|
|
if (InAnimAsset)
|
|
{
|
|
if (UAnimSequence* Sequence = Cast<UAnimSequence>(InAnimAsset))
|
|
{
|
|
Result = SNew(SSequenceEditor)
|
|
.Persona(SharedThis(this))
|
|
.Sequence(Sequence);
|
|
|
|
DocumentLink = TEXT("Engine/Animation/Sequences");
|
|
}
|
|
else if (UAnimComposite* Composite = Cast<UAnimComposite>(InAnimAsset))
|
|
{
|
|
Result = SNew(SAnimCompositeEditor)
|
|
.Persona(SharedThis(this))
|
|
.Composite(Composite);
|
|
|
|
DocumentLink = TEXT("Engine/Animation/AnimationComposite");
|
|
}
|
|
else if (UAnimMontage* Montage = Cast<UAnimMontage>(InAnimAsset))
|
|
{
|
|
Result = SNew(SMontageEditor)
|
|
.Persona(SharedThis(this))
|
|
.Montage(Montage);
|
|
|
|
DocumentLink = TEXT("Engine/Animation/AnimMontage");
|
|
}
|
|
else if (UBlendSpace* BlendSpace = Cast<UBlendSpace>(InAnimAsset))
|
|
{
|
|
Result = SNew(SBlendSpaceEditor)
|
|
.Persona(SharedThis(this))
|
|
.BlendSpace(BlendSpace);
|
|
|
|
if (Cast<UAimOffsetBlendSpace>(InAnimAsset))
|
|
{
|
|
DocumentLink = TEXT("Engine/Animation/AimOffset");
|
|
}
|
|
else
|
|
{
|
|
DocumentLink = TEXT("Engine/Animation/Blendspaces");
|
|
}
|
|
}
|
|
else if (UBlendSpace1D* BlendSpace1D = Cast<UBlendSpace1D>(InAnimAsset))
|
|
{
|
|
Result = SNew(SBlendSpaceEditor1D)
|
|
.Persona(SharedThis(this))
|
|
.BlendSpace1D(BlendSpace1D);
|
|
|
|
|
|
if (Cast<UAimOffsetBlendSpace1D>(InAnimAsset))
|
|
{
|
|
DocumentLink = TEXT("Engine/Animation/AimOffset");
|
|
}
|
|
else
|
|
{
|
|
DocumentLink = TEXT("Engine/Animation/Blendspaces");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Result.IsValid())
|
|
{
|
|
InAnimAsset->SetFlags(RF_Transactional);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
void FPersona::ReinitMode()
|
|
{
|
|
FName CurrentMode = GetCurrentMode();
|
|
|
|
if (CurrentMode == FPersonaModes::AnimationEditMode)
|
|
{
|
|
// if opening animation edit mode, open the animation editor
|
|
OpenNewAnimationDocumentTab(GetPreviewAnimationAsset());
|
|
}
|
|
}
|
|
|
|
TSharedPtr<SDockTab> FPersona::OpenNewAnimationDocumentTab(UObject* InAnimAsset)
|
|
{
|
|
TSharedPtr<SDockTab> OpenedTab;
|
|
|
|
if (InAnimAsset != NULL)
|
|
{
|
|
FString DocumentLink;
|
|
TSharedPtr<SWidget> TabContents = CreateEditorWidgetForAnimDocument(InAnimAsset, DocumentLink);
|
|
if (TabContents.IsValid())
|
|
{
|
|
if ( SharedAnimAssetBeingEdited.IsValid() )
|
|
{
|
|
RemoveEditingObject(SharedAnimAssetBeingEdited.Get());
|
|
}
|
|
|
|
if ( InAnimAsset != NULL )
|
|
{
|
|
AddEditingObject(InAnimAsset);
|
|
}
|
|
|
|
SharedAnimAssetBeingEdited = InAnimAsset;
|
|
TAttribute<FText> NameAttribute = TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateStatic(&FLocalCharEditorCallbacks::GetObjectName, (UObject*)InAnimAsset));
|
|
|
|
if (SharedAnimDocumentTab.IsValid())
|
|
{
|
|
OpenedTab = SharedAnimDocumentTab.Pin();
|
|
OpenedTab->SetContent(TabContents.ToSharedRef());
|
|
OpenedTab->ActivateInParent(ETabActivationCause::SetDirectly);
|
|
OpenedTab->SetLabel(NameAttribute);
|
|
OpenedTab->SetLeftContent(IDocumentation::Get()->CreateAnchor(DocumentLink));
|
|
}
|
|
else
|
|
{
|
|
OpenedTab = SNew(SDockTab)
|
|
.Label(NameAttribute)
|
|
.OnTabClosed(this, &FPersona::OnEditTabClosed)
|
|
.TabRole(ETabRole::DocumentTab)
|
|
.TabColorScale(GetTabColorScale())
|
|
[
|
|
TabContents.ToSharedRef()
|
|
];
|
|
|
|
OpenedTab->SetLeftContent(IDocumentation::Get()->CreateAnchor(DocumentLink));
|
|
|
|
TabManager->InsertNewDocumentTab("Document", FTabManager::ESearchPreference::RequireClosedTab, OpenedTab.ToSharedRef());
|
|
|
|
SharedAnimDocumentTab = OpenedTab;
|
|
}
|
|
}
|
|
|
|
if(SequenceBrowser.IsValid())
|
|
{
|
|
UAnimationAsset* NewAsset = CastChecked<UAnimationAsset>(InAnimAsset);
|
|
SequenceBrowser.Pin()->SelectAsset(NewAsset);
|
|
}
|
|
}
|
|
|
|
return OpenedTab;
|
|
}
|
|
|
|
TSharedPtr<SDockTab> FPersona::OpenNewDocumentTab(class UAnimationAsset* InAnimAsset)
|
|
{
|
|
/// before opening new asset, clear the currently selected object
|
|
SetDetailObject(NULL);
|
|
|
|
TSharedPtr<SDockTab> NewTab;
|
|
if (InAnimAsset)
|
|
{
|
|
// Are we allowed to open animation documents right now?
|
|
//@TODO: Super-hacky check
|
|
FName CurrentMode = GetCurrentMode();
|
|
if (CurrentMode == FPersonaModes::AnimationEditMode)
|
|
{
|
|
NewTab = OpenNewAnimationDocumentTab(InAnimAsset);
|
|
}
|
|
|
|
SetPreviewAnimationAsset(InAnimAsset);
|
|
}
|
|
|
|
return NewTab;
|
|
}
|
|
|
|
void FPersona::SetViewport(TWeakPtr<class SAnimationEditorViewportTabBody> NewViewport)
|
|
{
|
|
Viewport = NewViewport;
|
|
if(Viewport.IsValid())
|
|
{
|
|
Viewport.Pin()->SetPreviewComponent(PreviewComponent);
|
|
}
|
|
OnViewportCreated.Broadcast(Viewport);
|
|
}
|
|
|
|
void FPersona::SetSequenceBrowser(class SAnimationSequenceBrowser* InSequenceBrowser)
|
|
{
|
|
if (InSequenceBrowser)
|
|
{
|
|
SequenceBrowser = SharedThis(InSequenceBrowser);
|
|
}
|
|
else
|
|
{
|
|
SequenceBrowser = NULL;
|
|
}
|
|
}
|
|
|
|
void FPersona::RefreshViewport()
|
|
{
|
|
if (Viewport.IsValid())
|
|
{
|
|
Viewport.Pin()->RefreshViewport();
|
|
}
|
|
}
|
|
|
|
USkeleton* FPersona::GetSkeleton() const
|
|
{
|
|
return TargetSkeleton;
|
|
}
|
|
|
|
USkeletalMesh* FPersona::GetMesh() const
|
|
{
|
|
return PreviewComponent->SkeletalMesh;
|
|
}
|
|
|
|
UPhysicsAsset* FPersona::GetPhysicsAsset() const
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
UAnimBlueprint* FPersona::GetAnimBlueprint() const
|
|
{
|
|
return Cast<UAnimBlueprint>(GetBlueprintObj());
|
|
}
|
|
|
|
UObject* FPersona::GetMeshAsObject() const
|
|
{
|
|
return GetMesh();
|
|
}
|
|
|
|
UObject* FPersona::GetSkeletonAsObject() const
|
|
{
|
|
return GetSkeleton();
|
|
}
|
|
|
|
UObject* FPersona::GetPhysicsAssetAsObject() const
|
|
{
|
|
return GetPhysicsAsset();
|
|
}
|
|
|
|
UObject* FPersona::GetAnimBlueprintAsObject() const
|
|
{
|
|
return GetAnimBlueprint();
|
|
}
|
|
|
|
void FPersona::ExtendMenu()
|
|
{
|
|
// Add additional persona editor menus
|
|
struct Local
|
|
{
|
|
static void AddPersonaSaveLoadMenu(FMenuBuilder& MenuBuilder)
|
|
{
|
|
MenuBuilder.AddMenuEntry(FPersonaCommands::Get().SaveAnimationAssets);
|
|
MenuBuilder.AddMenuSeparator();
|
|
}
|
|
|
|
static void AddPersonaFileMenu(FMenuBuilder& MenuBuilder)
|
|
{
|
|
// View
|
|
MenuBuilder.BeginSection("Persona", LOCTEXT("PersonaEditorMenu_File", "Blueprint"));
|
|
{
|
|
}
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
static void AddPersonaAssetMenu(FMenuBuilder& MenuBuilder, FPersona* PersonaPtr)
|
|
{
|
|
// View
|
|
MenuBuilder.BeginSection("Persona", LOCTEXT("PersonaAssetMenuMenu_Skeleton", "Skeleton"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(FPersonaCommands::Get().ChangeSkeletonPreviewMesh);
|
|
MenuBuilder.AddMenuEntry(FPersonaCommands::Get().RemoveUnusedBones);
|
|
MenuBuilder.AddMenuEntry( FPersonaCommands::Get().UpdateSkeletonRefPose);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
// Animation menu
|
|
MenuBuilder.BeginSection("Persona", LOCTEXT("PersonaAssetMenuMenu_Animation", "Animation"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(FPersonaCommands::Get().ApplyCompression);
|
|
MenuBuilder.AddMenuEntry(FPersonaCommands::Get().ExportToFBX);
|
|
MenuBuilder.AddMenuEntry(FPersonaCommands::Get().AddLoopingInterpolation);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
MenuBuilder.BeginSection("Persona", LOCTEXT("PersonaAssetMenuMenu_Record", "Record"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(FPersonaCommands::Get().RecordAnimation,
|
|
NAME_None,
|
|
TAttribute<FText>(PersonaPtr, &FPersona::GetRecordMenuLabel));
|
|
}
|
|
MenuBuilder.EndSection();
|
|
}
|
|
};
|
|
|
|
if(MenuExtender.IsValid())
|
|
{
|
|
RemoveMenuExtender(MenuExtender);
|
|
MenuExtender.Reset();
|
|
}
|
|
|
|
MenuExtender = MakeShareable(new FExtender);
|
|
|
|
UAnimBlueprint* AnimBlueprint = GetAnimBlueprint();
|
|
if(AnimBlueprint)
|
|
{
|
|
TSharedPtr<FExtender> AnimBPMenuExtender = MakeShareable(new FExtender);
|
|
FKismet2Menu::SetupBlueprintEditorMenu(AnimBPMenuExtender, *this);
|
|
AddMenuExtender(AnimBPMenuExtender);
|
|
|
|
AnimBPMenuExtender->AddMenuExtension(
|
|
"FileLoadAndSave",
|
|
EExtensionHook::After,
|
|
GetToolkitCommands(),
|
|
FMenuExtensionDelegate::CreateStatic(&Local::AddPersonaFileMenu));
|
|
}
|
|
|
|
MenuExtender->AddMenuExtension(
|
|
"FileLoadAndSave",
|
|
EExtensionHook::First,
|
|
GetToolkitCommands(),
|
|
FMenuExtensionDelegate::CreateStatic(&Local::AddPersonaSaveLoadMenu));
|
|
|
|
MenuExtender->AddMenuExtension(
|
|
"AssetEditorActions",
|
|
EExtensionHook::After,
|
|
GetToolkitCommands(),
|
|
FMenuExtensionDelegate::CreateStatic(&Local::AddPersonaAssetMenu, this)
|
|
);
|
|
|
|
AddMenuExtender(MenuExtender);
|
|
|
|
// add extensible menu if exists
|
|
FPersonaModule* PersonaModule = &FModuleManager::LoadModuleChecked<FPersonaModule>("Persona");
|
|
AddMenuExtender(PersonaModule->GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
}
|
|
|
|
void FPersona::InitPersona(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, USkeleton* InitSkeleton, UAnimBlueprint* InitAnimBlueprint, UAnimationAsset* InitAnimationAsset, class USkeletalMesh* InitMesh)
|
|
{
|
|
FReimportManager::Instance()->OnPostReimport().AddRaw(this, &FPersona::OnPostReimport);
|
|
|
|
AssetDirtyBrush = FEditorStyle::GetBrush("ContentBrowser.ContentDirty");
|
|
|
|
if (!Toolbar.IsValid())
|
|
{
|
|
Toolbar = MakeShareable(new FBlueprintEditorToolbar(SharedThis(this)));
|
|
}
|
|
if (!PersonaToolbar.IsValid())
|
|
{
|
|
PersonaToolbar = MakeShareable(new FPersonaToolbar);
|
|
}
|
|
|
|
GetToolkitCommands()->Append(FPlayWorldCommands::GlobalPlayWorldActions.ToSharedRef());
|
|
|
|
// Create a list of available modes
|
|
{
|
|
TSharedPtr<FPersona> ThisPtr(SharedThis(this));
|
|
|
|
TArray< TSharedRef<FApplicationMode> > TempModeList;
|
|
TempModeList.Add(MakeShareable(new FSkeletonEditAppMode(ThisPtr)));
|
|
TempModeList.Add(MakeShareable(new FMeshEditAppMode(ThisPtr)));
|
|
//TempModeList.Add(MakeShareable(new FPhysicsEditAppMode(ThisPtr)));
|
|
TempModeList.Add(MakeShareable(new FAnimEditAppMode(ThisPtr)));
|
|
TempModeList.Add(MakeShareable(new FAnimBlueprintEditAppMode(ThisPtr)));
|
|
|
|
for (auto ModeListIt = TempModeList.CreateIterator(); ModeListIt; ++ModeListIt)
|
|
{
|
|
TSharedRef<FApplicationMode> ApplicationMode = *ModeListIt;
|
|
AddApplicationMode(ApplicationMode->GetModeName(), ApplicationMode);
|
|
}
|
|
}
|
|
|
|
// Determine the initial mode the app will open up in
|
|
FName InitialMode = NAME_None;
|
|
if (InitAnimBlueprint != NULL)
|
|
{
|
|
InitialMode = FPersonaModes::AnimBlueprintEditMode;
|
|
TargetSkeleton = InitAnimBlueprint->TargetSkeleton;
|
|
}
|
|
else if (InitAnimationAsset != NULL)
|
|
{
|
|
InitialMode = FPersonaModes::AnimationEditMode;
|
|
TargetSkeleton = InitSkeleton;
|
|
}
|
|
else if (InitMesh != NULL)
|
|
{
|
|
InitialMode = FPersonaModes::MeshEditMode;
|
|
TargetSkeleton = InitSkeleton;
|
|
}
|
|
else
|
|
{
|
|
InitialMode = FPersonaModes::SkeletonDisplayMode;
|
|
TargetSkeleton = InitSkeleton;
|
|
}
|
|
|
|
// Build up a list of objects being edited in this asset editor
|
|
TArray<UObject*> ObjectsBeingEdited;
|
|
|
|
if (InitAnimBlueprint != NULL)
|
|
{
|
|
ObjectsBeingEdited.Add(InitAnimBlueprint);
|
|
}
|
|
|
|
// Purposefully skipping adding InitAnimationAsset here because it will get added when opening a document tab for it
|
|
//if (InitAnimationAsset != NULL)
|
|
//{
|
|
// ObjectsBeingEdited.Add(InitAnimationAsset);
|
|
//}
|
|
|
|
// Purposefully skipping adding InitMesh here because it will get added when setting the preview mesh
|
|
//if (InitMesh != NULL)
|
|
//{
|
|
// ObjectsBeingEdited.Add(InitMesh);
|
|
//}
|
|
|
|
ObjectsBeingEdited.Add(TargetSkeleton);
|
|
check(TargetSkeleton);
|
|
|
|
TargetSkeleton->CollectAnimationNotifies();
|
|
|
|
TargetSkeleton->RegisterOnSkeletonHierarchyChanged( USkeleton::FOnSkeletonHierarchyChanged::CreateSP( this, &FPersona::OnSkeletonHierarchyChanged ) );
|
|
|
|
// We could modify the skeleton within Persona (add/remove sockets), so we need to enable undo/redo on it
|
|
TargetSkeleton->SetFlags( RF_Transactional );
|
|
|
|
// Initialize the asset editor and spawn tabs
|
|
const TSharedRef<FTabManager::FLayout> DummyLayout = FTabManager::NewLayout("NullLayout")->AddArea(FTabManager::NewPrimaryArea());
|
|
const bool bCreateDefaultStandaloneMenu = true;
|
|
const bool bCreateDefaultToolbar = true;
|
|
InitAssetEditor(Mode, InitToolkitHost, PersonaAppName, DummyLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectsBeingEdited);
|
|
|
|
TArray<UBlueprint*> AnimBlueprints;
|
|
if (InitAnimBlueprint)
|
|
{
|
|
AnimBlueprints.Add(InitAnimBlueprint);
|
|
}
|
|
CommonInitialization(AnimBlueprints);
|
|
|
|
check(PreviewComponent == NULL);
|
|
|
|
// Create the preview component
|
|
PreviewComponent = NewObject<UDebugSkelMeshComponent>();
|
|
|
|
// note: we add to root here (rather than using RF_Standalone) as all standalone objects will be cleaned up when we switch
|
|
// preview worlds (in UWorld::CleanupWorld), but we want this to stick around while Persona exists
|
|
PreviewComponent->AddToRoot();
|
|
|
|
bool bSetMesh = false;
|
|
|
|
// Set the mesh
|
|
if (InitMesh != NULL)
|
|
{
|
|
SetPreviewMesh(InitMesh);
|
|
bSetMesh = true;
|
|
|
|
if (TargetSkeleton && !TargetSkeleton->GetPreviewMesh())
|
|
{
|
|
TargetSkeleton->SetPreviewMesh(InitMesh, false);
|
|
}
|
|
}
|
|
else if (InitAnimationAsset != NULL)
|
|
{
|
|
USkeletalMesh* AssetMesh = InitAnimationAsset->GetPreviewMesh();
|
|
if (AssetMesh)
|
|
{
|
|
SetPreviewMesh(AssetMesh);
|
|
bSetMesh = true;
|
|
}
|
|
}
|
|
|
|
if (!bSetMesh && TargetSkeleton)
|
|
{
|
|
//If no preview mesh set, just find the first mesh that uses this skeleton
|
|
USkeletalMesh* PreviewMesh = TargetSkeleton->GetPreviewMesh(true);
|
|
if ( PreviewMesh )
|
|
{
|
|
SetPreviewMesh( PreviewMesh );
|
|
}
|
|
}
|
|
|
|
// Force validation of preview attached assets (catch case of never doing it if we dont have a valid preview mesh)
|
|
ValidatePreviewAttachedAssets(NULL);
|
|
|
|
UAnimBlueprint* AnimBlueprint = GetAnimBlueprint();
|
|
PreviewComponent->SetAnimInstanceClass(AnimBlueprint ? AnimBlueprint->GeneratedClass : NULL);
|
|
|
|
// We always want a preview instance unless we are using blueprints so that bone manipulation works
|
|
if (AnimBlueprint == NULL)
|
|
{
|
|
PreviewComponent->EnablePreview(true, NULL, NULL);
|
|
}
|
|
else
|
|
{
|
|
// Make sure the object being debugged is the preview instance
|
|
AnimBlueprint->SetObjectBeingDebugged(PreviewComponent->AnimScriptInstance);
|
|
}
|
|
|
|
ExtendMenu();
|
|
ExtendDefaultPersonaToolbar();
|
|
RegenerateMenusAndToolbars();
|
|
|
|
// Activate the initial mode (which will populate with a real layout)
|
|
SetCurrentMode(InitialMode);
|
|
check(CurrentAppModePtr.IsValid());
|
|
|
|
// Post-layout initialization
|
|
PostLayoutBlueprintEditorInitialization();
|
|
|
|
//@TODO: Push somewhere else?
|
|
if (InitAnimationAsset != NULL)
|
|
{
|
|
OpenNewDocumentTab( InitAnimationAsset );
|
|
}
|
|
|
|
// register customization of Slot node for this Persona
|
|
// this is so that you can open the manage window per Persona
|
|
TWeakPtr<FPersona> PersonaPtr = SharedThis(this);
|
|
Inspector->GetPropertyView()->RegisterInstancedCustomPropertyLayout(UAnimGraphNode_Slot::StaticClass(),
|
|
FOnGetDetailCustomizationInstance::CreateStatic(&FAnimGraphNodeSlotDetails::MakeInstance, PersonaPtr));
|
|
|
|
// Register post import callback to catch animation imports when we have the asset open (we need to reinit)
|
|
FEditorDelegates::OnAssetPostImport.AddRaw(this, &FPersona::OnPostImport);
|
|
|
|
|
|
}
|
|
|
|
void FPersona::CreateAnimation(const TArray<UObject*> NewAssets, int32 Option)
|
|
{
|
|
bool bResult = true;
|
|
if (NewAssets.Num() > 0)
|
|
{
|
|
USkeletalMeshComponent* MeshComponent = GetPreviewMeshComponent();
|
|
UAnimSequence* Sequence = Cast<UAnimSequence> (GetPreviewAnimationAsset());
|
|
|
|
for (auto NewAsset : NewAssets)
|
|
{
|
|
UAnimSequence* NewAnimSequence = Cast<UAnimSequence>(NewAsset);
|
|
if (NewAnimSequence)
|
|
{
|
|
switch (Option)
|
|
{
|
|
case 0:
|
|
bResult &= NewAnimSequence->CreateAnimation(MeshComponent->SkeletalMesh);
|
|
break;
|
|
case 1:
|
|
bResult &= NewAnimSequence->CreateAnimation(MeshComponent);
|
|
break;
|
|
case 2:
|
|
bResult &= NewAnimSequence->CreateAnimation(Sequence);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if it contains error, warn them
|
|
if (bResult)
|
|
{
|
|
OnAssetCreated(NewAssets);
|
|
|
|
// if it created based on current mesh component,
|
|
if (Option == 1)
|
|
{
|
|
PreviewComponent->PreviewInstance->ResetModifiedBone();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// give warning
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPersona::FillCreateAnimationMenu(FMenuBuilder& MenuBuilder) const
|
|
{
|
|
TArray<TWeakObjectPtr<USkeleton>> Skeletons;
|
|
|
|
Skeletons.Add(TargetSkeleton);
|
|
|
|
// create rig
|
|
MenuBuilder.BeginSection("CreateAnimationSubMenu", LOCTEXT("CreateAnimationSubMenuHeading", "Create Animation"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("CreateAnimation_RefPose", "From Reference Pose"),
|
|
LOCTEXT("CreateAnimation_RefPose_Tooltip", "Create Animation from reference pose."),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset<UAnimSequenceFactory, UAnimSequence>, Skeletons, FString("_Sequence"), FAnimAssetCreated::CreateSP(this, &FPersona::CreateAnimation, 0), false),
|
|
FCanExecuteAction()
|
|
)
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("CreateAnimation_CurrentPose", "From Current Pose"),
|
|
LOCTEXT("CreateAnimation_CurrentPose_Tooltip", "Create Animation from current pose."),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset<UAnimSequenceFactory, UAnimSequence>, Skeletons, FString("_Sequence"), FAnimAssetCreated::CreateSP(this, &FPersona::CreateAnimation, 1), false),
|
|
FCanExecuteAction()
|
|
)
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("CreateAnimation_CurrentAnimation", "From Current Animation"),
|
|
LOCTEXT("CreateAnimation_CurrentAnimation_Tooltip", "Create Animation from current animation."),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset<UAnimSequenceFactory, UAnimSequence>, Skeletons, FString("_Sequence"), FAnimAssetCreated::CreateSP(this, &FPersona::CreateAnimation, 2), false),
|
|
FCanExecuteAction::CreateSP(this, &FPersona::HasValidAnimationSequencePlaying)
|
|
)
|
|
);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
TSharedRef< SWidget > FPersona::GenerateCreateAssetMenu( USkeleton* Skeleton ) 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, &FPersona::FillCreateAnimationMenu),
|
|
false,
|
|
FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.AssetActions.CreateAnimAsset")
|
|
);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
TArray<TWeakObjectPtr<USkeleton>> Skeletons;
|
|
|
|
Skeletons.Add(Skeleton);
|
|
|
|
AnimationEditorUtils::FillCreateAssetMenu(MenuBuilder, Skeletons, FAnimAssetCreated::CreateSP(this, &FPersona::OnAssetCreated), false);
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
void FPersona::OnAssetCreated(const TArray<UObject*> NewAssets)
|
|
{
|
|
if ( NewAssets.Num() > 0 )
|
|
{
|
|
FAssetRegistryModule::AssetCreated(NewAssets[0]);
|
|
OpenNewDocumentTab(CastChecked<UAnimationAsset>(NewAssets[0]));
|
|
}
|
|
}
|
|
|
|
void FPersona::ExtendDefaultPersonaToolbar()
|
|
{
|
|
// If the ToolbarExtender is valid, remove it before rebuilding it
|
|
if(ToolbarExtender.IsValid())
|
|
{
|
|
RemoveToolbarExtender(ToolbarExtender);
|
|
ToolbarExtender.Reset();
|
|
}
|
|
|
|
ToolbarExtender = MakeShareable(new FExtender);
|
|
|
|
PersonaToolbar->SetupPersonaToolbar(ToolbarExtender, SharedThis(this));
|
|
|
|
// extend extra menu/toolbars
|
|
struct Local
|
|
{
|
|
static void FillToolbar(FToolBarBuilder& ToolbarBuilder, USkeleton* Skeleton, FPersona* PersonaPtr)
|
|
{
|
|
ToolbarBuilder.BeginSection("Skeleton");
|
|
{
|
|
ToolbarBuilder.AddToolBarButton(FPersonaCommands::Get().TogglePreviewAsset, NAME_None, LOCTEXT("Toolbar_PreviewAsset", "Preview"), TAttribute<FText>(PersonaPtr, &FPersona::GetPreviewAssetTooltip));
|
|
ToolbarBuilder.AddToolBarButton(FPersonaCommands::Get().ToggleReferencePose, NAME_None, LOCTEXT("Toolbar_ToggleReferencePose", "Ref Pose"), LOCTEXT("Toolbar_ToggleReferencePoseTooltip", "Show Reference Pose"));
|
|
ToolbarBuilder.AddSeparator();
|
|
ToolbarBuilder.AddToolBarButton(FPersonaCommands::Get().AnimNotifyWindow);
|
|
ToolbarBuilder.AddToolBarButton(FPersonaCommands::Get().RetargetManager, NAME_None, LOCTEXT("Toolbar_RetargetManager", "Retarget Manager"));
|
|
ToolbarBuilder.AddToolBarButton(FPersonaCommands::Get().ImportMesh);
|
|
ToolbarBuilder.AddToolBarButton(FPersonaCommands::Get().ReimportMesh);
|
|
|
|
// animation import menu
|
|
ToolbarBuilder.AddToolBarButton(FPersonaCommands::Get().ImportAnimation, NAME_None, LOCTEXT("Toolbar_ImportAnimation", "Import"));
|
|
ToolbarBuilder.AddToolBarButton(FPersonaCommands::Get().ReimportAnimation, NAME_None, LOCTEXT("Toolbar_ReimportAnimation", "Reimport"));
|
|
ToolbarBuilder.AddToolBarButton(FPersonaCommands::Get().ExportToFBX, NAME_None, LOCTEXT("Toolbar_ExportToFBX", "Export"));
|
|
|
|
ToolbarBuilder.AddToolBarButton(FPersonaCommands::Get().RecordAnimation,
|
|
NAME_None,
|
|
TAttribute<FText>(PersonaPtr, &FPersona::GetRecordStatusLabel),
|
|
TAttribute<FText>(PersonaPtr, &FPersona::GetRecordStatusTooltip),
|
|
TAttribute<FSlateIcon>(PersonaPtr, &FPersona::GetRecordStatusImage),
|
|
NAME_None);
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
|
|
ToolbarBuilder.BeginSection("Animation");
|
|
{
|
|
// create button
|
|
{
|
|
ToolbarBuilder.AddComboButton(
|
|
FUIAction(
|
|
FExecuteAction(),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(PersonaPtr, &FPersona::IsInPersonaMode, FPersonaModes::AnimationEditMode)
|
|
),
|
|
FOnGetContent::CreateSP(PersonaPtr, &FPersona::GenerateCreateAssetMenu, Skeleton),
|
|
LOCTEXT("OpenBlueprint_Label", "Create Asset"),
|
|
LOCTEXT("OpenBlueprint_ToolTip", "Create Assets for this skeleton."),
|
|
FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.CreateAsset")
|
|
);
|
|
}
|
|
|
|
ToolbarBuilder.AddToolBarButton(FPersonaCommands::Get().ApplyCompression, NAME_None, LOCTEXT("Toolbar_ApplyCompression", "Compression"));
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
|
|
ToolbarBuilder.BeginSection("Editing");
|
|
{
|
|
ToolbarBuilder.AddToolBarButton(FPersonaCommands::Get().SetKey, NAME_None, LOCTEXT("Toolbar_SetKey", "Key"));
|
|
ToolbarBuilder.AddToolBarButton(FPersonaCommands::Get().ApplyAnimation, NAME_None, LOCTEXT("Toolbar_ApplyAnimation", "Apply"));
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
}
|
|
};
|
|
|
|
|
|
ToolbarExtender->AddToolBarExtension(
|
|
"Asset",
|
|
EExtensionHook::After,
|
|
GetToolkitCommands(),
|
|
FToolBarExtensionDelegate::CreateStatic(&Local::FillToolbar, TargetSkeleton, this)
|
|
);
|
|
|
|
AddToolbarExtender(ToolbarExtender);
|
|
|
|
FPersonaModule* PersonaModule = &FModuleManager::LoadModuleChecked<FPersonaModule>("Persona");
|
|
AddToolbarExtender(PersonaModule->GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
|
|
}
|
|
|
|
UBlueprint* FPersona::GetBlueprintObj() const
|
|
{
|
|
auto EditingObjects = GetEditingObjects();
|
|
for (int32 i = 0; i < EditingObjects.Num(); ++i)
|
|
{
|
|
if (EditingObjects[i]->IsA<UAnimBlueprint>()) {return (UBlueprint*)EditingObjects[i];}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
UObject* FPersona::GetPreviewAnimationAsset() const
|
|
{
|
|
if (Viewport.IsValid())
|
|
{
|
|
if (PreviewComponent)
|
|
{
|
|
// if same, do not overwrite. It will reset time and everything
|
|
if (PreviewComponent->PreviewInstance != NULL)
|
|
{
|
|
return PreviewComponent->PreviewInstance->CurrentAsset;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
UObject* FPersona::GetAnimationAssetBeingEdited() const
|
|
{
|
|
if (!SharedAnimDocumentTab.IsValid())
|
|
{
|
|
SharedAnimAssetBeingEdited = NULL;
|
|
}
|
|
|
|
return SharedAnimAssetBeingEdited.Get();
|
|
}
|
|
|
|
void FPersona::SetPreviewAnimationAsset(UAnimationAsset* AnimAsset, bool bEnablePreview)
|
|
{
|
|
if (Viewport.IsValid() && !Viewport.Pin()->bPreviewLockModeOn)
|
|
{
|
|
if (PreviewComponent)
|
|
{
|
|
RemoveAttachedComponent(false);
|
|
|
|
if (AnimAsset != NULL)
|
|
{
|
|
// Early out if the new preview asset is the same as the current one, to avoid replaying from the beginning, etc...
|
|
if (AnimAsset == GetPreviewAnimationAsset() && PreviewComponent->IsPreviewOn())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Treat it as invalid if it's got a bogus skeleton pointer
|
|
if (AnimAsset->GetSkeleton() != TargetSkeleton)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
PreviewComponent->EnablePreview(bEnablePreview, AnimAsset, NULL);
|
|
}
|
|
|
|
OnAnimChanged.Broadcast(AnimAsset);
|
|
}
|
|
}
|
|
|
|
void FPersona::SetPreviewVertexAnim(UVertexAnimation* VertexAnim)
|
|
{
|
|
if (Viewport.IsValid() && !Viewport.Pin()->bPreviewLockModeOn)
|
|
{
|
|
if (PreviewComponent)
|
|
{
|
|
// if same, do not overwrite. It will reset time and everything
|
|
if( VertexAnim &&
|
|
PreviewComponent->PreviewInstance &&
|
|
VertexAnim == PreviewComponent->PreviewInstance->CurrentVertexAnim )
|
|
{
|
|
return;
|
|
}
|
|
|
|
PreviewComponent->EnablePreview(true, NULL, VertexAnim);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPersona::UpdateSelectionDetails(UObject* Object, const FText& ForcedTitle)
|
|
{
|
|
Inspector->ShowDetailsForSingleObject(Object, SKismetInspector::FShowDetailsOptions(ForcedTitle));
|
|
}
|
|
|
|
void FPersona::SetDetailObject(UObject* Obj)
|
|
{
|
|
FText ForcedTitle = (Obj != NULL) ? FText::FromString(Obj->GetName()) : FText::GetEmpty();
|
|
UpdateSelectionDetails(Obj, ForcedTitle);
|
|
}
|
|
|
|
UDebugSkelMeshComponent* FPersona::GetPreviewMeshComponent()
|
|
{
|
|
return PreviewComponent;
|
|
}
|
|
|
|
void FPersona::OnPostReimport(UObject* InObject, bool bSuccess)
|
|
{
|
|
if (bSuccess)
|
|
{
|
|
ConditionalRefreshEditor(InObject);
|
|
}
|
|
}
|
|
|
|
void FPersona::OnPostImport(UFactory* InFactory, UObject* InObject)
|
|
{
|
|
ConditionalRefreshEditor(InObject);
|
|
}
|
|
|
|
void FPersona::ConditionalRefreshEditor(UObject* InObject)
|
|
{
|
|
bool bInterestingAsset = true;
|
|
// Ignore if this is regarding a different object
|
|
if(InObject != TargetSkeleton && InObject != TargetSkeleton->GetPreviewMesh() && InObject != GetAnimationAssetBeingEdited())
|
|
{
|
|
bInterestingAsset = false;
|
|
}
|
|
|
|
// Check that we aren't a montage that uses an incoming animation
|
|
if(UAnimMontage* Montage = Cast<UAnimMontage>(GetAnimationAssetBeingEdited()))
|
|
{
|
|
for(FSlotAnimationTrack& Slot : Montage->SlotAnimTracks)
|
|
{
|
|
if(bInterestingAsset)
|
|
{
|
|
break;
|
|
}
|
|
|
|
for(FAnimSegment& Segment : Slot.AnimTrack.AnimSegments)
|
|
{
|
|
if(Segment.AnimReference == InObject)
|
|
{
|
|
bInterestingAsset = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(bInterestingAsset)
|
|
{
|
|
RefreshViewport();
|
|
ReinitMode();
|
|
|
|
OnPersonaRefresh.Broadcast();
|
|
}
|
|
}
|
|
|
|
/** Called when graph editor focus is changed */
|
|
void FPersona::OnGraphEditorFocused(const TSharedRef<class SGraphEditor>& InGraphEditor)
|
|
{
|
|
// in the future, depending on which graph editor is this will act different
|
|
FBlueprintEditor::OnGraphEditorFocused(InGraphEditor);
|
|
}
|
|
|
|
/** Create Default Tabs **/
|
|
void FPersona::CreateDefaultCommands()
|
|
{
|
|
if (GetBlueprintObj())
|
|
{
|
|
FBlueprintEditor::CreateDefaultCommands();
|
|
}
|
|
else
|
|
{
|
|
ToolkitCommands->MapAction( FGenericCommands::Get().Undo,
|
|
FExecuteAction::CreateSP( this, &FPersona::UndoAction ));
|
|
ToolkitCommands->MapAction( FGenericCommands::Get().Redo,
|
|
FExecuteAction::CreateSP( this, &FPersona::RedoAction ));
|
|
}
|
|
|
|
// now add default commands
|
|
FPersonaCommands::Register();
|
|
|
|
// save all animation assets
|
|
ToolkitCommands->MapAction(FPersonaCommands::Get().SaveAnimationAssets,
|
|
FExecuteAction::CreateSP(this, &FPersona::SaveAnimationAssets_Execute),
|
|
FCanExecuteAction::CreateSP(this, &FPersona::CanSaveAnimationAssets)
|
|
);
|
|
|
|
// record animation
|
|
ToolkitCommands->MapAction( FPersonaCommands::Get().RecordAnimation,
|
|
FExecuteAction::CreateSP( this, &FPersona::RecordAnimation ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP( this, &FPersona::IsRecordAvailable )
|
|
);
|
|
|
|
ToolkitCommands->MapAction(FPersonaCommands::Get().ApplyCompression,
|
|
FExecuteAction::CreateSP(this, &FPersona::OnApplyCompression),
|
|
FCanExecuteAction::CreateSP(this, &FPersona::HasValidAnimationSequencePlaying),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FPersona::IsInPersonaMode, FPersonaModes::AnimationEditMode)
|
|
);
|
|
|
|
ToolkitCommands->MapAction(FPersonaCommands::Get().SetKey,
|
|
FExecuteAction::CreateSP(this, &FPersona::OnSetKey),
|
|
FCanExecuteAction::CreateSP(this, &FPersona::CanSetKey),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FPersona::IsInPersonaMode, FPersonaModes::AnimationEditMode)
|
|
);
|
|
|
|
ToolkitCommands->MapAction(FPersonaCommands::Get().ApplyAnimation,
|
|
FExecuteAction::CreateSP(this, &FPersona::OnBakeAnimation),
|
|
FCanExecuteAction::CreateSP(this, &FPersona::CanBakeAnimation),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FPersona::IsInPersonaMode, FPersonaModes::AnimationEditMode)
|
|
);
|
|
|
|
ToolkitCommands->MapAction(FPersonaCommands::Get().ExportToFBX,
|
|
FExecuteAction::CreateSP(this, &FPersona::OnExportToFBX),
|
|
FCanExecuteAction::CreateSP(this, &FPersona::HasValidAnimationSequencePlaying),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FPersona::IsInPersonaMode, FPersonaModes::AnimationEditMode)
|
|
);
|
|
|
|
ToolkitCommands->MapAction(FPersonaCommands::Get().AddLoopingInterpolation,
|
|
FExecuteAction::CreateSP(this, &FPersona::OnAddLoopingInterpolation),
|
|
FCanExecuteAction::CreateSP(this, &FPersona::HasValidAnimationSequencePlaying),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FPersona::IsInPersonaMode, FPersonaModes::AnimationEditMode)
|
|
);
|
|
|
|
ToolkitCommands->MapAction( FPersonaCommands::Get().ChangeSkeletonPreviewMesh,
|
|
FExecuteAction::CreateSP( this, &FPersona::ChangeSkeletonPreviewMesh ),
|
|
FCanExecuteAction::CreateSP( this, &FPersona::CanChangeSkeletonPreviewMesh )
|
|
);
|
|
|
|
ToolkitCommands->MapAction(FPersonaCommands::Get().ToggleReferencePose,
|
|
FExecuteAction::CreateSP(this, &FPersona::ShowReferencePose, true),
|
|
FCanExecuteAction::CreateSP(this, &FPersona::CanShowReferencePose),
|
|
FIsActionChecked::CreateSP(this, &FPersona::IsShowReferencePoseEnabled)
|
|
);
|
|
|
|
ToolkitCommands->MapAction(FPersonaCommands::Get().TogglePreviewAsset,
|
|
FExecuteAction::CreateSP(this, &FPersona::ShowReferencePose, false),
|
|
FCanExecuteAction::CreateSP(this, &FPersona::CanPreviewAsset),
|
|
FIsActionChecked::CreateSP(this, &FPersona::IsPreviewAssetEnabled)
|
|
);
|
|
|
|
ToolkitCommands->MapAction( FPersonaCommands::Get().RemoveUnusedBones,
|
|
FExecuteAction::CreateSP( this, &FPersona::RemoveUnusedBones ),
|
|
FCanExecuteAction::CreateSP( this, &FPersona::CanRemoveBones )
|
|
);
|
|
ToolkitCommands->MapAction(FPersonaCommands::Get().UpdateSkeletonRefPose,
|
|
FExecuteAction::CreateSP(this, &FPersona::UpdateSkeletonRefPose)
|
|
);
|
|
|
|
ToolkitCommands->MapAction(FPersonaCommands::Get().AnimNotifyWindow,
|
|
FExecuteAction::CreateSP(this, &FPersona::OnAnimNotifyWindow),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FPersona::IsInPersonaMode, FPersonaModes::SkeletonDisplayMode)
|
|
);
|
|
|
|
ToolkitCommands->MapAction(FPersonaCommands::Get().RetargetManager,
|
|
FExecuteAction::CreateSP(this, &FPersona::OnRetargetManager),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FPersona::IsInPersonaMode, FPersonaModes::SkeletonDisplayMode)
|
|
);
|
|
|
|
ToolkitCommands->MapAction(FPersonaCommands::Get().ImportMesh,
|
|
FExecuteAction::CreateSP(this, &FPersona::OnImportAsset, FBXIT_SkeletalMesh),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FPersona::IsInPersonaMode, FPersonaModes::SkeletonDisplayMode)
|
|
);
|
|
|
|
ToolkitCommands->MapAction(FPersonaCommands::Get().ReimportMesh,
|
|
FExecuteAction::CreateSP(this, &FPersona::OnReimportMesh),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FPersona::IsInPersonaMode, FPersonaModes::MeshEditMode)
|
|
);
|
|
|
|
// ToolkitCommands->MapAction(FPersonaCommands::Get().ImportLODs,
|
|
// FExecuteAction::CreateSP(this, &FPersona::OnImportLODs),
|
|
// FCanExecuteAction(),
|
|
// FIsActionChecked(),
|
|
// FIsActionButtonVisible::CreateSP(this, &FPersona::IsInPersonaMode, FPersonaModes::MeshEditMode)
|
|
// );
|
|
|
|
// ToolkitCommands->MapAction(FPersonaCommands::Get().AddBodyPart,
|
|
// FExecuteAction::CreateSP(this, &FPersona::OnAddBodyPart),
|
|
// FCanExecuteAction(),
|
|
// FIsActionChecked(),
|
|
// FIsActionButtonVisible::CreateSP(this, &FPersona::IsInPersonaMode, FPersonaModes::MeshEditMode)
|
|
// );
|
|
|
|
|
|
// animation menu options
|
|
// import animation
|
|
ToolkitCommands->MapAction(FPersonaCommands::Get().ImportAnimation,
|
|
FExecuteAction::CreateSP(this, &FPersona::OnImportAsset, FBXIT_Animation),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FPersona::IsInPersonaMode, FPersonaModes::AnimationEditMode)
|
|
);
|
|
|
|
ToolkitCommands->MapAction(FPersonaCommands::Get().ReimportAnimation,
|
|
FExecuteAction::CreateSP(this, &FPersona::OnReimportAnimation),
|
|
FCanExecuteAction::CreateSP(this, &FPersona::HasValidAnimationSequencePlaying),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FPersona::IsInPersonaMode, FPersonaModes::AnimationEditMode)
|
|
);
|
|
}
|
|
|
|
bool FPersona::CanSelectBone() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void FPersona::OnSelectBone()
|
|
{
|
|
//@TODO: A2REMOVAL: This doesn't do anything
|
|
}
|
|
|
|
void FPersona::OnAddPosePin()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
if (SelectedNodes.Num() == 1)
|
|
{
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UObject* Node = *NodeIt;
|
|
|
|
if (UAnimGraphNode_BlendListByInt* BlendNode = Cast<UAnimGraphNode_BlendListByInt>(Node))
|
|
{
|
|
BlendNode->AddPinToBlendList();
|
|
break;
|
|
}
|
|
else if (UAnimGraphNode_LayeredBoneBlend* FilterNode = Cast<UAnimGraphNode_LayeredBoneBlend>(Node))
|
|
{
|
|
FilterNode->AddPinToBlendByFilter();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FPersona::CanAddPosePin() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void FPersona::OnRemovePosePin()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
UAnimGraphNode_BlendListByInt* BlendListIntNode = NULL;
|
|
UAnimGraphNode_LayeredBoneBlend* BlendByFilterNode = NULL;
|
|
|
|
if (SelectedNodes.Num() == 1)
|
|
{
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
if (UAnimGraphNode_BlendListByInt* BlendNode = Cast<UAnimGraphNode_BlendListByInt>(*NodeIt))
|
|
{
|
|
BlendListIntNode = BlendNode;
|
|
break;
|
|
}
|
|
else if (UAnimGraphNode_LayeredBoneBlend* LayeredBlendNode = Cast<UAnimGraphNode_LayeredBoneBlend>(*NodeIt))
|
|
{
|
|
BlendByFilterNode = LayeredBlendNode;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
// @fixme: I think we can make blendlistbase to have common functionality
|
|
// and each can implement the common function, but for now, we separate them
|
|
// each implement their menu, so we still can use listbase as the root
|
|
if (BlendListIntNode)
|
|
{
|
|
// make sure we at least have BlendListNode selected
|
|
UEdGraphPin* SelectedPin = FocusedGraphEd->GetGraphPinForMenu();
|
|
|
|
BlendListIntNode->RemovePinFromBlendList(SelectedPin);
|
|
|
|
// Update the graph so that the node will be refreshed
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
}
|
|
|
|
if (BlendByFilterNode)
|
|
{
|
|
// make sure we at least have BlendListNode selected
|
|
UEdGraphPin* SelectedPin = FocusedGraphEd->GetGraphPinForMenu();
|
|
|
|
BlendByFilterNode->RemovePinFromBlendByFilter(SelectedPin);
|
|
|
|
// Update the graph so that the node will be refreshed
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPersona::OnConvertToSequenceEvaluator()
|
|
{
|
|
FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
|
|
if (SelectedNodes.Num() > 0)
|
|
{
|
|
for (auto NodeIter = SelectedNodes.CreateIterator(); NodeIter; ++NodeIter)
|
|
{
|
|
UAnimGraphNode_SequencePlayer* OldNode = Cast<UAnimGraphNode_SequencePlayer>(*NodeIter);
|
|
|
|
// see if sequence player
|
|
if ( OldNode && OldNode->Node.Sequence )
|
|
{
|
|
//const FScopedTransaction Transaction( LOCTEXT("ConvertToSequenceEvaluator", "Convert to Single Frame Animation") );
|
|
|
|
// convert to sequence evaluator
|
|
UEdGraph* TargetGraph = OldNode->GetGraph();
|
|
// create new evaluator
|
|
FGraphNodeCreator<UAnimGraphNode_SequenceEvaluator> NodeCreator(*TargetGraph);
|
|
UAnimGraphNode_SequenceEvaluator* NewNode = NodeCreator.CreateNode();
|
|
NewNode->Node.Sequence = OldNode->Node.Sequence;
|
|
NodeCreator.Finalize();
|
|
|
|
// get default data from old node to new node
|
|
FEdGraphUtilities::CopyCommonState(OldNode, NewNode);
|
|
|
|
UEdGraphPin* OldPosePin = OldNode->FindPin(TEXT("Pose"));
|
|
UEdGraphPin* NewPosePin = NewNode->FindPin(TEXT("Pose"));
|
|
|
|
if (ensure(OldPosePin && NewPosePin))
|
|
{
|
|
NewPosePin->CopyPersistentDataFromOldPin(*OldPosePin);
|
|
}
|
|
|
|
// remove from selection and from graph
|
|
NodeIter.RemoveCurrent();
|
|
TargetGraph->RemoveNode(OldNode);
|
|
|
|
NewNode->Modify();
|
|
}
|
|
}
|
|
|
|
// @todo fixme: below code doesn't work
|
|
// because of SetAndCenterObject kicks in after new node is added
|
|
// will need to disable that first
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
// Update the graph so that the node will be refreshed
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetAnimBlueprint());
|
|
}
|
|
}
|
|
|
|
void FPersona::OnConvertToSequencePlayer()
|
|
{
|
|
FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
if (SelectedNodes.Num() > 0)
|
|
{
|
|
for (auto NodeIter = SelectedNodes.CreateIterator(); NodeIter; ++NodeIter)
|
|
{
|
|
UAnimGraphNode_SequenceEvaluator* OldNode = Cast<UAnimGraphNode_SequenceEvaluator>(*NodeIter);
|
|
|
|
// see if sequence player
|
|
if ( OldNode && OldNode->Node.Sequence )
|
|
{
|
|
//const FScopedTransaction Transaction( LOCTEXT("ConvertToSequenceEvaluator", "Convert to Single Frame Animation") );
|
|
// convert to sequence player
|
|
UEdGraph* TargetGraph = OldNode->GetGraph();
|
|
// create new player
|
|
FGraphNodeCreator<UAnimGraphNode_SequencePlayer> NodeCreator(*TargetGraph);
|
|
UAnimGraphNode_SequencePlayer* NewNode = NodeCreator.CreateNode();
|
|
NewNode->Node.Sequence = OldNode->Node.Sequence;
|
|
NodeCreator.Finalize();
|
|
|
|
// get default data from old node to new node
|
|
FEdGraphUtilities::CopyCommonState(OldNode, NewNode);
|
|
|
|
UEdGraphPin* OldPosePin = OldNode->FindPin(TEXT("Pose"));
|
|
UEdGraphPin* NewPosePin = NewNode->FindPin(TEXT("Pose"));
|
|
|
|
if (ensure(OldPosePin && NewPosePin))
|
|
{
|
|
NewPosePin->CopyPersistentDataFromOldPin(*OldPosePin);
|
|
}
|
|
|
|
// remove from selection and from graph
|
|
NodeIter.RemoveCurrent();
|
|
TargetGraph->RemoveNode(OldNode);
|
|
|
|
NewNode->Modify();
|
|
}
|
|
}
|
|
|
|
// @todo fixme: below code doesn't work
|
|
// because of SetAndCenterObject kicks in after new node is added
|
|
// will need to disable that first
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
// Update the graph so that the node will be refreshed
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetAnimBlueprint());
|
|
}
|
|
}
|
|
|
|
void FPersona::OnConvertToBlendSpaceEvaluator()
|
|
{
|
|
FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
|
|
if (SelectedNodes.Num() > 0)
|
|
{
|
|
for (auto NodeIter = SelectedNodes.CreateIterator(); NodeIter; ++NodeIter)
|
|
{
|
|
UAnimGraphNode_BlendSpacePlayer* OldNode = Cast<UAnimGraphNode_BlendSpacePlayer>(*NodeIter);
|
|
|
|
// see if sequence player
|
|
if ( OldNode && OldNode->Node.BlendSpace )
|
|
{
|
|
//const FScopedTransaction Transaction( LOCTEXT("ConvertToSequenceEvaluator", "Convert to Single Frame Animation") );
|
|
|
|
// convert to sequence evaluator
|
|
UEdGraph* TargetGraph = OldNode->GetGraph();
|
|
// create new evaluator
|
|
FGraphNodeCreator<UAnimGraphNode_BlendSpaceEvaluator> NodeCreator(*TargetGraph);
|
|
UAnimGraphNode_BlendSpaceEvaluator* NewNode = NodeCreator.CreateNode();
|
|
NewNode->Node.BlendSpace = OldNode->Node.BlendSpace;
|
|
NodeCreator.Finalize();
|
|
|
|
// get default data from old node to new node
|
|
FEdGraphUtilities::CopyCommonState(OldNode, NewNode);
|
|
|
|
UEdGraphPin* OldPosePin = OldNode->FindPin(TEXT("X"));
|
|
UEdGraphPin* NewPosePin = NewNode->FindPin(TEXT("X"));
|
|
|
|
if (ensure(OldPosePin && NewPosePin))
|
|
{
|
|
NewPosePin->CopyPersistentDataFromOldPin(*OldPosePin);
|
|
}
|
|
|
|
OldPosePin = OldNode->FindPin(TEXT("Y"));
|
|
NewPosePin = NewNode->FindPin(TEXT("Y"));
|
|
|
|
if (ensure(OldPosePin && NewPosePin))
|
|
{
|
|
NewPosePin->CopyPersistentDataFromOldPin(*OldPosePin);
|
|
}
|
|
|
|
|
|
OldPosePin = OldNode->FindPin(TEXT("Pose"));
|
|
NewPosePin = NewNode->FindPin(TEXT("Pose"));
|
|
|
|
if (ensure(OldPosePin && NewPosePin))
|
|
{
|
|
NewPosePin->CopyPersistentDataFromOldPin(*OldPosePin);
|
|
}
|
|
|
|
// remove from selection and from graph
|
|
NodeIter.RemoveCurrent();
|
|
TargetGraph->RemoveNode(OldNode);
|
|
|
|
NewNode->Modify();
|
|
}
|
|
}
|
|
|
|
// @todo fixme: below code doesn't work
|
|
// because of SetAndCenterObject kicks in after new node is added
|
|
// will need to disable that first
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
// Update the graph so that the node will be refreshed
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetAnimBlueprint());
|
|
}
|
|
}
|
|
void FPersona::OnConvertToBlendSpacePlayer()
|
|
{
|
|
FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
if (SelectedNodes.Num() > 0)
|
|
{
|
|
for (auto NodeIter = SelectedNodes.CreateIterator(); NodeIter; ++NodeIter)
|
|
{
|
|
UAnimGraphNode_BlendSpaceEvaluator* OldNode = Cast<UAnimGraphNode_BlendSpaceEvaluator>(*NodeIter);
|
|
|
|
// see if sequence player
|
|
if ( OldNode && OldNode->Node.BlendSpace )
|
|
{
|
|
//const FScopedTransaction Transaction( LOCTEXT("ConvertToSequenceEvaluator", "Convert to Single Frame Animation") );
|
|
// convert to sequence player
|
|
UEdGraph* TargetGraph = OldNode->GetGraph();
|
|
// create new player
|
|
FGraphNodeCreator<UAnimGraphNode_BlendSpacePlayer> NodeCreator(*TargetGraph);
|
|
UAnimGraphNode_BlendSpacePlayer* NewNode = NodeCreator.CreateNode();
|
|
NewNode->Node.BlendSpace = OldNode->Node.BlendSpace;
|
|
NodeCreator.Finalize();
|
|
|
|
// get default data from old node to new node
|
|
FEdGraphUtilities::CopyCommonState(OldNode, NewNode);
|
|
|
|
UEdGraphPin* OldPosePin = OldNode->FindPin(TEXT("X"));
|
|
UEdGraphPin* NewPosePin = NewNode->FindPin(TEXT("X"));
|
|
|
|
if (ensure(OldPosePin && NewPosePin))
|
|
{
|
|
NewPosePin->CopyPersistentDataFromOldPin(*OldPosePin);
|
|
}
|
|
|
|
OldPosePin = OldNode->FindPin(TEXT("Y"));
|
|
NewPosePin = NewNode->FindPin(TEXT("Y"));
|
|
|
|
if (ensure(OldPosePin && NewPosePin))
|
|
{
|
|
NewPosePin->CopyPersistentDataFromOldPin(*OldPosePin);
|
|
}
|
|
|
|
|
|
OldPosePin = OldNode->FindPin(TEXT("Pose"));
|
|
NewPosePin = NewNode->FindPin(TEXT("Pose"));
|
|
|
|
if (ensure(OldPosePin && NewPosePin))
|
|
{
|
|
NewPosePin->CopyPersistentDataFromOldPin(*OldPosePin);
|
|
}
|
|
|
|
// remove from selection and from graph
|
|
NodeIter.RemoveCurrent();
|
|
TargetGraph->RemoveNode(OldNode);
|
|
|
|
NewNode->Modify();
|
|
}
|
|
}
|
|
|
|
// @todo fixme: below code doesn't work
|
|
// because of SetAndCenterObject kicks in after new node is added
|
|
// will need to disable that first
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
// Update the graph so that the node will be refreshed
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetAnimBlueprint());
|
|
}
|
|
}
|
|
|
|
void FPersona::OnOpenRelatedAsset()
|
|
{
|
|
FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
|
|
EToolkitMode::Type Mode = EToolkitMode::Standalone;
|
|
FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked<FPersonaModule>( "Persona" );
|
|
|
|
if (SelectedNodes.Num() > 0)
|
|
{
|
|
for (auto NodeIter = SelectedNodes.CreateIterator(); NodeIter; ++NodeIter)
|
|
{
|
|
if(UAnimGraphNode_Base* Node = Cast<UAnimGraphNode_Base>(*NodeIter))
|
|
{
|
|
UAnimationAsset* AnimAsset = Node->GetAnimationAsset();
|
|
|
|
if(AnimAsset)
|
|
{
|
|
PersonaModule.CreatePersona( Mode, TSharedPtr<IToolkitHost>(), AnimAsset->GetSkeleton(), NULL, AnimAsset, NULL );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FPersona::CanRemovePosePin() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void FPersona::RecompileAnimBlueprintIfDirty()
|
|
{
|
|
if (UBlueprint* Blueprint = GetBlueprintObj())
|
|
{
|
|
if (!Blueprint->IsUpToDate())
|
|
{
|
|
Compile();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPersona::Compile()
|
|
{
|
|
// Note if we were debugging the preview
|
|
UObject* CurrentDebugObject = GetBlueprintObj()->GetObjectBeingDebugged();
|
|
const bool bIsDebuggingPreview = (PreviewComponent != NULL) && PreviewComponent->IsAnimBlueprintInstanced() && (PreviewComponent->AnimScriptInstance == CurrentDebugObject);
|
|
|
|
if (PreviewComponent != NULL)
|
|
{
|
|
// Force close any asset editors that are using the AnimScriptInstance (such as the Property Matrix), the class will be garbage collected
|
|
FAssetEditorManager::Get().CloseOtherEditors(PreviewComponent->AnimScriptInstance, nullptr);
|
|
}
|
|
|
|
// Compile the blueprint
|
|
FBlueprintEditor::Compile();
|
|
|
|
if (PreviewComponent != NULL)
|
|
{
|
|
if (PreviewComponent->AnimScriptInstance == NULL)
|
|
{
|
|
// try reinitialize animation if it doesn't exist
|
|
PreviewComponent->InitAnim(true);
|
|
}
|
|
|
|
if (bIsDebuggingPreview)
|
|
{
|
|
GetBlueprintObj()->SetObjectBeingDebugged(PreviewComponent->AnimScriptInstance);
|
|
}
|
|
}
|
|
|
|
// calls PostCompile to copy proper values between anim nodes
|
|
if (Viewport.IsValid())
|
|
{
|
|
Viewport.Pin()->GetAnimationViewportClient()->PostCompile();
|
|
}
|
|
}
|
|
|
|
FName FPersona::GetToolkitContextFName() const
|
|
{
|
|
return GetToolkitFName();
|
|
}
|
|
|
|
FName FPersona::GetToolkitFName() const
|
|
{
|
|
return FName("Persona");
|
|
}
|
|
|
|
FText FPersona::GetBaseToolkitName() const
|
|
{
|
|
return LOCTEXT("AppLabel", "Persona");
|
|
}
|
|
|
|
FText FPersona::GetToolkitName() const
|
|
{
|
|
FFormatNamedArguments Args;
|
|
|
|
if (IsEditingSingleBlueprint())
|
|
{
|
|
const bool bDirtyState = GetBlueprintObj()->GetOutermost()->IsDirty();
|
|
|
|
Args.Add( TEXT("BlueprintName"), FText::FromString( GetBlueprintObj()->GetName() ) );
|
|
Args.Add( TEXT("DirtyState"), bDirtyState ? FText::FromString( TEXT( "*" ) ) : FText::GetEmpty() );
|
|
return FText::Format( LOCTEXT("KismetToolkitName_SingleBlueprint", "{BlueprintName}{DirtyState}"), Args );
|
|
}
|
|
else
|
|
{
|
|
check(TargetSkeleton != NULL);
|
|
|
|
const bool bDirtyState = TargetSkeleton->GetOutermost()->IsDirty();
|
|
|
|
Args.Add( TEXT("SkeletonName"), FText::FromString( TargetSkeleton->GetName() ) );
|
|
Args.Add( TEXT("DirtyState"), bDirtyState ? FText::FromString( TEXT( "*" ) ) : FText::GetEmpty() );
|
|
return FText::Format( LOCTEXT("KismetToolkitName_SingleSkeleton", "{SkeletonName}{DirtyState}"), Args );
|
|
}
|
|
}
|
|
|
|
FText FPersona::GetToolkitToolTipText() const
|
|
{
|
|
if (IsEditingSingleBlueprint())
|
|
{
|
|
return FAssetEditorToolkit::GetToolTipTextForObject(GetBlueprintObj());
|
|
}
|
|
else
|
|
{
|
|
check(TargetSkeleton != NULL);
|
|
return FAssetEditorToolkit::GetToolTipTextForObject(TargetSkeleton);
|
|
}
|
|
}
|
|
|
|
FString FPersona::GetWorldCentricTabPrefix() const
|
|
{
|
|
return NSLOCTEXT("Persona", "WorldCentricTabPrefix", "Persona ").ToString();
|
|
}
|
|
|
|
|
|
FLinearColor FPersona::GetWorldCentricTabColorScale() const
|
|
{
|
|
return FLinearColor( 0.5f, 0.25f, 0.35f, 0.5f );
|
|
}
|
|
|
|
void FPersona::SaveAsset_Execute()
|
|
{
|
|
if( ensure( GetEditingObjects().Num() > 0 ) )
|
|
{
|
|
const FName CurrentMode = GetCurrentMode();
|
|
|
|
TArray<UObject*> EditorObjects = GetEditorObjectsForMode(CurrentMode);
|
|
|
|
TArray< UPackage* > PackagesToSave;
|
|
for(auto Iter = EditorObjects.CreateIterator(); Iter; ++Iter)
|
|
{
|
|
PackagesToSave.Add((*Iter)->GetOutermost());
|
|
}
|
|
|
|
FEditorFileUtils::PromptForCheckoutAndSave( PackagesToSave, /*bCheckDirty=*/ false, /*bPromptToSave=*/ false );
|
|
}
|
|
}
|
|
|
|
void FPersona::SaveAnimationAssets_Execute()
|
|
{
|
|
// only save animation assets related to skeletons
|
|
TArray<UClass*> SaveClasses;
|
|
SaveClasses.Add(UAnimationAsset::StaticClass());
|
|
SaveClasses.Add(UAnimBlueprint::StaticClass());
|
|
SaveClasses.Add(USkeletalMesh::StaticClass());
|
|
SaveClasses.Add(USkeleton::StaticClass());
|
|
|
|
const bool bPromptUserToSave = true;
|
|
const bool bFastSave = false;
|
|
FEditorFileUtils::SaveDirtyContentPackages(SaveClasses, bPromptUserToSave, bFastSave);
|
|
}
|
|
|
|
bool FPersona::CanSaveAnimationAssets() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void FPersona::OnActiveTabChanged( TSharedPtr<SDockTab> PreviouslyActive, TSharedPtr<SDockTab> NewlyActivated )
|
|
{
|
|
if (!NewlyActivated.IsValid())
|
|
{
|
|
TArray<UObject*> ObjArray;
|
|
Inspector->ShowDetailsForObjects(ObjArray);
|
|
}
|
|
else if (SharedAnimDocumentTab.IsValid() && NewlyActivated.Get() == SharedAnimDocumentTab.Pin().Get())
|
|
{
|
|
if (UAnimationAsset* SharedAsset = Cast<UAnimationAsset>(SharedAnimAssetBeingEdited.Get()))
|
|
{
|
|
SetPreviewAnimationAsset(SharedAsset);
|
|
}
|
|
}
|
|
else if (SharedAnimDocumentTab.IsValid() && PreviouslyActive.Get() == SharedAnimDocumentTab.Pin().Get())
|
|
{
|
|
//@TODO: The flash focus makes it impossible to do this and still edit montages
|
|
//SetDetailObject(NULL);
|
|
}
|
|
else
|
|
{
|
|
FBlueprintEditor::OnActiveTabChanged(PreviouslyActive, NewlyActivated);
|
|
}
|
|
}
|
|
|
|
// Sets the current preview mesh
|
|
void FPersona::SetPreviewMesh(USkeletalMesh* NewPreviewMesh)
|
|
{
|
|
if(!TargetSkeleton->IsCompatibleMesh(NewPreviewMesh))
|
|
{
|
|
// Send a notification that the skeletal mesh cannot work with the skeleton
|
|
FFormatNamedArguments Args;
|
|
Args.Add( TEXT("PreviewMeshName"), FText::FromString( NewPreviewMesh->GetName() ) );
|
|
Args.Add( TEXT("TargetSkeletonName"), FText::FromString( TargetSkeleton->GetName() ) );
|
|
FNotificationInfo Info( FText::Format( LOCTEXT("SkeletalMeshIncompatible", "Skeletal Mesh \"{PreviewMeshName}\" incompatible with Skeleton \"{TargetSkeletonName}\"" ), Args ) );
|
|
Info.ExpireDuration = 3.0f;
|
|
Info.bUseLargeFont = false;
|
|
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
|
|
if ( Notification.IsValid() )
|
|
{
|
|
Notification->SetCompletionState( SNotificationItem::CS_Fail );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ValidatePreviewAttachedAssets(NewPreviewMesh);
|
|
if (NewPreviewMesh != PreviewComponent->SkeletalMesh)
|
|
{
|
|
if ( PreviewComponent->SkeletalMesh != NULL )
|
|
{
|
|
RemoveEditingObject(PreviewComponent->SkeletalMesh);
|
|
}
|
|
|
|
if ( NewPreviewMesh != NULL )
|
|
{
|
|
AddEditingObject(NewPreviewMesh);
|
|
}
|
|
|
|
// setting skeletalmesh unregister/re-register,
|
|
// so I have to save the animation settings and resetting after setting mesh
|
|
UAnimationAsset* AnimAssetToPlay = NULL;
|
|
float PlayPosition = 0.f;
|
|
bool bPlaying = false;
|
|
bool bNeedsToCopyAnimationData = PreviewComponent->AnimScriptInstance && PreviewComponent->AnimScriptInstance == PreviewComponent->PreviewInstance;
|
|
if(bNeedsToCopyAnimationData)
|
|
{
|
|
AnimAssetToPlay = PreviewComponent->PreviewInstance->CurrentAsset;
|
|
PlayPosition = PreviewComponent->PreviewInstance->CurrentTime;
|
|
bPlaying = PreviewComponent->PreviewInstance->bPlaying;
|
|
}
|
|
|
|
PreviewComponent->SetSkeletalMesh(NewPreviewMesh);
|
|
|
|
if(bNeedsToCopyAnimationData)
|
|
{
|
|
SetPreviewAnimationAsset(AnimAssetToPlay);
|
|
PreviewComponent->PreviewInstance->SetPosition(PlayPosition);
|
|
PreviewComponent->PreviewInstance->bPlaying = bPlaying;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PreviewComponent->InitAnim(true);
|
|
}
|
|
|
|
if (NewPreviewMesh != NULL)
|
|
{
|
|
PreviewScene.AddComponent(PreviewComponent, FTransform::Identity);
|
|
for (auto Iter = AdditionalMeshes.CreateIterator(); Iter; ++Iter)
|
|
{
|
|
PreviewScene.AddComponent((*Iter), FTransform::Identity);
|
|
}
|
|
|
|
// Set up the mesh for transactions
|
|
NewPreviewMesh->SetFlags(RF_Transactional);
|
|
|
|
AddPreviewAttachedObjects();
|
|
|
|
if(Viewport.IsValid())
|
|
{
|
|
Viewport.Pin()->SetPreviewComponent(PreviewComponent);
|
|
}
|
|
}
|
|
|
|
for(auto Iter = AdditionalMeshes.CreateIterator(); Iter; ++Iter)
|
|
{
|
|
(*Iter)->SetMasterPoseComponent(PreviewComponent);
|
|
(*Iter)->UpdateMasterBoneMap();
|
|
}
|
|
|
|
OnPreviewMeshChanged.Broadcast(NewPreviewMesh);
|
|
}
|
|
}
|
|
|
|
void FPersona::OnPropertyChanged(UObject* ObjectBeingModified, FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
// Re-initialize the preview when a skeletal control is being edited
|
|
//@TODO: Should we still do this?
|
|
}
|
|
|
|
void FPersona::RefreshPreviewInstanceTrackCurves()
|
|
{
|
|
// need to refresh the preview mesh
|
|
if(PreviewComponent->PreviewInstance)
|
|
{
|
|
PreviewComponent->PreviewInstance->RefreshCurveBoneControllers();
|
|
}
|
|
}
|
|
|
|
void FPersona::PostUndo(bool bSuccess)
|
|
{
|
|
DocumentManager->RefreshAllTabs();
|
|
|
|
FBlueprintEditor::PostUndo(bSuccess);
|
|
|
|
// PostUndo broadcast
|
|
OnPostUndo.Broadcast();
|
|
|
|
RefreshPreviewInstanceTrackCurves();
|
|
|
|
// clear up preview anim notify states
|
|
// animnotify states are saved in AnimInstance
|
|
// if those are undoed or redoed, they have to be
|
|
// cleared up, otherwise, they might have invalid data
|
|
ClearupPreviewMeshAnimNotifyStates();
|
|
}
|
|
|
|
void FPersona::ClearupPreviewMeshAnimNotifyStates()
|
|
{
|
|
USkeletalMeshComponent* PreviewMeshComponent = GetPreviewMeshComponent();
|
|
if ( PreviewMeshComponent )
|
|
{
|
|
UAnimInstance* AnimInstantace = PreviewMeshComponent->GetAnimInstance();
|
|
|
|
if (AnimInstantace)
|
|
{
|
|
// empty this because otherwise, it can have corrupted data
|
|
// this will cause state to be interrupted, but that is better
|
|
// than crashing
|
|
AnimInstantace->ActiveAnimNotifyState.Empty();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPersona::OnSkeletonHierarchyChanged()
|
|
{
|
|
OnSkeletonTreeChanged.Broadcast();
|
|
}
|
|
|
|
bool FPersona::IsInAScriptingMode() const
|
|
{
|
|
return IsModeCurrent(FPersonaModes::AnimBlueprintEditMode);
|
|
}
|
|
|
|
void FPersona::GetCustomDebugObjects(TArray<FCustomDebugObject>& DebugList) const
|
|
{
|
|
if (PreviewComponent->IsAnimBlueprintInstanced())
|
|
{
|
|
new (DebugList) FCustomDebugObject(PreviewComponent->AnimScriptInstance, LOCTEXT("PreviewObjectLabel", "Preview Instance").ToString());
|
|
}
|
|
}
|
|
|
|
void FPersona::CreateDefaultTabContents(const TArray<UBlueprint*>& InBlueprints)
|
|
{
|
|
FBlueprintEditor::CreateDefaultTabContents(InBlueprints);
|
|
|
|
PreviewEditor = SNew(SPersonaPreviewPropertyEditor, SharedThis(this));
|
|
}
|
|
|
|
FGraphAppearanceInfo FPersona::GetGraphAppearance(UEdGraph* InGraph) const
|
|
{
|
|
FGraphAppearanceInfo AppearanceInfo = FBlueprintEditor::GetGraphAppearance(InGraph);
|
|
|
|
if ( GetBlueprintObj()->IsA(UAnimBlueprint::StaticClass()) )
|
|
{
|
|
AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_Animation", "ANIMATION");
|
|
}
|
|
|
|
return AppearanceInfo;
|
|
}
|
|
|
|
void FPersona::FocusWindow(UObject* ObjectToFocusOn)
|
|
{
|
|
FBlueprintEditor::FocusWindow(ObjectToFocusOn);
|
|
if (ObjectToFocusOn != NULL)
|
|
{
|
|
// we should be already in our desired window, now just update mode
|
|
if (ObjectToFocusOn->IsA(UAnimBlueprint::StaticClass()))
|
|
{
|
|
SetCurrentMode(FPersonaModes::AnimBlueprintEditMode);
|
|
}
|
|
else if (UAnimationAsset* AnimationAssetToFocus = Cast<UAnimationAsset>(ObjectToFocusOn))
|
|
{
|
|
SetCurrentMode(FPersonaModes::AnimationEditMode);
|
|
OpenNewDocumentTab(AnimationAssetToFocus);
|
|
}
|
|
else if (USkeletalMesh* SkeletalMeshToFocus = Cast<USkeletalMesh>(ObjectToFocusOn))
|
|
{
|
|
SetCurrentMode(FPersonaModes::MeshEditMode);
|
|
SetPreviewMesh(SkeletalMeshToFocus);
|
|
}
|
|
else if (ObjectToFocusOn->IsA(USkeleton::StaticClass()))
|
|
{
|
|
SetCurrentMode(FPersonaModes::SkeletonDisplayMode);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPersona::ClearSelectedBones()
|
|
{
|
|
if (UDebugSkelMeshComponent* Preview = GetPreviewMeshComponent())
|
|
{
|
|
Preview->BonesOfInterest.Empty();
|
|
}
|
|
}
|
|
|
|
void FPersona::SetSelectedBone(USkeleton* InTargetSkeleton, const FName& BoneName, bool bRebroadcast /* = true */ )
|
|
{
|
|
const int32 BoneIndex = InTargetSkeleton->GetReferenceSkeleton().FindBoneIndex(BoneName);
|
|
if (BoneIndex != INDEX_NONE)
|
|
{
|
|
if (UDebugSkelMeshComponent* Preview = GetPreviewMeshComponent())
|
|
{
|
|
Preview->BonesOfInterest.Empty();
|
|
ClearSelectedSocket();
|
|
|
|
// Add in bone of interest only if we have a preview instance set-up
|
|
if (Preview->PreviewInstance != NULL)
|
|
{
|
|
// need to get mesh bone base since BonesOfInterest is saved in SkeletalMeshComponent
|
|
// and it is used by renderer. It is not Skeleton base
|
|
const int32 MeshBoneIndex = Preview->GetBoneIndex(BoneName);
|
|
|
|
if (MeshBoneIndex != INDEX_NONE)
|
|
{
|
|
Preview->BonesOfInterest.Add(MeshBoneIndex);
|
|
}
|
|
|
|
if ( bRebroadcast )
|
|
{
|
|
// Broadcast that a bone has been selected
|
|
OnBoneSelected.Broadcast( BoneName );
|
|
}
|
|
|
|
if( Viewport.IsValid() )
|
|
{
|
|
Viewport.Pin()->GetLevelViewportClient().Invalidate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPersona::SetSelectedSocket( const FSelectedSocketInfo& SocketInfo, bool bRebroadcast /* = true */ )
|
|
{
|
|
if ( UDebugSkelMeshComponent* Preview = GetPreviewMeshComponent() )
|
|
{
|
|
Preview->SocketsOfInterest.Empty();
|
|
Preview->BonesOfInterest.Empty();
|
|
|
|
Preview->SocketsOfInterest.Add( SocketInfo );
|
|
|
|
if ( bRebroadcast )
|
|
{
|
|
OnSocketSelected.Broadcast( SocketInfo );
|
|
}
|
|
|
|
// This populates the details panel with the information for the socket
|
|
SetDetailObject( SocketInfo.Socket );
|
|
|
|
if( Viewport.IsValid() )
|
|
{
|
|
Viewport.Pin()->GetLevelViewportClient().Invalidate();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPersona::ClearSelectedSocket()
|
|
{
|
|
if (UDebugSkelMeshComponent* Preview = GetPreviewMeshComponent())
|
|
{
|
|
Preview->SocketsOfInterest.Empty();
|
|
Preview->BonesOfInterest.Empty();
|
|
|
|
SetDetailObject( NULL );
|
|
}
|
|
}
|
|
|
|
void FPersona::ClearSelectedWindActor()
|
|
{
|
|
if (Viewport.IsValid())
|
|
{
|
|
Viewport.Pin()->GetAnimationViewportClient()->ClearSelectedWindActor();
|
|
}
|
|
}
|
|
|
|
void FPersona::ClearSelectedAnimGraphNode()
|
|
{
|
|
if(Viewport.IsValid())
|
|
{
|
|
Viewport.Pin()->GetAnimationViewportClient()->ClearSelectedAnimGraphNode();
|
|
}
|
|
}
|
|
|
|
void FPersona::RenameSocket( USkeletalMeshSocket* Socket, const FName& NewSocketName )
|
|
{
|
|
Socket->SocketName = NewSocketName;
|
|
|
|
// Broadcast that the skeleton tree has changed due to the new name
|
|
OnSkeletonTreeChanged.Broadcast();
|
|
|
|
}
|
|
|
|
void FPersona::ChangeSocketParent( USkeletalMeshSocket* Socket, const FName& NewSocketParent )
|
|
{
|
|
// Update the bone name.
|
|
Socket->BoneName = NewSocketParent;
|
|
|
|
// Broadcast that the skeleton tree has changed due to the new parent
|
|
OnSkeletonTreeChanged.Broadcast();
|
|
}
|
|
|
|
void FPersona::DuplicateAndSelectSocket( const FSelectedSocketInfo& SocketInfoToDuplicate, const FName& NewParentBoneName /*= FName()*/ )
|
|
{
|
|
check( SocketInfoToDuplicate.Socket );
|
|
|
|
USkeletalMesh* Mesh = GetMesh();
|
|
|
|
const FScopedTransaction Transaction( LOCTEXT( "CopySocket", "Copy Socket" ) );
|
|
|
|
USkeletalMeshSocket* NewSocket;
|
|
|
|
bool bModifiedSkeleton = false;
|
|
|
|
if ( SocketInfoToDuplicate.bSocketIsOnSkeleton )
|
|
{
|
|
TargetSkeleton->Modify();
|
|
bModifiedSkeleton = true;
|
|
|
|
NewSocket = NewObject<USkeletalMeshSocket>(TargetSkeleton);
|
|
check(NewSocket);
|
|
}
|
|
else if ( Mesh )
|
|
{
|
|
Mesh->Modify();
|
|
|
|
NewSocket = NewObject<USkeletalMeshSocket>(Mesh);
|
|
check(NewSocket);
|
|
}
|
|
else
|
|
{
|
|
// Original socket was on the mesh, but we have no mesh. Huh?
|
|
check( 0 );
|
|
return;
|
|
}
|
|
|
|
NewSocket->SocketName = GenerateUniqueSocketName( SocketInfoToDuplicate.Socket->SocketName );
|
|
NewSocket->BoneName = NewParentBoneName != "" ? NewParentBoneName : SocketInfoToDuplicate.Socket->BoneName;
|
|
NewSocket->RelativeLocation = SocketInfoToDuplicate.Socket->RelativeLocation;
|
|
NewSocket->RelativeRotation = SocketInfoToDuplicate.Socket->RelativeRotation;
|
|
NewSocket->RelativeScale = SocketInfoToDuplicate.Socket->RelativeScale;
|
|
|
|
if ( SocketInfoToDuplicate.bSocketIsOnSkeleton )
|
|
{
|
|
TargetSkeleton->Sockets.Add( NewSocket );
|
|
}
|
|
else if ( Mesh )
|
|
{
|
|
Mesh->GetMeshOnlySocketList().Add( NewSocket );
|
|
}
|
|
|
|
// Duplicated attached assets
|
|
int32 NumExistingAttachedObjects = TargetSkeleton->PreviewAttachedAssetContainer.Num();
|
|
for(int AttachedObjectIndex = 0; AttachedObjectIndex < NumExistingAttachedObjects; ++AttachedObjectIndex)
|
|
{
|
|
FPreviewAttachedObjectPair& Pair = TargetSkeleton->PreviewAttachedAssetContainer[AttachedObjectIndex];
|
|
if(Pair.AttachedTo == SocketInfoToDuplicate.Socket->SocketName)
|
|
{
|
|
if(!bModifiedSkeleton)
|
|
{
|
|
bModifiedSkeleton = true;
|
|
TargetSkeleton->Modify();
|
|
}
|
|
FPreviewAttachedObjectPair NewPair = Pair;
|
|
NewPair.AttachedTo = NewSocket->SocketName;
|
|
AttachObjectToPreviewComponent( NewPair.GetAttachedObject(), NewPair.AttachedTo, &TargetSkeleton->PreviewAttachedAssetContainer );
|
|
}
|
|
}
|
|
|
|
// Broadcast that the skeleton tree has changed due to the new socket
|
|
OnSkeletonTreeChanged.Broadcast();
|
|
|
|
SetSelectedSocket( FSelectedSocketInfo( NewSocket, SocketInfoToDuplicate.bSocketIsOnSkeleton ) );
|
|
}
|
|
|
|
FName FPersona::GenerateUniqueSocketName( FName InName )
|
|
{
|
|
USkeletalMesh* Mesh = GetMesh();
|
|
|
|
int32 NewNumber = InName.GetNumber();
|
|
|
|
// Increment NewNumber until we have a unique name (potential infinite loop if *all* int32 values are used!)
|
|
while ( DoesSocketAlreadyExist( NULL, FText::FromName( FName( InName, NewNumber ) ), TargetSkeleton->Sockets ) ||
|
|
( Mesh ? DoesSocketAlreadyExist( NULL, FText::FromName( FName( InName, NewNumber ) ), Mesh->GetMeshOnlySocketList() ) : false ) )
|
|
{
|
|
++NewNumber;
|
|
}
|
|
|
|
return FName( InName, NewNumber );
|
|
}
|
|
|
|
bool FPersona::DoesSocketAlreadyExist( const USkeletalMeshSocket* InSocket, const FText& InSocketName, const TArray< USkeletalMeshSocket* >& InSocketArray ) const
|
|
{
|
|
for ( auto SocketIt = InSocketArray.CreateConstIterator(); SocketIt; ++SocketIt )
|
|
{
|
|
USkeletalMeshSocket* Socket = *( SocketIt );
|
|
|
|
if ( InSocket != Socket && Socket->SocketName.ToString() == InSocketName.ToString() )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
USkeletalMeshComponent* FPersona::CreateNewSkeletalMeshComponent()
|
|
{
|
|
USkeletalMeshComponent* NewComp = NewObject<USkeletalMeshComponent>();
|
|
AdditionalMeshes.Add(NewComp);
|
|
NewComp->SetMasterPoseComponent(GetPreviewMeshComponent());
|
|
return NewComp;
|
|
}
|
|
|
|
void FPersona::RemoveAdditionalMesh(USkeletalMeshComponent* MeshToRemove)
|
|
{
|
|
PreviewScene.RemoveComponent(MeshToRemove);
|
|
AdditionalMeshes.Remove(MeshToRemove);
|
|
}
|
|
|
|
void FPersona::ClearAllAdditionalMeshes()
|
|
{
|
|
for(auto Iter = AdditionalMeshes.CreateIterator(); Iter; ++Iter)
|
|
{
|
|
PreviewScene.RemoveComponent( *Iter );
|
|
}
|
|
|
|
AdditionalMeshes.Empty();
|
|
}
|
|
|
|
void FPersona::AddPreviewAttachedObjects()
|
|
{
|
|
// Load up mesh attachments...
|
|
USkeletalMesh* Mesh = GetMesh();
|
|
|
|
if ( Mesh )
|
|
{
|
|
for(int32 i = 0; i < Mesh->PreviewAttachedAssetContainer.Num(); i++)
|
|
{
|
|
FPreviewAttachedObjectPair& PreviewAttachedObject = Mesh->PreviewAttachedAssetContainer[i];
|
|
|
|
AttachObjectToPreviewComponent(PreviewAttachedObject.GetAttachedObject(), PreviewAttachedObject.AttachedTo);
|
|
}
|
|
}
|
|
|
|
// ...and then skeleton attachments
|
|
for(int32 i = 0; i < TargetSkeleton->PreviewAttachedAssetContainer.Num(); i++)
|
|
{
|
|
FPreviewAttachedObjectPair& PreviewAttachedObject = TargetSkeleton->PreviewAttachedAssetContainer[i];
|
|
|
|
AttachObjectToPreviewComponent(PreviewAttachedObject.GetAttachedObject(), PreviewAttachedObject.AttachedTo);
|
|
}
|
|
}
|
|
|
|
bool FPersona::AttachObjectToPreviewComponent( UObject* Object, FName AttachTo, FPreviewAssetAttachContainer* PreviewAssetContainer /* = NULL */ )
|
|
{
|
|
if(GetComponentForAttachedObject(Object, AttachTo))
|
|
{
|
|
return false; // Already have this asset attached at this location
|
|
}
|
|
|
|
TSubclassOf<UActorComponent> ComponentClass = FComponentAssetBrokerage::GetPrimaryComponentForAsset(Object->GetClass());
|
|
if(PreviewComponent && *ComponentClass && ComponentClass->IsChildOf(USceneComponent::StaticClass()))
|
|
{
|
|
if ( PreviewAssetContainer )
|
|
{
|
|
// Set up the attached object for transactions
|
|
Object->SetFlags(RF_Transactional);
|
|
PreviewAssetContainer->AddAttachedObject( Object, AttachTo );
|
|
}
|
|
|
|
//set up world info for undo
|
|
AWorldSettings* WorldSettings = PreviewScene.GetWorld()->GetWorldSettings();
|
|
WorldSettings->SetFlags(RF_Transactional);
|
|
WorldSettings->Modify();
|
|
|
|
USceneComponent* SceneComponent = NewObject<USceneComponent>(WorldSettings, ComponentClass, NAME_None, RF_Transactional);
|
|
|
|
FComponentAssetBrokerage::AssignAssetToComponent(SceneComponent, Object);
|
|
|
|
if(UParticleSystemComponent* NewPSysComp = Cast<UParticleSystemComponent>(SceneComponent))
|
|
{
|
|
//maybe this should happen in FComponentAssetBrokerage::AssignAssetToComponent?
|
|
NewPSysComp->SetTickGroup(TG_PostUpdateWork);
|
|
}
|
|
|
|
//set up preview component for undo
|
|
PreviewComponent->SetFlags(RF_Transactional);
|
|
PreviewComponent->Modify();
|
|
|
|
// Attach component to the preview component
|
|
SceneComponent->AttachTo(PreviewComponent, AttachTo);
|
|
SceneComponent->RegisterComponent();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FPersona::RemoveAttachedObjectFromPreviewComponent(UObject* Object, FName AttachedTo)
|
|
{
|
|
// clean up components
|
|
if (PreviewComponent)
|
|
{
|
|
AWorldSettings* WorldSettings = PreviewScene.GetWorld()->GetWorldSettings();
|
|
WorldSettings->SetFlags(RF_Transactional);
|
|
WorldSettings->Modify();
|
|
|
|
//set up preview component for undo
|
|
PreviewComponent->SetFlags(RF_Transactional);
|
|
PreviewComponent->Modify();
|
|
|
|
for (int32 I=PreviewComponent->AttachChildren.Num()-1; I >= 0; --I) // Iterate backwards because Cleancomponent will remove from AttachChildren
|
|
{
|
|
USceneComponent* ChildComponent = PreviewComponent->AttachChildren[I];
|
|
UObject* Asset = FComponentAssetBrokerage::GetAssetFromComponent(ChildComponent);
|
|
|
|
if( Asset == Object && ChildComponent->AttachSocketName == AttachedTo)
|
|
{
|
|
// PreviewComponet will be cleaned up by PreviewScene,
|
|
// but if anything is attached, it won't be cleaned up,
|
|
// so we'll need to clean them up manually
|
|
CleanupComponent(PreviewComponent->AttachChildren[I]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
USceneComponent* FPersona::GetComponentForAttachedObject(UObject* Object, FName AttachedTo)
|
|
{
|
|
if (PreviewComponent)
|
|
{
|
|
for (int32 I=0; I < PreviewComponent->AttachChildren.Num(); ++I)
|
|
{
|
|
USceneComponent* ChildComponent = PreviewComponent->AttachChildren[I];
|
|
UObject* Asset = FComponentAssetBrokerage::GetAssetFromComponent(ChildComponent);
|
|
|
|
if( Asset == Object && ChildComponent->AttachSocketName == AttachedTo)
|
|
{
|
|
return ChildComponent;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void FPersona::RemoveAttachedComponent( bool bRemovePreviewAttached /* = true */ )
|
|
{
|
|
TMap<UObject*, TArray<FName>> PreviewAttachedObjects;
|
|
|
|
if( !bRemovePreviewAttached )
|
|
{
|
|
for(auto Iter = TargetSkeleton->PreviewAttachedAssetContainer.CreateConstIterator(); Iter; ++Iter)
|
|
{
|
|
PreviewAttachedObjects.FindOrAdd(Iter->GetAttachedObject()).Add(Iter->AttachedTo);
|
|
}
|
|
|
|
if ( USkeletalMesh* PreviewMesh = GetMesh() )
|
|
{
|
|
for(auto Iter = PreviewMesh->PreviewAttachedAssetContainer.CreateConstIterator(); Iter; ++Iter)
|
|
{
|
|
PreviewAttachedObjects.FindOrAdd(Iter->GetAttachedObject()).Add(Iter->AttachedTo);
|
|
}
|
|
}
|
|
}
|
|
|
|
// clean up components
|
|
if (PreviewComponent)
|
|
{
|
|
for (int32 I=PreviewComponent->AttachChildren.Num()-1; I >= 0; --I) // Iterate backwards because Cleancomponent will remove from AttachChildren
|
|
{
|
|
USceneComponent* ChildComponent = PreviewComponent->AttachChildren[I];
|
|
UObject* Asset = FComponentAssetBrokerage::GetAssetFromComponent(ChildComponent);
|
|
|
|
bool bRemove = true;
|
|
|
|
//are we supposed to leave assets that came from the skeleton
|
|
if( !bRemovePreviewAttached )
|
|
{
|
|
//could this asset have come from the skeleton
|
|
if(PreviewAttachedObjects.Contains(Asset))
|
|
{
|
|
if(PreviewAttachedObjects.Find(Asset)->Contains(ChildComponent->AttachSocketName))
|
|
{
|
|
bRemove = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(bRemove)
|
|
{
|
|
// PreviewComponet will be cleaned up by PreviewScene,
|
|
// but if anything is attached, it won't be cleaned up,
|
|
// so we'll need to clean them up manually
|
|
CleanupComponent(PreviewComponent->AttachChildren[I]);
|
|
}
|
|
}
|
|
|
|
if( bRemovePreviewAttached )
|
|
{
|
|
PreviewComponent->AttachChildren.Empty();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPersona::CleanupComponent(USceneComponent* Component)
|
|
{
|
|
if (Component)
|
|
{
|
|
for (int32 I=0; I<Component->AttachChildren.Num(); ++I)
|
|
{
|
|
CleanupComponent(Component->AttachChildren[I]);
|
|
}
|
|
|
|
Component->AttachChildren.Empty();
|
|
Component->DestroyComponent();
|
|
}
|
|
}
|
|
|
|
void FPersona::DeselectAll()
|
|
{
|
|
ClearSelectedBones();
|
|
ClearSelectedSocket();
|
|
ClearSelectedWindActor();
|
|
ClearSelectedAnimGraphNode();
|
|
|
|
OnAllDeselected.Broadcast();
|
|
}
|
|
|
|
FReply FPersona::OnClickEditMesh()
|
|
{
|
|
USkeletalMesh* PreviewMesh = TargetSkeleton->GetPreviewMesh();
|
|
if(PreviewMesh)
|
|
{
|
|
UpdateSelectionDetails(PreviewMesh, FText::FromString(PreviewMesh->GetName()));
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
|
|
|
|
void FPersona::PostRedo(bool bSuccess)
|
|
{
|
|
DocumentManager->RefreshAllTabs();
|
|
|
|
FBlueprintEditor::PostRedo(bSuccess);
|
|
|
|
// PostUndo broadcast, OnPostRedo
|
|
OnPostUndo.Broadcast();
|
|
|
|
// clear up preview anim notify states
|
|
// animnotify states are saved in AnimInstance
|
|
// if those are undoed or redoed, they have to be
|
|
// cleared up, otherwise, they might have invalid data
|
|
ClearupPreviewMeshAnimNotifyStates();
|
|
}
|
|
|
|
TArray<UObject*> FPersona::GetEditorObjectsForMode(FName Mode) const
|
|
{
|
|
if( Mode == FPersonaModes::AnimationEditMode )
|
|
{
|
|
return GetEditorObjectsOfClass( GetEditingObjects(), UAnimationAsset::StaticClass() );
|
|
}
|
|
else if( Mode == FPersonaModes::AnimBlueprintEditMode )
|
|
{
|
|
return GetEditorObjectsOfClass( GetEditingObjects(), UAnimBlueprint::StaticClass() );
|
|
}
|
|
else if( Mode == FPersonaModes::MeshEditMode )
|
|
{
|
|
return GetEditorObjectsOfClass( GetEditingObjects(), USkeletalMesh::StaticClass() );
|
|
}
|
|
else if( Mode == FPersonaModes::PhysicsEditMode )
|
|
{
|
|
return GetEditorObjectsOfClass( GetEditingObjects(), UPhysicsAsset::StaticClass() );
|
|
}
|
|
else if( Mode == FPersonaModes::SkeletonDisplayMode )
|
|
{
|
|
return GetEditorObjectsOfClass( GetEditingObjects(), USkeleton::StaticClass() );
|
|
}
|
|
return TArray<UObject*>();
|
|
}
|
|
|
|
const FSlateBrush* FPersona::GetDirtyImageForMode(FName Mode) const
|
|
{
|
|
TArray<UObject*> EditorObjects = GetEditorObjectsForMode(Mode);
|
|
for(auto Iter = EditorObjects.CreateIterator(); Iter; ++Iter)
|
|
{
|
|
if(UPackage* Package = (*Iter)->GetOutermost())
|
|
{
|
|
if(Package->IsDirty())
|
|
{
|
|
return AssetDirtyBrush;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void FPersona::FindInContentBrowser_Execute()
|
|
{
|
|
FName CurrentMode = GetCurrentMode();
|
|
TArray<UObject*> ObjectsToSyncTo = GetEditorObjectsForMode(CurrentMode);
|
|
GEditor->SyncBrowserToObjects( ObjectsToSyncTo );
|
|
}
|
|
|
|
void FPersona::OnCommandGenericDelete()
|
|
{
|
|
OnGenericDelete.Broadcast();
|
|
}
|
|
|
|
void FPersona::UndoAction()
|
|
{
|
|
GEditor->UndoTransaction();
|
|
}
|
|
|
|
void FPersona::RedoAction()
|
|
{
|
|
GEditor->RedoTransaction();
|
|
}
|
|
|
|
FSlateIcon FPersona::GetRecordStatusImage() const
|
|
{
|
|
if (Recorder.InRecording())
|
|
{
|
|
return FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.StopRecordAnimation");
|
|
}
|
|
|
|
return FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.StartRecordAnimation");
|
|
}
|
|
|
|
FText FPersona::GetRecordMenuLabel() const
|
|
{
|
|
if(Recorder.InRecording())
|
|
{
|
|
return LOCTEXT("Persona_StopRecordAnimationMenuLabel", "Stop Record Animation");
|
|
}
|
|
|
|
return LOCTEXT("Persona_StartRecordAnimationLabel", "Start Record Animation");
|
|
}
|
|
|
|
FText FPersona::GetRecordStatusLabel() const
|
|
{
|
|
if (Recorder.InRecording())
|
|
{
|
|
return LOCTEXT("Persona_StopRecordAnimationLabel", "Stop");
|
|
}
|
|
|
|
return LOCTEXT("Persona_StartRecordAnimationLabel", "Record");
|
|
}
|
|
|
|
FText FPersona::GetRecordStatusTooltip() const
|
|
{
|
|
if (Recorder.InRecording())
|
|
{
|
|
return LOCTEXT("Persona_StopRecordAnimation", "Stop Record Animation");
|
|
}
|
|
|
|
return LOCTEXT("Persona_StartRecordAnimation", "Start Record Animation");
|
|
}
|
|
|
|
void FPersona::RecordAnimation()
|
|
{
|
|
if (!PreviewComponent || !PreviewComponent->SkeletalMesh)
|
|
{
|
|
// error
|
|
return;
|
|
}
|
|
|
|
if (Recorder.InRecording())
|
|
{
|
|
Recorder.StopRecord(true);
|
|
}
|
|
else
|
|
{
|
|
Recorder.TriggerRecordAnimation(PreviewComponent);
|
|
}
|
|
}
|
|
|
|
void FPersona::OnSetKeyCompleted()
|
|
{
|
|
OnTrackCurvesChanged.Broadcast();
|
|
}
|
|
|
|
bool FPersona::CanSetKey() const
|
|
{
|
|
return ( HasValidAnimationSequencePlaying() && PreviewComponent->BonesOfInterest.Num() > 0);
|
|
}
|
|
|
|
void FPersona::OnSetKey()
|
|
{
|
|
UAnimSequence* AnimSequence = Cast<UAnimSequence> (GetAnimationAssetBeingEdited());
|
|
if (AnimSequence)
|
|
{
|
|
UDebugSkelMeshComponent* Component = GetPreviewMeshComponent();
|
|
Component->PreviewInstance->SetKey(FSimpleDelegate::CreateSP(this, &FPersona::OnSetKeyCompleted));
|
|
}
|
|
}
|
|
|
|
bool FPersona::CanBakeAnimation() const
|
|
{
|
|
UAnimSequence* AnimSequence = Cast<UAnimSequence> (GetAnimationAssetBeingEdited());
|
|
// ideally would be great if we can only show if something changed
|
|
return (AnimSequence && AnimSequence->DoesNeedRebake());
|
|
}
|
|
|
|
void FPersona::OnBakeAnimation()
|
|
{
|
|
UAnimSequence* AnimSequence = Cast<UAnimSequence>(GetAnimationAssetBeingEdited());
|
|
if(AnimSequence)
|
|
{
|
|
UDebugSkelMeshComponent* Component = GetPreviewMeshComponent();
|
|
// now bake
|
|
Component->PreviewInstance->BakeAnimation();
|
|
}
|
|
}
|
|
|
|
void FPersona::OnApplyCompression()
|
|
{
|
|
UAnimSequence* AnimSequence = Cast<UAnimSequence> (GetPreviewAnimationAsset());
|
|
|
|
if (AnimSequence)
|
|
{
|
|
TArray<TWeakObjectPtr<UAnimSequence>> AnimSequences;
|
|
AnimSequences.Add(AnimSequence);
|
|
ApplyCompression(AnimSequences);
|
|
}
|
|
}
|
|
|
|
void FPersona::OnExportToFBX()
|
|
{
|
|
UAnimSequence* AnimSequence = Cast<UAnimSequence>(GetPreviewAnimationAsset());
|
|
|
|
if(AnimSequence)
|
|
{
|
|
TArray<TWeakObjectPtr<UAnimSequence>> AnimSequences;
|
|
AnimSequences.Add(AnimSequence);
|
|
ExportToFBX(AnimSequences);
|
|
}
|
|
}
|
|
|
|
void FPersona::OnAddLoopingInterpolation()
|
|
{
|
|
UAnimSequence* AnimSequence = Cast<UAnimSequence>(GetPreviewAnimationAsset());
|
|
|
|
if(AnimSequence)
|
|
{
|
|
TArray<TWeakObjectPtr<UAnimSequence>> AnimSequences;
|
|
AnimSequences.Add(AnimSequence);
|
|
AddLoopingInterpolation(AnimSequences);
|
|
}
|
|
}
|
|
|
|
void FPersona::ApplyCompression(TArray<TWeakObjectPtr<UAnimSequence>>& AnimSequences)
|
|
{
|
|
FDlgAnimCompression AnimCompressionDialog(AnimSequences);
|
|
AnimCompressionDialog.ShowModal();
|
|
}
|
|
|
|
void FPersona::ExportToFBX(TArray<TWeakObjectPtr<UAnimSequence>>& AnimSequences)
|
|
{
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
|
|
|
if(DesktopPlatform)
|
|
{
|
|
USkeletalMesh* PreviewMesh = GetPreviewMeshComponent()->SkeletalMesh;
|
|
if(PreviewMesh == NULL)
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ExportToFBXExportMissingPreviewMesh", "ERROR: Missing preview mesh"));
|
|
return;
|
|
}
|
|
|
|
if(AnimSequences.Num() > 0)
|
|
{
|
|
//Get parent window for dialogs
|
|
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
|
|
const TSharedPtr<SWindow>& MainFrameParentWindow = MainFrameModule.GetParentWindow();
|
|
|
|
void* ParentWindowWindowHandle = NULL;
|
|
|
|
if(MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid())
|
|
{
|
|
ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle();
|
|
}
|
|
|
|
//Cache anim file names
|
|
TArray<FString> AnimFileNames;
|
|
for(auto Iter = AnimSequences.CreateIterator(); Iter; ++Iter)
|
|
{
|
|
AnimFileNames.Add(Iter->Get()->GetName() + TEXT(".fbx"));
|
|
}
|
|
|
|
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
|
|
FString DestinationFolder;
|
|
|
|
const FString Title = LOCTEXT("ExportFBXsToFolderTitle", "Choose a destination folder for the FBX file(s)").ToString();
|
|
|
|
if(AnimSequences.Num() > 1)
|
|
{
|
|
bool bFolderValid = false;
|
|
// More than one file, just ask for directory
|
|
while(!bFolderValid)
|
|
{
|
|
const bool bFolderSelected = DesktopPlatform->OpenDirectoryDialog(
|
|
ParentWindowWindowHandle,
|
|
Title,
|
|
FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_EXPORT),
|
|
DestinationFolder
|
|
);
|
|
|
|
if(!bFolderSelected)
|
|
{
|
|
// User canceled, return
|
|
return;
|
|
}
|
|
|
|
FEditorDirectories::Get().SetLastDirectory(ELastDirectory::GENERIC_EXPORT, DestinationFolder);
|
|
FPaths::NormalizeFilename(DestinationFolder);
|
|
|
|
//Check whether there are any fbx filename conflicts in this folder
|
|
for(auto Iter = AnimFileNames.CreateIterator(); Iter; ++Iter)
|
|
{
|
|
FString& AnimFileName = *Iter;
|
|
FString FullPath = DestinationFolder + "/" + AnimFileName;
|
|
|
|
bFolderValid = true;
|
|
if(PlatformFile.FileExists(*FullPath))
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("DestinationFolder"), FText::FromString(DestinationFolder));
|
|
const FText DialogMessage = FText::Format(LOCTEXT("ExportToFBXFileOverwriteMessage", "Exporting to '{DestinationFolder}' will cause one or more existing FBX files to be overwritten. Would you like to continue?"), Args);
|
|
EAppReturnType::Type DialogReturn = FMessageDialog::Open(EAppMsgType::YesNo, DialogMessage);
|
|
bFolderValid = (EAppReturnType::Yes == DialogReturn);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// One file only, ask for full filename.
|
|
// Can set bFolderValid from the SaveFileDialog call as the window will handle
|
|
// duplicate files for us.
|
|
TArray<FString> TempDestinationNames;
|
|
bool bSave = DesktopPlatform->SaveFileDialog(
|
|
ParentWindowWindowHandle,
|
|
Title,
|
|
FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_EXPORT),
|
|
AnimSequences[0]->GetName(),
|
|
"FBX |*.fbx",
|
|
EFileDialogFlags::None,
|
|
TempDestinationNames
|
|
);
|
|
|
|
if(!bSave)
|
|
{
|
|
// Canceled
|
|
return;
|
|
}
|
|
check(TempDestinationNames.Num() == 1);
|
|
check(AnimFileNames.Num() == 1);
|
|
|
|
DestinationFolder = FPaths::GetPath(TempDestinationNames[0]);
|
|
AnimFileNames[0] = FPaths::GetCleanFilename(TempDestinationNames[0]);
|
|
|
|
FEditorDirectories::Get().SetLastDirectory(ELastDirectory::GENERIC_EXPORT, DestinationFolder);
|
|
}
|
|
|
|
EAppReturnType::Type DialogReturn = FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("ExportToFBXExportPreviewMeshToo", "Would you like to export the current skeletal mesh with the animation(s)?"));
|
|
bool bSaveSkeletalMesh = EAppReturnType::Yes == DialogReturn;
|
|
|
|
const bool bShowCancel = false;
|
|
const bool bShowProgressDialog = true;
|
|
GWarn->BeginSlowTask(LOCTEXT("ExportToFBXProgress", "Exporting Animation(s) to FBX"), bShowProgressDialog, bShowCancel);
|
|
|
|
// make sure to use PreviewMesh, when export inside of Persona
|
|
const int32 NumberOfAnimations = AnimSequences.Num();
|
|
for(int32 i = 0; i < NumberOfAnimations; ++i)
|
|
{
|
|
GWarn->UpdateProgress(i, NumberOfAnimations);
|
|
|
|
UAnimSequence* AnimSequence = AnimSequences[i].Get();
|
|
|
|
FString FileName = FString::Printf(TEXT("%s/%s"), *DestinationFolder, *AnimFileNames[i]);
|
|
|
|
FbxAnimUtils::ExportAnimFbx(*FileName, AnimSequence, PreviewMesh, bSaveSkeletalMesh);
|
|
}
|
|
|
|
GWarn->EndSlowTask();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPersona::AddLoopingInterpolation(TArray<TWeakObjectPtr<UAnimSequence>>& AnimSequences)
|
|
{
|
|
FText WarningMessage = LOCTEXT("AddLoopiingInterpolation", "This will add an extra first frame at the end of the animation to create a better looping interpolation. This action cannot be undone. Would you like to proceed?");
|
|
|
|
if(FMessageDialog::Open(EAppMsgType::YesNo, WarningMessage) == EAppReturnType::Yes)
|
|
{
|
|
for(auto Animation : AnimSequences)
|
|
{
|
|
// get first frame and add to the last frame and go through track
|
|
// now calculating old animated space bases
|
|
Animation->AddLoopingInterpolation();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FPersona::HasValidAnimationSequencePlaying() const
|
|
{
|
|
UAnimSequence* AnimSequence = Cast<UAnimSequence> (GetAnimationAssetBeingEdited());
|
|
|
|
return (AnimSequence != NULL);
|
|
}
|
|
|
|
bool FPersona::IsInPersonaMode(const FName InPersonaMode) const
|
|
{
|
|
return (GetCurrentMode() == InPersonaMode);
|
|
}
|
|
|
|
void FPersona::ChangeSkeletonPreviewMesh()
|
|
{
|
|
// Menu option cannot be called unless this is valid
|
|
if(TargetSkeleton->GetPreviewMesh() != PreviewComponent->SkeletalMesh)
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT("ChangeSkeletonPreviewMesh", "Change Skeleton Preview Mesh") );
|
|
TargetSkeleton->SetPreviewMesh(PreviewComponent->SkeletalMesh);
|
|
|
|
FFormatNamedArguments Args;
|
|
Args.Add( TEXT("PreviewMeshName"), FText::FromString( PreviewComponent->SkeletalMesh->GetName() ) );
|
|
Args.Add( TEXT("TargetSkeletonName"), FText::FromString( TargetSkeleton->GetName() ) );
|
|
FNotificationInfo Info( FText::Format( LOCTEXT("SaveSkeletonWarning", "Please save Skeleton {TargetSkeletonName} if you'd like to keep {PreviewMeshName} as default preview mesh" ), Args ) );
|
|
Info.ExpireDuration = 5.0f;
|
|
Info.bUseLargeFont = false;
|
|
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
|
|
if ( Notification.IsValid() )
|
|
{
|
|
Notification->SetCompletionState( SNotificationItem::CS_Fail );
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPersona::UpdateSkeletonRefPose()
|
|
{
|
|
// update ref pose with current preview mesh
|
|
if (TargetSkeleton)
|
|
{
|
|
TargetSkeleton->UpdateReferencePoseFromMesh(GetPreviewMeshComponent()->SkeletalMesh);
|
|
}
|
|
}
|
|
|
|
void FPersona::RemoveUnusedBones()
|
|
{
|
|
// Menu option cannot be called unless this is valid
|
|
if(TargetSkeleton)
|
|
{
|
|
TArray<FName> SkeletonBones;
|
|
const FReferenceSkeleton& RefSkeleton = TargetSkeleton->GetReferenceSkeleton();
|
|
|
|
for(int32 BoneIndex = 0; BoneIndex < RefSkeleton.GetNum(); ++BoneIndex)
|
|
{
|
|
SkeletonBones.Add(RefSkeleton.GetBoneName(BoneIndex));
|
|
}
|
|
|
|
FARFilter Filter;
|
|
Filter.ClassNames.Add(USkeletalMesh::StaticClass()->GetFName());
|
|
|
|
FString SkeletonString = FAssetData(TargetSkeleton).GetExportTextName();
|
|
Filter.TagsAndValues.Add(GET_MEMBER_NAME_CHECKED(USkeletalMesh, Skeleton), SkeletonString);
|
|
|
|
TArray<FAssetData> SkeletalMeshes;
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
AssetRegistryModule.Get().GetAssets(Filter, SkeletalMeshes);
|
|
|
|
FText TimeTakenMessage = FText::Format( LOCTEXT("TimeTakenWarning", "In order to verify bone use all Skeletal Meshes that use this skeleton will be loaded, this may take some time.\n\nProceed?\n\nNumber of Meshes: {0}"), FText::AsNumber(SkeletalMeshes.Num()) );
|
|
|
|
if(FMessageDialog::Open( EAppMsgType::YesNo, TimeTakenMessage ) == EAppReturnType::Yes)
|
|
{
|
|
const FText StatusUpdate = FText::Format(LOCTEXT("RemoveUnusedBones_ProcessingAssets", "Processing Skeletal Meshes for {0}"), FText::FromString(TargetSkeleton->GetName()) );
|
|
GWarn->BeginSlowTask(StatusUpdate, true );
|
|
|
|
// Loop through all SkeletalMeshes and remove the bones they use from our list
|
|
for ( int32 MeshIdx = 0; MeshIdx < SkeletalMeshes.Num(); ++MeshIdx )
|
|
{
|
|
GWarn->StatusUpdate( MeshIdx, SkeletalMeshes.Num(), StatusUpdate );
|
|
|
|
USkeletalMesh* Mesh = Cast<USkeletalMesh>(SkeletalMeshes[MeshIdx].GetAsset());
|
|
const FReferenceSkeleton& MeshRefSkeleton = Mesh->RefSkeleton;
|
|
|
|
for(int32 BoneIndex = 0; BoneIndex < MeshRefSkeleton.GetNum(); ++BoneIndex)
|
|
{
|
|
SkeletonBones.Remove(MeshRefSkeleton.GetBoneName(BoneIndex));
|
|
if(SkeletonBones.Num() == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if(SkeletonBones.Num() == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
GWarn->EndSlowTask();
|
|
|
|
//Remove bones that are a parent to bones we aren't removing
|
|
for(int32 BoneIndex = RefSkeleton.GetNum() -1; BoneIndex >= 0; --BoneIndex)
|
|
{
|
|
FName CurrBoneName = RefSkeleton.GetBoneName(BoneIndex);
|
|
if(!SkeletonBones.Contains(CurrBoneName))
|
|
{
|
|
//We aren't removing this bone, so remove parent from list of bones to remove too
|
|
int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex);
|
|
if(ParentIndex != INDEX_NONE)
|
|
{
|
|
SkeletonBones.Remove(RefSkeleton.GetBoneName(ParentIndex));
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we have any bones left they are unused
|
|
if(SkeletonBones.Num() > 0)
|
|
{
|
|
const FText RemoveBoneMessage = FText::Format(LOCTEXT("RemoveBoneWarning", "Continuing will remove the following bones from the skeleton '{0}'. These bones are not being used by any of the SkeletalMeshes assigned to this skeleton\n\nOnce the bones have been removed all loaded animations for this skeleton will be recompressed (any that aren't loaded will be recompressed the next time they are loaded)."), FText::FromString(TargetSkeleton->GetName()) );
|
|
|
|
// Ask User whether they would like to remove the bones from the skeleton
|
|
if (SSkeletonBoneRemoval::ShowModal(SkeletonBones, RemoveBoneMessage))
|
|
{
|
|
//Remove these bones from the skeleton
|
|
TargetSkeleton->RemoveBonesFromSkeleton(SkeletonBones, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("NoBonesToRemove", "No unused bones were found."));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPersona::Tick(float DeltaTime)
|
|
{
|
|
FBlueprintEditor::Tick(DeltaTime);
|
|
|
|
if (Viewport.IsValid())
|
|
{
|
|
Viewport.Pin()->RefreshViewport();
|
|
}
|
|
|
|
if (Recorder.InRecording())
|
|
{
|
|
// make sure you don't allow switch previewcomponent
|
|
Recorder.UpdateRecord(PreviewComponent, DeltaTime);
|
|
}
|
|
}
|
|
|
|
bool FPersona::CanChangeSkeletonPreviewMesh() const
|
|
{
|
|
return PreviewComponent && PreviewComponent->SkeletalMesh;
|
|
}
|
|
|
|
bool FPersona::CanRemoveBones() const
|
|
{
|
|
return PreviewComponent && PreviewComponent->SkeletalMesh;
|
|
}
|
|
|
|
bool FPersona::IsRecordAvailable() const
|
|
{
|
|
// make sure mesh exists
|
|
return (PreviewComponent && PreviewComponent->SkeletalMesh);
|
|
}
|
|
|
|
bool FPersona::IsEditable(UEdGraph* InGraph) const
|
|
{
|
|
bool bEditable = FBlueprintEditor::IsEditable(InGraph);
|
|
bEditable &= IsGraphInCurrentBlueprint(InGraph);
|
|
|
|
return bEditable;
|
|
}
|
|
|
|
FText FPersona::GetGraphDecorationString(UEdGraph* InGraph) const
|
|
{
|
|
if (!IsGraphInCurrentBlueprint(InGraph))
|
|
{
|
|
return LOCTEXT("PersonaExternalGraphDecoration", " Parent Graph Preview");
|
|
}
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
void FPersona::ValidatePreviewAttachedAssets(USkeletalMesh* PreviewSkeletalMesh)
|
|
{
|
|
// Validate the skeleton/meshes attached objects and display a notification to the user if any were broken
|
|
int32 NumBrokenAssets = TargetSkeleton->ValidatePreviewAttachedObjects();
|
|
if (PreviewSkeletalMesh)
|
|
{
|
|
NumBrokenAssets += PreviewSkeletalMesh->ValidatePreviewAttachedObjects();
|
|
}
|
|
|
|
if (NumBrokenAssets > 0)
|
|
{
|
|
// Tell the user that there were assets that could not be loaded
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("NumBrokenAssets"), NumBrokenAssets);
|
|
FNotificationInfo Info(FText::Format(LOCTEXT("MissingPreviewAttachedAssets", "{NumBrokenAssets} attached assets could not be found on loading and were removed"), Args));
|
|
|
|
Info.bUseLargeFont = false;
|
|
Info.ExpireDuration = 5.0f;
|
|
|
|
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
|
|
if (Notification.IsValid())
|
|
{
|
|
Notification->SetCompletionState(SNotificationItem::CS_Fail);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPersona::OnAnimNotifyWindow()
|
|
{
|
|
TabManager->InvokeTab(FPersonaTabs::SkeletonAnimNotifiesID);
|
|
}
|
|
|
|
void FPersona::OnRetargetManager()
|
|
{
|
|
TabManager->InvokeTab(FPersonaTabs::RetargetManagerID);
|
|
}
|
|
|
|
void FPersona::OnImportAsset(enum EFBXImportType DefaultImportType)
|
|
{
|
|
// open dialog
|
|
// get path
|
|
FString AssetPath;
|
|
|
|
TSharedRef<SImportPathDialog> NewAnimDlg =
|
|
SNew(SImportPathDialog);
|
|
|
|
if(NewAnimDlg->ShowModal() != EAppReturnType::Cancel)
|
|
{
|
|
AssetPath = NewAnimDlg->GetAssetPath();
|
|
|
|
UFbxImportUI* ImportUI = NewObject<UFbxImportUI>();
|
|
ImportUI->Skeleton = TargetSkeleton;
|
|
ImportUI->MeshTypeToImport = DefaultImportType;
|
|
|
|
FbxMeshUtils::SetImportOption(ImportUI);
|
|
|
|
// now I have to set skeleton on it.
|
|
FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools");
|
|
TArray<UObject*> NewAssets = AssetToolsModule.Get().ImportAssets(AssetPath);
|
|
|
|
if ( NewAssets.Num() > 0 )
|
|
{
|
|
|
|
}
|
|
else
|
|
{
|
|
// error
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPersona::OnReimportMesh()
|
|
{
|
|
// Reimport the asset
|
|
UDebugSkelMeshComponent* PreviewMeshComponent = GetPreviewMeshComponent();
|
|
if(PreviewMeshComponent && PreviewMeshComponent->SkeletalMesh)
|
|
{
|
|
FReimportManager::Instance()->Reimport(PreviewMeshComponent->SkeletalMesh, true);
|
|
}
|
|
}
|
|
|
|
void FPersona::OnReimportAnimation()
|
|
{
|
|
// Reimport the asset
|
|
UAnimSequence* AnimSequence = Cast<UAnimSequence> (GetAnimationAssetBeingEdited());
|
|
if (AnimSequence)
|
|
{
|
|
FReimportManager::Instance()->Reimport(AnimSequence, true);
|
|
}
|
|
}
|
|
|
|
TStatId FPersona::GetStatId() const
|
|
{
|
|
RETURN_QUICK_DECLARE_CYCLE_STAT(FPersona, STATGROUP_Tickables);
|
|
}
|
|
|
|
void FPersona::OnBlueprintPreCompile(UBlueprint* BlueprintToCompile)
|
|
{
|
|
if(PreviewComponent && PreviewComponent->PreviewInstance)
|
|
{
|
|
// If we are compiling an anim notify state the class will soon be sanitized and
|
|
// if an anim instance is running a state when that happens it will likely
|
|
// crash, so we end any states that are about to compile.
|
|
UAnimPreviewInstance* Instance = PreviewComponent->PreviewInstance;
|
|
USkeletalMeshComponent* SkelMeshComp = Instance->GetSkelMeshComponent();
|
|
|
|
for(int32 Idx = Instance->ActiveAnimNotifyState.Num() - 1 ; Idx >= 0 ; --Idx)
|
|
{
|
|
FAnimNotifyEvent& Event = Instance->ActiveAnimNotifyState[Idx];
|
|
if(Event.NotifyStateClass->GetClass() == BlueprintToCompile->GeneratedClass)
|
|
{
|
|
Event.NotifyStateClass->NotifyEnd(SkelMeshComp, Cast<UAnimSequenceBase>(Event.NotifyStateClass->GetOuter()));
|
|
Instance->ActiveAnimNotifyState.RemoveAt(Idx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPersona::OnBlueprintChangedImpl(UBlueprint* InBlueprint, bool bIsJustBeingCompiled /*= false*/)
|
|
{
|
|
FBlueprintEditor::OnBlueprintChangedImpl(InBlueprint, bIsJustBeingCompiled);
|
|
|
|
UObject* CurrentDebugObject = GetBlueprintObj()->GetObjectBeingDebugged();
|
|
const bool bIsDebuggingPreview = (PreviewComponent != NULL) && PreviewComponent->IsAnimBlueprintInstanced() && (PreviewComponent->AnimScriptInstance == CurrentDebugObject);
|
|
|
|
if(PreviewComponent != NULL)
|
|
{
|
|
if(PreviewComponent->AnimScriptInstance == NULL)
|
|
{
|
|
// try reinitialize animation if it doesn't exist
|
|
PreviewComponent->InitAnim(true);
|
|
}
|
|
|
|
if(bIsDebuggingPreview)
|
|
{
|
|
GetBlueprintObj()->SetObjectBeingDebugged(PreviewComponent->AnimScriptInstance);
|
|
}
|
|
}
|
|
|
|
// calls PostCompile to copy proper values between anim nodes
|
|
if(Viewport.IsValid())
|
|
{
|
|
Viewport.Pin()->GetAnimationViewportClient()->PostCompile();
|
|
}
|
|
}
|
|
|
|
void FPersona::ShowReferencePose(bool bReferencePose)
|
|
{
|
|
if(PreviewComponent)
|
|
{
|
|
if(bReferencePose == false)
|
|
{
|
|
if(IsInPersonaMode(FPersonaModes::AnimBlueprintEditMode))
|
|
{
|
|
PreviewComponent->EnablePreview(false, NULL, NULL);
|
|
|
|
UAnimBlueprint* AnimBP = GetAnimBlueprint();
|
|
if(AnimBP)
|
|
{
|
|
PreviewComponent->SetAnimInstanceClass(AnimBP->GeneratedClass);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UObject* PreviewAsset = CachedPreviewAsset.IsValid()? CachedPreviewAsset.Get() : (GetAnimationAssetBeingEdited());
|
|
PreviewComponent->EnablePreview(true, Cast<UAnimationAsset>(PreviewAsset), NULL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (PreviewComponent->PreviewInstance && PreviewComponent->PreviewInstance->CurrentAsset)
|
|
{
|
|
CachedPreviewAsset = PreviewComponent->PreviewInstance->CurrentAsset;
|
|
}
|
|
|
|
PreviewComponent->EnablePreview(true, NULL, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FPersona::CanShowReferencePose() const
|
|
{
|
|
return PreviewComponent != NULL;
|
|
}
|
|
|
|
bool FPersona::IsShowReferencePoseEnabled() const
|
|
{
|
|
if(PreviewComponent)
|
|
{
|
|
return PreviewComponent->IsPreviewOn() && PreviewComponent->PreviewInstance->CurrentAsset == NULL;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FPersona::CanPreviewAsset() const
|
|
{
|
|
return CanShowReferencePose();
|
|
}
|
|
|
|
bool FPersona::IsPreviewAssetEnabled() const
|
|
{
|
|
return IsShowReferencePoseEnabled() == false;
|
|
}
|
|
|
|
FText FPersona::GetPreviewAssetTooltip() const
|
|
{
|
|
// if already looking at ref pose
|
|
if(IsShowReferencePoseEnabled())
|
|
{
|
|
FString AssetName = TEXT("None Available. Please select asset to preview.");
|
|
|
|
if(IsInPersonaMode(FPersonaModes::AnimBlueprintEditMode))
|
|
{
|
|
UAnimBlueprint* AnimBP = GetAnimBlueprint();
|
|
if(AnimBP)
|
|
{
|
|
AssetName = AnimBP->GetName();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UObject* PreviewAsset = CachedPreviewAsset.IsValid()? CachedPreviewAsset.Get() : (GetAnimationAssetBeingEdited());
|
|
if (PreviewAsset)
|
|
{
|
|
AssetName = PreviewAsset->GetName();
|
|
}
|
|
}
|
|
return FText::FromString(FString::Printf(TEXT("Preview %s"), *AssetName));
|
|
}
|
|
else
|
|
{
|
|
return FText::FromString(FString::Printf(TEXT("Currently previewing %s"), *PreviewComponent->GetPreviewText()));
|
|
}
|
|
}
|
|
|
|
static class FMeshHierarchyCmd : private FSelfRegisteringExec
|
|
{
|
|
public:
|
|
/** Console commands, see embeded usage statement **/
|
|
virtual bool Exec( UWorld*, const TCHAR* Cmd, FOutputDevice& Ar ) override
|
|
{
|
|
bool bResult = false;
|
|
if(FParse::Command(&Cmd,TEXT("TMH")))
|
|
{
|
|
Ar.Log(TEXT("Starting Mesh Test"));
|
|
FARFilter Filter;
|
|
Filter.ClassNames.Add(USkeletalMesh::StaticClass()->GetFName());
|
|
|
|
TArray<FAssetData> SkeletalMeshes;
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
AssetRegistryModule.Get().GetAssets(Filter, SkeletalMeshes);
|
|
|
|
const FText StatusUpdate = LOCTEXT("RemoveUnusedBones_ProcessingAssets", "Processing Skeletal Meshes");
|
|
GWarn->BeginSlowTask(StatusUpdate, true );
|
|
|
|
// go through all assets try load
|
|
for ( int32 MeshIdx = 0; MeshIdx < SkeletalMeshes.Num(); ++MeshIdx )
|
|
{
|
|
GWarn->StatusUpdate( MeshIdx, SkeletalMeshes.Num(), StatusUpdate );
|
|
|
|
USkeletalMesh* Mesh = Cast<USkeletalMesh>(SkeletalMeshes[MeshIdx].GetAsset());
|
|
if(Mesh->Skeleton)
|
|
{
|
|
FName MeshRoot = Mesh->RefSkeleton.GetBoneName(0);
|
|
FName SkelRoot = Mesh->Skeleton->GetReferenceSkeleton().GetBoneName(0);
|
|
|
|
if(MeshRoot != SkelRoot)
|
|
{
|
|
Ar.Logf( TEXT("Mesh Found '%s' %s->%s"), *Mesh->GetName(), *MeshRoot.ToString(), *SkelRoot.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
GWarn->EndSlowTask();
|
|
Ar.Log(TEXT("Mesh Test Finished"));
|
|
}
|
|
return bResult;
|
|
}
|
|
} MeshHierarchyCmdExec;
|
|
|
|
#undef LOCTEXT_NAMESPACE
|
|
|