EM_Task/UnrealEd/Private/SSkeletonWidget.cpp
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

1468 lines
62 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SSkeletonWidget.h"
#include "Modules/ModuleManager.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Layout/SSeparator.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Input/SComboButton.h"
#include "Widgets/Input/SCheckBox.h"
#include "Engine/SkeletalMesh.h"
#include "Animation/AnimationAsset.h"
#include "Animation/DebugSkelMeshComponent.h"
#include "Animation/AnimBlueprint.h"
#include "Editor.h"
#include "Animation/AnimSet.h"
#include "Interfaces/IMainFrameModule.h"
#include "IContentBrowserSingleton.h"
#include "ContentBrowserModule.h"
#include "IDocumentation.h"
#include "Animation/Rig.h"
#include "AnimPreviewInstance.h"
#include "AssetRegistryModule.h"
#include "AnimationRuntime.h"
#include "Settings/SkeletalMeshEditorSettings.h"
#include "Styling/CoreStyle.h"
#define LOCTEXT_NAMESPACE "SkeletonWidget"
void SSkeletonListWidget::Construct(const FArguments& InArgs)
{
CurSelectedSkeleton = nullptr;
FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
FAssetPickerConfig AssetPickerConfig;
AssetPickerConfig.Filter.ClassNames.Add(USkeleton::StaticClass()->GetFName());
AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateSP(this, &SSkeletonListWidget::SkeletonSelectionChanged);
AssetPickerConfig.InitialAssetViewType = EAssetViewType::Column;
AssetPickerConfig.SelectionMode = ESelectionMode::Single;
AssetPickerConfig.bShowPathInColumnView = true;
AssetPickerConfig.bShowTypeInColumnView = false;
this->ChildSlot
[SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[SNew(STextBlock)
.Text(LOCTEXT("SelectSkeletonLabel", "Select Skeleton: "))]
+ SVerticalBox::Slot().FillHeight(1).Padding(2)
[SNew(SBorder)
.Content()
[ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig)]]
+ SVerticalBox::Slot().FillHeight(1).Padding(2).Expose(BoneListSlot)
];
// Construct the BoneListSlot by clearing the skeleton selection.
SkeletonSelectionChanged(FAssetData());
}
void SSkeletonListWidget::SkeletonSelectionChanged(const FAssetData& AssetData)
{
BoneList.Empty();
CurSelectedSkeleton = Cast<USkeleton>(AssetData.GetAsset());
if (CurSelectedSkeleton != nullptr)
{
const FReferenceSkeleton& RefSkeleton = CurSelectedSkeleton->GetReferenceSkeleton();
for (int32 I = 0; I < RefSkeleton.GetNum(); ++I)
{
BoneList.Add(MakeShareable(new FName(RefSkeleton.GetBoneName(I))));
}
(*BoneListSlot)
[SNew(SBorder).Padding(2).Content()
[SNew(SListView<TSharedPtr<FName>>)
.OnGenerateRow(this, &SSkeletonListWidget::GenerateSkeletonBoneRow)
.ListItemsSource(&BoneList)
.HeaderRow(
SNew(SHeaderRow) + SHeaderRow::Column(TEXT("Bone Name"))
.DefaultLabel(NSLOCTEXT("SkeletonWidget", "BoneName", "Bone Name")))]];
}
else
{
(*BoneListSlot)
[SNew(SBorder).Padding(2).Content()
[SNew(STextBlock)
.Text(NSLOCTEXT("SkeletonWidget", "NoSkeletonIsSelected", "No skeleton is selected!"))]];
}
}
void SSkeletonCompareWidget::Construct(const FArguments& InArgs)
{
const UObject* Object = InArgs._Object;
CurSelectedSkeleton = nullptr;
BoneNames = *InArgs._BoneNames;
FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
FAssetPickerConfig AssetPickerConfig;
AssetPickerConfig.Filter.ClassNames.Add(USkeleton::StaticClass()->GetFName());
AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateSP(this, &SSkeletonCompareWidget::SkeletonSelectionChanged);
AssetPickerConfig.InitialAssetViewType = EAssetViewType::Column;
AssetPickerConfig.bShowPathInColumnView = true;
AssetPickerConfig.bShowTypeInColumnView = false;
TSharedPtr<SToolTip> SkeletonTooltip = IDocumentation::Get()->CreateToolTip(FText::FromString("Pick a skeleton for this mesh"), nullptr, FString("Shared/Editors/Persona"), FString("Skeleton"));
this->ChildSlot
[SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2)
.HAlign(HAlign_Fill)
[SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center) +
SHorizontalBox::Slot()
[SNew(STextBlock)
.Text(LOCTEXT("CurrentlySelectedSkeletonLabel_SelectSkeleton", "Select Skeleton"))
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 16))
.ToolTip(SkeletonTooltip)] +
SHorizontalBox::Slot()
.FillWidth(1)
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[IDocumentation::Get()->CreateAnchor(FString("Engine/Animation/Skeleton"))]]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2, 10)
.HAlign(HAlign_Fill)
[SNew(SSeparator)
.Orientation(Orient_Horizontal)]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2)
.HAlign(HAlign_Fill)
[SNew(STextBlock)
.Text(LOCTEXT("CurrentlySelectedSkeletonLabel", "Currently Selected : "))] +
SVerticalBox::Slot()
.AutoHeight()
.Padding(2)
.HAlign(HAlign_Fill)
[SNew(STextBlock)
.Text(FText::FromString(Object->GetFullName()))]]
+ SVerticalBox::Slot().FillHeight(1).Padding(2)
[SNew(SBorder)
.Content()
[ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig)]]
+ SVerticalBox::Slot().FillHeight(1).Padding(2).Expose(BonePairSlot)];
// Construct the BonePairSlot by clearing the skeleton selection.
SkeletonSelectionChanged(FAssetData());
}
void SSkeletonCompareWidget::SkeletonSelectionChanged(const FAssetData& AssetData)
{
BonePairList.Empty();
CurSelectedSkeleton = Cast<USkeleton>(AssetData.GetAsset());
if (CurSelectedSkeleton != nullptr)
{
for (int32 I = 0; I < BoneNames.Num(); ++I)
{
if (CurSelectedSkeleton->GetReferenceSkeleton().FindBoneIndex(BoneNames[I]) != INDEX_NONE)
{
BonePairList.Add(MakeShareable(new FBoneTrackPair(BoneNames[I], BoneNames[I])));
}
else
{
BonePairList.Add(MakeShareable(new FBoneTrackPair(BoneNames[I], TEXT(""))));
}
}
(*BonePairSlot)
[SNew(SBorder).Padding(2).Content()
[SNew(SListView<TSharedPtr<FBoneTrackPair>>)
.OnGenerateRow(this, &SSkeletonCompareWidget::GenerateBonePairRow)
.ListItemsSource(&BonePairList)
.HeaderRow(
SNew(SHeaderRow) + SHeaderRow::Column(TEXT("Curretly Selected")).DefaultLabel(NSLOCTEXT("SkeletonWidget", "CurrentlySelected", "Currently Selected")) + SHeaderRow::Column(TEXT("Target Skeleton Bone")).DefaultLabel(NSLOCTEXT("SkeletonWidget", "TargetSkeletonBone", "Target Skeleton Bone")))]];
}
else
{
(*BonePairSlot)
[SNew(SBorder).Padding(2).Content()
[SNew(STextBlock)
.Text(LOCTEXT("NoSkeletonSelectedLabel", "No skeleton is selected!"))]];
}
}
void SSkeletonSelectorWindow::Construct(const FArguments& InArgs)
{
UObject* Object = InArgs._Object;
WidgetWindow = InArgs._WidgetWindow;
SelectedSkeleton = nullptr;
if (Object == nullptr)
{
ConstructWindow();
}
else if (Object->IsA(USkeletalMesh::StaticClass()))
{
ConstructWindowFromMesh(CastChecked<USkeletalMesh>(Object));
}
else if (Object->IsA(UAnimSet::StaticClass()))
{
ConstructWindowFromAnimSet(CastChecked<UAnimSet>(Object));
}
}
void SSkeletonSelectorWindow::ConstructWindowFromAnimSet(UAnimSet* InAnimSet)
{
TArray<FName>* TrackNames = &InAnimSet->TrackBoneNames;
TSharedRef<SVerticalBox> ContentBox = SNew(SVerticalBox) + SVerticalBox::Slot()
.FillHeight(1)
.Padding(2)
[SAssignNew(SkeletonWidget, SSkeletonCompareWidget)
.Object(InAnimSet)
.BoneNames(TrackNames)];
ConstructButtons(ContentBox);
ChildSlot
[ContentBox];
}
void SSkeletonSelectorWindow::ConstructWindowFromMesh(USkeletalMesh* InSkeletalMesh)
{
TArray<FName> BoneNames;
for (int32 I = 0; I < InSkeletalMesh->GetRefSkeleton().GetRawBoneNum(); ++I)
{
BoneNames.Add(InSkeletalMesh->GetRefSkeleton().GetBoneName(I));
}
TSharedRef<SVerticalBox> ContentBox = SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1).Padding(2)
[SAssignNew(SkeletonWidget, SSkeletonCompareWidget)
.Object(InSkeletalMesh)
.BoneNames(&BoneNames)];
ConstructButtons(ContentBox);
ChildSlot
[ContentBox];
}
void SSkeletonSelectorWindow::ConstructWindow()
{
TSharedRef<SVerticalBox> ContentBox = SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1).Padding(2)
[SAssignNew(SkeletonWidget, SSkeletonListWidget)];
ConstructButtons(ContentBox);
ChildSlot
[ContentBox];
}
TSharedPtr<SWindow> SAnimationRemapSkeleton::DialogWindow;
bool SAnimationRemapSkeleton::OnShouldFilterAsset(const struct FAssetData& AssetData)
{
USkeleton* AssetSkeleton = nullptr;
if (AssetData.IsAssetLoaded())
{
AssetSkeleton = Cast<USkeleton>(AssetData.GetAsset());
}
// do not show same skeleton
if (OldSkeleton && OldSkeleton == AssetSkeleton)
{
return true;
}
if (bShowOnlyCompatibleSkeletons)
{
if (OldSkeleton && OldSkeleton->GetRig())
{
URig* Rig = OldSkeleton->GetRig();
const FString Value = AssetData.GetTagValueRef<FString>(USkeleton::RigTag);
if (Rig->GetFullName() == Value)
{
return false;
}
// if loaded, check to see if it has same rig
if (AssetData.IsAssetLoaded())
{
USkeleton* LoadedSkeleton = Cast<USkeleton>(AssetData.GetAsset());
if (LoadedSkeleton && LoadedSkeleton->GetRig() == Rig)
{
return false;
}
}
}
return true;
}
return false;
}
void SAnimationRemapSkeleton::UpdateAssetPicker()
{
FAssetPickerConfig AssetPickerConfig;
AssetPickerConfig.Filter.ClassNames.Add(USkeleton::StaticClass()->GetFName());
AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateSP(this, &SAnimationRemapSkeleton::OnAssetSelectedFromPicker);
AssetPickerConfig.bAllowNullSelection = false;
AssetPickerConfig.InitialAssetViewType = EAssetViewType::Column;
AssetPickerConfig.OnShouldFilterAsset = FOnShouldFilterAsset::CreateSP(this, &SAnimationRemapSkeleton::OnShouldFilterAsset);
AssetPickerConfig.bShowPathInColumnView = true;
AssetPickerConfig.bShowTypeInColumnView = false;
FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
if (AssetPickerBox.IsValid())
{
AssetPickerBox->SetContent(
ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig));
}
}
void SAnimationRemapSkeleton::Construct(const FArguments& InArgs)
{
OldSkeleton = InArgs._CurrentSkeleton;
NewSkeleton = nullptr;
WidgetWindow = InArgs._WidgetWindow;
bRemapReferencedAssets = true;
bConvertSpaces = false;
bShowOnlyCompatibleSkeletons = false;
OnRetargetAnimationDelegate = InArgs._OnRetargetDelegate;
bShowDuplicateAssetOption = InArgs._ShowDuplicateAssetOption;
TSharedRef<SVerticalBox> RetargetWidget = SNew(SVerticalBox);
RetargetWidget->AddSlot()
[SNew(STextBlock)
.AutoWrapText(true)
.Font(FEditorStyle::GetFontStyle("Persona.RetargetManager.SmallBoldFont"))
.Text(LOCTEXT("RetargetBasePose_OptionLabel", "Retarget Options"))];
if (InArgs._ShowRemapOption)
{
RetargetWidget->AddSlot()
[SNew(SCheckBox)
.IsChecked(this, &SAnimationRemapSkeleton::IsRemappingReferencedAssets)
.OnCheckStateChanged(this, &SAnimationRemapSkeleton::OnRemappingReferencedAssetsChanged)
[SNew(STextBlock).Text(LOCTEXT("RemapSkeleton_RemapAssets", "Remap referenced assets "))]];
bRemapReferencedAssets = true;
if (InArgs._ShowExistingRemapOption)
{
RetargetWidget->AddSlot()
[SNew(SCheckBox)
.IsChecked(this, &SAnimationRemapSkeleton::IsRemappingToExistingAssetsChecked)
.IsEnabled(this, &SAnimationRemapSkeleton::IsRemappingToExistingAssetsEnabled)
.OnCheckStateChanged(this, &SAnimationRemapSkeleton::OnRemappingToExistingAssetsChanged)
[SNew(STextBlock)
.Text(LOCTEXT("RemapSkeleton_RemapToExisting", "Allow remapping to existing assets"))]];
// Not by default, user must specify
bAllowRemappingToExistingAssets = false;
}
}
if (InArgs._ShowConvertSpacesOption)
{
TSharedPtr<SToolTip> ConvertSpaceTooltip = IDocumentation::Get()->CreateToolTip(FText::FromString("Check if you'd like to convert animation data to new skeleton space. If this is false, it won't convert any animation data to new space."),
nullptr, FString("Shared/Editors/Persona"), FString("AnimRemapSkeleton_ConvertSpace"));
RetargetWidget->AddSlot()
[SNew(SCheckBox)
.IsChecked(this, &SAnimationRemapSkeleton::IsConvertSpacesChecked)
.OnCheckStateChanged(this, &SAnimationRemapSkeleton::OnConvertSpacesCheckChanged)
[SNew(STextBlock).Text(LOCTEXT("RemapSkeleton_ConvertSpaces", "Convert Spaces to new Skeleton")).ToolTip(ConvertSpaceTooltip)]];
bConvertSpaces = true;
}
if (InArgs._ShowCompatibleDisplayOption)
{
TSharedPtr<SToolTip> ConvertSpaceTooltip = IDocumentation::Get()->CreateToolTip(FText::FromString("Check if you'd like to show only the skeleton that uses the same rig."),
nullptr, FString("Shared/Editors/Persona"), FString("AnimRemapSkeleton_ShowCompatbielSkeletons")); // @todo add tooltip
RetargetWidget->AddSlot()
[SNew(SCheckBox)
.IsChecked(this, &SAnimationRemapSkeleton::IsShowOnlyCompatibleSkeletonsChecked)
.IsEnabled(this, &SAnimationRemapSkeleton::IsShowOnlyCompatibleSkeletonsEnabled)
.OnCheckStateChanged(this, &SAnimationRemapSkeleton::OnShowOnlyCompatibleSkeletonsCheckChanged)
[SNew(STextBlock).Text(LOCTEXT("RemapSkeleton_ShowCompatible", "Show Only Compatible Skeletons")).ToolTip(ConvertSpaceTooltip)]];
bShowOnlyCompatibleSkeletons = true;
}
TSharedRef<SHorizontalBox> OptionWidget = SNew(SHorizontalBox);
OptionWidget->AddSlot()
[RetargetWidget];
if (bShowDuplicateAssetOption)
{
TSharedRef<SVerticalBox> NameOptionWidget = SNew(SVerticalBox);
NameOptionWidget->AddSlot()
[SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight().Padding(2, 3)[SNew(STextBlock).AutoWrapText(true).Font(FEditorStyle::GetFontStyle("Persona.RetargetManager.SmallBoldFont")).Text(LOCTEXT("RetargetBasePose_RenameLable", "New Asset Name"))]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2, 1)
[SNew(SHorizontalBox) + SHorizontalBox::Slot()[SNew(STextBlock).Text(LOCTEXT("RemapSkeleton_DupeName_Prefix", "Prefix"))]
+ SHorizontalBox::Slot()
[SNew(SEditableTextBox)
.Text(this, &SAnimationRemapSkeleton::GetPrefixName)
.MinDesiredWidth(100)
.OnTextChanged(this, &SAnimationRemapSkeleton::SetPrefixName)
.IsReadOnly(false)
.RevertTextOnEscape(true)]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2, 1)
[SNew(SHorizontalBox) + SHorizontalBox::Slot()[SNew(STextBlock).Text(LOCTEXT("RemapSkeleton_DupeName_Suffix", "Suffix"))]
+ SHorizontalBox::Slot()
[SNew(SEditableTextBox)
.Text(this, &SAnimationRemapSkeleton::GetSuffixName)
.MinDesiredWidth(100)
.OnTextChanged(this, &SAnimationRemapSkeleton::SetSuffixName)
.IsReadOnly(false)
.RevertTextOnEscape(true)]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2, 1)
[SNew(SHorizontalBox) + SHorizontalBox::Slot()[SNew(STextBlock).Text(LOCTEXT("RemapSkeleton_DupeName_ReplaceFrom", "Replace "))]
+ SHorizontalBox::Slot()
[SNew(SEditableTextBox)
.Text(this, &SAnimationRemapSkeleton::GetReplaceFrom)
.MinDesiredWidth(50)
.OnTextChanged(this, &SAnimationRemapSkeleton::SetReplaceFrom)
.IsReadOnly(false)
.RevertTextOnEscape(true)]
+ SHorizontalBox::Slot()
.Padding(5, 0)
.HAlign(HAlign_Center)
[SNew(STextBlock).Text(LOCTEXT("RemapSkeleton_DupeName_ReplaceTo", "with "))]
+ SHorizontalBox::Slot()
[SNew(SEditableTextBox)
.Text(this, &SAnimationRemapSkeleton::GetReplaceTo)
.MinDesiredWidth(50)
.OnTextChanged(this, &SAnimationRemapSkeleton::SetReplaceTo)
.IsReadOnly(false)
.RevertTextOnEscape(true)]]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2, 3)
[SNew(SHorizontalBox) + SHorizontalBox::Slot()
.Padding(5, 5)
[SNew(STextBlock).Text(this, &SAnimationRemapSkeleton::GetExampleText).Font(FEditorStyle::GetFontStyle("Persona.RetargetManager.ItalicFont"))]]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2, 3)
[SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[SNew(STextBlock)
.Text(LOCTEXT("RemapSkeleton_DupeName_Folder", "Folder "))
.Font(FEditorStyle::GetFontStyle("Persona.RetargetManager.SmallBoldFont"))]
+ SHorizontalBox::Slot()
.FillWidth(1)
.HAlign(HAlign_Center)
[SNew(STextBlock).Text(this, &SAnimationRemapSkeleton::GetFolderPath)]
+ SHorizontalBox::Slot()
.AutoWidth()
[SNew(SButton)
.HAlign(HAlign_Center)
.Text(LOCTEXT("RemapSkeleton_DupeName_ShowFolderOption", "Change..."))
.OnClicked(this, &SAnimationRemapSkeleton::ShowFolderOption)]]];
OptionWidget->AddSlot()
[NameOptionWidget];
}
TSharedPtr<SToolTip> SkeletonTooltip = IDocumentation::Get()->CreateToolTip(FText::FromString("Pick a skeleton for this mesh"), nullptr, FString("Shared/Editors/Persona"), FString("Skeleton"));
this->ChildSlot
[SNew(SHorizontalBox) + SHorizontalBox::Slot().AutoWidth()[SNew(SVerticalBox)
+ SVerticalBox::Slot().AutoHeight().Padding(2).HAlign(HAlign_Fill)[SNew(SHorizontalBox)
+ SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center) + SHorizontalBox::Slot()[SNew(STextBlock).Text(LOCTEXT("CurrentlySelectedSkeletonLabel_SelectSkeleton", "Select Skeleton")).Font(FCoreStyle::GetDefaultFontStyle("Regular", 16)).ToolTip(SkeletonTooltip)] + SHorizontalBox::Slot().FillWidth(1).HAlign(HAlign_Left).VAlign(VAlign_Center)[IDocumentation::Get()->CreateAnchor(FString("Engine/Animation/Skeleton"))]]
+ SVerticalBox::Slot().AutoHeight().Padding(5)[SNew(STextBlock).Font(FEditorStyle::GetFontStyle("Persona.RetargetManager.FilterFont")).Text(InArgs._WarningMessage)]
+ SVerticalBox::Slot().AutoHeight().Padding(5)[SNew(SSeparator)]
+ SVerticalBox::Slot().MaxHeight(500)[SAssignNew(AssetPickerBox, SBox).WidthOverride(400).HeightOverride(300)]
+ SVerticalBox::Slot().AutoHeight().Padding(5)[SNew(SSeparator)]
+ SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Right).VAlign(VAlign_Bottom)[SNew(SUniformGridPanel).SlotPadding(FEditorStyle::GetMargin("StandardDialog.SlotPadding")).MinDesiredSlotWidth(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotWidth")).MinDesiredSlotHeight(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotHeight")) + SUniformGridPanel::Slot(0, 0)[SNew(SButton).HAlign(HAlign_Center).Text(LOCTEXT("RemapSkeleton_Apply", "Retarget")).IsEnabled(this, &SAnimationRemapSkeleton::CanApply).OnClicked(this, &SAnimationRemapSkeleton::OnApply).HAlign(HAlign_Center).ContentPadding(FEditorStyle::GetMargin("StandardDialog.ContentPadding"))] + SUniformGridPanel::Slot(1, 0)[SNew(SButton).HAlign(HAlign_Center).Text(LOCTEXT("RemapSkeleton_Cancel", "Cancel")).ContentPadding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")).OnClicked(this, &SAnimationRemapSkeleton::OnCancel)]]]
+ SHorizontalBox::Slot()
.Padding(2)
.AutoWidth()
[SNew(SSeparator)
.Orientation(Orient_Vertical)]
+ SHorizontalBox::Slot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.AutoWidth()
[SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight().Padding(5, 5)[
// put nice message here
SNew(STextBlock).AutoWrapText(true).Font(FEditorStyle::GetFontStyle("Persona.RetargetManager.FilterFont")).ColorAndOpacity(FLinearColor::Red).Text(LOCTEXT("RetargetBasePose_WarningMessage", "*Make sure you have the similar retarget base pose. \nIf they don't look alike here, you can edit your base pose in the Retarget Manager window."))]
+ SVerticalBox::Slot()
.FillHeight(1)
.Padding(0, 5)
[SNew(SHorizontalBox) + SHorizontalBox::Slot()[SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight()[SNew(STextBlock).Text(LOCTEXT("SourceSkeleteonTitle", "[Source]")).Font(FEditorStyle::GetFontStyle("Persona.RetargetManager.BoldFont")).AutoWrapText(true)]
+ SVerticalBox::Slot().AutoHeight().Padding(5, 5)[SAssignNew(SourceViewport, SBasePoseViewport).Skeleton(OldSkeleton)]]
+ SHorizontalBox::Slot()
[SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight()[SNew(STextBlock).Text(LOCTEXT("TargetSkeleteonTitle", "[Target]")).Font(FEditorStyle::GetFontStyle("Persona.RetargetManager.BoldFont")).AutoWrapText(true)]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5, 5)
[SAssignNew(TargetViewport, SBasePoseViewport)
.Skeleton(nullptr)]]]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 5)
[SNew(SVerticalBox) + SVerticalBox::Slot()
.AutoHeight()
.Padding(5, 5)
[OptionWidget]]]];
UpdateAssetPicker();
UpdateExampleText();
}
FReply SAnimationRemapSkeleton::ShowFolderOption()
{
TSharedRef<SSelectFolderDlg> NewAnimDlg =
SNew(SSelectFolderDlg);
if (NewAnimDlg->ShowModal() != EAppReturnType::Cancel)
{
NameDuplicateRule.FolderPath = NewAnimDlg->GetAssetPath();
}
if (WidgetWindow.IsValid())
{
WidgetWindow.Pin()->BringToFront(true);
}
return FReply::Handled();
}
void SAnimationRemapSkeleton::UpdateExampleText()
{
FString ReplaceFrom = FString::Printf(TEXT("Old Name : ###%s###"), *NameDuplicateRule.ReplaceFrom);
FString ReplaceTo = FString::Printf(TEXT("New Name : %s###%s###%s"), *NameDuplicateRule.Prefix, *NameDuplicateRule.ReplaceTo, *NameDuplicateRule.Suffix);
ExampleText = FText::FromString(FString::Printf(TEXT("%s\n%s"), *ReplaceFrom, *ReplaceTo));
}
ECheckBoxState SAnimationRemapSkeleton::IsRemappingReferencedAssets() const
{
return bRemapReferencedAssets ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
void SAnimationRemapSkeleton::OnRemappingReferencedAssetsChanged(ECheckBoxState InNewRadioState)
{
bRemapReferencedAssets = (InNewRadioState == ECheckBoxState::Checked);
}
ECheckBoxState SAnimationRemapSkeleton::IsRemappingToExistingAssetsChecked() const
{
return bAllowRemappingToExistingAssets ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
bool SAnimationRemapSkeleton::IsRemappingToExistingAssetsEnabled() const
{
return bRemapReferencedAssets;
}
void SAnimationRemapSkeleton::OnRemappingToExistingAssetsChanged(ECheckBoxState InNewRadioState)
{
bAllowRemappingToExistingAssets = (InNewRadioState == ECheckBoxState::Checked);
}
ECheckBoxState SAnimationRemapSkeleton::IsConvertSpacesChecked() const
{
return bConvertSpaces ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
void SAnimationRemapSkeleton::OnConvertSpacesCheckChanged(ECheckBoxState InNewRadioState)
{
bConvertSpaces = (InNewRadioState == ECheckBoxState::Checked);
}
bool SAnimationRemapSkeleton::IsShowOnlyCompatibleSkeletonsEnabled() const
{
// if convert space is false, compatible skeletons won't matter either.
return (bConvertSpaces == true);
}
ECheckBoxState SAnimationRemapSkeleton::IsShowOnlyCompatibleSkeletonsChecked() const
{
return bShowOnlyCompatibleSkeletons ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
void SAnimationRemapSkeleton::OnShowOnlyCompatibleSkeletonsCheckChanged(ECheckBoxState InNewRadioState)
{
bShowOnlyCompatibleSkeletons = (InNewRadioState == ECheckBoxState::Checked);
UpdateAssetPicker();
}
bool SAnimationRemapSkeleton::CanApply() const
{
return (NewSkeleton != nullptr && NewSkeleton != OldSkeleton);
}
void SAnimationRemapSkeleton::OnAssetSelectedFromPicker(const FAssetData& AssetData)
{
if (AssetData.GetAsset())
{
NewSkeleton = Cast<USkeleton>(AssetData.GetAsset());
TargetViewport->SetSkeleton(NewSkeleton);
}
}
FReply SAnimationRemapSkeleton::OnApply()
{
if (OnRetargetAnimationDelegate.IsBound())
{
OnRetargetAnimationDelegate.Execute(OldSkeleton, NewSkeleton, bRemapReferencedAssets, bAllowRemappingToExistingAssets, bConvertSpaces, bShowDuplicateAssetOption ? &NameDuplicateRule : nullptr);
}
CloseWindow();
return FReply::Handled();
}
FReply SAnimationRemapSkeleton::OnCancel()
{
NewSkeleton = nullptr;
CloseWindow();
return FReply::Handled();
}
void SAnimationRemapSkeleton::OnRemapDialogClosed(const TSharedRef<SWindow>& Window)
{
NewSkeleton = nullptr;
DialogWindow = nullptr;
}
void SAnimationRemapSkeleton::CloseWindow()
{
if (WidgetWindow.IsValid())
{
WidgetWindow.Pin()->RequestDestroyWindow();
}
}
void SAnimationRemapSkeleton::ShowWindow(USkeleton* OldSkeleton, const FText& WarningMessage, bool bDuplicateAssets, FOnRetargetAnimation RetargetDelegate)
{
if (DialogWindow.IsValid())
{
FSlateApplication::Get().DestroyWindowImmediately(DialogWindow.ToSharedRef());
}
DialogWindow = SNew(SWindow)
.Title(LOCTEXT("RemapSkeleton", "Select Skeleton"))
.SupportsMinimize(false)
.SupportsMaximize(false)
.SizingRule(ESizingRule::Autosized);
TSharedPtr<class SAnimationRemapSkeleton> DialogWidget;
TSharedPtr<SBorder> DialogWrapper =
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[SAssignNew(DialogWidget, SAnimationRemapSkeleton)
.CurrentSkeleton(OldSkeleton)
.WidgetWindow(DialogWindow)
.WarningMessage(WarningMessage)
.ShowRemapOption(true)
.ShowExistingRemapOption(true)
.ShowConvertSpacesOption(OldSkeleton != nullptr)
.ShowCompatibleDisplayOption(OldSkeleton != nullptr)
.ShowDuplicateAssetOption(bDuplicateAssets)
.OnRetargetDelegate(RetargetDelegate)];
DialogWindow->SetOnWindowClosed(FRequestDestroyWindowOverride::CreateSP(DialogWidget.Get(), &SAnimationRemapSkeleton::OnRemapDialogClosed));
DialogWindow->SetContent(DialogWrapper.ToSharedRef());
FSlateApplication::Get().AddWindow(DialogWindow.ToSharedRef());
}
////////////////////////////////////////////////////
FDlgRemapSkeleton::FDlgRemapSkeleton(USkeleton* Skeleton)
{
if (FSlateApplication::IsInitialized())
{
DialogWindow = SNew(SWindow)
.Title(LOCTEXT("RemapSkeleton", "Select Skeleton"))
.SupportsMinimize(false)
.SupportsMaximize(false)
.SizingRule(ESizingRule::Autosized);
TSharedPtr<SBorder> DialogWrapper =
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[SAssignNew(DialogWidget, SAnimationRemapSkeleton)
.CurrentSkeleton(Skeleton)
.WidgetWindow(DialogWindow)];
DialogWindow->SetContent(DialogWrapper.ToSharedRef());
}
}
bool FDlgRemapSkeleton::ShowModal()
{
GEditor->EditorAddModalWindow(DialogWindow.ToSharedRef());
NewSkeleton = DialogWidget.Get()->NewSkeleton;
return (NewSkeleton != nullptr && NewSkeleton != DialogWidget.Get()->OldSkeleton);
}
//////////////////////////////////////////////////////////////
void SRemapFailures::Construct(const FArguments& InArgs)
{
for (auto RemapIt = InArgs._FailedRemaps.CreateConstIterator(); RemapIt; ++RemapIt)
{
FailedRemaps.Add(MakeShareable(new FText(*RemapIt)));
}
ChildSlot
[SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("Docking.Tab.ContentAreaBrush"))
.Padding(FMargin(4, 8, 4, 4))
[SNew(SVerticalBox)
// Title text
+ SVerticalBox::Slot()
.AutoHeight()
[SNew(STextBlock).Text(LOCTEXT("RemapFailureTitle", "The following assets could not be Remaped."))]
// Failure list
+ SVerticalBox::Slot()
.Padding(0, 8)
.FillHeight(1.f)
[SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
[SNew(SListView<TSharedRef<FText>>)
.ListItemsSource(&FailedRemaps)
.SelectionMode(ESelectionMode::None)
.OnGenerateRow(this, &SRemapFailures::MakeListViewWidget)]]
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Right)
[SNew(SUniformGridPanel)
.SlotPadding(FEditorStyle::GetMargin("StandardDialog.SlotPadding"))
.MinDesiredSlotWidth(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotWidth"))
.MinDesiredSlotHeight(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotHeight")) +
SUniformGridPanel::Slot(0, 0)
[SNew(SButton)
.HAlign(HAlign_Center)
.ContentPadding(FEditorStyle::GetMargin("StandardDialog.ContentPadding"))
.Text(LOCTEXT("RemapFailuresCloseButton", "Close"))
.OnClicked(this, &SRemapFailures::CloseClicked)]]]];
}
void SRemapFailures::OpenRemapFailuresDialog(const TArray<FText>& InFailedRemaps)
{
TSharedRef<SWindow> RemapWindow = SNew(SWindow)
.Title(LOCTEXT("FailedRemapsDialog", "Failed Remaps"))
.ClientSize(FVector2D(800, 400))
.SupportsMaximize(false)
.SupportsMinimize(false)
[SNew(SRemapFailures).FailedRemaps(InFailedRemaps)];
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
if (MainFrameModule.GetParentWindow().IsValid())
{
FSlateApplication::Get().AddWindowAsNativeChild(RemapWindow, MainFrameModule.GetParentWindow().ToSharedRef());
}
else
{
FSlateApplication::Get().AddWindow(RemapWindow);
}
}
TSharedRef<ITableRow> SRemapFailures::MakeListViewWidget(TSharedRef<FText> Item, const TSharedRef<STableViewBase>& OwnerTable)
{
return SNew(STableRow<TSharedRef<FText>>, OwnerTable)
[SNew(STextBlock).Text(Item.Get())];
}
FReply SRemapFailures::CloseClicked()
{
TSharedPtr<SWindow> Window = FSlateApplication::Get().FindWidgetWindow(AsShared());
if (Window.IsValid())
{
Window->RequestDestroyWindow();
}
return FReply::Handled();
}
void SSkeletonBoneRemoval::Construct(const FArguments& InArgs)
{
bShouldContinue = false;
WidgetWindow = InArgs._WidgetWindow;
for (int32 I = 0; I < InArgs._BonesToRemove.Num(); ++I)
{
BoneNames.Add(MakeShareable(new FName(InArgs._BonesToRemove[I])));
}
this->ChildSlot
[SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2)
.HAlign(HAlign_Fill)
[SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center) +
SHorizontalBox::Slot()
[SNew(STextBlock)
.Text(LOCTEXT("BoneRemovalLabel", "Bone Removal"))
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 16))]]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2, 10)
.HAlign(HAlign_Fill)
[SNew(SSeparator)
.Orientation(Orient_Horizontal)]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5)
[SNew(STextBlock)
.WrapTextAt(400.f)
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 10))
.Text(InArgs._WarningMessage)]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5)
[SNew(SSeparator)]
+ SVerticalBox::Slot()
.MaxHeight(300)
.Padding(5)
[SNew(SListView<TSharedPtr<FName>>)
.OnGenerateRow(this, &SSkeletonBoneRemoval::GenerateSkeletonBoneRow)
.ListItemsSource(&BoneNames)
.HeaderRow(
SNew(SHeaderRow) + SHeaderRow::Column(TEXT("Bone Name"))
.DefaultLabel(NSLOCTEXT("SkeletonWidget", "BoneName", "Bone Name")))]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5)
[SNew(SSeparator)]
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Right)
.VAlign(VAlign_Bottom)
[SNew(SUniformGridPanel)
.SlotPadding(FEditorStyle::GetMargin("StandardDialog.SlotPadding"))
.MinDesiredSlotWidth(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotWidth"))
.MinDesiredSlotHeight(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotHeight")) +
SUniformGridPanel::Slot(0, 0)
[SNew(SButton).HAlign(HAlign_Center).Text(LOCTEXT("BoneRemoval_Ok", "Ok")).OnClicked(this, &SSkeletonBoneRemoval::OnOk).HAlign(HAlign_Center).ContentPadding(FEditorStyle::GetMargin("StandardDialog.ContentPadding"))] +
SUniformGridPanel::Slot(1, 0)
[SNew(SButton).HAlign(HAlign_Center).Text(LOCTEXT("BoneRemoval_Cancel", "Cancel")).ContentPadding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")).OnClicked(this, &SSkeletonBoneRemoval::OnCancel)]]];
}
FReply SSkeletonBoneRemoval::OnOk()
{
bShouldContinue = true;
CloseWindow();
return FReply::Handled();
}
FReply SSkeletonBoneRemoval::OnCancel()
{
CloseWindow();
return FReply::Handled();
}
void SSkeletonBoneRemoval::CloseWindow()
{
if (WidgetWindow.IsValid())
{
WidgetWindow.Pin()->RequestDestroyWindow();
}
}
bool SSkeletonBoneRemoval::ShowModal(const TArray<FName> BonesToRemove, const FText& WarningMessage)
{
TSharedPtr<class SSkeletonBoneRemoval> DialogWidget;
TSharedPtr<SWindow> DialogWindow = SNew(SWindow)
.Title(LOCTEXT("RemapSkeleton", "Select Skeleton"))
.SupportsMinimize(false)
.SupportsMaximize(false)
.SizingRule(ESizingRule::Autosized);
TSharedPtr<SBorder> DialogWrapper =
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[SAssignNew(DialogWidget, SSkeletonBoneRemoval)
.WidgetWindow(DialogWindow)
.BonesToRemove(BonesToRemove)
.WarningMessage(WarningMessage)];
DialogWindow->SetContent(DialogWrapper.ToSharedRef());
GEditor->EditorAddModalWindow(DialogWindow.ToSharedRef());
return DialogWidget.Get()->bShouldContinue;
}
////////////////////////////////////////
class FBasePoseViewportClient: public FEditorViewportClient
{
public:
FBasePoseViewportClient(FPreviewScene& InPreviewScene, const TSharedRef<SBasePoseViewport>& InBasePoseViewport)
: FEditorViewportClient(nullptr, &InPreviewScene, StaticCastSharedRef<SEditorViewport>(InBasePoseViewport))
{
SetViewMode(VMI_Lit);
// Always composite editor objects after post processing in the editor
EngineShowFlags.SetCompositeEditorPrimitives(true);
EngineShowFlags.DisableAdvancedFeatures();
UpdateLighting();
// Setup defaults for the common draw helper.
DrawHelper.bDrawPivot = false;
DrawHelper.bDrawWorldBox = false;
DrawHelper.bDrawKillZ = false;
DrawHelper.bDrawGrid = true;
DrawHelper.GridColorAxis = FColor(70, 70, 70);
DrawHelper.GridColorMajor = FColor(40, 40, 40);
DrawHelper.GridColorMinor = FColor(20, 20, 20);
DrawHelper.PerspectiveGridSize = HALF_WORLD_MAX1;
bDisableInput = true;
}
// FEditorViewportClient interface
virtual void Tick(float DeltaTime) override
{
if (PreviewScene)
{
PreviewScene->GetWorld()->Tick(LEVELTICK_All, DeltaTime);
}
}
virtual FSceneInterface* GetScene() const override
{
return PreviewScene->GetScene();
}
virtual FLinearColor GetBackgroundColor() const override
{
return FLinearColor::White;
}
// End of FEditorViewportClient
void UpdateLighting()
{
const USkeletalMeshEditorSettings* Options = GetDefault<USkeletalMeshEditorSettings>();
PreviewScene->SetLightDirection(Options->AnimPreviewLightingDirection);
PreviewScene->SetLightColor(Options->AnimPreviewDirectionalColor);
PreviewScene->SetLightBrightness(Options->AnimPreviewLightBrightness);
}
};
////////////////////////////////
// SBasePoseViewport
void SBasePoseViewport::Construct(const FArguments& InArgs)
{
SEditorViewport::Construct(SEditorViewport::FArguments());
PreviewComponent = NewObject<UDebugSkelMeshComponent>();
PreviewComponent->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones;
PreviewScene.AddComponent(PreviewComponent, FTransform::Identity);
SetSkeleton(InArgs._Skeleton);
}
void SBasePoseViewport::SetSkeleton(USkeleton* Skeleton)
{
if (Skeleton != TargetSkeleton)
{
TargetSkeleton = Skeleton;
if (TargetSkeleton)
{
USkeletalMesh* PreviewSkeletalMesh = Skeleton->GetPreviewMesh();
if (PreviewSkeletalMesh)
{
PreviewComponent->SetSkeletalMesh(PreviewSkeletalMesh);
PreviewComponent->EnablePreview(true, nullptr);
// PreviewComponent->AnimScriptInstance = PreviewComponent->PreviewInstance;
PreviewComponent->PreviewInstance->SetForceRetargetBasePose(true);
PreviewComponent->RefreshBoneTransforms(nullptr);
// Place the camera at a good viewer position
FVector NewPosition = Client->GetViewLocation();
NewPosition.Normalize();
NewPosition *= (PreviewSkeletalMesh->GetImportedBounds().SphereRadius * 1.5f);
Client->SetViewLocation(NewPosition);
}
else
{
PreviewComponent->SetSkeletalMesh(nullptr);
}
}
else
{
PreviewComponent->SetSkeletalMesh(nullptr);
}
Client->Invalidate();
}
}
SBasePoseViewport::SBasePoseViewport()
: PreviewScene(FPreviewScene::ConstructionValues())
{
}
bool SBasePoseViewport::IsVisible() const
{
return true;
}
TSharedRef<FEditorViewportClient> SBasePoseViewport::MakeEditorViewportClient()
{
TSharedPtr<FEditorViewportClient> EditorViewportClient = MakeShareable(new FBasePoseViewportClient(PreviewScene, SharedThis(this)));
EditorViewportClient->ViewportType = LVT_Perspective;
EditorViewportClient->bSetListenerPosition = false;
EditorViewportClient->SetViewLocation(EditorViewportDefs::DefaultPerspectiveViewLocation);
EditorViewportClient->SetViewRotation(EditorViewportDefs::DefaultPerspectiveViewRotation);
EditorViewportClient->SetRealtime(false);
EditorViewportClient->VisibilityDelegate.BindSP(this, &SBasePoseViewport::IsVisible);
EditorViewportClient->SetViewMode(VMI_Lit);
return EditorViewportClient.ToSharedRef();
}
TSharedPtr<SWidget> SBasePoseViewport::MakeViewportToolbar()
{
return nullptr;
}
/////////////////////////////////////////////////
// select folder dialog
//////////////////////////////////////////////////
void SSelectFolderDlg::Construct(const FArguments& InArgs)
{
AssetPath = FText::FromString(FPackageName::GetLongPackagePath(InArgs._DefaultAssetPath.ToString()));
if (AssetPath.IsEmpty())
{
AssetPath = FText::FromString(TEXT("/Game"));
}
FPathPickerConfig PathPickerConfig;
PathPickerConfig.DefaultPath = AssetPath.ToString();
PathPickerConfig.OnPathSelected = FOnPathSelected::CreateSP(this, &SSelectFolderDlg::OnPathChange);
PathPickerConfig.bAddDefaultPath = true;
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
SWindow::Construct(SWindow::FArguments()
.Title(LOCTEXT("SSelectFolderDlg_Title", "Create New Animation Object"))
.SupportsMinimize(false)
.SupportsMaximize(false)
//.SizingRule( ESizingRule::Autosized )
.ClientSize(FVector2D(450, 450))
[SNew(SVerticalBox)
+ SVerticalBox::Slot() // Add user input block
.Padding(2)
[SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
[SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[SNew(STextBlock)
.Text(LOCTEXT("SelectPath", "Select Path"))
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 14))]
+ SVerticalBox::Slot()
.FillHeight(1)
.Padding(3)
[ContentBrowserModule.Get().CreatePathPicker(PathPickerConfig)]]]
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Right)
.Padding(5)
[SNew(SUniformGridPanel)
.SlotPadding(FEditorStyle::GetMargin("StandardDialog.SlotPadding"))
.MinDesiredSlotWidth(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotWidth"))
.MinDesiredSlotHeight(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotHeight")) +
SUniformGridPanel::Slot(0, 0)
[SNew(SButton)
.HAlign(HAlign_Center)
.ContentPadding(FEditorStyle::GetMargin("StandardDialog.ContentPadding"))
.Text(LOCTEXT("OK", "OK"))
.OnClicked(this, &SSelectFolderDlg::OnButtonClick, EAppReturnType::Ok)] +
SUniformGridPanel::Slot(1, 0)
[SNew(SButton)
.HAlign(HAlign_Center)
.ContentPadding(FEditorStyle::GetMargin("StandardDialog.ContentPadding"))
.Text(LOCTEXT("Cancel", "Cancel"))
.OnClicked(this, &SSelectFolderDlg::OnButtonClick, EAppReturnType::Cancel)]]]);
}
void SSelectFolderDlg::OnPathChange(const FString& NewPath)
{
AssetPath = FText::FromString(NewPath);
}
FReply SSelectFolderDlg::OnButtonClick(EAppReturnType::Type ButtonID)
{
UserResponse = ButtonID;
RequestDestroyWindow();
return FReply::Handled();
}
EAppReturnType::Type SSelectFolderDlg::ShowModal()
{
GEditor->EditorAddModalWindow(SharedThis(this));
return UserResponse;
}
FString SSelectFolderDlg::GetAssetPath()
{
return AssetPath.ToString();
}
//////////////////////////////////////////////////////////////////////////
// Window for remapping assets when retargetting
TSharedPtr<SWindow> SAnimationRemapAssets::DialogWindow;
void SAnimationRemapAssets::Construct(const FArguments& InArgs)
{
NewSkeleton = InArgs._NewSkeleton;
RetargetContext = InArgs._RetargetContext;
TSharedPtr<SVerticalBox> ListBox;
TArray<UObject*> Duplicates = RetargetContext->GetAllDuplicates();
for (UObject* Asset: Duplicates)
{
// We don't want to add anim blueprints here, just animation assets
if (Asset->GetClass() != UAnimBlueprint::StaticClass())
{
AssetListInfo.Add(FDisplayedAssetEntryInfo::Make(Asset, NewSkeleton));
}
}
this->ChildSlot
[SNew(SVerticalBox) + SVerticalBox::Slot().Padding(5.0f).AutoHeight()[SNew(STextBlock).Text(LOCTEXT("RemapAssetsDescription", "The assets shown below need to be duplicated or remapped for the new blueprint. Select a new animation to use in the new animation blueprint for each asset or leave blank to duplicate the existing asset.")).AutoWrapText(true)] + SVerticalBox::Slot().Padding(5.0f).AutoHeight().MaxHeight(500.0f)[SNew(SBorder).BorderImage(FEditorStyle::GetBrush("ToolPanel.DarkGroupBorder"))[SAssignNew(ListWidget, SRemapAssetEntryList).ItemHeight(20.0f).ListItemsSource(&AssetListInfo).OnGenerateRow(this, &SAnimationRemapAssets::OnGenerateMontageReferenceRow).SelectionMode(ESelectionMode::None).HeaderRow(SNew(SHeaderRow) + SHeaderRow::Column("AssetName").DefaultLabel(LOCTEXT("ColumnLabel_RemapAssetName", "Asset Name")) + SHeaderRow::Column("AssetType").DefaultLabel(LOCTEXT("ColumnLabel_RemapAssetType", "Asset Type")) + SHeaderRow::Column("AssetRemap").DefaultLabel(LOCTEXT("ColumnLabel_RemapAssetRemap", "Remapped Asset")))]] + SVerticalBox::Slot().Padding(5.0f).AutoHeight()[SNew(SHorizontalBox) + SHorizontalBox::Slot().VAlign(VAlign_Center).HAlign(HAlign_Center).FillWidth(0.2f)[SNew(SButton).ContentPadding(2.0f).OnClicked(this, &SAnimationRemapAssets::OnBestGuessClicked).Content()[SNew(STextBlock).Text(LOCTEXT("BestGuessButton", "Auto-Fill Using Best Guess"))]] + SHorizontalBox::Slot().VAlign(VAlign_Center).HAlign(HAlign_Center).Padding(FMargin(5.0f, 0.0f, 0.0f, 0.0f))[SNew(STextBlock).AutoWrapText(true).Text(LOCTEXT("BestGuessDescription", "Auto-Fill will look at the names of all compatible assets for the new skeleton and look for something similar to use for the remapped asset."))]]
+ SVerticalBox::Slot()
.Padding(5.0f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.AutoHeight()
[SNew(SBox)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.Content()
[SNew(SButton)
.ContentPadding(FEditorStyle::GetMargin("StandardDialog.ContentPadding"))
.OnClicked(this, &SAnimationRemapAssets::OnOkClicked)
.Content()
[SNew(STextBlock)
.Text(LOCTEXT("OkButton", "OK"))]]]];
}
void SAnimationRemapAssets::ShowWindow(FAnimationRetargetContext& RetargetContext, USkeleton* RetargetToSkeleton)
{
if (DialogWindow.IsValid())
{
FSlateApplication::Get().DestroyWindowImmediately(DialogWindow.ToSharedRef());
}
DialogWindow = SNew(SWindow)
.Title(LOCTEXT("RemapAssets", "Choose Assets to Remap"))
.SupportsMinimize(false)
.SupportsMaximize(false)
.HasCloseButton(false)
.MaxWidth(1024.0f)
.SizingRule(ESizingRule::Autosized);
TSharedPtr<class SAnimationRemapAssets> DialogWidget;
TSharedPtr<SBorder> DialogWrapper =
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[SAssignNew(DialogWidget, SAnimationRemapAssets)
.NewSkeleton(RetargetToSkeleton)
.RetargetContext(&RetargetContext)];
DialogWindow->SetOnWindowClosed(FRequestDestroyWindowOverride::CreateSP(DialogWidget.Get(), &SAnimationRemapAssets::OnDialogClosed));
DialogWindow->SetContent(DialogWrapper.ToSharedRef());
FSlateApplication::Get().AddModalWindow(DialogWindow.ToSharedRef(), nullptr);
}
TSharedRef<ITableRow> SAnimationRemapAssets::OnGenerateMontageReferenceRow(TSharedPtr<FDisplayedAssetEntryInfo> Item, const TSharedRef<STableViewBase>& OwnerTable)
{
return SNew(SAssetEntryRow, OwnerTable)
.DisplayedInfo(Item);
}
void SAnimationRemapAssets::OnDialogClosed(const TSharedRef<SWindow>& Window)
{
DialogWindow = nullptr;
}
FReply SAnimationRemapAssets::OnOkClicked()
{
for (TSharedPtr<FDisplayedAssetEntryInfo> AssetInfo: AssetListInfo)
{
if (AssetInfo->RemapAsset)
{
RetargetContext->AddRemappedAsset(Cast<UAnimationAsset>(AssetInfo->AnimAsset), Cast<UAnimationAsset>(AssetInfo->RemapAsset));
}
}
DialogWindow->RequestDestroyWindow();
return FReply::Handled();
}
FReply SAnimationRemapAssets::OnBestGuessClicked()
{
// collect all compatible assets
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
FString SkeletonName = FAssetData(NewSkeleton).GetExportTextName();
TArray<FAssetData> CompatibleAssets;
TArray<FAssetData> AssetDataList;
AssetRegistryModule.Get().GetAssetsByClass(UAnimationAsset::StaticClass()->GetFName(), AssetDataList, true);
for (const FAssetData& Data: AssetDataList)
{
if (Data.GetTagValueRef<FString>("Skeleton") == SkeletonName)
{
CompatibleAssets.Add(Data);
}
}
if (CompatibleAssets.Num() > 0)
{
// Do best guess analysis for the assets based on name.
for (TSharedPtr<FDisplayedAssetEntryInfo>& Info: AssetListInfo)
{
const FAssetData* BestMatchData = FindBestGuessMatch(FAssetData(Info->AnimAsset), CompatibleAssets);
Info->RemapAsset = BestMatchData ? BestMatchData->GetAsset() : nullptr;
}
}
ListWidget->RequestListRefresh();
return FReply::Handled();
}
const FAssetData* SAnimationRemapAssets::FindBestGuessMatch(const FAssetData& AssetData, const TArray<FAssetData>& PossibleAssets) const
{
int32 LowestScore = MAX_int32;
int32 FoundIndex = INDEX_NONE;
for (int32 Idx = 0; Idx < PossibleAssets.Num(); ++Idx)
{
const FAssetData& Data = PossibleAssets[Idx];
if (Data.AssetClass == AssetData.AssetClass)
{
int32 Distance = FAnimationRuntime::GetStringDistance(AssetData.AssetName.ToString(), Data.AssetName.ToString());
if (Distance < LowestScore)
{
LowestScore = Distance;
FoundIndex = Idx;
}
}
}
if (FoundIndex != INDEX_NONE)
{
return &PossibleAssets[FoundIndex];
}
return nullptr;
}
void SAssetEntryRow::Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView)
{
check(InArgs._DisplayedInfo.IsValid());
DisplayedInfo = InArgs._DisplayedInfo;
SkeletonExportName = FAssetData(DisplayedInfo->NewSkeleton).GetExportTextName();
SMultiColumnTableRow<TSharedPtr<FDisplayedAssetEntryInfo>>::Construct(FSuperRowType::FArguments(), InOwnerTableView);
}
TSharedRef<SWidget> SAssetEntryRow::GenerateWidgetForColumn(const FName& ColumnName)
{
if (ColumnName == TEXT("AssetName"))
{
return SNew(STextBlock)
.Text(FText::Format(LOCTEXT("AssetNameEntry", "{0}"), FText::FromString(DisplayedInfo->AnimAsset->GetName())));
}
else if (ColumnName == TEXT("AssetType"))
{
return SNew(STextBlock)
.Text(FText::Format(LOCTEXT("AssetTypeEntry", "{0}"), FText::FromString(DisplayedInfo->AnimAsset->GetClass()->GetName())));
}
else if (ColumnName == TEXT("AssetRemap"))
{
return SNew(SBox)
.Padding(2.0f)
[SNew(SComboButton)
.ToolTipText(LOCTEXT("AssetRemapTooltip", "Select compatible asset to remap to."))
.ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle")
.ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity"))
.OnGetMenuContent(this, &SAssetEntryRow::GetRemapMenuContent)
.ContentPadding(2.0f)
.ButtonContent()
[SNew(STextBlock)
.TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass")
.Font(FEditorStyle::GetFontStyle("PropertyWindow.NormalFont"))
.Text(this, &SAssetEntryRow::GetRemapMenuButtonText)]];
}
return SNullWidget::NullWidget;
}
TSharedRef<SWidget> SAssetEntryRow::GetRemapMenuContent()
{
FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
FAssetPickerConfig PickerConfig;
PickerConfig.SelectionMode = ESelectionMode::Single;
PickerConfig.Filter.ClassNames.Add(DisplayedInfo->AnimAsset->GetClass()->GetFName());
PickerConfig.OnAssetSelected = FOnAssetSelected::CreateSP(this, &SAssetEntryRow::OnAssetSelected);
PickerConfig.OnShouldFilterAsset = FOnShouldFilterAsset::CreateSP(this, &SAssetEntryRow::OnShouldFilterAsset);
PickerConfig.bAllowNullSelection = true;
return SNew(SBox)
.WidthOverride(384)
.HeightOverride(768)
[ContentBrowserModule.Get().CreateAssetPicker(PickerConfig)];
}
FText SAssetEntryRow::GetRemapMenuButtonText() const
{
FText NameText = (DisplayedInfo->RemapAsset) ? FText::FromString(*DisplayedInfo->RemapAsset->GetName()) : LOCTEXT("AssetRemapNone", "None");
return FText::Format(LOCTEXT("RemapButtonText", "{0}"), NameText);
}
void SAssetEntryRow::OnAssetSelected(const FAssetData& AssetData)
{
// Close the asset picker menu
FSlateApplication::Get().DismissAllMenus();
RemapAsset = AssetData.GetAsset();
DisplayedInfo->RemapAsset = RemapAsset.Get();
}
bool SAssetEntryRow::OnShouldFilterAsset(const FAssetData& AssetData) const
{
if (AssetData.GetTagValueRef<FString>("Skeleton") == SkeletonExportName)
{
return false;
}
return true;
}
TSharedRef<FDisplayedAssetEntryInfo> FDisplayedAssetEntryInfo::Make(UObject* InAsset, USkeleton* InNewSkeleton)
{
return MakeShareable(new FDisplayedAssetEntryInfo(InAsset, InNewSkeleton));
}
FDisplayedAssetEntryInfo::FDisplayedAssetEntryInfo(UObject* InAsset, USkeleton* InNewSkeleton)
: NewSkeleton(InNewSkeleton), AnimAsset(InAsset), RemapAsset(nullptr)
{
}
#undef LOCTEXT_NAMESPACE