// 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" #include "PersonaPreviewSceneSkelMeshInstanceController.h" #define LOCTEXT_NAMESPACE "PreviewSceneCustomizations" // static list that contains available classes, so that we can only allow these classes TArray FPreviewSceneDescriptionCustomization::AvailableClassNameList; FPreviewSceneDescriptionCustomization::FPreviewSceneDescriptionCustomization(const FString& InSkeletonName, const TSharedRef& InPersonaToolkit) : SkeletonName(InSkeletonName) , PersonaToolkit(InPersonaToolkit) , PreviewScene(StaticCastSharedRef(InPersonaToolkit->GetPreviewScene())) , EditableSkeleton(InPersonaToolkit->GetEditableSkeleton()) { // setup custom factory up-front so we can control its lifetime FactoryToUse = NewObject(); FactoryToUse->AddToRoot(); // only first time if (AvailableClassNameList.Num() == 0) { for (TObjectIterator 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 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 PreviewControllerProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPersonaPreviewSceneDescription, PreviewController)); PreviewControllerProperty->MarkHiddenByCustomization(); TArray BuiltInPreviewControllers = { UPersonaPreviewSceneDefaultController::StaticClass(), UPersonaPreviewSceneRefPoseController::StaticClass(), UPersonaPreviewSceneAnimationController::StaticClass(), UPersonaPreviewSceneSkelMeshInstanceController::StaticClass() }; TArray DynamicPreviewControllers; for (TObjectIterator 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(ControllerClass)); } for (UClass* ControllerClass : DynamicPreviewControllers) { ControllerItems.Add(MakeShared(ControllerClass)); } ControllerItems.RemoveAll([](const TSharedPtr& ControllerEntry) { return !GetMutableDefault()->IsAllowedClass(ControllerEntry->Class); }); IDetailCategoryBuilder& AnimCategory = DetailBuilder.EditCategory("Animation"); AnimCategory.AddCustomRow(PreviewControllerProperty->GetPropertyDisplayName()) .NameContent() [ PreviewControllerProperty->CreatePropertyNameWidget() ] .ValueContent() .MinDesiredWidth(200.0f) [ SNew(SComboBox>) .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(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 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::CreateLambda([PreviewSceneWeakPtr = TWeakPtr(PreviewScene)]() { if (PreviewSceneWeakPtr.IsValid()) { return PreviewSceneWeakPtr.Pin()->GetPreviewMesh() != nullptr; } return false; }), FSimpleDelegate::CreateLambda([PreviewSceneWeakPtr = TWeakPtr(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(PersonaToolkit)]() { if (PersonaToolkitWeakPtr.IsValid()) { const TSharedPtr PinnedPersonaToolkit = PersonaToolkitWeakPtr.Pin(); USkeletalMesh* SkeletalMesh = PinnedPersonaToolkit->GetPreviewMesh(); return (SkeletalMesh != PinnedPersonaToolkit->GetPreviewScene()->GetPreviewMesh()) ? EVisibility::Visible : EVisibility::Collapsed; } return EVisibility::Collapsed; }) .OnClicked_Lambda([PersonaToolkitWeakPtr = TWeakPtr(PersonaToolkit)]() { if (PersonaToolkitWeakPtr.IsValid()) { TSharedPtr 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()) ]; } // Customize animation blueprint preview TSharedRef PreviewAnimationBlueprintProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPersonaPreviewSceneDescription, PreviewAnimationBlueprint)); TSharedRef ApplicationMethodProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPersonaPreviewSceneDescription, ApplicationMethod)); TSharedRef 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 PinnedPersonaToolkit = PersonaToolkit.Pin(); TSharedRef LocalPreviewScene = StaticCastSharedRef(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 PinnedPersonaToolkit = PersonaToolkit.Pin(); TSharedRef LocalPreviewScene = StaticCastSharedRef(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 PinnedPersonaToolkit = PersonaToolkit.Pin(); TSharedRef LocalPreviewScene = StaticCastSharedRef(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 PinnedPersonaToolkit = PersonaToolkit.Pin(); TSharedRef LocalPreviewScene = StaticCastSharedRef(PinnedPersonaToolkit->GetPreviewScene()); UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = LocalPreviewScene->GetPreviewSceneDescription(); return PersonaPreviewSceneDescription->PreviewAnimationBlueprint.IsValid() && PersonaPreviewSceneDescription->ApplicationMethod == EPreviewAnimationBlueprintApplicationMethod::LinkedAnimGraph; })); } else { PreviewAnimationBlueprintProperty->MarkHiddenByCustomization(); ApplicationMethodProperty->MarkHiddenByCustomization(); LinkedAnimGraphTagProperty->MarkHiddenByCustomization(); } // // Physics section... // #if CHAOS_SIMULATION_DETAIL_VIEW_FACTORY_SELECTOR // Physics settings ClothSimulationFactoryList.Reset(); const TArray ClassProviders = IModularFeatures::Get().GetModularFeatureImplementations(IClothingSimulationFactoryClassProvider::FeatureName); for (const auto& ClassProvider : ClassProviders) { // Populate cloth factory list ClothSimulationFactoryList.Add(MakeShared>(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>>) .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(&EditableSkeleton.Pin()->GetSkeleton())) : nullptr; TArray 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 Objects; Objects.Add(AdditionalMeshesAsset.GetAsset()); IDetailPropertyRow* PropertyRow = DetailBuilder.EditCategory("Additional Meshes") .AddExternalObjectProperty(Objects, "SkeletalMeshes"); if (PropertyRow) { PropertyRow->ShouldAutoExpand(true); } } } } EVisibility FPreviewSceneDescriptionCustomization::GetSaveButtonVisibility(TSharedRef 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 InAdditionalMeshesProperty, IDetailLayoutBuilder* DetailLayoutBuilder) { FAssetData AdditionalMeshesAsset; InAdditionalMeshesProperty->GetValue(AdditionalMeshesAsset); UPreviewMeshCollection* DefaultPreviewMeshCollection = CastChecked(AdditionalMeshesAsset.GetAsset()); if (DefaultPreviewMeshCollection) { IAssetTools& AssetTools = FModuleManager::GetModuleChecked("AssetTools").Get(); UPreviewMeshCollection* NewPreviewMeshCollection = Cast(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()->bAllowPreviewMeshCollectionsToSelectFromDifferentSkeletons) { return false; } if(!PersonaToolkit.IsValid()) { return false; } const USkeleton* Skeleton = PersonaToolkit.Pin()->GetSkeleton(); const FString SkeletonTag = InAssetData.GetTagValueRef(InTag); if (Skeleton && Skeleton->IsCompatibleForEditor(SkeletonTag)) { return false; } return true; } FText FPreviewSceneDescriptionCustomization::GetCurrentPreviewControllerText() const { UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = PreviewScene.Pin()->GetPreviewSceneDescription(); return PersonaPreviewSceneDescription->PreviewController->GetDisplayNameText(); } TSharedRef FPreviewSceneDescriptionCustomization::MakeControllerComboEntryWidget(TSharedPtr InItem) const { return SNew(STextBlock) .Text(InItem->Text); } void FPreviewSceneDescriptionCustomization::OnComboSelectionChanged(TSharedPtr InSelectedItem, ESelectInfo::Type SelectInfo) { TSharedPtr 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(InAssetData.GetAsset()); PersonaToolkit.Pin()->SetPreviewMesh(NewPreviewMesh, false); } void FPreviewSceneDescriptionCustomization::HandlePreviewAnimBlueprintChanged(const FAssetData& InAssetData) { UAnimBlueprint* NewAnimBlueprint = Cast(InAssetData.GetAsset()); PersonaToolkit.Pin()->SetPreviewAnimationBlueprint(NewAnimBlueprint); } void FPreviewSceneDescriptionCustomization::HandleAnimBlueprintCompiled(UBlueprint* Blueprint) { // 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 const TSharedPtr AnimPreviewScene = PreviewScene.Pin(); if(AnimPreviewScene->GetPreviewMeshComponent()->PreviewInstance == nullptr || AnimPreviewScene->GetPreviewMeshComponent()->PreviewInstance->GetDebugSkeletalMeshComponent() == nullptr) { UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = AnimPreviewScene->GetPreviewSceneDescription(); PersonaPreviewSceneDescription->PreviewControllerInstance->UninitializeView(PersonaPreviewSceneDescription, AnimPreviewScene.Get()); PersonaPreviewSceneDescription->PreviewControllerInstance->InitializeView(PersonaPreviewSceneDescription, AnimPreviewScene.Get()); } } void FPreviewSceneDescriptionCustomization::HandleAdditionalMeshesChanged(const FAssetData& InAssetData, IDetailLayoutBuilder* DetailLayoutBuilder) { UDataAsset* MeshCollection = Cast(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(); PersonaOptions->Modify(); PersonaOptions->bAllowPreviewMeshCollectionsToSelectFromDifferentSkeletons = (CheckState == ECheckBoxState::Checked); } ECheckBoxState FPreviewSceneDescriptionCustomization::HandleAllowDifferentSkeletonsIsChecked() const { return GetDefault()->bAllowPreviewMeshCollectionsToSelectFromDifferentSkeletons? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void FPreviewSceneDescriptionCustomization::HandleUseCustomAnimBPCheckedStateChanged(ECheckBoxState CheckState) { const FScopedTransaction Transaction(LOCTEXT("AllowDifferentSkeletons", "Setting Allow Different Skeletons")); UPersonaOptions* PersonaOptions = GetMutableDefault(); PersonaOptions->Modify(); PersonaOptions->bAllowPreviewMeshCollectionsToUseCustomAnimBP = (CheckState == ECheckBoxState::Checked); if (PreviewScene.IsValid()) { PreviewScene.Pin()->RefreshAdditionalMeshes(false); } } ECheckBoxState FPreviewSceneDescriptionCustomization::HandleUseCustomAnimBPIsChecked() const { return GetDefault()->bAllowPreviewMeshCollectionsToUseCustomAnimBP? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void FPreviewSceneDescriptionCustomization::ReinitializePreviewController() { TSharedPtr PreviewScenePtr = PreviewScene.Pin(); UPersonaPreviewSceneDescription* PersonaPreviewSceneDescription = PreviewScenePtr->GetPreviewSceneDescription(); PersonaPreviewSceneDescription->PreviewControllerInstance->UninitializeView(PersonaPreviewSceneDescription, PreviewScenePtr.Get()); PersonaPreviewSceneDescription->PreviewControllerInstance->InitializeView(PersonaPreviewSceneDescription, PreviewScenePtr.Get()); } bool FPreviewSceneDescriptionCustomization::GetReplaceVisibility(TSharedPtr 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 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 PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) { // get the enclosing preview mesh collection to determine the skeleton we want TArray OuterObjects; PropertyHandle->GetOuterObjects(OuterObjects); check(OuterObjects.Num() > 0); if (OuterObjects[0] != nullptr) { FString SkeletonName = FAssetData(CastChecked(OuterObjects[0])->Skeleton).GetExportTextName(); USkeleton* Skeleton = CastChecked(OuterObjects[0])->Skeleton; PropertyHandle->GetParentHandle()->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FPreviewMeshCollectionEntryCustomization::HandleMeshesArrayChanged, CustomizationUtils.GetPropertyUtilities())); TSharedPtr SkeletalMeshProperty = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FPreviewMeshCollectionEntry, SkeletalMesh)); TSharedPtr 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()->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 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 FPreviewSceneDescriptionCustomization::MakeClothingSimulationFactoryWidget(TSharedPtr> Item) const { return SNew(STextBlock) .Text(*Item ? FText::FromName((*Item)->GetFName()) : LOCTEXT("PhysicsClothingSimulationFactory_NoneSelected", "None")) .Font(IDetailLayoutBuilder::GetDetailFont()); } void FPreviewSceneDescriptionCustomization::OnClothingSimulationFactorySelectionChanged(TSharedPtr> Item, ESelectInfo::Type SelectInfo) const { // Set new factory to the preview mesh component: if (const TSharedPtr PersonaToolkitPin = PersonaToolkit.Pin()) { if (UDebugSkelMeshComponent* const DebugSkelMeshComponent = PersonaToolkitPin->GetPreviewMeshComponent()) { DebugSkelMeshComponent->UnregisterComponent(); DebugSkelMeshComponent->ClothingSimulationFactory = *Item; DebugSkelMeshComponent->RegisterComponent(); } } } FText FPreviewSceneDescriptionCustomization::GetCurrentClothingSimulationFactoryText() const { TSubclassOf Item; if (const TSharedPtr 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