You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
This change consists of multiple changes: Core: - Deprecation of ANY_PACKAGE macro. Added ANY_PACKAGE_DEPRECATED macro which can still be used for backwards compatibility purposes (only used in CoreUObject) - Deprecation of StaticFindObjectFast* functions that take bAnyPackage parameter - Added UStruct::GetStructPathName function that returns FTopLevelAssetPath representing the path name (package + object FName, super quick compared to UObject::GetPathName) + wrapper UClass::GetClassPathName to make it look better when used with UClasses - Added (Static)FindFirstObject* functions that find a first object given its Name (no Outer). These functions are used in places I consider valid to do global UObject (UClass) lookups like parsing command line parameters / checking for unique object names - Added static UClass::TryFindType function which serves a similar purpose as FindFirstObject however it's going to throw a warning (with a callstack / maybe ensure in the future?) if short class name is provided. This function is used in places that used to use short class names but now should have been converted to use path names to catch any potential regressions and or edge cases I missed. - Added static UClass::TryConvertShortNameToPathName utility function - Added static UClass::TryFixShortClassNameExportPath utility function - Object text export paths will now also include class path (Texture2D'/Game/Textures/Grass.Grass' -> /Script/Engine.Texture2D'/Game/Textures/Grass.Grass') - All places that manually generated object export paths for objects will now use FObjectPropertyBase::GetExportPath - Added a new startup test that checks for short type names in UClass/FProperty MetaData values AssetRegistry: - Deprecated any member variables (FAssetData / FARFilter) or functions that use FNames to represent class names and replaced them with FTopLevelAssetPath - Added new member variables and new function overloads that use FTopLevelAssetPath to represent class names - This also applies to a few other modules' APIs to match AssetRegistry changes Everything else: - Updated code that used ANY_PACKAGE (depending on the use case) to use FindObject(nullptr, PathToObject), UClass::TryFindType (used when path name is expected, warns if it's a short name) or FindFirstObject (usually for finding types based on user input but there's been a few legitimate use cases not related to user input) - Updated code that used AssetRegistry API to use FTopLevelAssetPaths and USomeClass::StaticClass()->GetClassPathName() instead of GetFName() - Updated meta data and hardcoded FindObject(ANY_PACKAGE, "EEnumNameOrClassName") calls to use path names #jira UE-99463 #rb many.people [FYI] Marcus.Wassmer #preflight 629248ec2256738f75de9b32 #codereviewnumbers 20320742, 20320791, 20320799, 20320756, 20320809, 20320830, 20320840, 20320846, 20320851, 20320863, 20320780, 20320765, 20320876, 20320786 #ROBOMERGE-OWNER: robert.manuszewski #ROBOMERGE-AUTHOR: robert.manuszewski #ROBOMERGE-SOURCE: CL 20430220 via CL 20433854 via CL 20435474 via CL 20435484 #ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v949-20362246) [CL 20448496 by robert manuszewski in ue5-main branch]
446 lines
14 KiB
C++
446 lines
14 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "SControlRigMappingWindow.h"
|
|
#include "Misc/MessageDialog.h"
|
|
#include "Widgets/SBoxPanel.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Widgets/Layout/SSeparator.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/SToolTip.h"
|
|
#include "IDocumentation.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "IContentBrowserSingleton.h"
|
|
#include "ContentBrowserModule.h"
|
|
#include "Engine/Blueprint.h"
|
|
#include "Engine/SkeletalMesh.h"
|
|
#include "Animation/NodeMappingProviderInterface.h"
|
|
#include "AnimationRuntime.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "SControlRigMappingWindow"
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// SAnimationMappingWindow
|
|
|
|
void SControlRigMappingWindow::Construct(const FArguments& InArgs, const TWeakObjectPtr<class USkeletalMesh>& InEditableMesh, FSimpleMulticastDelegate& InOnPostUndo)
|
|
{
|
|
EditableSkeletalMeshPtr = InEditableMesh;
|
|
InOnPostUndo.Add(FSimpleDelegate::CreateSP(this, &SControlRigMappingWindow::PostUndo));
|
|
|
|
const FString DocLink = TEXT("Shared/Editors/Persona");
|
|
ChildSlot
|
|
[
|
|
SNew (SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
.Padding(5, 5)
|
|
.AutoHeight()
|
|
[
|
|
// explain this is Control Rig window
|
|
// and what it is
|
|
SNew(STextBlock)
|
|
.TextStyle( FAppStyle::Get(), "Persona.RetargetManager.ImportantText" )
|
|
.Text(LOCTEXT("ControlRigMapping_Title", "Configure Control Rig Settings"))
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.Padding(5, 5)
|
|
.AutoHeight()
|
|
[
|
|
// explainint this is Control Rig window
|
|
// and what it is
|
|
SNew(STextBlock)
|
|
.AutoWrapText(true)
|
|
.ToolTip(IDocumentation::Get()->CreateToolTip(LOCTEXT("ControlRigMappingTooltip", "Add new Control Rig, and remap or edit mapping information."),
|
|
NULL,
|
|
DocLink,
|
|
TEXT("NodeMapping")))
|
|
.Font(FAppStyle::GetFontStyle(TEXT("Persona.RetargetManager.FilterFont")))
|
|
.Text(LOCTEXT("ControlRigMappingDescription", "You can add/delete Control Rig Mapping Configuration."))
|
|
]
|
|
|
|
// select and combo box
|
|
+ SVerticalBox::Slot()
|
|
.Padding(5)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+SHorizontalBox::Slot()
|
|
.Padding(2)
|
|
.AutoWidth()
|
|
[
|
|
// is this safe with uobject? - i.e. if this gets GC-ed?
|
|
SAssignNew(MappingOptionBox, SComboBox< TSharedPtr<class UNodeMappingContainer*> >)
|
|
.ContentPadding(FMargin(6.0f, 2.0f))
|
|
.OptionsSource(&MappingOptionBoxList)
|
|
.OnGenerateWidget(this, &SControlRigMappingWindow::HandleMappingOptionBoxGenerateWidget)
|
|
.OnSelectionChanged(this, &SControlRigMappingWindow::HandleMappingOptionBoxSelectionChanged)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &SControlRigMappingWindow::HandleMappingOptionBoxContentText)
|
|
.Font(FAppStyle::GetFontStyle(TEXT("Persona.RetargetManager.FilterFont")))
|
|
]
|
|
]
|
|
|
|
// add new or delete button
|
|
+SHorizontalBox::Slot()
|
|
.Padding(2)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.OnClicked(FOnClicked::CreateSP(this, &SControlRigMappingWindow::OnAddNodeMappingButtonClicked))
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
.Text(LOCTEXT("AddNodeMappingButton_Label", "Add New"))
|
|
]
|
|
|
|
+SHorizontalBox::Slot()
|
|
.Padding(2)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.OnClicked(FOnClicked::CreateSP(this, &SControlRigMappingWindow::OnDeleteNodeMappingButtonClicked))
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
.Text(LOCTEXT("DeleteNodeMappingButton_Label", "Delete Current"))
|
|
]
|
|
|
|
+SHorizontalBox::Slot()
|
|
.Padding(2)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.OnClicked(FOnClicked::CreateSP(this, &SControlRigMappingWindow::OnRefreshNodeMappingButtonClicked))
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
.Text(LOCTEXT("RefreshNodeMappingButton_Label", "Refresh Mapping"))
|
|
]
|
|
]
|
|
|
|
+SVerticalBox::Slot()
|
|
.Padding(2, 5)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SBox)
|
|
.MaxDesiredHeight(500)
|
|
.Content()
|
|
[
|
|
// add bone mapper window
|
|
SAssignNew(BoneMappingWidget, SBoneMappingBase, InOnPostUndo)
|
|
.OnBoneMappingChanged(this, &SControlRigMappingWindow::OnBoneMappingChanged)
|
|
.OnGetBoneMapping(this, &SControlRigMappingWindow::GetBoneMapping)
|
|
.OnCreateBoneMapping(this, &SControlRigMappingWindow::CreateBoneMappingList)
|
|
.OnGetReferenceSkeleton(this, &SControlRigMappingWindow::GetReferenceSkeleton)
|
|
]
|
|
]
|
|
|
|
+SVerticalBox::Slot()
|
|
.Padding(2, 5)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SSeparator)
|
|
.Orientation(Orient_Horizontal)
|
|
]
|
|
];
|
|
|
|
RefreshList();
|
|
}
|
|
|
|
void SControlRigMappingWindow::PostUndo()
|
|
{
|
|
RefreshList();
|
|
}
|
|
|
|
TSharedRef<SWidget> SControlRigMappingWindow::HandleMappingOptionBoxGenerateWidget(TSharedPtr<class UNodeMappingContainer*> Item)
|
|
{
|
|
// @todo: create tooltip with path
|
|
return SNew(STextBlock)
|
|
.Text(FText::FromString(*(*Item)->GetDisplayName()))
|
|
.Font(FAppStyle::GetFontStyle(TEXT("Persona.RetargetManager.FilterFont")));
|
|
}
|
|
|
|
void SControlRigMappingWindow::HandleMappingOptionBoxSelectionChanged(TSharedPtr<class UNodeMappingContainer*> Item, ESelectInfo::Type SelectInfo)
|
|
{
|
|
for (int32 Index=0; Index <MappingOptionBoxList.Num(); ++Index)
|
|
{
|
|
if (MappingOptionBoxList[Index] == Item)
|
|
{
|
|
CurrentlySelectedIndex = Index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// @todo: refresh mapping window
|
|
MappingOptionBox->RefreshOptions();
|
|
BoneMappingWidget->RefreshBoneMappingList();
|
|
}
|
|
|
|
FText SControlRigMappingWindow::HandleMappingOptionBoxContentText() const
|
|
{
|
|
if (MappingOptionBoxList.IsValidIndex(CurrentlySelectedIndex))
|
|
{
|
|
return FText::FromString(*(*MappingOptionBoxList[CurrentlySelectedIndex])->GetDisplayName());
|
|
}
|
|
|
|
return LOCTEXT("ControlRigMappingWindow_NoneSelected", "None Selected. Create New.");
|
|
}
|
|
|
|
void SControlRigMappingWindow::AddNodeMapping(UBlueprint* NewSourceControlRig)
|
|
{
|
|
if ( ensureAlways(EditableSkeletalMeshPtr.IsValid()) )
|
|
{
|
|
// make sure it doesn't have it yet. If so, we warn them.
|
|
// @todo: It is possible we could support multi, but then we should give unique display name, and use that to identify.
|
|
// this all can get very messy, so for now, we just support one for each
|
|
// create new mapper object
|
|
USkeletalMesh* SkeletalMesh = EditableSkeletalMeshPtr.Get();
|
|
TArray<UNodeMappingContainer*>& NodeMappingData = SkeletalMesh->GetNodeMappingData();
|
|
for (int32 Index = 0; Index < NodeMappingData.Num(); ++Index)
|
|
{
|
|
if (NewSourceControlRig == NodeMappingData[Index]->GetSourceAsset())
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ControlRigConfigAlreadyExists", "The same Control Rig configuration already exists in this mesh. Edit current existing setting."));
|
|
return;
|
|
}
|
|
}
|
|
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("ControlRigMapping_AddNew", "Add New Mapping"));
|
|
|
|
SkeletalMesh->Modify();
|
|
UNodeMappingContainer* NewMapperObject = NewObject<UNodeMappingContainer>(SkeletalMesh);
|
|
NewMapperObject->SetSourceAsset(NewSourceControlRig);
|
|
// set target asset as skeletalmesh
|
|
NewMapperObject->SetTargetAsset(SkeletalMesh);
|
|
// add default mapping, this will map default settings
|
|
NewMapperObject->AddDefaultMapping();
|
|
CurrentlySelectedIndex = SkeletalMesh->GetNodeMappingData().Add(NewMapperObject);
|
|
|
|
RefreshList();
|
|
}
|
|
}
|
|
}
|
|
|
|
FReply SControlRigMappingWindow::OnDeleteNodeMappingButtonClicked()
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("ControlRigMapping_Delete", "Delete Selected Mapping"));
|
|
|
|
// create new mapper object
|
|
USkeletalMesh* SkeletalMesh = EditableSkeletalMeshPtr.Get();
|
|
TArray<UNodeMappingContainer*>& NodeMappingData = SkeletalMesh->GetNodeMappingData();
|
|
if (NodeMappingData.IsValidIndex(CurrentlySelectedIndex))
|
|
{
|
|
SkeletalMesh->Modify();
|
|
NodeMappingData.RemoveAt(CurrentlySelectedIndex);
|
|
CurrentlySelectedIndex = (NodeMappingData.Num() > 0) ? 0 : INDEX_NONE;
|
|
RefreshList();
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply SControlRigMappingWindow::OnRefreshNodeMappingButtonClicked()
|
|
{
|
|
// create new mapper object
|
|
USkeletalMesh* SkeletalMesh = EditableSkeletalMeshPtr.Get();
|
|
if (SkeletalMesh->GetNodeMappingData().IsValidIndex(CurrentlySelectedIndex))
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("ControlRigMapping_Refresh", "Refresh Node Mapping"));
|
|
|
|
UNodeMappingContainer* Container = GetCurrentBoneMappingContainer();
|
|
if (SkeletalMesh && Container)
|
|
{
|
|
SkeletalMesh->Modify();
|
|
Container->RefreshDataFromAssets();
|
|
}
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
void SControlRigMappingWindow::OnAddNodeMapping()
|
|
{
|
|
// show list of skeletalmeshes that they can choose from
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
|
|
|
|
FAssetPickerConfig AssetPickerConfig;
|
|
AssetPickerConfig.Filter.ClassPaths.Add(UBlueprint::StaticClass()->GetClassPathName());
|
|
AssetPickerConfig.Filter.bRecursiveClasses = true;
|
|
AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateSP(this, &SControlRigMappingWindow::OnAssetSelectedFromMeshPicker);
|
|
AssetPickerConfig.bAllowNullSelection = false;
|
|
AssetPickerConfig.InitialAssetViewType = EAssetViewType::Tile;
|
|
AssetPickerConfig.OnShouldFilterAsset = FOnShouldFilterAsset::CreateSP(this, &SControlRigMappingWindow::OnShouldFilterAnimAsset);
|
|
|
|
TSharedRef<SWidget> Widget = SNew(SBox)
|
|
.WidthOverride(384)
|
|
.HeightOverride(768)
|
|
[
|
|
SNew(SBorder)
|
|
.BorderBackgroundColor(FLinearColor(0.25f, 0.25f, 0.25f, 1.f))
|
|
.Padding(2)
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
|
.Padding(8)
|
|
[
|
|
ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig)
|
|
]
|
|
]
|
|
];
|
|
|
|
FSlateApplication::Get().PushMenu(
|
|
AsShared(),
|
|
FWidgetPath(),
|
|
Widget,
|
|
FSlateApplication::Get().GetCursorPos(),
|
|
FPopupTransitionEffect(FPopupTransitionEffect::TopMenu)
|
|
);
|
|
}
|
|
|
|
FReply SControlRigMappingWindow::OnAddNodeMappingButtonClicked()
|
|
{
|
|
OnAddNodeMapping();
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void SControlRigMappingWindow::OnAssetSelectedFromMeshPicker(const FAssetData& AssetData)
|
|
{
|
|
// add to mapping now
|
|
AddNodeMapping(Cast<UBlueprint>(AssetData.GetAsset()));
|
|
FSlateApplication::Get().DismissAllMenus();
|
|
}
|
|
|
|
bool SControlRigMappingWindow::OnShouldFilterAnimAsset(const FAssetData& AssetData) const
|
|
{
|
|
FString ParentClassName;
|
|
if (AssetData.GetTagValue(FBlueprintTags::NativeParentClassPath, ParentClassName))
|
|
{
|
|
if (ParentClassName.IsEmpty() == false)
|
|
{
|
|
UClass* ParentClass = UClass::TryFindTypeSlow<UClass>(FPackageName::ExportTextPathToObjectPath(ParentClassName));
|
|
while (ParentClass && ParentClass != UObject::StaticClass())
|
|
{
|
|
if (ParentClass->ImplementsInterface(UNodeMappingProviderInterface::StaticClass()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ParentClass = ParentClass->GetSuperClass();
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void SControlRigMappingWindow::RefreshList()
|
|
{
|
|
// update list of mapping options
|
|
// @todo: have to make sure there is no duplicated name. If so, we'll have to create fake name
|
|
// for now, do path name
|
|
MappingOptionBoxList.Empty();
|
|
TArray<UNodeMappingContainer*>& NodeMappingData = EditableSkeletalMeshPtr->GetNodeMappingData();
|
|
for (int32 Index = 0; Index < NodeMappingData.Num(); ++Index)
|
|
{
|
|
class UNodeMappingContainer* MappingData = NodeMappingData[Index];
|
|
if (MappingData)
|
|
{
|
|
MappingOptionBoxList.Add(MakeShareable(new UNodeMappingContainer*(MappingData)));
|
|
}
|
|
else
|
|
{
|
|
// we have to add all index because we can't skip it
|
|
ensureAlwaysMsgf(false, TEXT("Invalid node mapping data exists. This will cause issue with later indices."));
|
|
}
|
|
}
|
|
|
|
CurrentlySelectedIndex = (MappingOptionBoxList.Num() > 0) ? 0 : INDEX_NONE;
|
|
|
|
MappingOptionBox->RefreshOptions();
|
|
|
|
BoneMappingWidget->RefreshBoneMappingList();
|
|
}
|
|
|
|
class UNodeMappingContainer* SControlRigMappingWindow::GetCurrentBoneMappingContainer() const
|
|
{
|
|
if (EditableSkeletalMeshPtr.IsValid())
|
|
{
|
|
USkeletalMesh* Mesh = EditableSkeletalMeshPtr.Get();
|
|
TArray<UNodeMappingContainer*>& NodeMappingData = Mesh->GetNodeMappingData();
|
|
if (NodeMappingData.IsValidIndex(CurrentlySelectedIndex))
|
|
{
|
|
return NodeMappingData[CurrentlySelectedIndex];
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void SControlRigMappingWindow::OnBoneMappingChanged(FName NodeName, FName BoneName)
|
|
{
|
|
// set mapping
|
|
USkeletalMesh* Mesh = EditableSkeletalMeshPtr.Get();
|
|
UNodeMappingContainer* Container = GetCurrentBoneMappingContainer();
|
|
if (Mesh && Container)
|
|
{
|
|
Container->AddMapping(NodeName, BoneName);
|
|
}
|
|
}
|
|
|
|
FName SControlRigMappingWindow::GetBoneMapping(FName NodeName)
|
|
{
|
|
UNodeMappingContainer* Container = GetCurrentBoneMappingContainer();
|
|
if (Container)
|
|
{
|
|
const FName* Target = Container->GetNodeMappingTable().Find(NodeName);;
|
|
return Target? (*Target) : NAME_None;
|
|
}
|
|
|
|
return NAME_None;
|
|
}
|
|
|
|
void SControlRigMappingWindow::CreateBoneMappingList(const FString& SearchText, TArray< TSharedPtr<FDisplayedBoneMappingInfo> >& BoneMappingList)
|
|
{
|
|
BoneMappingList.Empty();
|
|
|
|
const FReferenceSkeleton RefSkeleton = EditableSkeletalMeshPtr.Get()->GetRefSkeleton();
|
|
UNodeMappingContainer* Container = GetCurrentBoneMappingContainer();
|
|
if (Container)
|
|
{
|
|
bool bDoFiltering = !SearchText.IsEmpty();
|
|
const TMap<FName, FNodeItem>& SourceItems = Container->GetSourceItems();
|
|
const TMap<FName, FName>& SourceToTarget = Container->GetNodeMappingTable();
|
|
|
|
if ( SourceItems.Num() > 0 )
|
|
{
|
|
for ( auto Iter = SourceItems.CreateConstIterator() ; Iter; ++Iter )
|
|
{
|
|
const FName& Name = Iter.Key();
|
|
const FString& DisplayName = Name.ToString();
|
|
const FName* BoneName = SourceToTarget.Find(Name);
|
|
|
|
if (bDoFiltering)
|
|
{
|
|
// make sure it doens't fit any of them
|
|
if (!DisplayName.Contains(SearchText) && (BoneName && !(*BoneName).ToString().Contains(SearchText)))
|
|
{
|
|
continue; // Skip items that don't match our filter
|
|
}
|
|
}
|
|
|
|
TSharedRef<FDisplayedBoneMappingInfo> Info = FDisplayedBoneMappingInfo::Make(Name, DisplayName);
|
|
BoneMappingList.Add(Info);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const struct FReferenceSkeleton& SControlRigMappingWindow::GetReferenceSkeleton() const
|
|
{
|
|
static FReferenceSkeleton DummySkeleton;
|
|
return (EditableSkeletalMeshPtr.IsValid())? EditableSkeletalMeshPtr.Get()->GetRefSkeleton() : DummySkeleton;
|
|
}
|
|
#undef LOCTEXT_NAMESPACE
|
|
|