Files
UnrealEngineUWP/Engine/Source/Editor/SkeletonEditor/Private/SSkeletonTree.cpp
robert manuszewski d1443992e1 Deprecating ANY_PACKAGE.
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]
2022-06-01 03:46:59 -04:00

2409 lines
82 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SSkeletonTree.h"
#include "Misc/MessageDialog.h"
#include "Modules/ModuleManager.h"
#include "UObject/PropertyPortFlags.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/PackageReload.h"
#include "Framework/Application/SlateApplication.h"
#include "Textures/SlateIcon.h"
#include "Framework/Commands/UIAction.h"
#include "UICommandList_Pinnable.h"
#include "Widgets/Images/SImage.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Widgets/Layout/SScrollBorder.h"
#include "Widgets/Input/SComboButton.h"
#include "Widgets/Input/SSpinBox.h"
#include "Styling/AppStyle.h"
#include "ActorFactories/ActorFactory.h"
#include "Exporters/Exporter.h"
#include "Sound/SoundBase.h"
#include "Editor.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "PropertyEditorModule.h"
#include "IDetailsView.h"
#include "ScopedTransaction.h"
#include "BoneDragDropOp.h"
#include "SocketDragDropOp.h"
#include "SkeletonTreeCommands.h"
#include "Styling/SlateIconFinder.h"
#include "DragAndDrop/AssetDragDropOp.h"
#include "AssetSelection.h"
#include "IContentBrowserSingleton.h"
#include "ContentBrowserModule.h"
#include "ComponentAssetBroker.h"
#include "AnimPreviewInstance.h"
#include "MeshUtilities.h"
#include "UnrealExporter.h"
#include "Widgets/Input/SSearchBox.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "Framework/Commands/GenericCommands.h"
#include "Animation/BlendProfile.h"
#include "SBlendProfilePicker.h"
#include "IPersonaPreviewScene.h"
#include "IDocumentation.h"
#include "PersonaUtils.h"
#include "Misc/TextFilterExpressionEvaluator.h"
#include "SkeletonTreeBoneItem.h"
#include "SkeletonTreeSocketItem.h"
#include "SkeletonTreeAttachedAssetItem.h"
#include "SkeletonTreeVirtualBoneItem.h"
#include "BoneSelectionWidget.h"
#include "SkeletonTreeSelection.h"
#include "Widgets/Layout/SGridPanel.h"
#include "HAL/PlatformApplicationMisc.h"
#include "Widgets/Views/STreeView.h"
#include "IPinnedCommandList.h"
#include "PersonaModule.h"
#include "SPositiveActionButton.h"
#include "ToolMenus.h"
#include "ToolMenuMisc.h"
#define LOCTEXT_NAMESPACE "SSkeletonTree"
const FName ISkeletonTree::Columns::Name("Name");
const FName ISkeletonTree::Columns::Retargeting("Retargeting");
const FName ISkeletonTree::Columns::BlendProfile("BlendProfile");
const FName ISkeletonTree::Columns::DebugVisualization("DebugVisualization");
// This is mostly duplicated from SListView, to allow for us to avoid selecting collapsed items
template <typename ItemType>
class SSkeletonTreeView : public STreeView<ItemType>
{
public:
bool Private_CanItemBeSelected(ItemType InItem) const
{
return !(InItem->GetFilterResult() == ESkeletonTreeFilterResult::ShownDescendant && GetMutableDefault<UPersonaOptions>()->bHideParentsWhenFiltering);
}
virtual void Private_SelectRangeFromCurrentTo( ItemType InRangeSelectionEnd ) override
{
if ( this->SelectionMode.Get() == ESelectionMode::None )
{
return;
}
const TArray<ItemType>& ItemsSourceRef = (*this->ItemsSource);
int32 RangeStartIndex = 0;
if( TListTypeTraits<ItemType>::IsPtrValid(this->RangeSelectionStart) )
{
RangeStartIndex = ItemsSourceRef.Find( TListTypeTraits<ItemType>::NullableItemTypeConvertToItemType( this->RangeSelectionStart ) );
}
int32 RangeEndIndex = ItemsSourceRef.Find( InRangeSelectionEnd );
RangeStartIndex = FMath::Clamp(RangeStartIndex, 0, ItemsSourceRef.Num());
RangeEndIndex = FMath::Clamp(RangeEndIndex, 0, ItemsSourceRef.Num());
if (RangeEndIndex < RangeStartIndex)
{
Swap( RangeStartIndex, RangeEndIndex );
}
for( int32 ItemIndex = RangeStartIndex; ItemIndex <= RangeEndIndex; ++ItemIndex )
{
// check if this item can actually be selected
if(Private_CanItemBeSelected(ItemsSourceRef[ItemIndex]))
{
this->SelectedItems.Add( ItemsSourceRef[ItemIndex] );
}
}
this->InertialScrollManager.ClearScrollVelocity();
}
virtual void Private_SignalSelectionChanged(ESelectInfo::Type SelectInfo) override
{
STreeView<ItemType>::Private_SignalSelectionChanged(SelectInfo);
// the SListView does not know about bHideParentsWhenFiltering and will select the boens regardless of their visible
// ( For example when using select all )
// this filter out those ones to only keep the ones that can be selected
{
TArray<ItemType> FilteredSelection;
for (const ItemType& Item: this->SelectedItems)
{
if (Private_CanItemBeSelected(Item))
{
FilteredSelection.Add(Item);
}
}
if (FilteredSelection.Num() != this->SelectedItems.Num())
{
this->ClearSelection();
this->SetItemSelection(FilteredSelection, true, SelectInfo);
}
}
}
};
void SSkeletonTree::Construct(const FArguments& InArgs, const TSharedRef<FEditableSkeleton>& InEditableSkeleton, const FSkeletonTreeArgs& InSkeletonTreeArgs)
{
if (InSkeletonTreeArgs.bHideBonesByDefault)
{
BoneFilter = EBoneFilter::None;
}
else
{
BoneFilter = EBoneFilter::All;
}
SocketFilter = ESocketFilter::Active;
bSelecting = false;
EditableSkeleton = InEditableSkeleton;
PreviewScene = InSkeletonTreeArgs.PreviewScene;
IsEditable = InArgs._IsEditable;
Mode = InSkeletonTreeArgs.Mode;
bAllowMeshOperations = InSkeletonTreeArgs.bAllowMeshOperations;
bAllowSkeletonOperations = InSkeletonTreeArgs.bAllowSkeletonOperations;
bShowDebugVisualizationOptions = InSkeletonTreeArgs.bShowDebugVisualizationOptions;
Extenders = InSkeletonTreeArgs.Extenders;
OnGetFilterText = InSkeletonTreeArgs.OnGetFilterText;
Builder = InSkeletonTreeArgs.Builder;
if (!Builder.IsValid())
{
Builder = MakeShareable(new FSkeletonTreeBuilder(FSkeletonTreeBuilderArgs()));
}
Builder->Initialize(SharedThis(this), InSkeletonTreeArgs.PreviewScene, FOnFilterSkeletonTreeItem::CreateSP(this, &SSkeletonTree::HandleFilterSkeletonTreeItem));
ContextName = InSkeletonTreeArgs.ContextName;
TextFilterPtr = MakeShareable(new FTextFilterExpressionEvaluator(ETextFilterExpressionEvaluatorMode::BasicString));
SetPreviewComponentSocketFilter();
// Register delegates
if(PreviewScene.IsValid())
{
PreviewScene.Pin()->RegisterOnLODChanged(FSimpleDelegate::CreateSP(this, &SSkeletonTree::OnLODSwitched));
PreviewScene.Pin()->RegisterOnPreviewMeshChanged(FOnPreviewMeshChanged::CreateSP(this, &SSkeletonTree::OnPreviewMeshChanged));
PreviewScene.Pin()->RegisterOnSelectedBoneChanged(FOnSelectedBoneChanged::CreateSP(this, &SSkeletonTree::HandleSelectedBoneChanged));
PreviewScene.Pin()->RegisterOnSelectedSocketChanged(FOnSelectedSocketChanged::CreateSP(this, &SSkeletonTree::HandleSelectedSocketChanged));
PreviewScene.Pin()->RegisterOnDeselectAll(FSimpleDelegate::CreateSP(this, &SSkeletonTree::HandleDeselectAll));
RegisterOnSelectionChanged(FOnSkeletonTreeSelectionChanged::CreateRaw(PreviewScene.Pin().Get(), &IPersonaPreviewScene::HandleSkeletonTreeSelectionChanged));
}
if (InSkeletonTreeArgs.OnSelectionChanged.IsBound())
{
RegisterOnSelectionChanged(InSkeletonTreeArgs.OnSelectionChanged);
}
FCoreUObjectDelegates::OnPackageReloaded.AddSP(this, &SSkeletonTree::HandlePackageReloaded);
// Create our pinned commands before we bind commands
IPinnedCommandListModule& PinnedCommandListModule = FModuleManager::LoadModuleChecked<IPinnedCommandListModule>(TEXT("PinnedCommandList"));
PinnedCommands = PinnedCommandListModule.CreatePinnedCommandList(ContextName);
// Register and bind all our menu commands
FSkeletonTreeCommands::Register();
BindCommands();
CreateBlendProfileMenu(nullptr);
CreateNewMenu();
CreateFilterMenu();
this->ChildSlot
[
SNew( SOverlay )
+SOverlay::Slot()
[
// Add a border if we are being used as a picker
SNew(SBorder)
.Visibility_Lambda([this](){ return Mode == ESkeletonTreeMode::Picker ? EVisibility::Visible: EVisibility::Collapsed; })
.BorderImage(FAppStyle::Get().GetBrush("Menu.Background"))
]
+SOverlay::Slot()
[
SNew( SVerticalBox )
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(0.f, 2.f))
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(FMargin(6.f, 0.0))
[
SNew(SPositiveActionButton)
.OnGetMenuContent( this, &SSkeletonTree::CreateNewMenuWidget )
.Icon(FAppStyle::Get().GetBrush("Icons.Plus"))
]
+SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SAssignNew( NameFilterBox, SSearchBox )
.SelectAllTextWhenFocused( true )
.OnTextChanged( this, &SSkeletonTree::OnFilterTextChanged )
.HintText( LOCTEXT( "SearchBoxHint", "Search Skeleton Tree...") )
.AddMetaData<FTagMetaData>(TEXT("SkelTree.Search"))
]
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(FMargin(6.f, 0.0))
.VAlign(VAlign_Center)
[
SAssignNew(FilterComboButton, SComboButton)
.Visibility(InSkeletonTreeArgs.bShowFilterMenu ? EVisibility::Visible : EVisibility::Collapsed)
.ComboButtonStyle(&FAppStyle::Get().GetWidgetStyle<FComboButtonStyle>("SimpleComboButton"))
.ForegroundColor(FSlateColor::UseStyle())
.ContentPadding(2.0f)
.OnGetMenuContent( this, &SSkeletonTree::CreateFilterMenuWidget )
.ToolTipText( this, &SSkeletonTree::GetFilterMenuTooltip )
.AddMetaData<FTagMetaData>(TEXT("SkelTree.Bones"))
.HasDownArrow(true)
.ButtonContent()
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.Settings"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
]
]
+ SVerticalBox::Slot()
.Padding( FMargin( 0.0f, 2.0f, 0.0f, 0.0f ) )
.AutoHeight()
[
PinnedCommands.ToSharedRef()
]
+ SVerticalBox::Slot()
.Padding( FMargin( 0.0f, 2.0f, 0.0f, 0.0f ) )
[
SAssignNew(TreeHolder, SOverlay)
]
]
];
SAssignNew(BlendProfilePicker, SBlendProfilePicker, GetEditableSkeleton())
.Standalone(true)
.OnBlendProfileSelected(this, &SSkeletonTree::OnBlendProfileSelected);
CreateTreeColumns();
SetInitialExpansionState();
OnLODSwitched();
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
SSkeletonTree::~SSkeletonTree()
{
if (EditableSkeleton.IsValid())
{
EditableSkeleton.Pin()->UnregisterOnSkeletonHierarchyChanged(this);
}
FCoreUObjectDelegates::OnPackageReloaded.RemoveAll(this);
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
void SSkeletonTree::BindCommands()
{
// This should not be called twice on the same instance
check( !UICommandList.IsValid() );
UICommandList = MakeShareable( new FUICommandList_Pinnable );
FUICommandList_Pinnable& CommandList = *UICommandList;
// Grab the list of menu commands to bind...
const FSkeletonTreeCommands& MenuActions = FSkeletonTreeCommands::Get();
// ...and bind them all
CommandList.MapAction(
MenuActions.FilteringFlattensHierarchy,
FExecuteAction::CreateLambda([this]() { GetMutableDefault<UPersonaOptions>()->bFlattenSkeletonHierarchyWhenFiltering = !GetDefault<UPersonaOptions>()->bFlattenSkeletonHierarchyWhenFiltering; ApplyFilter(); }),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([]() { return GetDefault<UPersonaOptions>()->bFlattenSkeletonHierarchyWhenFiltering; }));
CommandList.MapAction(
MenuActions.HideParentsWhenFiltering,
FExecuteAction::CreateLambda([this]() { GetMutableDefault<UPersonaOptions>()->bHideParentsWhenFiltering = !GetDefault<UPersonaOptions>()->bHideParentsWhenFiltering; }),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([]() { return GetDefault<UPersonaOptions>()->bHideParentsWhenFiltering; }));
// Bone Filter commands
CommandList.BeginGroup(TEXT("BoneFilterGroup"));
CommandList.MapAction(
MenuActions.ShowAllBones,
FExecuteAction::CreateSP( this, &SSkeletonTree::SetBoneFilter, EBoneFilter::All ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsBoneFilter, EBoneFilter::All ),
FIsActionButtonVisible::CreateSP( Builder.Get(), &ISkeletonTreeBuilder::IsShowingBones ));
CommandList.MapAction(
MenuActions.ShowMeshBones,
FExecuteAction::CreateSP( this, &SSkeletonTree::SetBoneFilter, EBoneFilter::Mesh ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsBoneFilter, EBoneFilter::Mesh ),
FIsActionButtonVisible::CreateSP(Builder.Get(), &ISkeletonTreeBuilder::IsShowingBones));
CommandList.MapAction(
MenuActions.ShowLODBones,
FExecuteAction::CreateSP(this, &SSkeletonTree::SetBoneFilter, EBoneFilter::LOD),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SSkeletonTree::IsBoneFilter, EBoneFilter::LOD),
FIsActionButtonVisible::CreateSP(Builder.Get(), &ISkeletonTreeBuilder::IsShowingBones));
CommandList.MapAction(
MenuActions.ShowWeightedBones,
FExecuteAction::CreateSP( this, &SSkeletonTree::SetBoneFilter, EBoneFilter::Weighted ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsBoneFilter, EBoneFilter::Weighted ),
FIsActionButtonVisible::CreateSP(Builder.Get(), &ISkeletonTreeBuilder::IsShowingBones));
CommandList.MapAction(
MenuActions.HideBones,
FExecuteAction::CreateSP( this, &SSkeletonTree::SetBoneFilter, EBoneFilter::None ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsBoneFilter, EBoneFilter::None ),
FIsActionButtonVisible::CreateSP(Builder.Get(), &ISkeletonTreeBuilder::IsShowingBones));
CommandList.EndGroup();
// Socket filter commands
CommandList.BeginGroup(TEXT("SocketFilterGroup"));
CommandList.MapAction(
MenuActions.ShowActiveSockets,
FExecuteAction::CreateSP( this, &SSkeletonTree::SetSocketFilter, ESocketFilter::Active ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsSocketFilter, ESocketFilter::Active ),
FIsActionButtonVisible::CreateSP(Builder.Get(), &ISkeletonTreeBuilder::IsShowingSockets ));
CommandList.MapAction(
MenuActions.ShowMeshSockets,
FExecuteAction::CreateSP( this, &SSkeletonTree::SetSocketFilter, ESocketFilter::Mesh ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsSocketFilter, ESocketFilter::Mesh ),
FIsActionButtonVisible::CreateSP(Builder.Get(), &ISkeletonTreeBuilder::IsShowingSockets ));
CommandList.MapAction(
MenuActions.ShowSkeletonSockets,
FExecuteAction::CreateSP( this, &SSkeletonTree::SetSocketFilter, ESocketFilter::Skeleton ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsSocketFilter, ESocketFilter::Skeleton ),
FIsActionButtonVisible::CreateSP(Builder.Get(), &ISkeletonTreeBuilder::IsShowingSockets ));
CommandList.MapAction(
MenuActions.ShowAllSockets,
FExecuteAction::CreateSP( this, &SSkeletonTree::SetSocketFilter, ESocketFilter::All ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsSocketFilter, ESocketFilter::All ),
FIsActionButtonVisible::CreateSP(Builder.Get(), &ISkeletonTreeBuilder::IsShowingSockets ));
CommandList.MapAction(
MenuActions.HideSockets,
FExecuteAction::CreateSP( this, &SSkeletonTree::SetSocketFilter, ESocketFilter::None ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsSocketFilter, ESocketFilter::None ),
FIsActionButtonVisible::CreateSP(Builder.Get(), &ISkeletonTreeBuilder::IsShowingSockets ));
CommandList.EndGroup();
CommandList.MapAction(
MenuActions.ShowRetargeting,
FExecuteAction::CreateSP(this, &SSkeletonTree::OnChangeShowingAdvancedOptions),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SSkeletonTree::IsShowingAdvancedOptions),
FIsActionButtonVisible::CreateLambda([this]() { return Builder->IsShowingBones() && bAllowSkeletonOperations; }));
CommandList.MapAction(
MenuActions.ShowDebugVisualization,
FExecuteAction::CreateSP(this, &SSkeletonTree::OnChangeShowingDebugVisualizationOptions),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SSkeletonTree::IsShowingDebugVisualizationOptions),
FIsActionButtonVisible::CreateLambda([this](){ return bShowDebugVisualizationOptions; }));
// Socket manipulation commands
CommandList.MapAction(
MenuActions.AddSocket,
FExecuteAction::CreateSP( this, &SSkeletonTree::OnAddSocket ),
FCanExecuteAction::CreateSP( this, &SSkeletonTree::IsAddingSocketsAllowed ) );
CommandList.MapAction(
FGenericCommands::Get().Rename,
FExecuteAction::CreateSP( this, &SSkeletonTree::OnRenameSelected ),
FCanExecuteAction::CreateSP( this, &SSkeletonTree::CanRenameSelected ) );
CommandList.MapAction(
MenuActions.CreateMeshSocket,
FExecuteAction::CreateSP( this, &SSkeletonTree::OnCustomizeSocket ) );
CommandList.MapAction(
MenuActions.RemoveMeshSocket,
FExecuteAction::CreateSP( this, &SSkeletonTree::OnDeleteSelectedRows ),
FCanExecuteAction::CreateSP( this, &SSkeletonTree::CanDeleteSelectedRows ) );
CommandList.MapAction(
MenuActions.PromoteSocketToSkeleton,
FExecuteAction::CreateSP( this, &SSkeletonTree::OnPromoteSocket ) ); // Adding customization just deletes the mesh socket
CommandList.MapAction(
MenuActions.DeleteSelectedRows,
FExecuteAction::CreateSP( this, &SSkeletonTree::OnDeleteSelectedRows ),
FCanExecuteAction::CreateSP( this, &SSkeletonTree::CanDeleteSelectedRows ));
CommandList.MapAction(
MenuActions.CopyBoneNames,
FExecuteAction::CreateSP( this, &SSkeletonTree::OnCopyBoneNames ));
CommandList.MapAction(
MenuActions.ResetBoneTransforms,
FExecuteAction::CreateSP(this, &SSkeletonTree::OnResetBoneTransforms ) );
CommandList.MapAction(
MenuActions.CopySockets,
FExecuteAction::CreateSP( this, &SSkeletonTree::OnCopySockets ),
FCanExecuteAction::CreateSP( this, &SSkeletonTree::CanCopySockets ));
CommandList.MapAction(
MenuActions.PasteSockets,
FExecuteAction::CreateSP( this, &SSkeletonTree::OnPasteSockets, false ),
FCanExecuteAction::CreateSP( this, &SSkeletonTree::CanPasteSockets ));
CommandList.MapAction(
MenuActions.PasteSocketsToSelectedBone,
FExecuteAction::CreateSP(this, &SSkeletonTree::OnPasteSockets, true),
FCanExecuteAction::CreateSP( this, &SSkeletonTree::CanPasteSockets ));
CommandList.MapAction(
MenuActions.FocusCamera,
FExecuteAction::CreateSP(this, &SSkeletonTree::HandleFocusCamera));
CommandList.MapAction(
MenuActions.CreateTimeBlendProfile,
FExecuteAction::CreateSP( this, &SSkeletonTree::OnCreateBlendProfile, EBlendProfileMode::TimeFactor));
CommandList.MapAction(
MenuActions.CreateWeightBlendProfile,
FExecuteAction::CreateSP(this, &SSkeletonTree::OnCreateBlendProfile, EBlendProfileMode::WeightFactor));
CommandList.MapAction(
MenuActions.CreateBlendMask,
FExecuteAction::CreateSP(this, &SSkeletonTree::OnCreateBlendProfile, EBlendProfileMode::BlendMask));
CommandList.MapAction(
MenuActions.DeleteCurrentBlendProfile,
FExecuteAction::CreateSP( this, &SSkeletonTree::OnDeleteCurrentBlendProfile));
PinnedCommands->BindCommandList(UICommandList.ToSharedRef());
}
TSharedRef<ITableRow> SSkeletonTree::MakeTreeRowWidget(TSharedPtr<ISkeletonTreeItem> InInfo, const TSharedRef<STableViewBase>& OwnerTable)
{
check( InInfo.IsValid() );
return InInfo->MakeTreeRowWidget(OwnerTable, TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateLambda([this]() { return FilterText; })));
}
void SSkeletonTree::GetFilteredChildren(TSharedPtr<ISkeletonTreeItem> InInfo, TArray< TSharedPtr<ISkeletonTreeItem> >& OutChildren)
{
check(InInfo.IsValid());
OutChildren = InInfo->GetFilteredChildren();
}
/** Helper struct for when we rebuild the tree because of a change to its structure */
struct FScopedSavedSelection
{
FScopedSavedSelection(TSharedPtr<SSkeletonTree> InSkeletonTree)
: SkeletonTree(InSkeletonTree)
{
// record selected items
if (SkeletonTree.IsValid() && InSkeletonTree->SkeletonTreeView.IsValid())
{
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = InSkeletonTree->SkeletonTreeView->GetSelectedItems();
for (const TSharedPtr<ISkeletonTreeItem>& SelectedItem : SelectedItems)
{
SavedSelections.Add({ SelectedItem->GetRowItemName(), SelectedItem->GetTypeName(), SelectedItem->GetObject() });
}
}
}
~FScopedSavedSelection()
{
if (SkeletonTree.IsValid() && SkeletonTree->SkeletonTreeView.IsValid())
{
// restore selection
for (const TSharedPtr<ISkeletonTreeItem>& Item : SkeletonTree->LinearItems)
{
if (Item->GetFilterResult() != ESkeletonTreeFilterResult::Hidden)
{
for (FSavedSelection& SavedSelection : SavedSelections)
{
if (Item->GetRowItemName() == SavedSelection.ItemName && Item->GetTypeName() == SavedSelection.ItemType && Item->GetObject() == SavedSelection.ItemObject)
{
SkeletonTree->SkeletonTreeView->SetItemSelection(Item, true);
break;
}
}
}
}
}
}
struct FSavedSelection
{
/** Name of the selected item */
FName ItemName;
/** Type of the selected item */
FName ItemType;
/** Object of selected item */
UObject* ItemObject;
};
TSharedPtr<SSkeletonTree> SkeletonTree;
TArray<FSavedSelection> SavedSelections;
};
void SSkeletonTree::CreateTreeColumns()
{
TArray<FName> HiddenColumnsList;
HiddenColumnsList.Add(ISkeletonTree::Columns::Retargeting);
HiddenColumnsList.Add(ISkeletonTree::Columns::BlendProfile);
HiddenColumnsList.Add(ISkeletonTree::Columns::DebugVisualization);
TSharedRef<SHeaderRow> TreeHeaderRow =
SNew(SHeaderRow)
.CanSelectGeneratedColumn(true)
.HiddenColumnsList(HiddenColumnsList)
+ SHeaderRow::Column(ISkeletonTree::Columns::Name)
.ShouldGenerateWidget(true)
.DefaultLabel(LOCTEXT("SkeletonBoneNameLabel", "Name"))
.FillWidth(0.5f)
+ SHeaderRow::Column(ISkeletonTree::Columns::Retargeting)
.DefaultLabel(LOCTEXT("SkeletonBoneTranslationRetargetingLabel", "Translation Retargeting"))
.FillWidth(0.25f)
+ SHeaderRow::Column(ISkeletonTree::Columns::DebugVisualization)
.DefaultLabel(LOCTEXT("SkeletonBoneDebugVisualizationLabel", "Debug"))
.FillWidth(0.25f)
+ SHeaderRow::Column(ISkeletonTree::Columns::BlendProfile)
.DefaultLabel(LOCTEXT("BlendProfile", "Blend Profile"))
.FillWidth(0.25f)
.OnGetMenuContent(this, &SSkeletonTree::GetBlendProfileColumnMenuContent )
.HeaderContent()
[
SNew(SBox)
.HeightOverride(24)
.HAlign(HAlign_Left)
[
SAssignNew(BlendProfileHeader, SInlineEditableTextBlock)
.Text_Lambda([this] () -> FText
{
FName CurrentProfile = BlendProfilePicker->GetSelectedBlendProfileName();
return (CurrentProfile != NAME_None) ? FText::FromName(CurrentProfile) : LOCTEXT("NoBlendProfile", "NoBlend");
})
.OnTextCommitted_Lambda([this](const FText& InText, ETextCommit::Type InCommitType)
{
BlendProfilePicker->OnCreateNewProfileComitted(InText, InCommitType, NewBlendProfileMode);
})
.OnVerifyTextChanged_Lambda([](const FText& InNewText, FText& OutErrorMessage) -> bool
{
return FName::IsValidXName(InNewText.ToString(), INVALID_OBJECTNAME_CHARACTERS INVALID_LONGPACKAGE_CHARACTERS, &OutErrorMessage);
})
.IsReadOnly(true)
]
];
{
FScopedSavedSelection ScopedSelection(SharedThis(this));
SkeletonTreeView = SNew(SSkeletonTreeView<TSharedPtr<ISkeletonTreeItem>>)
.TreeItemsSource(&FilteredItems)
.OnGenerateRow(this, &SSkeletonTree::MakeTreeRowWidget)
.OnGetChildren(this, &SSkeletonTree::GetFilteredChildren)
.OnContextMenuOpening(this, &SSkeletonTree::CreateContextMenu)
.OnSelectionChanged(this, &SSkeletonTree::OnSelectionChanged)
.OnIsSelectableOrNavigable(this, &SSkeletonTree::OnIsSelectableOrNavigable)
.OnItemScrolledIntoView(this, &SSkeletonTree::OnItemScrolledIntoView)
.OnMouseButtonDoubleClick(this, &SSkeletonTree::OnTreeDoubleClick)
.OnSetExpansionRecursive(this, &SSkeletonTree::SetTreeItemExpansionRecursive)
.ItemHeight(24)
.HighlightParentNodesForSelection(true)
.HeaderRow
(
TreeHeaderRow
);
TreeHolder->ClearChildren();
TreeHolder->AddSlot()
[
SNew(SScrollBorder, SkeletonTreeView.ToSharedRef())
[
SkeletonTreeView.ToSharedRef()
]
];
}
CreateFromSkeleton();
}
void SSkeletonTree::CreateFromSkeleton()
{
// save selected items
FScopedSavedSelection ScopedSelection(SharedThis(this));
Items.Empty();
LinearItems.Empty();
FilteredItems.Empty();
FSkeletonTreeBuilderOutput Output(Items, LinearItems);
Builder->Build(Output);
ApplyFilter();
}
void SSkeletonTree::ApplyFilter()
{
TextFilterPtr->SetFilterText(FilterText);
FilteredItems.Empty();
FSkeletonTreeFilterArgs FilterArgs(!FilterText.IsEmpty() ? TextFilterPtr : nullptr);
FilterArgs.bFlattenHierarchyOnFilter = GetDefault<UPersonaOptions>()->bFlattenSkeletonHierarchyWhenFiltering;
Builder->Filter(FilterArgs, Items, FilteredItems);
if(!FilterText.IsEmpty())
{
for (TSharedPtr<ISkeletonTreeItem>& Item : LinearItems)
{
if (Item->GetFilterResult() > ESkeletonTreeFilterResult::Hidden)
{
SkeletonTreeView->SetItemExpansion(Item, true);
}
}
}
else
{
SetInitialExpansionState();
}
HandleTreeRefresh();
}
void SSkeletonTree::SetInitialExpansionState()
{
for (TSharedPtr<ISkeletonTreeItem>& Item : LinearItems)
{
SkeletonTreeView->SetItemExpansion(Item, Item->IsInitiallyExpanded());
}
}
TSharedPtr< SWidget > SSkeletonTree::CreateContextMenu()
{
const FSkeletonTreeCommands& Actions = FSkeletonTreeCommands::Get();
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
FSkeletonTreeSelection BoneTreeSelection(SelectedItems);
const bool CloseAfterSelection = true;
FMenuBuilder MenuBuilder( CloseAfterSelection, UICommandList, Extenders );
{
if(BoneTreeSelection.HasSelectedOfType<FSkeletonTreeAttachedAssetItem>() || BoneTreeSelection.HasSelectedOfType<FSkeletonTreeSocketItem>() || BoneTreeSelection.HasSelectedOfType<FSkeletonTreeVirtualBoneItem>())
{
MenuBuilder.BeginSection("SkeletonTreeSelectedItemsActions", LOCTEXT( "SelectedActions", "Selected Item Actions" ) );
MenuBuilder.AddMenuEntry( Actions.DeleteSelectedRows );
MenuBuilder.EndSection();
}
const bool bNeedsBoneActionsHeading = BoneTreeSelection.HasSelectedOfType<FSkeletonTreeBoneItem>() || BoneTreeSelection.HasSelectedOfType<FSkeletonTreeVirtualBoneItem>();
if (bNeedsBoneActionsHeading)
{
MenuBuilder.BeginSection("SkeletonTreeBonesAction", LOCTEXT("BoneActions", "Selected Bone Actions"));
}
if (BoneTreeSelection.HasSelectedOfType<FSkeletonTreeBoneItem>())
{
MenuBuilder.AddMenuEntry(Actions.CopyBoneNames);
MenuBuilder.AddMenuEntry(Actions.ResetBoneTransforms);
if (BoneTreeSelection.IsSingleOfTypeSelected<FSkeletonTreeBoneItem>() && bAllowSkeletonOperations)
{
MenuBuilder.AddMenuEntry(Actions.AddSocket);
MenuBuilder.AddMenuEntry(Actions.PasteSockets);
MenuBuilder.AddMenuEntry(Actions.PasteSocketsToSelectedBone);
}
}
if (bNeedsBoneActionsHeading)
{
if (bAllowSkeletonOperations)
{
MenuBuilder.AddSubMenu(LOCTEXT("AddVirtualBone", "Add Virtual Bone"),
LOCTEXT("AddVirtualBone_ToolTip", "Adds a virtual bone to the skeleton."),
FNewMenuDelegate::CreateSP(this, &SSkeletonTree::FillVirtualBoneSubmenu));
}
MenuBuilder.EndSection();
}
if (bAllowSkeletonOperations)
{
if(BoneTreeSelection.HasSelectedOfType<FSkeletonTreeBoneItem>())
{
UBlendProfile* const SelectedBlendProfile = BlendProfilePicker->GetSelectedBlendProfile();
if(SelectedBlendProfile && BoneTreeSelection.IsSingleOfTypeSelected<FSkeletonTreeBoneItem>())
{
TSharedPtr<FSkeletonTreeBoneItem> BoneItem = BoneTreeSelection.GetSelectedItems<FSkeletonTreeBoneItem>()[0];
FName BoneName = BoneItem->GetAttachName();
const USkeleton& Skeleton = GetEditableSkeletonInternal()->GetSkeleton();
int32 BoneIndex = Skeleton.GetReferenceSkeleton().FindBoneIndex(BoneName);
float CurrentBlendScale = SelectedBlendProfile->GetBoneBlendScale(BoneIndex);
MenuBuilder.BeginSection("SkeletonTreeBlendProfileScales", LOCTEXT("BlendProfileContextOptions", "Blend Profile"));
{
FUIAction RecursiveSetScales;
RecursiveSetScales.ExecuteAction = FExecuteAction::CreateSP(this, &SSkeletonTree::RecursiveSetBlendProfileScales, CurrentBlendScale);
MenuBuilder.AddMenuEntry
(
FText::Format(LOCTEXT("RecursiveSetBlendScales_Label", "Recursively Set Blend Scales To {0}"), FText::AsNumber(CurrentBlendScale)),
LOCTEXT("RecursiveSetBlendScales_ToolTip", "Sets all child bones to use the same blend profile scale as the selected bone"),
FSlateIcon(),
RecursiveSetScales
);
}
MenuBuilder.EndSection();
}
if(IsShowingAdvancedOptions())
{
MenuBuilder.BeginSection("SkeletonTreeBoneTranslationRetargeting", LOCTEXT("BoneTranslationRetargetingHeader", "Bone Translation Retargeting"));
{
FUIAction RecursiveRetargetingSkeletonAction = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonTree::SetBoneTranslationRetargetingModeRecursive, EBoneTranslationRetargetingMode::Skeleton));
FUIAction RecursiveRetargetingAnimationAction = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonTree::SetBoneTranslationRetargetingModeRecursive, EBoneTranslationRetargetingMode::Animation));
FUIAction RecursiveRetargetingAnimationScaledAction = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonTree::SetBoneTranslationRetargetingModeRecursive, EBoneTranslationRetargetingMode::AnimationScaled));
FUIAction RecursiveRetargetingAnimationRelativeAction = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonTree::SetBoneTranslationRetargetingModeRecursive, EBoneTranslationRetargetingMode::AnimationRelative));
FUIAction RecursiveRetargetingOrientAndScaleAction = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonTree::SetBoneTranslationRetargetingModeRecursive, EBoneTranslationRetargetingMode::OrientAndScale));
MenuBuilder.AddMenuEntry
(LOCTEXT("SetTranslationRetargetingSkeletonChildrenAction", "Recursively Set Translation Retargeting Skeleton")
, LOCTEXT("BoneTranslationRetargetingSkeletonToolTip", "Use translation from Skeleton.")
, FSlateIcon()
, RecursiveRetargetingSkeletonAction
);
MenuBuilder.AddMenuEntry
(LOCTEXT("SetTranslationRetargetingAnimationChildrenAction", "Recursively Set Translation Retargeting Animation")
, LOCTEXT("BoneTranslationRetargetingAnimationToolTip", "Use translation from animation.")
, FSlateIcon()
, RecursiveRetargetingAnimationAction
);
MenuBuilder.AddMenuEntry
(LOCTEXT("SetTranslationRetargetingAnimationScaledChildrenAction", "Recursively Set Translation Retargeting AnimationScaled")
, LOCTEXT("BoneTranslationRetargetingAnimationScaledToolTip", "Use translation from animation, scale length by Skeleton's proportions.")
, FSlateIcon()
, RecursiveRetargetingAnimationScaledAction
);
MenuBuilder.AddMenuEntry
(LOCTEXT("SetTranslationRetargetingAnimationRelativeChildrenAction", "Recursively Set Translation Retargeting AnimationRelative")
, LOCTEXT("BoneTranslationRetargetingAnimationRelativeToolTip", "Use relative translation from animation similar to an additive animation.")
, FSlateIcon()
, RecursiveRetargetingAnimationRelativeAction
);
MenuBuilder.AddMenuEntry
(LOCTEXT("SetTranslationRetargetingOrientAndScaleChildrenAction", "Recursively Set Translation Retargeting OrientAndScale")
, LOCTEXT("BoneTranslationRetargetingOrientAndScaleToolTip", "Orient And Scale Translation.")
, FSlateIcon()
, RecursiveRetargetingOrientAndScaleAction
);
}
MenuBuilder.EndSection();
}
}
}
if(bAllowMeshOperations)
{
MenuBuilder.BeginSection("SkeletonTreeBoneReductionForLOD", LOCTEXT("BoneReductionHeader", "LOD Bone Reduction"));
{
MenuBuilder.AddSubMenu(
LOCTEXT("SkeletonTreeBoneReductionForLOD_RemoveSelectedFromLOD", "Remove Selected..."),
FText::GetEmpty(),
FNewMenuDelegate::CreateStatic(&SSkeletonTree::CreateMenuForBoneReduction, this, LastCachedLODForPreviewMeshComponent, true)
);
MenuBuilder.AddSubMenu(
LOCTEXT("SkeletonTreeBoneReductionForLOD_RemoveChildrenFromLOD", "Remove Children..."),
FText::GetEmpty(),
FNewMenuDelegate::CreateStatic(&SSkeletonTree::CreateMenuForBoneReduction, this, LastCachedLODForPreviewMeshComponent, false)
);
}
MenuBuilder.EndSection();
}
if(bAllowSkeletonOperations)
{
if (BoneTreeSelection.HasSelectedOfType<FSkeletonTreeVirtualBoneItem>())
{
MenuBuilder.BeginSection("SkeletonTreeVirtualBoneActions", LOCTEXT("VirtualBoneActions", "Selected Virtual Bone Actions"));
if (BoneTreeSelection.IsSingleOfTypeSelected<FSkeletonTreeVirtualBoneItem>())
{
MenuBuilder.AddMenuEntry(FGenericCommands::Get().Rename, NAME_None, LOCTEXT("RenameVirtualBone_Label", "Rename Virtual Bone"), LOCTEXT("RenameVirtualBone_Tooltip", "Rename this virtual bone"));
}
MenuBuilder.EndSection();
}
if(BoneTreeSelection.HasSelectedOfType<FSkeletonTreeSocketItem>())
{
MenuBuilder.BeginSection("SkeletonTreeSocketsActions", LOCTEXT( "SocketActions", "Selected Socket Actions" ) );
MenuBuilder.AddMenuEntry( Actions.CopySockets );
if(BoneTreeSelection.IsSingleOfTypeSelected<FSkeletonTreeSocketItem>())
{
MenuBuilder.AddMenuEntry( FGenericCommands::Get().Rename, NAME_None, LOCTEXT("RenameSocket_Label", "Rename Socket"), LOCTEXT("RenameSocket_Tooltip", "Rename this socket") );
TSharedPtr<FSkeletonTreeSocketItem> SocketItem = BoneTreeSelection.GetSelectedItems<FSkeletonTreeSocketItem>()[0];
if (SocketItem->IsSocketCustomized() && SocketItem->GetParentType() == ESocketParentType::Mesh )
{
MenuBuilder.AddMenuEntry( Actions.RemoveMeshSocket );
}
// If the socket is on the skeleton, we have a valid mesh
// and there isn't one of the same name on the mesh, we can customize it
if (SocketItem->CanCustomizeSocket() )
{
if (SocketItem->GetParentType() == ESocketParentType::Skeleton )
{
MenuBuilder.AddMenuEntry( Actions.CreateMeshSocket );
}
else if (SocketItem->GetParentType() == ESocketParentType::Mesh )
{
// If a socket is on the mesh only, then offer to promote it to the skeleton
MenuBuilder.AddMenuEntry( Actions.PromoteSocketToSkeleton );
}
}
}
MenuBuilder.EndSection();
}
}
if (BoneTreeSelection.HasSelectedOfType<FSkeletonTreeBoneItem>() || BoneTreeSelection.HasSelectedOfType<FSkeletonTreeSocketItem>())
{
MenuBuilder.BeginSection("SkeletonTreeAttachedAssets", LOCTEXT( "AttachedAssetsActionsHeader", "Attached Assets Actions" ) );
if ( BoneTreeSelection.IsSingleItemSelected() )
{
MenuBuilder.AddSubMenu( LOCTEXT( "AttachNewAsset", "Add Preview Asset" ),
LOCTEXT ( "AttachNewAsset_ToolTip", "Attaches an asset to this part of the skeleton. Assets can also be dragged onto the skeleton from a content browser to attach" ),
FNewMenuDelegate::CreateSP( this, &SSkeletonTree::FillAttachAssetSubmenu, BoneTreeSelection.GetSingleSelectedItem() ) );
}
FUIAction RemoveAllAttachedAssets = FUIAction( FExecuteAction::CreateSP( this, &SSkeletonTree::OnRemoveAllAssets ),
FCanExecuteAction::CreateSP( this, &SSkeletonTree::CanRemoveAllAssets ));
MenuBuilder.AddMenuEntry( LOCTEXT( "RemoveAllAttachedAssets", "Remove All Attached Assets" ),
LOCTEXT ( "RemoveAllAttachedAssets_ToolTip", "Removes all the attached assets from the skeleton and mesh." ),
FSlateIcon(), RemoveAllAttachedAssets );
MenuBuilder.EndSection();
}
// Add an empty section so the menu can be extended when there are no optionally-added entries
MenuBuilder.BeginSection("SkeletonTreeContextMenu");
MenuBuilder.EndSection();
}
return MenuBuilder.MakeWidget();
}
bool GetSourceNameFromItem(TSharedPtr<ISkeletonTreeItem> SourceBone, FName& OutName)
{
if (SourceBone->IsOfType<FSkeletonTreeBoneItem>())
{
OutName = SourceBone->GetRowItemName();
return true;
}
if (SourceBone->IsOfType<FSkeletonTreeVirtualBoneItem>())
{
OutName = SourceBone->GetRowItemName();
return true;
}
return false;
}
void SSkeletonTree::FillVirtualBoneSubmenu(FMenuBuilder& MenuBuilder)
{
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
const bool bShowVirtualBones = false;
TSharedRef<SBoneTreeMenu> MenuContent = SNew(SBoneTreeMenu)
.bShowVirtualBones(false)
.Title(LOCTEXT("TargetBonePickerTitle", "Pick Target Bone..."))
.OnBoneSelectionChanged(this, &SSkeletonTree::OnVirtualTargetBonePicked, SelectedItems)
.OnGetReferenceSkeleton(this, &SSkeletonTree::OnGetReferenceSkeleton);
MenuBuilder.AddWidget(MenuContent, FText::GetEmpty(), true);
MenuContent->RegisterActiveTimer(0.0f, FWidgetActiveTimerDelegate::CreateLambda(
[FilterTextBox = MenuContent->GetFilterTextWidget()](double, float)
{
FSlateApplication::Get().SetKeyboardFocus(FilterTextBox);
return EActiveTimerReturnType::Stop;
}
));
}
void SSkeletonTree::OnVirtualTargetBonePicked(FName TargetBoneName, TArray<TSharedPtr<ISkeletonTreeItem>> SourceBones)
{
FSlateApplication::Get().DismissAllMenus();
TArray<FName> VirtualBoneNames;
for (const TSharedPtr<ISkeletonTreeItem>& SourceBone : SourceBones)
{
FName SourceBoneName;
if(GetSourceNameFromItem(SourceBone, SourceBoneName))
{
FName NewVirtualBoneName;
if(!GetEditableSkeletonInternal()->HandleAddVirtualBone(SourceBoneName, TargetBoneName, NewVirtualBoneName))
{
UE_LOG(LogAnimation, Log, TEXT("Could not create space switch bone from %s to %s, it already exists"), *SourceBoneName.ToString(), *TargetBoneName.ToString());
}
else
{
VirtualBoneNames.Add(NewVirtualBoneName);
}
}
}
if (VirtualBoneNames.Num() > 0)
{
CreateFromSkeleton();
SkeletonTreeView->ClearSelection();
TSharedPtr<ISkeletonTreeItem> LastItem;
for (TSharedPtr<ISkeletonTreeItem> SkeletonRow : LinearItems)
{
if (SkeletonRow->IsOfType<FSkeletonTreeVirtualBoneItem>())
{
LastItem = SkeletonRow;
FName RowName = SkeletonRow->GetRowItemName();
for (const FName& VB : VirtualBoneNames)
{
if (RowName == VB)
{
SkeletonTreeView->SetItemSelection(SkeletonRow, true);
SkeletonTreeView->RequestScrollIntoView(SkeletonRow);
break;
}
}
}
}
if (LastItem.IsValid())
{
SkeletonTreeView->RequestScrollIntoView(LastItem);
}
}
}
void SSkeletonTree::CreateMenuForBoneReduction(FMenuBuilder& MenuBuilder, SSkeletonTree * Widget, int32 LODIndex, bool bIncludeSelected)
{
MenuBuilder.AddMenuEntry
(FText::FromString(FString::Printf(TEXT("From LOD %d and below"), LODIndex))
, FText::FromString(FString::Printf(TEXT("Remove Selected %s from current LOD %d and all lower LODs"), (bIncludeSelected) ? TEXT("bones") : TEXT("children"), LODIndex))
, FSlateIcon()
, FUIAction(FExecuteAction::CreateSP(Widget, &SSkeletonTree::RemoveFromLOD, LODIndex, bIncludeSelected, true))
);
MenuBuilder.AddMenuEntry
(FText::FromString(FString::Printf(TEXT("From LOD %d only"), LODIndex))
, FText::FromString(FString::Printf(TEXT("Remove selected %s from current LOD %d only"), (bIncludeSelected) ? TEXT("bones") : TEXT("children"), LODIndex))
, FSlateIcon()
, FUIAction(FExecuteAction::CreateSP(Widget, &SSkeletonTree::RemoveFromLOD, LODIndex, bIncludeSelected, false))
);
}
void SSkeletonTree::SetBoneTranslationRetargetingModeRecursive(EBoneTranslationRetargetingMode::Type NewRetargetingMode)
{
TArray<FName> BoneNames;
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
FSkeletonTreeSelection TreeSelection(SelectedItems);
for (const TSharedPtr<FSkeletonTreeBoneItem>& Item : TreeSelection.GetSelectedItems<FSkeletonTreeBoneItem>())
{
BoneNames.Add(Item->GetRowItemName());
}
GetEditableSkeletonInternal()->SetBoneTranslationRetargetingModeRecursive(BoneNames, NewRetargetingMode);
}
void SSkeletonTree::RemoveFromLOD(int32 LODIndex, bool bIncludeSelected, bool bIncludeBelowLODs)
{
// we cant do this without a preview scene
if (!GetPreviewScene().IsValid())
{
return;
}
UDebugSkelMeshComponent* PreviewMeshComponent = GetPreviewScene()->GetPreviewMeshComponent();
if (!PreviewMeshComponent->SkeletalMesh)
{
return;
}
// ask users you can't undo this change, and warn them
const FText Message(LOCTEXT("RemoveBonesFromLODWarning", "This action can't be undone. Would you like to continue?"));
if (FMessageDialog::Open(EAppMsgType::YesNo, Message) == EAppReturnType::Yes)
{
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
FSkeletonTreeSelection TreeSelection(SelectedItems);
const FReferenceSkeleton& RefSkeleton = GetEditableSkeletonInternal()->GetSkeleton().GetReferenceSkeleton();
TArray<FName> BonesToRemove;
//Scoped post edit change
{
FScopedSkeletalMeshPostEditChange ScopedPostEditChange(PreviewMeshComponent->SkeletalMesh);
for (const TSharedPtr<FSkeletonTreeBoneItem>& Item : TreeSelection.GetSelectedItems<FSkeletonTreeBoneItem>())
{
FName BoneName = Item->GetRowItemName();
int32 BoneIndex = RefSkeleton.FindBoneIndex(BoneName);
if (BoneIndex != INDEX_NONE)
{
if (bIncludeSelected)
{
PreviewMeshComponent->SkeletalMesh->AddBoneToReductionSetting(LODIndex, BoneName);
BonesToRemove.AddUnique(BoneName);
}
else
{
for (int32 ChildIndex = BoneIndex + 1; ChildIndex < RefSkeleton.GetRawBoneNum(); ++ChildIndex)
{
if (RefSkeleton.GetParentIndex(ChildIndex) == BoneIndex)
{
FName ChildBoneName = RefSkeleton.GetBoneName(ChildIndex);
PreviewMeshComponent->SkeletalMesh->AddBoneToReductionSetting(LODIndex, ChildBoneName);
BonesToRemove.AddUnique(ChildBoneName);
}
}
}
}
}
int32 TotalLOD = PreviewMeshComponent->SkeletalMesh->GetLODNum();
IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshUtilities>("MeshUtilities");
if (bIncludeBelowLODs)
{
for (int32 Index = LODIndex + 1; Index < TotalLOD; ++Index)
{
MeshUtilities.RemoveBonesFromMesh(PreviewMeshComponent->SkeletalMesh, Index, &BonesToRemove);
PreviewMeshComponent->SkeletalMesh->AddBoneToReductionSetting(Index, BonesToRemove);
}
}
// remove from current LOD
MeshUtilities.RemoveBonesFromMesh(PreviewMeshComponent->SkeletalMesh, LODIndex, &BonesToRemove);
}
// update UI to reflect the change
OnLODSwitched();
}
}
void SSkeletonTree::OnCopyBoneNames()
{
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
FSkeletonTreeSelection TreeSelection(SelectedItems);
TArray<TSharedPtr<FSkeletonTreeBoneItem>> SelectedBones = TreeSelection.GetSelectedItems<FSkeletonTreeBoneItem>();
if( SelectedBones.Num() > 0 )
{
bool bFirst = true;
FString BoneNames;
for (const TSharedPtr<FSkeletonTreeBoneItem>& Item : SelectedBones)
{
FName BoneName = Item->GetRowItemName();
if (!bFirst)
{
BoneNames += "\r\n";
}
BoneNames += BoneName.ToString();
bFirst = false;
}
FPlatformApplicationMisc::ClipboardCopy( *BoneNames );
}
}
void SSkeletonTree::OnResetBoneTransforms()
{
if (GetPreviewScene().IsValid())
{
UDebugSkelMeshComponent* PreviewComponent = GetPreviewScene()->GetPreviewMeshComponent();
check(PreviewComponent);
UAnimPreviewInstance* PreviewInstance = PreviewComponent->PreviewInstance;
check(PreviewInstance);
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
FSkeletonTreeSelection TreeSelection(SelectedItems);
TArray<TSharedPtr<FSkeletonTreeBoneItem>> SelectedBones = TreeSelection.GetSelectedItems<FSkeletonTreeBoneItem>();
if (SelectedBones.Num() > 0)
{
bool bModified = false;
GEditor->BeginTransaction(LOCTEXT("SkeletonTree_ResetBoneTransforms", "Reset Bone Transforms"));
for (const TSharedPtr<FSkeletonTreeBoneItem>& Item : SelectedBones)
{
FName BoneName = Item->GetRowItemName();
const FAnimNode_ModifyBone* ModifiedBone = PreviewInstance->FindModifiedBone(BoneName);
if (ModifiedBone != nullptr)
{
if (!bModified)
{
PreviewInstance->SetFlags(RF_Transactional);
PreviewInstance->Modify();
bModified = true;
}
PreviewInstance->RemoveBoneModification(BoneName);
}
}
GEditor->EndTransaction();
}
}
}
void SSkeletonTree::OnCopySockets() const
{
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
FSkeletonTreeSelection TreeSelection(SelectedItems);
TArray<TSharedPtr<FSkeletonTreeSocketItem>> SelectedSockets = TreeSelection.GetSelectedItems<FSkeletonTreeSocketItem>();
int32 NumSocketsToCopy = SelectedSockets.Num();
if ( NumSocketsToCopy > 0 )
{
FString SocketsDataString;
for (const TSharedPtr<FSkeletonTreeSocketItem>& Item : SelectedSockets)
{
SocketsDataString += SerializeSocketToString(Item->GetSocket(), Item->GetParentType());
}
FString CopyString = FString::Printf( TEXT("%s\nNumSockets=%d\n%s"), *FEditableSkeleton::SocketCopyPasteHeader, NumSocketsToCopy, *SocketsDataString );
FPlatformApplicationMisc::ClipboardCopy( *CopyString );
}
}
bool SSkeletonTree::CanCopySockets() const
{
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
FSkeletonTreeSelection TreeSelection(SelectedItems);
TArray<TSharedPtr<FSkeletonTreeSocketItem>> SelectedSockets = TreeSelection.GetSelectedItems<FSkeletonTreeSocketItem>();
return SelectedSockets.Num() > 0;
}
FString SSkeletonTree::SerializeSocketToString( USkeletalMeshSocket* Socket, ESocketParentType ParentType) const
{
FString SocketString;
SocketString += FString::Printf( TEXT( "IsOnSkeleton=%s\n" ), ParentType == ESocketParentType::Skeleton ? TEXT( "1" ) : TEXT( "0" ) );
FStringOutputDevice Buffer;
const FExportObjectInnerContext Context;
UExporter::ExportToOutputDevice( &Context, Socket, nullptr, Buffer, TEXT( "copy" ), 0, PPF_Copy, false );
SocketString += Buffer;
return SocketString;
}
void SSkeletonTree::OnPasteSockets(bool bPasteToSelectedBone)
{
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
FSkeletonTreeSelection TreeSelection(SelectedItems);
// Pasting sockets should only work if there is just one bone selected
if ( TreeSelection.IsSingleOfTypeSelected<FSkeletonTreeBoneItem>() )
{
FName DestBoneName = bPasteToSelectedBone ? TreeSelection.GetSingleSelectedItem()->GetRowItemName() : NAME_None;
USkeletalMesh* SkeletalMesh = GetPreviewScene().IsValid() ? ToRawPtr(GetPreviewScene()->GetPreviewMeshComponent()->SkeletalMesh) : nullptr;
GetEditableSkeletonInternal()->HandlePasteSockets(DestBoneName, SkeletalMesh);
CreateFromSkeleton();
}
}
bool SSkeletonTree::CanPasteSockets() const
{
if(SkeletonTreeView->GetNumItemsSelected() == 1)
{
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
FSkeletonTreeSelection TreeSelection(SelectedItems);
return TreeSelection.IsSingleOfTypeSelected<FSkeletonTreeBoneItem>();
}
return false;
}
void SSkeletonTree::OnAddSocket()
{
// This adds a socket to the currently selected bone in the SKELETON, not the MESH.
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
FSkeletonTreeSelection TreeSelection(SelectedItems);
// Can only add a socket to one bone
if (TreeSelection.IsSingleOfTypeSelected<FSkeletonTreeBoneItem>())
{
FName BoneName = TreeSelection.GetSingleSelectedItem()->GetRowItemName();
USkeletalMeshSocket* NewSocket = GetEditableSkeletonInternal()->HandleAddSocket(BoneName);
CreateFromSkeleton();
FSelectedSocketInfo SocketInfo(NewSocket, true);
SetSelectedSocket(SocketInfo);
// now let us choose the socket name
for (TSharedPtr<ISkeletonTreeItem>& Item : LinearItems)
{
if (Item->IsOfType<FSkeletonTreeSocketItem>())
{
if (Item->GetRowItemName() == NewSocket->SocketName)
{
OnRenameSelected();
break;
}
}
}
}
}
void SSkeletonTree::OnCustomizeSocket()
{
// This should only be called on a skeleton socket, it copies the
// socket to the mesh so the user can edit it separately
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
FSkeletonTreeSelection TreeSelection(SelectedItems);
if(TreeSelection.IsSingleOfTypeSelected<FSkeletonTreeSocketItem>())
{
USkeletalMeshSocket* SocketToCustomize = StaticCastSharedPtr<FSkeletonTreeSocketItem>(TreeSelection.GetSingleSelectedItem())->GetSocket();
USkeletalMesh* SkeletalMesh = GetPreviewScene().IsValid() ? ToRawPtr(GetPreviewScene()->GetPreviewMeshComponent()->SkeletalMesh) : nullptr;
GetEditableSkeletonInternal()->HandleCustomizeSocket(SocketToCustomize, SkeletalMesh);
CreateFromSkeleton();
}
}
void SSkeletonTree::OnPromoteSocket()
{
// This should only be called on a mesh socket, it copies the
// socket to the skeleton so all meshes can use it
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
FSkeletonTreeSelection TreeSelection(SelectedItems);
// Can only customize one socket (CreateContextMenu() should prevent this firing!)
if(TreeSelection.IsSingleOfTypeSelected<FSkeletonTreeSocketItem>())
{
USkeletalMeshSocket* SocketToPromote = StaticCastSharedPtr<FSkeletonTreeSocketItem>(TreeSelection.GetSingleSelectedItem())->GetSocket();
GetEditableSkeletonInternal()->HandlePromoteSocket(SocketToPromote);
CreateFromSkeleton();
}
}
void SSkeletonTree::FillAttachAssetSubmenu(FMenuBuilder& MenuBuilder, const TSharedPtr<ISkeletonTreeItem> TargetItem)
{
FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
TArray<UClass*> FilterClasses = FComponentAssetBrokerage::GetSupportedAssets(USceneComponent::StaticClass());
//Clean up the selection so it is relevant to Persona
FilterClasses.RemoveSingleSwap(UBlueprint::StaticClass(), false); //Child actor components broker gives us blueprints which isn't wanted
FilterClasses.RemoveSingleSwap(USoundBase::StaticClass(), false); //No sounds wanted
FAssetPickerConfig AssetPickerConfig;
AssetPickerConfig.Filter.bRecursiveClasses = true;
for(int i = 0; i < FilterClasses.Num(); ++i)
{
AssetPickerConfig.Filter.ClassPaths.Add(FilterClasses[i]->GetClassPathName());
}
AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateSP(this, &SSkeletonTree::OnAssetSelectedFromPicker, TargetItem);
TSharedRef<SWidget> MenuContent = SNew(SBox)
.WidthOverride(384)
.HeightOverride(500)
[
ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig)
];
MenuBuilder.AddWidget( MenuContent, FText::GetEmpty(), true);
}
void SSkeletonTree::OnAssetSelectedFromPicker(const FAssetData& AssetData, const TSharedPtr<ISkeletonTreeItem> TargetItem)
{
FSlateApplication::Get().DismissAllMenus();
TArray<FAssetData> Assets;
Assets.Add(AssetData);
AttachAssets(TargetItem.ToSharedRef(), Assets);
}
void SSkeletonTree::OnRemoveAllAssets()
{
GetEditableSkeletonInternal()->HandleRemoveAllAssets(GetPreviewScene());
CreateFromSkeleton();
}
bool SSkeletonTree::CanRemoveAllAssets() const
{
USkeletalMesh* SkeletalMesh = GetPreviewScene().IsValid() ? ToRawPtr(GetPreviewScene()->GetPreviewMeshComponent()->SkeletalMesh) : nullptr;
const bool bHasPreviewAttachedObjects = GetEditableSkeletonInternal()->GetSkeleton().PreviewAttachedAssetContainer.Num() > 0;
const bool bHasMeshPreviewAttachedObjects = ( SkeletalMesh && SkeletalMesh->GetPreviewAttachedAssetContainer().Num() );
return bHasPreviewAttachedObjects || bHasMeshPreviewAttachedObjects;
}
bool SSkeletonTree::CanRenameSelected() const
{
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
return SelectedItems.Num() == 1 && SelectedItems[0]->CanRenameItem();
}
void SSkeletonTree::OnRenameSelected()
{
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
if(SelectedItems.Num() == 1 && SelectedItems[0]->CanRenameItem())
{
SkeletonTreeView->RequestScrollIntoView(SelectedItems[0]);
DeferredRenameRequest = SelectedItems[0];
}
}
bool SSkeletonTree::OnIsSelectableOrNavigable(TSharedPtr<class ISkeletonTreeItem> InItem) const
{
return InItem && InItem->GetFilterResult() == ESkeletonTreeFilterResult::Shown;
}
void SSkeletonTree::OnSelectionChanged(TSharedPtr<ISkeletonTreeItem> Selection, ESelectInfo::Type SelectInfo)
{
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
if( Selection.IsValid() )
{
// Disable bone proxy ticking on all bone/virtual bones
for (TSharedPtr<ISkeletonTreeItem>& Item : LinearItems)
{
if (Item->IsOfType<FSkeletonTreeBoneItem>())
{
StaticCastSharedPtr<FSkeletonTreeBoneItem>(Item)->EnableBoneProxyTick(false);
}
else if (Item->IsOfType<FSkeletonTreeVirtualBoneItem>())
{
StaticCastSharedPtr<FSkeletonTreeVirtualBoneItem>(Item)->EnableBoneProxyTick(false);
}
}
//Get all the selected items
FSkeletonTreeSelection TreeSelection(SelectedItems);
if (GetPreviewScene().IsValid())
{
UDebugSkelMeshComponent* PreviewComponent = GetPreviewScene()->GetPreviewMeshComponent();
if (TreeSelection.SelectedItems.Num() > 0 && PreviewComponent)
{
// pick the first settable bone from the selection
for (TSharedPtr<ISkeletonTreeItem> Item : TreeSelection.SelectedItems)
{
if((Item->IsOfType<FSkeletonTreeBoneItem>() || Item->IsOfType<FSkeletonTreeVirtualBoneItem>()))
{
// enable ticking on the selected bone proxies
if (Item->IsOfType<FSkeletonTreeBoneItem>())
{
StaticCastSharedPtr<FSkeletonTreeBoneItem>(Item)->EnableBoneProxyTick(true);
}
else if (Item->IsOfType<FSkeletonTreeVirtualBoneItem>())
{
StaticCastSharedPtr<FSkeletonTreeVirtualBoneItem>(Item)->EnableBoneProxyTick(true);
}
// Test SelectInfo so we don't end up in an infinite loop due to delegates calling each other
if (SelectInfo != ESelectInfo::Direct)
{
FName BoneName = Item->GetRowItemName();
// Get bone index
int32 BoneIndex = PreviewComponent->GetBoneIndex(BoneName);
if (BoneIndex != INDEX_NONE)
{
GetPreviewScene()->SetSelectedBone(BoneName, SelectInfo);
break;
}
}
}
// Test SelectInfo so we don't end up in an infinite loop due to delegates calling each other
else if (SelectInfo != ESelectInfo::Direct && Item->IsOfType<FSkeletonTreeSocketItem>())
{
TSharedPtr<FSkeletonTreeSocketItem> SocketItem = StaticCastSharedPtr<FSkeletonTreeSocketItem>(Item);
USkeletalMeshSocket* Socket = SocketItem->GetSocket();
FSelectedSocketInfo SocketInfo(Socket, SocketItem->GetParentType() == ESocketParentType::Skeleton);
GetPreviewScene()->SetSelectedSocket(SocketInfo);
}
else if (Item->IsOfType<FSkeletonTreeAttachedAssetItem>())
{
GetPreviewScene()->DeselectAll();
}
}
PreviewComponent->PostInitMeshObject(PreviewComponent->MeshObject);
}
}
}
else
{
if (GetPreviewScene().IsValid())
{
// Tell the preview scene if the user ctrl-clicked the selected bone/socket to de-select it
GetPreviewScene()->DeselectAll();
}
}
TArrayView<TSharedPtr<ISkeletonTreeItem>> ArrayView(SelectedItems);
OnSelectionChangedMulticast.Broadcast(ArrayView, SelectInfo);
}
void SSkeletonTree::AttachAssets(const TSharedRef<ISkeletonTreeItem>& TargetItem, const TArray<FAssetData>& AssetData)
{
bool bAllAssetWereLoaded = true;
TArray<UObject*> DroppedObjects;
for (int32 AssetIdx = 0; AssetIdx < AssetData.Num(); ++AssetIdx)
{
UObject* Object = AssetData[AssetIdx].GetAsset();
if ( Object != NULL )
{
if (FComponentAssetBrokerage::GetPrimaryComponentForAsset(Object->GetClass()) != nullptr)
{
DroppedObjects.Add(Object);
}
}
else
{
bAllAssetWereLoaded = false;
}
}
if(bAllAssetWereLoaded)
{
FName AttachToName = TargetItem->GetAttachName();
bool bAttachToMesh = TargetItem->IsOfType<FSkeletonTreeSocketItem>() &&
StaticCastSharedRef<FSkeletonTreeSocketItem>(TargetItem)->GetParentType() == ESocketParentType::Mesh;
GetEditableSkeletonInternal()->HandleAttachAssets(DroppedObjects, AttachToName, bAttachToMesh, GetPreviewScene());
CreateFromSkeleton();
}
}
void SSkeletonTree::OnItemScrolledIntoView( TSharedPtr<ISkeletonTreeItem> InItem, const TSharedPtr<ITableRow>& InWidget)
{
if(DeferredRenameRequest.IsValid())
{
DeferredRenameRequest->RequestRename();
DeferredRenameRequest.Reset();
}
}
void SSkeletonTree::OnTreeDoubleClick( TSharedPtr<ISkeletonTreeItem> InItem )
{
InItem->OnItemDoubleClicked();
}
void SSkeletonTree::SetTreeItemExpansionRecursive(TSharedPtr< ISkeletonTreeItem > TreeItem, bool bInExpansionState) const
{
SkeletonTreeView->SetItemExpansion(TreeItem, bInExpansionState);
// Recursively go through the children.
for (auto It = TreeItem->GetChildren().CreateIterator(); It; ++It)
{
SetTreeItemExpansionRecursive(*It, bInExpansionState);
}
}
void SSkeletonTree::PostUndo(bool bSuccess)
{
// Rebuild the tree view whenever we undo a change to the skeleton
CreateFromSkeleton();
HandleTreeRefresh();
}
void SSkeletonTree::PostRedo(bool bSuccess)
{
// Rebuild the tree view whenever we redo a change to the skeleton
CreateFromSkeleton();
HandleTreeRefresh();
}
void SSkeletonTree::OnFilterTextChanged( const FText& SearchText )
{
FilterText = SearchText;
ApplyFilter();
}
void SSkeletonTree::HandlePackageReloaded(const EPackageReloadPhase InPackageReloadPhase, FPackageReloadedEvent* InPackageReloadedEvent)
{
if (InPackageReloadPhase == EPackageReloadPhase::PostPackageFixup)
{
for (const auto& RepointedObjectPair : InPackageReloadedEvent->GetRepointedObjects())
{
if (USkeleton* NewObject = Cast<USkeleton>(RepointedObjectPair.Value))
{
if (&GetEditableSkeletonInternal()->GetSkeleton() == NewObject)
{
Refresh();
}
}
}
}
}
TSharedRef<SWidget> SSkeletonTree::GetBlendProfileColumnMenuContent()
{
return UToolMenus::Get()->GenerateWidget("SkeletonTree.BlendProfilesMenu", FToolMenuContext(UICommandList, Extenders));
}
void SSkeletonTree::ExpandTreeOnSelection(TSharedPtr<ISkeletonTreeItem> RowToExpand, bool bForce)
{
if(GetDefault<UPersonaOptions>()->bExpandTreeOnSelection || bForce)
{
RowToExpand = RowToExpand->GetParent();
while(RowToExpand.IsValid())
{
SkeletonTreeView->SetItemExpansion(RowToExpand, true);
RowToExpand = RowToExpand->GetParent();
}
}
}
void SSkeletonTree::CreateBlendProfileMenu(UToolMenu* InMenu)
{
FToolMenuOwnerScoped OwnerScoped(this);
UToolMenu* Menu = InMenu ? InMenu : UToolMenus::Get()->ExtendMenu("SkeletonTree.BlendProfilesMenu");
Menu->AddDynamicSection(NAME_None,
FNewSectionConstructChoice(FNewToolMenuDelegate::CreateLambda([this](UToolMenu* InMenu)
{
const FSkeletonTreeCommands& Actions = FSkeletonTreeCommands::Get();
FToolMenuSection& ProfilesSection = InMenu->AddSection(TEXT("BlendProfileActions"), LOCTEXT("BlendProfiles", "Blend Profiles"));
for (UBlendProfile* Profile : GetEditableSkeletonInternal()->GetBlendProfiles())
{
ProfilesSection.AddMenuEntry(
Profile->GetFName(),
FText::FromName(Profile->GetFName()),
LOCTEXT("SelectBlendProfileTooltip", "Select this profile for editing."),
FSlateIcon(),
FToolUIActionChoice(
FUIAction(
FExecuteAction::CreateSP(BlendProfilePicker.ToSharedRef(), &SBlendProfilePicker::SetSelectedProfile, Profile, true),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SSkeletonTree::IsBlendProfileSelected, Profile->GetFName())
)
),
EUserInterfaceActionType::RadioButton
);
}
if (BlendProfilePicker->GetSelectedBlendProfileName() != NAME_None)
{
FToolMenuSection& EditSection = InMenu->AddSection(TEXT("BlendProfileEdit"), LOCTEXT("EditBlendProfilesSection", "Edit"));
EditSection.AddMenuEntry(
"ClearBlendProfile",
LOCTEXT("Clear", "Clear Selected"),
LOCTEXT("Clear_ToolTip", "Clear the selected blend profile."),
FSlateIcon(),
FToolUIActionChoice(FUIAction(FExecuteAction::CreateSP(BlendProfilePicker.ToSharedRef(), &SBlendProfilePicker::OnClearSelection))));
EditSection.AddMenuEntry(
Actions.DeleteCurrentBlendProfile,
FText::Format(LOCTEXT("DeleteBlendProfileLabel", "Delete {0}"),
FText::FromName(BlendProfilePicker->GetSelectedBlendProfileName())));
}
{
FToolMenuSection& NewSection = InMenu->AddSection(TEXT("BlendProfileNew"), LOCTEXT("NewBlendProfiles", "New"));
NewSection.AddMenuEntry(Actions.CreateTimeBlendProfile);
NewSection.AddMenuEntry(Actions.CreateWeightBlendProfile);
NewSection.AddMenuEntry(Actions.CreateBlendMask);
}
})));
}
void SSkeletonTree::OnCreateBlendProfile(const EBlendProfileMode InMode)
{
// Ensure the Blend Profile Column is Visible
BlendProfilePicker->OnClearSelection();
SkeletonTreeView->GetHeaderRow()->SetShowGeneratedColumn(ISkeletonTree::Columns::BlendProfile);
// Set our NewBlendProfileMode for our BlendProfileHeader to use when the text is commited.
NewBlendProfileMode = InMode;
// Activate the Header Entry Box
BlendProfileHeader->SetReadOnly(false);
BlendProfileHeader->EnterEditingMode();
}
void SSkeletonTree::OnDeleteCurrentBlendProfile()
{
GetEditableSkeletonInternal()->RemoveBlendProfile(BlendProfilePicker->GetSelectedBlendProfile());
BlendProfilePicker->OnClearSelection();
}
bool SSkeletonTree::IsBlendProfileSelected(FName ProfileName) const
{
return BlendProfilePicker->GetSelectedBlendProfileName() == ProfileName;
}
void SSkeletonTree::Refresh()
{
CreateFromSkeleton();
}
void SSkeletonTree::RefreshFilter()
{
ApplyFilter();
}
void SSkeletonTree::SetSkeletalMesh(USkeletalMesh* NewSkeletalMesh)
{
if (GetPreviewScene().IsValid())
{
GetPreviewScene()->SetPreviewMesh(NewSkeletalMesh);
}
CreateFromSkeleton();
}
void SSkeletonTree::SetSelectedSocket( const FSelectedSocketInfo& SocketInfo )
{
if (!bSelecting)
{
TGuardValue<bool> RecursionGuard(bSelecting, true);
// Firstly, find which row (if any) contains the socket requested
for (auto SkeletonRowIt = LinearItems.CreateConstIterator(); SkeletonRowIt; ++SkeletonRowIt)
{
TSharedPtr<ISkeletonTreeItem> SkeletonRow = *(SkeletonRowIt);
if (SkeletonRow->GetFilterResult() != ESkeletonTreeFilterResult::Hidden && SkeletonRow->IsOfType<FSkeletonTreeSocketItem>() && StaticCastSharedPtr<FSkeletonTreeSocketItem>(SkeletonRow)->GetSocket() == SocketInfo.Socket)
{
SkeletonTreeView->SetItemSelection(SkeletonRow, true);
ExpandTreeOnSelection(SkeletonRow);
SkeletonTreeView->RequestScrollIntoView(SkeletonRow);
}
}
}
}
void SSkeletonTree::SetSelectedBone( const FName& BoneName, ESelectInfo::Type InSelectInfo )
{
if (!bSelecting)
{
TGuardValue<bool> RecursionGuard(bSelecting, true);
// Find which row (if any) contains the bone requested
for (auto SkeletonRowIt = LinearItems.CreateConstIterator(); SkeletonRowIt; ++SkeletonRowIt)
{
TSharedPtr<ISkeletonTreeItem> SkeletonRow = *(SkeletonRowIt);
if (SkeletonRow->GetFilterResult() != ESkeletonTreeFilterResult::Hidden && (SkeletonRow->IsOfType<FSkeletonTreeBoneItem>() || SkeletonRow->IsOfType<FSkeletonTreeVirtualBoneItem>()) && SkeletonRow->GetRowItemName() == BoneName)
{
SkeletonTreeView->SetItemSelection(SkeletonRow, true, InSelectInfo);
ExpandTreeOnSelection(SkeletonRow);
SkeletonTreeView->RequestScrollIntoView(SkeletonRow);
}
}
}
}
void SSkeletonTree::DeselectAll()
{
if (!bSelecting)
{
TGuardValue<bool> RecursionGuard(bSelecting, true);
SkeletonTreeView->ClearSelection();
}
}
void SSkeletonTree::NotifyUser( FNotificationInfo& NotificationInfo )
{
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification( NotificationInfo );
if ( Notification.IsValid() )
{
Notification->SetCompletionState( SNotificationItem::CS_Fail );
}
}
void SSkeletonTree::CreateNewMenu()
{
FToolMenuOwnerScoped OwnerScoped(this);
const FSkeletonTreeCommands& Actions = FSkeletonTreeCommands::Get();
UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("SkeletonTree.NewMenu");
{
FToolMenuSection& CreateSection = Menu->AddSection("CreateNew", LOCTEXT("SkeletonCreateNew", "Create"));
CreateSection.AddMenuEntry(Actions.AddSocket);
CreateSection.AddSubMenu(
"VirtualBones",
LOCTEXT("AddVirtualBone", "Add Virtual Bone"),
LOCTEXT("AddVirtualBone_ToolTip", "Adds a virtual bone to the skeleton."),
FNewToolMenuChoice(FNewMenuDelegate::CreateSP(this, &SSkeletonTree::FillVirtualBoneSubmenu)));
}
{
FToolMenuSection& BlendSection = Menu->AddSection("Blend", LOCTEXT("SkeletonBlend", "Blend"));
BlendSection.AddMenuEntry(Actions.CreateTimeBlendProfile);
BlendSection.AddMenuEntry(Actions.CreateWeightBlendProfile);
BlendSection.AddMenuEntry(Actions.CreateBlendMask);
}
}
TSharedRef< SWidget > SSkeletonTree::CreateNewMenuWidget()
{
return UToolMenus::Get()->GenerateWidget("SkeletonTree.NewMenu", FToolMenuContext(UICommandList, Extenders));
}
void SSkeletonTree::CreateFilterMenu()
{
FToolMenuOwnerScoped OwnerScoped(this);
const FSkeletonTreeCommands& Actions = FSkeletonTreeCommands::Get();
UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("SkeletonTree.FilterMenu");
{
FToolMenuSection& BlendProfilesSection = Menu->AddSection("BlendProfiles", LOCTEXT("BlendProfilesMenuHeading", "Blend Profiles"));
BlendProfilesSection.AddSubMenu(
"BlendProfiles",
LOCTEXT("BlendProfilesSubMenu", "Blend Profiles"),
LOCTEXT("BlendProfilesSubMenuTooltip", "Edit Blend Profiles in this Skeleton"),
FNewToolMenuChoice(FNewToolMenuDelegate::CreateSP(this, &SSkeletonTree::CreateBlendProfileMenu)));
}
{
FToolMenuSection& OptionsSection = Menu->AddSection("FilterOptions", LOCTEXT("OptionsMenuHeading", "Options"));
OptionsSection.AddMenuEntry(Actions.ShowRetargeting);
OptionsSection.AddMenuEntry(Actions.FilteringFlattensHierarchy);
OptionsSection.AddMenuEntry(Actions.HideParentsWhenFiltering);
OptionsSection.AddMenuEntry(Actions.ShowDebugVisualization);
}
{
FToolMenuSection& BonesSection = Menu->AddSection("FilterBones", LOCTEXT("BonesMenuHeading", "Bones"));
BonesSection.AddMenuEntry(Actions.ShowAllBones);
BonesSection.AddMenuEntry(Actions.ShowMeshBones);
BonesSection.AddMenuEntry(Actions.ShowLODBones);
BonesSection.AddMenuEntry(Actions.ShowWeightedBones);
BonesSection.AddMenuEntry(Actions.HideBones);
}
{
FToolMenuSection& BonesSection = Menu->AddSection("FilterSockets", LOCTEXT("SocketsMenuHeading", "Sockets"));
BonesSection.AddMenuEntry(Actions.ShowActiveSockets);
BonesSection.AddMenuEntry(Actions.ShowMeshSockets);
BonesSection.AddMenuEntry(Actions.ShowSkeletonSockets);
BonesSection.AddMenuEntry(Actions.ShowAllSockets);
BonesSection.AddMenuEntry(Actions.HideSockets);
}
}
TSharedRef< SWidget > SSkeletonTree::CreateFilterMenuWidget()
{
return UToolMenus::Get()->GenerateWidget("SkeletonTree.FilterMenu", FToolMenuContext(UICommandList, Extenders));
}
void SSkeletonTree::SetBoneFilter( EBoneFilter InBoneFilter )
{
check( InBoneFilter < EBoneFilter::Count );
BoneFilter = InBoneFilter;
ApplyFilter();
}
bool SSkeletonTree::IsBoneFilter( EBoneFilter InBoneFilter ) const
{
return BoneFilter == InBoneFilter;
}
void SSkeletonTree::SetSocketFilter( ESocketFilter InSocketFilter )
{
check( InSocketFilter < ESocketFilter::Count );
SocketFilter = InSocketFilter;
SetPreviewComponentSocketFilter();
ApplyFilter();
}
void SSkeletonTree::SetPreviewComponentSocketFilter() const
{
// Set the socket filter in the debug skeletal mesh component so the viewport can share the filter settings
if (GetPreviewScene().IsValid())
{
UDebugSkelMeshComponent* PreviewComponent = GetPreviewScene()->GetPreviewMeshComponent();
bool bAllOrActive = (SocketFilter == ESocketFilter::All || SocketFilter == ESocketFilter::Active);
if (PreviewComponent)
{
PreviewComponent->bMeshSocketsVisible = bAllOrActive || SocketFilter == ESocketFilter::Mesh;
PreviewComponent->bSkeletonSocketsVisible = bAllOrActive || SocketFilter == ESocketFilter::Skeleton;
}
}
}
bool SSkeletonTree::IsSocketFilter( ESocketFilter InSocketFilter ) const
{
return SocketFilter == InSocketFilter;
}
FText SSkeletonTree::GetFilterMenuTooltip() const
{
TArray<FText> FilterLabels;
if(Builder->IsShowingBones())
{
switch ( BoneFilter )
{
case EBoneFilter::All:
FilterLabels.Add(LOCTEXT( "BoneFilterMenuAll", "Bones" ));
break;
case EBoneFilter::Mesh:
FilterLabels.Add(LOCTEXT( "BoneFilterMenuMesh", "Mesh Bones" ));
break;
case EBoneFilter::LOD:
FilterLabels.Add(LOCTEXT("BoneFilterMenuLOD", "LOD Bones"));
break;
case EBoneFilter::Weighted:
FilterLabels.Add(LOCTEXT( "BoneFilterMenuWeighted", "Weighted Bones" ));
break;
case EBoneFilter::None:
break;
default:
// Unknown mode
check(false);
break;
}
}
if(Builder->IsShowingSockets())
{
switch (SocketFilter)
{
case ESocketFilter::Active:
FilterLabels.Add(LOCTEXT("SocketFilterMenuActive", "Active Sockets"));
break;
case ESocketFilter::Mesh:
FilterLabels.Add(LOCTEXT("SocketFilterMenuMesh", "Mesh Sockets"));
break;
case ESocketFilter::Skeleton:
FilterLabels.Add(LOCTEXT("SocketFilterMenuSkeleton", "Skeleton Sockets"));
break;
case ESocketFilter::All:
FilterLabels.Add(LOCTEXT("SocketFilterMenuAll", "All Sockets"));
break;
case ESocketFilter::None:
break;
default:
// Unknown mode
check(false);
break;
}
}
OnGetFilterText.ExecuteIfBound(FilterLabels);
FText Label;
if(FilterLabels.Num() > 0)
{
Label = FText::Format(LOCTEXT("FilterMenuLabelFormatStart", "Showing: {0}"), FilterLabels[0]);
for(int32 LabelIndex = 1; LabelIndex < FilterLabels.Num(); ++LabelIndex)
{
Label = FText::Format(LOCTEXT("FilterMenuLabelFormat", "{0}, {1}"), Label, FilterLabels[LabelIndex]);
}
Label = FText::Format(LOCTEXT("FilterMenuLabelFormatEnd", "{0}\nShift-clicking on items will 'pin' them to the skeleton tree."), Label);
}
else
{
Label = LOCTEXT("ShowingNoneLabel", "Filters.\nShift-clicking on items will 'pin' them to the skeleton tree.");
}
return Label;
}
bool SSkeletonTree::IsAddingSocketsAllowed() const
{
if ( SocketFilter == ESocketFilter::Skeleton ||
SocketFilter == ESocketFilter::Active ||
SocketFilter == ESocketFilter::All )
{
return true;
}
return false;
}
FReply SSkeletonTree::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
{
if ( UICommandList->ProcessCommandBindings( InKeyEvent ) )
{
return FReply::Handled();
}
return FReply::Unhandled();
}
bool SSkeletonTree::CanDeleteSelectedRows() const
{
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
FSkeletonTreeSelection TreeSelection(SelectedItems);
return (TreeSelection.HasSelectedOfType<FSkeletonTreeAttachedAssetItem>() || TreeSelection.HasSelectedOfType<FSkeletonTreeSocketItem>() || TreeSelection.HasSelectedOfType<FSkeletonTreeVirtualBoneItem>());
}
void SSkeletonTree::OnDeleteSelectedRows()
{
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
FSkeletonTreeSelection TreeSelection(SelectedItems);
if(TreeSelection.HasSelectedOfType<FSkeletonTreeAttachedAssetItem>() || TreeSelection.HasSelectedOfType<FSkeletonTreeSocketItem>() || TreeSelection.HasSelectedOfType<FSkeletonTreeVirtualBoneItem>())
{
FScopedTransaction Transaction( LOCTEXT( "SkeletonTreeDeleteSelected", "Delete selected sockets/meshes/bones from skeleton tree" ) );
DeleteAttachedAssets( TreeSelection.GetSelectedItems<FSkeletonTreeAttachedAssetItem>() );
DeleteSockets( TreeSelection.GetSelectedItems<FSkeletonTreeSocketItem>() );
DeleteVirtualBones( TreeSelection.GetSelectedItems<FSkeletonTreeVirtualBoneItem>() );
CreateFromSkeleton();
}
}
void SSkeletonTree::DeleteAttachedAssets(const TArray<TSharedPtr<FSkeletonTreeAttachedAssetItem>>& InDisplayedAttachedAssetInfos)
{
DeselectAll();
TArray<FPreviewAttachedObjectPair> AttachedObjects;
for(const TSharedPtr<FSkeletonTreeAttachedAssetItem>& AttachedAssetInfo : InDisplayedAttachedAssetInfos)
{
FPreviewAttachedObjectPair Pair;
Pair.SetAttachedObject(AttachedAssetInfo->GetAsset());
Pair.AttachedTo = AttachedAssetInfo->GetParentName();
AttachedObjects.Add(Pair);
}
GetEditableSkeletonInternal()->HandleDeleteAttachedAssets(AttachedObjects, GetPreviewScene());
}
void SSkeletonTree::DeleteSockets(const TArray<TSharedPtr<FSkeletonTreeSocketItem>>& InDisplayedSocketInfos)
{
DeselectAll();
TArray<FSelectedSocketInfo> SocketInfo;
for (const TSharedPtr<FSkeletonTreeSocketItem>& DisplayedSocketInfo : InDisplayedSocketInfos)
{
USkeletalMeshSocket* SocketToDelete = DisplayedSocketInfo->GetSocket();
SocketInfo.Add(FSelectedSocketInfo(SocketToDelete, DisplayedSocketInfo->GetParentType() == ESocketParentType::Skeleton));
}
GetEditableSkeletonInternal()->HandleDeleteSockets(SocketInfo, GetPreviewScene());
}
void SSkeletonTree::DeleteVirtualBones(const TArray<TSharedPtr<FSkeletonTreeVirtualBoneItem>>& InDisplayedVirtualBoneInfos)
{
DeselectAll();
TArray<FName> VirtualBoneInfo;
for (const TSharedPtr<FSkeletonTreeVirtualBoneItem>& DisplayedVirtualBoneInfo : InDisplayedVirtualBoneInfos)
{
VirtualBoneInfo.Add(DisplayedVirtualBoneInfo->GetRowItemName());
}
GetEditableSkeletonInternal()->HandleDeleteVirtualBones(VirtualBoneInfo, GetPreviewScene());
}
void SSkeletonTree::OnChangeShowingAdvancedOptions()
{
SkeletonTreeView->GetHeaderRow()->SetShowGeneratedColumn(ISkeletonTree::Columns::Retargeting, !IsShowingAdvancedOptions());
HandleTreeRefresh();
}
bool SSkeletonTree::IsShowingAdvancedOptions() const
{
return SkeletonTreeView->GetHeaderRow()->IsColumnVisible(ISkeletonTree::Columns::Retargeting);
}
void SSkeletonTree::OnChangeShowingDebugVisualizationOptions()
{
SkeletonTreeView->GetHeaderRow()->SetShowGeneratedColumn(ISkeletonTree::Columns::DebugVisualization, !IsShowingDebugVisualizationOptions());
}
bool SSkeletonTree::IsShowingDebugVisualizationOptions() const
{
return SkeletonTreeView->GetHeaderRow()->IsColumnVisible(ISkeletonTree::Columns::DebugVisualization);
}
UBlendProfile* SSkeletonTree::GetSelectedBlendProfile()
{
return BlendProfilePicker->GetSelectedBlendProfile();
}
FName SSkeletonTree::GetSelectedBlendProfileName() const
{
return BlendProfilePicker->GetSelectedBlendProfileName();
}
void SSkeletonTree::OnBlendProfileSelected(UBlendProfile* NewProfile)
{
SkeletonTreeView->GetHeaderRow()->RefreshColumns();
if (NewProfile != nullptr)
SkeletonTreeView->GetHeaderRow()->SetShowGeneratedColumn(ISkeletonTree::Columns::BlendProfile);
HandleTreeRefresh();
// When a new blend profile is created/selected - reaffirm that the header can't be edited.
// At this time, there is no re-naming of Blend Profiles
BlendProfileHeader->SetReadOnly(true);
}
void SSkeletonTree::RecursiveSetBlendProfileScales(float InScaleToSet)
{
UBlendProfile* SelectedBlendProfile = BlendProfilePicker->GetSelectedBlendProfile();
if(SelectedBlendProfile)
{
TArray<FName> BoneNames;
TArray<TSharedPtr<ISkeletonTreeItem>> SelectedItems = SkeletonTreeView->GetSelectedItems();
FSkeletonTreeSelection TreeSelection(SelectedItems);
for(TSharedPtr<FSkeletonTreeBoneItem>& SelectedBone : TreeSelection.GetSelectedItems<FSkeletonTreeBoneItem>())
{
BoneNames.Add(SelectedBone->GetRowItemName());
}
GetEditableSkeletonInternal()->RecursiveSetBlendProfileScales(SelectedBlendProfile->GetFName(), BoneNames, InScaleToSet);
HandleTreeRefresh();
}
}
void SSkeletonTree::HandleTreeRefresh()
{
SkeletonTreeView->RequestTreeRefresh();
}
void SSkeletonTree::PostRenameSocket(UObject* InAttachedObject, const FName& InOldName, const FName& InNewName)
{
TSharedPtr<IPersonaPreviewScene> LinkedPreviewScene = GetPreviewScene();
if (LinkedPreviewScene.IsValid())
{
LinkedPreviewScene->RemoveAttachedObjectFromPreviewComponent(InAttachedObject, InOldName);
LinkedPreviewScene->AttachObjectToPreviewComponent(InAttachedObject, InNewName);
}
}
void SSkeletonTree::PostDuplicateSocket(UObject* InAttachedObject, const FName& InSocketName)
{
TSharedPtr<IPersonaPreviewScene> LinkedPreviewScene = GetPreviewScene();
if (LinkedPreviewScene.IsValid())
{
LinkedPreviewScene->AttachObjectToPreviewComponent(InAttachedObject, InSocketName);
}
CreateFromSkeleton();
}
void SSkeletonTree::PostSetSocketParent()
{
CreateFromSkeleton();
}
void RecursiveSetLODChange(UDebugSkelMeshComponent* PreviewComponent, TSharedPtr<ISkeletonTreeItem> TreeRow)
{
if (TreeRow->IsOfType<FSkeletonTreeBoneItem>())
{
StaticCastSharedPtr<FSkeletonTreeBoneItem>(TreeRow)->CacheLODChange(PreviewComponent);
}
for (auto& Child : TreeRow->GetChildren())
{
RecursiveSetLODChange(PreviewComponent, Child);
}
}
void SSkeletonTree::OnLODSwitched()
{
if (GetPreviewScene().IsValid())
{
UDebugSkelMeshComponent* PreviewComponent = GetPreviewScene()->GetPreviewMeshComponent();
if (PreviewComponent)
{
LastCachedLODForPreviewMeshComponent = PreviewComponent->GetPredictedLODLevel();
if (BoneFilter == EBoneFilter::Weighted || BoneFilter == EBoneFilter::LOD)
{
CreateFromSkeleton();
}
else
{
for (TSharedPtr<ISkeletonTreeItem>& Item : Items)
{
RecursiveSetLODChange(PreviewComponent, Item);
}
}
}
}
}
void SSkeletonTree::OnPreviewMeshChanged(USkeletalMesh* InOldSkeletalMesh, USkeletalMesh* InNewSkeletalMesh)
{
if (InOldSkeletalMesh != InNewSkeletalMesh || InNewSkeletalMesh == nullptr)
{
DeselectAll();
}
}
void SSkeletonTree::SelectItemsBy(TFunctionRef<bool(const TSharedRef<ISkeletonTreeItem>&, bool&)> Predicate) const
{
TArray<TPair<TSharedPtr<ISkeletonTreeItem>, bool>> ItemsToSelect;
TSharedPtr<ISkeletonTreeItem> ScrollItem = nullptr;
for (const TSharedPtr<ISkeletonTreeItem>& Item : LinearItems)
{
if(Item->GetFilterResult() != ESkeletonTreeFilterResult::Hidden)
{
bool bExpand = false;
if (Predicate(Item.ToSharedRef(), bExpand))
{
ItemsToSelect.Emplace(Item, bExpand);
ScrollItem = Item;
}
}
}
if(ItemsToSelect.Num() > 0)
{
SkeletonTreeView->ClearSelection();
for (const TPair<TSharedPtr<ISkeletonTreeItem>, bool>& ItemPair : ItemsToSelect)
{
TSharedPtr<ISkeletonTreeItem> Item = ItemPair.Key;
SkeletonTreeView->SetItemSelection(Item, true);
if (ItemPair.Value)
{
if(Item->GetChildren().Num() == 0)
{
// leaf nodes expand their parent
TSharedPtr<ISkeletonTreeItem> ParentItem = Item->GetParent();
if(ParentItem.IsValid())
{
SkeletonTreeView->SetItemExpansion(ParentItem, true);
}
}
else
{
SkeletonTreeView->SetItemExpansion(Item, true);
}
}
}
}
if (ScrollItem.IsValid())
{
SkeletonTreeView->RequestScrollIntoView(ScrollItem);
}
}
void SSkeletonTree::DuplicateAndSelectSocket(const FSelectedSocketInfo& SocketInfoToDuplicate, const FName& NewParentBoneName /*= FName()*/)
{
USkeletalMesh* SkeletalMesh = GetPreviewScene().IsValid() ? ToRawPtr(GetPreviewScene()->GetPreviewMeshComponent()->SkeletalMesh) : nullptr;
USkeletalMeshSocket* NewSocket = GetEditableSkeleton()->DuplicateSocket(SocketInfoToDuplicate, NewParentBoneName, SkeletalMesh);
if (GetPreviewScene().IsValid())
{
GetPreviewScene()->SetSelectedSocket(FSelectedSocketInfo(NewSocket, SocketInfoToDuplicate.bSocketIsOnSkeleton));
}
CreateFromSkeleton();
FSelectedSocketInfo NewSocketInfo(NewSocket, SocketInfoToDuplicate.bSocketIsOnSkeleton);
SetSelectedSocket(NewSocketInfo);
}
void SSkeletonTree::HandleFocusCamera()
{
if (GetPreviewScene().IsValid())
{
GetPreviewScene()->FocusViews();
}
if(!SkeletonTreeView->GetSelectedItems().IsEmpty())
{
TSharedPtr<class ISkeletonTreeItem> SelectedRow = SkeletonTreeView->GetSelectedItems()[0];
ExpandTreeOnSelection(SelectedRow);
SkeletonTreeView->RequestScrollIntoView(SelectedRow);
}
}
ESkeletonTreeFilterResult SSkeletonTree::HandleFilterSkeletonTreeItem(const FSkeletonTreeFilterArgs& InArgs, const TSharedPtr<class ISkeletonTreeItem>& InItem)
{
ESkeletonTreeFilterResult Result = ESkeletonTreeFilterResult::Shown;
if(InItem->IsOfType<FSkeletonTreeBoneItem>() || InItem->IsOfType<FSkeletonTreeSocketItem>() || InItem->IsOfType<FSkeletonTreeAttachedAssetItem>() || InItem->IsOfType<FSkeletonTreeVirtualBoneItem>())
{
if (InArgs.TextFilter.IsValid())
{
if (InArgs.TextFilter->TestTextFilter(FBasicStringFilterExpressionContext(InItem->GetRowItemName().ToString())))
{
Result = ESkeletonTreeFilterResult::ShownHighlighted;
}
else
{
Result = ESkeletonTreeFilterResult::Hidden;
}
}
if (InItem->IsOfType<FSkeletonTreeBoneItem>())
{
TSharedPtr<FSkeletonTreeBoneItem> BoneItem = StaticCastSharedPtr<FSkeletonTreeBoneItem>(InItem);
if (BoneFilter == EBoneFilter::None)
{
Result = ESkeletonTreeFilterResult::Hidden;
}
else if (GetPreviewScene().IsValid())
{
UDebugSkelMeshComponent* PreviewMeshComponent = GetPreviewScene()->GetPreviewMeshComponent();
if (PreviewMeshComponent)
{
int32 BoneMeshIndex = PreviewMeshComponent->GetBoneIndex(BoneItem->GetRowItemName());
// Remove non-mesh bones if we're filtering
if ((BoneFilter == EBoneFilter::Mesh || BoneFilter == EBoneFilter::Weighted || BoneFilter == EBoneFilter::LOD) &&
BoneMeshIndex == INDEX_NONE)
{
Result = ESkeletonTreeFilterResult::Hidden;
}
// Remove non-vertex-weighted bones if we're filtering
if (BoneFilter == EBoneFilter::Weighted && !BoneItem->IsBoneWeighted(BoneMeshIndex, PreviewMeshComponent))
{
Result = ESkeletonTreeFilterResult::Hidden;
}
// Remove non-vertex-weighted bones if we're filtering
if (BoneFilter == EBoneFilter::LOD && !BoneItem->IsBoneRequired(BoneMeshIndex, PreviewMeshComponent))
{
Result = ESkeletonTreeFilterResult::Hidden;
}
}
}
}
else if (InItem->IsOfType<FSkeletonTreeSocketItem>())
{
TSharedPtr<FSkeletonTreeSocketItem> SocketItem = StaticCastSharedPtr<FSkeletonTreeSocketItem>(InItem);
if (SocketFilter == ESocketFilter::None)
{
Result = ESkeletonTreeFilterResult::Hidden;
}
// Remove non-mesh sockets if we're filtering
if ((SocketFilter == ESocketFilter::Mesh || SocketFilter == ESocketFilter::None) && SocketItem->GetParentType() == ESocketParentType::Skeleton)
{
Result = ESkeletonTreeFilterResult::Hidden;
}
// Remove non-skeleton sockets if we're filtering
if ((SocketFilter == ESocketFilter::Skeleton || SocketFilter == ESocketFilter::None) && SocketItem->GetParentType() == ESocketParentType::Mesh)
{
Result = ESkeletonTreeFilterResult::Hidden;
}
if (SocketFilter == ESocketFilter::Active && SocketItem->GetParentType() == ESocketParentType::Skeleton && SocketItem->IsSocketCustomized())
{
// Don't add the skeleton socket if it's already added for the mesh
Result = ESkeletonTreeFilterResult::Hidden;
}
}
}
return Result;
}
void SSkeletonTree::HandleSelectedBoneChanged(const FName& InBoneName, ESelectInfo::Type InSelectInfo)
{
SetSelectedBone(InBoneName, InSelectInfo);
}
void SSkeletonTree::HandleSelectedSocketChanged(const FSelectedSocketInfo& InSocketInfo)
{
SetSelectedSocket(InSocketInfo);
}
void SSkeletonTree::HandleDeselectAll()
{
DeselectAll();
}
#undef LOCTEXT_NAMESPACE