Files
UnrealEngineUWP/Engine/Source/Editor/Persona/Private/PreviewSceneCustomizations.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

825 lines
33 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PreviewSceneCustomizations.h"
#include "Modules/ModuleManager.h"
#include "AssetRegistry/AssetData.h"
#include "IDetailPropertyRow.h"
#include "DetailLayoutBuilder.h"
#include "DetailCategoryBuilder.h"
#include "PropertyCustomizationHelpers.h"
#include "PersonaPreviewSceneDescription.h"
#include "PersonaPreviewSceneController.h"
#include "PersonaPreviewSceneDefaultController.h"
#include "PersonaPreviewSceneRefPoseController.h"
#include "PersonaPreviewSceneAnimationController.h"
#include "Engine/PreviewMeshCollection.h"
#include "Factories/PreviewMeshCollectionFactory.h"
#include "IPropertyUtilities.h"
#include "Preferences/PersonaOptions.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Images/SImage.h"
#include "AssetToolsModule.h"
#include "IAssetTools.h"
#include "Animation/AnimBlueprint.h"
#include "Animation/Skeleton.h"
#include "UObject/UObjectIterator.h"
#include "Widgets/Input/SComboBox.h"
#include "PhysicsEngine/PhysicsAsset.h"
#include "Algo/Sort.h"
#include "ScopedTransaction.h"
#include "Features/IModularFeatures.h"
#include "AnimPreviewInstance.h"
#define LOCTEXT_NAMESPACE "PreviewSceneCustomizations"
// static list that contains available classes, so that we can only allow these classes
TArray<FTopLevelAssetPath> FPreviewSceneDescriptionCustomization::AvailableClassNameList;
FPreviewSceneDescriptionCustomization::FPreviewSceneDescriptionCustomization(const FString& InSkeletonName, const TSharedRef<class IPersonaToolkit>& InPersonaToolkit)
: SkeletonName(InSkeletonName)
, PersonaToolkit(InPersonaToolkit)
, PreviewScene(StaticCastSharedRef<FAnimationEditorPreviewScene>(InPersonaToolkit->GetPreviewScene()))
, EditableSkeleton(InPersonaToolkit->GetEditableSkeleton())
{
// setup custom factory up-front so we can control its lifetime
FactoryToUse = NewObject<UPreviewMeshCollectionFactory>();
FactoryToUse->AddToRoot();
// only first time
if (AvailableClassNameList.Num() == 0)
{
for (TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt)
{
if (ClassIt->IsChildOf(UDataAsset::StaticClass()) && ClassIt->ImplementsInterface(UPreviewCollectionInterface::StaticClass()))
{
AvailableClassNameList.Add(ClassIt->GetClassPathName());
}
}
}
}
FPreviewSceneDescriptionCustomization::~FPreviewSceneDescriptionCustomization()
{
if (FactoryToUse)
{
FactoryToUse->RemoveFromRoot();
FactoryToUse = nullptr;
}
if (const TSharedPtr<IPersonaToolkit> Toolkit = PersonaToolkit.Pin())
{
if (UAnimBlueprint* AnimBlueprint = Toolkit->GetAnimBlueprint())
{
AnimBlueprint->OnCompiled().RemoveAll(this);
}
}
}
void FPreviewSceneDescriptionCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
MyDetailLayout = &DetailBuilder;
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
TSharedRef<IPropertyHandle> PreviewControllerProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPersonaPreviewSceneDescription, PreviewController));
TSharedRef<IPropertyHandle> SkeletalMeshProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPersonaPreviewSceneDescription, PreviewMesh));
AdditionalMeshesProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPersonaPreviewSceneDescription, AdditionalMeshes));
AdditionalMeshesProperty->SetOnPropertyResetToDefault(FSimpleDelegate::CreateSP(this, &FPreviewSceneDescriptionCustomization::OnResetAdditionalMeshes));
TArray<UClass*> BuiltInPreviewControllers = { UPersonaPreviewSceneDefaultController::StaticClass(), UPersonaPreviewSceneRefPoseController::StaticClass(), UPersonaPreviewSceneAnimationController::StaticClass() };
TArray<UClass*> DynamicPreviewControllers;
for (TObjectIterator<UClass> It; It; ++It)
{
UClass* CurrentClass = *It;
if (CurrentClass->IsChildOf(UPersonaPreviewSceneController::StaticClass()) &&
!(CurrentClass->HasAnyClassFlags(CLASS_Abstract)) &&
!BuiltInPreviewControllers.Contains(CurrentClass))
{
DynamicPreviewControllers.Add(CurrentClass);
}
}
Algo::SortBy(DynamicPreviewControllers, [](UClass* Cls) { return Cls->GetName(); });
ControllerItems.Reset();
for (UClass* ControllerClass : BuiltInPreviewControllers)
{
ControllerItems.Add(MakeShared<FPersonaModeComboEntry>(ControllerClass));
}
for (UClass* ControllerClass : DynamicPreviewControllers)
{
ControllerItems.Add(MakeShared<FPersonaModeComboEntry>(ControllerClass));
}
PreviewControllerProperty->MarkHiddenByCustomization();
IDetailCategoryBuilder& AnimCategory = DetailBuilder.EditCategory("Animation");
AnimCategory.AddCustomRow(PreviewControllerProperty->GetPropertyDisplayName())
.NameContent()
[
PreviewControllerProperty->CreatePropertyNameWidget()
]
.ValueContent()
.MinDesiredWidth(200.0f)
[
SNew(SComboBox<TSharedPtr<FPersonaModeComboEntry>>)
.OptionsSource(&ControllerItems)
.OnGenerateWidget(this, &FPreviewSceneDescriptionCustomization::MakeControllerComboEntryWidget)
.OnSelectionChanged(this, &FPreviewSceneDescriptionCustomization::OnComboSelectionChanged)
[
SNew(STextBlock)
.Text(this, &FPreviewSceneDescriptionCustomization::GetCurrentPreviewControllerText)
]
];
TSharedPtr<FAnimationEditorPreviewScene> PreviewScenePtr = PreviewScene.Pin();
UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = PreviewScenePtr->GetPreviewSceneDescription();
FSimpleDelegate PropertyChangedDelegate = FSimpleDelegate::CreateSP(this, &FPreviewSceneDescriptionCustomization::HandlePreviewControllerPropertyChanged);
for (const FProperty* TestProperty : TFieldRange<FProperty>(PersonaPreviewSceneDescription->PreviewControllerInstance->GetClass()))
{
if (TestProperty->HasAnyPropertyFlags(CPF_Edit))
{
const bool bAdvancedDisplay = TestProperty->HasAnyPropertyFlags(CPF_AdvancedDisplay);
const EPropertyLocation::Type PropertyLocation = bAdvancedDisplay ? EPropertyLocation::Advanced : EPropertyLocation::Common;
IDetailPropertyRow* NewRow = PersonaPreviewSceneDescription->PreviewControllerInstance->AddPreviewControllerPropertyToDetails(PersonaToolkit.Pin().ToSharedRef(), DetailBuilder, AnimCategory, TestProperty, PropertyLocation);
if (NewRow)
{
NewRow->GetPropertyHandle()->SetOnPropertyValueChanged(PropertyChangedDelegate);
}
}
}
// if mesh editor, we hide preview mesh section and additional mesh section
// sometimes additional meshes are interfering with preview mesh, it is not a great experience
const bool bMeshEditor = PersonaToolkit.Pin()->GetContext() == USkeletalMesh::StaticClass()->GetFName();
if (!bMeshEditor)
{
FText PreviewMeshName;
if (PersonaToolkit.Pin()->GetContext() == UAnimationAsset::StaticClass()->GetFName())
{
PreviewMeshName = FText::Format(LOCTEXT("PreviewMeshAnimation", "{0}\n(Animation)"), SkeletalMeshProperty->GetPropertyDisplayName());
}
else if(PersonaToolkit.Pin()->GetContext() == UAnimBlueprint::StaticClass()->GetFName())
{
PreviewMeshName = FText::Format(LOCTEXT("PreviewMeshAnimBlueprint", "{0}\n(Animation Blueprint)"), SkeletalMeshProperty->GetPropertyDisplayName());
}
else if(PersonaToolkit.Pin()->GetContext() == UPhysicsAsset::StaticClass()->GetFName())
{
PreviewMeshName = FText::Format(LOCTEXT("PreviewMeshPhysicsAsset", "{0}\n(Physics Asset)"), SkeletalMeshProperty->GetPropertyDisplayName());
}
else if(PersonaToolkit.Pin()->GetContext() == USkeleton::StaticClass()->GetFName())
{
PreviewMeshName = FText::Format(LOCTEXT("PreviewMeshSkeleton", "{0}\n(Skeleton)"), SkeletalMeshProperty->GetPropertyDisplayName());
}
else
{
PreviewMeshName = SkeletalMeshProperty->GetPropertyDisplayName();
}
const bool bCanUseDifferentSkeleton =
(PersonaToolkit.Pin()->GetContext() == UPhysicsAsset::StaticClass()->GetFName()) ||
(PersonaToolkit.Pin()->GetContext() == TEXT("ControlRigBlueprint"));
DetailBuilder.EditCategory("Mesh")
.AddProperty(SkeletalMeshProperty)
.CustomWidget()
.OverrideResetToDefault(FResetToDefaultOverride::Create(
TAttribute<bool>::CreateLambda([this]()
{
if (PreviewScene.IsValid())
{
return PreviewScene.Pin()->GetPreviewMesh() != nullptr;
}
return false;
}),
FSimpleDelegate::CreateLambda([this]()
{
if (PreviewScene.IsValid())
{
PreviewScene.Pin()->SetPreviewMesh(nullptr, false);
}
}))
)
.NameContent()
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
[
SkeletalMeshProperty->CreatePropertyNameWidget(PreviewMeshName)
]
+SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Center)
[
SNew(SButton)
.Text(LOCTEXT("ApplyToAsset", "Apply To Asset"))
.ToolTipText(LOCTEXT("ApplyToAssetToolTip", "The preview mesh has changed, but it will not be able to be saved until it is applied to the asset. Click here to make the change to the preview mesh persistent."))
.Visibility_Lambda([this]()
{
if (PersonaToolkit.IsValid() && PreviewScene.IsValid())
{
const TSharedPtr<IPersonaToolkit> PinnedPersonaToolkit = PersonaToolkit.Pin();
USkeletalMesh* SkeletalMesh = PinnedPersonaToolkit->GetPreviewMesh();
return (SkeletalMesh != PinnedPersonaToolkit->GetPreviewScene()->GetPreviewMesh()) ? EVisibility::Visible : EVisibility::Collapsed;
}
return EVisibility::Collapsed;
})
.OnClicked_Lambda([this]()
{
TSharedPtr<IPersonaToolkit> PinnedPersonaToolkit = PersonaToolkit.Pin();
PinnedPersonaToolkit->SetPreviewMesh(PinnedPersonaToolkit->GetPreviewScene()->GetPreviewMesh(), true);
return FReply::Handled();
})
]
]
.ValueContent()
.MaxDesiredWidth(250.0f)
.MinDesiredWidth(250.0f)
[
SNew(SObjectPropertyEntryBox)
.AllowedClass(USkeletalMesh::StaticClass())
.PropertyHandle(SkeletalMeshProperty)
.OnShouldFilterAsset(this, &FPreviewSceneDescriptionCustomization::HandleShouldFilterAsset, USkeletalMesh::GetSkeletonMemberName(), bCanUseDifferentSkeleton)
.OnObjectChanged(this, &FPreviewSceneDescriptionCustomization::HandleMeshChanged)
.ThumbnailPool(DetailBuilder.GetThumbnailPool())
];
// Customize animation blueprint preview
TSharedRef<IPropertyHandle> PreviewAnimationBlueprintProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPersonaPreviewSceneDescription, PreviewAnimationBlueprint));
TSharedRef<IPropertyHandle> ApplicationMethodProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPersonaPreviewSceneDescription, ApplicationMethod));
TSharedRef<IPropertyHandle> LinkedAnimGraphTagProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPersonaPreviewSceneDescription, LinkedAnimGraphTag));
if (PersonaToolkit.Pin()->GetContext() == UAnimBlueprint::StaticClass()->GetFName())
{
DetailBuilder.EditCategory("Animation Blueprint")
.AddProperty(PreviewAnimationBlueprintProperty)
.CustomWidget()
.NameContent()
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
[
PreviewAnimationBlueprintProperty->CreatePropertyNameWidget()
]
]
.ValueContent()
.MaxDesiredWidth(250.0f)
.MinDesiredWidth(250.0f)
[
SNew(SObjectPropertyEntryBox)
.AllowedClass(UAnimBlueprint::StaticClass())
.PropertyHandle(PreviewAnimationBlueprintProperty)
.OnShouldFilterAsset(this, &FPreviewSceneDescriptionCustomization::HandleShouldFilterAsset, FName("TargetSkeleton"), false)
.OnObjectChanged(this, &FPreviewSceneDescriptionCustomization::HandlePreviewAnimBlueprintChanged)
.ThumbnailPool(DetailBuilder.GetThumbnailPool())
];
ApplicationMethodProperty->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([this]()
{
FScopedTransaction Transaction(LOCTEXT("SetAnimationBlueprintApplicationMethod", "Set Application Method"));
TSharedPtr<IPersonaToolkit> PinnedPersonaToolkit = PersonaToolkit.Pin();
TSharedRef<FAnimationEditorPreviewScene> LocalPreviewScene = StaticCastSharedRef<FAnimationEditorPreviewScene>(PinnedPersonaToolkit->GetPreviewScene());
UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = LocalPreviewScene->GetPreviewSceneDescription();
PinnedPersonaToolkit->GetAnimBlueprint()->SetPreviewAnimationBlueprintApplicationMethod(PersonaPreviewSceneDescription->ApplicationMethod);
LocalPreviewScene->SetPreviewAnimationBlueprint(PersonaPreviewSceneDescription->PreviewAnimationBlueprint.Get(), PinnedPersonaToolkit->GetAnimBlueprint());
}));
DetailBuilder.EditCategory("Animation Blueprint")
.AddProperty(ApplicationMethodProperty)
.IsEnabled(MakeAttributeLambda([this]()
{
TSharedPtr<IPersonaToolkit> PinnedPersonaToolkit = PersonaToolkit.Pin();
TSharedRef<FAnimationEditorPreviewScene> LocalPreviewScene = StaticCastSharedRef<FAnimationEditorPreviewScene>(PinnedPersonaToolkit->GetPreviewScene());
UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = LocalPreviewScene->GetPreviewSceneDescription();
return PersonaPreviewSceneDescription->PreviewAnimationBlueprint.IsValid();
}));
LinkedAnimGraphTagProperty->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([this]()
{
FScopedTransaction Transaction(LOCTEXT("SetAnimationBlueprintTag", "Set Linked Anim Graph Tag"));
TSharedPtr<IPersonaToolkit> PinnedPersonaToolkit = PersonaToolkit.Pin();
TSharedRef<FAnimationEditorPreviewScene> LocalPreviewScene = StaticCastSharedRef<FAnimationEditorPreviewScene>(PinnedPersonaToolkit->GetPreviewScene());
UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = LocalPreviewScene->GetPreviewSceneDescription();
PinnedPersonaToolkit->GetAnimBlueprint()->SetPreviewAnimationBlueprintTag(PersonaPreviewSceneDescription->LinkedAnimGraphTag);
LocalPreviewScene->SetPreviewAnimationBlueprint(PersonaPreviewSceneDescription->PreviewAnimationBlueprint.Get(), PinnedPersonaToolkit->GetAnimBlueprint());
}));
DetailBuilder.EditCategory("Animation Blueprint")
.AddProperty(LinkedAnimGraphTagProperty)
.IsEnabled(MakeAttributeLambda([this]()
{
TSharedPtr<IPersonaToolkit> PinnedPersonaToolkit = PersonaToolkit.Pin();
TSharedRef<FAnimationEditorPreviewScene> LocalPreviewScene = StaticCastSharedRef<FAnimationEditorPreviewScene>(PinnedPersonaToolkit->GetPreviewScene());
UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = LocalPreviewScene->GetPreviewSceneDescription();
return PersonaPreviewSceneDescription->PreviewAnimationBlueprint.IsValid() && PersonaPreviewSceneDescription->ApplicationMethod == EPreviewAnimationBlueprintApplicationMethod::LinkedAnimGraph;
}));
}
else
{
PreviewAnimationBlueprintProperty->MarkHiddenByCustomization();
ApplicationMethodProperty->MarkHiddenByCustomization();
LinkedAnimGraphTagProperty->MarkHiddenByCustomization();
}
#if CHAOS_SIMULATION_DETAIL_VIEW_FACTORY_SELECTOR
// Physics settings
ClothSimulationFactoryList.Reset();
const TArray<IClothingSimulationFactoryClassProvider*> ClassProviders = IModularFeatures::Get().GetModularFeatureImplementations<IClothingSimulationFactoryClassProvider>(IClothingSimulationFactoryClassProvider::FeatureName);
for (const auto& ClassProvider : ClassProviders)
{
// Populate cloth factory list
ClothSimulationFactoryList.Add(MakeShared<TSubclassOf<class UClothingSimulationFactory>>(ClassProvider->GetClothingSimulationFactoryClass()));
}
DetailBuilder.EditCategory("Physics")
.AddCustomRow(LOCTEXT("PhysicsClothingSimulationFactory", "Clothing Simulation Factory Option"))
.NameContent()
[
SNew(STextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(LOCTEXT("PhysicsClothingSimulationFactory_Text", "Clothing Simulation Factory"))
.ToolTipText(LOCTEXT("PhysicsClothingSimulationFactory_ToolTip", "Select the cloth simulation used to preview the scene."))
]
.ValueContent()
.MinDesiredWidth(200.0f)
[
SNew(SComboBox<TSharedPtr<TSubclassOf<class UClothingSimulationFactory>>>)
.OptionsSource(&ClothSimulationFactoryList)
.OnGenerateWidget(this, &FPreviewSceneDescriptionCustomization::MakeClothingSimulationFactoryWidget)
.OnSelectionChanged(this, &FPreviewSceneDescriptionCustomization::OnClothingSimulationFactorySelectionChanged)
[
SNew(STextBlock)
.Text(this, &FPreviewSceneDescriptionCustomization::GetCurrentClothingSimulationFactoryText)
]
];
#endif // #if CHAOS_SIMULATION_DETAIL_VIEW_FACTORY_SELECTOR
// set the skeleton to use in our factory as we shouldn't be picking one here
FactoryToUse->CurrentSkeleton = EditableSkeleton.IsValid() ? MakeWeakObjectPtr(const_cast<USkeleton*>(&EditableSkeleton.Pin()->GetSkeleton())) : nullptr;
TArray<UFactory*> FactoriesToUse({ FactoryToUse });
// bAllowPreviewMeshCollectionsToSelectFromDifferentSkeletons option
DetailBuilder.EditCategory("Additional Meshes")
.AddCustomRow(LOCTEXT("AdditionalMeshOption", "Additional Mesh Selection Option"))
.NameContent()
[
SNew(STextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(LOCTEXT("AdditionalMeshSelectionFromDifferentSkeletons", "Allow Different Skeletons"))
.ToolTipText(LOCTEXT("AdditionalMeshSelectionFromDifferentSkeletons_ToolTip", "When selecting additional mesh, whether or not filter by the current skeleton."))
]
.ValueContent()
[
SNew(SCheckBox)
.IsChecked(this, &FPreviewSceneDescriptionCustomization::HandleAllowDifferentSkeletonsIsChecked)
.OnCheckStateChanged(this, &FPreviewSceneDescriptionCustomization::HandleAllowDifferentSkeletonsCheckedStateChanged)
];
// bAllowPreviewMeshCollectionsToSelectFromDifferentSkeletons option
DetailBuilder.EditCategory("Additional Meshes")
.AddCustomRow(LOCTEXT("AdditionalMeshOption_AnimBP", "Additional Mesh Anim Selection Option"))
.NameContent()
[
SNew(STextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(LOCTEXT("UseCustomAnimBP", "Allow Custom AnimBP Override"))
.ToolTipText(LOCTEXT("UseCustomAnimBP_ToolTip", "When using preview collection, allow it to override custom AnimBP also."))
]
.ValueContent()
[
SNew(SCheckBox)
.IsChecked(this, &FPreviewSceneDescriptionCustomization::HandleUseCustomAnimBPIsChecked)
.OnCheckStateChanged(this, &FPreviewSceneDescriptionCustomization::HandleUseCustomAnimBPCheckedStateChanged)
];
FResetToDefaultOverride ResetToDefaultOverride = FResetToDefaultOverride::Create(
FIsResetToDefaultVisible::CreateSP(this, &FPreviewSceneDescriptionCustomization::GetReplaceVisibility),
FResetToDefaultHandler::CreateSP(this, &FPreviewSceneDescriptionCustomization::OnResetToBaseClicked)
);
DetailBuilder.EditCategory("Additional Meshes")
.AddProperty(AdditionalMeshesProperty)
.CustomWidget()
.OverrideResetToDefault(ResetToDefaultOverride)
.NameContent()
[
AdditionalMeshesProperty->CreatePropertyNameWidget()
]
.ValueContent()
.MaxDesiredWidth(250.0f)
.MinDesiredWidth(250.0f)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SObjectPropertyEntryBox)
// searching uobject is too much for a scale of Fortnite
// for now we just allow UDataAsset
.AllowedClass(UDataAsset::StaticClass())
.PropertyHandle(AdditionalMeshesProperty)
.OnShouldFilterAsset(this, &FPreviewSceneDescriptionCustomization::HandleShouldFilterAdditionalMesh, true)
.OnObjectChanged(this, &FPreviewSceneDescriptionCustomization::HandleAdditionalMeshesChanged, &DetailBuilder)
.ThumbnailPool(DetailBuilder.GetThumbnailPool())
.NewAssetFactories(FactoriesToUse)
]
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
.Padding(2.0f)
[
SNew(SButton)
.Visibility(this, &FPreviewSceneDescriptionCustomization::GetSaveButtonVisibility, AdditionalMeshesProperty.ToSharedRef())
.ButtonStyle(FAppStyle::Get(), "HoverHintOnly")
.OnClicked(this, &FPreviewSceneDescriptionCustomization::OnSaveCollectionClicked, AdditionalMeshesProperty.ToSharedRef(), &DetailBuilder)
.ContentPadding(4.0f)
.ForegroundColor(FSlateColor::UseForeground())
[
SNew(SImage)
.Image(FAppStyle::GetBrush("Persona.SavePreviewMeshCollection"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
]
];
FAssetData AdditionalMeshesAsset;
AdditionalMeshesProperty->GetValue(AdditionalMeshesAsset);
if (AdditionalMeshesAsset.IsValid())
{
TArray<UObject*> Objects;
Objects.Add(AdditionalMeshesAsset.GetAsset());
IDetailPropertyRow* PropertyRow = DetailBuilder.EditCategory("Additional Meshes")
.AddExternalObjectProperty(Objects, "SkeletalMeshes");
if (PropertyRow)
{
PropertyRow->ShouldAutoExpand(true);
}
}
}
else
{
DetailBuilder.HideProperty(SkeletalMeshProperty);
DetailBuilder.HideProperty(AdditionalMeshesProperty);
}
TSharedPtr<IPersonaToolkit> Toolkit = PersonaToolkit.Pin();
if (Toolkit && Toolkit->GetAnimBlueprint())
{
Toolkit->GetAnimBlueprint()->OnCompiled().AddSP(this, &FPreviewSceneDescriptionCustomization::HandleAnimBlueprintCompiled);
}
}
EVisibility FPreviewSceneDescriptionCustomization::GetSaveButtonVisibility(TSharedRef<IPropertyHandle> InAdditionalMeshesProperty) const
{
FAssetData AdditionalMeshesAsset;
InAdditionalMeshesProperty->GetValue(AdditionalMeshesAsset);
UObject* Object = AdditionalMeshesAsset.GetAsset();
return Object == nullptr || !Object->HasAnyFlags(RF_Transient) ? EVisibility::Collapsed : EVisibility::Visible;
}
FReply FPreviewSceneDescriptionCustomization::OnSaveCollectionClicked(TSharedRef<IPropertyHandle> InAdditionalMeshesProperty, IDetailLayoutBuilder* DetailLayoutBuilder)
{
FAssetData AdditionalMeshesAsset;
InAdditionalMeshesProperty->GetValue(AdditionalMeshesAsset);
UPreviewMeshCollection* DefaultPreviewMeshCollection = CastChecked<UPreviewMeshCollection>(AdditionalMeshesAsset.GetAsset());
if (DefaultPreviewMeshCollection)
{
IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
UPreviewMeshCollection* NewPreviewMeshCollection = Cast<UPreviewMeshCollection>(AssetTools.CreateAssetWithDialog(UPreviewMeshCollection::StaticClass(), FactoryToUse));
if (NewPreviewMeshCollection)
{
NewPreviewMeshCollection->Skeleton = DefaultPreviewMeshCollection->Skeleton;
NewPreviewMeshCollection->SkeletalMeshes = DefaultPreviewMeshCollection->SkeletalMeshes;
InAdditionalMeshesProperty->SetValue(FAssetData(NewPreviewMeshCollection));
PreviewScene.Pin()->SetAdditionalMeshes(NewPreviewMeshCollection);
DetailLayoutBuilder->ForceRefreshDetails();
}
}
return FReply::Handled();
}
bool FPreviewSceneDescriptionCustomization::HandleShouldFilterAdditionalMesh(const FAssetData& InAssetData, bool bCanUseDifferentSkeleton)
{
// see if it's in valid class set
bool bValidClass = false;
// first to see if it's allowed class
for (FTopLevelAssetPath ClassName: AvailableClassNameList)
{
if (ClassName == InAssetData.AssetClassPath)
{
bValidClass = true;
break;
}
}
// not valid class, filter it
if (!bValidClass)
{
return true;
}
return HandleShouldFilterAsset(InAssetData, USkeletalMesh::GetSkeletonMemberName(), bCanUseDifferentSkeleton);
}
bool FPreviewSceneDescriptionCustomization::HandleShouldFilterAsset(const FAssetData& InAssetData, FName InTag, bool bCanUseDifferentSkeleton)
{
if (bCanUseDifferentSkeleton && GetDefault<UPersonaOptions>()->bAllowPreviewMeshCollectionsToSelectFromDifferentSkeletons)
{
return false;
}
const UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = PreviewScene.Pin()->GetPreviewSceneDescription();
if(!PersonaPreviewSceneDescription->PreviewMesh.IsValid())
{
return false;
}
const USkeleton* Skeleton = PersonaPreviewSceneDescription->PreviewMesh->GetSkeleton();
const FString SkeletonTag = InAssetData.GetTagValueRef<FString>(InTag);
if (Skeleton && Skeleton->IsCompatibleSkeletonByAssetString(SkeletonTag))
{
return false;
}
return true;
}
FText FPreviewSceneDescriptionCustomization::GetCurrentPreviewControllerText() const
{
UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = PreviewScene.Pin()->GetPreviewSceneDescription();
return PersonaPreviewSceneDescription->PreviewController->GetDisplayNameText();
}
TSharedRef<SWidget> FPreviewSceneDescriptionCustomization::MakeControllerComboEntryWidget(TSharedPtr<FPersonaModeComboEntry> InItem) const
{
return
SNew(STextBlock)
.Text(InItem->Text);
}
void FPreviewSceneDescriptionCustomization::OnComboSelectionChanged(TSharedPtr<FPersonaModeComboEntry> InSelectedItem, ESelectInfo::Type SelectInfo)
{
TSharedPtr<FAnimationEditorPreviewScene> PreviewScenePtr = PreviewScene.Pin();
UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = PreviewScenePtr->GetPreviewSceneDescription();
PersonaPreviewSceneDescription->SetPreviewController(InSelectedItem->Class, PreviewScenePtr.Get());
MyDetailLayout->ForceRefreshDetails();
}
void FPreviewSceneDescriptionCustomization::HandlePreviewControllerPropertyChanged()
{
ReinitializePreviewController();
}
void FPreviewSceneDescriptionCustomization::HandleMeshChanged(const FAssetData& InAssetData)
{
USkeletalMesh* NewPreviewMesh = Cast<USkeletalMesh>(InAssetData.GetAsset());
PersonaToolkit.Pin()->SetPreviewMesh(NewPreviewMesh, false);
}
void FPreviewSceneDescriptionCustomization::HandlePreviewAnimBlueprintChanged(const FAssetData& InAssetData)
{
UAnimBlueprint* NewAnimBlueprint = Cast<UAnimBlueprint>(InAssetData.GetAsset());
PersonaToolkit.Pin()->SetPreviewAnimationBlueprint(NewAnimBlueprint);
}
void FPreviewSceneDescriptionCustomization::HandleAdditionalMeshesChanged(const FAssetData& InAssetData, IDetailLayoutBuilder* DetailLayoutBuilder)
{
UDataAsset* MeshCollection = Cast<UDataAsset>(InAssetData.GetAsset());
if (!MeshCollection || MeshCollection->GetClass()->ImplementsInterface(UPreviewCollectionInterface::StaticClass()))
{
PreviewScene.Pin()->SetAdditionalMeshes(MeshCollection);
}
DataAssetToDisplay = MeshCollection;
DetailLayoutBuilder->ForceRefreshDetails();
}
void FPreviewSceneDescriptionCustomization::HandleAllowDifferentSkeletonsCheckedStateChanged(ECheckBoxState CheckState)
{
GetMutableDefault<UPersonaOptions>()->bAllowPreviewMeshCollectionsToSelectFromDifferentSkeletons = (CheckState == ECheckBoxState::Checked);
}
ECheckBoxState FPreviewSceneDescriptionCustomization::HandleAllowDifferentSkeletonsIsChecked() const
{
return GetDefault<UPersonaOptions>()->bAllowPreviewMeshCollectionsToSelectFromDifferentSkeletons? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
void FPreviewSceneDescriptionCustomization::HandleUseCustomAnimBPCheckedStateChanged(ECheckBoxState CheckState)
{
GetMutableDefault<UPersonaOptions>()->bAllowPreviewMeshCollectionsToUseCustomAnimBP = (CheckState == ECheckBoxState::Checked);
if (PreviewScene.IsValid())
{
PreviewScene.Pin()->RefreshAdditionalMeshes(false);
}
}
ECheckBoxState FPreviewSceneDescriptionCustomization::HandleUseCustomAnimBPIsChecked() const
{
return GetDefault<UPersonaOptions>()->bAllowPreviewMeshCollectionsToUseCustomAnimBP? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
void FPreviewSceneDescriptionCustomization::HandleAnimBlueprintCompiled(UBlueprint*)
{
// Only re-initialize controller if we are not debugging an external instance.
// If we switch at this point then we will disconnect from the external instance
TSharedPtr<FAnimationEditorPreviewScene> PreviewScenePtr = PreviewScene.Pin();
if(PreviewScenePtr->GetPreviewMeshComponent()->PreviewInstance == nullptr || PreviewScenePtr->GetPreviewMeshComponent()->PreviewInstance->GetDebugSkeletalMeshComponent() == nullptr)
{
ReinitializePreviewController();
}
}
void FPreviewSceneDescriptionCustomization::ReinitializePreviewController()
{
TSharedPtr<FAnimationEditorPreviewScene> PreviewScenePtr = PreviewScene.Pin();
UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = PreviewScenePtr->GetPreviewSceneDescription();
PersonaPreviewSceneDescription->PreviewControllerInstance->UninitializeView(PersonaPreviewSceneDescription, PreviewScenePtr.Get());
PersonaPreviewSceneDescription->PreviewControllerInstance->InitializeView(PersonaPreviewSceneDescription, PreviewScenePtr.Get());
}
bool FPreviewSceneDescriptionCustomization::GetReplaceVisibility(TSharedPtr<IPropertyHandle> PropertyHandle) const
{
// Only show the replace button if the current material can be replaced
if (AdditionalMeshesProperty.IsValid())
{
FAssetData AdditionalMeshesAsset;
AdditionalMeshesProperty->GetValue(AdditionalMeshesAsset);
return AdditionalMeshesAsset.IsValid();
}
return false;
}
/**
* Called when reset to base is clicked
*/
void FPreviewSceneDescriptionCustomization::OnResetToBaseClicked(TSharedPtr<IPropertyHandle> PropertyHandle)
{
// Only allow reset to base if the current material can be replaced
if (AdditionalMeshesProperty.IsValid())
{
FAssetData NullAsset;
AdditionalMeshesProperty->SetValue(NullAsset);
PreviewScene.Pin()->SetAdditionalMeshes(nullptr);
}
}
void FPreviewSceneDescriptionCustomization::OnResetAdditionalMeshes()
{
// this function resets the additional meshes property to null,
// in the future if we serialize the default setting, this will
// need to reset it to the default value, not just null.
// Only allow reset to base if the current material can be replaced
if (AdditionalMeshesProperty.IsValid())
{
FAssetData NullAsset;
AdditionalMeshesProperty->SetValue(NullAsset);
PreviewScene.Pin()->SetAdditionalMeshes(nullptr);
}
MyDetailLayout->ForceRefreshDetails();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// FPreviewMeshCollectionEntryCustomization
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FPreviewMeshCollectionEntryCustomization::CustomizeHeader(TSharedRef<IPropertyHandle> PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils)
{
// get the enclosing preview mesh collection to determine the skeleton we want
TArray<UObject*> OuterObjects;
PropertyHandle->GetOuterObjects(OuterObjects);
check(OuterObjects.Num() > 0);
if (OuterObjects[0] != nullptr)
{
FString SkeletonName = FAssetData(CastChecked<UPreviewMeshCollection>(OuterObjects[0])->Skeleton).GetExportTextName();
USkeleton* Skeleton = CastChecked<UPreviewMeshCollection>(OuterObjects[0])->Skeleton;
PropertyHandle->GetParentHandle()->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FPreviewMeshCollectionEntryCustomization::HandleMeshesArrayChanged, CustomizationUtils.GetPropertyUtilities()));
TSharedPtr<IPropertyHandle> SkeletalMeshProperty = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FPreviewMeshCollectionEntry, SkeletalMesh));
if (SkeletalMeshProperty.IsValid())
{
HeaderRow.NameContent()
[
SkeletalMeshProperty->CreatePropertyNameWidget()
]
.ValueContent()
.MaxDesiredWidth(250.0f)
.MinDesiredWidth(250.0f)
[
SNew(SObjectPropertyEntryBox)
.AllowedClass(USkeletalMesh::StaticClass())
.PropertyHandle(SkeletalMeshProperty)
.OnShouldFilterAsset(this, &FPreviewMeshCollectionEntryCustomization::HandleShouldFilterAsset, SkeletonName, Skeleton)
.OnObjectChanged(this, &FPreviewMeshCollectionEntryCustomization::HandleMeshChanged)
.ThumbnailPool(CustomizationUtils.GetThumbnailPool())
];
}
}
}
bool FPreviewMeshCollectionEntryCustomization::HandleShouldFilterAsset(const FAssetData& InAssetData, FString SkeletonName, USkeleton* Skeleton)
{
if (GetDefault<UPersonaOptions>()->bAllowPreviewMeshCollectionsToSelectFromDifferentSkeletons)
{
return false;
}
if (Skeleton && Skeleton->IsCompatibleSkeletonByAssetData(InAssetData))
{
return false;
}
return true;
}
void FPreviewMeshCollectionEntryCustomization::HandleMeshChanged(const FAssetData& InAssetData)
{
if (PreviewScene.IsValid())
{
// if mesh changes, don't override base mesh
PreviewScene.Pin()->RefreshAdditionalMeshes(false);
}
}
void FPreviewMeshCollectionEntryCustomization::HandleMeshesArrayChanged(TSharedPtr<IPropertyUtilities> PropertyUtilities)
{
if (PreviewScene.IsValid())
{
// if additional mesh changes, allow it to override
PreviewScene.Pin()->RefreshAdditionalMeshes(true);
if (PropertyUtilities.IsValid())
{
PropertyUtilities->ForceRefresh();
}
}
}
#if CHAOS_SIMULATION_DETAIL_VIEW_FACTORY_SELECTOR
TSharedRef<SWidget> FPreviewSceneDescriptionCustomization::MakeClothingSimulationFactoryWidget(TSharedPtr<TSubclassOf<class UClothingSimulationFactory>> Item) const
{
return SNew(STextBlock)
.Text(*Item ? FText::FromName((*Item)->GetFName()) : LOCTEXT("PhysicsClothingSimulationFactory_NoneSelected", "None"))
.Font(IDetailLayoutBuilder::GetDetailFont());
}
void FPreviewSceneDescriptionCustomization::OnClothingSimulationFactorySelectionChanged(TSharedPtr<TSubclassOf<class UClothingSimulationFactory>> Item, ESelectInfo::Type SelectInfo) const
{
// Set new factory to the preview mesh component:
if (const TSharedPtr<IPersonaToolkit> PersonaToolkitPin = PersonaToolkit.Pin())
{
if (UDebugSkelMeshComponent* const DebugSkelMeshComponent = PersonaToolkitPin->GetPreviewMeshComponent())
{
DebugSkelMeshComponent->UnregisterComponent();
DebugSkelMeshComponent->ClothingSimulationFactory = *Item;
DebugSkelMeshComponent->RegisterComponent();
}
}
}
FText FPreviewSceneDescriptionCustomization::GetCurrentClothingSimulationFactoryText() const
{
TSubclassOf<class UClothingSimulationFactory> Item;
if (const TSharedPtr<IPersonaToolkit> PersonaToolkitPin = PersonaToolkit.Pin())
{
if (const UDebugSkelMeshComponent* const DebugSkelMeshComponent = PersonaToolkitPin->GetPreviewMeshComponent())
{
Item = DebugSkelMeshComponent->ClothingSimulationFactory;
}
}
return *Item ? FText::FromName((*Item)->GetFName()) : LOCTEXT("PhysicsClothingSimulationFactory_NoneSelected", "None");
}
#endif // #if CHAOS_SIMULATION_DETAIL_VIEW_FACTORY_SELECTOR
#undef LOCTEXT_NAMESPACE