Files
UnrealEngineUWP/Engine/Source/Editor/Persona/Private/PreviewSceneCustomizations.cpp
lucas dower 89f2dc33fa [UEFN] Misc AnimBP editor permission changes
* Allow BlueprintInitializeAnimation function override
* Added missing UseCachedPose node to match SaveCachedPose
* Hide bReceiveNotifiesFromLinkedInstances and bPropagateNotifiesToLinkedInstances as notifies are disabled
* Removed Mirror node until the MirrorDataTable asset is exposed
* Removed the CCDIK node as it is marked as experimental and subject to change
* Removed the AimOffsetLookAt as AimOffset assets are not exposed
* Hide bRunConstructionScriptOnDrag and bRunConstructionScriptInSequencer as UEFN AnimBPs have no construction scripts

#rb ben.jillich
#preflight 64412a049566b614504db9c6

[CL 25129707 by lucas dower in ue5-main branch]
2023-04-20 12:31:58 -04:00

763 lines
29 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"
#include "PersonaModule.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;
// allow customization by client asset editor
PersonaToolkit.Pin()->CustomizeSceneSettings(DetailBuilder);
// name label given to the context of this persona instance (usually the class name of the asset)
const FName PersonaContextName = PersonaToolkit.Pin()->GetContext();
//
// Preview Controller section...
//
{
TSharedRef<IPropertyHandle> PreviewControllerProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPersonaPreviewSceneDescription, PreviewController));
PreviewControllerProperty->MarkHiddenByCustomization();
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));
}
ControllerItems.RemoveAll([](const TSharedPtr<FPersonaModeComboEntry>& ControllerEntry)
{
return !GetMutableDefault<UPersonaOptions>()->IsAllowedClass(ControllerEntry->Class);
});
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)
]
];
// register PropertyValueChanged callbacks for all properties in the preview controller
FSimpleDelegate PropertyChangedDelegate = FSimpleDelegate::CreateSP(this, &FPreviewSceneDescriptionCustomization::HandlePreviewControllerPropertyChanged);
UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = PreviewScene.Pin()->GetPreviewSceneDescription();
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);
}
}
}
}
//
// Preview Mesh section...
//
{
TSharedRef<IPropertyHandle> SkeletalMeshProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPersonaPreviewSceneDescription, PreviewMesh));
SkeletalMeshProperty->MarkHiddenByCustomization();
FText PreviewMeshName;
if (PersonaContextName == UAnimationAsset::StaticClass()->GetFName())
{
PreviewMeshName = FText::Format(LOCTEXT("PreviewMeshAnimation", "{0}\n(Animation)"), SkeletalMeshProperty->GetPropertyDisplayName());
}
else if(PersonaContextName == UAnimBlueprint::StaticClass()->GetFName())
{
PreviewMeshName = FText::Format(LOCTEXT("PreviewMeshAnimBlueprint", "{0}\n(Animation Blueprint)"), SkeletalMeshProperty->GetPropertyDisplayName());
}
else if(PersonaContextName == UPhysicsAsset::StaticClass()->GetFName())
{
PreviewMeshName = FText::Format(LOCTEXT("PreviewMeshPhysicsAsset", "{0}\n(Physics Asset)"), SkeletalMeshProperty->GetPropertyDisplayName());
}
else if(PersonaContextName == USkeleton::StaticClass()->GetFName())
{
PreviewMeshName = FText::Format(LOCTEXT("PreviewMeshSkeleton", "{0}\n(Skeleton)"), SkeletalMeshProperty->GetPropertyDisplayName());
}
else
{
PreviewMeshName = SkeletalMeshProperty->GetPropertyDisplayName();
}
const bool bCanUseDifferentSkeleton = PersonaToolkit.Pin()->CanPreviewMeshUseDifferentSkeleton();
DetailBuilder.EditCategory("Mesh")
.AddProperty(SkeletalMeshProperty)
.CustomWidget()
.OverrideResetToDefault(FResetToDefaultOverride::Create(
TAttribute<bool>::CreateLambda([PreviewSceneWeakPtr = TWeakPtr<class FAnimationEditorPreviewScene>(PreviewScene)]()
{
if (PreviewSceneWeakPtr.IsValid())
{
return PreviewSceneWeakPtr.Pin()->GetPreviewMesh() != nullptr;
}
return false;
}),
FSimpleDelegate::CreateLambda([PreviewSceneWeakPtr = TWeakPtr<class FAnimationEditorPreviewScene>(PreviewScene)]()
{
if (PreviewSceneWeakPtr.IsValid())
{
PreviewSceneWeakPtr.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([PersonaToolkitWeakPtr = TWeakPtr<class IPersonaToolkit>(PersonaToolkit)]()
{
if (PersonaToolkitWeakPtr.IsValid())
{
const TSharedPtr<IPersonaToolkit> PinnedPersonaToolkit = PersonaToolkitWeakPtr.Pin();
USkeletalMesh* SkeletalMesh = PinnedPersonaToolkit->GetPreviewMesh();
return (SkeletalMesh != PinnedPersonaToolkit->GetPreviewScene()->GetPreviewMesh()) ? EVisibility::Visible : EVisibility::Collapsed;
}
return EVisibility::Collapsed;
})
.OnClicked_Lambda([PersonaToolkitWeakPtr = TWeakPtr<class IPersonaToolkit>(PersonaToolkit)]()
{
if (PersonaToolkitWeakPtr.IsValid())
{
TSharedPtr<IPersonaToolkit> PinnedPersonaToolkit = PersonaToolkitWeakPtr.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())
];
}
//
// Physics section...
//
#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"))
.RowTag("PhysicsClothingSimulationFactory")
.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
//
// Additional Meshes section...
//
{
AdditionalMeshesProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPersonaPreviewSceneDescription, AdditionalMeshes));
AdditionalMeshesProperty->SetOnPropertyResetToDefault(FSimpleDelegate::CreateSP(this, &FPreviewSceneDescriptionCustomization::OnResetAdditionalMeshes));
// 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);
}
}
}
}
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;
}
if(!PersonaToolkit.IsValid())
{
return false;
}
const USkeleton* Skeleton = PersonaToolkit.Pin()->GetSkeleton();
const FString SkeletonTag = InAssetData.GetTagValueRef<FString>(InTag);
if (Skeleton && Skeleton->IsCompatibleForEditor(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();
const FScopedTransaction Transaction(LOCTEXT("ChangePreviewSceneController", "Setting Preview Scene Controller"));
UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = PreviewScenePtr->GetPreviewSceneDescription();
PersonaPreviewSceneDescription->Modify();
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::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)
{
const FScopedTransaction Transaction(LOCTEXT("AllowDifferentSkeletons", "Setting Allow Different Skeletons"));
UPersonaOptions* PersonaOptions = GetMutableDefault<UPersonaOptions>();
PersonaOptions->Modify();
PersonaOptions->bAllowPreviewMeshCollectionsToSelectFromDifferentSkeletons = (CheckState == ECheckBoxState::Checked);
}
ECheckBoxState FPreviewSceneDescriptionCustomization::HandleAllowDifferentSkeletonsIsChecked() const
{
return GetDefault<UPersonaOptions>()->bAllowPreviewMeshCollectionsToSelectFromDifferentSkeletons? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
void FPreviewSceneDescriptionCustomization::HandleUseCustomAnimBPCheckedStateChanged(ECheckBoxState CheckState)
{
const FScopedTransaction Transaction(LOCTEXT("AllowDifferentSkeletons", "Setting Allow Different Skeletons"));
UPersonaOptions* PersonaOptions = GetMutableDefault<UPersonaOptions>();
PersonaOptions->Modify();
PersonaOptions->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::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));
TSharedPtr<IPropertyHandle> AnimBlueprintProperty = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FPreviewMeshCollectionEntry, AnimBlueprint));
if (SkeletalMeshProperty.IsValid() && AnimBlueprintProperty.IsValid())
{
HeaderRow.NameContent()
[
SkeletalMeshProperty->CreatePropertyNameWidget()
]
.ValueContent()
.MaxDesiredWidth(250.0f)
.MinDesiredWidth(250.0f)
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
[
SNew(SObjectPropertyEntryBox)
.AllowedClass(USkeletalMesh::StaticClass())
.PropertyHandle(SkeletalMeshProperty)
.OnShouldFilterAsset(this, &FPreviewMeshCollectionEntryCustomization::HandleShouldFilterAsset, SkeletonName, Skeleton)
.OnObjectChanged(this, &FPreviewMeshCollectionEntryCustomization::HandleMeshChanged)
.ThumbnailPool(CustomizationUtils.GetThumbnailPool())
]
+SVerticalBox::Slot()
[
SNew(SObjectPropertyEntryBox)
.AllowedClass(UAnimBlueprint::StaticClass())
.PropertyHandle(AnimBlueprintProperty)
.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->IsCompatibleForEditor(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